Skip to content

Commit

Permalink
Add choose function for generating random strings (#2049)
Browse files Browse the repository at this point in the history
  • Loading branch information
laniakea64 authored May 18, 2024
1 parent 6907847 commit 7fa6ed8
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 0 deletions.
37 changes: 37 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 27 additions & 0 deletions src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use {
ToKebabCase, ToLowerCamelCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnakeCase, ToTitleCase,
ToUpperCamelCase,
},
rand::{seq::SliceRandom, thread_rng},
semver::{Version, VersionReq},
std::collections::HashSet,
Function::*,
};

Expand All @@ -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)),
Expand Down Expand Up @@ -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())
}
Expand Down
63 changes: 63 additions & 0 deletions tests/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit 7fa6ed8

Please sign in to comment.