refactor(cli): change subcommand order

This commit is contained in:
Kristofers Solo 2025-10-17 22:05:56 +03:00
parent dc243a8731
commit 54ddcab377
Signed by: kristoferssolo
GPG Key ID: 8687F2D3EEE6F0ED
2 changed files with 53 additions and 56 deletions

View File

@ -2,7 +2,6 @@ use clap::{Parser, Subcommand, ValueEnum};
use std::{ use std::{
fmt::{Display, LowerHex, UpperHex}, fmt::{Display, LowerHex, UpperHex},
fs::read_to_string, fs::read_to_string,
num::IntErrorKind,
path::PathBuf, path::PathBuf,
str::FromStr, str::FromStr,
}; };
@ -25,7 +24,7 @@ pub enum ValueError {
#[error("Invalid number format: {0}")] #[error("Invalid number format: {0}")]
InvalidFormat(String), InvalidFormat(String),
#[error("Invalid byte string length: expected no more than, found {0}")] #[error("Invalid byte string length: expected no more than 8, found {0}")]
InvalidByteStringLength(usize), InvalidByteStringLength(usize),
#[error("String-to-u64 conversion error: {0}")] #[error("String-to-u64 conversion error: {0}")]
@ -37,23 +36,30 @@ pub enum ValueError {
pub struct Args { pub struct Args {
#[command(subcommand)] #[command(subcommand)]
pub operation: Operation, pub operation: Operation,
/// Key used to encrypt/decrypt data (64-bit number, string, or path to file)
#[arg(short, long, value_parser = Value::from_str, required = true)]
pub key: Value,
/// The text to encrypt/decrypt data (64-bit number, string, or path to file)
#[arg(value_name = "TEXT", value_parser = Value::from_str, required = true)]
pub text: Value,
} }
#[derive(Debug, Clone, Subcommand, Default)] #[derive(Debug, Clone, Subcommand)]
pub enum Operation { pub enum Operation {
/// Encrypt data /// Encrypt data
#[default] Encrypt {
Encrypt, /// Key used to encrypt/decrypt data (64-bit number, string, or path to file)
#[arg(short, long, value_parser = Value::from_str, required = true)]
key: Value,
/// The text to encrypt/decrypt data (64-bit number, string, or path to file)
#[arg(value_name = "TEXT", value_parser = Value::from_str, required = true)]
text: Value,
},
/// Decrypt data /// Decrypt data
Decrypt { Decrypt {
/// Key used to encrypt/decrypt data (64-bit number, string, or path to file)
#[arg(short, long, value_parser = Value::from_str, required = true)]
key: Value,
/// The text to encrypt/decrypt data (64-bit number, string, or path to file)
#[arg(value_name = "TEXT", value_parser = Value::from_str, required = true)]
text: Value,
/// Output format for decrypted data /// Output format for decrypted data
#[arg(short = 'f', long, value_enum)] #[arg(short = 'f', long, value_enum)]
output_format: Option<OutputFormat>, output_format: Option<OutputFormat>,
@ -131,55 +137,41 @@ fn parse_string_to_u64(s: &str) -> Result<u64, ValueError> {
} }
// Hexadecimal with 0x/0X prefix // Hexadecimal with 0x/0X prefix
if trimmed.starts_with("0x") || trimmed.starts_with("0X") { if let Some(hex_str) = trimmed
let hex_str = &trimmed[2..].trim_start_matches('0'); .strip_prefix("0X")
if hex_str.is_empty() { .or_else(|| trimmed.strip_prefix("0x"))
return Ok(0); // 0x000 -> {
} return parse_radix(hex_str, 16, "Hex");
return u64::from_str_radix(hex_str, 16)
.map_err(|e| ValueError::InvalidFormat(format!("Hex parsing failed: {e}")));
} }
// Binary with 0b/0B prefix // Binary with 0b/0B prefix
if trimmed.starts_with("0b") || trimmed.starts_with("0B") { if let Some(bin_str) = trimmed
let bin_str = &trimmed[2..].trim_start_matches('0'); .strip_prefix("0b")
if bin_str.is_empty() { .or_else(|| trimmed.strip_prefix("0B"))
return Ok(0); // 0b000 -> 0 {
} return parse_radix(bin_str, 2, "Binary");
if !bin_str.chars().all(|ch| ch == '0' || ch == '1') {
return Err(ValueError::InvalidFormat(
"Binary string contains invalid characters".into(),
));
}
return u64::from_str_radix(bin_str, 2)
.map_err(|e| ValueError::InvalidFormat(format!("Binary parsing failed: {e}")));
} }
// 8-character ASCII string conversion to u64 // 8-character ASCII string conversion to u64
if trimmed.len() <= 8 { if trimmed.len() > 8 {
return ascii_string_to_u64(trimmed); return Err(ValueError::InvalidByteStringLength(trimmed.len()));
} }
// Regular decimal parsing ascii_string_to_u64(trimmed)
trimmed.parse::<u64>().map_err(|e| { }
ValueError::InvalidFormat(match e.kind() {
IntErrorKind::InvalidDigit => "contains invalid digits".into(), fn parse_radix(s: &str, radix: u32, name: &str) -> Result<u64, ValueError> {
IntErrorKind::PosOverflow => "number too large for u64".into(), let trimmed = s.trim_start_matches('0');
IntErrorKind::NegOverflow => "negative numbers not allowed".into(), if trimmed.is_empty() {
IntErrorKind::Empty => "empty string".into(), return Ok(0);
IntErrorKind::Zero => "invalid zero".into(), }
_ => format!("parsing error: {e}"),
}) u64::from_str_radix(trimmed, radix)
}) .map_err(|e| ValueError::InvalidFormat(format!("{name} parsing failed: {e}")))
} }
fn ascii_string_to_u64(s: &str) -> Result<u64, ValueError> { fn ascii_string_to_u64(s: &str) -> Result<u64, ValueError> {
if s.len() > 8 { if !s.is_ascii() {
return Err(ValueError::InvalidByteStringLength(s.len()));
}
// Ensure all characters are valid ASCII (0-127)
if !s.bytes().all(|b| b <= 127) {
return Err(ValueError::ConversionError( return Err(ValueError::ConversionError(
"String contains non-ASCII characters".into(), "String contains non-ASCII characters".into(),
)); ));

View File

@ -7,15 +7,20 @@ use des::Des;
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
let args = Args::parse(); let args = Args::parse();
let des = Des::new(args.key.as_64());
match args.operation { match args.operation {
Operation::Encrypt => { Operation::Encrypt { key, text } => {
let ciphertext = des.encrypt(&args.text.to_be_bytes())?; let des = Des::new(key.as_64());
let ciphertext = des.encrypt(&text.to_be_bytes())?;
println!("{ciphertext:016X}"); println!("{ciphertext:016X}");
} }
Operation::Decrypt { output_format } => { Operation::Decrypt {
let plaintext = des.decrypt(&args.text.to_be_bytes())?; key,
text,
output_format,
} => {
let des = Des::new(key.as_64());
let plaintext = des.decrypt(&text.to_be_bytes())?;
match output_format.unwrap_or_default() { match output_format.unwrap_or_default() {
OutputFormat::Binary => println!("{plaintext:064b}"), OutputFormat::Binary => println!("{plaintext:064b}"),
OutputFormat::Octal => println!("{plaintext:022o}"), OutputFormat::Octal => println!("{plaintext:022o}"),