use anyhow::{bail, Result};
use regex::Regex;
use std::path::Path;
pub fn is_non_ascii_name(name: &str) -> bool {
name.chars().any(|ch| ch > '\x7f')
}
pub fn is_keyword(name: &str) -> bool {
[
"Self", "abstract", "as", "await", "become", "box", "break", "const", "continue", "dep",
"do", "dyn", "else", "enum", "extern", "false", "final", "fn", "for", "if", "impl", "in",
"let", "loop", "macro", "match", "move", "mut", "override", "priv", "pub", "ref", "return",
"self", "static", "struct", "super", "trait", "true", "try", "type", "typeof", "unsafe",
"unsized", "use", "virtual", "where", "while", "yield",
]
.contains(&name)
}
pub fn is_windows_reserved(name: &str) -> bool {
[
"con", "prn", "aux", "nul", "com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8",
"com9", "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9",
]
.contains(&name.to_ascii_lowercase().as_str())
}
pub fn is_conflicting_suffix(name: &str) -> bool {
["alloc", "proc_macro", "proc-macro"].contains(&name)
}
pub fn is_conflicting_artifact_name(name: &str) -> bool {
["deps", "examples", "build", "incremental"].contains(&name)
}
pub fn contains_invalid_char(name: &str, use_case: &str) -> Result<()> {
let mut chars = name.chars();
if let Some(ch) = chars.next() {
if ch.is_ascii_digit() {
bail!(
"the name `{name}` cannot be used as a {use_case}, \
the name cannot start with a digit"
);
}
if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_') {
bail!(
"invalid character `{ch}` in {use_case}: `{name}`, \
the first character must be a Unicode XID start character \
(most letters or `_`)"
);
}
}
for ch in chars {
if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-') {
bail!(
"invalid character `{ch}` in {use_case}: `{name}`, \
characters must be Unicode XID characters \
(numbers, `-`, `_`, or most letters)"
);
}
}
if name.is_empty() {
bail!(
"{use_case} cannot be left empty, \
please use a valid name"
);
}
Ok(())
}
pub fn is_windows_reserved_path(path: &Path) -> bool {
path.iter()
.filter_map(|component| component.to_str())
.any(|component| {
let stem = component.split('.').next().unwrap();
is_windows_reserved(stem)
})
}
pub fn is_glob_pattern<T: AsRef<str>>(name: T) -> bool {
name.as_ref().contains(&['*', '?', '[', ']'][..])
}
pub fn is_valid_project_name_format(name: &str) -> Result<()> {
let re = Regex::new(r"^([a-zA-Z]([a-zA-Z0-9-_]+)|)$").unwrap();
if !re.is_match(name) {
bail!(
"'{name}' is not a valid name for a project. \n\
The name may use letters, numbers, hyphens, and underscores, and must start with a letter."
);
}
Ok(())
}
#[test]
fn test_invalid_char() {
assert_eq!(
contains_invalid_char("test#proj", "package name").map_err(|e| e.to_string()),
std::result::Result::Err(
"invalid character `#` in package name: `test#proj`, \
characters must be Unicode XID characters \
(numbers, `-`, `_`, or most letters)"
.into()
)
);
assert_eq!(
contains_invalid_char("test proj", "package name").map_err(|e| e.to_string()),
std::result::Result::Err(
"invalid character ` ` in package name: `test proj`, \
characters must be Unicode XID characters \
(numbers, `-`, `_`, or most letters)"
.into()
)
);
assert_eq!(
contains_invalid_char("", "package name").map_err(|e| e.to_string()),
std::result::Result::Err(
"package name cannot be left empty, \
please use a valid name"
.into()
)
);
assert!(matches!(
contains_invalid_char("test_proj", "package name"),
std::result::Result::Ok(())
));
}
#[test]
fn test_is_valid_project_name_format() {
let assert_valid = |name: &str| {
is_valid_project_name_format(name).expect("this should pass");
};
let assert_invalid = |name: &str, expected_error: &str| {
assert_eq!(
is_valid_project_name_format(name).map_err(|e| e.to_string()),
Err(expected_error.into())
);
};
let format_error_message = |name: &str| -> String {
format!(
"'{}' is not a valid name for a project. \n\
The name may use letters, numbers, hyphens, and underscores, and must start with a letter.",
name
)
};
assert_valid("mock_project_name");
assert_valid("mock_project_name123");
assert_valid("mock_project_name-123-_");
assert_invalid("1mock_project", &format_error_message("1mock_project"));
assert_invalid("mock_.project", &format_error_message("mock_.project"));
assert_invalid("mock_/project", &format_error_message("mock_/project"));
}