midenc_session/flags/
mod.rs

1mod arg_matches;
2mod flag;
3
4#[cfg(not(feature = "std"))]
5use alloc::borrow::Cow;
6use alloc::vec::Vec;
7use core::fmt;
8
9pub use self::{
10    arg_matches::ArgMatches,
11    flag::{CompileFlag, FlagAction},
12};
13use crate::diagnostics::Report;
14
15pub struct CompileFlags {
16    flags: Vec<CompileFlag>,
17    arg_matches: ArgMatches,
18}
19
20#[cfg(feature = "std")]
21impl Default for CompileFlags {
22    fn default() -> Self {
23        Self::new(None::<std::ffi::OsString>).unwrap()
24    }
25}
26
27#[cfg(not(feature = "std"))]
28impl Default for CompileFlags {
29    fn default() -> Self {
30        Self::new(None::<alloc::string::String>).unwrap()
31    }
32}
33
34impl From<ArgMatches> for CompileFlags {
35    fn from(arg_matches: ArgMatches) -> Self {
36        let flags = inventory::iter::<CompileFlag>.into_iter().cloned().collect();
37        Self { flags, arg_matches }
38    }
39}
40
41impl CompileFlags {
42    /// Create a new [CompileFlags] from the given argument vector
43    #[cfg(feature = "std")]
44    pub fn new<I, V>(argv: I) -> Result<Self, Report>
45    where
46        I: IntoIterator<Item = V>,
47        V: Into<std::ffi::OsString> + Clone,
48    {
49        use crate::diagnostics::IntoDiagnostic;
50
51        let flags = inventory::iter::<CompileFlag>.into_iter().cloned().collect();
52        fake_compile_command()
53            .try_get_matches_from(argv)
54            .into_diagnostic()
55            .map(|arg_matches| Self { flags, arg_matches })
56    }
57
58    /// Get [clap::ArgMatches] for registered command-line flags, without a [clap::Command]
59    #[cfg(not(feature = "std"))]
60    pub fn new<I, V>(argv: I) -> Result<Self, Report>
61    where
62        I: IntoIterator<Item = V>,
63        V: Into<Cow<'static, str>> + Clone,
64    {
65        use alloc::collections::{BTreeMap, VecDeque};
66
67        let argv = argv.into_iter().map(|arg| arg.into()).collect::<VecDeque<_>>();
68        let flags = inventory::iter::<CompileFlag>
69            .into_iter()
70            .map(|flag| (flag.name, flag))
71            .collect::<BTreeMap<_, _>>();
72
73        let arg_matches = ArgMatches::parse(argv, &flags)?;
74        let this = Self {
75            flags: flags.values().copied().cloned().collect(),
76            arg_matches,
77        };
78
79        Ok(this)
80    }
81
82    pub fn flags(&self) -> &[CompileFlag] {
83        self.flags.as_slice()
84    }
85
86    /// Get the value of a custom flag with action `FlagAction::SetTrue` or `FlagAction::SetFalse`
87    pub fn get_flag(&self, name: &str) -> bool {
88        self.arg_matches.get_flag(name)
89    }
90
91    /// Get the count of a specific custom flag with action `FlagAction::Count`
92    pub fn get_flag_count(&self, name: &str) -> usize {
93        self.arg_matches.get_count(name) as usize
94    }
95
96    /// Get the remaining [ArgMatches] left after parsing the base session configuration
97    pub fn matches(&self) -> &ArgMatches {
98        &self.arg_matches
99    }
100}
101
102impl fmt::Debug for CompileFlags {
103    #[cfg(feature = "std")]
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        let mut map = f.debug_map();
106        for id in self.arg_matches.ids() {
107            use clap::parser::ValueSource;
108            // Don't print CompilerOptions arg group
109            if id.as_str() == "CompilerOptions" {
110                continue;
111            }
112            // Don't print default values
113            if matches!(self.arg_matches.value_source(id.as_str()), Some(ValueSource::DefaultValue))
114            {
115                continue;
116            }
117            map.key(&id.as_str()).value_with(|f| {
118                let mut list = f.debug_list();
119                if let Some(occurs) =
120                    self.arg_matches.try_get_raw_occurrences(id.as_str()).expect("expected flag")
121                {
122                    list.entries(occurs.flatten());
123                }
124                list.finish()
125            });
126        }
127        map.finish()
128    }
129
130    #[cfg(not(feature = "std"))]
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        let mut map = f.debug_map();
133        for (name, raw_values) in self.arg_matches.iter() {
134            map.key(&name)
135                .value_with(|f| f.debug_list().entries(raw_values.iter()).finish());
136        }
137        map.finish()
138    }
139}
140
141/// Generate a fake compile command for use with default options
142#[cfg(feature = "std")]
143fn fake_compile_command() -> clap::Command {
144    let cmd = clap::Command::new("compile")
145        .no_binary_name(true)
146        .disable_help_flag(true)
147        .disable_version_flag(true)
148        .disable_help_subcommand(true);
149    register_flags(cmd)
150}
151
152/// Register dynamic flags to be shown via `midenc help compile`
153#[cfg(feature = "std")]
154pub fn register_flags(cmd: clap::Command) -> clap::Command {
155    inventory::iter::<CompileFlag>.into_iter().fold(cmd, |cmd, flag| {
156        let arg = clap::Arg::new(flag.name)
157            .long(flag.long.unwrap_or(flag.name))
158            .action(clap::ArgAction::from(flag.action));
159        let arg = if let Some(help) = flag.help {
160            arg.help(help)
161        } else {
162            arg
163        };
164        let arg = if let Some(help_heading) = flag.help_heading {
165            arg.help_heading(help_heading)
166        } else {
167            arg
168        };
169        let arg = if let Some(short) = flag.short {
170            arg.short(short)
171        } else {
172            arg
173        };
174        let arg = if let Some(env) = flag.env {
175            arg.env(env)
176        } else {
177            arg
178        };
179        let arg = if let Some(value) = flag.default_missing_value {
180            arg.default_missing_value(value)
181        } else {
182            arg
183        };
184        let arg = if let Some(value) = flag.default_value {
185            arg.default_value(value)
186        } else {
187            arg
188        };
189        let arg = if let Some(value) = flag.hide {
190            arg.hide(value)
191        } else {
192            arg
193        };
194        cmd.arg(arg)
195    })
196}