Awesome
helical
Helical is a general purpose code generator. It takes a data model definition and a set of templates (called generators) and generates an output.
Data model definition
It is just a JSON file with no given structure. The structure depends on the templates used. This file can be stored at any location.
Manifest and generators
You need a manifest file and a set of templates. You have to put all of them in the same directory. The templates can be in subdirectories, but
the manifest file must always be in the root of the directory and it must be called helical.json
.
The templating language used in all the templates is nunjucks
The manifest file is a JSON file with two sections:
generators
options
Here there is an example:
{
"generators": [
{
"source": "generators/entity.js",
"path": "models/{{ object.name | lower }}.js",
"foreach": "entities"
},
{
"source": "generators/endpoint.js",
"path": "controllers/{{ ancestors[0].name | lower }}-{{ object.action | lower }}.js",
"foreach": "entities.endpoints"
},
{
"source": "generators/app.js",
"path": "index.js",
"foreach": ""
}
],
"options": [
{
"name": "css",
"describe": "CSS preprocessor",
"type": "string",
"choices": ["less", "sass"]
}
]
}
And this could be a data model suitable of being processed by these generators:
{
"projectName": "project_name",
"entities": [
{
"name": "User",
"fields": [
{
"name": "email",
"type": "string"
},
{
"name": "name",
"type": "string"
}
],
"endpoints": [
{
"action": "show",
"method": "get"
},
{
"action": "create",
"method": "post"
},
{
"action": "edit",
"method": "put"
},
{
"action": "delete",
"method": "delete"
}
]
}
]
}
Generators
Every generator must have at least these attributes: source
, path
and foreach
.
source
: this is the relative path to the template.path
: this is an inline template to generate the output filename.foreach
: tells helical which objects should be processed by this generator. Leave it empty if you only want to execute this template once for the whole data model.
In the example above for the first generator helical will loop over all the entities
in your data model.
And for each entity it will run the entity.js
template. If the data model file contains an entity with { "name": "User" }
then after processing that entity the output will be stored in a file called models/user.js
.
Every template, including the inlined templates to generate the filename (path
attribute), will receive these objects:
object
: the object being processed.ancestors
: an array of ancestor objects.root
: the root of the data model.options
: see sectionoptions
below.
In the example above the second generator will be processed for each endpoint of each entity. Every time the endpoint.js
template
is processed it will receive these objects:
object
: the endpoint being processed.ancestors
: in this exampleancestors[0]
will reference the parent entity of this endpoint.root
: the root of the data model.
Options
Optionally you can define an array of options
in your helical.json
file. These are values that the user can pass in the command line interface
to the generators. Each option has the following attributes:
name
. The name of the option. If you have an option calledcss
in your templates you can useoptions.css
to access the valuedescribe
(optional). A description of this optiontype
(optional). The type of this option. Can bestring
,array
,boolean
,count
orstring
. See the yargs documentation about this.choices
(optional). You can specify an array of allowed values for this option.default
(optional). The default value for this option.
If you specify a not boolean option and there's not a default value and the user didn't specify the option in the command line then the application will exit with a message like this:
Usage: helical --model model.json --generator /path/to/some-generator
Options:
--model, -m The data model file [string] [required]
--generator, -g The directory that contains the helical generator [string] [required]
--output, -o Output directory [string] [required]
--force, -f Override files existing files [boolean]
-h, --help Show help [boolean]
--css CSS preprocessor [string] [required] [choices: "less", "sass"]
Missing required argument: css
Static files
You can create a static
directory and everything on it will be copied to the output directory. This is a good place to put files that are not plain text such
as images, fonts, or simply plain text files that don't need to be processed.
Next steps message
You can render a message after all the files are generated creating a template next-steps.txt
. This template will have access to the root
and options
objects.
Overriding files
By default helical
will not override existing files. But you can use the --force
option to change this behavior.
Watching for file changes
While developing helical generators or just to see interactively how the generators change when editing your data model, you can use the --watch
option in the command line.
Note: files starting with a dot (.
) are ignored.
Error notifications
If you want to get notified when an error occurs processing any file, parsing the model, etc. you can use the --notify
option. Right now it only notifies about errors, but in a future it will notify if the whole process got fixed if you change any file and it was giving errors previously.
# Installation
npm install helical -g
Example
In this repository there is an example in the example
directory. You can run it with:
helical \
--model example/model.json \
--generator example \
--output output \
--css less
This is the output:
Wrote output/models/user.js
Wrote output/controllers/user-show.js
Wrote output/controllers/user-create.js
Wrote output/controllers/user-edit.js
Wrote output/controllers/user-delete.js
Wrote output/index.js
Skipping objects
You can avoid some objects to be processed by returning an empty file name in the path
attribute of a generator. Example:
{
"source": "endpoint.js",
"path": "{% if object.action != 'delete' %}controllers/{{ ancestors[0].name | lower }}-{{ object.action | lower }}.js{% endif %}",
"foreach": "entities.endpoints"
}
In this case endpoints with "action": "delete"
won't generate any output.