clap-num-1.2.0/.cargo_vcs_info.json0000644000000001360000000000100125500ustar { "git": { "sha1": "7e70a75a32cb4173a51581af03bb7d0885c009a2" }, "path_in_vcs": "" }clap-num-1.2.0/.github/dependabot.yml000064400000000000000000000003161046102023000155300ustar 00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" - package-ecosystem: "cargo" directory: "/" schedule: interval: "daily" clap-num-1.2.0/.github/workflows/ci.yml000064400000000000000000000026441046102023000160610ustar 00000000000000on: push: branches: - main tags: - '*' pull_request: schedule: - cron: "13 3 * * 6" name: CI jobs: build: name: Build runs-on: ubuntu-latest env: RUSTFLAGS: "-D warnings" RUSTDOCFLAGS: "-D warnings" steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: cargo build - run: cargo doc test: name: Test runs-on: ubuntu-latest env: {"RUSTFLAGS": "-D warnings"} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: cargo test clippy: name: Clippy runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: clippy - run: cargo clippy -- --deny warnings format: name: Format runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - run: cargo fmt -- --check release: name: crates.io release if: startsWith(github.ref, 'refs/tags/') needs: - build - test - clippy - format runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: cargo publish --token ${CRATES_IO_TOKEN} env: CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} clap-num-1.2.0/.gitignore000064400000000000000000000000511046102023000133240ustar 00000000000000/target Cargo.lock tarpaulin-report.html clap-num-1.2.0/CHANGELOG.md000064400000000000000000000031431046102023000131520ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [1.2.0] - 2025-01-22 ### Added - Added `maybe_bin` and `maybe_bin_range` for binary numbers. ## [1.1.1] - 2024-01-21 ### Fixed - Fixed a typo in the error message when a value is below the minimum limit. ## [1.1.0] - 2024-01-21 ### Added - Added support for capital 'K' for kilo. - Added support for underscore separators in numbers. ## [1.0.2] - 2022-10-08 ### Fixed - Fixed panics when parsing SI numbers with multi-byte UTF-8 sequences. ## [1.0.1] - 2022-10-06 ### Changed - Updated documentation for clap v4 API changes. ## [1.0.0] - 2021-12-31 ### Fixed - Fixed typos in documentation. ### Changed - Changed edition from 2018 to 2021. - Updated examples to use clap 3.0.0. ## [0.2.0] - 2020-10-18 ### Added - Added `maybe_hex` and `maybe_hex_range` functions. - Added a changelog. [Unreleased]: https://github.com/newAM/clap-num/compare/1.2.0...HEAD [1.2.0]: https://github.com/newAM/clap-num/compare/1.1.1...1.2.0 [1.1.1]: https://github.com/newAM/clap-num/compare/1.1.0...1.1.1 [1.1.1]: https://github.com/newAM/clap-num/compare/1.1.0...1.1.1 [1.1.0]: https://github.com/newAM/clap-num/compare/1.0.2...1.1.0 [1.0.2]: https://github.com/newAM/clap-num/compare/1.0.1...1.0.2 [1.0.1]: https://github.com/newAM/clap-num/compare/1.0.0...1.0.1 [1.0.0]: https://github.com/newAM/clap-num/compare/0.2.0...1.0.0 [0.2.0]: https://github.com/newAM/clap-num/releases/tag/0.2.0 clap-num-1.2.0/Cargo.lock0000644000000160040000000000100105240ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "anstream" version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", "once_cell", "windows-sys", ] [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "clap" version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap-num" version = "1.2.0" dependencies = [ "clap", "num-traits", ] [[package]] name = "clap_builder" version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "proc-macro2" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" clap-num-1.2.0/Cargo.toml0000644000000026330000000000100105520ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "clap-num" version = "1.2.0" authors = ["Alex Martens "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Number parsers for clap" readme = "README.md" keywords = [ "argument", "cli", "arg", "parser", "parse", ] categories = ["command-line-interface"] license = "MIT" repository = "https://github.com/newAM/clap-num/" [lib] name = "clap_num" path = "src/lib.rs" [[example]] name = "change" path = "examples/change.rs" [[example]] name = "resistance" path = "examples/resistance.rs" [[test]] name = "maybe_bin" path = "tests/maybe_bin.rs" [[test]] name = "maybe_hex" path = "tests/maybe_hex.rs" [[test]] name = "number_range" path = "tests/number_range.rs" [[test]] name = "si_number" path = "tests/si_number.rs" [dependencies.num-traits] version = "0.2" [dev-dependencies.clap] version = "4" features = ["derive"] clap-num-1.2.0/Cargo.toml.orig000064400000000000000000000006651046102023000142360ustar 00000000000000[package] name = "clap-num" version = "1.2.0" authors = ["Alex Martens "] keywords = ["argument", "cli", "arg", "parser", "parse"] categories = ["command-line-interface"] description = "Number parsers for clap" edition = "2021" license = "MIT" repository = "https://github.com/newAM/clap-num/" readme = "README.md" [dependencies] num-traits = "0.2" [dev-dependencies] clap = { version = "4", features = ["derive"] } clap-num-1.2.0/LICENSE000064400000000000000000000020671046102023000123520ustar 00000000000000MIT License Copyright (c) 2020 - present Alex Martens Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. clap-num-1.2.0/README.md000064400000000000000000000021071046102023000126170ustar 00000000000000[![crates.io](https://img.shields.io/crates/v/clap-num.svg)](https://crates.io/crates/clap-num) [![docs.rs](https://docs.rs/clap-num/badge.svg)](https://docs.rs/clap-num/) [![Build Status](https://github.com/newAM/clap-num/workflows/CI/badge.svg)](https://github.com/newAM/clap-num/actions) # clap-num clap number parsers. This crate contains functions to validate and parse numerical values from strings provided by [clap]. * `maybe_hex` Validates an unsigned integer value that can be base-10 or base-16. * `maybe_hex_range` Validates an unsigned integer value that can be base-10 or base-16 within a range. * `maybe_bin` Validates an unsigned integer value that can be base-10 or base-2. * `maybe_bin_range` Validates an unsigned integer value that can be base-10 or base-2 within a range. * `number_range` Validate a signed or unsigned integer value. * `si_number` Validate a signed or unsigned integer value with a metric prefix. * `si_number_range` Validate a signed or unsigned integer value with a metric prefix within a range. [clap]: https://github.com/clap-rs/clap clap-num-1.2.0/README.tpl000064400000000000000000000004741046102023000130230ustar 00000000000000[![crates.io](https://img.shields.io/crates/v/clap-num.svg)](https://crates.io/crates/clap-num) [![docs.rs](https://docs.rs/clap-num/badge.svg)](https://docs.rs/clap-num/) [![Build Status](https://github.com/newAM/clap-num/workflows/CI/badge.svg)](https://github.com/newAM/clap-num/actions) # {{crate}} {{readme}} clap-num-1.2.0/examples/change.rs000064400000000000000000000004751046102023000147570ustar 00000000000000use clap::Parser; use clap_num::number_range; fn less_than_100(s: &str) -> Result { number_range(s, 0, 99) } #[derive(Parser)] struct Change { #[clap(long, value_parser=less_than_100)] cents: u8, } fn main() { let args = Change::parse(); println!("Change: {} cents", args.cents); } clap-num-1.2.0/examples/resistance.rs000064400000000000000000000003771046102023000156730ustar 00000000000000use clap::Parser; use clap_num::si_number; #[derive(Parser)] struct Args { #[clap(short, long, value_parser=si_number::)] resistance: u128, } fn main() { let args = Args::parse(); println!("Resistance: {} ohms", args.resistance) } clap-num-1.2.0/src/lib.rs000064400000000000000000000335571046102023000132600ustar 00000000000000//! clap number parsers. //! //! This crate contains functions to validate and parse numerical values from //! strings provided by [clap]. //! //! * `maybe_hex` //! Validates an unsigned integer value that can be base-10 or base-16. //! * `maybe_hex_range` //! Validates an unsigned integer value that can be base-10 or base-16 within a range. //! * `number_range` //! Validate a signed or unsigned integer value. //! * `si_number` //! Validate a signed or unsigned integer value with a metric prefix. //! * `si_number_range` //! Validate a signed or unsigned integer value with a metric prefix within a range. //! //! [clap]: https://github.com/clap-rs/clap #![deny(missing_docs)] use core::{iter, str::FromStr}; use num_traits::identities::Zero; use num_traits::{sign, CheckedAdd, CheckedMul, CheckedSub, Num}; fn check_range(val: T, min: T, max: T) -> Result where T: FromStr, ::Err: std::fmt::Display, T: Ord, T: std::fmt::Display, { if val > max { Err(format!("exceeds maximum of {max}")) } else if val < min { Err(format!("less than minimum of {min}")) } else { Ok(val) } } /// Validate a signed or unsigned integer value. /// /// # Arguments /// /// * `s` - String to parse. /// * `min` - Minimum value, inclusive. /// * `max` - Maximum value, inclusive. /// /// # Example /// /// This allows for a number of cents to be passed in the range of 0-99 /// (inclusive). /// /// ``` /// use clap::Parser; /// use clap_num::number_range; /// /// fn less_than_100(s: &str) -> Result { /// number_range(s, 0, 99) /// } /// /// #[derive(Parser)] /// struct Change { /// #[clap(long, value_parser=less_than_100)] /// cents: u8, /// } /// # let args = Change::parse_from(&["", "--cents", "99"]); /// # assert_eq!(args.cents, 99); /// ``` /// /// To run this example run `cargo run --example change`, giving arguments to /// the program after `--`, for example: /// /// ```text /// $ cargo run --example change -- --cents 99 /// Change: 99 cents /// ``` /// /// ## Error Messages /// /// Values that are not numbers will show an error message similar to this: /// /// ```text /// error: Invalid value for '--cents ': invalid digit found in string /// ``` /// /// Values resulting in integer overflow will show an error message similar to this: /// /// ```text /// error: Invalid value for '--cents ': number too large to fit in target type /// ``` /// /// Values exceeding the limits will show an error message similar to this: /// /// ```text /// error: Invalid value for '--cents ': exceeds maximum of 99 /// ``` pub fn number_range(s: &str, min: T, max: T) -> Result where T: FromStr, ::Err: std::fmt::Display, T: Ord, T: PartialOrd, T: std::fmt::Display, { debug_assert!(min <= max, "minimum of {min} exceeds maximum of {max}"); let val = s.parse::().map_err(stringify)?; check_range(val, min, max) } static OVERFLOW_MSG: &str = "number too large to fit in target type"; // helper for mapping errors to strings fn stringify(e: T) -> String { format!("{e}") } #[derive(Copy, Clone)] enum SiPrefix { Yotta, Zetta, Exa, Peta, Tera, Giga, Mega, Kilo, } impl SiPrefix { fn from_char(symbol: char) -> Option { match symbol { 'Y' => Some(Self::Yotta), 'Z' => Some(Self::Zetta), 'E' => Some(Self::Exa), 'P' => Some(Self::Peta), 'T' => Some(Self::Tera), 'G' => Some(Self::Giga), 'M' => Some(Self::Mega), 'k' | 'K' => Some(Self::Kilo), _ => None, } } fn multiplier(&self) -> u128 { match self { SiPrefix::Yotta => 1_000_000_000_000_000_000_000_000, SiPrefix::Zetta => 1_000_000_000_000_000_000_000, SiPrefix::Exa => 1_000_000_000_000_000_000, SiPrefix::Peta => 1_000_000_000_000_000, SiPrefix::Tera => 1_000_000_000_000, SiPrefix::Giga => 1_000_000_000, SiPrefix::Mega => 1_000_000, SiPrefix::Kilo => 1_000, } } fn digits(&self) -> usize { match self { SiPrefix::Yotta => 24, SiPrefix::Zetta => 21, SiPrefix::Exa => 18, SiPrefix::Peta => 15, SiPrefix::Tera => 12, SiPrefix::Giga => 9, SiPrefix::Mega => 6, SiPrefix::Kilo => 3, } } } fn parse_post(mut post: String, digits: usize) -> Result where ::Err: std::fmt::Display, T: PartialOrd + FromStr, { if let Some(zeros) = digits.checked_sub(post.len()) { post.extend(iter::repeat('0').take(zeros)); post.parse::().map_err(stringify) } else { Err(String::from("not an integer")) } } /// Validate a signed or unsigned integer value with a [metric prefix]. /// /// This can accept strings with the (case sensitive) SI symbols. /// /// | Symbol | Name | Value | /// |--------|-------|-----------------------------------| /// | Y | yotta | 1_000_000_000_000_000_000_000_000 | /// | Z | zetta | 1_000_000_000_000_000_000_000 | /// | E | exa | 1_000_000_000_000_000_000 | /// | P | peta | 1_000_000_000_000_000 | /// | T | tera | 1_000_000_000_000 | /// | G | giga | 1_000_000_000 | /// | M | mega | 1_000_000 | /// | k | kilo | 1_000 | /// /// The strings can be provided with a decimal, or using the SI symbol as the /// decimal separator. /// /// | String | Value | /// |--------|-----------| /// | 3k3 | 3300 | /// | 3.3k | 3300 | /// | 1M | 1_000_000 | /// /// # Example /// /// This allows for resistance value to be passed using SI symbols. /// /// ``` /// use clap::Parser; /// use clap_num::si_number; /// /// #[derive(Parser)] /// struct Args { /// #[clap(short, long, value_parser=si_number::)] /// resistance: u128, /// } /// # let args = Args::parse_from(&["", "--resistance", "1M1"]); /// # assert_eq!(args.resistance, 1_100_000); /// ``` /// /// To run this example use `cargo run --example resistance`, giving arguments /// to the program after `--`, for example: /// /// ```text /// $ cargo run --example resistance -- --resistance 1M1 /// Resistance: 1100000 ohms /// ``` /// /// [metric prefix]: https://en.wikipedia.org/wiki/Metric_prefix pub fn si_number(s: &str) -> Result where >::Error: std::fmt::Display, ::Err: std::fmt::Display, T: CheckedAdd, T: CheckedMul, T: CheckedSub, T: FromStr, T: PartialOrd, T: TryFrom, T: Zero, { // contains SI symbol if let Some(si_prefix_index) = s.find(|c| SiPrefix::from_char(c).is_some()) { let si_prefix = SiPrefix::from_char(s.as_bytes()[si_prefix_index] as char).unwrap(); let multiplier: T = T::try_from(si_prefix.multiplier()).map_err(|_| OVERFLOW_MSG)?; let (pre_si, post_si) = s.split_at(si_prefix_index); let post_si = &post_si[1..]; if pre_si.is_empty() { return Err("no value found before SI symbol".to_string()); } // in the format of "1k234" for 1_234 let (pre, post) = if !post_si.is_empty() { ( pre_si.parse::().map_err(stringify)?, parse_post(post_si.to_string(), si_prefix.digits())?, ) // in the format of "1.234k" for 1_234 } else if let Some((pre_dec, post_dec)) = s.split_once('.') { let mut post_dec: String = post_dec.to_string(); post_dec.pop(); // remove SI symbol let post_dec = parse_post(post_dec, si_prefix.digits())?; (pre_dec.parse::().map_err(stringify)?, post_dec) // no decimal } else { (pre_si.parse::().map_err(stringify)?, T::zero()) }; let pre = pre.checked_mul(&multiplier).ok_or(OVERFLOW_MSG)?; if pre >= T::zero() { pre.checked_add(&post) } else { pre.checked_sub(&post) } .ok_or_else(|| OVERFLOW_MSG.to_string()) } else { // no SI symbol, parse normally s.chars() .filter(|&c| c != '_') .collect::() .parse::() .map_err(stringify) } } /// Validate a signed or unsigned integer value with a [metric prefix] within /// a range. /// /// This combines [`si_number`] and [`number_range`], see the /// documentation for those functions for details. /// /// # Example /// /// This extends the example in [`si_number`], and only allows a range of /// resistances from 1k to 999.999k. /// /// ``` /// use clap::Parser; /// use clap_num::si_number_range; /// /// fn kilo(s: &str) -> Result { /// si_number_range(s, 1_000, 999_999) /// } /// /// #[derive(Parser)] /// struct Args { /// #[clap(short, long, value_parser=kilo)] /// resistance: u32, /// } /// # let args = Args::parse_from(&["", "--resistance", "999k999"]); /// # assert_eq!(args.resistance, 999_999); /// ``` /// /// [metric prefix]: https://en.wikipedia.org/wiki/Metric_prefix pub fn si_number_range(s: &str, min: T, max: T) -> Result where >::Error: std::fmt::Display, ::Err: std::fmt::Display, T: CheckedAdd, T: CheckedMul, T: CheckedSub, T: FromStr, T: PartialOrd, T: TryFrom, T: Zero, T: Ord, T: PartialOrd, T: std::fmt::Display, { let val = si_number(s)?; check_range(val, min, max) } /// Validates an unsigned integer value that can be base-10 or base-16. /// /// The number is assumed to be base-10 by default, it is parsed as hex if the /// number is prefixed with `0x`, case insensitive. /// /// # Example /// /// This allows base-10 addresses to be passed normally, or base-16 values to /// be passed when prefixed with `0x`. /// /// ``` /// use clap::Parser; /// use clap_num::maybe_hex; /// /// #[derive(Parser)] /// struct Args { /// #[clap(short, long, value_parser=maybe_hex::)] /// address: u32, /// } /// # let args = Args::parse_from(&["", "-a", "0x10"]); /// # assert_eq!(args.address, 16); /// ``` pub fn maybe_hex(s: &str) -> Result where ::FromStrRadixErr: std::fmt::Display, { const HEX_PREFIX: &str = "0x"; const HEX_PREFIX_UPPER: &str = "0X"; const HEX_PREFIX_LEN: usize = HEX_PREFIX.len(); let result = if s.starts_with(HEX_PREFIX) || s.starts_with(HEX_PREFIX_UPPER) { T::from_str_radix(&s[HEX_PREFIX_LEN..], 16) } else { T::from_str_radix(s, 10) }; result.map_err(stringify) } /// Validates an unsigned integer value that can be base-10 or base-16 within /// a range. /// /// This combines [`maybe_hex`] and [`number_range`], see the /// documentation for those functions for details. /// /// # Example /// /// This extends the example in [`maybe_hex`], and only allows a range of /// addresses from `0x100` to `0x200`. /// /// ``` /// use clap::Parser; /// use clap_num::maybe_hex_range; /// /// fn address_in_range(s: &str) -> Result { /// maybe_hex_range(s, 0x100, 0x200) /// } /// /// #[derive(Parser)] /// struct Args { /// #[clap(short, long, value_parser=address_in_range)] /// address: u32, /// } /// # let args = Args::parse_from(&["", "-a", "300"]); /// # assert_eq!(args.address, 300); /// ``` pub fn maybe_hex_range(s: &str, min: T, max: T) -> Result where ::FromStrRadixErr: std::fmt::Display, ::Err: std::fmt::Display, T: FromStr, T: std::fmt::Display, T: Ord, T: Num, T: sign::Unsigned, { let val = maybe_hex(s)?; check_range(val, min, max) } /// Validates an unsigned integer value that can be base-10 or base-2. /// /// The number is assumed to be base-10 by default, it is parsed as binary if the /// number is prefixed with `0b` (case sensitive!). /// /// # Example /// /// This allows base-10 addresses to be passed normally, or base-2 values to /// be passed when prefixed with `0b`. /// /// ``` /// use clap::Parser; /// use clap_num::maybe_bin; /// /// #[derive(Parser)] /// struct Args { /// #[clap(short, long, value_parser=maybe_bin::)] /// address: u8, /// } /// # let args = Args::parse_from(&["", "-a", "0b1001"]); /// # assert_eq!(args.address, 9); /// ``` pub fn maybe_bin(s: &str) -> Result where ::FromStrRadixErr: std::fmt::Display, { const BIN_PREFIX: &str = "0b"; const BIN_PREFIX_LEN: usize = BIN_PREFIX.len(); let result = if s.starts_with(BIN_PREFIX) { T::from_str_radix(&s[BIN_PREFIX_LEN..], 2) } else { T::from_str_radix(s, 10) }; result.map_err(stringify) } /// Validates an unsigned integer value that can be base-10 or base-2 within /// a range. /// /// This combines [`maybe_bin`] and [`number_range`], see the /// documentation of those functions for details. /// /// # Example /// /// This extends the example in [`maybe_bin`], and only allows a range of /// addresses from 2 (`0b10`) to 15 (`0b1111`). /// /// ``` /// use clap::Parser; /// use clap_num::maybe_bin_range; /// /// fn address_in_range(s: &str) -> Result { /// maybe_bin_range(s, 0b10, 0b1111) /// } /// /// #[derive(Parser)] /// struct Args { /// #[clap(short, long, value_parser=address_in_range)] /// address: u8, /// } /// # let args = Args::parse_from(&["", "-a", "0b1001"]); /// # assert_eq!(args.address, 9); /// ``` pub fn maybe_bin_range(s: &str, min: T, max: T) -> Result where ::FromStrRadixErr: std::fmt::Display, ::Err: std::fmt::Display, T: FromStr, T: std::fmt::Display, T: Ord, T: Num, T: sign::Unsigned, { let val = maybe_bin(s)?; check_range(val, min, max) } clap-num-1.2.0/tests/maybe_bin.rs000064400000000000000000000017551046102023000150050ustar 00000000000000use clap_num::maybe_bin; #[cfg(test)] mod basic { use super::*; // positive path macro_rules! pos { ($NAME:ident, $VAL:expr, $RESULT:expr) => { #[test] fn $NAME() { assert_eq!(maybe_bin($VAL), Ok($RESULT)); } }; } // negative path macro_rules! neg { ($NAME:ident, $VAL:expr, $RESULT:expr) => { #[test] fn $NAME() { let val: Result = maybe_bin($VAL); assert_eq!(val, Err(String::from($RESULT))); } }; } pos!(simple, "123", 123u8); pos!(zero_dec, "0", 0u16); pos!(zero_bin, "0b0", 0u16); pos!(one_dec, "1", 1u64); pos!(one_bin, "0b1", 1u64); pos!(aa, "0b10101010", 0xaau64); pos!(leading_zero, "001", 1u64); neg!( missing_suffix, "0b", "cannot parse integer from empty string" ); neg!(non_bin_digit, "0b12G", "invalid digit found in string"); } clap-num-1.2.0/tests/maybe_hex.rs000064400000000000000000000020551046102023000150130ustar 00000000000000use clap_num::maybe_hex; #[cfg(test)] mod basic { use super::*; // positive path macro_rules! pos { ($NAME:ident, $VAL:expr, $RESULT:expr) => { #[test] fn $NAME() { assert_eq!(maybe_hex($VAL), Ok($RESULT)); } }; } // negative path macro_rules! neg { ($NAME:ident, $VAL:expr, $RESULT:expr) => { #[test] fn $NAME() { let val: Result = maybe_hex($VAL); assert_eq!(val, Err(String::from($RESULT))); } }; } pos!(simple, "123", 123u8); pos!(zero_dec, "0", 0u16); pos!(zero_hex, "0x0", 0u16); pos!(one_dec, "1", 1u64); pos!(one_hex, "0x1", 1u64); pos!(leading_zero, "001", 1u64); pos!(case, "0XABcDE", 703710u32); neg!( missing_suffix, "0x", "cannot parse integer from empty string" ); neg!(dec_with_hex, "1A", "invalid digit found in string"); neg!(non_hex_digit, "0x12G", "invalid digit found in string"); } clap-num-1.2.0/tests/number_range.rs000064400000000000000000000053421046102023000155200ustar 00000000000000use clap::Parser; use clap_num::number_range; // standalone basic tests #[cfg(test)] mod basic { use super::*; macro_rules! pos { ($NAME:ident, $VAL:expr, $MIN:expr, $MAX:expr, $RESULT:expr) => { #[test] fn $NAME() { assert_eq!(number_range($VAL, $MIN, $MAX), Ok($RESULT)); } }; } macro_rules! neg { ($NAME:ident, $VAL:expr, $MIN:expr, $MAX:expr, $RESULT:expr) => { #[test] fn $NAME() { assert_eq!(number_range($VAL, $MIN, $MAX), Err(String::from($RESULT))); } }; } pos!(simple, "123", 12u8, 200u8, 123u8); pos!(zero, "0", 0u8, 0u8, 0u8); pos!(neg, "-1", -10i8, 10i8, -1); pos!(min_limit, "-5", -5i8, -5i8, -5i8); pos!(max_limit, "65535", 0, u16::MAX, u16::MAX); neg!(decimal, "1.1", -10i8, 10i8, "invalid digit found in string"); neg!(min, "-1", 0i8, 0i8, "less than minimum of 0"); neg!(max, "1", 0i8, 0i8, "exceeds maximum of 0"); neg!( overflow, "256", 0, std::u8::MAX, "number too large to fit in target type" ); neg!(nan, "nan", 0, 0, "invalid digit found in string"); #[test] #[should_panic] fn min_max_debug_assert() { let _ = number_range("", 2, 1); } } // integration tests with clap #[cfg(test)] mod integration { use super::*; fn human_livable_temperature(s: &str) -> Result { number_range(s, -40, 60) } #[derive(Parser, Debug)] struct Thermostat { #[clap( long, value_parser=human_livable_temperature, allow_hyphen_values=true )] temperature: i8, } // positive path macro_rules! pos { ($NAME:ident, $VAL:expr, $RESULT:expr) => { #[test] fn $NAME() { let opt = Thermostat::parse_from(&["", "--temperature", $VAL]); assert_eq!(opt.temperature, $RESULT); } }; } // negative path macro_rules! neg { ($NAME:ident, $VAL:expr, $RESULT:expr) => { #[test] fn $NAME() { let opt = Thermostat::try_parse_from(&["", "--temperature", $VAL]); match opt { Err(e) => { assert!(format!("{:?}", e).contains($RESULT)); } _ => unreachable!(), }; } }; } pos!(simple, "50", 50); pos!(zero, "0", 0); pos!(negative, "-30", -30); pos!(positive_limit, "60", 60); pos!(negative_limit, "-40", -40); neg!(too_small, "-41", "less than minimum of -40"); neg!(too_large, "61", "exceeds maximum of 60"); } clap-num-1.2.0/tests/si_number.rs000064400000000000000000000100041046102023000150260ustar 00000000000000use clap::Parser; use clap_num::si_number; #[test] fn utf8_byte_index_not_char_boundry() { let _ = si_number::("˲TP"); } #[test] fn utf8_byte_index_not_char_boundry_with_decimal() { let _ = si_number::("˲.E"); } // standalone basic tests #[cfg(test)] mod basic { use super::*; macro_rules! pos { ($NAME:ident, $VAL:expr, $RESULT:expr) => { #[test] fn $NAME() { assert_eq!(si_number($VAL), Ok($RESULT)); } }; } macro_rules! neg { ($NAME:ident, $VAL:expr, $TYPE:ident, $RESULT:expr) => { #[test] fn $NAME() { let num: Result<$TYPE, String> = si_number($VAL); assert_eq!(num, Err(String::from($RESULT))); } }; } // basic positive path pos!(zero, "0", 0u8); pos!(one, "1", 1u8); pos!(neg_one, "-1", -1i8); pos!(limit, "255", 255u8); pos!(underscores, "1_000_000", 1_000_000u32); // basic positive path with Si suffix pos!(kilo, "1k", 1_000u16); pos!(kilo_caps, "1K", 1_000u16); pos!(mega, "1M", 1_000_000u32); pos!(giga, "1G", 1_000_000_000u64); pos!(tera, "1T", 1_000_000_000_000u64); pos!(peta, "1P", 1_000_000_000_000_000u64); pos!(exa, "1E", 1_000_000_000_000_000_000u64); pos!(zetta, "1Z", 1_000_000_000_000_000_000_000u128); pos!(yotta, "1Y", 1_000_000_000_000_000_000_000_000u128); pos!(trailing_1, "1k2", 1_200u16); pos!(trailing_2, "1k23", 1_230u16); pos!(trailing_3, "1k234", 1_234u16); neg!(trailing_4, "1k2345", u16, "not an integer"); pos!(trailing_do_nothing, "1k000", 1_000u16); pos!(negative_trailing, "-1k234", -1_234i16); pos!(leading_2, "12k123", 12_123u16); pos!(leading_3, "123k123", 123_123u32); pos!(leading_4, "1234k123", 1_234_123i32); pos!(negative_leading, "-123k123", -123_123i32); pos!(dec_1, "1.2k", 1_200u16); pos!(dec_2, "1.23k", 1_230u16); pos!(dec_3, "1.234k", 1_234u16); neg!(dec_4, "1.2345k", u16, "not an integer"); pos!(dec_do_nothing, "1.000k", 1_000u16); pos!(dec_ending_si, "1.k", 1_000u16); neg!(mixed_1, "1K23.45", u16, "not an integer"); neg!(mixed_2, "1.23k45", u16, "invalid digit found in string"); neg!(trailing_dec, "1.", u8, "invalid digit found in string"); pos!( big, "1Y123456789987654321", 1_123_456_789_987_654_321_000_000u128 ); neg!(leading_si, "k1", u16, "no value found before SI symbol"); neg!(overflow, "1k", u8, "number too large to fit in target type"); neg!( normal_overflow, "300", u8, "number too large to fit in target type" ); neg!(multiple_suffix, "1kk", u16, "invalid digit found in string"); } // integration tests with clap #[cfg(test)] mod integration { use super::*; #[derive(Parser)] struct Args { #[clap(long, value_parser=si_number::)] resistance: u128, } // positive path macro_rules! pos { ($NAME:ident, $VAL:expr, $RESULT:expr) => { #[test] fn $NAME() { let opt = Args::parse_from(&["", "--resistance", $VAL]); assert_eq!(opt.resistance, $RESULT); } }; } // negative path macro_rules! neg { ($NAME:ident, $VAL:expr, $RESULT:expr) => { #[test] fn $NAME() { let opt = Args::try_parse_from(&["", "--resistance", $VAL]); match opt { Err(e) => { assert!(format!("{:?}", e).contains($RESULT)); } _ => unreachable!(), }; } }; } pos!(simple_0, "1k123", 1123); pos!(simple_1, "456789k123", 456789123); pos!(simple_2, "1M1", 1_100_000); neg!(big, "999999999999999999999Y", "too large"); neg!(invalid, "1k1k", "invalid digit"); neg!(precise, "1k1111", "not an integer"); neg!(leading_prefix, "k123", "no value found before SI symbol"); }