Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

cargo, make me a project

Build status crates.io dependency status

cargo-generate is a developer tool to help you get up and running quickly with a new Rust project by leveraging a pre-existing git repository as a template.

cargo-generate uses Shopify’s Liquid template language, Rhai for hook scripts and regex for placeholders.

Due to the use of Shopify’s Liquid, cargo-generate special cases files with the file-ending .liquid, by simply removing the file-ending when processing the files. If you, as a template author, truly want the .liquid file-ending, you need to repeat it twice!

For example: The file README.md.liquid will be renamed after templating to README.md. If README.md already exists, then it will be overwritten to include the contents of README.md.liquid.

Here’s an example of using cargo-generate with this template:

Installation

From crates.io

cargo install cargo-generate --locked

No system libraries are required. New in version 0.24.0 is the pure-Rust git stack via gix and TLS via rustls, so a plain Rust toolchain is all you need — no pkg-config, libgit2, libssl-dev, perl, or C compiler.

Using pacman (Arch Linux)

cargo-generate can be installed from the extra repository for Arch Linux:

pacman -S cargo-generate

Manual Installation

  1. Download the binary tarball for your platform from our releases page.
  2. Unpack the tarball and place the binary cargo-generate in ~/.cargo/bin/.

Usage

Standard usage is to pass a git repository to cargo generate or short cargo gen. This will prompt you to enter the name of your project.

⚠️ NOTE: cargo gen requires a cargo alias configuration

# full git url
cargo generate --git https://github.com/username-on-github/mytemplate.git

# shorthand for github (org/repo expands to https://github.com/org/repo.git)
# note: if a local directory with that name exists, it is used as a path instead
cargo generate --git username-on-github/mytemplate
# is the same as
cargo generate username-on-github/mytemplate

# prefixed shorthand (works with --git or as positional argument)
cargo generate gh:username-on-github/mytemplate
# is the same as
cargo generate --git gh:username-on-github/mytemplate

If you have your templates not GitHub then you can leverage the lazy abbreviation prefixes:

# for gitlab.com
cargo generate gl:username-on-gitlab/mytemplate # translates to https://gitlab.com/username-on-gitlab/mytemplate.git
# or for bitbucket.org
cargo generate bb:username-on-bitbucket/mytemplate # translates to https://bitbucket.org/username-on-bitbucket/mytemplate.git
# or for github.com
cargo generate gh:username-on-github/mytemplate # translates to https://github.com/username-on-github/mytemplate.git
# or for git.sr.ht
cargo generate sr:username-on-sourcehut/mytemplate # translates to https://git.sr.ht/~username-on-sourcehut/mytemplate (note the tilde)

Both will expand to the https urls of the repo with the suffix .git in the URL.

You can also pass the name of your project to the tool using the --name or -n flag:

cargo generate --git https://github.com/username-on-github/mytemplate.git --name myproject

Templates in subfolders

If the repository or path specified for the template contains multiple templates (Any sub-folder that contains a cargo-generate.toml file), cargo-generate will ask for the specific folder to be used as the template.

Multiple sub-templates can also be configured in the cargo-generate.toml file like this:

[template]
sub_templates = ["folder1", "folder2"]

Doing so also sets the order when cargo-generate asks what to expand, while the first option will be the default.

The specific subfolder in the git repository may be specified on the command line like this:

cargo generate --git https://github.com/username-on-github/mytemplate.git <relative-template-path>

⚠️ NOTE: When using the subfolder feature, cargo-generate will search for the cargo-generate.toml file in the subfolder first, traversing back towards the template root in case it is not found.

Generating into current dir

If the user wants to generate a template straight into the current folder, without creating a subfolder for the contents and without attempting to initialize a .git repo or similar, the --init flag can be used.

cargo generate --init --git https://github.com/username-on-github/mytemplate.git

⚠️ NOTE: cargo-generate will not allow any existing files to be overwritten and will fail to generate any files should there be any conflicts.

Generating using a local template

You can generate a project using a local template via the --path flag:

git clone https://github.com/username-on-github/mytemplate.git $HOME/mytemplate # Clone any template
cargo generate --path $HOME/mytemplate # Use it locally

⚠️ NOTE: cargo-generate will not allow to use the association --path and --git flags.

Http(s) proxy

New in version 0.7.0 is automatic proxy usage. So, if http(s)_PROXY env variables are provided, they will be used for cloning a http(s) template repository.

Git over SSH

Both SSH URL forms are supported. Note that the ssh:// form uses a path separator, while the git@ shorthand uses a colon between host and org:

git@github.com:rustwasm/wasm-pack-template.git

# vs

ssh://git@github.com/rustwasm/wasm-pack-template.git

Either one can also be used as the right-hand side of .gitconfig insteadOf — see the next chapter.

cargo generate --git git@github.com:rustwasm/wasm-pack-template.git --name mywasm

How authentication works

New in version 0.24.0 is the full delegation of SSH to the system ssh binary (via gix). In practice that means:

  • ssh-agent is picked up automatically wherever the OS exposes it.
  • ~/.ssh/config is honored, including per-host IdentityFile, IdentitiesOnly, ProxyJump, and friends.
  • Default identity discovery is whatever your ssh uses — typically ~/.ssh/id_ed25519, ~/.ssh/id_rsa, etc.
  • Passphrase prompts come from ssh (or the agent) directly, so they look and behave exactly like a plain git clone.

