leitmotif
is a very simple templating tool that generates directories from prototypes stored in git repositories. Its design prioritizes simplicity and a minimal set of external dependencies.
While the leitmotif
tool is still under development and some interesting features are planned for future work, it is already useful for many project-templating tasks. This post will show you how to get started.
Installing Leitmotif
You can install leitmotif
either by using the RubyGem or by using the standalone version, which only depends on Ruby 2 and the Thor gem.
To install the RubyGem, simply run gem install leitmotif
.
To install the standalone script, run the following commands:
gem install thor --version=0.17 && \
curl -O https://github.com/willb/leitmotif/raw/master/extra/standalone/leitmotif && \
chmod 755 leitmotif
In the future, I expect that leitmotif
packages will be available for CentOS and Fedora.
Using Leitmotif
The leitmotif
tool is self-documenting. Run leitmotif help
to see options.
Copying a prototype from a remote repository
Use leitmotif clone URL
to make a local copy of the remote prototype repository at URL
in your local Leitmotif prototype store (under your home directory).
Listing locally-installed prototypes
Use leitmotif list
to see the prototypes you have installed in your local store.
Instantiating prototypes
Use leitmotif generate PROTOTYPE OUTPUT_DIR
to instantiate PROTOTYPE
in OUTPUT_DIR
. In this form, PROTOTYPE
must be the path to a git repository containing a Leitmotif prototype. This command supports the following options:
--local
: treatPROTOTYPE
as the name of a prototype repository in the local store rather than as a path--clobber
: deleteOUTPUT_DIR
before processing the prototype, if it exists--ref
: agit
tag, branch, or SHA to use from the prototype repository (defaults tomaster
if unspecified)--bindings KEY:VALUE ...
: a list of variable bindings to use while instantiating the prototype--verbose
: provide additional output asleitmotif
works
Creating Leitmotif prototypes
A Leitmotif prototype is just a git repository with a particular structure. Specifically, a prototype must have two entries in the repository root directory:
- a YAML file named
.leitmotif
that contains metadata about the prototype, and - a directory named
proto
, which contains the prototype itself.
Prototype metadata
A .leitmotif
file is just a YAML representation of a hash of metadata options. The following keys can appear in a .leitmotif
file:
:name
: the name of the prototype (used for documentation):version
: the version of the prototype (used for documentation):required
: a list of variables that must have user-provided values when the prototype is instantiated:ignore
: a list of files to copy to the instantiated prototype without processing by the templating engine:defaults
: a hash consisting of default values for prototype variables
Here’s an example .leitmotif
file, from a prototype for Spark application development:
---
:name: sparkdev
:version: '0'
:required:
- name
:ignore:
- sbt
:defaults:
:version: '0.0.1'
:scala_version: '2.10.4'
:spark_version: '1.1.0'
Prototypes and templating
With the exception of the files in the :ignore
list, all of the files in a prototype repository are processed by ERB after they’re copied to the output directory. For more details on eRuby, see elsewhere, but here are a few basics to get you started:
- If a template file contains Ruby code surrounded by
<%
and%>
, that code is evaluated at prototype instantiation time with user-supplied variable bindings. - If a template file contains Ruby code surrounded by
<%=
and%>
, that code is evaluated at prototype instantiation time with user-supplied variable bindings and the result of evaluating that code is substituted into the document at that point.
Combining these two, we can see how to use loops in a file:
Specifications of properties are often terser than explicit expected results. For example:
<% 99.downto(1).each do |bottles| %>
* <%= bottles %> bottles of beer on the wall,
<%= bottles %> bottles of beer;
take one down, pass it around,
<%= bottles > 1 ? "#{bottles} bottles" : "just one bottle" %> of beer on the wall
<% end %>
The above will generate a Markdown file containing a bulleted list that will strike terror into the heart of any adult who has ever been on a bus full of middle-schoolers.
Coming soon
I wrote this tool to solve an immediate need1 and will be updating it as new requirements become apparent. However, there are a few things that are already on my roadmap:
- automated test coverage (currently – and shamefully! – there is none)2
- additional commands, e.g., to inspect installed prototypes
- post-instantiation actions, e.g., to rename a file or create a directory based on the result of a variable expansion
Of course, I welcome your feedback, issue reports, and patches as well.