//! This example implements parsers for a few coreutils tools - they tend to have complicated CLI //! rules due to historical reasons use bpaf::*; mod boilerplate { use super::*; pub trait ExtraParsers { /// Assuming parser can consume one of several values - try to consume them all and return /// last one fn my_last(self) -> ParseLast; } impl Parser for ParseLast { fn eval(&self, args: &mut State) -> Result { self.inner.eval(args) } fn meta(&self) -> Meta { self.inner.meta() } } pub struct ParseLast { inner: Box>, } impl ExtraParsers for P where P: Parser + 'static, T: 'static, { fn my_last(self) -> ParseLast { let p = self .some("need to specify at least once") .map(|mut xs| xs.pop().unwrap()); ParseLast { inner: p.boxed() } } } } pub mod shared { use super::boilerplate::*; use bpaf::*; #[derive(Debug, Clone, Copy, Bpaf)] pub enum Verbosity { /// Display warnings #[bpaf(short, long)] Warn, /// Display only diagnostics #[bpaf(short, long)] Quiet, /// Display status only #[bpaf(short, long)] Status, } pub fn parse_verbosity() -> impl Parser { verbosity().my_last().fallback(Verbosity::Status) } pub fn parse_binary() -> impl Parser { #[derive(Debug, Clone, Copy, Bpaf, Eq, PartialEq)] enum Mode { /// Use binary mode #[bpaf(short, long)] Binary, /// Use text mode #[bpaf(short, long)] Text, } mode() .last() .fallback(Mode::Text) .debug_fallback() .map(|mode| mode == Mode::Binary) } } mod arch { use bpaf::*; #[derive(Debug, Clone, Bpaf)] #[bpaf(command)] /// Print machine architecture. pub struct Arch; } mod b2sum { use super::shared::*; use bpaf::*; use std::path::PathBuf; #[derive(Debug, Clone, Bpaf)] #[bpaf(command("b2sum"))] /// Print or check BLAKE2 (512-bit) checksums. pub struct B2Sum { #[bpaf(external(parse_binary))] pub binary: bool, /// read BLAKE2 sums from the FILEs and check them #[bpaf(short, long)] pub check: bool, /// create a BSD-style checksum pub tag: bool, #[bpaf(external(parse_verbosity))] pub check_output: Verbosity, /// exit non-zero for improperly formatted checksum lines pub strict: bool, #[bpaf(positional("FILE"))] pub files: Vec, } } mod base32 { use bpaf::*; use std::path::PathBuf; fn non_zero(val: Option) -> Option { val.and_then(|v| (v > 0).then_some(v)) } #[derive(Debug, Clone, Bpaf)] #[bpaf(command)] /// Base32 encode or decode FILE, or standard input, to standard output. pub struct Base32 { /// decode data #[bpaf(long, short)] pub decode: bool, #[bpaf(long, short)] /// when decoding, ignore non-alphabet characters pub ignore_garbage: bool, #[bpaf( long, short, argument("COLS"), optional, map(non_zero), fallback(Some(76)), debug_fallback )] /// wrap encoded lines after COLS character /// Use 0 to disable line wrapping pub wrap: Option, #[bpaf(positional("FILE"))] /// With no FILE, or when FILE is -, read standard input. pub file: Option, } } mod basename { use bpaf::*; #[derive(Debug, Clone, Bpaf)] #[bpaf(command)] pub struct Basename { /// support multiple arguments and treat each as a NAME #[bpaf(short('a'), long)] pub multiple: bool, /// remove a trailing SUFFIX; implies -a #[bpaf(short, long, argument("SUFFIX"), optional)] pub suffix: Option, /// end each output line with NUL, not newline #[bpaf(short, long)] pub zero: bool, /// Print NAME with any leading directory components removed. #[bpaf(positional("NAME"), many)] pub names: Vec, } pub fn parse_basename() -> impl Parser { basename().map(|mut b| { if b.suffix.is_some() { b.multiple = true; } b }) } } mod cat { use std::path::PathBuf; use bpaf::*; #[derive(Debug, Clone, Bpaf)] struct Extra { #[bpaf(short('A'), long)] /// equivalent to -vET show_all: bool, #[bpaf(short('b'), long)] /// number nonempty output lines, overrides -n number_nonblank: bool, #[bpaf(short('e'))] /// equivalent to -vE show_non_printing_ends: bool, } #[derive(Debug, Clone, Bpaf)] #[bpaf(fallback(NumberingMode::None))] pub enum NumberingMode { #[bpaf(hide)] /// Don't number lines, default behavior None, /// Number nonempty output lines, overrides -n #[bpaf(short('b'), long("number-nonblank"))] NonEmpty, /// Number all output lines #[bpaf(short('n'), long("number"))] All, } #[derive(Debug, Clone, Bpaf)] pub struct Cat { #[bpaf(short('T'), long)] /// display TAB characters as ^I pub show_tabs: bool, /// display $ at end of each line #[bpaf(short('E'))] pub show_ends: bool, /// use ^ and M- notation, except for LFD and TAB #[bpaf(short('n'), long("number"))] show_nonprinting: bool, #[bpaf(external(numbering_mode))] pub number: NumberingMode, #[bpaf(short('s'), long)] /// suppress repeated empty output lines pub squeeze_blank: bool, #[bpaf(positional("FILE"), many)] /// Concatenate FILE(s) to standard output. pub files: Vec, } pub fn parse_cat() -> impl Parser { construct!(extra(), cat()) .map(|(extra, mut cat)| { if extra.show_all { cat.show_tabs = true; cat.show_ends = true; cat.show_nonprinting = true; } if extra.show_non_printing_ends { cat.show_nonprinting = true; cat.show_ends = true; } if extra.number_nonblank { cat.number = NumberingMode::NonEmpty; } cat }) .to_options() .command("cat") } } #[derive(Debug, Clone, Bpaf)] #[bpaf(options)] pub enum Options { Arch(#[bpaf(external(arch::arch))] arch::Arch), B2Sum(#[bpaf(external(b2sum::b2_sum))] b2sum::B2Sum), Base32(#[bpaf(external(base32::base32))] base32::Base32), Basename(#[bpaf(external(basename::parse_basename))] basename::Basename), Cat(#[bpaf(external(cat::parse_cat))] cat::Cat), } fn main() { let parser = options(); println!("{:?}", parser.run()); }