assemble_core/task/
flags.rs

1//! Add flags for tasks
2
3use log::error;
4use std::any::{type_name, Any, TypeId};
5use std::collections::HashMap;
6use std::error::Error;
7use std::marker::PhantomData;
8use std::ops::Deref;
9use std::str::FromStr;
10
11use crate::{ok, Task};
12
13/// A Flag request is a given flag and an optional list of strings
14pub struct OptionRequest<T> {
15    flag: String,
16    values: Option<Vec<T>>,
17}
18
19impl<T> OptionRequest<T> {
20    /// The name of the flag
21    pub fn flag(&self) -> &str {
22        &self.flag
23    }
24
25    /// The values in the flag.
26    ///
27    /// If no value is taken, then `None` returned.
28    pub fn values(&self) -> Option<&[T]> {
29        self.values.as_ref().map(|vals| &vals[..])
30    }
31}
32
33/// A flag declaration defines how a task should be executed.
34///
35/// All tasks will return a list of flag declarations, which by default is empty. In addition,
36/// tasks are required to respond to all flag requests.
37pub struct OptionDeclaration {
38    flag: String,
39    help: String,
40    takes_value: bool,
41    allow_multiple_values: bool,
42    optional: bool,
43    flag_type: TypeId,
44    parse_value: Option<Box<dyn Fn(&str) -> Result<Box<dyn Any>, Box<dyn Error + Send + Sync>>>>,
45    verify_value: Option<Box<dyn Fn(&str) -> Result<(), Box<dyn Error + Send + Sync>>>>,
46}
47
48impl OptionDeclaration {
49    pub fn flag(&self) -> &str {
50        &self.flag
51    }
52    pub fn help(&self) -> &str {
53        &self.help
54    }
55    pub fn takes_value(&self) -> bool {
56        self.takes_value
57    }
58
59    pub fn is_flag(&self) -> bool {
60        !self.takes_value
61    }
62
63    pub fn allow_multiple_values(&self) -> bool {
64        self.allow_multiple_values
65    }
66    pub fn optional(&self) -> bool {
67        self.optional
68    }
69}
70
71pub struct OptionDeclarations {
72    task_type: String,
73    declarations: HashMap<String, OptionDeclaration>,
74}
75
76impl OptionDeclarations {
77    pub fn new<T: Task, I: IntoIterator<Item = OptionDeclaration>>(options: I) -> Self {
78        Self {
79            task_type: type_name::<T>().to_string(),
80            declarations: options
81                .into_iter()
82                .map(|opt: OptionDeclaration| (opt.flag.to_string(), opt))
83                .collect(),
84        }
85    }
86
87    fn new_weak(&self, map: HashMap<String, Vec<String>>) -> WeakOptionsDecoder {
88        WeakOptionsDecoder {
89            option_dec_string: self.task_type.clone(),
90            fed_options: map,
91        }
92    }
93
94    pub fn slurper(&self) -> OptionsSlurper {
95        OptionsSlurper::new(self)
96    }
97}
98
99impl Deref for OptionDeclarations {
100    type Target = HashMap<String, OptionDeclaration>;
101
102    fn deref(&self) -> &Self::Target {
103        &self.declarations
104    }
105}
106
107/// Build flag declarations
108pub struct OptionDeclarationBuilder<T> {
109    flag: String,
110    help: Option<String>,
111    takes_value: bool,
112    allow_multiple_values: bool,
113    optional: bool,
114    parse_value: Option<Box<dyn Fn(&str) -> Result<Box<dyn Any>, Box<dyn Error + Send + Sync>>>>,
115    verify_value: Option<Box<dyn Fn(&str) -> Result<(), Box<dyn Error + Send + Sync>>>>,
116    _phantom: PhantomData<T>,
117}
118
119impl<T: 'static> OptionDeclarationBuilder<T> {
120    pub fn new(flag: &str) -> Self {
121        Self {
122            flag: flag.to_string(),
123            help: None,
124            takes_value: true,
125            allow_multiple_values: false,
126            optional: false,
127            parse_value: None,
128            verify_value: None,
129            _phantom: PhantomData,
130        }
131    }
132
133    pub fn help(mut self, help: impl AsRef<str>) -> Self {
134        self.help = Some(help.as_ref().to_string());
135        self
136    }
137    pub fn takes_value(mut self, takes_value: bool) -> Self {
138        self.takes_value = takes_value;
139        self
140    }
141    pub fn allow_multiple_values(mut self, allow_multiple_values: bool) -> Self {
142        if allow_multiple_values {
143            self.takes_value = true;
144        }
145        self.allow_multiple_values = allow_multiple_values;
146        self
147    }
148    pub fn optional(mut self, optional: bool) -> Self {
149        self.optional = optional;
150        self
151    }
152
153    pub fn value_parser<F, E>(mut self, func: F) -> Self
154    where
155        F: Fn(&str) -> Result<T, E>,
156        F: 'static,
157        E: 'static + Error + Send + Sync,
158    {
159        let boxed: Box<(dyn Fn(&str) -> Result<Box<dyn Any>, Box<dyn Error + Send + Sync>>)> =
160            Box::new(move |str| {
161                let res = (func)(str);
162                res.map(|t| Box::new(t) as Box<dyn Any>)
163                    .map_err(|e| Box::new(e) as Box<dyn Error + Send + Sync>)
164            });
165        self.parse_value = Some(boxed);
166        self
167    }
168
169    pub fn build(self) -> OptionDeclaration {
170        OptionDeclaration {
171            flag: self.flag,
172            help: self.help.unwrap_or_default(),
173            takes_value: self.takes_value,
174            allow_multiple_values: self.allow_multiple_values,
175            optional: self.optional,
176            flag_type: TypeId::of::<T>(),
177            parse_value: (self.takes_value).then_some(()).map(|_| {
178                self.parse_value
179                    .expect("Value parser required for flags that take a value")
180            }),
181            verify_value: self.verify_value,
182        }
183    }
184}
185
186impl<T: FromStr + 'static> OptionDeclarationBuilder<T>
187where
188    <T as FromStr>::Err: Error + Send + Sync,
189{
190    /// Use the FromStr::from_str as the value parser
191    pub fn use_from_str(self) -> Self {
192        self.value_parser(T::from_str)
193    }
194}
195
196impl OptionDeclarationBuilder<bool> {
197    pub fn flag(flag: &str) -> Self {
198        Self::new(flag).takes_value(false).optional(true)
199    }
200}
201
202/// Slurps a set of options based on a given [`OptionDeclarations`](OptionDeclaration)
203pub struct OptionsSlurper<'dec> {
204    decs: &'dec OptionDeclarations,
205}
206
207fn flag_value_entry() -> Vec<String> {
208    vec![String::new()]
209}
210
211impl<'dec> OptionsSlurper<'dec> {
212    pub fn new(decs: &'dec OptionDeclarations) -> Self {
213        Self { decs }
214    }
215
216    /// From a slice of strings, parses left from right. Slurped arguments are returned in a form of
217    /// hashmap of strings along with the number of slurped values
218    pub fn slurp<S: AsRef<str>>(
219        self,
220        args_slice: &[S],
221    ) -> Result<(WeakOptionsDecoder, usize), OptionsSlurperError> {
222        let mut slurped_args: HashMap<String, Vec<String>> = HashMap::new();
223        let mut count = 0;
224
225        let mut prev_arg: Option<&OptionDeclaration> = None;
226
227        while let Some(arg) = args_slice.get(count).map(<S as AsRef<str>>::as_ref) {
228            if let Some(option) = arg.strip_prefix("--") {
229                // is an option of some sort
230
231                if let Some(prev) = prev_arg {
232                    // can't use -- as value
233                    return Err(OptionsSlurperError::OptionTakesValueButNoneProvided(
234                        prev.flag().to_string(),
235                    ));
236                }
237
238                if let Some(declaration) = self.decs.get(option) {
239                    if declaration.takes_value() {
240                        prev_arg = Some(declaration);
241                    } else {
242                        slurped_args
243                            .entry(option.to_string())
244                            .or_default()
245                            .push(String::new());
246                    }
247                } else {
248                    return Err(OptionsSlurperError::UnknownOption(option.to_string()));
249                }
250            } else {
251                // can either be a value for the previous flag or a different task
252                match prev_arg {
253                    Some(v) => {
254                        let value = arg;
255                        let option = v.flag().to_string();
256                        if v.takes_value() {
257                            slurped_args
258                                .entry(option)
259                                .or_default()
260                                .push(value.to_string());
261                            prev_arg = None;
262                        } else {
263                            break;
264                        }
265                    }
266                    None => {
267                        // A task has been found
268                        break;
269                    }
270                }
271            }
272
273            count += 1;
274        }
275
276        if let Some(prev) = prev_arg {
277            Err(OptionsSlurperError::OptionTakesValueButNoneProvided(
278                prev.flag().to_string(),
279            ))
280        } else {
281            ok!(self.decs.new_weak(slurped_args), count)
282        }
283    }
284}
285
286/// An error occurred while slurping task options
287#[derive(Debug, thiserror::Error)]
288pub enum OptionsSlurperError {
289    #[error("No known option {0}")]
290    UnknownOption(String),
291    #[error("Given option {0} does not take a value")]
292    OptionDoesNotTakeValue(String),
293    #[error("Given option {0} takes a value but none provided")]
294    OptionTakesValueButNoneProvided(String),
295}
296
297/// Provides the struct to decode options
298#[derive(Clone, Debug)]
299pub struct WeakOptionsDecoder {
300    option_dec_string: String,
301    fed_options: HashMap<String, Vec<String>>,
302}
303
304impl WeakOptionsDecoder {
305    /// Try to upgrade this weak options decoder into an actual options decoder
306    pub fn upgrade(self, decs: &OptionDeclarations) -> Result<OptionsDecoder, OptionsDecoderError> {
307        if decs.task_type != self.option_dec_string {
308            Err(OptionsDecoderError::OptionsMismatch)
309        } else {
310            Ok(OptionsDecoder {
311                decs,
312                fed_options: self.fed_options,
313            })
314        }
315    }
316}
317
318/// Decodes a list of arbitrary arguments into a set of [`OptionRequest`s](OptionRequest).
319///
320/// Takes a set of
321pub struct OptionsDecoder<'dec> {
322    decs: &'dec OptionDeclarations,
323    fed_options: HashMap<String, Vec<String>>,
324}
325
326pub type DecoderResult<T> = Result<T, OptionsDecoderError>;
327
328impl<'dec> OptionsDecoder<'dec> {
329    /// gets the declaration and verifies its correct
330    fn get_option_dec(&self, flag: &str) -> DecoderResult<&OptionDeclaration> {
331        let dec = self
332            .decs
333            .get(flag)
334            .ok_or(OptionsDecoderError::InvalidOption(flag.to_string()))?;
335
336        if dec.is_flag()
337            && self
338                .fed_options
339                .get(flag)
340                .map(|v| v != &flag_value_entry())
341                .unwrap_or(false)
342        {
343            error!("flag has bad value: {:?}", self.fed_options.get(flag));
344            return Err(OptionsDecoderError::OptionDoesNotTakeValue(
345                flag.to_string(),
346            ));
347        }
348
349        if dec.takes_value() {
350            match self.fed_options.get(flag) {
351                None => {
352                    if !dec.optional() {
353                        return Err(OptionsDecoderError::OptionNotOptional(flag.to_string()));
354                    }
355                }
356                Some(v) => {
357                    if v.len() > 1 && !dec.allow_multiple_values() {
358                        return Err(OptionsDecoderError::OptionDoesNotTakeMultipleValue(
359                            flag.to_string(),
360                        ));
361                    }
362                }
363            }
364        }
365
366        Ok(dec)
367    }
368
369    /// Check whether a flag is present
370    pub fn flag_present(&self, flag: &str) -> DecoderResult<bool> {
371        let dec = self.get_option_dec(flag)?;
372        if dec.is_flag() {
373            if let Some(entry) = self.fed_options.get(flag) {
374                assert_eq!(entry, &flag_value_entry(), "flag improperly set in options");
375                ok!(true)
376            } else {
377                ok!(false)
378            }
379        } else {
380            Err(OptionsDecoderError::OptionNotFlag(flag.to_string()))
381        }
382    }
383
384    /// Get a value for a flag, if present. Only returns Ok(None) if the option is optional, otherwise
385    /// an Err() is returned.
386    ///
387    /// Will also return an error if multiple values are defined for this type.
388    pub fn get_value<T: 'static>(&self, flag: &str) -> DecoderResult<Option<T>> {
389        let declaration = self.get_option_dec(flag)?;
390        if declaration.is_flag() {
391            return Err(OptionsDecoderError::OptionDoesNotTakeValue(
392                flag.to_string(),
393            ));
394        }
395
396        if declaration.allow_multiple_values() {
397            return Err(OptionsDecoderError::OptionTakesMultipleValue(
398                flag.to_string(),
399            ));
400        }
401
402        if declaration.flag_type != TypeId::of::<T>() {
403            return Err(OptionsDecoderError::incorrect_type::<T>(flag));
404        }
405
406        if let Some(values) = self.fed_options.get(flag) {
407            let value = values.first().unwrap();
408            let parse_function = declaration.parse_value.as_ref().unwrap();
409            let parsed: Box<dyn Any> = parse_function(value)?;
410            Ok(Some(*parsed.downcast::<T>().unwrap()))
411        } else if declaration.optional {
412            Ok(None)
413        } else {
414            Err(OptionsDecoderError::OptionNotOptional(flag.to_string()))
415        }
416    }
417
418    /// Get all values for a flag, if present. Only returns Ok(None) if the option is optional, otherwise
419    /// an Err() is returned.
420    ///
421    /// Will also return an error if this option does not accept multiple values.
422    pub fn get_values<T: 'static>(&self, flag: &str) -> DecoderResult<Option<Vec<T>>> {
423        let declaration = self.get_option_dec(flag)?;
424        if declaration.is_flag() {
425            return Err(OptionsDecoderError::OptionDoesNotTakeValue(
426                flag.to_string(),
427            ));
428        }
429
430        if !declaration.allow_multiple_values() {
431            return Err(OptionsDecoderError::OptionDoesNotTakeMultipleValue(
432                flag.to_string(),
433            ));
434        }
435
436        if declaration.flag_type != TypeId::of::<T>() {
437            return Err(OptionsDecoderError::incorrect_type::<T>(flag));
438        }
439
440        if let Some(values) = self.fed_options.get(flag) {
441            let parse_function = declaration.parse_value.as_ref().unwrap();
442            let output: Vec<T> = values
443                .iter()
444                .map(|value| {
445                    let parsed: Box<dyn Any> = parse_function(value)?;
446                    Ok(*parsed.downcast::<T>().unwrap())
447                })
448                .collect::<DecoderResult<Vec<_>>>()?;
449            Ok(Some(output))
450        } else if declaration.optional {
451            Ok(None)
452        } else {
453            Err(OptionsDecoderError::OptionNotOptional(flag.to_string()))
454        }
455    }
456}
457
458/// An error occurred while decoding task options
459#[derive(Debug, thiserror::Error)]
460pub enum OptionsDecoderError {
461    #[error("Given options declarations not meant for this options decoder")]
462    OptionsMismatch,
463    #[error("Given option {0} is not a flag")]
464    OptionNotFlag(String),
465    #[error("Given option {0} requires a value to be provided")]
466    OptionNotOptional(String),
467    #[error("Given option {0} does not take a value")]
468    OptionDoesNotTakeValue(String),
469    #[error("Given option {0} does not take a value")]
470    OptionDoesNotTakeMultipleValue(String),
471    #[error("Given option {0} takes multiple values")]
472    OptionTakesMultipleValue(String),
473    #[error("Given string is not a registered option")]
474    InvalidOption(String),
475    #[error(transparent)]
476    ValueParserError(#[from] Box<dyn Error + Send + Sync>),
477    #[error("Given option {option} does not take values of type {given_type}")]
478    IncorrectType { given_type: String, option: String },
479}
480
481impl OptionsDecoderError {
482    pub fn incorrect_type<T>(option: &str) -> Self {
483        Self::IncorrectType {
484            given_type: type_name::<T>().to_string(),
485            option: option.to_string(),
486        }
487    }
488}
489
490#[cfg(test)]
491mod slurper_tests {
492    use super::*;
493    use crate::defaults::tasks::Empty;
494    use more_collection_macros::map;
495
496    #[test]
497    fn slurp_flags() {
498        let args = ["--flag1", "--flag2", "task"];
499        let options = OptionDeclarations::new::<Empty, _>([
500            OptionDeclarationBuilder::flag("flag1").build(),
501            OptionDeclarationBuilder::flag("flag2").build(),
502        ]);
503
504        let slurper = OptionsSlurper::new(&options);
505        let (map, slurped) = slurper.slurp(&args).unwrap();
506        assert_eq!(slurped, 2, "only 2 values should be slurped");
507        assert_eq!(
508            map.fed_options,
509            map![
510                "flag1".to_string() => flag_value_entry(),
511                "flag2".to_string() => flag_value_entry()
512            ]
513        );
514    }
515
516    #[test]
517    fn slurp_values() {
518        let args = ["--flag1", "value1", "--flag2", "value2", "task"];
519        let options = OptionDeclarations::new::<Empty, _>([
520            OptionDeclarationBuilder::<String>::new("flag1")
521                .use_from_str()
522                .build(),
523            OptionDeclarationBuilder::<String>::new("flag2")
524                .use_from_str()
525                .build(),
526        ]);
527
528        let slurper = OptionsSlurper::new(&options);
529        let (map, slurped) = slurper.slurp(&args).unwrap();
530        assert_eq!(slurped, 4, "only 4 values should be slurped");
531        assert_eq!(
532            map.fed_options,
533            map![
534                "flag1".to_string() => vec!["value1".to_string()],
535                "flag2".to_string() => vec!["value2".to_string()]
536            ]
537        );
538    }
539
540    #[test]
541    fn mix_slurp_values_and_flags() {
542        let args = ["--flag1", "value1", "--flag3", "--flag2", "value2", "task"];
543        let options = OptionDeclarations::new::<Empty, _>([
544            OptionDeclarationBuilder::<String>::new("flag1")
545                .use_from_str()
546                .build(),
547            OptionDeclarationBuilder::<String>::new("flag2")
548                .use_from_str()
549                .build(),
550            OptionDeclarationBuilder::flag("flag3").build(),
551        ]);
552
553        let slurper = OptionsSlurper::new(&options);
554        let (map, slurped) = slurper.slurp(&args).unwrap();
555        assert_eq!(slurped, 5, "only 5 values should be slurped");
556        assert_eq!(
557            map.fed_options,
558            map![
559                "flag1".to_string() => vec!["value1".to_string()],
560                "flag2".to_string() => vec!["value2".to_string()],
561                "flag3".to_string() => flag_value_entry()
562            ]
563        );
564    }
565
566    #[test]
567    fn slurp_multiple_values() {
568        let args = ["--flag1", "value1", "--flag1", "value2"];
569        let options =
570            OptionDeclarations::new::<Empty, _>([OptionDeclarationBuilder::<String>::new("flag1")
571                .use_from_str()
572                .allow_multiple_values(true)
573                .build()]);
574
575        let slurper = OptionsSlurper::new(&options);
576        let (map, slurped) = slurper.slurp(&args).unwrap();
577        assert_eq!(slurped, 4, "only 4 values should be slurped");
578        assert_eq!(
579            map.fed_options,
580            map![
581                "flag1".to_string() => vec!["value1".to_string(), "value2".to_string()],
582            ]
583        );
584    }
585
586    #[test]
587    fn flag_not_a_value() {
588        let args = ["--flag1", "--flag2", "task"];
589        let options = OptionDeclarations::new::<Empty, _>([
590            OptionDeclarationBuilder::<String>::new("flag1")
591                .use_from_str()
592                .build(),
593            OptionDeclarationBuilder::flag("flag2").build(),
594        ]);
595
596        let slurper = OptionsSlurper::new(&options);
597        assert!(slurper.slurp(&args).is_err());
598    }
599
600    #[test]
601    fn option_missing_value() {
602        let args = ["--flag1"];
603        let options = OptionDeclarations::new::<Empty, _>([
604            OptionDeclarationBuilder::<String>::new("flag1")
605                .use_from_str()
606                .build(),
607            OptionDeclarationBuilder::flag("flag2").build(),
608        ]);
609
610        let slurper = OptionsSlurper::new(&options);
611        assert!(slurper.slurp(&args).is_err());
612    }
613}
614
615#[cfg(test)]
616mod decoder_tests {
617    use super::*;
618    use crate::defaults::tasks::Empty;
619
620    #[test]
621    fn can_use_value() {
622        let options =
623            OptionDeclarations::new::<Empty, _>([OptionDeclarationBuilder::<i32>::new("count")
624                .use_from_str()
625                .build()]);
626
627        let slurper = options.slurper();
628        let (weak, _) = slurper.slurp(&["--count", "15"]).unwrap();
629
630        let upgraded = weak.upgrade(&options).unwrap();
631
632        let count = upgraded.get_value::<i32>("count").unwrap();
633        assert_eq!(count, Some(15));
634    }
635
636    #[test]
637    fn optional_not_required() {
638        let options =
639            OptionDeclarations::new::<Empty, _>([OptionDeclarationBuilder::<i32>::new("count")
640                .use_from_str()
641                .optional(true)
642                .build()]);
643
644        let slurper = options.slurper();
645        let (weak, _) = slurper.slurp::<&str>(&[]).unwrap();
646
647        let upgraded = weak.upgrade(&options).unwrap();
648
649        let count = upgraded.get_value::<i32>("count").unwrap();
650        assert_eq!(count, None);
651    }
652
653    #[test]
654    fn can_use_multiple_values() {
655        let options =
656            OptionDeclarations::new::<Empty, _>([OptionDeclarationBuilder::<i32>::new("count")
657                .use_from_str()
658                .allow_multiple_values(true)
659                .build()]);
660
661        let slurper = options.slurper();
662        let (weak, _) = slurper.slurp(&["--count", "15", "--count", "16"]).unwrap();
663
664        let upgraded = weak.upgrade(&options).unwrap();
665
666        let count = upgraded.get_values::<i32>("count").unwrap();
667        assert_eq!(count, Some(vec![15, 16]));
668    }
669
670    #[test]
671    fn optional_not_required_multiples() {
672        let options =
673            OptionDeclarations::new::<Empty, _>([OptionDeclarationBuilder::<i32>::new("count")
674                .use_from_str()
675                .optional(true)
676                .allow_multiple_values(true)
677                .build()]);
678
679        let slurper = options.slurper();
680        let (weak, _) = slurper.slurp::<&str>(&[]).unwrap();
681
682        let upgraded = weak.upgrade(&options).unwrap();
683
684        let count = upgraded.get_values::<i32>("count").unwrap();
685        assert_eq!(count, None);
686    }
687
688    #[test]
689    fn slurp_multiple_values_can_cause_errors_if_invalid() {
690        let args = ["--flag1", "value1", "--flag1", "value2"];
691        let options =
692            OptionDeclarations::new::<Empty, _>([OptionDeclarationBuilder::<String>::new("flag1")
693                .use_from_str()
694                .takes_value(true)
695                .allow_multiple_values(false)
696                .build()]);
697
698        let slurper = OptionsSlurper::new(&options);
699        let (weak, _) = slurper.slurp(&args).unwrap();
700
701        let upgraded = weak.upgrade(&options).unwrap();
702
703        let err = upgraded.get_value::<String>("flag1");
704        if let Err(OptionsDecoderError::OptionDoesNotTakeMultipleValue(_)) = err {
705        } else {
706            panic!("Should cause an error, does take multiple values error")
707        }
708    }
709
710    #[test]
711    fn non_optional_cause_errors_if_missing() {
712        let args = ["--flag1", "value1"];
713        let options = OptionDeclarations::new::<Empty, _>([
714            OptionDeclarationBuilder::<String>::new("flag1")
715                .use_from_str()
716                .build(),
717            OptionDeclarationBuilder::<String>::new("flag2")
718                .use_from_str()
719                .build(),
720        ]);
721
722        let slurper = OptionsSlurper::new(&options);
723        let (weak, _) = slurper.slurp(&args).unwrap();
724
725        let upgraded = weak.upgrade(&options).unwrap();
726
727        let err = upgraded.get_value::<String>("flag2");
728        if let Err(OptionsDecoderError::OptionNotOptional(_)) = err {
729        } else {
730            panic!("Should cause an error, flag2 is not optional")
731        }
732    }
733}