Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/ktonon/cog

Command-line utility that makes it easy to organize a project which uses code generation
https://github.com/ktonon/cog

code-generation

Last synced: about 1 month ago
JSON representation

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

Awesome Lists containing this project

README

        

![cog](http://ktonon.github.com/cog/images/cog-logo-small.png)

[![Build Status](https://secure.travis-ci.org/ktonon/cog.png?branch=master)](https://travis-ci.org/ktonon/cog)
[![Dependency Status](https://gemnasium.com/ktonon/cog.png)](https://gemnasium.com/ktonon/cog)
[![Code Climate](https://codeclimate.com/github/ktonon/cog.png)](https://codeclimate.com/github/ktonon/cog)
[![Gem Version](https://badge.fury.io/rb/cog.svg)](http://badge.fury.io/rb/cog)

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

See also,

* The [API documentation][docs]
* The [change log][changelog]

Table of contents
-----------------

1. [Getting Started](#getting-started) - Install `cog` and prepare a project
1. [Generators](#generators) - Create ruby scripts which generate code
1. [Templates](#templates) - Use ERB templates to help generators
1. [Embeds](#embeds) - Generate code segments into otherwise manually maintained code
1. [Keeps](#keeps) - Preserve manually maintained code segments in a generated file
1. [Plugins](#plugins) - Express your generators using DSLs
1. [Debugging](#debugging) - How to get a full stack trace

Getting Started
---------------

Install the `cog` gem

```bash
$ gem install cog
```

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

```bash
$ 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

```bash
$ 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

```ruby
$ 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

```c
<%= warning %>

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

Then modify `my_generator.rb` like this

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

The generator would be executed like this

```bash
$ cog gen run my_generator
Created src/generated_example.c
```

Listing of `generated_example.c`

```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

```bash
$ 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

```bash
$ 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

```bash
$ 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

```bash
[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

```ruby
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

```text
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

```ruby
@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.

```text
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

```c++
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

```c++
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][DSL] 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

```bash
$ 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

```bash
$ 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

```bash
$ 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`

```bash
$ export GLI_DEBUG=true
```

[gem]:https://rubygems.org/gems/cog
[docs]:http://ktonon.github.com/cog/frames.html
[changelog]:https://github.com/ktonon/cog/blob/master/CHANGELOG.md
[ERB]:http://www.stuartellis.eu/articles/erb/
[DSL]:http://jroller.com/rolsen/entry/building_a_dsl_in_ruby

[Cogfile]:http://ktonon.github.com/cog/Cog/DSL/Cogfile.html
[Generator]:http://ktonon.github.com/cog/Cog/Generator.html
[GeneratorSandbox]:http://ktonon.github.com/cog/Cog/GeneratorSandbox.html
[LanguageDSL]:http://ktonon.github.com/cog/Cog/DSL/LanguageDSL.html

[autoload_plugin]:http://ktonon.github.com/cog/Cog/DSL/Cogfile.html#autoload_plugin-instance_method
[comment]:http://ktonon.github.com/cog/Cog/Generator/Filters.html#comment-instance_method
[embed]:http://ktonon.github.com/cog/Cog/Generator.html#embed-instance_method
[generator_path]:http://ktonon.github.com/cog/Cog/Config.html#generator_path-instance_method
[GLI]:https://github.com/davetron5000/gli
[plugin_path]:http://ktonon.github.com/cog/Cog/Config.html#plugin_path-instance_method
[project_path]:http://ktonon.github.com/cog/Cog/Config/ProjectConfig.html#project_path-instance_method
[project_plugin_path]:http://ktonon.github.com/cog/Cog/Config/ProjectConfig.html#project_plugin_path-instance_method
[stamp]:http://ktonon.github.com/cog/Cog/Generator.html#method-i-stamp
[stamp_generator]:http://ktonon.github.com/cog/Cog/DSL/Cogfile.html#stamp_generator-instance_method
[template_path]:http://ktonon.github.com/cog/Cog/Config.html#template_path-instance_method
[warning]:http://ktonon.github.com/cog/Cog/Generator/LanguageMethods.html#warning-instance_method