diff --git a/Cargo.toml b/Cargo.toml index f313aafb..272913cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "logname", "nice", "pwd", + "seq", "sleep", "true", "whoami", diff --git a/seq/Cargo.toml b/seq/Cargo.toml new file mode 100644 index 00000000..931ad93f --- /dev/null +++ b/seq/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "seq" +version = "0.1.0" +authors = ["GrayJack " , "FrancisMurillo "] +build = "build.rs" +edition = "2018" + +[dependencies] +clap = { version = "^2.33.0", features = ["yaml", "wrap_help"] } +rust_decimal = "^1.0.3" + +[build-dependencies] +clap = { version = "^2.33.0", features = ["yaml"] } diff --git a/seq/build.rs b/seq/build.rs new file mode 100644 index 00000000..6639d05d --- /dev/null +++ b/seq/build.rs @@ -0,0 +1,19 @@ +use std::env; + +use clap::{load_yaml, App, Shell}; + +fn main() { + let yaml = load_yaml!("src/seq.yml"); + let mut app = App::from_yaml(yaml); + + let out_dir = match env::var("OUT_DIR") { + Ok(dir) => dir, + _ => return + }; + + app.gen_completions("seq", Shell::Zsh, out_dir.clone()); + app.gen_completions("seq", Shell::Fish, out_dir.clone()); + app.gen_completions("seq", Shell::Bash, out_dir.clone()); + app.gen_completions("seq", Shell::PowerShell, out_dir.clone()); + app.gen_completions("seq", Shell::Elvish, out_dir); +} diff --git a/seq/src/main.rs b/seq/src/main.rs new file mode 100644 index 00000000..f0abe31a --- /dev/null +++ b/seq/src/main.rs @@ -0,0 +1,192 @@ +#![allow(clippy::suspicious_arithmetic_impl)] +use std::{ + cmp::Ordering, + fmt, + ops::{Add, AddAssign}, + process, + str::FromStr +}; + +use clap::{load_yaml, App, AppSettings}; +use rust_decimal::Decimal; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Number { + NegInf, + PosInf, + NaN, + Num(Decimal) +} + +use std::cmp::Ordering::*; +use Number::*; + +impl Add for Number { + type Output = Self; + + fn add(self, other: Self) -> Self { + match (self, other) { + (Num(left), Num(right)) => match left.checked_add(right) { + Some(next_dec) => Num(next_dec), + None => { + if left.is_sign_positive() && right.is_sign_positive() { + PosInf + } else if left.is_sign_negative() && right.is_sign_negative() { + NegInf + } else { + NaN + } + }, + }, + (NaN, _) | (_, NaN) | (PosInf, NegInf) | (NegInf, PosInf) => NaN, + (PosInf, PosInf) | (PosInf, Num(_)) | (Num(_), PosInf) => PosInf, + (NegInf, NegInf) | (NegInf, Num(_)) | (Num(_), NegInf) => NegInf + } + } +} + +impl AddAssign for Number { + fn add_assign(&mut self, other: Self) { *self = *self + other; } +} + +impl PartialOrd for Number { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (Num(left_dec), Num(right_dec)) => Some(left_dec.cmp(right_dec)), + (NaN, NaN) => Some(Equal), + (NaN, _) | (_, NaN) => None, + (PosInf, PosInf) | (NegInf, NegInf) => Some(Equal), + (NegInf, PosInf) | (Num(_), PosInf) | (NegInf, Num(_)) => Some(Less), + (PosInf, NegInf) | (PosInf, Num(_)) | (Num(_), NegInf) => Some(Greater) + } + } +} + +impl From for Number { + fn from(number: f64) -> Self { + match number { + value if value.is_infinite() => { + if value.is_sign_positive() { + PosInf + } else { + NegInf + } + }, + value if value.is_finite() => Num(Decimal::from_str(&value.to_string()).unwrap()), + _ => NaN + } + } +} + +impl fmt::Display for Number { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self { + PosInf => write!(f, "inf"), + NegInf => write!(f, "-inf"), + NaN => write!(f, "nan"), + Num(value) => write!(f, "{}", value) + } + } +} + +#[derive(Clone, Debug)] +pub struct Seq { + current: Number, + step: Number, + stop: Number +} + +impl Iterator for Seq { + type Item = Number; + + fn next(&mut self) -> Option { + match self.step { + step @ PosInf | step @ NegInf => Some(step + self.current), + Num(step_dec) => { + if (step_dec.is_sign_positive() && self.current <= self.stop) + || (step_dec.is_sign_negative() && self.current >= self.stop) + { + let result = self.current; + + self.current += self.step; + + Some(result) + } else { + None + } + }, + NaN => Some(NaN) + } + } +} + +fn parse_number(text: &str) -> Result { text.parse::().map_err(|_| ()) } + +fn check_nan(value: f64) -> Result { if value.is_nan() { Err(()) } else { Ok(value) } } + +fn check_zero(value: f64) -> Result { if value == 0.0 { Err(()) } else { Ok(value) } } + +fn argument_error(field: &str) -> impl Fn(()) -> f64 + '_ { + move |_| { + eprintln!( + "seq: Invalid {}.\nTry 'seq --help' for more information.", + field + ); + process::exit(1); + } +} + +const DEFAULT_FIRST: &str = "1"; +const DEFAULT_INCREMENT: &str = "1"; +const DEFAULT_SEPARATOR: &str = "\n"; + +fn main() { + let yaml = load_yaml!("seq.yml"); + let matches = App::from_yaml(yaml) + .setting(AppSettings::AllowNegativeNumbers) + .get_matches(); + + let (raw_first, raw_increment, raw_last) = match ( + matches.value_of("FIRST"), + matches.value_of("SECOND"), + matches.value_of("THIRD") + ) { + (Some(last), None, None) => (DEFAULT_FIRST, DEFAULT_INCREMENT, last), + (Some(first), Some(last), None) => (first, DEFAULT_INCREMENT, last), + (Some(first), Some(increment), Some(last)) => (first, increment, last), + _ => { + eprintln!("seq: Missing operands.\nTry 'seq --help' for more information."); + process::exit(1); + } + }; + + let separator = matches.value_of("SEPARATOR").unwrap_or(DEFAULT_SEPARATOR); + + let first = parse_number(raw_first) + .and_then(check_nan) + .unwrap_or_else(argument_error("FIRST")); + + let last = parse_number(raw_last) + .and_then(check_nan) + .unwrap_or_else(argument_error("LAST")); + + let increment = parse_number(raw_increment) + .and_then(check_nan) + .and_then(check_zero) + .unwrap_or_else(argument_error("INCREMENT")); + + let mut iter = (Seq { + current: Number::from(first), + stop: Number::from(last), + step: Number::from(increment) + }) + .peekable(); + + while let Some(index) = iter.next() { + if *&iter.peek().is_some() { + print!("{}{}", index, separator); + } else { + println!("{}", index); + } + } +} diff --git a/seq/src/seq.yml b/seq/src/seq.yml new file mode 100644 index 00000000..1a5d3ef6 --- /dev/null +++ b/seq/src/seq.yml @@ -0,0 +1,59 @@ +name: seq +version: "0.0.0" +author: Francis Murillo +about: "Print a sequence of numbers" +long_about: "Print numbers from FIRST to LAST, in steps of INCREMENT." +usage: > + + [OPTIONS] + + [OPTIONS] + + [OPTIONS] +args: + - FORMAT: + short: "f" + long: "format" + help: "Use printf style floating-point FORMAT" + takes_value: true + - SEPARATOR: + short: "s" + long: "separator" + help: "Use STRING to separate numbers" + default_value: "\n" + takes_value: true + - WIDTH: + short: "w" + long: "equal-width" + help: "Equalize width by padding with leading zeroes" + takes_value: false + - FIRST: + help: "The initial number to start the sequence." + long_help: > + The initial number to start the sequence. + + + This must not be NaN. If omitted, defaults to 1. + required: true + - SECOND: + help: "The step/increment added after each preceeding number in the sequence." + long_help: > + The step/increment added after each preceeding number in the + sequence. + + + If FIRST is greater than LAST, this should be a positive + number; if less than, this should be a negative number. This + must not be 0 or NaN. If omitted, defaults to 1 even if FIRST + is greater than LAST. + required: false + - THIRD: + help: "The limit of the sequence." + long_help: > + The limit of the sequence. + + + If the next number in a sequence exceeds above or falls below + LAST whether STEP is positive or negative respectively, the + sequence ends. This must not be NaN. + required: false