Add choose function for generating random strings (#2049)

This commit is contained in:
laniakea64 2024-05-18 19:29:14 -04:00 committed by GitHub
parent 6907847a77
commit 7fa6ed8149
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 135 additions and 0 deletions

37
Cargo.lock generated
View File

@ -537,6 +537,7 @@ dependencies = [
"log",
"num_cpus",
"pretty_assertions",
"rand",
"regex",
"semver",
"serde",
@ -637,6 +638,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "pretty_assertions"
version = "1.4.0"
@ -710,6 +717,36 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "rayon"
version = "1.10.0"

View File

@ -36,6 +36,7 @@ lexiclean = "0.0.1"
libc = "0.2.0"
log = "0.4.4"
num_cpus = "1.15.0"
rand = "0.8.5"
regex = "1.10.4"
semver = "1.0.20"
serde = { version = "1.0.130", features = ["derive", "rc"] }

View File

@ -1517,6 +1517,13 @@ which will halt execution.
[BLAKE3]: https://github.com/BLAKE3-team/BLAKE3/
#### Random
- `choose(n, alphabet)`<sup>master</sup> - Generate a string of `n` randomly
selected characters from `alphabet`, which may not contain repeated
characters. For example, `choose('64', HEX)` will generate a random
64-character lowercase hex string.
#### Semantic Versions
- `semver_matches(version, requirement)`<sup>1.16.0</sup> - Check whether a

View File

@ -4,7 +4,9 @@ use {
ToKebabCase, ToLowerCamelCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnakeCase, ToTitleCase,
ToUpperCamelCase,
},
rand::{seq::SliceRandom, thread_rng},
semver::{Version, VersionReq},
std::collections::HashSet,
Function::*,
};
@ -27,6 +29,7 @@ pub(crate) fn get(name: &str) -> Option<Function> {
"cache_directory" => Nullary(|_| dir("cache", dirs::cache_dir)),
"canonicalize" => Unary(canonicalize),
"capitalize" => Unary(capitalize),
"choose" => Binary(choose),
"clean" => Unary(clean),
"config_directory" => Nullary(|_| dir("config", dirs::config_dir)),
"config_local_directory" => Nullary(|_| dir("local config", dirs::config_local_dir)),
@ -157,6 +160,30 @@ fn capitalize(_evaluator: &Evaluator, s: &str) -> Result<String, String> {
Ok(capitalized)
}
fn choose(_evaluator: &Evaluator, n: &str, alphabet: &str) -> Result<String, String> {
if alphabet.is_empty() {
return Err("empty alphabet".into());
}
let mut chars = HashSet::<char>::with_capacity(alphabet.len());
for c in alphabet.chars() {
if !chars.insert(c) {
return Err(format!("alphabet contains repeated character `{c}`"));
}
}
let alphabet = alphabet.chars().collect::<Vec<char>>();
let n = n
.parse::<usize>()
.map_err(|err| format!("failed to parse `{n}` as positive integer: {err}"))?;
let mut rng = thread_rng();
Ok((0..n).map(|_| alphabet.choose(&mut rng).unwrap()).collect())
}
fn clean(_evaluator: &Evaluator, path: &str) -> Result<String, String> {
Ok(Path::new(path).lexiclean().to_str().unwrap().to_owned())
}

View File

@ -661,6 +661,69 @@ fn uuid() {
.run();
}
#[test]
fn choose() {
Test::new()
.justfile(r#"x := choose('10', 'xXyYzZ')"#)
.args(["--evaluate", "x"])
.stdout_regex("^[X-Zx-z]{10}$")
.run();
}
#[test]
fn choose_bad_alphabet_empty() {
Test::new()
.justfile("x := choose('10', '')")
.args(["--evaluate"])
.status(1)
.stderr(
"
error: Call to function `choose` failed: empty alphabet
justfile:1:6
1 x := choose('10', '')
^^^^^^
",
)
.run();
}
#[test]
fn choose_bad_alphabet_repeated() {
Test::new()
.justfile("x := choose('10', 'aa')")
.args(["--evaluate"])
.status(1)
.stderr(
"
error: Call to function `choose` failed: alphabet contains repeated character `a`
justfile:1:6
1 x := choose('10', 'aa')
^^^^^^
",
)
.run();
}
#[test]
fn choose_bad_length() {
Test::new()
.justfile("x := choose('foo', HEX)")
.args(["--evaluate"])
.status(1)
.stderr(
"
error: Call to function `choose` failed: failed to parse `foo` as positive integer: invalid digit found in string
justfile:1:6
1 x := choose('foo', HEX)
^^^^^^
",
)
.run();
}
#[test]
fn sha256() {
Test::new()