Merge pull request 'v0.3.0' (#14) from devel into master
cargo devel CI / cargo CI (push) Successful in 1m15s Details

Reviewed-on: #14
This commit is contained in:
Christoph J. Scherr 2024-05-13 16:34:21 +02:00
commit 09a7fa22cf
6 changed files with 208 additions and 21 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "numf" name = "numf"
version = "0.2.0" version = "0.3.0"
edition = "2021" edition = "2021"
publish = true publish = true
authors = ["Christoph J. Scherr <software@cscherr.de>"] authors = ["Christoph J. Scherr <software@cscherr.de>"]
@ -18,5 +18,6 @@ anyhow = "1.0.83"
clap = { version = "4.5.4", features = ["derive"] } clap = { version = "4.5.4", features = ["derive"] }
clap-num = "1.1.1" clap-num = "1.1.1"
fast32 = "1.0.2" fast32 = "1.0.2"
libpt = { version = "0.5.0", features = ["bintols"]} libpt = { version = "0.5.1", features = ["bintols"]}
num = "0.4.3"

View File

@ -8,6 +8,8 @@ Current formats are:
- Binary - Binary
- Octal - Octal
- Decimal - Decimal
- Base32
- Base64
`numf` also has the option of prepending a prefix for each format, such as `numf` also has the option of prepending a prefix for each format, such as
`0x` for hexadecimal. `0x` for hexadecimal.

View File

@ -1,9 +1,9 @@
#![allow(dead_code)] // this is exported to lib.rs #![allow(dead_code)] // this is exported to lib.rs
use anyhow::anyhow;
use clap::{ArgGroup, Parser}; use clap::{ArgGroup, Parser};
use clap_num::maybe_hex; use libpt::bintols::{join, split};
use libpt::bintols::split;
pub type Num = u128; pub type NumberType = u128;
/// formats supported by numf /// formats supported by numf
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
@ -68,11 +68,11 @@ pub struct FormatOptions {
#[arg(short = 'z', long)] #[arg(short = 'z', long)]
/// format to base32 /// format to base32
base32: bool, base32: bool,
#[clap(value_parser=maybe_hex::<Num>, required=true)] #[clap(value_parser=numf_parser::<NumberType>, required=true)]
/// at least one number that should be formatted /// at least one number that should be formatted
/// ///
/// supports either base 10 or base 16 inputs (with 0xaaaa) /// supports either base 10 or base 16 inputs (with 0xaaaa)
numbers: Vec<Num>, numbers: Vec<NumberType>,
} }
impl FormatOptions { impl FormatOptions {
@ -119,7 +119,7 @@ impl FormatOptions {
} }
/// set numbers manually /// set numbers manually
pub fn set_numbers(&mut self, numbers: Vec<Num>) { pub fn set_numbers(&mut self, numbers: Vec<NumberType>) {
self.numbers = numbers; self.numbers = numbers;
} }
@ -161,6 +161,7 @@ impl Default for FormatOptions {
} }
impl Format { impl Format {
/// Get the perfix for that [Format]
pub fn prefix(&self) -> String { pub fn prefix(&self) -> String {
match self { match self {
// apperently used nowhere, sometimes 0 is used as a prefix but I // apperently used nowhere, sometimes 0 is used as a prefix but I
@ -179,14 +180,15 @@ impl Format {
} }
.to_string() .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(); let mut buf = String::new();
if options.prefix { if options.prefix() {
buf += &self.prefix(); buf += &self.prefix();
} }
match self { match self {
Format::Hex => { Format::Hex => {
if options.padding { if options.padding() {
let tmp = &format!("{num:X}"); let tmp = &format!("{num:X}");
buf += &("0".repeat((2 - tmp.len() % 2) % 2) + tmp); buf += &("0".repeat((2 - tmp.len() % 2) % 2) + tmp);
} else { } else {
@ -194,22 +196,135 @@ impl Format {
} }
} }
Format::Bin => { Format::Bin => {
if options.padding { if options.padding() {
let tmp = &format!("{num:b}"); let tmp = &format!("{num:b}");
buf += &("0".repeat((8 - tmp.len() % 8) % 8) + tmp); buf += &("0".repeat((8 - tmp.len() % 8) % 8) + tmp);
} else { } else {
buf += &format!("{num:b}"); buf += &format!("{num:b}");
} }
} }
Format::Octal => { Format::Octal => buf += &format!("{num:o}"),
buf += &format!("{num:o}"); Format::Dec => buf += &format!("{num}"),
}
Format::Dec => {
buf += &format!("{num}");
}
Format::Base64 => buf += &fast32::base64::RFC4648.encode(&split::unsigned_to_vec(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)), Format::Base32 => buf += &fast32::base32::RFC4648.encode(&split::unsigned_to_vec(num)),
} }
buf 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::<u128>)]
/// address: u128,
/// }
/// let args = Args::parse_from(&["", "-a", "0x10"]);
/// assert_eq!(args.address, 16);
/// ```
pub fn numf_parser<T>(s: &str) -> anyhow::Result<T>
where
T: std::str::FromStr + std::convert::TryFrom<u128>,
<T as std::str::FromStr>::Err: std::fmt::Display,
T: num::Num,
<T as num::Num>::FromStrRadixErr: std::fmt::Display,
<T as std::str::FromStr>::Err: std::fmt::Debug,
u128: std::convert::From<T>,
<T as std::str::FromStr>::Err: std::error::Error,
<T as std::convert::TryFrom<u128>>::Error: std::error::Error,
<T as std::convert::TryFrom<u128>>::Error: std::marker::Send,
<T as std::convert::TryFrom<u128>>::Error: std::marker::Sync,
<T as std::convert::TryFrom<u128>>::Error: 'static,
{
if s.starts_with(&Format::Dec.prefix()) || s.parse::<T>().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::<T>(&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::<T>(&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))
}
}

View File

@ -3,8 +3,8 @@
//! This crate contains several utility functions for formatting numbers //! This crate contains several utility functions for formatting numbers
//! into other systems, such as converting decimal numbers to hexadecimal. //! 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; pub mod format;

View File

@ -3,13 +3,41 @@
//! This binary should just take any amount of numbers and print them out formatted to some other //! This binary should just take any amount of numbers and print them out formatted to some other
//! system. //! system.
use std::io::Read;
use std::process::exit;
use clap::Parser; use clap::Parser;
mod format; mod format;
use format::*; use format::*;
fn main() { fn main() {
let options = FormatOptions::parse(); // try to read from stdin first, appending the numbers we read to the FormatOptions
let mut args: Vec<String> = 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<String> = Vec::new(); let mut out: Vec<String> = Vec::new();

View File

@ -198,3 +198,44 @@ fn set_format_checker() {
options.set_format(Format::Base64); options.set_format(Format::Base64);
assert_eq!(options.format(), Format::Base64); assert_eq!(options.format(), Format::Base64);
} }
#[test]
fn parser_dec() {
assert_eq!(numf_parser::<u32>("1337").unwrap(), 1337);
assert_eq!(numf_parser::<u32>("0d1337").unwrap(), 1337);
}
#[test]
fn parser_bin() {
assert_eq!(numf_parser::<u32>("0b11001").unwrap(), 0b11001);
assert_eq!(numf_parser::<u32>("0b11001").unwrap(), 0b11001);
}
#[test]
fn parser_hex() {
assert_eq!(numf_parser::<u32>("0xdeadbeef").unwrap(), 0xdeadbeef);
}
#[test]
fn parser_oct() {
assert_eq!(numf_parser::<u32>("0o771171").unwrap(), 0o771171);
}
#[test]
fn parser_b64() {
assert_eq!(numf_parser::<u32>("0sQUFCQg==").unwrap(), 0x41414242);
}
#[test]
fn parser_b32() {
assert_eq!(numf_parser::<u32>("032sIFAUEQQ=").unwrap(), 0x41414242);
}
#[test]
fn parser_generics() {
assert_eq!(numf_parser::<u8>("55").unwrap(), 55);
assert_eq!(numf_parser::<u16>("55").unwrap(), 55);
assert_eq!(numf_parser::<u32>("55").unwrap(), 55);
assert_eq!(numf_parser::<u64>("55").unwrap(), 55);
assert_eq!(numf_parser::<u128>("55").unwrap(), 55);
}