use std::ffi::OsString;
use clap::{Args, Parser, Subcommand};
use crate::commands;
use crate::util::color::ColorChoice;
use crate::util::version::version_string;
#[derive(Debug, Parser)]
#[command(
name = "cardanowall",
bin_name = "cardanowall",
about = "Label 309 standalone verifier and Proof-of-Existence toolkit",
long_about = "Label 309 standalone verifier and Proof-of-Existence toolkit.\n\n\
ENVIRONMENT (consistent across every command):\n \
CARDANOWALL_BASE_URL gateway base URL (--base-url)\n \
CARDANOWALL_API_KEY opaque bearer API key (--api-key)\n \
CARDANOWALL_SEED 32-byte identity seed (--seed)\n \
CARDANOWALL_RECIPIENT_KEY X25519 recipient key (--secret-key)\n \
CARDANOWALL_CARDANO_GATEWAY / CARDANOWALL_ARWEAVE_GATEWAY /\n \
CARDANOWALL_IPFS_GATEWAY / CARDANOWALL_BLOCKFROST_PROJECT_ID /\n \
CARDANOWALL_CONFIRMATION_DEPTH_THRESHOLD / CARDANOWALL_DENY_HOST\n \
CARDANOWALL_CONFIG_PATH overrides ~/.cardanowall/config.toml\n\n\
High-secret flags (--seed, --secret-key) also accept a *-file / *-stdin\n\
variant and, on a TTY, a hidden interactive prompt; the raw --seed/\n\
--secret-key hex flag is INSECURE (shell history / ps / CI logs).",
version = version_string(),
disable_version_flag = true
)]
pub struct Cli {
#[arg(long, action = clap::ArgAction::Version)]
version: Option<bool>,
#[command(flatten)]
pub global: GlobalArgs,
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, Clone, Default, Args)]
pub struct GlobalArgs {
#[arg(long, global = true, value_enum, default_value_t = ColorMode::Auto)]
pub color: ColorMode,
#[arg(long, global = true)]
pub no_color: bool,
#[arg(long, short = 'q', global = true)]
pub quiet: bool,
#[arg(long, global = true)]
pub json: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, clap::ValueEnum)]
pub enum ColorMode {
#[default]
Auto,
Always,
Never,
}
impl GlobalArgs {
#[must_use]
pub fn color_choice(&self) -> ColorChoice {
if self.no_color {
return ColorChoice::Never;
}
match self.color {
ColorMode::Auto => ColorChoice::Auto,
ColorMode::Always => ColorChoice::Always,
ColorMode::Never => ColorChoice::Never,
}
}
}
#[derive(Debug, Subcommand)]
pub enum Command {
Verify(commands::verify::VerifyArgs),
Submit(commands::submit::SubmitArgs),
Sign(commands::sign::SignArgs),
Identity(commands::identity::IdentityArgs),
Merkle(commands::merkle::MerkleArgs),
Inbox(commands::inbox::InboxArgs),
Gateway(commands::gateway::GatewayArgs),
Completion(commands::completion::CompletionArgs),
}
impl Command {
fn name(&self) -> &'static str {
match self {
Command::Verify(_) => "verify",
Command::Submit(_) => "submit",
Command::Sign(_) => "sign",
Command::Identity(_) => "identity",
Command::Merkle(_) => "merkle",
Command::Inbox(_) => "inbox",
Command::Gateway(_) => "gateway",
Command::Completion(_) => "completion",
}
}
fn json_mode(&self, global_json: bool) -> bool {
global_json || self.local_json()
}
fn local_json(&self) -> bool {
match self {
Command::Verify(a) => a.json,
Command::Submit(a) => a.json,
Command::Sign(a) => a.source_json(),
Command::Identity(a) => a.json,
Command::Merkle(a) => a.json_mode(),
Command::Inbox(a) => a.json_mode(),
Command::Gateway(a) => a.json_mode(),
Command::Completion(_) => false,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct GlobalContext {
pub color: ColorChoice,
pub quiet: bool,
pub json: bool,
}
pub fn run<I, T>(args: I) -> i32
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
let cli = match Cli::try_parse_from(args) {
Ok(cli) => cli,
Err(err) => {
use clap::error::ErrorKind;
let kind = err.kind();
let _ = err.print();
if matches!(
kind,
ErrorKind::DisplayHelp
| ErrorKind::DisplayVersion
| ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
) {
return 0;
}
return 4;
}
};
let json = cli.command.json_mode(cli.global.json);
let command_name = cli.command.name();
let ctx = GlobalContext {
color: cli.global.color_choice(),
quiet: cli.global.quiet,
json,
};
let result = match cli.command {
Command::Verify(args) => commands::verify::run(args),
Command::Submit(args) => commands::submit::run(args),
Command::Sign(args) => commands::sign::run(args),
Command::Identity(args) => commands::identity::run(args),
Command::Merkle(args) => commands::merkle::run(args),
Command::Inbox(args) => commands::inbox::run(args),
Command::Gateway(args) => commands::gateway::run(args),
Command::Completion(args) => commands::completion::run(args),
};
match result {
Ok(()) => 0,
Err(err) => {
report_error(&err, command_name, &ctx);
err.code
}
}
}
fn report_error(err: &crate::util::CliError, command: &str, ctx: &GlobalContext) {
if ctx.json {
let value = serde_json::json!({
"error": {
"code": err.code,
"message": err.message,
"command": command,
}
});
eprintln!("{value}");
} else if !err.message.is_empty() {
use crate::util::color::{should_color, Stream, SystemColorEnv};
use owo_colors::OwoColorize;
let colored = should_color(ctx.color, false, Stream::Stderr, &SystemColorEnv);
if colored {
eprintln!("{}: {}", "cardanowall".red(), err.message);
} else {
eprintln!("cardanowall: {}", err.message);
}
}
}