mod core;
mod shell;
use std::io::{ErrorKind, Write};
use std::path::PathBuf;
use std::process::ExitCode;
use clap::{Parser, Subcommand, ValueEnum};
use crate::core::report::EXIT_ERROR;
use crate::core::reqfile::Kind;
#[derive(Parser)]
#[command(version, about)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Check {
#[arg(long, value_name = "BASE", num_args = 0..=1)]
changed: Option<Option<String>>,
#[arg(long, value_name = "ID,...", value_delimiter = ',')]
only: Option<Vec<String>>,
#[arg(long)]
fast: bool,
#[arg(long, value_name = "FILE")]
log: Option<PathBuf>,
#[arg(long, value_name = "KEY=VALUE", requires = "log")]
log_tag: Vec<String>,
#[arg(long, value_enum, default_value_t = Format::Summary)]
format: Format,
},
Explain { path: PathBuf },
List {
#[arg(long, value_enum)]
kind: Option<KindArg>,
},
Test {
#[arg(long, value_name = "ID,...", value_delimiter = ',')]
only: Option<Vec<String>>,
},
}
#[derive(Clone, Copy, ValueEnum)]
enum KindArg {
Product,
Code,
}
#[derive(Clone, Copy, ValueEnum)]
enum Format {
Summary,
Json,
}
fn main() -> ExitCode {
let cli = Cli::parse();
let cwd = match std::env::current_dir() {
Ok(cwd) => cwd,
Err(e) => {
eprintln!("error: cannot read the current folder: {e}");
return ExitCode::from(EXIT_ERROR as u8);
}
};
match cli.command {
Command::Check {
changed,
only,
fast,
log,
log_tag,
format,
} => {
let report = shell::check::run(
&cwd,
&shell::check::Options {
changed,
only,
fast,
log,
log_tags: log_tag,
},
);
let text = match format {
Format::Summary => report.render_summary(),
Format::Json => report.render_json(),
};
match emit(&text) {
Ok(()) => ExitCode::from(report.exit_code() as u8),
Err(()) => ExitCode::from(EXIT_ERROR as u8),
}
}
Command::Test { only } => match shell::examples::run(&cwd, only.as_deref()) {
Ok((text, code)) => match emit(&text) {
Ok(()) => ExitCode::from(code as u8),
Err(()) => ExitCode::from(EXIT_ERROR as u8),
},
Err(errors) => {
for error in errors {
eprintln!("error: {error}");
}
ExitCode::from(EXIT_ERROR as u8)
}
},
Command::Explain { path } => match shell::explain::run(&cwd, &path) {
Ok(text) => match emit(&text) {
Ok(()) => ExitCode::SUCCESS,
Err(()) => ExitCode::from(EXIT_ERROR as u8),
},
Err(errors) => {
for error in errors {
eprintln!("error: {error}");
}
ExitCode::from(EXIT_ERROR as u8)
}
},
Command::List { kind } => {
let kind = kind.map(|k| match k {
KindArg::Product => Kind::Product,
KindArg::Code => Kind::Code,
});
match shell::list::run(&cwd, kind) {
Ok(text) => match emit(&text) {
Ok(()) => ExitCode::SUCCESS,
Err(()) => ExitCode::from(EXIT_ERROR as u8),
},
Err(errors) => {
for error in errors {
eprintln!("error: {error}");
}
ExitCode::from(EXIT_ERROR as u8)
}
}
}
}
}
fn emit(text: &str) -> Result<(), ()> {
let mut out = std::io::stdout().lock();
match out.write_all(text.as_bytes()).and_then(|()| out.flush()) {
Ok(()) => Ok(()),
Err(e) if e.kind() == ErrorKind::BrokenPipe => Ok(()),
Err(e) => {
eprintln!("error: cannot write the output: {e}");
Err(())
}
}
}