cog
is a command line utility that makes it easy to organize a project which
uses code generation.
See also,
- The API documentation
- The change log
Table of contents
- Getting Started - Install
cog
and prepare a project - Generators - Create ruby scripts which generate code
- Templates - Use ERB templates to help generators
- Embeds - Generate code segments into otherwise manually maintained code
- Keeps - Preserve manually maintained code segments in a generated file
- Plugins - Express your generators using DSLs
- 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 runcog init
- this directory and a user
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