[Pre-RFC] Cargo Templates

Sorry for the delayed reply.

The problem with resigning ourselves to that is that it runs a strong risk of developing into the situation C and C++ "enjoy" with build systems, where there are half a dozen different popular options and everyone wishes everyone else were using their personal favourite.

If I use my own hacked-together templating solution because the "blessed option" isn't powerful enough, then I'm not going to bother with something as silly as maintaining two different copies of my templates... I'll just tell everyone to use my template instancer (either as a crate or, if it's simple enough, by including it in each template repo) when I decide to share the templates I use myself and then we're right back to the C/C++ mess.

I'm not saying we should go for something as insane as Yeoman's "each template is an interactive wizard written in JavaScript" madness, but we need to find a happy medium where developers are willing to dogfood templates written in the blessed option.

Yes, the comparison to examples is apt. I’ve found it annoying that examples don’t (can’t?) have their own Cargo.toml already, maybe there’s a way to enhance examples and then just let you instantiate a project from one of them.

The big issue with re-using examples is the string interpolation, I’m not sure how that could be made to work…

Okay, here’s a (probably wrong) sketch of a counterproposal that uses examples as templates:

  • A directory in examples which contains a Cargo.toml is treated as a separate crate which will be compiled by the example harness (this is useful separate from templates).
  • An crate’s Cargo.toml can contain a template object, with these fields:
[template]
example_name = "my-example"
example_author  = "me"
templated_paths = ["./LICENSE", "./README.md"]
  • If it has this key, the Cargo.toml as well as all files in the “template.templated_paths” item can contain interpolation string. In the example harness, it will use the example name, author, and current time.
  • Templates can be downloaded from crates.io using crate-name/template-name.
  • Templates can be downloaded from a path using a path to the directory containing their Cargo.toml.
  • Templates can be downloaded from git using a git URL and a separate argument for a directory in the repo.
1 Like

My main concern here is that it support relative paths and the precedence order be such that someone can't hijack my existing local use of "foo/bar" by creating a crate or git repository with that name.

As for the rest, I don't currently have time to form an opinion on it.

I uploaded my prototype here. Currently I think docopt doesn’t work very well when it’s installed since running cargo-template list works but cargo template list doesn’t.

Anyway, some example uses (caveat: prototype level code):

$ cat ~/.config/cargo-template/CargoTemplate.toml 
[user]
name = "Ewan Higgs"
email = "some-email-address@foo.bar"

[sources]
ehiggs = {uri="https://github.com/ehiggs/rust-cmdline-skeleton"}

$ ./target/debug/cargo-template list
ehiggs/rust-cmdline-skeleton
$ ./target/debug/cargo-template new ehiggs/rust-cmdline-skeleton foo
$ ls foo
Cargo.toml	LICENSE		README.md	src
$ cat foo/Cargo.toml 
[package]
name = "foo"
version = "0.1.0"
authors = []
license = "GPLv3"

[dependencies]
docopt = "0.6"
rustc-serialize = "0.3"

I guess another way to use cargo template could be to co-opt an example and rewrite the name and dependencies in the Cargo.toml. It could be cargo template clone tokio/hello-world. But, as @withoutboats pointed out, examples don't have Cargo.toml files.

[quote]A directory in examples which contains a Cargo.toml is treated as a separate crate which will be compiled by the example harness (this is useful separate from templates). [/quote] Yes please! :+1:

I'm not sure think this is even needed. We know the name because it's on the cmd line. We know the author because it's in the git or hg config (or overridden using --author). Indeed the only thing left is the templated_paths to set the date in the LICENSE and/or README.

[quote]Templates can be downloaded from crates.io using crate-name/template-name. [/quote] I'm still not sold on using crates.io for this. I'm with @ssokolow here that I would give preference to the local filesystem. I like the debian/ubuntu style sources.list, which is what inspired the CargoTemplates.toml mentioned above but maybe that's a hassle for people to add a new source each time they want to try a new template.

Maybe if we use crates:// as a custom URL scheme? I'm not an IETF-type-person so I don't know how frowned upon custom URL schemes are. I defer to any Mozillan here.

