Skip to content

Commit 8eb73b5

Browse files
committed
generate: template discovery
1 parent b3c30a3 commit 8eb73b5

File tree

1 file changed

+109
-15
lines changed

1 file changed

+109
-15
lines changed

xtask/src/generate.rs

Lines changed: 109 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
use crate::cargo_generate_config::{CONFIG_FILE_NAME, Config};
2-
use anyhow::{Context, anyhow, bail};
2+
use anyhow::{Context, bail};
33
use cargo_generate::GenerateArgs;
44
use clap::Parser;
55
use indexmap::IndexMap;
66
use log::{debug, info};
7-
use std::collections::HashMap;
7+
use std::collections::{HashMap, HashSet};
88
use std::fmt::{Debug, Display, Formatter};
99
use std::path::{Path, PathBuf};
1010

11-
pub const TEMPLATE_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../graphics");
12-
1311
#[derive(Parser, Debug, Default)]
1412
pub struct Generate {
1513
/// Directory where to place the generated templates.
@@ -31,6 +29,64 @@ pub struct Generate {
3129
filter: Vec<String>,
3230
}
3331

32+
#[derive(Clone, Debug)]
33+
struct TemplateDiscovery {
34+
templates: Vec<Template>,
35+
}
36+
37+
impl TemplateDiscovery {
38+
pub const TEMPLATE_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/..");
39+
40+
fn discover() -> anyhow::Result<Self> {
41+
Self::discover_at(Path::new(Self::TEMPLATE_PATH))
42+
}
43+
44+
/// Our discovery is not as dynamic as `cargo_generate`, written to be just sufficient for this repo and to be
45+
/// *fast*, even with a large target directory. Primarily, that means not scanning through the entire dir tree,
46+
/// instead just using the paths that are explicitly defined in the config.
47+
/// https://github.com/cargo-generate/cargo-generate/issues/1600
48+
fn discover_at(base_dir: &Path) -> anyhow::Result<Self> {
49+
let sub_templates = {
50+
let root_file = base_dir.join(CONFIG_FILE_NAME);
51+
let root: Config = toml::from_str(&std::fs::read_to_string(&root_file)?)?;
52+
let root_template = root
53+
.template
54+
.with_context(|| format!("Expected `template` in `{}`", root_file.display()))?;
55+
root_template.sub_templates.unwrap_or_else(Vec::new)
56+
};
57+
58+
let templates = sub_templates
59+
.into_iter()
60+
.map(|name| {
61+
let template_dir = base_dir.join(&name);
62+
Template::parse(name, template_dir)
63+
})
64+
.collect::<anyhow::Result<Vec<_>>>()?;
65+
let discovery = Self { templates };
66+
debug!("Discovery found: {discovery:#?}");
67+
Ok(discovery)
68+
}
69+
70+
fn split_filter<'a>(&self, filters: impl Iterator<Item = &'a str>) -> Filters {
71+
let mut out = Filters::default();
72+
for filter in filters {
73+
if self.templates.iter().any(|t| t.name == filter) {
74+
out.template_filters.insert(filter.to_string());
75+
} else {
76+
out.placeholder_filters.insert(filter.to_string(), false);
77+
}
78+
}
79+
out
80+
}
81+
}
82+
83+
#[derive(Clone, Debug, Default)]
84+
struct Filters {
85+
template_filters: HashSet<String>,
86+
/// value whether it was used
87+
placeholder_filters: HashMap<String, bool>,
88+
}
89+
3490
#[derive(Clone, Debug)]
3591
struct Template {
3692
name: String,
@@ -57,10 +113,6 @@ impl Debug for Define<'_> {
57113
}
58114

59115
impl Template {
60-
fn graphics() -> anyhow::Result<Self> {
61-
Self::parse("graphics".to_string(), PathBuf::from(TEMPLATE_PATH))
62-
}
63-
64116
fn parse(name: String, template_dir: PathBuf) -> anyhow::Result<Self> {
65117
let config_file = template_dir.join(CONFIG_FILE_NAME);
66118
let config: Config = toml::from_str(&std::fs::read_to_string(&config_file)?)?;
@@ -243,15 +295,57 @@ impl Generate {
243295
self.normalize_env();
244296
let out_base_dir = self.out_base_dir()?;
245297

246-
let template = Template::graphics()?;
247-
let variants = template
248-
.variants(self.filter.iter().map(|f| f.as_str()))
249-
.map_err(|filter| anyhow!("Unknown filter `{filter}`"))?;
250-
let results = variants
298+
let discovery = TemplateDiscovery::discover()?;
299+
let filters = discovery.split_filter(self.filter.iter().map(|a| a.as_str()));
300+
301+
let mut has_unknown_filter = true;
302+
let mut unknown_filter = None;
303+
let results = discovery
304+
.templates
251305
.iter()
252-
.map(|variant| self.generate(&out_base_dir, &template, variant))
306+
.filter(|template| {
307+
filters.template_filters.is_empty()
308+
|| filters.placeholder_filters.contains_key(&template.name)
309+
})
310+
.map(|template| {
311+
let variants_result = template.variants(self.filter.iter().map(|f| f.as_str()));
312+
let variants = match variants_result {
313+
Ok(e) => {
314+
has_unknown_filter = false;
315+
e
316+
}
317+
Err(e) => {
318+
unknown_filter = Some(e);
319+
Vec::new()
320+
}
321+
};
322+
let results = variants
323+
.iter()
324+
.map(|variant| self.generate(&out_base_dir, &template, variant))
325+
.collect::<anyhow::Result<Vec<_>>>()?;
326+
Ok((template, results))
327+
})
253328
.collect::<anyhow::Result<Vec<_>>>()?;
254-
self.execute(results.iter().map(|a| a.as_path()))?;
329+
if has_unknown_filter {
330+
if let Some(filter) = unknown_filter {
331+
bail!("Unknown filter `{filter}`")
332+
} else {
333+
// Only reachable if no templates exist, Or if all templates have been filtered out, but you must filter
334+
// for at least one template for template filtering to even activate, so should be unreachable.
335+
bail!("No templates exist?")
336+
}
337+
}
338+
339+
let results = results
340+
.into_iter()
341+
.flat_map(|(_, a)| a.into_iter())
342+
.collect::<Vec<_>>();
343+
if results.is_empty() {
344+
// reachable with two templates with differing placeholders and filtering for both
345+
bail!("Nothing generated, all variants filtered out");
346+
}
347+
348+
self.execute(results.iter().map(|b| b.as_path()))?;
255349
Ok(())
256350
}
257351
}

0 commit comments

Comments
 (0)