2022-12-27 20:16:18 -08:00
|
|
|
|
use {
|
|
|
|
|
super::*,
|
|
|
|
|
heck::{
|
|
|
|
|
ToKebabCase, ToLowerCamelCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnakeCase, ToTitleCase,
|
|
|
|
|
ToUpperCamelCase,
|
|
|
|
|
},
|
2024-05-18 19:29:14 -04:00
|
|
|
|
rand::{seq::SliceRandom, thread_rng},
|
2023-10-28 05:07:46 +09:00
|
|
|
|
semver::{Version, VersionReq},
|
2024-05-18 19:29:14 -04:00
|
|
|
|
std::collections::HashSet,
|
2022-12-27 20:16:18 -08:00
|
|
|
|
Function::*,
|
2022-10-27 00:44:18 -03:00
|
|
|
|
};
|
|
|
|
|
|
2019-11-21 12:14:10 -06:00
|
|
|
|
pub(crate) enum Function {
|
2024-05-17 17:21:47 -07:00
|
|
|
|
Nullary(fn(&Evaluator) -> Result<String, String>),
|
|
|
|
|
Unary(fn(&Evaluator, &str) -> Result<String, String>),
|
|
|
|
|
UnaryOpt(fn(&Evaluator, &str, Option<&str>) -> Result<String, String>),
|
2024-05-20 01:24:27 +01:00
|
|
|
|
UnaryPlus(fn(&Evaluator, &str, &[String]) -> Result<String, String>),
|
2024-05-17 17:21:47 -07:00
|
|
|
|
Binary(fn(&Evaluator, &str, &str) -> Result<String, String>),
|
|
|
|
|
BinaryPlus(fn(&Evaluator, &str, &str, &[String]) -> Result<String, String>),
|
|
|
|
|
Ternary(fn(&Evaluator, &str, &str, &str) -> Result<String, String>),
|
2019-11-21 12:14:10 -06:00
|
|
|
|
}
|
|
|
|
|
|
2022-12-15 18:53:21 -06:00
|
|
|
|
pub(crate) fn get(name: &str) -> Option<Function> {
|
|
|
|
|
let function = match name {
|
|
|
|
|
"absolute_path" => Unary(absolute_path),
|
2024-05-18 00:12:38 +01:00
|
|
|
|
"append" => Binary(append),
|
2022-12-15 18:53:21 -06:00
|
|
|
|
"arch" => Nullary(arch),
|
2024-02-11 15:56:04 -05:00
|
|
|
|
"blake3" => Unary(blake3),
|
|
|
|
|
"blake3_file" => Unary(blake3_file),
|
2024-01-11 18:50:04 -05:00
|
|
|
|
"cache_directory" => Nullary(|_| dir("cache", dirs::cache_dir)),
|
2024-05-18 00:23:59 +01:00
|
|
|
|
"canonicalize" => Unary(canonicalize),
|
2022-12-15 18:53:21 -06:00
|
|
|
|
"capitalize" => Unary(capitalize),
|
2024-05-18 19:29:14 -04:00
|
|
|
|
"choose" => Binary(choose),
|
2022-12-15 18:53:21 -06:00
|
|
|
|
"clean" => Unary(clean),
|
2024-01-11 18:50:04 -05:00
|
|
|
|
"config_directory" => Nullary(|_| dir("config", dirs::config_dir)),
|
|
|
|
|
"config_local_directory" => Nullary(|_| dir("local config", dirs::config_local_dir)),
|
|
|
|
|
"data_directory" => Nullary(|_| dir("data", dirs::data_dir)),
|
|
|
|
|
"data_local_directory" => Nullary(|_| dir("local data", dirs::data_local_dir)),
|
2024-05-18 20:36:34 -04:00
|
|
|
|
"encode_uri_component" => Unary(encode_uri_component),
|
2023-06-13 13:49:46 +01:00
|
|
|
|
"env" => UnaryOpt(env),
|
2022-12-15 18:53:21 -06:00
|
|
|
|
"env_var" => Unary(env_var),
|
|
|
|
|
"env_var_or_default" => Binary(env_var_or_default),
|
|
|
|
|
"error" => Unary(error),
|
2024-01-11 18:50:04 -05:00
|
|
|
|
"executable_directory" => Nullary(|_| dir("executable", dirs::executable_dir)),
|
2022-12-15 18:53:21 -06:00
|
|
|
|
"extension" => Unary(extension),
|
|
|
|
|
"file_name" => Unary(file_name),
|
|
|
|
|
"file_stem" => Unary(file_stem),
|
2024-01-11 18:50:04 -05:00
|
|
|
|
"home_directory" => Nullary(|_| dir("home", dirs::home_dir)),
|
2022-12-15 18:53:21 -06:00
|
|
|
|
"invocation_directory" => Nullary(invocation_directory),
|
2023-01-13 11:03:14 -08:00
|
|
|
|
"invocation_directory_native" => Nullary(invocation_directory_native),
|
2022-12-15 18:53:21 -06:00
|
|
|
|
"join" => BinaryPlus(join),
|
|
|
|
|
"just_executable" => Nullary(just_executable),
|
2024-01-12 06:22:27 +03:00
|
|
|
|
"just_pid" => Nullary(just_pid),
|
2022-12-15 18:53:21 -06:00
|
|
|
|
"justfile" => Nullary(justfile),
|
|
|
|
|
"justfile_directory" => Nullary(justfile_directory),
|
|
|
|
|
"kebabcase" => Unary(kebabcase),
|
|
|
|
|
"lowercamelcase" => Unary(lowercamelcase),
|
|
|
|
|
"lowercase" => Unary(lowercase),
|
2023-08-02 16:52:21 -07:00
|
|
|
|
"num_cpus" => Nullary(num_cpus),
|
2022-12-15 18:53:21 -06:00
|
|
|
|
"os" => Nullary(os),
|
|
|
|
|
"os_family" => Nullary(os_family),
|
|
|
|
|
"parent_directory" => Unary(parent_directory),
|
|
|
|
|
"path_exists" => Unary(path_exists),
|
2024-05-18 00:23:59 +01:00
|
|
|
|
"prepend" => Binary(prepend),
|
2022-12-15 18:53:21 -06:00
|
|
|
|
"quote" => Unary(quote),
|
|
|
|
|
"replace" => Ternary(replace),
|
|
|
|
|
"replace_regex" => Ternary(replace_regex),
|
2023-10-28 05:07:46 +09:00
|
|
|
|
"semver_matches" => Binary(semver_matches),
|
2022-12-15 18:53:21 -06:00
|
|
|
|
"sha256" => Unary(sha256),
|
|
|
|
|
"sha256_file" => Unary(sha256_file),
|
2024-05-20 01:24:27 +01:00
|
|
|
|
"shell" => UnaryPlus(shell),
|
2022-12-15 18:53:21 -06:00
|
|
|
|
"shoutykebabcase" => Unary(shoutykebabcase),
|
|
|
|
|
"shoutysnakecase" => Unary(shoutysnakecase),
|
|
|
|
|
"snakecase" => Unary(snakecase),
|
|
|
|
|
"titlecase" => Unary(titlecase),
|
|
|
|
|
"trim" => Unary(trim),
|
|
|
|
|
"trim_end" => Unary(trim_end),
|
|
|
|
|
"trim_end_match" => Binary(trim_end_match),
|
|
|
|
|
"trim_end_matches" => Binary(trim_end_matches),
|
|
|
|
|
"trim_start" => Unary(trim_start),
|
|
|
|
|
"trim_start_match" => Binary(trim_start_match),
|
|
|
|
|
"trim_start_matches" => Binary(trim_start_matches),
|
|
|
|
|
"uppercamelcase" => Unary(uppercamelcase),
|
|
|
|
|
"uppercase" => Unary(uppercase),
|
|
|
|
|
"uuid" => Nullary(uuid),
|
|
|
|
|
"without_extension" => Unary(without_extension),
|
|
|
|
|
_ => return None,
|
|
|
|
|
};
|
|
|
|
|
Some(function)
|
2017-12-02 23:59:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Function {
|
2021-10-14 17:00:58 -07:00
|
|
|
|
pub(crate) fn argc(&self) -> Range<usize> {
|
2017-12-02 23:59:07 +01:00
|
|
|
|
match *self {
|
2021-10-14 17:00:58 -07:00
|
|
|
|
Nullary(_) => 0..0,
|
|
|
|
|
Unary(_) => 1..1,
|
2023-06-13 13:49:46 +01:00
|
|
|
|
UnaryOpt(_) => 1..2,
|
2024-05-20 01:24:27 +01:00
|
|
|
|
UnaryPlus(_) => 1..usize::MAX,
|
2021-10-14 17:00:58 -07:00
|
|
|
|
Binary(_) => 2..2,
|
|
|
|
|
BinaryPlus(_) => 2..usize::MAX,
|
|
|
|
|
Ternary(_) => 3..3,
|
2017-12-02 23:59:07 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-12-02 14:37:10 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn absolute_path(evaluator: &Evaluator, path: &str) -> Result<String, String> {
|
|
|
|
|
let abs_path_unchecked = evaluator.search.working_directory.join(path).lexiclean();
|
2022-03-03 00:41:48 +00:00
|
|
|
|
match abs_path_unchecked.to_str() {
|
|
|
|
|
Some(absolute_path) => Ok(absolute_path.to_owned()),
|
|
|
|
|
None => Err(format!(
|
|
|
|
|
"Working directory is not valid unicode: {}",
|
2024-05-17 17:21:47 -07:00
|
|
|
|
evaluator.search.working_directory.display()
|
2022-03-03 00:41:48 +00:00
|
|
|
|
)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn append(_evaluator: &Evaluator, suffix: &str, s: &str) -> Result<String, String> {
|
2024-05-18 00:12:38 +01:00
|
|
|
|
Ok(
|
|
|
|
|
s.split_whitespace()
|
|
|
|
|
.map(|s| format!("{s}{suffix}"))
|
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
|
.join(" "),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn arch(_evaluator: &Evaluator) -> Result<String, String> {
|
2021-02-15 01:18:31 -08:00
|
|
|
|
Ok(target::arch().to_owned())
|
2017-12-02 23:59:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn blake3(_evaluator: &Evaluator, s: &str) -> Result<String, String> {
|
2024-02-11 15:56:04 -05:00
|
|
|
|
Ok(blake3::hash(s.as_bytes()).to_string())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn blake3_file(evaluator: &Evaluator, path: &str) -> Result<String, String> {
|
|
|
|
|
let path = evaluator.search.working_directory.join(path);
|
2024-02-11 15:56:04 -05:00
|
|
|
|
let mut hasher = blake3::Hasher::new();
|
|
|
|
|
hasher
|
|
|
|
|
.update_mmap_rayon(&path)
|
|
|
|
|
.map_err(|err| format!("Failed to hash `{}`: {err}", path.display()))?;
|
|
|
|
|
Ok(hasher.finalize().to_string())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn canonicalize(_evaluator: &Evaluator, path: &str) -> Result<String, String> {
|
2024-01-19 12:04:28 -08:00
|
|
|
|
let canonical =
|
|
|
|
|
std::fs::canonicalize(path).map_err(|err| format!("I/O error canonicalizing path: {err}"))?;
|
|
|
|
|
|
|
|
|
|
canonical.to_str().map(str::to_string).ok_or_else(|| {
|
|
|
|
|
format!(
|
|
|
|
|
"Canonical path is not valid unicode: {}",
|
|
|
|
|
canonical.display(),
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn capitalize(_evaluator: &Evaluator, s: &str) -> Result<String, String> {
|
2022-10-24 20:52:43 -07:00
|
|
|
|
let mut capitalized = String::new();
|
|
|
|
|
for (i, c) in s.chars().enumerate() {
|
|
|
|
|
if i == 0 {
|
|
|
|
|
capitalized.extend(c.to_uppercase());
|
|
|
|
|
} else {
|
|
|
|
|
capitalized.extend(c.to_lowercase());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(capitalized)
|
2022-10-25 04:39:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-18 19:29:14 -04:00
|
|
|
|
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())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn clean(_evaluator: &Evaluator, path: &str) -> Result<String, String> {
|
2021-06-24 23:41:20 -07:00
|
|
|
|
Ok(Path::new(path).lexiclean().to_str().unwrap().to_owned())
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-11 18:50:04 -05:00
|
|
|
|
fn dir(name: &'static str, f: fn() -> Option<PathBuf>) -> Result<String, String> {
|
|
|
|
|
match f() {
|
|
|
|
|
Some(path) => path
|
|
|
|
|
.as_os_str()
|
|
|
|
|
.to_str()
|
|
|
|
|
.map(str::to_string)
|
|
|
|
|
.ok_or_else(|| {
|
|
|
|
|
format!(
|
|
|
|
|
"unable to convert {name} directory path to string: {}",
|
|
|
|
|
path.display(),
|
|
|
|
|
)
|
|
|
|
|
}),
|
|
|
|
|
None => Err(format!("{name} directory not found")),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-18 20:36:34 -04:00
|
|
|
|
fn encode_uri_component(_evaluator: &Evaluator, s: &str) -> Result<String, String> {
|
|
|
|
|
static PERCENT_ENCODE: percent_encoding::AsciiSet = percent_encoding::NON_ALPHANUMERIC
|
|
|
|
|
.remove(b'-')
|
|
|
|
|
.remove(b'_')
|
|
|
|
|
.remove(b'.')
|
|
|
|
|
.remove(b'!')
|
|
|
|
|
.remove(b'~')
|
|
|
|
|
.remove(b'*')
|
|
|
|
|
.remove(b'\'')
|
|
|
|
|
.remove(b'(')
|
|
|
|
|
.remove(b')');
|
|
|
|
|
Ok(percent_encoding::utf8_percent_encode(s, &PERCENT_ENCODE).to_string())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn env_var(evaluator: &Evaluator, key: &str) -> Result<String, String> {
|
2017-12-02 23:59:07 +01:00
|
|
|
|
use std::env::VarError::*;
|
2018-08-27 16:03:52 -07:00
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
if let Some(value) = evaluator.dotenv.get(key) {
|
2018-03-17 09:17:41 -07:00
|
|
|
|
return Ok(value.clone());
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-02 23:59:07 +01:00
|
|
|
|
match env::var(key) {
|
2022-12-15 18:53:21 -06:00
|
|
|
|
Err(NotPresent) => Err(format!("environment variable `{key}` not present")),
|
2018-12-08 14:29:41 -08:00
|
|
|
|
Err(NotUnicode(os_string)) => Err(format!(
|
2023-01-26 18:49:03 -08:00
|
|
|
|
"environment variable `{key}` not unicode: {os_string:?}"
|
2018-12-08 14:29:41 -08:00
|
|
|
|
)),
|
2017-12-02 23:59:07 +01:00
|
|
|
|
Ok(value) => Ok(value),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn env_var_or_default(evaluator: &Evaluator, key: &str, default: &str) -> Result<String, String> {
|
2020-01-15 02:16:13 -08:00
|
|
|
|
use std::env::VarError::*;
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
if let Some(value) = evaluator.dotenv.get(key) {
|
2018-03-17 09:17:41 -07:00
|
|
|
|
return Ok(value.clone());
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-02 23:59:07 +01:00
|
|
|
|
match env::var(key) {
|
2021-02-15 01:18:31 -08:00
|
|
|
|
Err(NotPresent) => Ok(default.to_owned()),
|
2018-12-08 14:29:41 -08:00
|
|
|
|
Err(NotUnicode(os_string)) => Err(format!(
|
2023-01-26 18:49:03 -08:00
|
|
|
|
"environment variable `{key}` not unicode: {os_string:?}"
|
2018-12-08 14:29:41 -08:00
|
|
|
|
)),
|
2017-12-02 23:59:07 +01:00
|
|
|
|
Ok(value) => Ok(value),
|
|
|
|
|
}
|
2017-12-02 14:37:10 +01:00
|
|
|
|
}
|
2021-03-29 00:44:02 +02:00
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn env(evaluator: &Evaluator, key: &str, default: Option<&str>) -> Result<String, String> {
|
2023-06-13 13:49:46 +01:00
|
|
|
|
match default {
|
2024-05-17 17:21:47 -07:00
|
|
|
|
Some(val) => env_var_or_default(evaluator, key, val),
|
|
|
|
|
None => env_var(evaluator, key),
|
2023-06-13 13:49:46 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn error(_evaluator: &Evaluator, message: &str) -> Result<String, String> {
|
2022-03-02 18:48:28 -06:00
|
|
|
|
Err(message.to_owned())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn extension(_evaluator: &Evaluator, path: &str) -> Result<String, String> {
|
2021-06-24 15:55:29 -07:00
|
|
|
|
Utf8Path::new(path)
|
|
|
|
|
.extension()
|
|
|
|
|
.map(str::to_owned)
|
2022-12-15 18:53:21 -06:00
|
|
|
|
.ok_or_else(|| format!("Could not extract extension from `{path}`"))
|
2021-06-24 15:55:29 -07:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn file_name(_evaluator: &Evaluator, path: &str) -> Result<String, String> {
|
2021-06-24 15:55:29 -07:00
|
|
|
|
Utf8Path::new(path)
|
|
|
|
|
.file_name()
|
|
|
|
|
.map(str::to_owned)
|
2022-12-15 18:53:21 -06:00
|
|
|
|
.ok_or_else(|| format!("Could not extract file name from `{path}`"))
|
2021-06-24 15:55:29 -07:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn file_stem(_evaluator: &Evaluator, path: &str) -> Result<String, String> {
|
2021-06-24 15:55:29 -07:00
|
|
|
|
Utf8Path::new(path)
|
|
|
|
|
.file_stem()
|
|
|
|
|
.map(str::to_owned)
|
2022-12-15 18:53:21 -06:00
|
|
|
|
.ok_or_else(|| format!("Could not extract file stem from `{path}`"))
|
2021-06-24 15:55:29 -07:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn invocation_directory(evaluator: &Evaluator) -> Result<String, String> {
|
2021-06-24 15:55:29 -07:00
|
|
|
|
Platform::convert_native_path(
|
2024-05-17 17:21:47 -07:00
|
|
|
|
&evaluator.search.working_directory,
|
|
|
|
|
&evaluator.config.invocation_directory,
|
2021-06-24 15:55:29 -07:00
|
|
|
|
)
|
2022-12-15 18:53:21 -06:00
|
|
|
|
.map_err(|e| format!("Error getting shell path: {e}"))
|
2021-06-24 15:55:29 -07:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn invocation_directory_native(evaluator: &Evaluator) -> Result<String, String> {
|
|
|
|
|
evaluator
|
|
|
|
|
.config
|
2023-01-13 11:03:14 -08:00
|
|
|
|
.invocation_directory
|
|
|
|
|
.to_str()
|
|
|
|
|
.map(str::to_owned)
|
|
|
|
|
.ok_or_else(|| {
|
|
|
|
|
format!(
|
|
|
|
|
"Invocation directory is not valid unicode: {}",
|
2024-05-17 17:21:47 -07:00
|
|
|
|
evaluator.config.invocation_directory.display()
|
2023-01-13 11:03:14 -08:00
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn prepend(_evaluator: &Evaluator, prefix: &str, s: &str) -> Result<String, String> {
|
2024-05-18 00:23:59 +01:00
|
|
|
|
Ok(
|
|
|
|
|
s.split_whitespace()
|
|
|
|
|
.map(|s| format!("{prefix}{s}"))
|
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
|
.join(" "),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn join(_evaluator: &Evaluator, base: &str, with: &str, and: &[String]) -> Result<String, String> {
|
2021-10-14 17:00:58 -07:00
|
|
|
|
let mut result = Utf8Path::new(base).join(with);
|
|
|
|
|
for arg in and {
|
|
|
|
|
result.push(arg);
|
|
|
|
|
}
|
|
|
|
|
Ok(result.to_string())
|
2021-06-24 15:55:29 -07:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn just_executable(_evaluator: &Evaluator) -> Result<String, String> {
|
2021-03-29 00:44:02 +02:00
|
|
|
|
let exe_path =
|
2023-06-12 12:53:55 -04:00
|
|
|
|
env::current_exe().map_err(|e| format!("Error getting current executable: {e}"))?;
|
2021-03-29 00:44:02 +02:00
|
|
|
|
|
|
|
|
|
exe_path.to_str().map(str::to_owned).ok_or_else(|| {
|
|
|
|
|
format!(
|
|
|
|
|
"Executable path is not valid unicode: {}",
|
2022-02-27 19:09:31 -08:00
|
|
|
|
exe_path.display()
|
2021-03-29 00:44:02 +02:00
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
}
|
2021-06-17 09:56:09 +02:00
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn just_pid(_evaluator: &Evaluator) -> Result<String, String> {
|
2024-01-12 06:22:27 +03:00
|
|
|
|
Ok(std::process::id().to_string())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn justfile(evaluator: &Evaluator) -> Result<String, String> {
|
|
|
|
|
evaluator
|
2021-06-24 15:55:29 -07:00
|
|
|
|
.search
|
|
|
|
|
.justfile
|
|
|
|
|
.to_str()
|
2021-06-17 09:56:09 +02:00
|
|
|
|
.map(str::to_owned)
|
2021-06-24 15:55:29 -07:00
|
|
|
|
.ok_or_else(|| {
|
|
|
|
|
format!(
|
|
|
|
|
"Justfile path is not valid unicode: {}",
|
2024-05-17 17:21:47 -07:00
|
|
|
|
evaluator.search.justfile.display()
|
2021-06-24 15:55:29 -07:00
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn justfile_directory(evaluator: &Evaluator) -> Result<String, String> {
|
|
|
|
|
let justfile_directory = evaluator.search.justfile.parent().ok_or_else(|| {
|
2021-06-24 15:55:29 -07:00
|
|
|
|
format!(
|
|
|
|
|
"Could not resolve justfile directory. Justfile `{}` had no parent.",
|
2024-05-17 17:21:47 -07:00
|
|
|
|
evaluator.search.justfile.display()
|
2021-06-24 15:55:29 -07:00
|
|
|
|
)
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
justfile_directory
|
|
|
|
|
.to_str()
|
|
|
|
|
.map(str::to_owned)
|
|
|
|
|
.ok_or_else(|| {
|
|
|
|
|
format!(
|
|
|
|
|
"Justfile directory is not valid unicode: {}",
|
2022-02-27 19:09:31 -08:00
|
|
|
|
justfile_directory.display()
|
2021-06-24 15:55:29 -07:00
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn kebabcase(_evaluator: &Evaluator, s: &str) -> Result<String, String> {
|
2022-10-27 00:44:18 -03:00
|
|
|
|
Ok(s.to_kebab_case())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn lowercamelcase(_evaluator: &Evaluator, s: &str) -> Result<String, String> {
|
2022-10-27 00:44:18 -03:00
|
|
|
|
Ok(s.to_lower_camel_case())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn lowercase(_evaluator: &Evaluator, s: &str) -> Result<String, String> {
|
2021-07-03 15:39:45 -04:00
|
|
|
|
Ok(s.to_lowercase())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn num_cpus(_evaluator: &Evaluator) -> Result<String, String> {
|
2023-08-02 16:52:21 -07:00
|
|
|
|
let num = num_cpus::get();
|
|
|
|
|
Ok(num.to_string())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn os(_evaluator: &Evaluator) -> Result<String, String> {
|
2021-06-24 15:55:29 -07:00
|
|
|
|
Ok(target::os().to_owned())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn os_family(_evaluator: &Evaluator) -> Result<String, String> {
|
2021-08-27 16:36:41 -07:00
|
|
|
|
Ok(target::family().to_owned())
|
2021-06-17 09:56:09 +02:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn parent_directory(_evaluator: &Evaluator, path: &str) -> Result<String, String> {
|
2021-06-17 09:56:09 +02:00
|
|
|
|
Utf8Path::new(path)
|
|
|
|
|
.parent()
|
|
|
|
|
.map(Utf8Path::to_string)
|
2022-12-15 18:53:21 -06:00
|
|
|
|
.ok_or_else(|| format!("Could not extract parent directory from `{path}`"))
|
2021-06-17 09:56:09 +02:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn path_exists(evaluator: &Evaluator, path: &str) -> Result<String, String> {
|
2022-02-27 19:09:31 -08:00
|
|
|
|
Ok(
|
2024-05-17 17:21:47 -07:00
|
|
|
|
evaluator
|
2022-02-27 19:09:31 -08:00
|
|
|
|
.search
|
|
|
|
|
.working_directory
|
|
|
|
|
.join(path)
|
|
|
|
|
.exists()
|
|
|
|
|
.to_string(),
|
|
|
|
|
)
|
2022-02-21 22:45:30 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn quote(_evaluator: &Evaluator, s: &str) -> Result<String, String> {
|
2021-11-08 11:22:58 -08:00
|
|
|
|
Ok(format!("'{}'", s.replace('\'', "'\\''")))
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn replace(_evaluator: &Evaluator, s: &str, from: &str, to: &str) -> Result<String, String> {
|
2021-07-03 15:39:45 -04:00
|
|
|
|
Ok(s.replace(from, to))
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-08 14:47:33 +08:00
|
|
|
|
fn replace_regex(
|
2024-05-17 17:21:47 -07:00
|
|
|
|
_evaluator: &Evaluator,
|
2022-11-08 14:47:33 +08:00
|
|
|
|
s: &str,
|
|
|
|
|
regex: &str,
|
|
|
|
|
replacement: &str,
|
|
|
|
|
) -> Result<String, String> {
|
|
|
|
|
Ok(
|
|
|
|
|
Regex::new(regex)
|
|
|
|
|
.map_err(|err| err.to_string())?
|
|
|
|
|
.replace_all(s, replacement)
|
|
|
|
|
.to_string(),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn sha256(_evaluator: &Evaluator, s: &str) -> Result<String, String> {
|
2022-05-05 01:18:31 +02:00
|
|
|
|
use sha2::{Digest, Sha256};
|
|
|
|
|
let mut hasher = Sha256::new();
|
|
|
|
|
hasher.update(s);
|
|
|
|
|
let hash = hasher.finalize();
|
2022-12-15 18:53:21 -06:00
|
|
|
|
Ok(format!("{hash:x}"))
|
2022-05-05 01:18:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn sha256_file(evaluator: &Evaluator, path: &str) -> Result<String, String> {
|
2022-05-05 01:18:31 +02:00
|
|
|
|
use sha2::{Digest, Sha256};
|
2024-05-17 17:21:47 -07:00
|
|
|
|
let path = evaluator.search.working_directory.join(path);
|
2022-05-05 01:18:31 +02:00
|
|
|
|
let mut hasher = Sha256::new();
|
2024-02-11 15:56:04 -05:00
|
|
|
|
let mut file =
|
|
|
|
|
fs::File::open(&path).map_err(|err| format!("Failed to open `{}`: {err}", path.display()))?;
|
2022-05-05 01:18:31 +02:00
|
|
|
|
std::io::copy(&mut file, &mut hasher)
|
2024-02-11 15:56:04 -05:00
|
|
|
|
.map_err(|err| format!("Failed to read `{}`: {err}", path.display()))?;
|
2022-05-05 01:18:31 +02:00
|
|
|
|
let hash = hasher.finalize();
|
2022-12-15 18:53:21 -06:00
|
|
|
|
Ok(format!("{hash:x}"))
|
2022-05-05 01:18:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-20 01:24:27 +01:00
|
|
|
|
fn shell(evaluator: &Evaluator, command: &str, args: &[String]) -> Result<String, String> {
|
2024-05-19 21:12:09 -07:00
|
|
|
|
let args = iter::once(command)
|
|
|
|
|
.chain(args.iter().map(String::as_str))
|
|
|
|
|
.collect::<Vec<&str>>();
|
|
|
|
|
|
2024-05-20 01:24:27 +01:00
|
|
|
|
evaluator
|
2024-05-19 21:12:09 -07:00
|
|
|
|
.run_command(command, &args)
|
2024-05-20 01:24:27 +01:00
|
|
|
|
.map_err(|output_error| output_error.to_string())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn shoutykebabcase(_evaluator: &Evaluator, s: &str) -> Result<String, String> {
|
2022-10-27 00:44:18 -03:00
|
|
|
|
Ok(s.to_shouty_kebab_case())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn shoutysnakecase(_evaluator: &Evaluator, s: &str) -> Result<String, String> {
|
2022-10-27 00:44:18 -03:00
|
|
|
|
Ok(s.to_shouty_snake_case())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn snakecase(_evaluator: &Evaluator, s: &str) -> Result<String, String> {
|
2022-10-27 00:44:18 -03:00
|
|
|
|
Ok(s.to_snake_case())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn titlecase(_evaluator: &Evaluator, s: &str) -> Result<String, String> {
|
2022-10-27 00:44:18 -03:00
|
|
|
|
Ok(s.to_title_case())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn trim(_evaluator: &Evaluator, s: &str) -> Result<String, String> {
|
2021-07-03 15:39:45 -04:00
|
|
|
|
Ok(s.trim().to_owned())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn trim_end(_evaluator: &Evaluator, s: &str) -> Result<String, String> {
|
2021-10-14 00:35:15 -07:00
|
|
|
|
Ok(s.trim_end().to_owned())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn trim_end_match(_evaluator: &Evaluator, s: &str, pat: &str) -> Result<String, String> {
|
2021-10-14 00:03:57 -07:00
|
|
|
|
Ok(s.strip_suffix(pat).unwrap_or(s).to_owned())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn trim_end_matches(_evaluator: &Evaluator, s: &str, pat: &str) -> Result<String, String> {
|
2021-10-14 00:03:57 -07:00
|
|
|
|
Ok(s.trim_end_matches(pat).to_owned())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn trim_start(_evaluator: &Evaluator, s: &str) -> Result<String, String> {
|
2021-10-14 00:35:15 -07:00
|
|
|
|
Ok(s.trim_start().to_owned())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn trim_start_match(_evaluator: &Evaluator, s: &str, pat: &str) -> Result<String, String> {
|
2021-10-14 00:03:57 -07:00
|
|
|
|
Ok(s.strip_prefix(pat).unwrap_or(s).to_owned())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn trim_start_matches(_evaluator: &Evaluator, s: &str, pat: &str) -> Result<String, String> {
|
2021-10-14 00:03:57 -07:00
|
|
|
|
Ok(s.trim_start_matches(pat).to_owned())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn uppercamelcase(_evaluator: &Evaluator, s: &str) -> Result<String, String> {
|
2022-10-27 00:44:18 -03:00
|
|
|
|
Ok(s.to_upper_camel_case())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn uppercase(_evaluator: &Evaluator, s: &str) -> Result<String, String> {
|
2021-07-03 15:39:45 -04:00
|
|
|
|
Ok(s.to_uppercase())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn uuid(_evaluator: &Evaluator) -> Result<String, String> {
|
2022-05-05 01:18:31 +02:00
|
|
|
|
Ok(uuid::Uuid::new_v4().to_string())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 17:21:47 -07:00
|
|
|
|
fn without_extension(_evaluator: &Evaluator, path: &str) -> Result<String, String> {
|
2021-06-17 09:56:09 +02:00
|
|
|
|
let parent = Utf8Path::new(path)
|
|
|
|
|
.parent()
|
2022-12-15 18:53:21 -06:00
|
|
|
|
.ok_or_else(|| format!("Could not extract parent from `{path}`"))?;
|
2021-06-17 09:56:09 +02:00
|
|
|
|
|
|
|
|
|
let file_stem = Utf8Path::new(path)
|
|
|
|
|
.file_stem()
|
2022-12-15 18:53:21 -06:00
|
|
|
|
.ok_or_else(|| format!("Could not extract file stem from `{path}`"))?;
|
2021-06-17 09:56:09 +02:00
|
|
|
|
|
|
|
|
|
Ok(parent.join(file_stem).to_string())
|
|
|
|
|
}
|
2023-10-28 05:07:46 +09:00
|
|
|
|
|
|
|
|
|
/// Check whether a string processes properly as semver (e.x. "0.1.0")
|
|
|
|
|
/// and matches a given semver requirement (e.x. ">=0.1.0")
|
|
|
|
|
fn semver_matches(
|
2024-05-17 17:21:47 -07:00
|
|
|
|
_evaluator: &Evaluator,
|
2023-10-28 05:07:46 +09:00
|
|
|
|
version: &str,
|
|
|
|
|
requirement: &str,
|
|
|
|
|
) -> Result<String, String> {
|
|
|
|
|
Ok(
|
|
|
|
|
requirement
|
|
|
|
|
.parse::<VersionReq>()
|
|
|
|
|
.map_err(|err| format!("invalid semver requirement: {err}"))?
|
|
|
|
|
.matches(
|
|
|
|
|
&version
|
|
|
|
|
.parse::<Version>()
|
|
|
|
|
.map_err(|err| format!("invalid semver version: {err}"))?,
|
|
|
|
|
)
|
|
|
|
|
.to_string(),
|
|
|
|
|
)
|
|
|
|
|
}
|
2024-01-11 18:50:04 -05:00
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn dir_not_found() {
|
|
|
|
|
assert_eq!(dir("foo", || None).unwrap_err(), "foo directory not found");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(unix)]
|
|
|
|
|
#[test]
|
|
|
|
|
fn dir_not_unicode() {
|
|
|
|
|
use std::os::unix::ffi::OsStrExt;
|
|
|
|
|
assert_eq!(
|
2024-05-15 05:29:40 +02:00
|
|
|
|
dir("foo", || Some(
|
|
|
|
|
std::ffi::OsStr::from_bytes(b"\xe0\x80\x80").into()
|
|
|
|
|
))
|
|
|
|
|
.unwrap_err(),
|
2024-01-11 18:50:04 -05:00
|
|
|
|
"unable to convert foo directory path to string: <20><><EFBFBD>",
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|