summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorToby Vincent <tobyv13@gmail.com>2022-12-04 00:01:20 -0600
committerToby Vincent <tobyv13@gmail.com>2022-12-04 00:01:20 -0600
commit1939e8d2e2411497291b419abd0a85a2b4808834 (patch)
treedfb89c98b0f9069d5f12f732ec302e58df2cfc49
parent32fa816a16c6ba5a26ad69fb8f30134836a093f5 (diff)
feat: add missing options and improve option parsing
-rw-r--r--src/cli.rs213
1 files changed, 161 insertions, 52 deletions
diff --git a/src/cli.rs b/src/cli.rs
index 12f1cfc..8c72155 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -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(())
}
}