use std::io::{stdout, ErrorKind, Write};
use std::process::exit;
use clap::{crate_version, Arg, ArgAction, Command};
use num_traits::Zero;
use uucore::error::FromIo;
use uucore::error::UResult;
use uucore::memo::printf;
use uucore::show;
use uucore::{format_usage, help_about, help_usage};
mod error;
mod extendedbigdecimal;
mod extendedbigint;
mod number;
mod numberparse;
use crate::error::SeqError;
use crate::extendedbigdecimal::ExtendedBigDecimal;
use crate::extendedbigint::ExtendedBigInt;
use crate::number::Number;
use crate::number::PreciseNumber;
const ABOUT: &str = help_about!("seq.md");
const USAGE: &str = help_usage!("seq.md");
const OPT_SEPARATOR: &str = "separator";
const OPT_TERMINATOR: &str = "terminator";
const OPT_EQUAL_WIDTH: &str = "equal-width";
const OPT_FORMAT: &str = "format";
const ARG_NUMBERS: &str = "numbers";
#[derive(Clone)]
struct SeqOptions<'a> {
separator: String,
terminator: String,
equal_width: bool,
format: Option<&'a str>,
}
type RangeInt = (ExtendedBigInt, ExtendedBigInt, ExtendedBigInt);
type RangeFloat = (ExtendedBigDecimal, ExtendedBigDecimal, ExtendedBigDecimal);
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from(args)?;
let numbers_option = matches.get_many::<String>(ARG_NUMBERS);
if numbers_option.is_none() {
return Err(SeqError::NoArguments.into());
}
let numbers = numbers_option.unwrap().collect::<Vec<_>>();
let options = SeqOptions {
separator: matches
.get_one::<String>(OPT_SEPARATOR)
.map(|s| s.as_str())
.unwrap_or("\n")
.to_string(),
terminator: matches
.get_one::<String>(OPT_TERMINATOR)
.map(|s| s.as_str())
.unwrap_or("\n")
.to_string(),
equal_width: matches.get_flag(OPT_EQUAL_WIDTH),
format: matches.get_one::<String>(OPT_FORMAT).map(|s| s.as_str()),
};
let first = if numbers.len() > 1 {
match numbers[0].parse() {
Ok(num) => num,
Err(e) => return Err(SeqError::ParseError(numbers[0].to_string(), e).into()),
}
} else {
PreciseNumber::one()
};
let increment = if numbers.len() > 2 {
match numbers[1].parse() {
Ok(num) => num,
Err(e) => return Err(SeqError::ParseError(numbers[1].to_string(), e).into()),
}
} else {
PreciseNumber::one()
};
if increment.is_zero() {
return Err(SeqError::ZeroIncrement(numbers[1].to_string()).into());
}
let last: PreciseNumber = {
let n: usize = numbers.len();
match numbers[n - 1].parse() {
Ok(num) => num,
Err(e) => return Err(SeqError::ParseError(numbers[n - 1].to_string(), e).into()),
}
};
let padding = first
.num_integral_digits
.max(increment.num_integral_digits)
.max(last.num_integral_digits);
let largest_dec = first
.num_fractional_digits
.max(increment.num_fractional_digits);
let result = match (first.number, increment.number, last.number) {
(Number::Int(first), Number::Int(increment), last) => {
let last = last.round_towards(&first);
print_seq_integers(
(first, increment, last),
&options.separator,
&options.terminator,
options.equal_width,
padding,
options.format,
)
}
(first, increment, last) => print_seq(
(
first.into_extended_big_decimal(),
increment.into_extended_big_decimal(),
last.into_extended_big_decimal(),
),
largest_dec,
&options.separator,
&options.terminator,
options.equal_width,
padding,
options.format,
),
};
match result {
Ok(_) => Ok(()),
Err(err) if err.kind() == ErrorKind::BrokenPipe => Ok(()),
Err(e) => Err(e.map_err_context(|| "write error".into())),
}
}
pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.trailing_var_arg(true)
.allow_negative_numbers(true)
.infer_long_args(true)
.version(crate_version!())
.about(ABOUT)
.override_usage(format_usage(USAGE))
.arg(
Arg::new(OPT_SEPARATOR)
.short('s')
.long("separator")
.help("Separator character (defaults to \\n)"),
)
.arg(
Arg::new(OPT_TERMINATOR)
.short('t')
.long("terminator")
.help("Terminator character (defaults to \\n)"),
)
.arg(
Arg::new(OPT_EQUAL_WIDTH)
.short('w')
.long("equal-width")
.help("Equalize widths of all numbers by padding with zeros")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_FORMAT)
.short('f')
.long(OPT_FORMAT)
.help("use printf style floating-point FORMAT"),
)
.arg(
Arg::new(ARG_NUMBERS)
.action(ArgAction::Append)
.num_args(1..=3),
)
}
fn done_printing<T: Zero + PartialOrd>(next: &T, increment: &T, last: &T) -> bool {
if increment >= &T::zero() {
next > last
} else {
next < last
}
}
fn write_value_float(
writer: &mut impl Write,
value: &ExtendedBigDecimal,
width: usize,
precision: usize,
) -> std::io::Result<()> {
let value_as_str =
if *value == ExtendedBigDecimal::Infinity || *value == ExtendedBigDecimal::MinusInfinity {
format!("{value:>width$.precision$}")
} else {
format!("{value:>0width$.precision$}")
};
write!(writer, "{value_as_str}")
}
fn write_value_int(
writer: &mut impl Write,
value: &ExtendedBigInt,
width: usize,
pad: bool,
) -> std::io::Result<()> {
let value_as_str = if pad {
if *value == ExtendedBigInt::MinusZero {
format!("{value:0<width$}")
} else {
format!("{value:>0width$}")
}
} else {
format!("{value}")
};
write!(writer, "{value_as_str}")
}
fn print_seq(
range: RangeFloat,
largest_dec: usize,
separator: &str,
terminator: &str,
pad: bool,
padding: usize,
format: Option<&str>,
) -> std::io::Result<()> {
let stdout = stdout();
let mut stdout = stdout.lock();
let (first, increment, last) = range;
let mut value = first;
let padding = if pad { padding + 1 + largest_dec } else { 0 };
let mut is_first_iteration = true;
while !done_printing(&value, &increment, &last) {
if !is_first_iteration {
write!(stdout, "{separator}")?;
}
match format {
Some(f) => {
let s = format!("{value}");
if let Err(x) = printf(f, &[s]) {
show!(x);
exit(1);
}
}
None => write_value_float(&mut stdout, &value, padding, largest_dec)?,
}
value = value + increment.clone();
is_first_iteration = false;
}
if !is_first_iteration {
write!(stdout, "{terminator}")?;
}
stdout.flush()?;
Ok(())
}
fn print_seq_integers(
range: RangeInt,
separator: &str,
terminator: &str,
pad: bool,
padding: usize,
format: Option<&str>,
) -> std::io::Result<()> {
let stdout = stdout();
let mut stdout = stdout.lock();
let (first, increment, last) = range;
let mut value = first;
let mut is_first_iteration = true;
while !done_printing(&value, &increment, &last) {
if !is_first_iteration {
write!(stdout, "{separator}")?;
}
match format {
Some(f) => {
let s = format!("{value}");
if let Err(x) = printf(f, &[s]) {
show!(x);
exit(1);
}
}
None => write_value_int(&mut stdout, &value, padding, pad)?,
}
value = value + increment.clone();
is_first_iteration = false;
}
if !is_first_iteration {
write!(stdout, "{terminator}")?;
}
Ok(())
}