Skip to main content

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