No cargo-generate–specific configuration is needed for the common case: if git clone <ssh-url> works in your shell, so does cargo generate --git <ssh-url>.

On Windows

ssh-agent ships as an optional service with modern Windows. Follow this guide for one-time setup; once it’s running, cargo-generate uses it transparently.

Custom SSH identity file (private key)

If you need a specific key for a single invocation, pass it with -i / --identity:

cargo generate -i ~/.ssh/id_rsa_other --git git@github.com:org/template.git

Under the hood this becomes an in-memory core.sshCommand = ssh -i <path> override — equivalent to running git with GIT_SSH_COMMAND=ssh -i <path>. Passphrase prompts (if any) come from ssh directly.

For a persistent choice, ~/.ssh/config is usually the cleanest option:

Host github.com
    IdentityFile ~/.ssh/id_rsa_other
    IdentitiesOnly yes

Alternatively, configure it in the cargo-generate config file:

# an extract of ~/.cargo/cargo-generate.toml
[defaults]
# note that `~/` and `$HOME/` are expanded to the full path seamlessly
ssh_identity = "~/.ssh/id_rsa_other"
# equivalent to
ssh_identity = "$HOME/.ssh/id_rsa_other"
# equivalent to
ssh_identity = "/home/john/.ssh/id_rsa_other"

⚠️ NOTE: the CLI argument -i always overrules ssh_identity from the config file.

.gitconfig and insteadOf configuration

⚠️ New in version 0.22.0

git supports a magic trick to rewrite urls on the fly. This is done by adding a url.<base>.insteadOf configuration to your .gitconfig file.

In cargo-generate this is supported as well.

For example, if you prefer the ssh over the https urls and you want to use cargo-generate with it, you can add the following to your .gitconfig:

# ~/.gitconfig

[url "git@github.com:"]
insteadOf = https://github.com/

and then you can use cargo-generate with the https url:

RUST_LOG=debug cargo generate https://github.com/Rahix/avr-hal-template.git

🔧   gitconfig 'insteadOf' lead to this url: git@github.com:Rahix/avr-hal-template.git

...

In this case please notice the ssh url is git@github.com: if you prefer the more explicit notation you can also write it like this:

# ~/.gitconfig

[url "ssh://git@github.com/"]
insteadOf = https://github.com/

that would lead to the same result, with slightly different url:

RUST_LOG=debug cargo generate https://github.com/Rahix/avr-hal-template.git

🔧   gitconfig 'insteadOf' lead to this url: ssh://git@github.com/Rahix/avr-hal-template.git

...

⚠️ NOTE: RUST_LOG=debug would allow you to see the rewritten url in the output.

In cases where you have a different .gitconfig location, you can use the --gitconfig argument to specify the path to the .gitconfig file, like this:

$ cd /path/to/my/workspace
$ cat .gitconfig
[url "git@github.com:"]
insteadOf = https://github.com/

$ cargo generate --gitconfig ./.gitconfig https://github.com/Rahix/avr-hal-template.git

Library Usage

cargo-generate is also a Rust library. Any crate can depend on it and drive template generation from code, using the same path as the cargo generate CLI.

