Skip to content

Commit

Permalink
Seq: Implement with support for -sw
Browse files Browse the repository at this point in the history
  • Loading branch information
tl-john-vincent committed Aug 31, 2020
1 parent f339463 commit f6a9aec
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ members = [
"pwd",
"rm",
"rmdir",
"seq",
"sleep",
"touch",
"true",
Expand Down
18 changes: 18 additions & 0 deletions seq/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "seq"
version = "0.1.0"
authors = ["John Pradeep Vincent <yehohanan7@gmail.com>"]
license = "MPL-2.0-no-copyleft-exception"
build = "build.rs"
edition = "2018"
description = """
Print numbers from FIRST to LAST, in steps of INCREMENT.
"""

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "^2.33.0", features = ["wrap_help"] }
sugars = "^1.0.0"

[build-dependencies]
clap = "^2.33.0"
24 changes: 24 additions & 0 deletions seq/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use std::env;

use clap::Shell;

#[path = "src/cli.rs"]
mod cli;

fn main() {
let mut app = cli::create_app();

let out_dir = match env::var("OUT_DIR") {
Ok(dir) => dir,
Err(err) => {
eprintln!("No OUT_DIR: {}", err);
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);
}
30 changes: 30 additions & 0 deletions seq/src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use clap::{
crate_authors, crate_description, crate_name, crate_version, App, AppSettings::ColoredHelp, Arg,
};

pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> {
App::new(crate_name!())
.version(crate_version!())
.author(crate_authors!())
.about(crate_description!())
.help_message("Display help information.")
.version_message("Display version information.")
.help_short("?")
.settings(&[ColoredHelp])
.arg(Arg::with_name("FIRST INCREMENT LAST").multiple(true))
.arg(
Arg::with_name("SEPERATOR")
.short("s")
.long("separator")
.help("use STRING to separate numbers (default: \\n)")
.hide_default_value(true)
.default_value("\n"),
)
.arg(
Arg::with_name("WIDTH")
.short("w")
.long("equal-width")
.help("equalize width by padding with leading zeroes")
.takes_value(false),
)
}
179 changes: 179 additions & 0 deletions seq/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
use std::process;

mod cli;

fn main() {
let matches = cli::create_app().get_matches();

if let Some(values) = matches.values_of("FIRST INCREMENT LAST") {
let args: Vec<&str> = values.collect();
if args.len() > 3 {
eprintln!("seq: extra operand '{}'\n Try 'seq --help' for more information.", args[3]);
process::exit(1);
}
let seperator = matches.value_of("SEPERATOR").map(String::from).unwrap();
let decimals = max_decimal_digits(&args);
let padding = if matches.is_present("WIDTH") { Some(max_digits(&args)) } else { None };
let (first, inc, last) = find_operands(&args);
let seq = Seq::new(first, inc, last, decimals, seperator, padding);

for val in seq.into_iter() {
print!("{}", val);
}
println!("");
} else {
eprintln!("seq: missing operand\n Try 'seq --help' for more information.");
process::exit(1);
}
}

fn find_operands(args: &[&str]) -> (f64, f64, f64) {
match args.len() {
1 => (1.0, 1.0, args[0].parse::<f64>().unwrap()),
2 => (args[0].parse().unwrap(), 1.0, args[1].parse().unwrap()),
_ => (args[0].parse().unwrap(), args[1].parse().unwrap(), args[2].parse().unwrap()),
}
}

struct Seq {
first: f64,
inc: f64,
last: f64,
decimals: usize,
seperator: String,
padding: Option<usize>,
}

impl Seq {
fn new(
first: f64, inc: f64, last: f64, decimals: usize, seperator: String, padding: Option<usize>,
) -> Seq {
Seq { first, inc, last, decimals, seperator, padding }
}
}

fn max_decimal_digits(args: &[&str]) -> usize {
args.iter().map(|v| v.len() - v.find(".").map(|pos| pos + 1).unwrap_or(v.len())).max().unwrap()
}

fn max_digits(args: &[&str]) -> usize {
args.iter().map(|v| v.find(".").unwrap_or(v.len())).max().unwrap()
}

impl IntoIterator for Seq {
type IntoIter = SeqIterator;
type Item = String;

fn into_iter(self) -> Self::IntoIter {
let current = self.first;
SeqIterator { seq: self, current }
}
}

struct SeqIterator {
seq: Seq,
current: f64,
}

impl Iterator for SeqIterator {
type Item = String;

fn next(&mut self) -> Option<Self::Item> {
if self.current > self.seq.last {
None
} else {
let value = format!("{:.*}", self.seq.decimals, self.current);
let digits = value.find(".").unwrap_or(value.len());

let mut value = match self.seq.padding {
Some(width) if width > digits => {
let mut padded = "0".repeat(width - digits);
padded.push_str(&value);
padded
},
_ => value,
};

if self.current + self.seq.inc <= self.seq.last {
value.push_str(&self.seq.seperator);
}
self.current += self.seq.inc;
Some(value)
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn should_find_max_decimal_digits() {
assert_eq!(max_decimal_digits(&["1.22", "1.3", "1"]), 2);
assert_eq!(max_decimal_digits(&["1", "1.3", "1.1"]), 1);
assert_eq!(max_decimal_digits(&["1"]), 0);
}

#[test]
fn should_find_max_digits() {
assert_eq!(max_digits(&["1.22", "12.3", "1"]), 2);
assert_eq!(max_digits(&["1.22", "12.3", "123"]), 3);
assert_eq!(max_digits(&["1.22", "-152.3", "123"]), 4);
}

#[test]
fn should_find_operands() {
assert_eq!(find_operands(&["2", "3", "10"]), (2.0, 3.0, 10.0));
assert_eq!(find_operands(&["2", "10"]), (2.0, 1.0, 10.0));
assert_eq!(find_operands(&["3"]), (1.0, 1.0, 3.0));
}

fn to_string(xs: Vec<&str>) -> Vec<String> {
xs.into_iter().map(|v: &str| v.to_owned()).collect::<Vec<String>>()
}

#[test]
fn should_generate_sequence() {
assert_eq!(
Seq::new(1.0, 1.0, 3.0, 0, "".to_owned(), None).into_iter().collect::<Vec<String>>(),
to_string(vec!["1", "2", "3"])
);

assert_eq!(
Seq::new(1.0, 1.0, 3.0, 0, ",".to_owned(), None).into_iter().collect::<Vec<String>>(),
to_string(vec!["1,", "2,", "3"])
);

assert_eq!(
Seq::new(1.0, 1.0, 3.0, 1, "".to_owned(), None).into_iter().collect::<Vec<String>>(),
to_string(vec!["1.0", "2.0", "3.0"])
);

assert_eq!(
Seq::new(1.0, 0.2, 2.0, 1, "".to_owned(), None).into_iter().collect::<Vec<String>>(),
to_string(vec!["1.0", "1.2", "1.4", "1.6", "1.8", "2.0"])
);

assert_eq!(
Seq::new(1.0, 0.2, 2.0, 3, "".to_owned(), None).into_iter().collect::<Vec<String>>(),
to_string(vec!["1.000", "1.200", "1.400", "1.600", "1.800", "2.000"])
);

assert_eq!(
Seq::new(-2.0, 1.0, 2.0, 0, "".to_owned(), None).into_iter().collect::<Vec<String>>(),
to_string(vec!["-2", "-1", "0", "1", "2"])
);

assert_eq!(
Seq::new(1.0, 1.0, 3.0, 0, "".to_owned(), None).into_iter().collect::<Vec<String>>(),
to_string(vec!["1", "2", "3"])
);

assert_eq!(
Seq::new(1.0, 5.0, 16.0, 0, "".to_owned(), Some(2))
.into_iter()
.collect::<Vec<String>>(),
to_string(vec!["01", "06", "11", "16"])
);
}
}

0 comments on commit f6a9aec

Please sign in to comment.