#![allow(missing_docs)]
mod cmd;
mod error;
use std::io::Write;
use std::process::ExitCode;
use clap::{Parser, Subcommand};
use serde_json::json;
use error::{CliError, Result};
#[derive(Parser, Debug)]
#[command(
name = "mk",
version,
about = "mk — engrave-friendly Bitcoin xpub backups (mk1 format)"
)]
pub(crate) struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand, Debug)]
enum Command {
Encode(cmd::encode::EncodeArgs),
Decode(cmd::decode::DecodeArgs),
Inspect(cmd::inspect::InspectArgs),
Verify(cmd::verify::VerifyArgs),
Vectors(cmd::vectors::VectorsArgs),
GuiSchema(cmd::gui_schema::GuiSchemaArgs),
}
fn main() -> ExitCode {
let cli = match Cli::try_parse() {
Ok(cli) => cli,
Err(e) => {
e.print().ok();
return match e.kind() {
clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion => {
ExitCode::SUCCESS
}
_ => ExitCode::from(64),
};
}
};
let json_mode = is_json_mode(&cli.command);
let result: Result<()> = match cli.command {
Command::Encode(a) => cmd::encode::run(a),
Command::Decode(a) => cmd::decode::run(a),
Command::Inspect(a) => cmd::inspect::run(a),
Command::Verify(a) => cmd::verify::run(a),
Command::Vectors(a) => cmd::vectors::run(a),
Command::GuiSchema(a) => cmd::gui_schema::run(a),
};
match result {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
emit_error(&e, json_mode);
ExitCode::from(e.exit_code())
}
}
}
fn is_json_mode(cmd: &Command) -> bool {
match cmd {
Command::Encode(a) => a.json,
Command::Decode(a) => a.json,
Command::Inspect(a) => a.json,
Command::Verify(a) => a.json,
Command::Vectors(_) => false,
Command::GuiSchema(_) => false,
}
}
fn emit_error(e: &CliError, json_mode: bool) {
if json_mode {
let envelope = json!({
"schema_version": 1,
"error": {
"kind": e.kind(),
"message": e.message(),
"exit_code": e.exit_code(),
"details": e.details(),
},
});
let s = serde_json::to_string(&envelope).expect("error envelope serializes");
println!("{s}");
} else {
let mut stderr = std::io::stderr().lock();
writeln!(stderr, "{e}").ok();
}
}