11use crate :: cargo_generate_config:: { CONFIG_FILE_NAME , Config } ;
2- use anyhow:: { Context , anyhow , bail} ;
2+ use anyhow:: { Context , bail} ;
33use cargo_generate:: GenerateArgs ;
44use clap:: Parser ;
55use indexmap:: IndexMap ;
66use log:: { debug, info} ;
7- use std:: collections:: HashMap ;
7+ use std:: collections:: { HashMap , HashSet } ;
88use std:: fmt:: { Debug , Display , Formatter } ;
99use std:: path:: { Path , PathBuf } ;
1010
11- pub const TEMPLATE_PATH : & str = concat ! ( env!( "CARGO_MANIFEST_DIR" ) , "/../graphics" ) ;
12-
1311#[ derive( Parser , Debug , Default ) ]
1412pub 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 ) ]
3591struct Template {
3692 name : String ,
@@ -57,10 +113,6 @@ impl Debug for Define<'_> {
57113}
58114
59115impl 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