coreutils/
coreutils.rs

1//! This example implements parsers for a few coreutils tools - they tend to have complicated CLI
2//! rules due to historical reasons
3use bpaf::*;
4
5mod boilerplate {
6    use super::*;
7    pub trait ExtraParsers<T> {
8        /// Assuming parser can consume one of several values - try to consume them all and return
9        /// last one
10        fn my_last(self) -> ParseLast<T>;
11    }
12
13    impl<T> Parser<T> for ParseLast<T> {
14        fn eval(&self, args: &mut State) -> Result<T, Error> {
15            self.inner.eval(args)
16        }
17
18        fn meta(&self) -> Meta {
19            self.inner.meta()
20        }
21    }
22
23    pub struct ParseLast<T> {
24        inner: Box<dyn Parser<T>>,
25    }
26
27    impl<T, P> ExtraParsers<T> for P
28    where
29        P: Parser<T> + 'static,
30        T: 'static,
31    {
32        fn my_last(self) -> ParseLast<T> {
33            let p = self
34                .some("need to specify at least once")
35                .map(|mut xs| xs.pop().unwrap());
36            ParseLast { inner: p.boxed() }
37        }
38    }
39}
40pub mod shared {
41    use super::boilerplate::*;
42    use bpaf::*;
43
44    #[derive(Debug, Clone, Copy, Bpaf)]
45    pub enum Verbosity {
46        /// Display warnings
47        #[bpaf(short, long)]
48        Warn,
49        /// Display only diagnostics
50        #[bpaf(short, long)]
51        Quiet,
52        /// Display status only
53        #[bpaf(short, long)]
54        Status,
55    }
56
57    pub fn parse_verbosity() -> impl Parser<Verbosity> {
58        verbosity().my_last().fallback(Verbosity::Status)
59    }
60
61    pub fn parse_binary() -> impl Parser<bool> {
62        #[derive(Debug, Clone, Copy, Bpaf, Eq, PartialEq)]
63        enum Mode {
64            /// Use binary mode
65            #[bpaf(short, long)]
66            Binary,
67            /// Use text mode
68            #[bpaf(short, long)]
69            Text,
70        }
71        mode()
72            .last()
73            .fallback(Mode::Text)
74            .debug_fallback()
75            .map(|mode| mode == Mode::Binary)
76    }
77}
78
79mod arch {
80    use bpaf::*;
81
82    #[derive(Debug, Clone, Bpaf)]
83    #[bpaf(command)]
84    /// Print machine architecture.
85    pub struct Arch;
86}
87
88mod b2sum {
89    use super::shared::*;
90    use bpaf::*;
91    use std::path::PathBuf;
92
93    #[derive(Debug, Clone, Bpaf)]
94    #[bpaf(command("b2sum"))]
95    /// Print or check BLAKE2 (512-bit) checksums.
96    pub struct B2Sum {
97        #[bpaf(external(parse_binary))]
98        pub binary: bool,
99
100        /// read BLAKE2 sums from the FILEs and check them
101        #[bpaf(short, long)]
102        pub check: bool,
103
104        /// create a BSD-style checksum
105        pub tag: bool,
106
107        #[bpaf(external(parse_verbosity))]
108        pub check_output: Verbosity,
109
110        /// exit non-zero for improperly formatted checksum lines
111        pub strict: bool,
112
113        #[bpaf(positional("FILE"))]
114        pub files: Vec<PathBuf>,
115    }
116}
117
118mod base32 {
119    use bpaf::*;
120    use std::path::PathBuf;
121
122    fn non_zero(val: Option<usize>) -> Option<usize> {
123        val.and_then(|v| (v > 0).then_some(v))
124    }
125
126    #[derive(Debug, Clone, Bpaf)]
127    #[bpaf(command)]
128    /// Base32 encode or decode FILE, or standard input, to standard output.
129    pub struct Base32 {
130        /// decode data
131        #[bpaf(long, short)]
132        pub decode: bool,
133        #[bpaf(long, short)]
134        /// when decoding, ignore non-alphabet characters
135        pub ignore_garbage: bool,
136
137        #[bpaf(
138            long,
139            short,
140            argument("COLS"),
141            optional,
142            map(non_zero),
143            fallback(Some(76)),
144            debug_fallback
145        )]
146        /// wrap encoded lines after COLS character
147        ///  Use 0 to disable line wrapping
148        pub wrap: Option<usize>,
149
150        #[bpaf(positional("FILE"))]
151        /// With no FILE, or when FILE is -, read standard input.
152        pub file: Option<PathBuf>,
153    }
154}
155
156mod basename {
157    use bpaf::*;
158
159    #[derive(Debug, Clone, Bpaf)]
160    #[bpaf(command)]
161    pub struct Basename {
162        /// support multiple arguments and treat each as a NAME
163        #[bpaf(short('a'), long)]
164        pub multiple: bool,
165
166        /// remove a trailing SUFFIX; implies -a
167        #[bpaf(short, long, argument("SUFFIX"), optional)]
168        pub suffix: Option<String>,
169
170        ///  end each output line with NUL, not newline
171        #[bpaf(short, long)]
172        pub zero: bool,
173
174        /// Print NAME with any leading directory components removed.
175        #[bpaf(positional("NAME"), many)]
176        pub names: Vec<String>,
177    }
178
179    pub fn parse_basename() -> impl Parser<Basename> {
180        basename().map(|mut b| {
181            if b.suffix.is_some() {
182                b.multiple = true;
183            }
184            b
185        })
186    }
187}
188
189mod cat {
190    use std::path::PathBuf;
191
192    use bpaf::*;
193
194    #[derive(Debug, Clone, Bpaf)]
195    struct Extra {
196        #[bpaf(short('A'), long)]
197        /// equivalent to -vET
198        show_all: bool,
199
200        #[bpaf(short('b'), long)]
201        /// number nonempty output lines, overrides -n
202        number_nonblank: bool,
203
204        #[bpaf(short('e'))]
205        /// equivalent to -vE
206        show_non_printing_ends: bool,
207    }
208
209    #[derive(Debug, Clone, Bpaf)]
210    #[bpaf(fallback(NumberingMode::None))]
211    pub enum NumberingMode {
212        #[bpaf(hide)]
213        /// Don't number lines, default behavior
214        None,
215
216        /// Number nonempty output lines, overrides -n
217        #[bpaf(short('b'), long("number-nonblank"))]
218        NonEmpty,
219
220        /// Number all output lines
221        #[bpaf(short('n'), long("number"))]
222        All,
223    }
224
225    #[derive(Debug, Clone, Bpaf)]
226    pub struct Cat {
227        #[bpaf(short('T'), long)]
228        /// display TAB characters as ^I
229        pub show_tabs: bool,
230
231        /// display $ at end of each line
232        #[bpaf(short('E'))]
233        pub show_ends: bool,
234
235        /// use ^ and M- notation, except for LFD and TAB
236        #[bpaf(short('n'), long("number"))]
237        show_nonprinting: bool,
238
239        #[bpaf(external(numbering_mode))]
240        pub number: NumberingMode,
241
242        #[bpaf(short('s'), long)]
243        /// suppress repeated empty output lines
244        pub squeeze_blank: bool,
245
246        #[bpaf(positional("FILE"), many)]
247        /// Concatenate FILE(s) to standard output.
248        pub files: Vec<PathBuf>,
249    }
250
251    pub fn parse_cat() -> impl Parser<Cat> {
252        construct!(extra(), cat())
253            .map(|(extra, mut cat)| {
254                if extra.show_all {
255                    cat.show_tabs = true;
256                    cat.show_ends = true;
257                    cat.show_nonprinting = true;
258                }
259                if extra.show_non_printing_ends {
260                    cat.show_nonprinting = true;
261                    cat.show_ends = true;
262                }
263                if extra.number_nonblank {
264                    cat.number = NumberingMode::NonEmpty;
265                }
266                cat
267            })
268            .to_options()
269            .command("cat")
270    }
271}
272
273#[derive(Debug, Clone, Bpaf)]
274#[bpaf(options)]
275pub enum Options {
276    Arch(#[bpaf(external(arch::arch))] arch::Arch),
277    B2Sum(#[bpaf(external(b2sum::b2_sum))] b2sum::B2Sum),
278    Base32(#[bpaf(external(base32::base32))] base32::Base32),
279    Basename(#[bpaf(external(basename::parse_basename))] basename::Basename),
280    Cat(#[bpaf(external(cat::parse_cat))] cat::Cat),
281}
282
283fn main() {
284    let parser = options();
285
286    println!("{:?}", parser.run());
287}