Skip to content
/ cog Public

Command-line utility that makes it easy to organize a project which uses code generation

License

Notifications You must be signed in to change notification settings

ktonon/cog

Repository files navigation

cog

Build Status Dependency Status Code Climate Gem Version

cog is a command line utility that makes it easy to organize a project which uses code generation.

See also,

Table of contents

  1. Getting Started - Install cog and prepare a project
  2. Generators - Create ruby scripts which generate code
  3. Templates - Use ERB templates to help generators
  4. Embeds - Generate code segments into otherwise manually maintained code
  5. Keeps - Preserve manually maintained code segments in a generated file
  6. Plugins - Express your generators using DSLs
  7. Debugging - How to get a full stack trace

Getting Started

Install the cog gem

$ gem install cog

Once installed prepare a project for use with cog. Open a terminal in the root directory of your project

$ cog init
Created Cogfile

This will add a Cogfile which configures cog for use with the project. In short, it tells cog where to find generators and templates and where to put generated source code. Open the Cogfile to find out more, each setting is documented. Most settings can be left as-is, but the project_path might need to be changed.

Generators

A generator is a ruby script which resides on the generator_path. A basic generator can be created using the command line tool once a project has been initialized

$ cog generator new my_generator
Created cog/generators/my_generator.rb

my_generator.rb will contain a blank canvas. Generator scripts are evaluated as instances of GeneratorSandbox. The sandbox includes the Generator mixin, which provides an interface for easily generating source code from templates. The stamp method is particularly useful. If finds an ERB template on the template_path and renders it to a file under the project_path. To use the stamp method first create a template

$ cog template new my_generator/example.c
Created cog/templates/my_generator
Created cog/templates/my_generator/example.c.erb

The new template will be empty. Edit it with the following example

<%= warning %> 

void <%= @method_name %>()
{
    // ...
}

Then modify my_generator.rb like this

@method_name = 'example'
stamp 'my_generator/example.c', 'generated_example.c'

The generator would be executed like this

$ cog gen run my_generator
Created src/generated_example.c

Listing of generated_example.c

/*
-------------------------------------------------------------------------------

  WARNING

  This is a generated file. DO NOT EDIT THIS FILE! Your changes will
  be lost the next time this file is regenerated.
   
  This file was generated using cog
  https://github.com/ktonon/cog

-------------------------------------------------------------------------------
 */ 

void example()
{
    // ...
}

Get a list of the generators like this

$ cog gen list
[my_app] my_generator
[cog]    sort

Templates

In the example from the previous section, you may have noticed that the generator method <%= warning %> produced a warning message correctly formatted as a comment. If you look at the implementation of the warning method, you'll see that its just a shortcut for rendering the warning.erb template and passing it through a comment filter.

This warning.erb template comes built-in with cog. You can see a list of all the available templates like this

$ cog template list
[basic]  basic/generator.rb
[cog]    cog/Cogfile
[cog]    cog/plugin/generator.rb.erb
[cog]    cog/plugin/plugin.rb
[my_app] my_generator/example.c
[cog]    warning

Note that the .erb extensions are omitted from the listing. If you don't like the default warning message and want to use a different one you can override it

$ cog tm new warning
Created cog/templates/warning.erb

Listing the templates again would now show that there are two warning.erb templates and that the project version overrides the built-in version

[cog < my_app] warning

Embeds

As shown above, the stamp method can be used to create files which are entirely generated. While this is useful, it might at times be more convenient to inject generated content directly into an otherwise manually maintained file. Such an injection should be automatically updated when the generated content changes, but leave the rest of the file alone. cog provides this kind of functionality through embeds. For example, consider the following generator

1.upto(5).each do |i|
  stamp 'widget.cpp', "widget_#{i}.cpp"
  stamp 'widget.h', "widget_#{i}.h"
end

This generator would add 10 new files to a project. These files would need to be included in the project's build script. It would be tedious to enter them manually. It would make sense for the generator to maintain the list of build files. Depending on the build tool being used, it might be possible to generate a partial build file and include it by reference in the main build file.

