@@ -19,6 +19,9 @@ use crate::{
1919 template:: Template ,
2020} ;
2121
22+ /// A set of partials to be included in a Liquid template.
23+ type PartialsBuilder = liquid:: partials:: EagerCompiler < liquid:: partials:: InMemorySource > ;
24+
2225/// Executes a template to the point where it is ready to generate
2326/// artefacts.
2427pub struct Run {
@@ -104,6 +107,9 @@ impl Run {
104107 } ;
105108 }
106109
110+ let partials = self . partials ( ) ?;
111+ let parser = Self :: template_parser ( partials) ?;
112+
107113 self . validate_provided_values ( ) ?;
108114
109115 let files = match self . template . content_dir ( ) {
@@ -112,15 +118,15 @@ impl Run {
112118 let from = path
113119 . absolutize ( )
114120 . context ( "Failed to get absolute path of template directory" ) ?;
115- self . included_files ( & from, & to) ?
121+ self . included_files ( & from, & to, & parser ) ?
116122 }
117123 } ;
118124
119125 let snippets = self
120126 . template
121127 . snippets ( & self . options . variant )
122128 . iter ( )
123- . map ( |( id, path) | self . snippet_operation ( id, path) )
129+ . map ( |( id, path) | self . snippet_operation ( id, path, & parser ) )
124130 . collect :: < anyhow:: Result < Vec < _ > > > ( ) ?;
125131
126132 let extras = self
@@ -151,7 +157,12 @@ impl Run {
151157 }
152158 }
153159
154- fn included_files ( & self , from : & Path , to : & Path ) -> anyhow:: Result < Vec < RenderOperation > > {
160+ fn included_files (
161+ & self ,
162+ from : & Path ,
163+ to : & Path ,
164+ parser : & liquid:: Parser ,
165+ ) -> anyhow:: Result < Vec < RenderOperation > > {
155166 let gitignore = ".gitignore" ;
156167 let mut all_content_files = Self :: list_content_files ( from) ?;
157168 // If user asked for no_vcs
@@ -164,7 +175,7 @@ impl Run {
164175 let included_files =
165176 self . template
166177 . included_files ( from, all_content_files, & self . options . variant ) ;
167- let template_contents = self . read_all ( included_files) ?;
178+ let template_contents = self . read_all ( included_files, parser ) ?;
168179 let outputs = Self :: to_output_paths ( from, to, template_contents) ;
169180 let file_ops = outputs
170181 . into_iter ( )
@@ -262,7 +273,12 @@ impl Run {
262273 }
263274 }
264275
265- fn snippet_operation ( & self , id : & str , snippet_file : & str ) -> anyhow:: Result < RenderOperation > {
276+ fn snippet_operation (
277+ & self ,
278+ id : & str ,
279+ snippet_file : & str ,
280+ parser : & liquid:: Parser ,
281+ ) -> anyhow:: Result < RenderOperation > {
266282 let snippets_dir = self
267283 . template
268284 . snippets_dir ( )
@@ -271,7 +287,7 @@ impl Run {
271287 let abs_snippet_file = snippets_dir. join ( snippet_file) ;
272288 let file_content = std:: fs:: read ( abs_snippet_file)
273289 . with_context ( || format ! ( "Error reading snippet file {}" , snippet_file) ) ?;
274- let content = TemplateContent :: infer_from_bytes ( file_content, & Self :: template_parser ( ) )
290+ let content = TemplateContent :: infer_from_bytes ( file_content, parser )
275291 . with_context ( || format ! ( "Error parsing snippet file {}" , snippet_file) ) ?;
276292
277293 match id {
@@ -356,11 +372,14 @@ impl Run {
356372 }
357373
358374 // TODO: async when we know where things sit
359- fn read_all ( & self , paths : Vec < PathBuf > ) -> anyhow:: Result < Vec < ( PathBuf , TemplateContent ) > > {
360- let template_parser = Self :: template_parser ( ) ;
375+ fn read_all (
376+ & self ,
377+ paths : Vec < PathBuf > ,
378+ template_parser : & liquid:: Parser ,
379+ ) -> anyhow:: Result < Vec < ( PathBuf , TemplateContent ) > > {
361380 let contents = paths
362381 . iter ( )
363- . map ( |path| TemplateContent :: infer_from_bytes ( std:: fs:: read ( path) ?, & template_parser) )
382+ . map ( |path| TemplateContent :: infer_from_bytes ( std:: fs:: read ( path) ?, template_parser) )
364383 . collect :: < Result < Vec < _ > , _ > > ( ) ?;
365384 // Strip optional .tmpl extension
366385 // Templates can use this if they don't want to store files with their final extensions
@@ -394,16 +413,45 @@ impl Run {
394413 pathdiff:: diff_paths ( source, src_dir) . map ( |rel| ( dest_dir. join ( rel) , cont) )
395414 }
396415
397- fn template_parser ( ) -> liquid:: Parser {
416+ fn template_parser (
417+ partials : impl liquid:: partials:: PartialCompiler ,
418+ ) -> anyhow:: Result < liquid:: Parser > {
398419 let builder = liquid:: ParserBuilder :: with_stdlib ( )
420+ . partials ( partials)
399421 . filter ( crate :: filters:: KebabCaseFilterParser )
400422 . filter ( crate :: filters:: PascalCaseFilterParser )
401423 . filter ( crate :: filters:: DottedPascalCaseFilterParser )
402424 . filter ( crate :: filters:: SnakeCaseFilterParser )
403425 . filter ( crate :: filters:: HttpWildcardFilterParser ) ;
404426 builder
405427 . build ( )
406- . expect ( "can't fail due to no partials support" )
428+ . context ( "Template error: unable to build parser" )
429+ }
430+
431+ fn partials ( & self ) -> anyhow:: Result < impl liquid:: partials:: PartialCompiler > {
432+ let mut partials = PartialsBuilder :: empty ( ) ;
433+
434+ if let Some ( partials_dir) = self . template . partials_dir ( ) {
435+ let partials_dir = std:: fs:: read_dir ( partials_dir)
436+ . context ( "Error opening template partials directory" ) ?;
437+ for partial_file in partials_dir {
438+ let partial_file =
439+ partial_file. context ( "Error scanning template partials directory" ) ?;
440+ if !partial_file. file_type ( ) . is_ok_and ( |t| t. is_file ( ) ) {
441+ anyhow:: bail!( "Non-file in partials directory: {partial_file:?}" ) ;
442+ }
443+ let partial_name = partial_file
444+ . file_name ( )
445+ . into_string ( )
446+ . map_err ( |f| anyhow ! ( "Unusable partial name {f:?}" ) ) ?;
447+ let partial_file = partial_file. path ( ) ;
448+ let content = std:: fs:: read_to_string ( & partial_file)
449+ . with_context ( || format ! ( "Invalid partial template {partial_file:?}" ) ) ?;
450+ partials. add ( partial_name, content) ;
451+ }
452+ }
453+
454+ Ok ( partials)
407455 }
408456}
409457
@@ -418,7 +466,8 @@ mod test {
418466 "kebabby" : "originally-kebabby" ,
419467 "dotted" : "originally.semi-dotted"
420468 } ) ;
421- let parser = Run :: template_parser ( ) ;
469+ let no_partials = super :: PartialsBuilder :: empty ( ) ;
470+ let parser = Run :: template_parser ( no_partials) . unwrap ( ) ;
422471
423472 let eval = |s : & str | parser. parse ( s) . unwrap ( ) . render ( & data) . unwrap ( ) ;
424473
0 commit comments