#handlebars #database-schema #template-generator #generator #codegen

bin+lib ctgen

Code Generator based on Handlebars Templates and Database Reflection

6 releases

0.1.5 Aug 2, 2024
0.1.4 Apr 17, 2024
0.1.3 Mar 18, 2024
0.1.1 Feb 25, 2024

#188 in Template engine

MIT license

87KB
1.5K SLoC

Crates.io API dependency status build

About

Code generation tool meant to reduce repetitive tasks in day-to-day operations.

Generate code or text documents based on pre-defined code templates and a database schema.

Currently supports only MySQL/MariaDB databases with InnoDB table storage engine.

Code templates are written in handlebars format and support rhai scripts.

Install

Run cargo install ctgen (if this project reached the public crate stage).

Or alternatively clone the repository and run cargo build --release and then
copy target/release/ctgen to a bin path of your choice.

Or just cargo install --path .

To see some hands-on examples, check ctgen-samples.

Usage

There are 3 modes of operation (commands).

  1. The init command is for creating a new configuration profile project.
  2. The config command is for managing existing configuration profiles.
  3. The run command is for running a generation task inside another project.

Disclaimer

Under no circumstances should you ever run generation tasks based on templates you are not very well familiar with! This poses a great security threat! When using ctgen with templates that you did not create yourself you should read and study the code carefully before running any tasks with that template! Ctgen can and does modify your local filesystem and has the capability to execute additional shell commands with or without user input!

Under no circumstances should you ever run ctgen as root or any other privileged account! As of time of writing ctgen does NOT have any mechanisms to predict or prevent any potentially negative outcomes or dangerous operations. Use discretion and study the templates you use before attempting to run any tasks!

Create profile

To create your first configuration profile go somewhere in your filesystem and run ctgen init.

Optionally you can create the profile project in a new directory by running ctgen init <dirname>.

To avoid being prompted for a profile name, use the --name option: ctgen init --name backend backend_templates.

This will create a new configuration profile project and register it using the same name.

The default project layout is:

  • Profile config file: Ctgen.toml. Describes the profile behavior, templates and build targets.
  • Templates directory: assets/templates. Contains all handlebars templates with .hbs extension. The main part of the filename is the template name.
  • Scripts directory: assets/scripts. Contains all rhai scripts with .rhai extension. The main part of the filename is used to register the script as handlebars helper.

Manage profiles

  • To add an existing configuration profile to the registry, run ctgen config add [path to Ctgen.toml]. If you are in the profile directory, you can just run ctgen config add. Optionally you can pass --default to override your default profile or --name my_name to override the profile name. Otherwise the name of the profile is used in the registry.
  • To list registered profiles, run ctgen config ls. If an item in the list is blinking in red, that means that the profile is broken and the config file does not exist.
  • To remove a profile from the registry, run ctgen config rm profile_name.

Run tasks

Assuming you have a valid configuration profile setup already (see above), to run a generation task you need to:

  • Go inside your project: cd my_awesome_project
  • Run ctgen run
  • Answer prompts
  • ???
  • PROFIT

Check ctgen help run for extra options like:

  • Choosing a profile other than the default using --profile=flutter
  • Overriding the profile setting for .env file, environment variable name, DSN string or target path
  • Overriding the profile prompts with the --prompt option, for example --prompt "dummy=1". Prompts answered with command-line params will be skipped during the run.

Example runs:

Let's imagine you are generating flutter code for your mobile project. Your profile is called mobile. It doesn't know where your database is so you enter it manually. You chose to generate code for table clients and know that the profile will ask you whether you want to generate a password reset flow and also add login with Google.

Run: ctgen run --profile=mobile --dsn="mysql://root@127.0.0.1:3306/project_db" --prompt "password_reset=1" --prompt "google_auth=1" clients

Profile TOML Schema

