Skip to content

Commit

Permalink
Allow git subfolder as a template.
Browse files Browse the repository at this point in the history
Using cargo generate --git <repo>#<sub-folder, this one
1) Allows to use a subfolder as the actual template (see #47)
2) Allows to e.g. generate a project from an example in a repo (see #78)
3) Avoids the conflicting requirements for cargo-generate#295 and cargo-generate#291, allowing for easier implementation of `--pick` for cargo-generate#291.
  • Loading branch information
taurr committed Jul 25, 2021
1 parent 4e75549 commit 0d0a802
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 68 deletions.
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ anyhow = "1.0.42"
toml = "0.5.8"
thiserror = "1.0.26"
home = "0.5.3"
indoc = "1.0.3"

[dependencies.openssl]
version = "0.10.35"
Expand Down
48 changes: 28 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ cargo install cargo-generate --features vendored-openssl

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

> NOTE: `cargo gen` requires an [cargo alias configuration](#cargo-gen---alias)
> NOTE: `cargo gen` requires an [cargo alias configuration](#cargo-gen---alias)
```sh
cargo generate --git https://github.com/githubusername/mytemplate.git
Expand All @@ -62,6 +62,14 @@ You can also pass the name of your project to the tool using the `--name` or `-n
cargo generate --git https://github.com/githubusername/mytemplate.git --name myproject
```

If the git repository contains multiple templates, the specific subfolder in the git repository may be specified like this:

```sh
cargo generate --git "https://github.com/githubusername/mytemplate.git#<relative-template-path>"
```

> NOTE: The specified `relative-template-path` will be used as the actual template root, whether or not this is actually true!
## git over ssh

New in version [0.7.0] is the support for both public and private and ssh git remote urls.
Expand All @@ -82,12 +90,12 @@ cargo generate --git rustwasm/wasm-pack-template --name mywasm
## 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.
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.

## Favorites

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

Each favorite template is specified in its own section, e.g.:
Expand Down Expand Up @@ -121,22 +129,22 @@ Templates are git repositories whose files contain placeholders. The current
supported 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.
- `{{project-name}}`

this is supplied by either passing the `--name` flag to the command or working with the interactive CLI to supply a name.
- `{{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`

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

[liquid]: https://shopify.github.io/liquid
Expand All @@ -150,7 +158,7 @@ If you have a great template that you'd like to feature here, please [file an is

[file an issue or a PR]: https://github.com/cargo-generate/cargo-generate/issues

### Example for `--bin` and `--lib`
### 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:

Expand All @@ -171,16 +179,16 @@ name = "{{crate_name}}-cli"
{% endif %}
```

Now a user of this template could decide weather they want the binary version by passing `--bin`
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.

## Template defined placeholders

Sometimes templates need to make decisions. For example one might want to conditionally include some code or not.
Sometimes templates need to make decisions. For example one might want to conditionally include some code or not.
Another use case might be that the user of a template should be able to choose out of provided options in an interactive way.
Also, it might be helpful to offer a reasonable default value that the user just simply can use.

Since version [0.6.0](https://github.com/cargo-generate/cargo-generate/releases/tag/v0.6.0) it is possible to use placeholders in a `cargo-generate.toml` that is in the root folder of a template.
Since version [0.6.0](https://github.com/cargo-generate/cargo-generate/releases/tag/v0.6.0) it is possible to use placeholders in a `cargo-generate.toml` that is in the root folder of a template.
Here [an example](https://github.com/sassman/hermit-template-rs):

```toml
Expand Down Expand Up @@ -240,7 +248,7 @@ A placeholder can be of type `string` or `bool`. Boolean types are usually helpf

### `choices` property (optional)

A placeholder can come with a list of choices that the user can choose from.
A placeholder can come with a list of choices that the user can choose from.
It's further also validated at the time when a user generates a project from a template.

```toml
Expand All @@ -258,7 +266,7 @@ default = 'qemu'

### `regex` property (optional)

A `regex` property is a string, that can be used to enforce a certain validation rule. The input dialog will keep repeating
A `regex` property is a string, that can be used to enforce a certain validation rule. The input dialog will keep repeating
until the user entered something that is allowed by this regex.

### Placeholder Examples
Expand All @@ -284,8 +292,8 @@ network_enabled = true
## 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](https://doc.rust-lang.org/cargo/reference/manifest.html#the-exclude-and-include-fields-optional).
If you are using placeholders in a file name, and also wish to use placeholders in the contents of that file,
The behavior mirrors Cargo's Include / Exclude functionality, which is [documented here](https://doc.rust-lang.org/cargo/reference/manifest.html#the-exclude-and-include-fields-optional).
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.

```toml
Expand All @@ -297,7 +305,7 @@ exclude = ["*.c"]

## Cargo gen - alias

`cargo gen` requires an [cargo alias](https://doc.rust-lang.org/cargo/reference/config.html)
`cargo gen` requires an [cargo alias](https://doc.rust-lang.org/cargo/reference/config.html)
to be configured in your `$HOME/.cargo/config` like this:

```toml
Expand All @@ -323,4 +331,4 @@ conditions.
If you want to contribute to `cargo-generate`, please read our [CONTRIBUTING notes].

[CONTRIBUTING notes]: CONTRIBUTING.md
[0.7.0]: https://github.com/cargo-generate/cargo-generate/releases/tag/v0.7.0
[0.7.0]: https://github.com/cargo-generate/cargo-generate/releases/tag/v0.7.0
13 changes: 11 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,16 @@ impl TryFrom<String> for Config {
}

impl Config {
pub(crate) fn new<P: AsRef<Path>>(path: P) -> Result<Option<Self>> {
pub(crate) fn from_path<P: AsRef<Path>>(path: P) -> Result<Option<Self>> {
let path = path.as_ref();

if !path.exists() {
let filename = path.file_name().unwrap();
if let Some(parent) = path.parent().unwrap().parent() {
return Config::from_path(parent.join(filename));
}
}

match fs::read_to_string(path) {
Ok(contents) => Config::try_from(contents)
.map(Option::from)
Expand Down Expand Up @@ -89,7 +98,7 @@ mod tests {
)
.unwrap();

let config = Config::new(&config_path).unwrap().unwrap();
let config = Config::from_path(&config_path).unwrap().unwrap();

assert_eq!(
config.template,
Expand Down
94 changes: 48 additions & 46 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,24 +210,46 @@ pub fn generate(mut args: Args) -> Result<()> {
let project_name = resolve_project_name(&args)?;
let project_dir = create_project_dir(&project_name, args.force)?;

let (template_base_dir, branch) = clone_git_template_into_temp(&args)?;
copy_dir_all(&template_base_dir, &project_dir)?;
drop(template_base_dir);
let (template_base_dir, subfolder, branch) = clone_git_template_into_temp(&args)?;
let template_folder = match subfolder {
Some(subfolder) => template_base_dir.path().join(subfolder),
None => template_base_dir.path().to_owned(),
};

let template_values = args
.template_values_file
.as_ref()
.map(|p| Path::new(p))
.map_or(Ok(Default::default()), |path| get_config_file_values(path))?;
.map(|p| template_folder.as_path().join(p).into_boxed_path())
.map_or(Ok(Default::default()), |path| get_config_file_values(&path))?;

progress(
expand_template(
&project_name,
&template_values,
&project_dir,
&branch,
&template_folder,
&args,
)?;

copy_dir_all(&template_folder, &project_dir)?;

initialize_vcs(args, &project_dir, branch)?;

println!(
"{} {} {} {}",
emoji::SPARKLE,
style("Done!").bold().green(),
style("New project created").bold(),
style(&project_dir.display()).underlined()
);
Ok(())
}

fn initialize_vcs(args: Args, project_dir: &Path, branch: String) -> Result<()> {
match args.vcs {
Vcs::None => {}
Vcs::Git => {
git::init(project_dir, &branch)?;
}
};
Ok(())
}

Expand All @@ -246,19 +268,22 @@ fn resolve_project_name(args: &Args) -> Result<ProjectName> {
}
}

fn clone_git_template_into_temp(args: &Args) -> Result<(TempDir, String)> {
fn clone_git_template_into_temp(args: &Args) -> Result<(TempDir, Option<String>, String)> {
let git_clone_dir = tempfile::tempdir()?;
let git_config = {
let remote = args
.git
.clone()
.with_context(|| "Missing option git, or a favorite")?;
GitConfig::new_abbr(
remote.into(),
args.branch.to_owned(),
args.ssh_identity.clone(),
)?

let remote = args
.git
.clone()
.with_context(|| "Missing option git, or a favorite")?;
let (remote, subfolder) = match remote.rsplit_once("#") {
Some((r, s)) => (r.to_owned(), Some(s.to_owned())),
None => (remote, None),
};
let git_config = GitConfig::new_abbr(
remote.into(),
args.branch.to_owned(),
args.ssh_identity.clone(),
)?;

let branch = git::create(&git_clone_dir.path(), git_config).map_err(|e| {
anyhow!(
Expand All @@ -269,7 +294,7 @@ fn clone_git_template_into_temp(args: &Args) -> Result<(TempDir, String)> {
)
})?;

Ok((git_clone_dir, branch))
Ok((git_clone_dir, subfolder, branch))
}

pub(crate) fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
Expand All @@ -278,9 +303,7 @@ pub(crate) fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Resu
let entry = entry?;
let ty = entry.file_type()?;
if ty.is_dir() {
if entry.file_name() != ".git" {
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
}
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
} else if ty.is_file() {
fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
} else {
Expand Down Expand Up @@ -339,17 +362,16 @@ fn create_project_dir(name: &ProjectName, force: bool) -> Result<PathBuf> {
}
}

fn progress(
fn expand_template(
name: &ProjectName,
template_values: &HashMap<String, toml::Value>,
dir: &Path,
branch: &str,
args: &Args,
) -> Result<()> {
let crate_type: CrateType = args.into();
let template = template::substitute(name, &crate_type, template_values, args.force)?;
let config_path = dir.join(CONFIG_FILE_NAME);
let template_config = Config::new(config_path)?;
let template_config = Config::from_path(config_path)?;
let template = match template_config.as_ref() {
None => Ok(template),
Some(config) => {
Expand All @@ -371,29 +393,9 @@ fn progress(

pbar.join().unwrap();

match args.vcs {
Vcs::None => {}
Vcs::Git => {
git::init(dir, branch)?;
}
}

gen_success(dir);

Ok(())
}

fn gen_success(dir: &Path) {
let dir_string = dir.to_str().unwrap_or("");
println!(
"{} {} {} {}",
emoji::SPARKLE,
style("Done!").bold().green(),
style("New project created").bold(),
style(dir_string).underlined()
);
}

fn rename_warning(name: &ProjectName) {
if !name.is_crate_name() {
info!(
Expand Down

0 comments on commit 0d0a802

Please sign in to comment.