Of course! We just use the name of the example and the current author, all we need are the list of files to interpolate.

To me it seems imperative that this be as simple as possible for people trying to start a $FRAMEWORK project. It really needs to be as easy as cargo new -template=rocket/app my-awesome-app (indeed, I wonder if we can even get rid of --template argument!). I know there are other use cases that advanced users have, but I think this feature is most valuable to the ecosystem as an onboarding tool - by a wide margin.

Its trivial to check if there's a template at $PWD/rocket/app and use that instead of looking on crates.io.

cargo clone rocket/app my-awesome-app

For simplicity sake, I wouldn't even bother with the date templating. Could give --licence apache2 and it would fill in the template on it's own with the date. This cuts down on license proliferation as well. And templating a README is of questionable value. The value, imo, is filling out the code.

So I'd be v. v. happy with cargo clone as above, but examples will need a Cargo.toml.

1 Like

I don’t understand why you’d need any kind of date argument instead of just getting the current date.

EDIT: cargo new barfs when you pass two arguments today, cargo new rocket/app my-awesome-app could Just Work.

I don't mean a date argument, I mean date templating. As in, maybe don't bother using {{date}}.

I just found out that the old --template option got removed from cargo nightly tbh I thought that it was already pretty good but I can see that some sort of marking files/sections as verbatim might be necessary.

So In my opinion something like a template-manifest.toml which contains a set of backlisted files which will not get parsed by the template engine (for blobs), a rename section, and a tag like {{#verbatim}} ... {{/verbatim}} (for text files which might contain other template data) would be enough.

For template sources I think using a path like ~/.templates/foo/bar or urls with different schemes such as git@... or https://.. should infer a sensible default. Maybe an option like --protocol=git could be provided optionally (if inference failed).

Multiple templates per repository might be nice, but are not really necessary imo.

For future proofing if there might ever be a global template repository local paths might need to start with a file:/// scheme or the protocol option needs to be set to local.

The problem with blacklisting is that you're introducing unexpected behaviour at a distance in contexts which are likely to silently pass through the result without compile-time errors.

(I was lucky that my justfile was calling commands that produced error messages when the Just variables were replaced with nothing. Things like Django/Jinja/Twig/etc.-style HTML templates would just silently do the wrong thing.)

Also, substituting templating variables is typically only going to be done for one or a few files.

It makes much more sense to whitelist, rather than blacklist, both for reasons of intuitiveness (you get verbatim behaviour unless you ask for templating and you've got a clear, exact list of files which this behaviour will be applied to) and convenience (you don't have to amend the blacklist every time you add a new HTML template to your project).

While it'd be nice to have the option of a place to keep templates that is out of the way, I keep mine in ~/src/ alongside my projects so all of my backup configuration and navigational shell shortcutting just work. I wouldn't want to be forced to use a ~/.templates to get compact "init a new project" commands. (Also, you're not supposed to create new dotfiles/dotdirs directly in ~.)

...though, worst case, I suppose I could ln -s src ~/.templates

Be aware that I'm not that atypical as programmers go and that in itself would probably be enough to prompt me to just bundle my homegrown templating solution with every project template I publish to avoid having to deal with two separate templating solutions for the same language.

(It's not as if I'm getting paid to write these things, so I might as well optimize for my own convenience.)

1 Like

I’d love to see this feature. A proposal for an (IMO) simpler toml config than I’ve seen here is something like the following:

[template.default]
template_root_dir = "./template"  # the only required key
escapes = ["{{", "}}"]  # set to null to make every file literal
template_files = ["*"]  # every file is a template file
literal_files = [] # no file is a literal file
merge_with = []  # these templates are applied in order, after this one

[template.base]
root_dir = "template-base"  # or whatever

This would allow any project to have any number of templates, and to compose them as they’d like. If we added a target_root = "." default then it would be easy for projects to turn their examples into templates by setting up a config file like:

[template.default]
template_root_dir = "examples/my-awesome-example"
target_root = "src"
merge_with = ["exampleBase"]

[template.exampleBase]
template_root_dir = "a-dir-with-a-Cargo.toml-that-will-build-my-example"
target_root = "."  # maybe this should default to the target_root in its called template?

This would also allow cargo new --template $any-uri-or-path and cargo new --template $any --template-name $something-specific without forcing projects to not be able to refactor their templates.

I’m sorry to be the wet blanket, but that looks so complex and over-engineered that, were it my choice, I’d never allow it to be an official part of cargo.

The whole design feels like a textbook case of “try to make everyone happy and please no one”. (In fact, as I was proofreading and editing this, I realized that it feels like someone copy-pasted the essence of C++'s “growth by under-planned accretion” feature set into TOML.)

Here is a more concrete list of the problems I see:

  1. It contains quite a few pieces of functionality I don’t remember even seeing mentioned in the thread up until now. (There’s a reason why principles like YAGNI keep showing up in different forms in software development.)
  2. It sets up a massive fallback chain of overlapping include and exclude mechanisms which leaves me feeling scared that that I’ll miss-intuit or mis-remember the precedence rules. (And I have experience with various tools like rdiff-backup which allow mixing of blacklisting and whitelisting in the same task, so I have more intuition than most about how fallback chains tend to operate.)
  3. This degree of complexity makes it feel like you’re trying to subsume bits of work meant for build.rs, procedural macros, and/or git attributes into the templating system.
  4. I don’t even understand what cargo new --template $any --template-name $something-specific is supposed to do, let alone why I’d want it.

In short, it’s quite literally the opposite of idiomatic Rust design, with a garnish of TOML. If that were how project templating were done, I might still rely on my own templating solution, simply because I wouldn’t trust my ability to maintain templating rules written for your idea without bugs sneaking in.

I think the first thing that needs to be done before any further hypothesizing is to make a clear list of the requirements real people need in real projects so we can consciously and rationally delineate the scope of the tool.

I’ll summarize mine first:

For the tool:

  1. I want something simple. (A template_files key and nothing else would be acceptable for my needs, though uncomfortable. Adding template_root_dir or a renames key would expand it to do everything I want.)
  2. I want something that’s “pay only for what you use”. (ie. I want the ability to just list the files that need templating, possibly with one or two other pieces of metadata, and then have everything else in the repo pass through verbatim without my having to babysit a duplicate list of the repo’s contents.)
  3. I want something easy to maintain and robust against unexpected behaviours. (See my comments about passthrough-by-default and un-complicated configuration.)
  4. I want a design that feels like it’s in line with the philosophy of the language and tooling it supports. (ie. streamlined, simple, good defaults, and minimal overlap in the configuration options.)
  5. I want something people will actually use. (ie. It can’t scare people away because they feel intimidated by its complexity or think, as with Yeoman, that it’s intended for some mythical “person more enterprise-y than me”.)

For my projects:

  1. I need to pass through files verbatim unless otherwise specified. (So the templater can’t cause a repeat of “It mangled my justfile and I only noticed without harm because the first command errors out if it has no argument” if I forget to add something to a blacklist.)
  2. I need to be able to template Cargo.toml (Other files would be nice, but I’m willing to do without)
  3. I want to be able to use different LICENSE and .travis.yml contents for the template repo and the repos it generates.

Given that I can write something which satisfies all of my needs in under 15 minutes, and do it in shell script or Python so it can be bundled in with the template for others to use easily, I have low tolerance for over-engineered solutions.

2 Likes

In fact, as I was proofreading and editing this, I realized that it feels like someone copy-pasted the essence of C++'s "growth by under-planned accretion" feature set into TOML.

Rough, if probably funny to people who aren't me. I'll respond to the meat of your content, which I appreciate.

Regarding your specific points:

It contains quite a few pieces of functionality I don't remember even seeing mentioned in the thread up until now

Let me start by describing what I saw as the fundamental problem being solved: users want to be able to specify a directory that contains files that should be basically just copy-pasted into a new cargo template, with a couple keys interpolated in. Then you, I believe, pointed out that the existing scheme wouldn't work for binary files or files that shared a template language with the Cargo template system.

That lead me to two things: adding in the escapes key (which I added because it exists in basically every templating language I've ever used). It can be added backwards-compatibly later, if it proves necessary. If this is used for non-trivial things I think it will be necessary, but we might not need it so let's ditch it. Also I added both template_files and literal_files. Since they should be perfect complements of each other, it's not necessary to have both. Since there are only two possible keys right now it seems like it makes sense to ditch template_files, but if this system grows to accept even just a project_name key I think listing every literal file will be more annoying than specifying every template file. I'm going to continue using template_files.

At this point my original proposal has become (using the [template...] prefix instead of defining a file, I don't care where it lives but putting it inside of Cargo.toml is as reasonable as anything else at this point in the game):

[template.default]
template_root_dir = ... # required
template_files = ["*"] # glob pattern of files to perform interpolation on, files that don't match are treated as literals
# the following two keys are useful to be able to use existing examples as templates
target_root = "."  # move all the files from the template_root_dir into here
merge_with = []  # apply templating with the named templates in here

This degree of complexity makes it feel like you're trying to subsume bits of work meant for build.rs, procedural macros, and/or git attributes into the templating system.

I have no idea where you got this from. I was trying to describe a way to combine a bunch of directories into one.

I don't even understand what cargo new --template $any --template-name $something-specific is supposed to do, let alone why I'd want it.

Sorry, I thought this would be obvious.

The idea was that if I had a project that specified a [template.default] (again, either in Cargo.toml or under some unknown key in Template.toml or whatever) that lived at https://www.github.com/me/templates (or ~/templates/my/template) I could do cargo new --template https://www.github.com/me/templates (or cargo new --template ~/templates/my/template). If there was a [template.something-specific] in there then I could do cargo new --template ~/some/file --template-name something-specific).

Does that make sense? Is that something you'd want? I've seen "multiple templates in one repo" requested multiple times in this thread, so I'm pretty sure it's desired.

What I saw, then, in addition to that is that most of the requests in here have been describing a template as a sub-directory of some repo. That works well if you can completely control the directory, but it falls down when you want to use your examples as templates.

My desires

In descending order of priority, I want a system that:

  1. is easy to use, and is hard to use incorrectly (both for template producers and for consumers)
  • makes it easy for me to maintain templates inline with the code that it's a template for
  • encourages testing of templates by default
  • allows me to reduce boilerplate if I have many templates in one repo
  • encourages me to write templates as idiomatic rust (specifically, doesn't punish me for wanting to refactor my existing template to multiple files)

Points 3 and 4 there are what encouraged me to include the target_root and merge_with directories, because they would play very nicely with the existing "test by default" behavior of examples.

Notably, I don't care whether it defaults to "do interpolation" or "don't do interpolation". I can see your argument, if you feel strongly just flip my default to template_files = [].

Obviously I want something that is in-line with the philosophy of the language and tools and that people will use. I don't think that those principles help make decisions -- I think that the 4 points I described are inline with the philosophy, and would encourage use.

Honestly I don't know what "pay for what you use" means in the context of a templating system. It sounds like you're using it to argue for just listing files, instead of having some sort of "include" mechanism?

Examples

With all that said, pretty much everything is coverd by just a couple of examples, because I think that my system is simple.

Assuming all of these exist in $uri

  1. Treat a directory as a template source:

     [template.default]
     template_root_dir = "template"
    

    used as: cargo new --template $uri

  • Treat an example as a template, combining with some existing cargo files -- this is points 2 and three of your "for my projects".

      [template.default]
      template_root_dir = "examples/mine"
      target_root = "src"
      merge_with = ["buildfiles"]
    
      [buildfiles]
      template_root_dir = "templates/buildfiles"
    

    this would allow you to put a Cargo.toml in templates/buildfiles/Cargo.toml and share it with all your examples.

    used as cargo new --template $uri

  • Treat a directory as entirely not-templates:

      [template.default]
      template_root_dir = "src"
      template_files = []
    

    still used as cargo new --template $uri

  • Have multiple possible templates in one repo:

      [template.default]
      template_root_dir = "examples/mine"
      target_root = "src"
      merge_with = ["buildfiles"]
    
      [template.complex]
      template_root_dir = "examples/complex"
      target_root = "src"
      merge_with = ["buildfiles"]
    
      [template.simple]
      template_root_dir = "examples/simple"
      target_root = "src"
      merge_with = ["buildfiles"]
    
      [buildfiles]
      template_root_dir = "templates/buildfiles"
    

    used as any of: cargo new --template $uri or cargo new --template $uri --template-name complex or cargo new --template $uri --template-name simple or even cargo new --template $uri --template-name buildfiles -- because maybe your Cargo.toml is friggin awesome.

1 Like

Actually, my desire number 1:

Suggests that it would be better for my proposed merge_with implementation to error on overwriting files rather than allow allow it.

Having read your post, it seems like most of our disagreement comes down to a breakdown of communication and simple disagreements over what would make good defaults. I'll address your points inline.

Since writing that, I realized that I have two specific issues with escapes:

  1. Given that it can be added later in a backwards-compatible way and given that I've never actually seen such functionality being used in templating languages which provide it, it doesn't feel like there's such an urgent need that it has to be frozen into the design from the very beginning.

  2. Changing the metacharacters on a template-wide basis, rather than for a specific filename match pattern (eg. justfile or *.jinja2) feels like an anti-pattern.

A more reasonable first move would be to add something like Django's {% verbatim %}...{% endverbatim %}, which should probably be offered anyway for good ergonomics, as a viable workaround for "I need to template this file, but the {{ syntax already has meaning" while assessing the real-world pros and cons of allowing the token delimiters to be configurable.

That also is something I don't mind. My ideal system would have a default of template_files = ["Cargo.toml"] and, if literal_files were supported, it would have two properties:

  1. It would be empty and optional by default, so it could be ignored unless you really need it.

  2. It would have very very clear documentation surrounding how it interacts with template_files and one of two behaviours:

    1. literal_files would always override template_files. This would be the most "surprise-proof" and the simpler choice to implement, but would limit the effectiveness of the templating system.
    2. Use a CSS-like "most specific match" system where you can have an include inside an exclude inside an include inside and exclude inside and include with paths like templatedA/literalB/templatedC/literalD/templatedE.file.

That way, the behaviour is easy to explain, there isn't really any additional maintenance burden for the common case, and, relative to the maintenance burden, expressiveness is maximized.

I'll get to target_root and merge_with, since you mention them later, but I want to clarify my perception of template_root_dir.

Am I correct in understanding that it serves the following two purposes?

  1. Move the template into a subdirectory so that no renames key is required to give the template and generated projects different LICENSE and .travis.yml files.
  2. Allow multiple templates in a repo which may also contain a non-template project.

If so, I think that would be perfectly fine with the one change that I'd like to discuss the implications of making template_root_dir optional with the default value of . so it doesn't add to the cognitive burden of "my first template" or templates which are just "a repo which would be a ready-to-start project except that one or more files contain placeholders".

That's a big part of what wasn't clicking for me. I'll get back to it when you address it below.

Ahh. For that, I'd probably take advantage of the fact that HTTP URLs already have the concept of a "fragment identifier" and use something like --template ~/some/file#something-specific. (Python's pip also overloads the fragment identifier to add extra information)

Your approach is at risk of conceptual conflation. What I got stuck on was thinking that, since --template specified the path to the "template" (ie. not the "repo" in a multi-template situation), there was no obvious purpose to --template-name. (Leaving me to wonder if maybe it controlled some some aspect of the generated outpu.)

...and, if you use --template-repo for the URL, it feels like it's bogging down the default case with unnecessary verbosity akin to requiring that local templates be specified via file:// URLs. Hence, my suggestion of --template <url to repo>[#<name of template>].

That makes sense as a rationale.

It sounds like we're mostly in accord, with disagreement on the fine details of how best to accomplish those goals. Heck, #3 is one I just plain forgot to mention.

My main concern here is that merge_with didn't feel at all intuitive and should be brainstormed. Perhaps something like overlay_onto to use a more illustrative term and a phrasing that makes the precedence ordering more clear.

("merge" tends to refer to things like git merge or the merge option in a three-way diff tool, which operate within files, while "overlay" has quite a history, both in filesystems like UnionFS and in things like Bioware's infinity engine, of referring to "this file or that file but not some combination of both" merging of directory trees.)

I'm basically saying that you shouldn't have to think about features unless you want them.

For example, a blacklist-based approach which isn't off by default forces you to have "Did I leave the iron on"-esque thoughts about the extent of the blacklist whenever you get strange, unexpected behaviours after adding or editing a file. ("confusing action at a distance")

By contrast, for a whitelist, what you see in a file is what you literally get, unless you explicitly opt into templating. It's more resistant to tiredness, forgetfulness, distraction, overwork, etc.

That's also my concern with things like a template_root_dir that's required, rather than defaulting to .. It's another piece of complexity that someone shouldn't have to deal with if they just want a simple repo containing a single template.

To be honest, I found target_root to be one of the most under-explained things in your idea and these examples only make its purpose more confusing.

Assuming I'm interpreting its meaning correctly, how is it so useful to generate a repo which is empty outside of src that it would justify a whole new config key when you could just put things in a subdirectory inside template_root_dir? (Especially when that lack of a clearly apparent purpose makes it likely to cause confusion and misunderstanding in template authors.)

Aside from that, I think I've covered everything with my responses to previous segments of your post.

Not necessarily.

The concept of overlays isn't exactly alien and I think that being able to do things like "every template has a default license, and some overlays change which one" justifies having that aspect of them.

[quote]I'm sorry to be the wet blanket, but that looks so complex and over-engineered that, were it my choice, I'd never allow it to be an official part of cargo. [/quote]

I think we're already in a territory where it should be a separate cargo command (like, e.g. clippy).

[quote] The concept of overlays isn't exactly alien and I think that being able to do things like "every template has a default license, and some overlays change which one" justifies having that aspect of them.[/quote] The concept of overlays isn't alien, but it's certainly easy to use incorrectly.

I think the tension is between capability vs. suitability. I strongly prefer a templating system which is not flexible but does ~70-80% of the job as simply as possible. I don't want or need a cascading template system to generate projects. By limiting the creators of templates we can create a lot of value.

I think this is a useful question to answer, but the more important one is a list of explicit use cases. e.g.

  1. Ruby-on-Rails alike for Iron. (afaik a lot of value from Ruby on Rails was the opinionated environment and the quick start template system).
  2. Tokio-on-tracks.
  3. Clap-on-course.
  4. Tokio+Diesel+auth

Can these be served by a single template system or should we be focused on making primitives that enable templating tools to easily be constructed?

I'm enjoying the discussion. I don't have almost any time at the moment to flesh the ideas out into prototypes, but obviously there's a lot of interesting thought going into it. I think you're right, @ssokolow, that it's largely about settling on the defaults.

@ssokolow Thanks for your detailed response! I think that we basically agree on everything except the details and the things that described too tersely to be understandable, which is nice. I especially agree with your points that the specific names that I used aren't self-descriptive (in particular I had a hard time even coming up with a name for target_root_dir, so it makes sense that it'd be hard to understand what it means).

I'm going to go back and describe what I think are the N most common examples that template producers want to be able to express, which might help us come up with a better system than my original strawman proposal. Feel free to skip ahead to "Finishing up (open questions?)" where I think I list all the details that I'm not sure if we agree about. At this point I'm actually a little unclear what you think of my broader proposal :wink:

Quick definition: I'm going to use template repo to refer to the root of a directory tree that has templates (so it's a template repository), it can be a plain local directory with no vcs or a remote version-controlled repository.

Example: Single template repo

In this case the ideal file system layout for the template producer probably looks something like:

./Cargo.toml
  └─ src/some─files

And the Cargo.toml probably includes nothing except for some {{ interpolation }} keys.

I think that the features that would be required to make this work nicely would be to make cargo new --template (or cargo from-template, etc) capable of working with a directory that has no explicit template markers. That is, no Template.toml or [template.*] keys in Cargo.toml.

AFAICT -- without significantly more advanced templating than has been proposed -- this has so little benefit over just a directory that you can directly clone that I'm not sure that it is worth supporting this case explicitly.

Example: Multiple templates in one repo

This is the example that motivates the target_root_dir key, and also the one that introduces most of the conflicting pressures.

What I was imagining as being most desirable situation is a template author's directory tree that looks like:

$project
├── Cargo.toml
├── src/
│   └── lib.rs
└── templates/
    ├── one/
    │   └── lib.rs
    ├── two/
    │    └── (etc)
    └── base/
        └── Cargo.toml

Which, with a Cargo.toml that includes:

[templates.default]
template_root_dir = "templates/one"
template_target_dir = "src"
overlay = ["base"]

[templates.two]
template_root_dir = "templates/two"
template_target_dir = "src"
overlay = ["base"]

[templates.base]
template_root_dir = "templates/base"

Would, given the (hypothetical) arguments cargo new --template $project, produce:

.
├── Cargo.toml
└── src/
    └── lib.rs

That is, the target_root_dir is used to say "place the contents of this template directory relative to target_root_dir instead of relative to the actual template root."

That is... hard to explain. Now that I've tried to explain it I think that it's almost certainly better to just require the correct directory in the template_root_dir. So the above example would look like:

├── Cargo.toml
├── src/
│   └── lib.rs
└── templates/
    ├── one/
    │   └── src/
    │       └── lib.rs
    ├── two/
    │    └── (etc)
    └── base/
        └── Cargo.toml

With a Cargo.toml that has deleted its target_root_dir it would look like:

[templates.default]
template_root_dir = "templates/one"
overlay = ["base"]

[templates.two]
template_root_dir = "templates/two"
overlay = ["base"]

[templates.base]
template_root_dir = "templates/base"

Oh, re-reading this I realize I just convinced myself of your argument that:

how is it so useful to generate a repo which is empty outside of src that it would justify a whole new config key when you could just put things in a subdirectory inside template_root_dir?

I should have read your post better.

Example: Using examples

This is identical to the previous case, except that we substitute examples/ everywhere that templates/.

Finishing up (open questions?)

Mandatory template_root_dir

I think that whether this should default to . or be a required parameter depends on if you think that the single-template repo is the common case. I don't think that it will be the common case, so I think it should be mandatory.

It also brings in questions about whether we should automatically strip Template.toml (or template keys in Cargo.toml) when doing cargo new --template. I think it's easier to document that no stripping happens (makes it easy to create a meta-template template repo!) and require that all templates be in sub-directories if they want to avoid including the template-related items. It's easy enough to specify ..

Interpolation whitelist/blacklist

Regarding the whitelist/blacklist for interpolation: I think that your experience with just hit a kind of surprising edge-case: most template languages use mustaches ({{) for their template characters specifically because they're uncommon in all other programming languages.

I think that your problem would have been avoided if cargo-template had produced a hard error on unknown keys instead of silently replacing them with the empty string. So I agree that it should be whitelist-only, and I think that the interpolation strings should be a hard error if their missing. But I haven't been convinced that something like interpolate_patterns = ["*"] as the default is wrong. Especially given that I would like this to eventually support something like cookiecutter's cookiecutter.json. I'm imagining something like this, eventually, being desired:

[template.default]
template_root_dir = "template"

[template.default.substitutions]
project = "my default project name"
listen_on = "127.0.0.1:3000"
# etc

This is after we have significantly more experience with the system. I'm not certain that it will be desired, but the competing pressures are a more conservative default now and a more verbose system in the long term.

Testing

It would probably be nice long term to build some features into cargo test that guarantees that, if there are templates keys in the Cargo.toml (or if a Template.toml exists) that generated templates can be successfully built, but I don't feel like specifying that right now.

The "template-name" cli parameter

My opinion isn't very strong. I have nightmares from explaining #egg_name=blah to coworkers, so I don't want it to be a named parameter. But repo#non-default-name seems basically okay, if not particularly self-documenting. It's shorter but I'm still partial to --template-name. I wouldn't fight whoever implemented it if they disagreed with me :slight_smile:

Template.toml verse [template] in Cargo.toml

I don't think anyone else has actually brought this up, but: I like that all cargo config happens inside of Cargo.toml, so I'd prefer if it was keys inside Cargo.toml.