The Ctgen.toml file describes the profile behavior and follows this set of rules:

  1. The first section in the file is called profile, this section holds these fields:
  • field name: the default profile name
  • field env-file: the name of the env file to look for when trying to initialize context, typically .env
  • field env-var: the name of the env variable to look for in the .env file, for example DATABASE_CONNECTION; the value of the variable is expected to be a valid DSN
  • field dsn: if env-file and env-var are left empty, the profile could have a hardcoded database DSN instead; otherwise this field could be omitted or left blank
  • field target-dir: this is the directory that should hold all build targets. It is relative to current working dir when running a generation task (ctgen run). CWD is used if left blank
  • field templates-dir: this is the directory that holds all handlebars templates. It is relative to the profile containing directory.
  • field scripts-dir: this is the directory that holds all rhai scripts. It is relative to the profile containing directory.
  • field prompts: this is an array of strings. Every string in the array must be a valid prompt ID of a prompt defined in the prompt sections that follow.
  • field targets: this is an array of strings. Every string in the array must be a valid target ID of a target defined in the target sections that follow.
  1. Any number of prompt sections after the profile section declare profile prompts by assigning a prompt ID as a dot-nested value to the section name, for example [prompt.dummy]. A prompt can have the following fields (properties):
  • field condition: optional, containing an inline handlebars template that should render 1 to trigger this prompt
  • field prompt: containing plain text or an inline handlebars template that is being rendered to the user as prompt text
  • field options: optional, containing either an array or table (object) of available options (for select and multiselect prompts), or string (for input prompts), or an inline handlebars template that renders a comma-separated list of options (for select and multi-select prompts)
  • field multiple: optional, boolean flag indicating a multi-select; default is false
  • field ordered: optional, boolean flag indicating that order matters for multi-select values; default is false
  • field required: optional, boolean flag indicating that empty values will not be accepted; default is false
  1. Any number of target sections after the prompt sections declare profile build targets by assigning a target ID as a dot-nested value to the section name, for example [target.dummy]. A target can have the following fields (properties):
  • field condition: optional, containing an inline handlebars template that should render 1 to trigger this target to be rendered
  • field template: string containing a template name, which should exist as a file with .hbs extension in the templates-dir directory. For example dummy, or backend/dummy.
  • field target: string containing an inline handlebars template that should render to a file path inside the target-dir. Missing path elements will be created. Could also be plain text path like main.rs.
  • field formatter: optional, containing an inline handlebars template that should render a valid shell command to execute after the target has been rendered and written to disk. Could also be plain text shell command if no context conditional parameters are necessary. NOTE: The only available variable to render is {{target}}.

Notes

  • If a rhai script file is named op.rhai inside assets/scripts, then you will have {{op}} helper available in your handlebars templates
  • Rhai comes with the following additional packages enabled: rhai-chrono, rhai-sci, rhai-fs, rhai-url.
  • If your template file is named backend.hbs inside assets/templates, to define a target that uses that template, use the name backend as template name
  • Available helpers (other than handlebars' defaults) are: {{inflect}} handlebars-inflector, {{concat}} handlebars-concat, {{datetime}} handlebars-chrono and {{{json}}} (takes the first argument and turns it into a JSON)
  • The context available during rendering handlebars templates looks roughly like:
{
  "database": {
    "name": "db_name",
    "tables": [],
    "constraints": [],
    "metadata": {}
  },
  "table_name": "selected_table_name",
  "table": {
    "name": "selected_table_name",
    "primary_key": [],
    "columns": [],
    "indexes": [],
    "metadata": {}
  },
  "constraints_local": [],
  "constraints_foreign": [],
  "prompts": {
    "dummy": "1"
  },
  "timestamp": "2024-03-18T21:35:09.750752900+00:00",
  "ctgen_ver": "0.1.2"
}

To dump your own context for debugging purposes use {{{json this}}} in your template.

Acknowledgements

This tool relies heavily on handlebars-rust and rhai crates. ❤️

TODO

  • improve error handling
  • improve logging
  • improve database-reflection (add more adapters)
  • improve lib layout and exports and isolate cli junk better
  • more data sources, different context structs and context builders
  • add dump/load context option to repeat runs
  • more handlebars helpers
  • maybe template updates by target reverse-engineering? based on diff?
  • maybe fs watch to re-run task when templates change
  • comments and documentation
  • init produces crappy prompt options because of toml and serde circle of hell

Dependencies

~32–46MB
~741K SLoC