A full worked example lives in this repository at examples/how-to-use-cargo-gen-as-library/. The snippets below are pulled straight from that file with mdbook’s {{#include}}, so they cannot drift out of sync.

Depend on cargo-generate

Add the crate to your Cargo.toml:

[dependencies]
cargo-generate = "*"

Cargo features

FeatureDefaultDescription
gitFetching templates from git repositories, and initializing a repository in the generated project. Pulls in gix and reqwest.

If your tool only ever generates from local templates, turning git off drops the whole gix/reqwest/rustls tree:

[dependencies]
cargo-generate = { version = "*", default-features = false }

Your code does not change. GenerateArgs, TemplatePath and Vcs keep every field, so whatever compiled with default features still compiles. What changes is at runtime: asking for a git template — as the example on this page does — or for a git repository in the generated project returns an error naming the feature rather than doing the work. Local-path templates, hooks, rendering and workspace membership are unaffected.

Imports

Three types cover the common cases:

#![allow(unused)]
fn main() {
use cargo_generate::{generate, GenerateArgs, TemplatePath, Vcs};
}
  • GenerateArgs mirrors the top-level CLI arguments.
  • TemplatePath describes where the template comes from: a git url, a local path, a favorite, etc.
  • Vcs controls which version control system (if any) is initialized in the generated project.

1. Build a GenerateArgs

Populate only the fields you care about; everything else falls back to GenerateArgs::default() / TemplatePath::default():

#![allow(unused)]
fn main() {
    let wasm_pack_args = GenerateArgs {
        name: Some("my-project".to_string()),
        vcs: Some(Vcs::Git),
        template_path: TemplatePath {
            git: Some("https://github.com/rustwasm/wasm-pack-template.git".to_string()),
            ..TemplatePath::default()
        },
        ..GenerateArgs::default()
    };
}

The example above is equivalent to running:

cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project

2. Call generate

generate runs the same flow as the CLI: clone the template, expand placeholders, run hooks, and (if a VCS is requested) initialize the new repository. On success it returns the PathBuf of the generated project:

#![allow(unused)]
fn main() {
    let _path = generate(wasm_pack_args).expect("something went wrong!");
}

Generating without git

If your tool carries its own blueprint, there is nothing to clone — and no reason to carry a git implementation. examples/scaffold-from-blueprint/ is that shape in full, with default-features = false on its dependency line.

The blueprint is compiled into the binary, so the tool keeps working wherever it is installed:

#![allow(unused)]
fn main() {
/// The blueprint, as bytes in the binary. Nothing is read from the
/// source tree at runtime, so the tool keeps working after
/// `cargo install`.
static BLUEPRINT: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/blueprint");
}

cargo-generate reads templates from a directory, so the bytes are unpacked to a temporary one first. It has to outlive the call — dropping it takes the blueprint with it:

#![allow(unused)]
fn main() {
    // cargo-generate reads templates from a directory, so hand it one.
    // `tmp` has to outlive the call — dropping it deletes the blueprint.
    let tmp = TempDir::new()?;
    BLUEPRINT.extract(tmp.path())?;
}

From there it is an ordinary local-path generation:

#![allow(unused)]
fn main() {
    let args = GenerateArgs {
        name: Some("my-service".to_string()),
        // Without the `git` feature this is already the default, but
        // saying it is what makes the example independent of that.
        vcs: Some(Vcs::None),
        template_path: TemplatePath {
            path: Some(tmp.path().display().to_string()),
            ..TemplatePath::default()
        },
        ..GenerateArgs::default()
    };
}

vcs: Some(Vcs::None) is redundant without the git feature — that is already the default there — but stating it keeps the example working the same way whichever features are on.

Running the example

From a checkout of this repository:

cd examples/how-to-use-cargo-gen-as-library
cargo run

This creates a my-project/ directory in the current folder, the same result you would get from running cargo generate on the command line.

Favorites

Favorite templates can be defined in a config file, that by default is placed at $CARGO_HOME/cargo-generate.toml or $CARGO_HOME/cargo-generate. To specify an alternate configuration file, use the --config <config-file> option.

⚠️ NOTE: A relative --config option, will be relative to the template root during expansion.

Each favorite template is specified in its own section, e.g.:

[favorites.demo]
description = "<optional description, visible with --list-favorites>"
git = "https://github.com/rustwasm/wasm-pack-template"
branch = "<optional-branch>"
subfolder = "<optional-subfolder>"
vcs = "<optional: None|Git>"
init = optional true|false
overwrite = optional true|false

Values may be overridden using the CLI arguments of the same names (e.g. --subfolder for the subfolder value).

Note: Specifying init = true has the effect of forcing the template to exhibit behaviour as if --init is specified on the commandline, as there is no counter-option!

Note: Specifying overwrite = true has the effect of allowing the template to always overwrite files as there is no counter-option!

When favorites are available, they can be generated simply by invoking:

cargo gen <favorite>

or slightly more involved:

cargo generate demo --branch mybranch --name expanded_demo --subfolder myfolder

⚠️ NOTE: when <favorite> is not defined in the config file, it is interpreted as a git repo like as if --git <favorite>

Templates

Placeholders

Templates are git repositories whose files can contain placeholders. A placeholder can be seen as a variable that is substituted by another value upon expansion of the template.

cargo-generate supports both builtin variables/placeholders and custom defined ones.

Additionally, all filters and tags of the liquid template language are supported. For more information, check out the Liquid Documentation on Tags and Filters.

You can use those placeholders in the file and directory names of the generated project. For example, for a project named awesome, the filename {{project-name}}/{{project-name}}.rs will be transformed to awesome/awesome.rs during generation. Only files that are not listed in the exclude settings will be templated.

⚠️ NOTE: invalid characters for a filename or directory name will be sanitized after template substitution. Invalid is e.g. / or \.

⚠️ Deprecated in favor of using ignore in cargo-generate.toml

You can also add a .genignore file to your template. The files listed in the .genignore file will be removed from the local machine when cargo-generate is run on the end user’s machine. The .genignore file is always ignored, so there is no need to list it in the .genignore file.

Additional liquid filters

Following are filters that cargo-generate expands the liquid language with.

  • rhai

    Tries to run the argument as a rhai script. Whatever the script returns will be the output of the filter.

    Example Liquid:

    Here we try to run a rhai script: {{"script_name.rhai" | rhai}}
    

    ⚠️ NOTE: Liquid does not support failing filters, thus if the script fails for any reason, cargo-generate will simply print a warning message to stderr, and Liquid will leave the substitution in its original form.

  • kebab_case

    "We are going to inherit the earth." => "we-are-going-to-inherit-the-earth"

  • lower_camel_case

    "It is we who built these palaces and cities." => "itIsWeWhoBuiltThesePalacesAndCities"

  • pascal_case

    Same as upper_camel_case

  • shouty_kebab_case

    "We are going to inherit the earth." => "WE-ARE-GOING-TO-INHERIT-THE-EARTH"

  • shouty_snake_case

    "That world is growing in this minute." => "THAT_WORLD_IS_GROWING_IN_THIS_MINUTE"

  • snake_case

    "We carry a new world here, in our hearts." => "we_carry_a_new_world_here_in_our_hearts"

  • title_case

    "We have always lived in slums and holes in the wall." => "We Have Always Lived In Slums And Holes In The Wall"

  • upper_camel_case

    "We are not in the least afraid of ruins." => "WeAreNotInTheLeastAfraidOfRuins"

Templates by the community

It’s encouraged to classify your template repository with a GitHub topic labeled cargo-generate.

So that every developer can find the template via cargo-generate topic on GitHub.

If you have a great template, please tag your repository with the topic and tweet about it by including the hashtag #cargogenerate (since twitter does not support hashtags with -).

⚠️ Note: the list of currently available templates is still available, but is now deprecated.

Example for --bin and --lib

A template could be prepared in a way to act as a binary or a library. For example the Cargo.toml might look like:

[package]
# the usual stuff

[dependencies]
{% if crate_type == "bin" %}
structopt = "0.3.21"
{% endif %}
# other general dependencies

{% if crate_type == "bin" %}
[[bin]]
path = "src/main.rs"
name = "{{crate_name}}-cli"
{% endif %}

Now a user of this template could decide weather they want the binary version by passing --bin or use only the library version by passing --lib as a command line argument.

Builtin placeholders

cargo-generate supports a number of builtin placeholders for use in templates.

These placeholders can be used directly in files using the Liquid language, or from Rhai scripts using the syntax:

variable::get("placeholder name")

Builtin placeholders are:

  • authors
    • this will be filled in by a function borrowed from Cargo’s source code, that determines your information from Cargo’s configuration. It will either be on the form username <email> or just plain username.
  • project-name
    • this is supplied by either passing the --name flag to the command or working with the interactive CLI to supply a name. It can be provided in snake_case or dash-case, in all other cases it is converted to dash-case.
    • it can also be supplied via the environment variable CARGO_GENERATE_VALUE_PROJECT_NAME when running in --silent mode

      ⚠️ Note: the --force flag allows you to use the project name as it is given, without adjusting. Please use it carefully.

  • crate_name
    • the snake_case_version of project-name
  • crate_type
    • this is supplied by either passing the --bin or --lib flag to the command line, contains either bin or lib, --bin is the default
  • os-arch
    • contains the current operating system and architecture ex: linux-x86_64
  • username
    • this will be filled in by a function borrowed from Cargo’s source code, that determines your information from Cargo’s configuration.
  • within_cargo_project
    • A boolean with the value true if the template is being expanded inside a Cargo project. It’s a simple matter of whether Cargo.toml is present in any parent folder.
  • is_init
    • A boolean that reflects the value of the --init parameter of cargo-generate.

Overriding builtin placeholders

Available since version 0.24.0

Builtin placeholders can be overridden on the command line via --define, the --values-file, or a CARGO_GENERATE_VALUE_<NAME> environment variable — the same mechanisms used for template-defined placeholders. This is useful when the auto-derived value (e.g. authors from local git config) should be pinned to a stable value, for example when committing the expanded template to a repository.

cargo generate --git … --define 'authors=The Project Authors'

An info message is logged whenever a builtin is overridden, so accidental overrides remain visible.

Usage example

// README.md

This awesome crate `{{ crate_name }}` is brought to you by {{ authors }}.

Template Defined Placeholders

Template defined placeholders offer a powerful way for template authors to customize project templates and streamline project creation. In addition to defining placeholders directly within template files, users can also define placeholders in the cargo-generate.toml file, providing additional flexibility and customization options.

Defining Placeholders in cargo-generate.toml

To define placeholders in the cargo-generate.toml file, template authors can specify them under the placeholders section using the following syntax:

[placeholders]
placeholder_name = { prompt = "Enter your name", choices = ["Alice", "Bob"], default = "Alice", type = "string" }
  • placeholder_name: The name of the placeholder.
  • prompt: The prompt message displayed to the user during project creation.
  • choices (optional): A list of predefined choices for the placeholder value. Each entry is either a plain string or a { value = "...", label = "..." } table (see Choices with display labels).
  • default (optional): The default value for the placeholder if no user input is provided.
  • regex (optional and only for string-like types): The entered value is validated against this regex.
  • type: The data type of the placeholder value (see Supported Types).

Prompt, Choices, and Default Values

  • Prompt: With the prompt will be displayed it to the user during project creation, prompting them to provide a value for the placeholder.
  • Choices: If choices are specified, cargo-generate will present them as options to the user, restricting the input to the predefined choices and provide more convenience.
  • Default Value: If a default value is provided and the user does not provide input, cargo-generate will use the default value for the placeholder.

Choices with display labels

By default each choice is a plain string that serves both as the option shown to the user and as the value substituted into the template. When you want to show a friendlier prompt than the value itself, a choice may instead be written as a table with an explicit value and an optional display label:

[placeholders.version]
type = "string"
prompt = "Which version?"
choices = [
    { value = "recommended",  label = "1.3.7 (recommended)"  },
    { value = "experimental", label = "1.4.3 (experimental)" },
    "older",
]
default = "recommended"

The label is only used when prompting; the template (and the default, --define and regex checks) always sees the value:

{% if version == "recommended" %}framework = { version = "1.3.7" }{% endif %}

The two forms can be mixed freely within the same choices array, and a table without a label behaves exactly like the plain-string form. Both string and array (multi-select) placeholders support labelled choices.

Supported Types

cargo-generate supports the following placeholder value types:

  • "string": Represents a string value.
  • "text": Represents a multiline string value. (terminated by hitting CTRL-D)
  • "editor": Represents a multiline string value, collected from the user by a real terminal editor.
  • "bool": Represents a boolean value (true or false).

Available since version 0.23.0

  • "array": Represents an array of strings (["a","b","c"])

Example

Consider the following cargo-generate.toml file:

[placeholders]
project_name = { prompt = "Enter project name", default = "my_project", type = "string" }
environment = { prompt = "Which environment?", choices = ["dev", "prod"], default = "dev", type = "string"}
features = { prompt = "Include features?", choices = ["serde", "logging"], default = ["serde"], type = "array"}
use_git = { prompt = "Initialize Git repository?", default = true, type = "bool" }
phone_number = { prompt = "What's your phone number?", type = "string", regex = "^[0-9]+$" }

During project creation, cargo-generate will prompt the user to provide values for project_name, use_git and phone_number using the specified prompts, choices, and default values.

Further phone_number is validated against the provided regex, hence it can only contain digits.

Conclusion

Template defined placeholders, defined in the cargo-generate.toml configuration file, offer powerful customization options for project templates. By specifying prompts, choices, default values, and supported types, template authors can create intuitive and flexible project scaffolding experiences, enhancing developer productivity and project consistency.

Default values for placeholders

For automation purposes the user of the template may provide the values for the keys in the template using one or more of the following methods.

The methods are listed by falling priority.

--define or -d flag

The user may specify variables individually using the --define flag.

cargo generate template-above -n project-name -d hypervisor=qemu -d network_enabled=true

--template_values_file flag

The user of the template may provide a file containing the values for the keys in the template by using the --template-values-file flag.

⚠️ NOTE: A relative path will be relative to current working dir, which is not inside the expanding template!

[values]
hypervisor = "qemu"
network_enabled = true

Individual values via environment variables

Variables may be specified using environment variables. To do so, set the env var CARGO_GENERATE_VALUE_<variable key> to the desired value.

set CARGO_GENERATE_VALUE_HYPERVISOR=qemu
set CARGO_GENERATE_VALUE_NETWORK_ENABLED=true
cargo generate template-above

⚠️ Windows does not support mixed case environment variables. Internally, cargo-generate will ensure the variable name is all lowercase. For that reason, it is strongly recommended that template authors only use lowercase variable/placeholder names.

Template values file via environment variable

The user may use the environment variable CARGO_GENERATE_TEMPLATE_VALUES to specify a file with default values.

For the file format, see above.

Default values

Default values may be specified in the config file (specified with the --config flag, or in the default config file $CARGO_HOME/cargo-generate)

Example config file:

[values]
placeholder1 = "default value"

[favorites.my_favorite]
git = "https://github.com/username-on-github/mytemplate.git"

[favorites.my_favorite.values]
placeholder1 = "default value overriding the default"
placeholder2 = "default value for favorite"

Further examples

You can find further examples in the example-templates folder that provide some template provided placeholders.

Ignoring files

The template author may choose to ignore files completely, by including an ignore list in the cargo-generate.toml file.

Example:

[template]
ignore = [ 
  "file",
  "or folder",
  "to be ignored" 
]

Both files and folders may be ignored using this method, but currently wildcards are not supported.

Note that cargo-generate checks for which files to ignore after the removal of any .liquid file extensions. Meaning; Setting ignore to ["file.txt"] will result in the ignoring of a file named file.txt.liquid.

Include / Exclude

Templates support a cargo-generate.toml, with a “template” section that allows you to configure the files that will be processed by cargo-generate. The behavior mirrors Cargo’s Include / Exclude functionality, which is documented here. If you are using placeholders in a file name, and also wish to use placeholders in the contents of that file, you should setup your globs to match on the pre-rename filename.

[template]
include = ["Cargo.toml"]
# include and exclude are exclusive, if both appear we will use include
exclude = ["*.c"]

⚠️ NOTE: exclude only makes cargo-generate ignore any liquid tags in the file. In order to exclude a file from being copied to the final dir, see ignoring files.

The cargo-generate.toml file should be placed in the root of the template. If using the subfolder feature, the root is the subfolder inside the repository, though cargo-generate will look for the file in all parent folders until it reaches the repository root.

Require cargo-generate version from template

Available since version 0.9.0

Using the supported cargo-generate.toml file, the template author may setup version requirements towards cargo-generate.

[template]
cargo_generate_version = ">=0.9.0"

The format for the version requirement is documented here.

Conditional template settings

Using cargo-generate.toml, values and some Rhai syntax, the template author can make certain conditional decisions before expansion of the template.

include, exclude, ignore and placeholders can all be used in sections that are only used based upon the value of one or more values, possibly input by the user using the interactive prompt (if the values in question are defined as placeholders in the non-conditional section).

The condition inside conditional.'...' is evaluated as a Rhai expression. Placeholder arrays are exposed as Rhai arrays, so array helpers such as .is_empty and .contains("serde") can be used in these expressions.

Using the following example, cargo-generate will ask for the license, and depending on the --lib | --bin flags it’ll as for the hypervisor and network_enabled values. It will then continue to expand the template, ignoring the src/main.rs file (and thus excluding it from the output) in case --lib was specified.

The example is broken up in order to explain each section.

[template]
cargo_generate_version = ">=0.10.0"
# ignore = [ "..." ]
# include = [ "..." ]
# exclude = [ "..." ]
...

This first part declares that the template requires cargo-generate version 0.10 or higher. In this same section the template author may also specify the following 3 lists:

  • ignore Files/folders on this list will be ignored entirely and are not included in the final output.
  • include These files will be processed for Liquid syntax by the template engine.
  • exclude These files will not be processed for any liquid syntax. The files will be in the final output.
...
[placeholders]
license = { type = "string", prompt = "What license to use?", choices = ["MIT", "Unrestricted"], default = "MIT" }
...

This is the section for the default placeholders. These are variable definitions that cargo-generate knows about and will query for if they are not provided e.g. on the commandline (see [Default-values-for-placeholders]).

The section should contain at least all variables used for any conditions (unless it’s an automatic variable such as crate_type). All variables that are not specific to a condition are recommended to go here as well.

Here we simply define a variable license for selecting the desired license type.

...
[conditional.'crate_type == "lib"']
ignore = [ "src/main.rs" ]
# include = [ "..." ]
# exclude = [ "..." ]
...

This is a conditional block.

Here it has been chosen that the src/main.rs file must be ignored when the crate_type variable is equal to the string "lib".

...
[conditional.'crate_type != "lib"'.placeholders]
hypervisor = { type = "string", prompt = "What hypervisor to use?", choices = ["uhyve", "qemu"], default = "qemu" }
network_enabled = { type = "bool", prompt = "Want to enable network?", default = true }
...

This block uses the same condition as the last, but it defines some extra placeholders - that is, is defines the variables hypervisor and network_enabled, so that cargo-generate may ask for their values.

⚠️ cargo-generate will ask for values using the placeholders defined in [placeholders] before evaluating the conditional sections.

Placeholder values defined in conditional sections cannot be used to enable/disable further conditional sections, they can however still be used in the actual template!

...
[conditional.'license == "MIT"']
ignore = [ "LICENSE-UNRESTRICTED.txt" ]
# include = [ "..." ]
# exclude = [ "..." ]

[conditional.'license == "Unrestricted"']
ignore = [ "LICENSE-MIT.txt" ]
# include = [ "..." ]
# exclude = [ "..." ]

This last conditional block is simply to ignore the unneeded license files, based upon the users choice for the license variable.

⚠️ Note that include and exclude are still mutually exclusive even if they are in different, but included, conditional sections.

Init/Pre/Post Scripts

cargo-generate can run scripts in the Rhai language as part of the template expansion.

Doing so requires the template is configured to use hooks, which can be used at specific times during template expansion.

To configure the use of hooks, write a hooks section in the cargo-generate.toml file.

[hooks]
init = ["init-script.rhai"]
pre = ["pre-script.rhai"]
post = ["post-script.rhai"]

Running system commands

Hooks can execute programs on the user’s system. See System commands for configuration, examples, permission controls, and the associated security risks.

Hook types

Hook types

Init

  • Init hooks are executed before anything else.

  • The variables crate_type/authors/username/os-arch and is_init are available.

  • The variable project-name may be available.

    And only if cargo-generate was called with the --init flag, in which case it is the raw user input.

  • The variable project-name may be set - avoiding a user prompt!

    The variable will still be subject for case changes to fit with the rust/cargo expectations.

    The --name parameter still decides the final destination dir (together with the the --init flag), in order not to confuse the user.

Pre

  • Pre hooks are run after all placeholders mentioned in cargo-generate.toml has been resolved.

  • The hooks are free to add additional variables, but its too late to influence the conditional system.

    This is a side effect of conditionals influencing the hooks - so placeholders need to be evaluated before the hooks are known.

Post

  • Post hooks are run after template expansion, but before final output is moved to the final destination.

Why not later? Security, and the fact that a failing script still causes no errors in the users destination.

Rhai extensions

Rhai extensions

Besides the basic Rhai features, these are the modules/behaviors defined:

Variables with the variable module

get/set

  • variable::is_set(name: &str) -> bool

    Returns true if the variable/placeholder has been set for the template

  • variable::get(name: &str) -> value

    Gets any defined variable in the Liquid template object

  • variable::set(name: &str, value: (&str|bool))

    Set new or overwrite existing variables. Do not allow to change types. Note that you can set entire arrays with this (e.g. variable::set("array",["a","b"])) but not individual elements (variable::set("array[1]","a") will not work).

Prompt for values with variable::prompt

  • variable::prompt(text: &str, default_value: bool) -> value

    Prompt the user for a boolean value

  • variable::prompt(text: &str) -> value

    Prompt the user for a string value

  • variable::prompt(text: &str, default_value: &str) -> value

    Prompt the user for a string value, with a default already in place

  • variable::prompt(text: &str, default_value: &str, regex: &str) -> value

    Prompt the user for a string value, validated with a regex

  • variable::prompt(text: &str, default_value: &str, choices: Array) -> value

    Prompt the user for a choice value

Files with the file module

  • file::exists(path: &str)

    Test if a path exists

  • file::rename(from: &str, to: &str)

    Rename one of the files in the template folder

  • file::delete(path: &str)

    Delete a file or folder inside the template folder

  • file::write(file: &str, content: &str)

    Create/overwrite a file inside the template folder

  • file::write(file: &str, content: Array)

    Create/overwrite a file inside the template folder, each entry in the array on a new line

  • file::listdir(path = ".") -> Array<String>

    List the contents of a directory

    Note: The path is relative to the template folder, and cannot be outside the template folder.

    Examples:

    let files = file::listdir();
    for f in files {
        print(`file: ${f}`);
    }
    
    // this is actually the same as above, the path must be inside the template directory, cannot be absolute or ourside
    let files = file::listdir(".");
    for f in files {
        print(`file: ${f}`);
    }
    

    See also: the many-hooks-in-action example project

The system module

  • system::command(cmd: &str, args: Array = []) -> value

    Execute a command on the system generating the project from a template.

    The user will be prompted with

    The template is requesting to run the following command. Do you agree?
    <command> <args>
    

    unless the user uses the flag --allow-commands. If the user attempts to use the --silent flag without the --allow-commands flag will fail.

    Examples:

    // this returns the PWD as a string
    let pwd = system::command("pwd");
    
    // but this works too and does the same
    system::command("pwd", []);
    
    // this will cat a file and returns the content
    let content = system::command("cat", ["file.txt"]);
    

    See also: the many-hooks-in-action example project

  • system::date() -> Date

    Get the date in UTC from the system as an object with the properties year, month, and day.

The env module

The env module provides access to environment variables.

  • env::working_directory

    Returns the current working directory as a string. This is the directory where the cargo-generate pre-processes the template, before it is copied over to the users destination directory.

    Examples:

    let wd = env::working_directory;
    print(`Working directory: ${wd}`);
    

    See also: the many-hooks-in-action example project

  • env::destination_directory

    Returns the destination directory as a string. This is the directory where the template is copied to, and where the user will find the generated project.

    Examples:

    let dd = env::destination_directory;
    print(`Destination directory: ${dd}`);
    

Other

  • abort(reason: &str): Aborts cargo-generate with a script error.

Changing case of strings

  • to_kebab_case(str: &str) -> String

    "We are going to inherit the earth." => "we-are-going-to-inherit-the-earth"

  • to_lower_camel_case(str: &str) -> String

    "It is we who built these palaces and cities." => "itIsWeWhoBuiltThesePalacesAndCities"

  • to_pascal_case(str: &str) -> String

    Same as to_upper_camel_case(str: &str) -> String

  • to_shouty_kebab_case(str: &str) -> String

    "We are going to inherit the earth." => "WE-ARE-GOING-TO-INHERIT-THE-EARTH"

  • to_shouty_snake_case(str: &str) -> String

    "That world is growing in this minute." => "THAT_WORLD_IS_GROWING_IN_THIS_MINUTE"

  • to_snake_case(str: &str) -> String

    "We carry a new world here, in our hearts." => "we_carry_a_new_world_here_in_our_hearts"

  • to_title_case(str: &str) -> String

    "We have always lived in slums and holes in the wall." => "We Have Always Lived In Slums And Holes In The Wall"

  • to_upper_camel_case(str: &str) -> String

    "We are not in the least afraid of ruins." => "WeAreNotInTheLeastAfraidOfRuins"

System commands

Hook scripts can run programs with system::command. First, register the script as a hook in cargo-generate.toml:

[hooks]
post = ["post-script.rhai"]

Then call the program from post-script.rhai, passing its arguments in an array:

system::command("cargo", ["fmt"]);

let rustc_version = system::command("rustc", ["--version"]);
print(`Generated with ${rustc_version}`);

Commands run in the template’s working directory through sh on Unix and cmd on Windows, so available programs and shell syntax can vary by platform. On success, system::command returns trimmed standard output, or () if the command produced no output. A command that cannot start or exits unsuccessfully stops template generation with an error.

By default, cargo-generate shows the requested command and asks the user to approve it. To run commands without confirmation, the user must opt in:

cargo generate --git https://github.com/example/template.git --allow-commands

The --silent option cannot run a command hook unless --allow-commands is also set. A template cannot enable this permission for itself.

Security warning: --allow-commands lets a template execute arbitrary shell commands with your user account’s permissions and without further confirmation. Those commands can access secrets, modify files outside the generated project, or communicate over the network. Arguments are joined into shell command text without automatic quoting or escaping, so template authors must not insert untrusted values. Only enable commands for templates you trust after reviewing their hook scripts and imported modules.

See the system module reference for the complete function signature and more examples.

Mini example

Mini Example

In cargo-generate.toml write a [hooks] section:

[template]
cargo_generate_version = "0.10.0"

[hooks]
#init = [...]
pre = ["pre-script.rhai"]
#post = [...]

[placeholders]
license = { type = "string", prompt = "What license to use?", choices = ["APACHE", "MIT"], default = "MIT" }

Now, write the script in Rhai, utilizing the cargo-generate provided extensions:

// we can see existing variables.
// note that template and Rhai variables are separate!
let crate_type = variable::get("crate_type");
debug(`crate_type: ${crate_type}`);

let license = variable::get("license").to_upper();
while switch license {
  "APACHE" => {
    file::delete("LICENSE-MIT");
    file::rename("LICENSE-APACHE", "LICENSE");
    false
  }
  "MIT" => {
    file::delete("LICENSE-APACHE");
    file::rename("LICENSE-MIT", "LICENSE");
    false
  }
  _ => true,
} {
  license = variable::prompt("Select license?", "MIT", [
    "APACHE",
    "MIT",
  ]);
}
variable::set("license", license);

Template Authoring

Available since version 0.9.0

As a template author you’re probably concerned about successful builds of your template.

Imagine a couple of months after your first template release, some new versions of any dependencies would break your template, and you would not even be aware of it?

The answer to this question is a vital build pipeline for your template project. This challenge got much simpler to solve with the new official cargo-generate GitHub Action.

Here is an example:

tree .github
.github
└── workflows
    └── build.yml

The content of build.yml as a paste template:

name: Build Template
on:
  # https://docs.github.com/en/actions/reference/events-that-trigger-workflows#workflow_dispatch
  workflow_dispatch:
  schedule:
    - cron: '0 18 * * 5'
  push:
    branches: [ '*' ]
    paths-ignore:
      - "**/docs/**"
      - "**.md"

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      PROJECT_NAME: mytemplate
    steps:
      - uses: actions/checkout@v4
      - uses: cargo-generate/cargo-generate-action@latest
        with:
          name: ${{ env.PROJECT_NAME }}
      - uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: stable
      # we need to move the generated project to a temp folder, away from the template project
      # otherwise `cargo` runs would fail 
      # see https://github.com/rust-lang/cargo/issues/9922
      - run: |
          mv $PROJECT_NAME ${{ runner.temp }}/
          cd ${{ runner.temp }}/$PROJECT_NAME
          cargo check

This is a very simple pipeline that builds weekly and on push. It processes your template repo and runs a cargo check as the final step. That’s it, a good start to build on.

Common Pitfalls

When creating templates with cargo-generate, there are several common issues and pitfalls that template authors may encounter. This section aims to highlight these issues and provide guidance on how to avoid them.

GitHub Actions and Liquid Template Language Interference

GitHub Actions use their own templating language, which can interfere with the Liquid template language used by cargo-generate. This can lead to unexpected behavior when placeholders are used in GitHub Actions workflow files.

Issue

When using placeholders in GitHub Actions workflow files, the syntax for GitHub Actions (${{ ... }}) can conflict with the Liquid syntax ({{ ... }}). This can cause errors or unexpected behavior during template generation.

For more details, you can refer to the discussion that was opened for this purpose.

Workarounds

  1. Escape Liquid Syntax: One way to avoid conflicts is to escape the Liquid syntax in the workflow files. This can be done by using {% raw %} and {% endraw %} tags around the GitHub Actions syntax.

    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout code
            uses: actions/checkout@v2
          - name: Run cargo-generate
            run: |
              cargo generate --git https://github.com/your/repo.git --name ${{ '{% raw %}' }}{{ project-name }}{% endraw %}
    
  2. Use Different Placeholders: Another approach is to use different placeholders for GitHub Actions and Liquid. For example, you can use a different syntax for placeholders in GitHub Actions and then replace them with the correct values in a pre-processing step.

    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout code
            uses: actions/checkout@v2
          - name: Set up project
            run: |
              PROJECT_NAME={{ project-name }}
              echo "Project name is $PROJECT_NAME"
    
  3. Use cargo-generate Placeholders Sparingly: Limit the use of cargo-generate placeholders in GitHub Actions workflow files to only where necessary. This reduces the chances of conflicts and makes the workflow files easier to manage.

  4. Liquid Prepend and Append: Use Liquid’s prepend and append filters to dynamically generate the GitHub Actions syntax. This ensures that the placeholders are correctly processed by Liquid and result in the correct GitHub Actions markup.

    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout code
            uses: actions/checkout@v2
          - name: Set up project
            run: |
              echo "${{ "github-variable" | prepend: "{{" | append: "}}" }}"
    
  5. Pre-Hook Rhai Script: Another solution is to create a pre-hook Rhai script that executes gsed or sed to replace all GitHub Actions placeholders with the Liquid syntax on the fly. This automates the escaping process.

    [hooks]
    pre = ["pre-hook.rhai"]
    
    // pre-hook.rhai
    let result = system::command("gsed", ["-i", 's/${{ /${{ "{{" | prepend: "{{" | append: "}}" }}/g', "path/to/workflow/file.yml"]);
    if result != 0 {
        abort("Failed to replace GitHub Actions placeholders");
    }
    

    For more details, you can refer to the issue that was opened for this.

Undefined Placeholders

Another common pitfall is using placeholders that are not defined. When a placeholder is not defined, cargo-generate will not throw an error; instead, it will replace the placeholder with an empty string. This can lead to unexpected results in the generated files.

Issue

If a placeholder is used in a template file but is not defined in the cargo-generate.toml file or provided by the user, it will be replaced with an empty string. This can cause issues such as missing values in configuration files or broken code.

Solution

  1. Define All Placeholders: Ensure that all placeholders used in the template files are defined in the cargo-generate.toml file. This includes providing default values or prompting the user for input.

    [placeholders]
    project_name = { prompt = "Enter project name", default = "my_project", type = "string" }
    author_name = { prompt = "Enter author name", default = "John Doe", type = "string" }
    
  2. Validate Placeholder Usage: Before generating the template, validate that all placeholders used in the template files are defined. This can be done by reviewing the template files and cross-referencing them with the placeholders defined in the cargo-generate.toml file.

  3. Provide Default Values: Where possible, provide default values for placeholders to ensure that they are always replaced with meaningful values.

    [placeholders]
    project_name = { prompt = "Enter project name", default = "my_project", type = "string" }
    

By being aware of these common issues and pitfalls, template authors can create more robust and reliable templates with cargo-generate. Proper handling of GitHub Actions and Liquid template language interference, as well as ensuring that all placeholders are defined, will help avoid unexpected behavior and improve the overall template generation experience.

Contributing

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as noted in License, without any additional terms or conditions. If you want to contribute to cargo-generate, please see CONTRIBUTING.md.

License

Licensed, at your option, under either of the following licences: