feat(cli): parse arguments with clap
The binary now derives command-line parsing from clap instead of maintaining a custom parser. SEARCH_LIMIT remains an optional positional argument and keeps its default of 1_000_000_000, while clap now owns usage errors, --help, and --version output. The parser stores the limit as NonZeroU64 so zero is rejected before the search starts. The existing CLI parsing tests now exercise clap directly, and the README documents the generated help/version flags plus the top-level program structure. Test Plan: - cargo clippy - cargo clippy --benches - cargo clippy --tests - cargo test - cargo run -- --help Trailer: Refs: local request to replace custom argument parsing with clap Dependencies: none
This commit is contained in:
Generated
+180
-1
@@ -1,7 +1,186 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"once_cell_polyfill",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hcn"
|
name = "hcn"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.106"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.45"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||||
|
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.117"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ version = "1.0.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
clap = { version = "4", features = ["derive"] }
|
||||||
|
|
||||||
[lints.rust]
|
[lints.rust]
|
||||||
unsafe_code = "forbid"
|
unsafe_code = "forbid"
|
||||||
|
|||||||
@@ -3,10 +3,23 @@
|
|||||||
Find highly composite numbers up to a search limit.
|
Find highly composite numbers up to a search limit.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --release -- 1000000000
|
cargo run --release -- [SEARCH_LIMIT]
|
||||||
```
|
```
|
||||||
|
|
||||||
If no limit is given, the default is `1_000_000_000`.
|
If no limit is given, the default is `1_000_000_000`.
|
||||||
|
Use `--help` to see the generated command-line help and `--version` to print
|
||||||
|
the package version.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
- `src/main.rs`
|
||||||
|
- Command-line interface: parses the optional `SEARCH_LIMIT` argument with
|
||||||
|
`clap` and rejects non-positive limits before the search starts.
|
||||||
|
- Search setup: defines the default limit and the fixed prime list used for
|
||||||
|
candidate generation.
|
||||||
|
- Candidate generation: recursively builds only exponent sequences that can
|
||||||
|
produce highly composite record candidates.
|
||||||
|
- Record filtering: sorts candidates and prints each new divisor-count record.
|
||||||
|
|
||||||
## Algorithm
|
## Algorithm
|
||||||
|
|
||||||
|
|||||||
+27
-37
@@ -1,8 +1,17 @@
|
|||||||
use std::{env, process, time::Instant};
|
use std::{num::NonZeroU64, time::Instant};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
const DEFAULT_SEARCH_LIMIT: u64 = 1_000_000_000;
|
const DEFAULT_SEARCH_LIMIT: u64 = 1_000_000_000;
|
||||||
const FIRST_PRIMES: &[u64] = &[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31];
|
const FIRST_PRIMES: &[u64] = &[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31];
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[command(version, about = "Find highly composite numbers up to a search limit")]
|
||||||
|
struct Cli {
|
||||||
|
#[arg(value_name = "SEARCH_LIMIT", default_value_t = NonZeroU64::new(DEFAULT_SEARCH_LIMIT).expect("default search limit is non-zero"))]
|
||||||
|
search_limit: NonZeroU64,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
struct Candidate {
|
struct Candidate {
|
||||||
nr: u64,
|
nr: u64,
|
||||||
@@ -108,33 +117,8 @@ fn hcn_records(limit: u64) -> Vec<Candidate> {
|
|||||||
records
|
records
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_search_limit(args: &[String]) -> Result<u64, String> {
|
|
||||||
let program = args.first().map_or("hcn", String::as_str);
|
|
||||||
|
|
||||||
match args.len() {
|
|
||||||
0 | 1 => Ok(DEFAULT_SEARCH_LIMIT),
|
|
||||||
2 => {
|
|
||||||
let raw_limit = &args[1];
|
|
||||||
let limit = raw_limit
|
|
||||||
.parse::<u64>()
|
|
||||||
.map_err(|err| format!("invalid search limit {raw_limit:?}: {err}"))?;
|
|
||||||
|
|
||||||
if limit == 0 {
|
|
||||||
return Err("search limit must be greater than zero".to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(limit)
|
|
||||||
}
|
|
||||||
_ => Err(format!("usage: {program} [SEARCH_LIMIT]")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = env::args().collect::<Vec<_>>();
|
let search_limit = Cli::parse().search_limit.get();
|
||||||
let search_limit = parse_search_limit(&args).unwrap_or_else(|err| {
|
|
||||||
eprintln!("{err}");
|
|
||||||
process::exit(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let records = hcn_records(search_limit);
|
let records = hcn_records(search_limit);
|
||||||
@@ -146,7 +130,9 @@ fn main() {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{Candidate, DEFAULT_SEARCH_LIMIT, hcn_records, max_exponent, parse_search_limit};
|
use clap::Parser;
|
||||||
|
|
||||||
|
use super::{Candidate, Cli, DEFAULT_SEARCH_LIMIT, hcn_records, max_exponent};
|
||||||
|
|
||||||
fn count_divisors(mut nr: u64) -> u64 {
|
fn count_divisors(mut nr: u64) -> u64 {
|
||||||
let mut divisor_count = 1;
|
let mut divisor_count = 1;
|
||||||
@@ -264,25 +250,29 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn parses_default_search_limit_without_arg() {
|
fn parses_default_search_limit_without_arg() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_search_limit(&["hcn".to_owned()]),
|
Cli::try_parse_from(["hcn"])
|
||||||
Ok(DEFAULT_SEARCH_LIMIT)
|
.expect("CLI should parse without an explicit search limit")
|
||||||
|
.search_limit
|
||||||
|
.get(),
|
||||||
|
DEFAULT_SEARCH_LIMIT
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parses_search_limit_arg() {
|
fn parses_search_limit_arg() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_search_limit(&["hcn".to_owned(), "42000".to_owned()]),
|
Cli::try_parse_from(["hcn", "42000"])
|
||||||
Ok(42_000)
|
.expect("CLI should parse an explicit search limit")
|
||||||
|
.search_limit
|
||||||
|
.get(),
|
||||||
|
42_000
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rejects_invalid_search_limit_arg() {
|
fn rejects_invalid_search_limit_arg() {
|
||||||
assert!(parse_search_limit(&["hcn".to_owned(), "nope".to_owned()]).is_err());
|
assert!(Cli::try_parse_from(["hcn", "nope"]).is_err());
|
||||||
assert!(parse_search_limit(&["hcn".to_owned(), "0".to_owned()]).is_err());
|
assert!(Cli::try_parse_from(["hcn", "0"]).is_err());
|
||||||
assert!(
|
assert!(Cli::try_parse_from(["hcn", "100", "200"]).is_err());
|
||||||
parse_search_limit(&["hcn".to_owned(), "100".to_owned(), "200".to_owned()]).is_err()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user