generated from PlexSheep/rs-base
Compare commits
78 Commits
Author | SHA1 | Date |
---|---|---|
Christoph J. Scherr | 2cbc6b3b25 | |
Christoph J. Scherr | c09a1f243a | |
Christoph J. Scherr | 335a5b2013 | |
Christoph J. Scherr | 6b79c023bb | |
Christoph J. Scherr | fe71372de5 | |
Christoph J. Scherr | 0261d74acd | |
Christoph J. Scherr | df32a863d6 | |
Christoph J. Scherr | 45409c4ff3 | |
Christoph J. Scherr | 24ad56c111 | |
Christoph J. Scherr | c18d1c09e9 | |
cscherrNT | f3d7d807d5 | |
Christoph J. Scherr | c33b4ad1ed | |
Christoph J. Scherr | d315e24034 | |
cscherrNT | 887a29d86a | |
Christoph J. Scherr | e210b688f8 | |
Christoph J. Scherr | 727b84e56b | |
cscherrNT | d7372b93a3 | |
Christoph J. Scherr | e92af8acfa | |
Christoph J. Scherr | 23c653e9c0 | |
Christoph J. Scherr | 03a2243a3e | |
Christoph J. Scherr | 71f1f2ba08 | |
Christoph J. Scherr | c5790f9d79 | |
PlexSheep | 93a27564c6 | |
Christoph J. Scherr | 3926be8aac | |
Christoph J. Scherr | 8fbc612f5a | |
PlexSheep | 950c26a35c | |
Christoph J. Scherr | 0923bf5247 | |
Christoph J. Scherr | 826c3c7893 | |
Christoph J. Scherr | 2da1887379 | |
Christoph J. Scherr | d87d5d02da | |
Christoph J. Scherr | 9c8446c78d | |
Christoph J. Scherr | 3603c51ea9 | |
Christoph J. Scherr | 947636cf0a | |
Christoph J. Scherr | ccd4f7ac93 | |
Christoph J. Scherr | eba7d2fe01 | |
Christoph J. Scherr | 1a87391d5e | |
Christoph J. Scherr | f4b8927a6a | |
Christoph J. Scherr | 0520032ab5 | |
Christoph J. Scherr | 9c9b476b22 | |
Christoph J. Scherr | 83250589fb | |
Christoph J. Scherr | b73850a25d | |
Christoph J. Scherr | bf4e7b00d7 | |
Christoph J. Scherr | f93e5f6c4c | |
Christoph J. Scherr | 09a7fa22cf | |
Christoph J. Scherr | 378e3d7fd2 | |
Christoph J. Scherr | 706c06ce6f | |
Christoph J. Scherr | 585bb41a9d | |
Christoph J. Scherr | 00c6b35ef3 | |
Christoph J. Scherr | 1f4818ff98 | |
Christoph J. Scherr | abb8ed4f2e | |
PlexSheep | 6f50918f27 | |
Christoph J. Scherr | 7eb00e0f56 | |
Christoph J. Scherr | 1bb0ffd6f8 | |
Christoph J. Scherr | 2f14d672f4 | |
Christoph J. Scherr | 68d9cdaf69 | |
Christoph J. Scherr | 44be69a262 | |
Christoph J. Scherr | 47894a3f26 | |
Christoph J. Scherr | 6649470ff7 | |
Christoph J. Scherr | c6ad45fb82 | |
Christoph J. Scherr | 18cccddbb2 | |
Christoph J. Scherr | 399021ecc6 | |
Christoph J. Scherr | b007a54b4b | |
Christoph J. Scherr | 97608840b9 | |
PlexSheep | acdec5836e | |
Christoph J. Scherr | 9700ae5b35 | |
Christoph J. Scherr | 290b959b7e | |
Christoph J. Scherr | a88a03eab7 | |
Christoph J. Scherr | 1f91710bda | |
Christoph J. Scherr | b681a6b04d | |
Christoph J. Scherr | fe01412467 | |
PlexSheep | cd7f7cc376 | |
Christoph J. Scherr | 9bfc8504a4 | |
Christoph J. Scherr | 47ae8e135b | |
Christoph J. Scherr | 8b419048ef | |
Christoph J. Scherr | 6251546a31 | |
Christoph J. Scherr | 4f269fb509 | |
Christoph J. Scherr | cae701815e | |
Christoph J. Scherr | 82c1bf8d8a |
|
@ -1,46 +0,0 @@
|
||||||
name: cargo devel CI
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- '**'
|
|
||||||
# - '!master'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
format:
|
|
||||||
name: cargo CI
|
|
||||||
permissions:
|
|
||||||
# Give the default GITHUB_TOKEN write permission to commit and push the
|
|
||||||
# added or changed files to the repository.
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: get repo
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: install rust
|
|
||||||
uses: https://github.com/dtolnay/rust-toolchain@stable
|
|
||||||
- name: install additional rust things
|
|
||||||
run: |
|
|
||||||
rustup component add rustfmt
|
|
||||||
rustup component add clippy
|
|
||||||
- name: config custom registry
|
|
||||||
run: |
|
|
||||||
mkdir -p ~/.cargo/
|
|
||||||
echo "" > ~/.cargo/config.toml
|
|
||||||
echo "[registry]" >> ~/.cargo/config.toml
|
|
||||||
echo 'cscherr = "cscherr"' >> ~/.cargo/config.toml
|
|
||||||
echo '[registries.cscherr]' >> ~/.cargo/config.toml
|
|
||||||
echo 'index = "https://git.cscherr.de/PlexSheep/_cargo-index.git"' >> ~/.cargo/config.toml
|
|
||||||
cat ~/.cargo/config.toml
|
|
||||||
- name: cargo clippy check
|
|
||||||
run: cargo clippy --all-features --all-targets --workspace
|
|
||||||
- name: cargo clippy fix
|
|
||||||
run: cargo clippy --fix --all-features --all-targets --workspace
|
|
||||||
- name: cargo fmt
|
|
||||||
run: cargo fmt --all
|
|
||||||
- name: cargo test
|
|
||||||
run: cargo test --all-features --all-targets --workspace && cargo test --all-features --workspace --doc
|
|
||||||
- name: commit back to repository
|
|
||||||
uses: https://github.com/stefanzweifel/git-auto-commit-action@v5
|
|
||||||
with:
|
|
||||||
# Optional. Commit message for the created commit.
|
|
||||||
# Defaults to "Apply automatic changes"
|
|
||||||
commit_message: automatic cargo CI changes
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
# Check for updates every Monday
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
|
@ -1,10 +1,16 @@
|
||||||
name: cargo devel CI
|
name: Rust CI
|
||||||
on:
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- '**'
|
- '**'
|
||||||
# - '!master'
|
# - '!master'
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
CI:
|
CI:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -38,10 +44,10 @@ jobs:
|
||||||
- name: cargo fmt
|
- name: cargo fmt
|
||||||
run: cargo fmt --all
|
run: cargo fmt --all
|
||||||
- name: cargo test
|
- name: cargo test
|
||||||
run: cargo test --all-features --all-targets --workspace && cargo test --all-features --workspace --doc
|
run: cargo test --all-features --all-targets --workspace
|
||||||
- name: commit back to repository
|
- name: commit back to repository
|
||||||
uses: stefanzweifel/git-auto-commit-action@v5
|
uses: stefanzweifel/git-auto-commit-action@v5
|
||||||
with:
|
with:
|
||||||
# Optional. Commit message for the created commit.
|
# Optional. Commit message for the created commit.
|
||||||
# Defaults to "Apply automatic changes"
|
# Defaults to "Apply automatic changes"
|
||||||
commit_message: automatic cargo CI changes
|
commit_message: "ci: automatic Rust CI changes"
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
name: Release-plz
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
# Release unpublished packages.
|
||||||
|
release-plz-release:
|
||||||
|
name: Release-plz release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: Run release-plz
|
||||||
|
uses: MarcoIeni/release-plz-action@v0.5
|
||||||
|
with:
|
||||||
|
command: release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||||
|
|
||||||
|
# Create a PR with the new versions and changelog, preparing the next release.
|
||||||
|
release-plz-pr:
|
||||||
|
name: Release-plz PR
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
concurrency:
|
||||||
|
group: release-plz-${{ github.ref }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: Run release-plz
|
||||||
|
uses: MarcoIeni/release-plz-action@v0.5
|
||||||
|
with:
|
||||||
|
command: release-pr
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "numf"
|
name = "numf"
|
||||||
version = "0.1.0"
|
version = "0.4.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = true
|
publish = true
|
||||||
authors = ["Christoph J. Scherr <software@cscherr.de>"]
|
authors = ["Christoph J. Scherr <software@cscherr.de>"]
|
||||||
|
@ -16,7 +16,7 @@ categories = ["command-line-utilities", "encoding"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.83"
|
anyhow = "1.0.83"
|
||||||
clap = { version = "4.5.4", features = ["derive"] }
|
clap = { version = "4.5.4", features = ["derive"] }
|
||||||
clap-num = "1.1.1"
|
|
||||||
fast32 = "1.0.2"
|
fast32 = "1.0.2"
|
||||||
libpt = { version = "0.5.0", features = ["bintols"]}
|
libpt = { version = "0.6.0", features = ["bintols", "log", "cli"] }
|
||||||
|
num = "0.4.3"
|
||||||
|
rand = "0.8.5"
|
||||||
|
|
48
README.md
48
README.md
|
@ -1,5 +1,15 @@
|
||||||
# numf
|
# numf
|
||||||
|
|
||||||
|
![Project badge](https://img.shields.io/badge/language-Rust-blue.svg)
|
||||||
|
![Crates.io License](https://img.shields.io/crates/l/numf)
|
||||||
|
![GitHub Release](https://img.shields.io/github/v/release/PlexSheep/numf)
|
||||||
|
![GitHub language count](https://img.shields.io/github/languages/count/PlexSheep/numf)
|
||||||
|
[![Rust CI](https://github.com/PlexSheep/numf/actions/workflows/cargo.yaml/badge.svg)](https://github.com/PlexSheep/numf/actions/workflows/cargo.yaml)
|
||||||
|
|
||||||
|
* [GitHub](https://github.com/PlexSheep/numf)
|
||||||
|
* [crates.io](https://crates.io/crates/numf)
|
||||||
|
* [docs.rs](https://docs.rs/numf/latest/numf/)
|
||||||
|
|
||||||
`numf` is a number formatter. It formats the numbers provided to it.
|
`numf` is a number formatter. It formats the numbers provided to it.
|
||||||
|
|
||||||
Current formats are:
|
Current formats are:
|
||||||
|
@ -8,9 +18,13 @@ Current formats are:
|
||||||
- Binary
|
- Binary
|
||||||
- Octal
|
- Octal
|
||||||
- Decimal
|
- Decimal
|
||||||
|
- Base32
|
||||||
|
- Base64
|
||||||
|
- Raw
|
||||||
|
|
||||||
`numf` also has the option of prepending a prefix for each format, such as
|
`numf` also has the option of prepending a prefix for the formats, such as
|
||||||
`0x` for hexadecimal.
|
`0x` for hexadecimal. Numbers may also be provided from the stdin. See `--help`
|
||||||
|
flag for more information.
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
|
@ -19,4 +33,34 @@ $ numf -xp 1337 505 0xaabb
|
||||||
0x539
|
0x539
|
||||||
0x1F9
|
0x1F9
|
||||||
0xAABB
|
0xAABB
|
||||||
|
$ numf -a 505 | hedxump -C
|
||||||
|
00000000 01 f9 |..|
|
||||||
|
00000002
|
||||||
|
$ numf -a 505 | numf
|
||||||
|
1F9
|
||||||
|
$ numf -a 505 | numf -d
|
||||||
|
505
|
||||||
|
$ numf -a 505 | numf -b
|
||||||
|
111111001
|
||||||
|
$ echo -ne "\x20\xff\xb4" | numf -xpP
|
||||||
|
0x20FFB4
|
||||||
|
$ echo -ne "\x20\xff\xb4" | numf -d
|
||||||
|
2162612
|
||||||
|
$ base64='aGVsbG8gd29ybGQuCg==' ; echo "0s$base64" | numf -d
|
||||||
|
8271117963529473544792763018762
|
||||||
|
$ base64='aGVsbG8gd29ybGQuCg==' ; echo "0s$base64" | numf -s
|
||||||
|
aGVsbG8gd29ybGQuCg==
|
||||||
|
$ echo "0b100100101010" | numf -d
|
||||||
|
2346
|
||||||
|
$ echo "0b100100101010" | numf -bPp
|
||||||
|
0b0000100100101010
|
||||||
|
```
|
||||||
|
## Installing
|
||||||
|
|
||||||
|
### Cargo
|
||||||
|
|
||||||
|
`numf` is on [crates.io](https://crates.io).
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo install numf
|
||||||
```
|
```
|
||||||
|
|
|
@ -4,7 +4,7 @@ NEW_VERSION=$(cat Cargo.toml | rg '^\s*version\s*=\s*"([^"]*)"\s*$' -or '$1')
|
||||||
GIT_COMMIT_SHA=$(git rev-parse HEAD)
|
GIT_COMMIT_SHA=$(git rev-parse HEAD)
|
||||||
REPO=${PWD##*/} # name of cwd
|
REPO=${PWD##*/} # name of cwd
|
||||||
BODY="
|
BODY="
|
||||||
$(git log $(git describe --tags --abbrev=0)..HEAD --pretty="- %s" --oneline --decorate)
|
$(git log $(git describe --tags --abbrev=0)..HEAD --pretty="- %s" --oneline --no-decorate)
|
||||||
"
|
"
|
||||||
USER=PlexSheep
|
USER=PlexSheep
|
||||||
git tag "v$NEW_VERSION" || echo "could not tag"
|
git tag "v$NEW_VERSION" || echo "could not tag"
|
||||||
|
|
628
src/format.rs
628
src/format.rs
|
@ -1,57 +1,637 @@
|
||||||
use libpt::bintols::split;
|
//! This module implements the actual formatting in [numf](crate).
|
||||||
|
//!
|
||||||
|
//! You can use it in your own program to convert numbers to formats.
|
||||||
|
//!
|
||||||
|
//! # Example
|
||||||
|
//!
|
||||||
|
//! The following example shows how to use numf to format your integers.
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use numf::format::{Format, FormatOptions};
|
||||||
|
//!
|
||||||
|
//! let mut options = FormatOptions::default();
|
||||||
|
//! options.set_prefix(true);
|
||||||
|
//! options.set_padding(true);
|
||||||
|
//!
|
||||||
|
//! assert_eq!(Format::Hex.format_str(0x1337, &options), "0x1337");
|
||||||
|
//! assert_eq!(Format::Base32.format_str(0x41414242, &options), "032sIFAUEQQ=");
|
||||||
|
//! assert_eq!(Format::Base64.format_str(0x41414242, &options), "0sQUFCQg==");
|
||||||
|
//! // sometimes you might need the raw bytes instead of a String
|
||||||
|
//! assert_eq!(Format::Raw.format(0x1337, &options), vec![0x00, 0x13, 0x37]);
|
||||||
|
//! assert_eq!(Format::Hex.format(0x1337, &options), vec![48, 120, 49, 51, 51, 55]);
|
||||||
|
//!
|
||||||
|
//! options.set_prefix(false);
|
||||||
|
//! options.set_padding(false);
|
||||||
|
//!
|
||||||
|
//! assert_eq!(Format::Hex.format_str(0x1337, &options), "1337");
|
||||||
|
//! assert_eq!(Format::Base32.format_str(0x41414242, &options), "IFAUEQQ=");
|
||||||
|
//! assert_eq!(Format::Base64.format_str(0x41414242, &options), "QUFCQg==");
|
||||||
|
//!
|
||||||
|
//! assert_eq!(Format::Raw.format(0x1337, &options), vec![0x13, 0x37]);
|
||||||
|
//! assert_eq!(Format::Hex.format(0x1337, &options), vec![49, 51, 51, 55]);
|
||||||
|
//! ```
|
||||||
|
|
||||||
pub type Num = u128;
|
#![allow(dead_code)]
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
// this is exported to lib.rs
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use clap::{ArgGroup, Parser};
|
||||||
|
use libpt::bintols::{join, split};
|
||||||
|
use libpt::cli::args::VerbosityLevel;
|
||||||
|
use libpt::log::{debug, trace};
|
||||||
|
|
||||||
|
/// The number type [numf](crate) uses
|
||||||
|
pub type NumberType = u128;
|
||||||
|
|
||||||
|
/// Describes a format for numbers
|
||||||
|
///
|
||||||
|
/// [Format] can be used to convert unsigned integers into a textual or other representation. See
|
||||||
|
/// [Format::format_str] for more. It is also possible to parse the various represenations to
|
||||||
|
/// a rust integer, see [numf_parser_str] for that.
|
||||||
|
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Default)]
|
||||||
pub enum Format {
|
pub enum Format {
|
||||||
Dec,
|
Dec,
|
||||||
|
#[default]
|
||||||
Hex,
|
Hex,
|
||||||
Bin,
|
Bin,
|
||||||
Octal,
|
Octal,
|
||||||
Base64,
|
Base64,
|
||||||
Base32,
|
Base32,
|
||||||
|
/// Write raw data, not text
|
||||||
|
Raw,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Format {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes what the formatter should do exactly
|
||||||
|
///
|
||||||
|
/// Use [Self::default] to get a basic variant or create a object yourself.
|
||||||
|
///
|
||||||
|
/// This struct can be parsed with [clap] derive.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use numf::format::{Format, FormatOptions};
|
||||||
|
/// let mut options = FormatOptions::default();
|
||||||
|
///
|
||||||
|
/// assert_eq!(Format::Bin.format_str(256, &options), "100000000");
|
||||||
|
/// assert_eq!(Format::Hex.format_str(256, &options), "100");
|
||||||
|
/// assert_eq!(Format::Base64.format_str(256, &options), "AQA=");
|
||||||
|
///
|
||||||
|
/// options.set_prefix(true);
|
||||||
|
/// options.set_padding(true);
|
||||||
|
///
|
||||||
|
/// assert_eq!(Format::Bin.format_str(256, &options), "0b0000000100000000");
|
||||||
|
/// assert_eq!(Format::Hex.format_str(256, &options), "0x0100");
|
||||||
|
/// assert_eq!(Format::Base64.format_str(256, &options), "0sAQA=");
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
#[derive(Parser, Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
#[clap(author, version, about, long_about = None)]
|
||||||
|
#[command(
|
||||||
|
author,
|
||||||
|
version,
|
||||||
|
about,
|
||||||
|
long_about,
|
||||||
|
help_template = r#"{about-section}
|
||||||
|
{usage-heading} {usage}
|
||||||
|
{all-args}{tab}
|
||||||
|
|
||||||
|
{name}: {version}
|
||||||
|
Author: {author-with-newline}
|
||||||
|
"#
|
||||||
|
)]
|
||||||
|
#[clap(group(
|
||||||
|
ArgGroup::new("format")
|
||||||
|
.args(&["hex", "bin", "oct", "dec", "base64", "base32", "raw"]),
|
||||||
|
))]
|
||||||
|
pub struct FormatOptions {
|
||||||
|
#[arg(short, long)]
|
||||||
|
/// add a prefix (like "0x" for hex)
|
||||||
|
prefix: bool,
|
||||||
|
#[arg(short = 'P', long)]
|
||||||
|
/// add a padding to make the number at least one byte long
|
||||||
|
///
|
||||||
|
/// For example, `0b1100` will be `0b00001100` with this.
|
||||||
|
/// This does not apply to all formats, only hexadecimal and binary.
|
||||||
|
padding: bool,
|
||||||
|
#[arg(short = 'x', long)]
|
||||||
|
/// format to hexadecimal
|
||||||
|
hex: bool,
|
||||||
|
#[arg(short, long)]
|
||||||
|
/// format to binary
|
||||||
|
bin: bool,
|
||||||
|
#[arg(short, long)]
|
||||||
|
/// format to decimal
|
||||||
|
dec: bool,
|
||||||
|
#[arg(short, long)]
|
||||||
|
/// format to octal
|
||||||
|
oct: bool,
|
||||||
|
#[arg(short = 's', long)]
|
||||||
|
/// format to base64
|
||||||
|
base64: bool,
|
||||||
|
#[arg(short = 'a', long)]
|
||||||
|
/// format raw, no text
|
||||||
|
raw: bool,
|
||||||
|
#[arg(short = 'r', long, default_value_t = 0, value_parser=numf_parser_str::<NumberType>)]
|
||||||
|
/// output random numbers
|
||||||
|
///
|
||||||
|
/// Add a user defined amount of cryptographically pseudorandom numbers to the number list.
|
||||||
|
rand: NumberType,
|
||||||
|
#[arg(long, default_value_t = NumberType::MAX, value_parser=numf_parser_str::<NumberType>)]
|
||||||
|
/// max for the random numbers
|
||||||
|
///
|
||||||
|
/// Generated numbers will not be lower than this. Only has an effect with --rand set.
|
||||||
|
rand_max: NumberType,
|
||||||
|
#[arg(short = 'z', long)]
|
||||||
|
/// format to base32
|
||||||
|
base32: bool,
|
||||||
|
#[clap(value_parser=numf_parser_str::<NumberType>, required=false)]
|
||||||
|
/// numbers that should be formatted
|
||||||
|
///
|
||||||
|
/// Any of the [Formats](Format::format) are supported, but the prefixes are needed for formats
|
||||||
|
/// other than decimal.
|
||||||
|
///
|
||||||
|
/// Formats:
|
||||||
|
///
|
||||||
|
/// * '0x' - Hexadecimal
|
||||||
|
///
|
||||||
|
/// * '0b' - Binary
|
||||||
|
///
|
||||||
|
/// * '0o' - Octal
|
||||||
|
///
|
||||||
|
/// * '0s' - Base64
|
||||||
|
///
|
||||||
|
/// * '032s' - Base32
|
||||||
|
///
|
||||||
|
/// The numbers may be left empty at first, if numbers are provided from the stdin.
|
||||||
|
numbers: Vec<NumberType>,
|
||||||
|
|
||||||
|
#[command(flatten)]
|
||||||
|
pub(crate) verbosity: VerbosityLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormatOptions {
|
||||||
|
/// get the format that the user has configured
|
||||||
|
pub fn format(&self) -> Format {
|
||||||
|
trace!("self.hex: {}", self.hex);
|
||||||
|
if self.oct {
|
||||||
|
Format::Octal
|
||||||
|
} else if self.bin {
|
||||||
|
Format::Bin
|
||||||
|
} else if self.dec {
|
||||||
|
Format::Dec
|
||||||
|
} else if self.base64 {
|
||||||
|
Format::Base64
|
||||||
|
} else if self.base32 {
|
||||||
|
Format::Base32
|
||||||
|
} else if self.hex {
|
||||||
|
Format::Hex
|
||||||
|
} else if self.raw {
|
||||||
|
Format::Raw
|
||||||
|
} else {
|
||||||
|
// none was explicitly selected
|
||||||
|
debug!("no mode was explicitly selected, going with the default");
|
||||||
|
Format::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set the format manually
|
||||||
|
pub fn set_format(&mut self, format: Format) {
|
||||||
|
self.bin = false;
|
||||||
|
self.oct = false;
|
||||||
|
self.dec = false;
|
||||||
|
self.hex = false;
|
||||||
|
self.base64 = false;
|
||||||
|
self.raw = false;
|
||||||
|
self.base32 = false;
|
||||||
|
match format {
|
||||||
|
Format::Bin => self.bin = true,
|
||||||
|
Format::Raw => self.raw = true,
|
||||||
|
Format::Hex => self.hex = true,
|
||||||
|
Format::Octal => self.oct = true,
|
||||||
|
Format::Base64 => self.base64 = true,
|
||||||
|
Format::Base32 => self.base32 = true,
|
||||||
|
Format::Dec => self.dec = true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get numbers
|
||||||
|
pub fn numbers(&self) -> &[u128] {
|
||||||
|
self.numbers.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set numbers manually
|
||||||
|
pub fn set_numbers(&mut self, numbers: Vec<NumberType>) {
|
||||||
|
self.numbers = numbers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set padding manually
|
||||||
|
pub fn set_padding(&mut self, value: bool) {
|
||||||
|
self.padding = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get padding
|
||||||
|
pub fn padding(&self) -> bool {
|
||||||
|
self.padding
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get prefix
|
||||||
|
pub fn prefix(&self) -> bool {
|
||||||
|
self.prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set prefix manually
|
||||||
|
pub fn set_prefix(&mut self, value: bool) {
|
||||||
|
self.prefix = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// manually add a number
|
||||||
|
pub fn push_number(&mut self, value: NumberType) {
|
||||||
|
self.numbers.push(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get rand
|
||||||
|
pub fn rand(&self) -> NumberType {
|
||||||
|
self.rand
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set amount of extra random numbers manually
|
||||||
|
pub fn set_rand(&mut self, rand: NumberType) {
|
||||||
|
self.rand = rand;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get highes allowed random value
|
||||||
|
pub fn rand_max(&self) -> NumberType {
|
||||||
|
self.rand_max
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set highes allowed random value
|
||||||
|
pub fn set_rand_max(&mut self, rand_max: NumberType) {
|
||||||
|
self.rand_max = rand_max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FormatOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
padding: false,
|
||||||
|
prefix: false,
|
||||||
|
oct: false,
|
||||||
|
hex: false,
|
||||||
|
bin: false,
|
||||||
|
raw: false,
|
||||||
|
base32: false,
|
||||||
|
base64: false,
|
||||||
|
dec: false,
|
||||||
|
numbers: vec![],
|
||||||
|
rand: 0,
|
||||||
|
rand_max: NumberType::MAX,
|
||||||
|
verbosity: VerbosityLevel::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Format {
|
impl Format {
|
||||||
pub fn prefix(&self) -> String {
|
/// Get the perfix for that [Format] as [Vec<u8>].
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use numf::format::Format;
|
||||||
|
/// assert_eq!(Format::Bin.prefix_str(), "0b");
|
||||||
|
/// assert_eq!(Format::Dec.prefix_str(), "0d");
|
||||||
|
/// assert_eq!(Format::Hex.prefix_str(), "0x");
|
||||||
|
/// assert_eq!(Format::Octal.prefix_str(), "0o");
|
||||||
|
/// assert_eq!(Format::Base64.prefix_str(), "0s");
|
||||||
|
/// assert_eq!(Format::Base32.prefix_str(), "032s");
|
||||||
|
/// assert_eq!(Format::Raw.prefix_str(), "\x00");
|
||||||
|
/// ```
|
||||||
|
pub fn prefix_str(&self) -> String {
|
||||||
|
String::from_utf8_lossy(&self.prefix()).to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the perfix for that [Format] as [Vec<u8>].
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use numf::format::Format;
|
||||||
|
/// assert_eq!(Format::Bin.prefix(), b"0b");
|
||||||
|
/// assert_eq!(Format::Dec.prefix(), b"0d");
|
||||||
|
/// assert_eq!(Format::Hex.prefix(), b"0x");
|
||||||
|
/// assert_eq!(Format::Octal.prefix(), b"0o");
|
||||||
|
/// assert_eq!(Format::Base64.prefix(), b"0s");
|
||||||
|
/// assert_eq!(Format::Base32.prefix(), b"032s");
|
||||||
|
/// assert_eq!(Format::Raw.prefix(), vec![0x00]);
|
||||||
|
/// ```
|
||||||
|
pub fn prefix(&self) -> Vec<u8> {
|
||||||
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
|
||||||
// think this makes it more clear that this is decimal
|
// think this makes it more clear that this is decimal
|
||||||
Format::Dec => "0d",
|
Format::Dec => b"0d".to_vec(),
|
||||||
|
Format::Raw => [0x00].to_vec(),
|
||||||
// very common
|
// very common
|
||||||
Format::Hex => "0x",
|
Format::Hex => b"0x".to_vec(),
|
||||||
// very common
|
// very common
|
||||||
Format::Bin => "0b",
|
Format::Bin => b"0b".to_vec(),
|
||||||
// somewhat common
|
// somewhat common
|
||||||
Format::Octal => "0o",
|
Format::Octal => b"0o".to_vec(),
|
||||||
// perl and a few other programs seem to use this too
|
// perl and a few other programs seem to use this too
|
||||||
Format::Base64 => "0s",
|
Format::Base64 => b"0s".to_vec(),
|
||||||
// no idea, I made this up
|
// no idea, I made this up
|
||||||
Format::Base32 => "032s",
|
Format::Base32 => b"032s".to_vec(),
|
||||||
}
|
}
|
||||||
.to_string()
|
|
||||||
}
|
}
|
||||||
pub fn format(&self, num: Num, prefix: bool) -> String {
|
/// format a number with a [Format] and [FormatOptions] to a [String]
|
||||||
let mut buf = String::new();
|
///
|
||||||
if prefix {
|
/// If you need raw byte outputs, use [Format::format] instead.
|
||||||
buf += &self.prefix();
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use numf::format::{Format, FormatOptions};
|
||||||
|
/// let mut options = FormatOptions::default();
|
||||||
|
///
|
||||||
|
/// assert_eq!(Format::Bin.format_str(256, &options), "100000000");
|
||||||
|
/// assert_eq!(Format::Hex.format_str(256, &options), "100");
|
||||||
|
/// assert_eq!(Format::Base64.format_str(256, &options), "AQA=");
|
||||||
|
///
|
||||||
|
/// options.set_prefix(true);
|
||||||
|
/// options.set_padding(true);
|
||||||
|
///
|
||||||
|
/// assert_eq!(Format::Bin.format_str(256, &options), "0b0000000100000000");
|
||||||
|
/// assert_eq!(Format::Hex.format_str(256, &options), "0x0100");
|
||||||
|
/// assert_eq!(Format::Base64.format_str(256, &options), "0sAQA=");
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
pub fn format_str(&self, num: NumberType, options: &FormatOptions) -> String {
|
||||||
|
String::from_utf8_lossy(&self.format(num, options)).to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// format a number with a [Format] and [FormatOptions] to a byte vector [Vec<u8>]
|
||||||
|
///
|
||||||
|
/// If you need [String] outputs, use [Format::format_str] instead.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use numf::format::{Format, FormatOptions};
|
||||||
|
/// let mut options = FormatOptions::default();
|
||||||
|
///
|
||||||
|
/// assert_eq!(Format::Bin.format(256, &options), b"100000000");
|
||||||
|
/// assert_eq!(Format::Hex.format(256, &options), b"100");
|
||||||
|
/// assert_eq!(Format::Hex.format(256, &options), [49, 48, 48]);
|
||||||
|
/// assert_eq!(Format::Base64.format(256, &options), b"AQA=");
|
||||||
|
/// assert_eq!(Format::Raw.format(256, &options), [1, 0]);
|
||||||
|
///
|
||||||
|
/// options.set_prefix(true);
|
||||||
|
/// options.set_padding(true);
|
||||||
|
///
|
||||||
|
/// assert_eq!(Format::Bin.format(256, &options), b"0b0000000100000000");
|
||||||
|
/// assert_eq!(Format::Hex.format(256, &options), b"0x0100");
|
||||||
|
/// assert_eq!(Format::Hex.format(256, &options), [48, 120, 48, 49, 48, 48]);
|
||||||
|
/// assert_eq!(Format::Base64.format(256, &options), b"0sAQA=");
|
||||||
|
/// assert_eq!(Format::Raw.format(256, &options), [0, 1, 0]);
|
||||||
|
/// assert_eq!(Format::Raw.format(255, &options), [0, 255]);
|
||||||
|
/// assert_eq!(Format::Raw.format(32000, &options), [0, 125, 0]);
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
pub fn format(&self, num: NumberType, options: &FormatOptions) -> Vec<u8> {
|
||||||
|
debug!("formatting mode: {self}");
|
||||||
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
|
if options.prefix() {
|
||||||
|
buf.append(&mut self.prefix());
|
||||||
|
debug!("prefix the buffer: {buf:X?}");
|
||||||
}
|
}
|
||||||
match self {
|
match self {
|
||||||
Format::Hex => {
|
Format::Hex => {
|
||||||
buf += &format!("{num:X}");
|
if options.padding() {
|
||||||
|
let tmp = &format!("{num:X}");
|
||||||
|
let tmp1 = &("0".repeat((2 - tmp.len() % 2) % 2) + tmp);
|
||||||
|
buf.append(&mut tmp1.as_bytes().to_owned());
|
||||||
|
} else {
|
||||||
|
buf.append(&mut format!("{num:X}").as_bytes().to_owned());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Format::Bin => {
|
Format::Bin => {
|
||||||
buf += &format!("{num:b}");
|
if options.padding() {
|
||||||
|
let tmp = &format!("{num:b}");
|
||||||
|
let tmp1 = &("0".repeat((8 - tmp.len() % 8) % 8) + tmp);
|
||||||
|
buf.append(&mut tmp1.as_bytes().to_owned());
|
||||||
|
} else {
|
||||||
|
buf.append(&mut format!("{num:b}").as_bytes().to_owned());
|
||||||
}
|
}
|
||||||
Format::Octal => {
|
|
||||||
buf += &format!("{num:o}");
|
|
||||||
}
|
}
|
||||||
Format::Dec => {
|
Format::Octal => buf.append(&mut format!("{num:o}").as_bytes().to_owned()),
|
||||||
buf += &format!("{num}");
|
Format::Dec => buf.append(&mut format!("{num}").as_bytes().to_owned()),
|
||||||
}
|
Format::Base64 => buf.append(
|
||||||
Format::Base64 => buf += &fast32::base64::RFC4648.encode(&split::unsigned_to_vec(num)),
|
&mut fast32::base64::RFC4648
|
||||||
Format::Base32 => buf += &fast32::base32::RFC4648.encode(&split::unsigned_to_vec(num)),
|
.encode(&split::unsigned_to_vec(num))
|
||||||
|
.as_bytes()
|
||||||
|
.to_owned(),
|
||||||
|
),
|
||||||
|
Format::Base32 => buf.append(
|
||||||
|
&mut fast32::base32::RFC4648
|
||||||
|
.encode(&split::unsigned_to_vec(num))
|
||||||
|
.as_bytes()
|
||||||
|
.to_owned(),
|
||||||
|
),
|
||||||
|
// Format::Raw => buf.append(&mut split::unsigned_to_vec(num)),
|
||||||
|
Format::Raw => buf.append(&mut split::unsigned_to_vec(num)),
|
||||||
}
|
}
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts a &[str] into an unsigned integer value (like [u128]), according to one of the [Formats](Format)
|
||||||
|
///
|
||||||
|
/// The number is assumed to be base-10 by default, it is parsed as a different
|
||||||
|
/// [Format] if the number is prefixed with the [prefix](FormatOptions::prefix),
|
||||||
|
/// for that [Format]. So if the user inputs `0b1100` then this is parsed as
|
||||||
|
/// [Binary](Format::Bin) and so on.
|
||||||
|
///
|
||||||
|
/// If you also want to parse raw inputs, use [numf_parser].
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// This parser will only output unsigned integers, it cannot be used with signed integers.
|
||||||
|
///
|
||||||
|
/// # 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_str;
|
||||||
|
///
|
||||||
|
/// #[derive(Parser)]
|
||||||
|
/// struct Args {
|
||||||
|
/// #[clap(short, long, value_parser=numf_parser_str::<u128>)]
|
||||||
|
/// address: u128,
|
||||||
|
/// }
|
||||||
|
/// let args = Args::parse_from(&["", "-a", "0x10"]);
|
||||||
|
/// assert_eq!(args.address, 16);
|
||||||
|
/// ```
|
||||||
|
pub fn numf_parser_str<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,
|
||||||
|
{
|
||||||
|
numf_parser(s.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts any data (as bytes) into an unsigned integer value `T` (like [u128]), according to one of the [Formats](Format)
|
||||||
|
///
|
||||||
|
/// If you only want to parse text data, use [numf_parser_str] instead.
|
||||||
|
///
|
||||||
|
/// The parser will first try to convert the data to a [String].
|
||||||
|
///
|
||||||
|
/// Then, the number is assumed to be base-10 by default, it is parsed as a different
|
||||||
|
/// [Format] if the number is prefixed with the [prefix](FormatOptions::prefix),
|
||||||
|
/// for that [Format]. So if the user inputs `0b1100` then this is parsed as
|
||||||
|
/// [Binary](Format::Bin) and so on.
|
||||||
|
///
|
||||||
|
/// If none of the text [Formats](Format) matches, the data will be assumed to be raw and converted
|
||||||
|
/// to the ingeger type directly.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If no text [Format] matches and the data is too long for the integer `T`.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// This parser will only output unsigned integers, it cannot be used with signed integers.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use numf::format::numf_parser;
|
||||||
|
///
|
||||||
|
/// let data = &[0x15, 0x92, 0xff];
|
||||||
|
/// let result: u64 = 0x1592ff;
|
||||||
|
/// assert_eq!(result, numf_parser(data).unwrap());
|
||||||
|
///
|
||||||
|
/// let data = b"0x1337";
|
||||||
|
/// let result: u64 = 0x1337;
|
||||||
|
/// assert_eq!(result, numf_parser(data).unwrap());
|
||||||
|
///
|
||||||
|
/// let data = b"0b110011";
|
||||||
|
/// let result: u64 = 0b110011;
|
||||||
|
/// assert_eq!(result, numf_parser(data).unwrap());
|
||||||
|
/// ```
|
||||||
|
pub fn numf_parser<T>(data: &[u8]) -> 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,
|
||||||
|
{
|
||||||
|
let data_as_text = String::from_utf8_lossy(data).to_string();
|
||||||
|
|
||||||
|
if data_as_text.starts_with(&Format::Dec.prefix_str()) || data_as_text.parse::<T>().is_ok() {
|
||||||
|
let s = match data_as_text.strip_prefix(&Format::Dec.prefix_str()) {
|
||||||
|
Some(sr) => sr,
|
||||||
|
None => &data_as_text,
|
||||||
|
};
|
||||||
|
match s.parse() {
|
||||||
|
Ok(r) => Ok(r),
|
||||||
|
Err(e) => {
|
||||||
|
let e = format!("{e}");
|
||||||
|
Err(anyhow!(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if data_as_text.starts_with(&Format::Hex.prefix_str()) {
|
||||||
|
let s = match data_as_text.strip_prefix(&Format::Hex.prefix_str()) {
|
||||||
|
Some(sr) => sr,
|
||||||
|
None => &data_as_text,
|
||||||
|
};
|
||||||
|
match T::from_str_radix(s, 16) {
|
||||||
|
Ok(r) => Ok(r),
|
||||||
|
Err(e) => {
|
||||||
|
let e = format!("{e}");
|
||||||
|
Err(anyhow!(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if data_as_text.starts_with(&Format::Octal.prefix_str()) {
|
||||||
|
let s = match data_as_text.strip_prefix(&Format::Octal.prefix_str()) {
|
||||||
|
Some(sr) => sr,
|
||||||
|
None => &data_as_text,
|
||||||
|
};
|
||||||
|
match T::from_str_radix(s, 8) {
|
||||||
|
Ok(r) => Ok(r),
|
||||||
|
Err(e) => {
|
||||||
|
let e = format!("{e}");
|
||||||
|
Err(anyhow!(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if data_as_text.starts_with(&Format::Bin.prefix_str()) {
|
||||||
|
let s = match data_as_text.strip_prefix(&Format::Bin.prefix_str()) {
|
||||||
|
Some(sr) => sr,
|
||||||
|
None => &data_as_text,
|
||||||
|
};
|
||||||
|
match T::from_str_radix(s, 2) {
|
||||||
|
Ok(r) => Ok(r),
|
||||||
|
Err(e) => {
|
||||||
|
let e = format!("{e}");
|
||||||
|
Err(anyhow!(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if data_as_text.starts_with(&Format::Base64.prefix_str()) {
|
||||||
|
let s = match data_as_text.strip_prefix(&Format::Base64.prefix_str()) {
|
||||||
|
Some(sr) => sr,
|
||||||
|
None => &data_as_text,
|
||||||
|
};
|
||||||
|
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 data_as_text.starts_with(&Format::Base32.prefix_str()) {
|
||||||
|
let s = match data_as_text.strip_prefix(&Format::Base32.prefix_str()) {
|
||||||
|
Some(sr) => sr,
|
||||||
|
None => &data_as_text,
|
||||||
|
};
|
||||||
|
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 {
|
||||||
|
// what could go wrong with interpreting everything else as raw number input
|
||||||
|
let s: Vec<u8> = if data.len() > 2 && data[0] == 0x00 {
|
||||||
|
data.iter().skip(1).map(ToOwned::to_owned).collect()
|
||||||
|
} else {
|
||||||
|
data.as_ref().to_vec()
|
||||||
|
};
|
||||||
|
Ok(join::array_to_unsigned(&s)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
13
src/lib.rs
13
src/lib.rs
|
@ -1,10 +1,17 @@
|
||||||
//! Format numbers
|
//! Format numbers
|
||||||
//!
|
//!
|
||||||
//! 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
|
||||||
|
//! , and back.
|
||||||
//!
|
//!
|
||||||
//! 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.
|
||||||
|
//!
|
||||||
|
//! Highlights:
|
||||||
|
//! * [format::numf_parser]
|
||||||
|
//! * [format::numf_parser_str]
|
||||||
|
//! * [format::Format::format]
|
||||||
|
//! * [format::Format::format_str]
|
||||||
|
|
||||||
pub mod format;
|
pub mod format;
|
||||||
|
|
153
src/main.rs
153
src/main.rs
|
@ -1,91 +1,96 @@
|
||||||
//! # numf
|
use std::io::{IsTerminal, Read, Write};
|
||||||
//!
|
use std::process::exit;
|
||||||
//! This binary should just take any amount of numbers and print them out formatted to some other
|
|
||||||
//! system.
|
|
||||||
|
|
||||||
use clap::{ArgGroup, Parser};
|
use clap::{CommandFactory, Parser};
|
||||||
use clap_num::maybe_hex;
|
use numf::format::numf_parser_str;
|
||||||
|
|
||||||
mod format;
|
mod format;
|
||||||
|
use crate::format::{numf_parser, Format};
|
||||||
use format::*;
|
use format::*;
|
||||||
|
use libpt::log::{debug, error};
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
fn main() -> anyhow::Result<()> {
|
||||||
#[clap(author, version, about, long_about = None)]
|
// try to read from stdin first, appending the numbers we read to the FormatOptions
|
||||||
#[command(
|
let mut options = FormatOptions::parse();
|
||||||
author,
|
let _logger = libpt::log::Logger::builder()
|
||||||
version,
|
.set_level(options.verbosity.level())
|
||||||
about,
|
.display_time(false)
|
||||||
long_about,
|
.build()
|
||||||
help_template = r#"{about-section}
|
.map_err(|e| {
|
||||||
{usage-heading} {usage}
|
error!("could not initialize logger: {e}");
|
||||||
{all-args}{tab}
|
});
|
||||||
|
debug!("logger active");
|
||||||
|
|
||||||
{name}: {version}
|
let mut stdin_nums = Vec::new();
|
||||||
Author: {author-with-newline}
|
let stdin = std::io::stdin();
|
||||||
"#
|
// only accept numbers from stdin if the stdin is not an interactive terminal
|
||||||
)]
|
if !stdin.is_terminal() {
|
||||||
#[clap(group(
|
match stdin.lock().read_to_end(&mut stdin_nums) {
|
||||||
ArgGroup::new("format")
|
Ok(_) => {
|
||||||
.args(&["hex", "bin", "oct", "dec", "base64", "base32"]),
|
let whole: String = match String::from_utf8(stdin_nums.clone()) {
|
||||||
))]
|
Ok(r) => r,
|
||||||
struct Cli {
|
Err(_) => {
|
||||||
#[arg(short, long)]
|
let number = match numf_parser(&stdin_nums) {
|
||||||
/// add a prefix (like "0x" for hex)
|
Ok(n) => n,
|
||||||
prefix: bool,
|
Err(e) => {
|
||||||
#[arg(short = 'x', long, default_value_t = true)]
|
eprintln!("{}", FormatOptions::command().render_usage());
|
||||||
/// format to hexadecimal
|
error!("could raw inputs from stdin as numbers: {e:#?}");
|
||||||
hex: bool,
|
exit(2);
|
||||||
#[arg(short, long)]
|
|
||||||
/// format to binary
|
|
||||||
bin: bool,
|
|
||||||
#[arg(short, long)]
|
|
||||||
/// format to decimal
|
|
||||||
dec: bool,
|
|
||||||
#[arg(short, long)]
|
|
||||||
/// format to octal
|
|
||||||
oct: bool,
|
|
||||||
#[arg(short = 's', long)]
|
|
||||||
/// format to base64
|
|
||||||
base64: bool,
|
|
||||||
#[arg(short = 'z', long)]
|
|
||||||
/// format to base32
|
|
||||||
base32: bool,
|
|
||||||
#[clap(value_parser=maybe_hex::<Num>, required=true)]
|
|
||||||
/// at least one number that should be formatted
|
|
||||||
///
|
|
||||||
/// supports either base 10 or base 16 inputs (with 0xaaaa)
|
|
||||||
numbers: Vec<Num>,
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
impl Cli {
|
options.push_number(number);
|
||||||
fn format(&self) -> Format {
|
String::new()
|
||||||
if self.oct {
|
}
|
||||||
Format::Octal
|
};
|
||||||
} else if self.bin {
|
let split = whole.split_whitespace();
|
||||||
Format::Bin
|
for s in split {
|
||||||
} else if self.dec {
|
let number = match numf_parser_str(s) {
|
||||||
Format::Dec
|
Ok(n) => n,
|
||||||
} else if self.base64 {
|
Err(e) => {
|
||||||
Format::Base64
|
eprintln!("{}", FormatOptions::command().render_usage());
|
||||||
} else if self.base32 {
|
error!("could not parse number from stdin: {e:#?}");
|
||||||
Format::Base32
|
exit(2);
|
||||||
} else if self.hex {
|
}
|
||||||
Format::Hex
|
};
|
||||||
} else {
|
options.push_number(number)
|
||||||
unreachable!()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("{}", FormatOptions::command().render_usage());
|
||||||
|
error!("could not read from stdin: {e:#?}");
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
// add random numbers to the number list, according to how many are requested
|
||||||
let cli = Cli::parse();
|
if options.rand() > 0 {
|
||||||
|
use rand::prelude::*;
|
||||||
|
let mut rand = rand::rngs::OsRng;
|
||||||
|
for _i in 0..options.rand() {
|
||||||
|
options.push_number(rand.gen_range(0..options.rand_max()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut out: Vec<String> = Vec::new();
|
// exit with error if no numbers are to be formatted
|
||||||
|
if options.numbers().is_empty() {
|
||||||
|
eprintln!("{}", FormatOptions::command().render_usage());
|
||||||
|
error!("no numbers have been provided");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
for num in &cli.numbers {
|
let mut out: Vec<Vec<u8>> = Vec::new();
|
||||||
out.push(cli.format().format(*num, cli.prefix));
|
|
||||||
|
for num in options.numbers() {
|
||||||
|
out.push(options.format().format(*num, &options));
|
||||||
}
|
}
|
||||||
for o in out {
|
for o in out {
|
||||||
println!("{o}")
|
let mut stdout = std::io::stdout();
|
||||||
|
stdout.write_all(&o)?;
|
||||||
|
if options.format() != Format::Raw {
|
||||||
|
stdout.write_all(b"\n")?;
|
||||||
}
|
}
|
||||||
|
stdout.flush()?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,272 @@
|
||||||
|
use numf::format::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format() {
|
||||||
|
let options = FormatOptions::default();
|
||||||
|
assert_eq!(Format::Dec.format_str(1337, &options), "1337");
|
||||||
|
assert_eq!(
|
||||||
|
Format::Dec.format_str(u128::MAX, &options),
|
||||||
|
format!("{}", u128::MAX)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(Format::Hex.format_str(0x1337, &options), "1337");
|
||||||
|
assert_eq!(
|
||||||
|
Format::Hex.format_str(u128::MAX, &options),
|
||||||
|
format!("{:X}", u128::MAX)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Format::Bin.format_str(0b1010001001010010010100111, &options),
|
||||||
|
"1010001001010010010100111"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Format::Bin.format_str(u128::MAX, &options),
|
||||||
|
format!("{:b}", u128::MAX)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(Format::Octal.format_str(0o13377331, &options), "13377331");
|
||||||
|
assert_eq!(
|
||||||
|
Format::Octal.format_str(u128::MAX, &options),
|
||||||
|
format!("{:o}", u128::MAX)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(Format::Base32.format_str(0x41414242, &options), "IFAUEQQ=");
|
||||||
|
assert_eq!(
|
||||||
|
Format::Base32.format_str(0x4141414141414141, &options),
|
||||||
|
"IFAUCQKBIFAUC==="
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(Format::Base64.format_str(0x41414242, &options), "QUFCQg==");
|
||||||
|
assert_eq!(
|
||||||
|
Format::Base64.format_str(0x4141414141414141, &options),
|
||||||
|
"QUFBQUFBQUE="
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(Format::Raw.format(0x1337, &options), vec![0x13, 0x37]);
|
||||||
|
assert_eq!(Format::Raw.format(0x0, &options), vec![0x0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_padding() {
|
||||||
|
let mut options = FormatOptions::default();
|
||||||
|
options.set_padding(true);
|
||||||
|
|
||||||
|
assert_eq!(Format::Dec.format_str(1337, &options), "1337");
|
||||||
|
assert_eq!(
|
||||||
|
Format::Dec.format_str(u128::MAX, &options),
|
||||||
|
format!("{}", u128::MAX)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(Format::Hex.format_str(0xFFF, &options), "0FFF");
|
||||||
|
assert_eq!(Format::Hex.format_str(0xFFFF, &options), "FFFF");
|
||||||
|
assert_eq!(
|
||||||
|
Format::Hex.format_str(u128::MAX, &options),
|
||||||
|
format!("{:X}", u128::MAX)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Format::Bin.format_str(0b11110000_00001111, &options),
|
||||||
|
"1111000000001111"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Format::Bin.format_str(0b110000_00001111, &options),
|
||||||
|
"0011000000001111"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Format::Bin.format_str(u128::MAX, &options),
|
||||||
|
format!("{:b}", u128::MAX)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(Format::Octal.format_str(0o13377331, &options), "13377331");
|
||||||
|
assert_eq!(
|
||||||
|
Format::Octal.format_str(u128::MAX, &options),
|
||||||
|
format!("{:o}", u128::MAX)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(Format::Base32.format_str(0x41414242, &options), "IFAUEQQ=");
|
||||||
|
assert_eq!(
|
||||||
|
Format::Base32.format_str(0x4141414141414141, &options),
|
||||||
|
"IFAUCQKBIFAUC==="
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(Format::Base64.format_str(0x41414242, &options), "QUFCQg==");
|
||||||
|
assert_eq!(
|
||||||
|
Format::Base64.format_str(0x4141414141414141, &options),
|
||||||
|
"QUFBQUFBQUE="
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(Format::Raw.format(0x1337, &options), vec![0x13, 0x37]);
|
||||||
|
assert_eq!(Format::Raw.format(0x0, &options), vec![0x0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_prefix() {
|
||||||
|
let mut options = FormatOptions::default();
|
||||||
|
options.set_prefix(true);
|
||||||
|
|
||||||
|
assert_eq!(Format::Dec.format_str(1337, &options), "0d1337");
|
||||||
|
assert_eq!(
|
||||||
|
Format::Dec.format_str(u128::MAX, &options),
|
||||||
|
format!("0d{}", u128::MAX)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(Format::Hex.format_str(0x1337, &options), "0x1337");
|
||||||
|
assert_eq!(
|
||||||
|
Format::Hex.format_str(u128::MAX, &options),
|
||||||
|
format!("0x{:X}", u128::MAX)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Format::Bin.format_str(0b1010001001010010010100111, &options),
|
||||||
|
"0b1010001001010010010100111"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Format::Bin.format_str(u128::MAX, &options),
|
||||||
|
format!("0b{:b}", u128::MAX)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(Format::Octal.format_str(0o13377331, &options), "0o13377331");
|
||||||
|
assert_eq!(
|
||||||
|
Format::Octal.format_str(u128::MAX, &options),
|
||||||
|
format!("0o{:o}", u128::MAX)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Format::Base32.format_str(0x41414242, &options),
|
||||||
|
"032sIFAUEQQ="
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Format::Base32.format_str(0x4141414141414141, &options),
|
||||||
|
"032sIFAUCQKBIFAUC==="
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Format::Base64.format_str(0x41414242, &options),
|
||||||
|
"0sQUFCQg=="
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Format::Base64.format_str(0x4141414141414141, &options),
|
||||||
|
"0sQUFBQUFBQUE="
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(Format::Raw.format(0x1337, &options), vec![0x0, 0x13, 0x37]);
|
||||||
|
assert_eq!(Format::Raw.format(0x0, &options), vec![0x0, 0x0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_padded_prefix() {
|
||||||
|
let mut options = FormatOptions::default();
|
||||||
|
options.set_prefix(true);
|
||||||
|
options.set_padding(true);
|
||||||
|
|
||||||
|
assert_eq!(Format::Dec.format_str(1337, &options), "0d1337");
|
||||||
|
assert_eq!(
|
||||||
|
Format::Dec.format_str(u128::MAX, &options),
|
||||||
|
format!("0d{}", u128::MAX)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(Format::Hex.format_str(0xFFF, &options), "0x0FFF");
|
||||||
|
assert_eq!(Format::Hex.format_str(0xFFFF, &options), "0xFFFF");
|
||||||
|
assert_eq!(
|
||||||
|
Format::Hex.format_str(u128::MAX, &options),
|
||||||
|
format!("0x{:X}", u128::MAX)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Format::Bin.format_str(0b11110000_00001111, &options),
|
||||||
|
"0b1111000000001111"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Format::Bin.format_str(0b110000_00001111, &options),
|
||||||
|
"0b0011000000001111"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Format::Bin.format_str(u128::MAX, &options),
|
||||||
|
format!("0b{:b}", u128::MAX)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(Format::Octal.format_str(0o13377331, &options), "0o13377331");
|
||||||
|
assert_eq!(
|
||||||
|
Format::Octal.format_str(u128::MAX, &options),
|
||||||
|
format!("0o{:o}", u128::MAX)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Format::Base32.format_str(0x41414242, &options),
|
||||||
|
"032sIFAUEQQ="
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Format::Base32.format_str(0x4141414141414141, &options),
|
||||||
|
"032sIFAUCQKBIFAUC==="
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Format::Base64.format_str(0x41414242, &options),
|
||||||
|
"0sQUFCQg=="
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Format::Base64.format_str(0x4141414141414141, &options),
|
||||||
|
"0sQUFBQUFBQUE="
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(Format::Raw.format(0x1337, &options), vec![0x0, 0x13, 0x37]);
|
||||||
|
assert_eq!(Format::Raw.format(0x0, &options), vec![0x0, 0x0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_format_checker() {
|
||||||
|
let mut options = FormatOptions::default();
|
||||||
|
assert_eq!(options.format(), Format::Hex);
|
||||||
|
options.set_format(Format::Base32);
|
||||||
|
assert_eq!(options.format(), Format::Base32);
|
||||||
|
options.set_format(Format::Base64);
|
||||||
|
assert_eq!(options.format(), Format::Base64);
|
||||||
|
options.set_format(Format::Raw);
|
||||||
|
assert_eq!(options.format(), Format::Raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parser_dec() {
|
||||||
|
assert_eq!(numf_parser_str::<u32>("1337").unwrap(), 1337);
|
||||||
|
assert_eq!(numf_parser_str::<u32>("0d1337").unwrap(), 1337);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parser_bin() {
|
||||||
|
assert_eq!(numf_parser_str::<u32>("0b11001").unwrap(), 0b11001);
|
||||||
|
assert_eq!(numf_parser_str::<u32>("0b11001").unwrap(), 0b11001);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parser_hex() {
|
||||||
|
assert_eq!(numf_parser_str::<u32>("0xdeadbeef").unwrap(), 0xdeadbeef);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parser_oct() {
|
||||||
|
assert_eq!(numf_parser_str::<u32>("0o771171").unwrap(), 0o771171);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parser_b64() {
|
||||||
|
assert_eq!(numf_parser_str::<u32>("0sQUFCQg==").unwrap(), 0x41414242);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parser_b32() {
|
||||||
|
assert_eq!(numf_parser_str::<u32>("032sIFAUEQQ=").unwrap(), 0x41414242);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parser_raw() {
|
||||||
|
assert_eq!(numf_parser_str::<u32>("\x00\x50\x60").unwrap(), 0x5060);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parser_generics() {
|
||||||
|
assert_eq!(numf_parser_str::<u8>("55").unwrap(), 55);
|
||||||
|
assert_eq!(numf_parser_str::<u16>("55").unwrap(), 55);
|
||||||
|
assert_eq!(numf_parser_str::<u32>("55").unwrap(), 55);
|
||||||
|
assert_eq!(numf_parser_str::<u64>("55").unwrap(), 55);
|
||||||
|
assert_eq!(numf_parser_str::<u128>("55").unwrap(), 55);
|
||||||
|
}
|
Reference in New Issue