#![allow(rustdoc::bare_urls)]
use clap::{Parser, Subcommand};
use semver::{Version, VersionReq};
use std::error::Error;
use std::io;
mod misc;
mod results;
use misc::*;
use results::*;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
pub struct Args {
#[command(subcommand)]
cmd: Commands,
#[clap(long, short = 'o', value_enum, default_value_t=OutputFormat::Yaml)]
out: OutputFormat,
}
#[derive(Subcommand, Debug, Clone)]
pub enum Commands {
Explain { semantic_version: Version },
Compare {
#[clap(long, short = 'e', action)]
set_exit_status: bool,
#[clap(long, short = 's', action)]
semantic_exit_status: bool,
a: Version,
b: Version,
},
Sort {
#[clap(long, short = 'f', default_value = None)]
filter: Option<VersionReq>,
#[clap(long, action)]
lexical_sorting: bool,
#[clap(long, short = 'r', action)]
reverse: bool,
#[clap(long, action)]
flatten: bool,
versions: Option<Vec<Version>>,
},
FilterTest {
filter: VersionReq,
semantic_version: Version,
},
}
fn main() -> Result<ApplicationTermination, Box<dyn Error>> {
let args = Args::parse();
let mut ignore_exit_status_from_output = false;
let application_output: ApplicationOutput = match args.cmd {
Commands::Explain { semantic_version } => explain(&semantic_version).into(),
Commands::Compare {
set_exit_status,
semantic_exit_status,
a,
b,
} => {
if !set_exit_status {
ignore_exit_status_from_output = true;
}
let res = compare(&a, &b);
if semantic_exit_status && res.semantic_ordering() == &SerializableOrdering::Equal {
ignore_exit_status_from_output = true
}
res.into()
}
Commands::Sort {
versions,
filter,
lexical_sorting,
reverse,
flatten,
} => {
let mut parsed_versions = Vec::new();
match versions {
Some(versions) => parsed_versions = versions,
None => {
let lines = io::stdin().lines();
for (line_no, line) in lines.enumerate() {
match line {
Ok(line) => {
let line = line.trim();
parsed_versions.push(Version::parse(line)
.map_err(|e| {
eprintln!("unable to parse an enumerated version: line {line_no}: {line}: {e}");
e
})?);
Ok(())
}
Err(e) => {
eprintln!("unable to read from stdin: {e}");
Err(ApplicationError::InvalidArgument {
expected: "to be able to read from stdin".to_string(),
found: e.to_string(),
})
}
}?
}
}
}
let mut ordered_version_list =
sort(&mut parsed_versions, &filter, lexical_sorting, reverse);
match flatten {
true => FlatVersionsList::from(&mut ordered_version_list).into(),
false => ordered_version_list.into(),
}
}
Commands::FilterTest {
filter,
semantic_version,
} => filter_test(&filter, &semantic_version).into(),
};
match args.out {
OutputFormat::Text => print!("{application_output}"),
OutputFormat::Yaml => {
println!("---");
let yaml = serde_yaml::to_string(&application_output)
.map_err(|e| ApplicationError::OutputFormatError { err: e.to_string() })?;
print!("{yaml}");
}
OutputFormat::Json => {
let json = serde_json::to_string(&application_output)
.map_err(|e| ApplicationError::OutputFormatError { err: e.to_string() })?;
println!("{json}");
}
}
Ok(ApplicationTermination::new(
application_output,
ignore_exit_status_from_output,
))
}
fn sort(
versions: &mut Vec<Version>,
filter: &Option<VersionReq>,
lexical_sorting: bool,
reverse: bool,
) -> OrderedVersionMap {
OrderedVersionMap::new(versions, filter, lexical_sorting, reverse)
}
fn compare(a: &Version, b: &Version) -> ComparisonStatement {
ComparisonStatement::new(a, b)
}
fn explain(v: &Version) -> VersionExplaination {
VersionExplaination::from(v)
}
fn filter_test(filter: &VersionReq, semantic_version: &Version) -> FilterTestResult {
FilterTestResult::filter_test(filter, semantic_version)
}