use clientele::{
StandardOptions,
SysexitsError::{self, *},
crates::clap::{
ColorChoice, CommandFactory, FromArgMatches, Parser, Subcommand, error::ErrorKind,
},
};
use core::str::FromStr;
use derive_more::FromStrError;
use edky::{PUBLIC_KEY_FORMATS, PublicKeyBytes, PublicKeyEncoding};
use std::{ffi::OsString, io::IsTerminal};
#[derive(Debug, Parser)]
#[command(name = "Edky", long_about)]
#[command(arg_required_else_help = true)]
struct Options {
#[clap(flatten)]
flags: StandardOptions,
#[command(subcommand)]
command: Option<Command>,
}
#[derive(Debug, Subcommand)]
enum Command {
List {},
Convert {
#[clap(long, short, default_value = "hex", value_parser = parse_format)]
from: PublicKeyEncoding,
#[clap(long, short, default_value = "hex", value_parser = parse_format)]
to: PublicKeyEncoding,
#[arg(required = true)]
inputs: Vec<String>,
},
Parse {
#[clap(long, short, default_value = "hex", value_parser = parse_format)]
from: PublicKeyEncoding,
#[arg(required = true)]
inputs: Vec<String>,
},
}
pub fn main() -> Result<(), SysexitsError> {
clientele::dotenv().ok();
let args = clientele::args_os()?;
let mut cli = Options::command().color(color_choice(&args));
let matches = cli
.try_get_matches_from_mut(args)
.unwrap_or_else(|error| error.exit());
let options = Options::from_arg_matches(&matches).unwrap_or_else(|error| error.exit());
if options.flags.version {
println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
return Ok(());
}
if options.flags.license {
print!("{}", include_str!("../UNLICENSE"));
return Ok(());
}
let Some(command) = options.command else {
cli.error(ErrorKind::MissingSubcommand, "a subcommand is required")
.exit();
};
let diagnostics = Diagnostics::new(&options.flags);
diagnostics.log(3, format_args!("debug: {command:?}"));
match command {
Command::List {} => {
for format in PUBLIC_KEY_FORMATS {
println!("{}", format.name());
diagnostics.log(
2,
format_args!(
"{}: encoding={}, prefix={:?}",
format.name(),
format.encoding(),
format.prefix()
),
);
}
diagnostics.log(
1,
format_args!("listed {} formats", PUBLIC_KEY_FORMATS.len()),
);
Ok(())
},
Command::Convert { from, to, inputs } => {
let count = inputs.len();
for (index, input) in inputs.into_iter().enumerate() {
let key = PublicKeyBytes::decode(from, input)?;
let Some(encoded) = key.encode(to) else {
return Err(EX_CONFIG);
};
println!("{}", encoded);
diagnostics.log(2, format_args!("converted input {}/{}", index + 1, count));
}
diagnostics.log(
1,
format_args!("converted {count} public key(s) from {from} to {to}"),
);
Ok(())
},
Command::Parse { from, inputs } => {
let count = inputs.len();
for (index, input) in inputs.into_iter().enumerate() {
let _ = PublicKeyBytes::decode(from, input)?;
diagnostics.log(2, format_args!("validated input {}/{}", index + 1, count));
}
println!("OK");
diagnostics.log(1, format_args!("validated {count} public key(s) as {from}"));
Ok(())
},
}
}
fn color_choice(args: &[OsString]) -> ColorChoice {
let mut args = args
.iter()
.skip(1)
.take_while(|arg| arg.as_os_str() != "--");
let mut color = ColorChoice::Auto;
while let Some(arg) = args.next() {
let value = if arg == "--color" {
args.next().and_then(|value| value.to_str())
} else {
arg.to_str()
.and_then(|value| value.strip_prefix("--color="))
};
if let Some(value) = value.and_then(|value| value.parse().ok()) {
color = value;
}
}
color
}
struct Diagnostics {
verbosity: u8,
color: bool,
}
impl Diagnostics {
fn new(flags: &StandardOptions) -> Self {
Self {
verbosity: if flags.debug { u8::MAX } else { flags.verbose },
color: match flags.color {
ColorChoice::Always => true,
ColorChoice::Never => false,
ColorChoice::Auto => {
std::io::stderr().is_terminal() && std::env::var_os("NO_COLOR").is_none()
},
},
}
}
fn log(&self, level: u8, message: core::fmt::Arguments<'_>) {
if self.verbosity >= level {
if self.color {
eprintln!("\x1b[36medky:\x1b[0m {message}");
} else {
eprintln!("edky: {message}");
}
}
}
}
fn parse_format(input: &str) -> Result<PublicKeyEncoding, FromStrError> {
for format in PUBLIC_KEY_FORMATS {
if format.name() == input {
return Ok(format.encoding());
}
}
PublicKeyEncoding::from_str(input)
}