diff options
Diffstat (limited to 'src/cli.rs')
-rw-r--r-- | src/cli.rs | 213 |
1 files changed, 161 insertions, 52 deletions
@@ -3,7 +3,7 @@ use std::env; use crate::{Error, Input, Result}; // NOTE: I assumed I should keep this "DIY". I would most likely use some "de facto" external -// libraries like `clap`, `serde`, ect. +// libraries, e.g. `clap`, `serde`, ect. #[derive(Debug, Default)] pub struct Cli { @@ -13,81 +13,190 @@ pub struct Cli { impl Cli { pub fn parse() -> Result<Self> { - env::args() + let mut cli = env::args() .skip(1) - .try_fold(Cli::default(), |mut cli, opt| { - match opt.as_str() { - "-" => cli.files.push(Input::Stdin), - o if o.starts_with("--") => cli.opts.parse_long(o)?, - o if o.starts_with('-') => cli.opts.parse_short(o)?, - s => cli.files.push(s.into()), - }; - Ok(cli) - }) - .map(|mut cli| { - if cli.files.is_empty() { - cli.files.push(Input::Stdin); - } - cli - }) + .try_fold(Cli::default(), Cli::parse_arg)?; + + if cli.files.is_empty() { + cli.files.push(Input::Stdin); + } + + Ok(cli) + } + + fn parse_arg(mut self, opt: String) -> Result<Self> { + match opt.as_str() { + "-" => self.files.push(Input::Stdin), + o if o.starts_with('-') => self.opts.parse_opt(o)?, + s => self.files.push(s.into()), + }; + Ok(self) } } #[derive(Debug, Default)] pub struct Opts { - /// number nonempty output lines, overrides -n pub number_nonblank: bool, + pub show_ends: bool, + pub number: bool, + pub squeeze_blank: bool, + pub show_tabs: bool, + pub show_nonprinting: bool, + pub help: bool, + pub version: bool, +} + +impl Opts { + fn parse_opt(&mut self, s: &str) -> Result<()> { + if s.starts_with("--") { + self.parse_long(s) + } else if s.starts_with('-') { + self.parse_short(s) + } else { + Err(Error::Opts(format!("`{}` is not an option.", s))) + } + } + + fn parse_short(&mut self, short: &str) -> Result<()> { + short + .trim_start_matches('-') + .chars() + .map(Into::into) + .try_for_each(|opt| self.set_opt(opt)) + } + + fn parse_long(&mut self, long: &str) -> Result<()> { + let opt = long.trim_start_matches('-').into(); + self.set_opt(opt) + } + + fn set_opt(&mut self, opt: Opt) -> Result<()> { + match opt { + Opt::ShowAll => { + self.show_ends = true; + self.show_tabs = true; + self.show_nonprinting = true; + } + Opt::NumberNonblank => self.number_nonblank = true, + Opt::ShowEndsNonprinting => { + self.show_ends = true; + self.show_nonprinting = true; + } + Opt::ShowEnds => self.show_ends = true, + Opt::Number => self.number = true, + Opt::SqueezeBlank => self.squeeze_blank = true, + Opt::ShowTabsNonprinting => { + self.show_tabs = true; + self.show_nonprinting = true; + } + Opt::ShowTabs => self.show_tabs = true, + Opt::Ignored => {} + Opt::ShowNonprinting => self.show_nonprinting = true, + Opt::Help => self.help = true, + Opt::Version => self.version = true, + Opt::Unknown(s) => return Err(Error::Opts(s)), + }; + Ok(()) + } +} + +#[derive(Debug)] +enum Opt { + /// -A, --show-all + /// + /// equivalent to -vET + ShowAll, + + /// -b, --number-nonblank + /// + /// number nonempty output lines, overrides -n + NumberNonblank, + + /// -e + /// + /// equivalent to -vE + ShowEndsNonprinting, + /// -E, --show-ends + /// /// display $ at end of each line - pub show_ends: bool, + ShowEnds, + /// -n, --number + /// /// number all output lines - pub number: bool, + Number, + /// -s, --squeeze-blank + /// /// suppress repeated empty output lines - pub squeeze_blank: bool, + SqueezeBlank, + + /// -t + /// + /// equivalent to -vT + ShowTabsNonprinting, + /// -T, --show-tabs + /// /// display TAB characters as ^I - pub show_tabs: bool, + ShowTabs, + + /// -u + /// + /// (ignored) + Ignored, + /// -v, --show-nonprinting + /// /// use ^ and M- notation, except for LFD and TAB - pub show_nonprinting: bool, + ShowNonprinting, - /// display help and exit - pub help: bool, + /// --help + /// + /// display this help and exit + Help, + /// --version + /// /// output version information and exit - pub version: bool, + Version, + + /// unrecognized option + Unknown(String), } -impl Opts { - fn parse_long(&mut self, s: &str) -> Result<()> { - match s { - "--number-nonblank" => self.number_nonblank = true, - "--show-ends" => self.show_ends = true, - "--number" => self.number = true, - "--squeeze-blank" => self.squeeze_blank = true, - "--show-tabs" => self.show_tabs = true, - "--show-nonprinting" => self.show_nonprinting = true, - "--help" => self.help = true, - "--version" => self.version = true, - s => return Err(Error::Opts(s.to_string())), - }; - Ok(()) +impl From<char> for Opt { + fn from(value: char) -> Self { + match value { + 'A' => Self::ShowAll, + 'b' => Self::NumberNonblank, + 'e' => Self::ShowEndsNonprinting, + 'E' => Self::ShowEnds, + 'n' => Self::Number, + 's' => Self::SqueezeBlank, + 't' => Self::ShowTabsNonprinting, + 'T' => Self::ShowTabs, + 'u' => Self::Ignored, + 'v' => Self::ShowNonprinting, + s => Self::Unknown(s.into()), + } } +} - fn parse_short(&mut self, s: &str) -> Result<()> { - for o in s.chars().skip(1) { - match o { - 'b' => self.number_nonblank = true, - 'E' => self.show_ends = true, - 'n' => self.number = true, - 's' => self.squeeze_blank = true, - 'T' => self.show_tabs = true, - 'v' => self.show_nonprinting = true, - s => return Err(Error::Opts(s.to_string())), - }; +impl From<&str> for Opt { + fn from(value: &str) -> Self { + match value { + "show-all" => Self::ShowAll, + "number-nonblank" => Self::NumberNonblank, + "show-ends" => Self::ShowEnds, + "number" => Self::Number, + "squeeze-blank" => Self::SqueezeBlank, + "show-tabs" => Self::ShowTabs, + "show-nonprinting" => Self::ShowNonprinting, + "help" => Self::Help, + "version" => Self::Version, + s => Self::Unknown(s.into()), } - Ok(()) } } |