use crate::errors::BuildRunError;
use crate::RS_SUFFIX;
use bitflags::bitflags;
use clap::{ArgGroup, Parser};
use core::{fmt, str};
use std::error::Error;
#[allow(clippy::struct_excessive_bools)]
#[derive(Clone, Default, Parser, Debug)]
#[command(name = "thag_rs", version, about, long_about)]
#[command(group(
ArgGroup::new("commands")
.required(true)
.args(&["script", "expression", "repl", "filter", "stdin", "edit"]),
))]
#[command(group(
ArgGroup::new("volume")
.required(false)
.args(&["quiet", "normal", "verbose"]),
))]
pub struct Cli {
pub script: Option<String>,
#[arg(last = true, requires = "script")]
pub args: Vec<String>,
#[arg(short = 'g', long = "gen")]
pub generate: bool,
#[arg(short, long)]
pub build: bool,
#[arg(short, long)]
pub force: bool,
#[arg(short, long, conflicts_with_all(["edit","expression", "filter", "repl", "stdin"]))]
pub norun: bool,
#[arg(short, long = "expr", conflicts_with_all(["generate", "build"]))]
pub expression: Option<String>,
#[arg(short = 'r', long, conflicts_with_all(["generate", "build"]))]
pub repl: bool,
#[arg(short, long, conflicts_with_all(["generate", "build"]))]
pub stdin: bool,
#[arg(short = 'd', long, conflicts_with_all(["generate", "build"]))]
pub edit: bool,
#[arg(short = 'l', long = "loop", conflicts_with_all(["generate", "build"]))]
pub filter: Option<String>,
#[arg(short = 'C', long, requires = "filter", value_name = "CARGO-TOML")]
pub cargo: Option<String>,
#[arg(short = 'B', long, requires = "filter", value_name = "PRE-LOOP")]
pub begin: Option<String>,
#[arg(short = 'E', long, requires = "filter", value_name = "POST-LOOP")]
pub end: Option<String>,
#[arg(short, long)]
pub multimain: bool,
#[arg(short, long, conflicts_with_all(["build", "executable"]))]
pub check: bool,
#[arg(short = 'x', long)]
pub executable: bool,
#[arg(short, long)]
pub verbose: bool,
#[arg(short, long, action = clap::ArgAction::Count, conflicts_with("verbose"))]
pub quiet: u8,
#[arg(short = 'N', long = "normal verbosity")]
pub normal: bool,
#[arg(short, long)]
pub timings: bool,
}
#[must_use]
pub fn get_args() -> Cli {
Cli::parse()
}
pub fn validate_args(args: &Cli, proc_flags: &ProcFlags) -> Result<(), Box<dyn Error>> {
if let Some(ref script) = args.script {
if !script.ends_with(RS_SUFFIX) {
return Err(Box::new(BuildRunError::Command(format!(
"Script name {script} must end in {RS_SUFFIX}"
))));
}
} else if !proc_flags.contains(ProcFlags::EXPR)
&& !proc_flags.contains(ProcFlags::REPL)
&& !proc_flags.contains(ProcFlags::STDIN)
&& !proc_flags.contains(ProcFlags::EDIT)
&& !proc_flags.contains(ProcFlags::LOOP)
{
return Err(Box::new(BuildRunError::Command(
"Missing script name".to_string(),
)));
}
Ok(())
}
bitflags! {
#[derive(Clone, Default, PartialEq, Eq)]
pub struct ProcFlags: u32 {
const GENERATE = 1;
const BUILD = 2;
const FORCE = 4;
const RUN = 8;
const ALL = 16;
const VERBOSE = 32;
const TIMINGS = 64;
const REPL = 128;
const EXPR = 256;
const STDIN = 512;
const EDIT = 1024;
const QUIET = 2048;
const QUIETER = 4096;
const MULTI = 8192;
const NORUN = 16384;
const EXECUTABLE = 32768;
const LOOP = 65536;
const CHECK = 131_072;
const NORMAL = 262_144;
}
}
impl fmt::Debug for ProcFlags {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
bitflags::parser::to_writer(self, f)
}
}
impl fmt::Display for ProcFlags {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
bitflags::parser::to_writer(self, f)
}
}
impl str::FromStr for ProcFlags {
type Err = bitflags::parser::ParseError;
fn from_str(flags: &str) -> Result<Self, Self::Err> {
bitflags::parser::from_str(flags)
}
}
pub fn get_proc_flags(args: &Cli) -> Result<ProcFlags, Box<dyn Error>> {
let is_expr = args.expression.is_some();
let is_loop = args.filter.is_some();
let proc_flags = {
let mut proc_flags = ProcFlags::empty();
proc_flags.set(
ProcFlags::GENERATE,
args.generate | args.force | is_expr | args.executable | args.check,
);
proc_flags.set(
ProcFlags::BUILD,
args.build | args.force | is_expr | args.executable,
);
proc_flags.set(ProcFlags::CHECK, args.check);
proc_flags.set(ProcFlags::FORCE, args.force);
proc_flags.set(ProcFlags::QUIET, args.quiet == 1);
proc_flags.set(ProcFlags::QUIETER, args.quiet >= 2);
proc_flags.set(ProcFlags::MULTI, args.multimain);
proc_flags.set(ProcFlags::VERBOSE, args.verbose);
proc_flags.set(ProcFlags::TIMINGS, args.timings);
proc_flags.set(ProcFlags::NORUN, args.norun | args.check | args.executable);
proc_flags.set(ProcFlags::NORMAL, args.normal);
proc_flags.set(
ProcFlags::RUN,
!args.norun && !args.executable && !args.check,
);
proc_flags.set(
ProcFlags::ALL,
!args.norun && !args.executable && !args.check,
);
if proc_flags.contains(ProcFlags::ALL) {
proc_flags.set(ProcFlags::GENERATE, true);
proc_flags.set(ProcFlags::BUILD, true);
} else {
proc_flags.set(ProcFlags::ALL, args.generate & args.build);
}
proc_flags.set(ProcFlags::REPL, args.repl);
proc_flags.set(ProcFlags::EXPR, is_expr);
proc_flags.set(ProcFlags::STDIN, args.stdin);
proc_flags.set(ProcFlags::EDIT, args.edit);
proc_flags.set(ProcFlags::LOOP, is_loop);
proc_flags.set(ProcFlags::EXECUTABLE, args.executable);
let formatted = proc_flags.to_string();
let parsed = formatted
.parse::<ProcFlags>()
.map_err(|e| BuildRunError::FromStr(e.to_string()))?;
assert_eq!(proc_flags, parsed);
Ok::<ProcFlags, BuildRunError>(proc_flags)
}?;
Ok(proc_flags)
}