Another approach would be to use a embed to inject the build instuctions for the generator into the main build file. For example, consider a Qt project file

SOURCES += main.cpp Donkey.cpp
HEADERS += Donkey.h

# cog: widget-files

The last line is a comment that Qt will ignore, but which cog will recognize as an embed hook named 'widget-files'. Once the hook is in place, it's up to a generator to provide the content which will be injected beneath the hook. Consider again the generator from above, with a few modifications

@widgets = 1.upto(5)
@widgets.each do |i|
  stamp 'widget.cpp', "widget_#{i}.cpp"
  stamp 'widget.h', "widget_#{i}.h"
end

embed 'widget-files' do
  stamp 'widget.pro' # uses the @widgets context and returns a string
end

The embed method takes the name of the hook as an argument. The expansion value is returned by the provided block. In this case a stamp was used to pull the content from a template, but a string could also be constructed in the block without using a template. Running this generator would now inject content beneath the embed directive in the build file.

SOURCES += main.cpp Donkey.cpp
HEADERS += Donkey.h

# cog: widget-files {
SOURCES += widget_1.cpp widget_2.cpp widget_3.cpp widget_4.cpp widget_5.cpp
HEADERS += widget_1.h widget_2.h widget_3.h widget_4.h widget_5.h
# cog: }

Embeds are only updated when the generated content changes. So running the generator a second time would not touch the file.

Keeps

Often the interface of a class will be automatically generated, but the implementation of the methods will need to be manually maintained. In most languages, this could be achieved with an abstract/impl split, where the abstract is generated, and the impl is manually maintained.

With abstract/impl, the compiler will warn about missing or excess methods in the impl, as the abstract changes. But changes to the interface will still have to be manually maintained.

You may prefer to keep manually maintained code inside a generated file. Such code segments should be preserved whenever that file is regenerated. In cog, these code segments are called keeps. Take the following generated file

void MyClass::myMethod(int a, char b)
{
    // keep: MyClass_myMethod_int_char {
    // manually maintained code...
    // keep: }
}

Each keep statement must have a hook, which must be unique within the file in which it is found. In the above example the hook is the part after the opening keep:, that is MyClass_myMethod_int_char. The hook is used to identify the keep statement, in case the generator moves it to a different place in the file with respect to other keep statements. The corresponding generator template would look like this

void MyClass::myMethod(int a, char b)
{
    // keep: MyClass_myMethod_int_char
}

It is important to note that keeps rely on the stamp method.

Plugins

While it is possible to place all code generation logic into a generator script, you might also consider writing a cog plugin.

Very loosely, a plugin should provide

  • a domain specific language in which generator scripts can be written
  • a template for creating generators in that DSL
    • the purpose of the template is to help users of the plugin get started writing a generator

You can tell cog to help you get started writing a plugin. For example, if you wanted to write a command line interface generation tool and call it cons, you would do this

$ cog plugin new cons
Created cog/plugins/cons/Cogfile
Created cog/plugins/cons/lib/cons.rb
Created cog/plugins/cons/templates/cons/generator.rb.erb

When operating in the context of a project, the plugin will be created under the project_plugin_path, and will be available to that project only. Outside the context of a project it would be created under the current working directory. If that directory is not on the plugin_path, then cog will not know how to find it.

If you want to share a plugin between multiple projects, you have a few options.

  • distribute it as a gem
    • make sure to include the Cogfile in the gem
  • create the plugin under your ${HOME}/.cog directory
    • this directory and a user Cogfile are created the first time you run cog init

You can see a list of the available plugins like this

$ cog plugin list
[cog]    basic
[my_app] cons

As noted before, a plugin should contain a template for making generators. In the above example, that is the generator.rb.erb template. The instructions for stamping the generator are in the plugin's Cogfile. You can make a generator for a particular plugin like this

$ cog gen new -p cons my_cons
Created cog/generators/my_cons.rb

Debugging

The command-line interface to cog is provided by GLI. The default error behaviour is a one-line summary. To get a full stack trace set the GLI_DEBUG environment variable to true

$ export GLI_DEBUG=true

About

Command-line utility that makes it easy to organize a project which uses code generation

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published