1mod private
3{
4 #[ allow( unused_imports, clippy::wildcard_imports ) ]
5 use crate::tool::*;
6
7 use std::
8 {
9 fs,
10 path::
11 {
12 Path,
13 PathBuf
14 },
15 };
16 use error::untyped::Context;
17 use core::result::Result::Ok;
19
20 #[ derive( Debug ) ]
25 pub struct TemplateHolder
26 {
27 pub files : Vec< TemplateFileDescriptor >,
29 pub parameters : TemplateParameters,
31 pub values : TemplateValues,
33 pub parameter_storage : &'static Path,
36 pub template_name : &'static str,
38 }
39
40 impl TemplateFiles for Vec< TemplateFileDescriptor > {}
41
42
43 impl TemplateHolder
44 {
45 pub fn create_all( self, path : &path::Path ) -> error::untyped::Result< () > {
59 self.files.create_all( path, &self.values )
60 }
61
62 #[ must_use ]
68 pub fn parameters( &self ) -> &TemplateParameters
69 {
70 &self.parameters
71 }
72
73 pub fn set_values( &mut self, values : TemplateValues )
79 {
80 self.values = values;
81 }
82
83 #[ must_use ]
89 pub fn get_values( &self ) -> &TemplateValues
90 {
91 &self.values
92 }
93
94 pub fn get_values_mut( &mut self ) -> &mut TemplateValues
100 {
101 &mut self.values
102 }
103
104 pub fn load_existing_params( &mut self, path : &Path ) -> Option< () >
114 {
115 let data = fs::read_to_string( path.join( self.parameter_storage ) ).ok()?;
116 let document = data.parse::< toml_edit::Document >().ok()?;
117 let parameters : Vec< _ > = self.parameters().descriptors.iter().map( | d | &d.parameter ).cloned().collect();
118 let template_table = document.get( self.template_name )?;
119 for parameter in parameters
120 {
121 let value = template_table.get( ¶meter )
122 .and_then
123 (
124 | item |
125 match item
126 {
127 toml_edit::Item::Value( toml_edit::Value::String( val ) ) => Some( val.value() ),
128 _ => None
129 }
130 );
131 if let Some( value ) = value
132 {
133 self.get_values_mut().insert_if_empty( ¶meter, wca::Value::String( value.into() ) );
134 }
135 }
136 Some( () )
137 }
138
139 #[ must_use ]
141 pub fn get_missing_mandatory( &self ) -> Vec< &str >
142 {
143 let values = self.get_values();
144 self
145 .parameters()
146 .list_mandatory()
147 .into_iter()
148 .filter( | key | values.0.get( *key ).and_then( | val | val.as_ref() ).is_none() )
149 .collect()
150 }
151 }
152
153 pub trait TemplateFiles : IntoIterator< Item = TemplateFileDescriptor > + Sized
157 {
158 fn create_all( self, path : &Path, values : &TemplateValues ) -> error::untyped::Result< () > {
166 let fsw = FileSystem;
167 for file in self
168 {
169 file.create_file( &fsw, path, values )?;
170 }
171 Ok( () )
172 }
173 }
174
175 #[ derive( Debug, Default, former::Former ) ]
177 pub struct TemplateParameters
178 {
179 #[ subform_entry( setter = false ) ]
180 descriptors : Vec< TemplateParameterDescriptor >
181 }
182
183 impl TemplateParameters
184 {
185 #[ must_use ]
187 pub fn values_from_props( &self, props : &wca::executor::Props ) -> TemplateValues
188 {
189 let values = self.descriptors
190 .iter()
191 .map( | d | &d.parameter )
192 .map( | param | ( param.clone(), props.get( param ).cloned() ) )
193 .collect();
194 TemplateValues( values )
195 }
196
197 #[ must_use ]
199 pub fn list_mandatory( &self ) -> Vec< &str >
200 {
201 self.descriptors.iter().filter( | d | d.is_mandatory ).map( | d | d.parameter.as_str() ).collect()
202 }
203 }
204
205 #[ derive( Debug, Default, former::Former ) ]
207 pub struct TemplateParameterDescriptor
208 {
209 parameter : String,
210 is_mandatory : bool,
211 }
212
213 impl< Definition > TemplateParametersFormer< Definition >
214 where
215 Definition : former::FormerDefinition< Storage = < TemplateParameters as former::EntityToStorage >::Storage >,
216 {
217 #[ inline( always ) ]
218 pub fn parameter( self, name : &str ) ->
219 TemplateParameterDescriptorAsSubformer< Self, impl TemplateParameterDescriptorAsSubformerEnd< Self > >
220 {
221 self._descriptors_subform_entry::< TemplateParameterDescriptorFormer< _ >, _ >()
222 .parameter( name )
223 }
224 }
225
226 #[ derive( Debug, Default ) ]
228 pub struct TemplateValues( collection::HashMap< String, Option< wca::Value > > );
229
230 impl TemplateValues
231 {
232 #[ must_use ]
236 pub fn to_serializable( &self ) -> collection::BTreeMap< String, String >
237 {
238 self.0.iter().map
239 (
240 | ( key, value ) |
241 {
242 let value = value.as_ref().map_or
243 (
244 "___UNSPECIFIED___".to_string(),
245 | value |
246 {
247 match value
248 {
249 wca::Value::String( val ) => val.to_string(),
250 wca::Value::Number( val ) => val.to_string(),
251 wca::Value::Bool( val ) => val.to_string(),
252 wca::Value::Path( _ ) |
253 wca::Value::List( _ ) => "unsupported".to_string(),
254 }
255 }
256 );
257 ( key.to_owned(), value )
258 }
259 )
260 .collect()
261 }
262
263 pub fn insert_if_empty( &mut self, key : &str, value : wca::Value )
265 {
266 if self.0.get( key ).and_then( | v | v.as_ref() ).is_none()
267 {
268 self.0.insert( key.into() , Some( value ) );
269 }
270 }
271
272 #[allow(clippy::missing_panics_doc)]
274 pub fn interactive_if_empty( &mut self, key : &str )
275 {
276 if self.0.get( key ).and_then( | v | v.as_ref() ).is_none()
277 {
278 println! ("Parameter `{key}` is not set" );
279 print!( "Enter value: " );
280 use std::io::{ self, Write };
281 io::stdout().flush().unwrap();
282 let mut answer = String::new();
283 io::stdin().read_line( &mut answer ).unwrap();
284 let answer = answer.trim().to_string();
285 self.0.insert( key.into(), Some( wca::Value::String( answer ) ) );
286 }
287 }
288 }
289
290 #[ derive( Debug, former::Former ) ]
295 pub struct TemplateFileDescriptor
296 {
297 path : PathBuf,
298 data : &'static str,
299 is_template : bool,
300 mode : WriteMode
301 }
302
303 impl TemplateFileDescriptor
304 {
305 fn contents< FS : FileSystemPort >( &self, fs : &FS, path : &PathBuf, values : &TemplateValues )
306 -> error::untyped::Result< String >
307 {
308 let contents = if self.is_template
309 {
310 self.build_template( values )?
311 }
312 else
313 {
314 self.data.to_owned()
315 };
316 match self.mode
317 {
318 WriteMode::Rewrite => Ok( contents ),
319 WriteMode::TomlExtend =>
320 {
321 let instruction = FileReadInstruction { path : path.into() };
322 if let Ok( existing_contents ) = fs.read( &instruction )
323 {
324 let document = contents.parse::< toml_edit::Document >().context( "Failed to parse template toml file" )?;
325 let template_items = document.iter();
326 let existing_toml_contents = String::from_utf8( existing_contents ).context( "Failed to read existing toml file as a UTF-8 String" )?;
327 let mut existing_document = existing_toml_contents.parse::< toml_edit::Document >().context( "Failed to parse existing toml file" )?;
328 for ( template_key, template_item ) in template_items
329 {
330 match existing_document.get_mut( template_key )
331 {
332 Some( item ) => template_item.clone_into( item ),
333 None => template_item.clone_into( &mut existing_document[ template_key ] ),
334 }
335 }
336 return Ok( existing_document.to_string() );
337 }
338
339 Ok( contents )
340 }
341 }
342 }
343
344 fn build_template( &self, values : &TemplateValues ) -> error::untyped::Result< String >
346 {
347 let mut handlebars = handlebars::Handlebars::new();
348 handlebars.register_escape_fn( handlebars::no_escape );
349 handlebars.register_template_string( "templated_file", self.data )?;
350 handlebars.render( "templated_file", &values.to_serializable() ).context( "Failed creating a templated file" )
351 }
352
353 fn create_file< FS : FileSystemPort >( &self, fs : &FS, path : &Path, values : &TemplateValues ) -> error::untyped::Result< () > {
355 let path = path.join( &self.path );
356 let data = self.contents( fs, &path, values )?.as_bytes().to_vec();
357 let instruction = FileWriteInstruction { path, data };
358 fs.write( &instruction )?;
359 Ok( () )
360 }
361
362 }
363
364 #[ derive( Debug, Default ) ]
366 pub enum WriteMode
367 {
368 #[default]
370 Rewrite,
371 TomlExtend
377 }
378
379 #[ derive( Debug, former::Former ) ]
381 pub struct TemplateFilesBuilder
382 {
383 #[ subform_entry( setter = true ) ]
385 #[ scalar( setter = false ) ]
386 pub files : Vec< TemplateFileDescriptor >,
387 }
388
389 impl< Description > TemplateFilesBuilderFormer< Description >
390 where
391 Description : former::FormerDefinition< Storage = < TemplateFilesBuilder as former::EntityToStorage >::Storage >,
392 {
393 #[ inline( always ) ]
394 pub fn file( self ) -> TemplateFileDescriptorAsSubformer< Self, impl TemplateFileDescriptorAsSubformerEnd< Self > >
395 {
396 self._files_subform_entry()
397 }
398 }
399
400 #[ derive( Debug ) ]
402 pub struct FileWriteInstruction
403 {
404 path : PathBuf,
405 data : Vec< u8 >,
406 }
407
408 #[ derive( Debug ) ]
410 pub struct FileReadInstruction
411 {
412 path : PathBuf,
413 }
414
415 pub trait FileSystemPort
417 {
418 fn write( &self, instruction : &FileWriteInstruction ) -> error::untyped::Result< () >; fn read( &self, instruction : &FileReadInstruction ) -> error::untyped::Result< Vec< u8 > >; }
428
429 struct FileSystem;
431 impl FileSystemPort for FileSystem
432 {
433 fn write( &self, instruction : &FileWriteInstruction ) -> error::untyped::Result< () > {
435 let FileWriteInstruction { path, data } = instruction;
436 let dir = path.parent().context( "Invalid file path provided" )?;
437 if !dir.exists()
438 {
439 fs::create_dir_all( dir )?;
440 }
441 fs::write( path, data ).context( "Failed creating and writing to file" )
442 }
443
444 fn read( &self, instruction : &FileReadInstruction ) -> error::untyped::Result< Vec< u8 > >
446 {
447 let FileReadInstruction { path } = instruction;
448 fs::read( path ).context( "Failed reading a file" )
449 }
450
451 }
452
453}
454
455crate::mod_interface!
458{
459 orphan use TemplateHolder;
461 orphan use TemplateFiles;
462 orphan use TemplateFileDescriptor;
463 orphan use TemplateParameters;
464 orphan use TemplateParameterDescriptor;
465 orphan use TemplateValues;
466 orphan use TemplateFilesBuilder;
467 orphan use FileSystemPort;
468 orphan use FileWriteInstruction;
469 orphan use FileReadInstruction;
470 orphan use WriteMode;
471}