use std::ffi::{OsStr, OsString};
use std::io::ErrorKind as IoErrorKind;
use clap::builder::OsStringValueParser;
use clap::{Arg, ArgAction, ArgMatches, Command};
use crate::actions::{CompletionRequest, Shell};
use crate::error::{JaoError, JaoResult};
mod actions;
mod error;
mod platform;
mod script_discovery;
mod trust;
#[cfg(feature = "config")]
#[cfg_attr(not(feature = "trust-manifest"), allow(dead_code))]
mod config;
#[cfg(feature = "config")]
#[cfg_attr(not(feature = "trust-manifest"), allow(dead_code))]
mod storage;
#[doc(hidden)]
fn main() {
__exit(__main())
}
const GENERATE_COMPLETION_SPECIAL_ARG: &'static str = "__complete";
#[doc(hidden)]
fn __main() -> JaoResult<()> {
let raw_args = std::env::args_os().collect::<Vec<OsString>>();
if raw_args
.get(1)
.is_some_and(|arg| arg == GENERATE_COMPLETION_SPECIAL_ARG)
{
let complete_args = parse_internal_completion_args(
raw_args
.iter()
.skip(2)
.map(AsRef::as_ref),
)?;
let root = std::env::current_dir()?;
return actions::complete(root, complete_args);
}
let matches = build_clap_parser().try_get_matches_from(&raw_args)?;
let context = CliContext::from(&matches);
match CliAction::try_from(&matches)? {
CliAction::Help => actions::print_help(),
CliAction::PrintCompletionsForShell(shell) => actions::print_shell_completion(shell),
#[cfg(not(feature = "trust-manifest"))]
CliAction::List => {
let root = std::env::current_dir()?;
actions::list_scripts(root)
}
#[cfg(feature = "trust-manifest")]
CliAction::List => {
let root = std::env::current_dir()?;
let config = config::load_or_init()?;
let trusted_manifest = trust::manifest::load_or_init(&config)?;
actions::list_scripts_with_trust_status(root, &trusted_manifest)
}
CliAction::Fingerprint { parts } => {
let root = std::env::current_dir()?;
let script_path = script_discovery::resolve_script(root, parts)?;
actions::fingerprint_script(script_path)
}
CliAction::RunFingerprinted { parts, required_fingerprint } => {
let root = std::env::current_dir()?;
let script_path = script_discovery::resolve_script(root, parts)?;
actions::run_script_with_fingerprint(script_path, required_fingerprint)
}
CliAction::RunUntrusted { .. } if context.ci => Err(JaoError::CiRunRequiresFingerprint),
#[cfg(not(feature = "trust-manifest"))]
CliAction::RunUntrusted { .. } => Err(JaoError::RunWithoutTrustManifestRequiresFingerprint),
#[cfg(feature = "trust-manifest")]
CliAction::RunUntrusted { parts } => {
let root = std::env::current_dir()?;
let script_path = script_discovery::resolve_script(root, parts)?;
let config = config::load_or_init()?;
let mut trusted_manifest = trust::manifest::load_or_init(&config)?;
actions::run_script_with_trust(script_path, &config, &mut trusted_manifest)
}
}
}
fn __exit(final_result: JaoResult<()>) -> ! {
match &final_result {
Err(JaoError::Clap(clap_err)) => {
clap_err
.print()
.unwrap();
}
Err(error) => eprintln!("error: {error}"),
_ => (),
}
let exit_code = match final_result {
Ok(_) => 0,
Err(JaoError::Io(io_err)) if io_err.kind() == IoErrorKind::BrokenPipe => 0,
Err(JaoError::InvalidArguments(_)) | Err(JaoError::Clap(_)) => 2,
Err(_) => 1,
};
std::process::exit(exit_code)
}
fn parse_internal_completion_args<'a>(mut remaining_args: impl Iterator<Item = &'a OsStr>) -> JaoResult<CompletionRequest<'a>> {
if remaining_args.next() != Some(OsStr::new("--index")) {
return Err(JaoError::InvalidArguments("missing --index arg"));
}
let index_to_complete = if let Some(index_as_str) = remaining_args.next() {
index_as_str
.to_string_lossy()
.parse::<usize>()
.map_err(|_| JaoError::InvalidArguments("given index is not a valid number"))?
} else {
return Err(JaoError::InvalidArguments("missing index"));
};
if remaining_args.next() != Some(OsStr::new("--")) {
return Err(JaoError::InvalidArguments("missing -- after index"));
}
let completion_args = CompletionRequest {
index_to_complete,
given_arguments: remaining_args.collect(),
};
return Ok(completion_args);
}
#[derive(Debug, Clone, Copy)]
struct CliContext {
ci: bool,
}
impl From<&ArgMatches> for CliContext {
fn from(matches: &ArgMatches) -> Self {
Self { ci: matches.get_flag("ci") }
}
}
#[derive(Debug)]
enum CliAction<'a> {
Help,
PrintCompletionsForShell(Shell),
List,
Fingerprint {
parts: Vec<&'a OsStr>,
},
RunFingerprinted {
parts: Vec<&'a OsStr>,
required_fingerprint: &'a OsStr,
},
RunUntrusted {
#[cfg_attr(not(feature = "trust-manifest"), allow(dead_code))]
parts: Vec<&'a OsStr>,
},
}
impl<'a> TryFrom<&'a ArgMatches> for CliAction<'a> {
type Error = JaoError;
fn try_from(matches: &'a ArgMatches) -> Result<Self, Self::Error> {
if let Some(shell_str) = matches
.get_raw("completions")
.and_then(|mut values| values.next())
{
let shell = Shell::try_from(shell_str)?;
return Ok(CliAction::PrintCompletionsForShell(shell));
};
if matches.get_flag("list") {
return Ok(CliAction::List);
}
if let Some(parts) = matches.get_raw("fingerprint") {
return Ok(CliAction::Fingerprint { parts: parts.collect() });
}
match (
matches
.get_raw("require_fingerprint")
.and_then(|mut values| values.next()),
matches.get_raw("script_command"),
) {
(Some(required_fingerprint), Some(parts)) => Ok(CliAction::RunFingerprinted {
parts: parts.collect(),
required_fingerprint,
}),
(None, Some(parts)) => Ok(CliAction::RunUntrusted { parts: parts.collect() }),
(None, None) => Ok(CliAction::Help),
(Some(_), None) => Err(JaoError::InvalidArguments("missing script command after --require-fingerprint")),
}
}
}
fn build_clap_parser() -> Command {
Command::new("jao")
.version(env!("CARGO_PKG_VERSION"))
.disable_help_subcommand(true)
.arg(
Arg::new("ci")
.long("ci")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("list")
.long("list")
.action(ArgAction::SetTrue)
.conflicts_with_all(["completions", "script_command", "fingerprint", "require_fingerprint"]),
)
.arg(
Arg::new("fingerprint")
.long("fingerprint")
.num_args(1..)
.value_parser(OsStringValueParser::new())
.conflicts_with_all(["list", "completions", "script_command", "require_fingerprint"]),
)
.arg(
Arg::new("require_fingerprint")
.long("require-fingerprint")
.value_parser(OsStringValueParser::new())
.conflicts_with_all(["list", "fingerprint", "completions"]),
)
.arg(
Arg::new("completions")
.long("completions")
.value_parser(OsStringValueParser::new())
.conflicts_with_all(["ci", "fingerprint", "require_fingerprint", "list", "script_command"]),
)
.arg(
Arg::new("script_command")
.num_args(1..)
.trailing_var_arg(true)
.value_parser(OsStringValueParser::new()),
)
}