From 399021ecc665dad3bb767fcea7860a83bc1d9e5e Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Mon, 13 May 2024 13:19:49 +0200 Subject: [PATCH 01/11] feat: add a parser that can be used with clap for all formats except base32 and base64 Refs: #5 --- src/format.rs | 117 +++++++++++++++++++++++++++++++++++++++++++------- src/lib.rs | 4 +- 2 files changed, 103 insertions(+), 18 deletions(-) diff --git a/src/format.rs b/src/format.rs index 8d83915..fd24ba6 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1,9 +1,10 @@ -#![allow(dead_code)] // this is exported to lib.rs +#![allow(dead_code)] +use anyhow::anyhow; +// this is exported to lib.rs use clap::{ArgGroup, Parser}; -use clap_num::maybe_hex; use libpt::bintols::split; -pub type Num = u128; +pub type NumberType = u128; /// formats supported by numf #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] @@ -68,11 +69,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 +120,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 +162,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 +181,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 +197,104 @@ 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 insensitive. 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::parser::numf_parser; +/// +/// #[derive(Parser)] +/// struct Args { +/// #[clap(short, long, value_parser=numf_parser::)] +/// address: u32, +/// } +/// let args = Args::parse_from(&["", "-a", "0x10"]); +/// assert_eq!(args.address, 16); +/// ``` +pub fn numf_parser(s: &str) -> anyhow::Result { + 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 NumberType::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 NumberType::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 NumberType::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 e = "parsing of base64 is not yet implemented".to_string(); + Err(anyhow!(e)) + } else if s.starts_with(&Format::Base32.prefix()) { + let e = "parsing of base32 is not yet implemented".to_string(); + 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; From 18cccddbb2fc24579b22bdfba37c2772bd05aa86 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Mon, 13 May 2024 13:31:33 +0200 Subject: [PATCH 02/11] test: fix numf_parser doctest #13 #5 --- src/format.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/format.rs b/src/format.rs index fd24ba6..9f32bbe 100644 --- a/src/format.rs +++ b/src/format.rs @@ -228,12 +228,12 @@ impl Format { /// /// ``` /// use clap::Parser; -/// use numf::parser::numf_parser; +/// use numf::format::numf_parser; /// /// #[derive(Parser)] /// struct Args { -/// #[clap(short, long, value_parser=numf_parser::)] -/// address: u32, +/// #[clap(short, long, value_parser=numf_parser)] +/// address: u128, /// } /// let args = Args::parse_from(&["", "-a", "0x10"]); /// assert_eq!(args.address, 16); From c6ad45fb82b132784db8ab8cec54074e283c2816 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Mon, 13 May 2024 13:39:56 +0200 Subject: [PATCH 03/11] test: add tests for numf_parser #13 --- src/format.rs | 2 +- tests/format.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/format.rs b/src/format.rs index 9f32bbe..38470f0 100644 --- a/src/format.rs +++ b/src/format.rs @@ -217,7 +217,7 @@ impl 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 insensitive. So if the user inputs `0b1100` then this is parsed as +/// case sensitive. So if the user inputs `0b1100` then this is parsed as /// [Binary](format::Format::Bin) and so on. /// /// # Example diff --git a/tests/format.rs b/tests/format.rs index 62e18fb..476bd4a 100644 --- a/tests/format.rs +++ b/tests/format.rs @@ -198,3 +198,35 @@ 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("IFAUEQQ=").unwrap(), 0x41414242); +} From 6649470ff7009dab56dea820a81b95318ff060bb Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Mon, 13 May 2024 13:51:17 +0200 Subject: [PATCH 04/11] refactor: numf_parser takes a generic unsigned int #13 --- Cargo.toml | 1 + src/format.rs | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 835d278..d848183 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,4 +19,5 @@ clap = { version = "4.5.4", features = ["derive"] } clap-num = "1.1.1" fast32 = "1.0.2" libpt = { version = "0.5.0", features = ["bintols"]} +num = "0.4.3" diff --git a/src/format.rs b/src/format.rs index 38470f0..30c41d3 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1,8 +1,8 @@ -#![allow(dead_code)] +#![allow(dead_code)] // this is exported to lib.rs use anyhow::anyhow; -// this is exported to lib.rs use clap::{ArgGroup, Parser}; use libpt::bintols::split; +use num; pub type NumberType = u128; @@ -69,7 +69,7 @@ pub struct FormatOptions { #[arg(short = 'z', long)] /// format to base32 base32: bool, - #[clap(value_parser=numf_parser, 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) @@ -238,8 +238,14 @@ impl Format { /// let args = Args::parse_from(&["", "-a", "0x10"]); /// assert_eq!(args.address, 16); /// ``` -pub fn numf_parser(s: &str) -> anyhow::Result { - if s.starts_with(&Format::Dec.prefix()) || s.parse::().is_ok() { +pub fn numf_parser(s: &str) -> anyhow::Result +where + T: std::str::FromStr, + ::Err: std::fmt::Display, + T: num::Num, + ::FromStrRadixErr: std::fmt::Display, +{ + 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, @@ -256,7 +262,7 @@ pub fn numf_parser(s: &str) -> anyhow::Result { Some(sr) => sr, None => s, }; - match NumberType::from_str_radix(s, 16) { + match T::from_str_radix(s, 16) { Ok(r) => Ok(r), Err(e) => { let e = format!("{e}"); @@ -268,7 +274,7 @@ pub fn numf_parser(s: &str) -> anyhow::Result { Some(sr) => sr, None => s, }; - match NumberType::from_str_radix(s, 8) { + match T::from_str_radix(s, 8) { Ok(r) => Ok(r), Err(e) => { let e = format!("{e}"); @@ -280,7 +286,7 @@ pub fn numf_parser(s: &str) -> anyhow::Result { Some(sr) => sr, None => s, }; - match NumberType::from_str_radix(s, 2) { + match T::from_str_radix(s, 2) { Ok(r) => Ok(r), Err(e) => { let e = format!("{e}"); From 47894a3f26d0c2fb1c850dde425a815950d6810f Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Mon, 13 May 2024 13:51:46 +0200 Subject: [PATCH 05/11] test: numf_parser test for all types that should be supported by the generic #13 --- tests/format.rs | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/tests/format.rs b/tests/format.rs index 476bd4a..d47ade7 100644 --- a/tests/format.rs +++ b/tests/format.rs @@ -201,32 +201,41 @@ fn set_format_checker() { #[test] fn parser_dec() { - assert_eq!(numf_parser("1337").unwrap(), 1337); - assert_eq!(numf_parser("0d1337").unwrap(), 1337); + 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); + 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); + assert_eq!(numf_parser::("0xdeadbeef").unwrap(), 0xdeadbeef); } #[test] fn parser_oct() { - assert_eq!(numf_parser("0o771171").unwrap(), 0o771171); + assert_eq!(numf_parser::("0o771171").unwrap(), 0o771171); } #[test] fn parser_b64() { - assert_eq!(numf_parser("0sQUFCQg==").unwrap(), 0x41414242); + assert_eq!(numf_parser::("0sQUFCQg==").unwrap(), 0x41414242); } #[test] fn parser_b32() { - assert_eq!(numf_parser("IFAUEQQ=").unwrap(), 0x41414242); + assert_eq!(numf_parser::("IFAUEQQ=").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); } From 44be69a26277eb1916c2c18c1565ff5089856ed2 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Mon, 13 May 2024 14:24:40 +0200 Subject: [PATCH 06/11] test: add prefix to base32 parser test #13 --- tests/format.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/format.rs b/tests/format.rs index d47ade7..7b3bef6 100644 --- a/tests/format.rs +++ b/tests/format.rs @@ -228,7 +228,7 @@ fn parser_b64() { #[test] fn parser_b32() { - assert_eq!(numf_parser::("IFAUEQQ=").unwrap(), 0x41414242); + assert_eq!(numf_parser::("032sIFAUEQQ=").unwrap(), 0x41414242); } #[test] From 68d9cdaf6928ef4c27f38ef5adf286a1fe629284 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Mon, 13 May 2024 14:25:04 +0200 Subject: [PATCH 07/11] feat: add hacky parsing for base32 and base64 #13 #5 --- src/format.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/src/format.rs b/src/format.rs index 30c41d3..352d1af 100644 --- a/src/format.rs +++ b/src/format.rs @@ -2,7 +2,7 @@ use anyhow::anyhow; use clap::{ArgGroup, Parser}; use libpt::bintols::split; -use num; +use num::traits::Pow; pub type NumberType = u128; @@ -232,7 +232,7 @@ impl Format { /// /// #[derive(Parser)] /// struct Args { -/// #[clap(short, long, value_parser=numf_parser)] +/// #[clap(short, long, value_parser=numf_parser::)] /// address: u128, /// } /// let args = Args::parse_from(&["", "-a", "0x10"]); @@ -244,6 +244,7 @@ where ::Err: std::fmt::Display, T: num::Num, ::FromStrRadixErr: std::fmt::Display, + ::Err: std::fmt::Debug, { if s.starts_with(&Format::Dec.prefix()) || s.parse::().is_ok() { let s = match s.strip_prefix(&Format::Dec.prefix()) { @@ -294,11 +295,51 @@ where } } } else if s.starts_with(&Format::Base64.prefix()) { - let e = "parsing of base64 is not yet implemented".to_string(); - Err(anyhow!(e)) + let s = match s.strip_prefix(&Format::Base64.prefix()) { + Some(sr) => sr, + None => s, + }; + match fast32::base64::RFC4648.decode_str(s) { + Ok(r) => { + if r.len() > 16 { + panic!("boom"); + } + let mut ri: u128 = 0; + for (i, e) in r.iter().rev().enumerate() { + ri += (*e as u128) * 256.pow(i as u32) as u128; + } + dbg!(ri); + dbg!(format!("{ri:#x}")); + Ok(ri.to_string().parse().unwrap()) + } + Err(e) => { + let e = format!("{e}"); + Err(anyhow!(e)) + } + } } else if s.starts_with(&Format::Base32.prefix()) { - let e = "parsing of base32 is not yet implemented".to_string(); - Err(anyhow!(e)) + let s = match s.strip_prefix(&Format::Base32.prefix()) { + Some(sr) => sr, + None => s, + }; + match fast32::base32::RFC4648.decode_str(s) { + Ok(r) => { + if r.len() > 16 { + panic!("boom"); + } + let mut ri: u128 = 0; + for (i, e) in r.iter().rev().enumerate() { + ri += (*e as u128) * 256.pow(i as u32) as u128; + } + dbg!(ri); + dbg!(format!("{ri:#x}")); + Ok(ri.to_string().parse().unwrap()) + } + 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)) From 2f14d672f46c08f95909b5f97e272549d9a236f0 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Mon, 13 May 2024 15:27:23 +0200 Subject: [PATCH 08/11] chore: bump libpt to v0.5.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d848183..0ced348 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +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" From 1bb0ffd6f89331b1cd85b83994f5e3deeb62522a Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Mon, 13 May 2024 15:45:38 +0200 Subject: [PATCH 09/11] refactor: use libpt to join the fast32 vecs into the integer type #13 #5 --- src/format.rs | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/format.rs b/src/format.rs index 352d1af..a2fced6 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] // this is exported to lib.rs use anyhow::anyhow; use clap::{ArgGroup, Parser}; -use libpt::bintols::split; +use libpt::bintols::{join, split}; use num::traits::Pow; pub type NumberType = u128; @@ -240,11 +240,17 @@ impl Format { /// ``` pub fn numf_parser(s: &str) -> anyhow::Result where - T: std::str::FromStr, + 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()) { @@ -323,18 +329,7 @@ where None => s, }; match fast32::base32::RFC4648.decode_str(s) { - Ok(r) => { - if r.len() > 16 { - panic!("boom"); - } - let mut ri: u128 = 0; - for (i, e) in r.iter().rev().enumerate() { - ri += (*e as u128) * 256.pow(i as u32) as u128; - } - dbg!(ri); - dbg!(format!("{ri:#x}")); - Ok(ri.to_string().parse().unwrap()) - } + Ok(r) => Ok(join::array_to_unsigned::(&r)?), Err(e) => { let e = format!("{e}"); Err(anyhow!(e)) From 7eb00e0f560154c9e7b68f34aef62aa101d74b0e Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Mon, 13 May 2024 15:52:18 +0200 Subject: [PATCH 10/11] refactor: use libpt for joining the base64 parser vec #5 #13 --- src/format.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/format.rs b/src/format.rs index a2fced6..bccfdc4 100644 --- a/src/format.rs +++ b/src/format.rs @@ -306,18 +306,7 @@ where None => s, }; match fast32::base64::RFC4648.decode_str(s) { - Ok(r) => { - if r.len() > 16 { - panic!("boom"); - } - let mut ri: u128 = 0; - for (i, e) in r.iter().rev().enumerate() { - ri += (*e as u128) * 256.pow(i as u32) as u128; - } - dbg!(ri); - dbg!(format!("{ri:#x}")); - Ok(ri.to_string().parse().unwrap()) - } + Ok(r) => Ok(join::array_to_unsigned::(&r)?), Err(e) => { let e = format!("{e}"); Err(anyhow!(e)) From 6f50918f271ed333354f75ed75942bc5f22d7796 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Mon, 13 May 2024 13:53:39 +0000 Subject: [PATCH 11/11] automatic cargo CI changes --- src/format.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/format.rs b/src/format.rs index bccfdc4..2f46b41 100644 --- a/src/format.rs +++ b/src/format.rs @@ -2,7 +2,6 @@ use anyhow::anyhow; use clap::{ArgGroup, Parser}; use libpt::bintols::{join, split}; -use num::traits::Pow; pub type NumberType = u128;