diff --git a/Cargo.toml b/Cargo.toml index 66c3b0f..115d6a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "numf" -version = "0.2.0" +version = "0.3.0" edition = "2021" publish = true authors = ["Christoph J. Scherr "] @@ -18,5 +18,6 @@ anyhow = "1.0.83" clap = { version = "4.5.4", features = ["derive"] } clap-num = "1.1.1" fast32 = "1.0.2" -libpt = { version = "0.5.0", features = ["bintols"]} +libpt = { version = "0.5.1", features = ["bintols"]} +num = "0.4.3" diff --git a/README.md b/README.md index 4e201e6..68d0887 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ Current formats are: - Binary - Octal - Decimal +- Base32 +- Base64 `numf` also has the option of prepending a prefix for each format, such as `0x` for hexadecimal. diff --git a/src/format.rs b/src/format.rs index 8d83915..2f46b41 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1,9 +1,9 @@ #![allow(dead_code)] // this is exported to lib.rs +use anyhow::anyhow; use clap::{ArgGroup, Parser}; -use clap_num::maybe_hex; -use libpt::bintols::split; +use libpt::bintols::{join, split}; -pub type Num = u128; +pub type NumberType = u128; /// formats supported by numf #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] @@ -68,11 +68,11 @@ pub struct FormatOptions { #[arg(short = 'z', long)] /// format to base32 base32: bool, - #[clap(value_parser=maybe_hex::, required=true)] + #[clap(value_parser=numf_parser::, required=true)] /// at least one number that should be formatted /// /// supports either base 10 or base 16 inputs (with 0xaaaa) - numbers: Vec, + numbers: Vec, } impl FormatOptions { @@ -119,7 +119,7 @@ impl FormatOptions { } /// set numbers manually - pub fn set_numbers(&mut self, numbers: Vec) { + pub fn set_numbers(&mut self, numbers: Vec) { self.numbers = numbers; } @@ -161,6 +161,7 @@ impl Default for FormatOptions { } impl Format { + /// Get the perfix for that [Format] pub fn prefix(&self) -> String { match self { // apperently used nowhere, sometimes 0 is used as a prefix but I @@ -179,14 +180,15 @@ impl Format { } .to_string() } - pub fn format(&self, num: Num, options: &FormatOptions) -> String { + /// format a number with a [Format] and [FormatOptions] + pub fn format(&self, num: NumberType, options: &FormatOptions) -> String { let mut buf = String::new(); - if options.prefix { + if options.prefix() { buf += &self.prefix(); } match self { Format::Hex => { - if options.padding { + if options.padding() { let tmp = &format!("{num:X}"); buf += &("0".repeat((2 - tmp.len() % 2) % 2) + tmp); } else { @@ -194,22 +196,135 @@ impl Format { } } Format::Bin => { - if options.padding { + if options.padding() { let tmp = &format!("{num:b}"); buf += &("0".repeat((8 - tmp.len() % 8) % 8) + tmp); } else { buf += &format!("{num:b}"); } } - Format::Octal => { - buf += &format!("{num:o}"); - } - Format::Dec => { - buf += &format!("{num}"); - } + Format::Octal => buf += &format!("{num:o}"), + Format::Dec => buf += &format!("{num}"), Format::Base64 => buf += &fast32::base64::RFC4648.encode(&split::unsigned_to_vec(num)), Format::Base32 => buf += &fast32::base32::RFC4648.encode(&split::unsigned_to_vec(num)), } buf } } + +/// Validates an unsigned integer value that can be one of [Format](format::Format). +/// +/// The number is assumed to be base-10 by default, it is parsed as a different +/// [Format](format::Format) if the number is prefixed with the [prefix](format::FormatOptions::prefix), +/// case sensitive. So if the user inputs `0b1100` then this is parsed as +/// [Binary](format::Format::Bin) and so on. +/// +/// # Example +/// +/// This allows base-10 addresses to be passed normally, or values formatted with any of the +/// [Formats](format::Format) defined by this crate to be passed when prefixed with the respective +/// prefix. +/// +/// ``` +/// use clap::Parser; +/// use numf::format::numf_parser; +/// +/// #[derive(Parser)] +/// struct Args { +/// #[clap(short, long, value_parser=numf_parser::)] +/// address: u128, +/// } +/// let args = Args::parse_from(&["", "-a", "0x10"]); +/// assert_eq!(args.address, 16); +/// ``` +pub fn numf_parser(s: &str) -> anyhow::Result +where + T: std::str::FromStr + std::convert::TryFrom, + ::Err: std::fmt::Display, + T: num::Num, + ::FromStrRadixErr: std::fmt::Display, + ::Err: std::fmt::Debug, + u128: std::convert::From, + ::Err: std::error::Error, + >::Error: std::error::Error, + >::Error: std::marker::Send, + >::Error: std::marker::Sync, + >::Error: 'static, +{ + if s.starts_with(&Format::Dec.prefix()) || s.parse::().is_ok() { + let s = match s.strip_prefix(&Format::Dec.prefix()) { + Some(sr) => sr, + None => s, + }; + match s.parse() { + Ok(r) => Ok(r), + Err(e) => { + let e = format!("{e}"); + Err(anyhow!(e)) + } + } + } else if s.starts_with(&Format::Hex.prefix()) { + let s = match s.strip_prefix(&Format::Hex.prefix()) { + Some(sr) => sr, + None => s, + }; + match T::from_str_radix(s, 16) { + Ok(r) => Ok(r), + Err(e) => { + let e = format!("{e}"); + Err(anyhow!(e)) + } + } + } else if s.starts_with(&Format::Octal.prefix()) { + let s = match s.strip_prefix(&Format::Octal.prefix()) { + Some(sr) => sr, + None => s, + }; + match T::from_str_radix(s, 8) { + Ok(r) => Ok(r), + Err(e) => { + let e = format!("{e}"); + Err(anyhow!(e)) + } + } + } else if s.starts_with(&Format::Bin.prefix()) { + let s = match s.strip_prefix(&Format::Bin.prefix()) { + Some(sr) => sr, + None => s, + }; + match T::from_str_radix(s, 2) { + Ok(r) => Ok(r), + Err(e) => { + let e = format!("{e}"); + Err(anyhow!(e)) + } + } + } else if s.starts_with(&Format::Base64.prefix()) { + let s = match s.strip_prefix(&Format::Base64.prefix()) { + Some(sr) => sr, + None => s, + }; + match fast32::base64::RFC4648.decode_str(s) { + Ok(r) => Ok(join::array_to_unsigned::(&r)?), + Err(e) => { + let e = format!("{e}"); + Err(anyhow!(e)) + } + } + } else if s.starts_with(&Format::Base32.prefix()) { + let s = match s.strip_prefix(&Format::Base32.prefix()) { + Some(sr) => sr, + None => s, + }; + match fast32::base32::RFC4648.decode_str(s) { + Ok(r) => Ok(join::array_to_unsigned::(&r)?), + Err(e) => { + let e = format!("{e}"); + Err(anyhow!(e)) + } + } + } else { + let e = "could not determine the format of the value".to_string(); + Err(anyhow!(e)) + } +} diff --git a/src/lib.rs b/src/lib.rs index bcab8de..b120604 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,8 +3,8 @@ //! This crate contains several utility functions for formatting numbers //! into other systems, such as converting decimal numbers to hexadecimal. //! -//! See [Format] for supported formats. +//! See [format::Format] for supported formats. //! -//! Note that this crate is primarily used as a executable. +//! Note that this crate is primarily intended to be used as a executable. pub mod format; diff --git a/src/main.rs b/src/main.rs index 1aa0470..342c755 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,13 +3,41 @@ //! This binary should just take any amount of numbers and print them out formatted to some other //! system. +use std::io::Read; +use std::process::exit; + use clap::Parser; mod format; use format::*; fn main() { - let options = FormatOptions::parse(); + // try to read from stdin first, appending the numbers we read to the FormatOptions + let mut args: Vec = std::env::args_os() + .map(|x| x.into_string().unwrap()) + .collect(); + let mut stdin_nums = Vec::new(); + match std::io::stdin().lock().read_to_end(&mut stdin_nums) { + Ok(_) => { + let whole: String = match String::from_utf8(stdin_nums) { + Ok(r) => r, + Err(e) => { + eprintln!("stdin for this program only accepts text: {e:#?}"); + exit(1); + } + }; + let split = whole.split_whitespace(); + for s in split { + args.push(s.to_string()); + } + } + Err(e) => { + eprintln!("could not read from stdin: {e:#?}"); + exit(2); + } + }; + + let options = FormatOptions::parse_from(args); let mut out: Vec = Vec::new(); diff --git a/tests/format.rs b/tests/format.rs index 62e18fb..7b3bef6 100644 --- a/tests/format.rs +++ b/tests/format.rs @@ -198,3 +198,44 @@ fn set_format_checker() { options.set_format(Format::Base64); assert_eq!(options.format(), Format::Base64); } + +#[test] +fn parser_dec() { + assert_eq!(numf_parser::("1337").unwrap(), 1337); + assert_eq!(numf_parser::("0d1337").unwrap(), 1337); +} + +#[test] +fn parser_bin() { + assert_eq!(numf_parser::("0b11001").unwrap(), 0b11001); + assert_eq!(numf_parser::("0b11001").unwrap(), 0b11001); +} + +#[test] +fn parser_hex() { + assert_eq!(numf_parser::("0xdeadbeef").unwrap(), 0xdeadbeef); +} + +#[test] +fn parser_oct() { + assert_eq!(numf_parser::("0o771171").unwrap(), 0o771171); +} + +#[test] +fn parser_b64() { + assert_eq!(numf_parser::("0sQUFCQg==").unwrap(), 0x41414242); +} + +#[test] +fn parser_b32() { + assert_eq!(numf_parser::("032sIFAUEQQ=").unwrap(), 0x41414242); +} + +#[test] +fn parser_generics() { + assert_eq!(numf_parser::("55").unwrap(), 55); + assert_eq!(numf_parser::("55").unwrap(), 55); + assert_eq!(numf_parser::("55").unwrap(), 55); + assert_eq!(numf_parser::("55").unwrap(), 55); + assert_eq!(numf_parser::("55").unwrap(), 55); +}