pub fn is_stdlib_function(name: &str) -> bool {
matches!(
name,
"string_trim"
| "string_contains"
| "string_len"
| "string_split"
| "string_replace"
| "string_to_upper"
| "string_to_lower"
| "string_starts_with"
| "string_ends_with"
| "fs_exists"
| "fs_read_file"
| "fs_write_file"
| "fs_copy"
| "fs_remove"
| "fs_is_file"
| "fs_is_dir"
| "array_len"
| "array_join"
| "env"
| "env_var_or"
| "arg"
| "args"
| "arg_count"
| "exit_code"
| "capture"
| "exec"
| "exit"
| "sleep"
| "glob"
| "mkdir"
| "mv"
| "chmod"
)
}
pub fn get_shell_function_name(name: &str) -> String {
format!("rash_{}", name)
}
pub use crate::stdlib_metadata::{StdlibFunction, STDLIB_FUNCTIONS};
const _STDLIB_METADATA_ANCHOR: () = {
assert!(!STDLIB_FUNCTIONS.is_empty());
};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_stdlib_function() {
assert!(is_stdlib_function("string_trim"));
assert!(is_stdlib_function("string_replace"));
assert!(is_stdlib_function("string_to_upper"));
assert!(is_stdlib_function("string_to_lower"));
assert!(is_stdlib_function("fs_exists"));
assert!(is_stdlib_function("fs_copy"));
assert!(is_stdlib_function("fs_remove"));
assert!(is_stdlib_function("fs_is_file"));
assert!(is_stdlib_function("fs_is_dir"));
assert!(is_stdlib_function("capture"));
assert!(is_stdlib_function("exec"));
assert!(is_stdlib_function("exit"));
assert!(is_stdlib_function("sleep"));
assert!(is_stdlib_function("string_starts_with"));
assert!(is_stdlib_function("string_ends_with"));
assert!(is_stdlib_function("glob"));
assert!(is_stdlib_function("mkdir"));
assert!(is_stdlib_function("mv"));
assert!(is_stdlib_function("chmod"));
assert!(!is_stdlib_function("custom_function"));
assert!(!is_stdlib_function("println"));
}
#[test]
fn test_get_shell_function_name() {
assert_eq!(get_shell_function_name("string_trim"), "rash_string_trim");
assert_eq!(get_shell_function_name("fs_exists"), "rash_fs_exists");
}
#[test]
fn test_stdlib_env_function_recognized() {
assert!(
is_stdlib_function("env"),
"env() should be recognized as stdlib function"
);
}
#[test]
fn test_stdlib_env_var_or_function_recognized() {
assert!(
is_stdlib_function("env_var_or"),
"env_var_or() should be recognized as stdlib function"
);
}
#[test]
fn test_stdlib_arg_function_recognized() {
assert!(
is_stdlib_function("arg"),
"arg() should be recognized as stdlib function"
);
}
#[test]
fn test_stdlib_args_function_recognized() {
assert!(
is_stdlib_function("args"),
"args() should be recognized as stdlib function"
);
}
#[test]
fn test_stdlib_arg_count_function_recognized() {
assert!(
is_stdlib_function("arg_count"),
"arg_count() should be recognized as stdlib function"
);
}
#[test]
fn test_stdlib_exit_code_function_recognized() {
assert!(
is_stdlib_function("exit_code"),
"exit_code() should be recognized as stdlib function"
);
}
#[test]
fn test_stdlib_exit_code_metadata() {
let metadata: Vec<&StdlibFunction> = STDLIB_FUNCTIONS
.iter()
.filter(|f| f.name == "exit_code")
.collect();
assert_eq!(metadata.len(), 1, "exit_code should have metadata entry");
assert_eq!(
metadata[0].module, "status",
"exit_code should be in 'status' module"
);
assert_eq!(
metadata[0].shell_name, "inline_exit_code",
"exit_code should use inline shell syntax"
);
}
#[test]
fn test_stdlib_string_split_recognized() {
assert!(
is_stdlib_function("string_split"),
"string_split() should be recognized as stdlib function"
);
}
#[test]
fn test_stdlib_string_split_metadata() {
let metadata: Vec<&StdlibFunction> = STDLIB_FUNCTIONS
.iter()
.filter(|f| f.name == "string_split")
.collect();
assert_eq!(metadata.len(), 1, "string_split should have metadata entry");
assert_eq!(
metadata[0].module, "string",
"string_split should be in 'string' module"
);
assert_eq!(
metadata[0].shell_name, "rash_string_split",
"string_split should use rash_ prefix"
);
}
#[test]
fn test_stdlib_array_len_recognized() {
assert!(
is_stdlib_function("array_len"),
"array_len() should be recognized as stdlib function"
);
}
#[test]
fn test_stdlib_array_len_metadata() {
let metadata: Vec<&StdlibFunction> = STDLIB_FUNCTIONS
.iter()
.filter(|f| f.name == "array_len")
.collect();
assert_eq!(metadata.len(), 1, "array_len should have metadata entry");
assert_eq!(
metadata[0].module, "array",
"array_len should be in 'array' module"
);
assert_eq!(
metadata[0].shell_name, "rash_array_len",
"array_len should use rash_ prefix"
);
}
#[test]
fn test_stdlib_array_join_recognized() {
assert!(
is_stdlib_function("array_join"),
"array_join() should be recognized as stdlib function"
);
}
#[test]
fn test_GH148_starts_with_recognized() {
assert!(
is_stdlib_function("string_starts_with"),
"string_starts_with() should be recognized as stdlib function"
);
}
#[test]
fn test_GH148_ends_with_recognized() {
assert!(
is_stdlib_function("string_ends_with"),
"string_ends_with() should be recognized as stdlib function"
);
}
#[test]
fn test_GH148_starts_with_metadata() {
let metadata: Vec<&StdlibFunction> = STDLIB_FUNCTIONS
.iter()
.filter(|f| f.name == "string_starts_with")
.collect();
assert_eq!(
metadata.len(),
1,
"string_starts_with should have metadata entry"
);
assert_eq!(metadata[0].module, "string");
assert_eq!(metadata[0].shell_name, "rash_string_starts_with");
}
#[test]
fn test_GH148_ends_with_metadata() {
let metadata: Vec<&StdlibFunction> = STDLIB_FUNCTIONS
.iter()
.filter(|f| f.name == "string_ends_with")
.collect();
assert_eq!(
metadata.len(),
1,
"string_ends_with should have metadata entry"
);
assert_eq!(metadata[0].module, "string");
assert_eq!(metadata[0].shell_name, "rash_string_ends_with");
}
#[test]
fn test_GH148_glob_recognized() {
assert!(
is_stdlib_function("glob"),
"glob() should be recognized as stdlib function"
);
}
#[test]
fn test_GH148_glob_metadata() {
let metadata: Vec<&StdlibFunction> = STDLIB_FUNCTIONS
.iter()
.filter(|f| f.name == "glob")
.collect();
assert_eq!(metadata.len(), 1, "glob should have metadata entry");
assert_eq!(metadata[0].module, "fs", "glob should be in 'fs' module");
assert_eq!(
metadata[0].shell_name, "inline_glob",
"glob should use inline shell syntax"
);
}
#[test]
fn test_stdlib_array_join_metadata() {
let metadata: Vec<&StdlibFunction> = STDLIB_FUNCTIONS
.iter()
.filter(|f| f.name == "array_join")
.collect();
assert_eq!(metadata.len(), 1, "array_join should have metadata entry");
assert_eq!(
metadata[0].module, "array",
"array_join should be in 'array' module"
);
assert_eq!(
metadata[0].shell_name, "rash_array_join",
"array_join should use rash_ prefix"
);
}
}
#[test]
fn test_env_rejects_invalid_var_names() {
assert!(is_valid_var_name("HOME"));
assert!(is_valid_var_name("MY_VAR"));
assert!(is_valid_var_name("VAR123"));
assert!(is_valid_var_name("_PRIVATE"));
assert!(!is_valid_var_name("'; rm -rf /; #"));
assert!(!is_valid_var_name("VAR; echo hack"));
assert!(!is_valid_var_name("$(whoami)"));
assert!(!is_valid_var_name("VAR`id`"));
assert!(!is_valid_var_name("VAR$OTHER"));
assert!(!is_valid_var_name("VAR-NAME")); assert!(!is_valid_var_name("VAR.NAME")); }
#[test]
fn test_env_var_or_escapes_default() {
let safe_defaults = vec![
"/usr/local",
"hello world",
"/path/to/file",
"value-with-dash",
];
for default in safe_defaults {
assert!(
is_safe_default_value(default),
"Default '{}' should be considered safe",
default
);
}
let dangerous_defaults = vec![
"\"; rm -rf /; echo \"",
"value`whoami`",
"value$(id)",
"value;ls",
];
for default in dangerous_defaults {
assert!(
contains_injection_attempt(default),
"Default '{}' contains injection attempt and should be detected",
default
);
}
}
#[cfg(test)]
fn is_valid_var_name(name: &str) -> bool {
name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
}
#[cfg(test)]
fn is_safe_default_value(_value: &str) -> bool {
true }
#[cfg(test)]
fn contains_injection_attempt(value: &str) -> bool {
value.contains(';') || value.contains('`') || value.contains("$(") || value.contains("${")
}