substrate_manager/util/
restricted_names.rs

1//! Helpers for validating and checking names like package and crate names.
2
3use anyhow::bail;
4use std::path::Path;
5
6use crate::util::SubstrateResult;
7
8/// Returns `true` if the name contains non-ASCII characters.
9pub fn is_non_ascii_name(name: &str) -> bool {
10    name.chars().any(|ch| ch > '\x7f')
11}
12
13/// A Rust keyword.
14pub fn is_keyword(name: &str) -> bool {
15    // See https://doc.rust-lang.org/reference/keywords.html
16    [
17        "Self", "abstract", "as", "async", "await", "become", "box", "break", "const", "continue",
18        "crate", "do", "dyn", "else", "enum", "extern", "false", "final", "fn", "for", "if",
19        "impl", "in", "let", "loop", "macro", "match", "mod", "move", "mut", "override", "priv",
20        "pub", "ref", "return", "self", "static", "struct", "super", "trait", "true", "try",
21        "type", "typeof", "unsafe", "unsized", "use", "virtual", "where", "while", "yield",
22    ]
23    .contains(&name)
24}
25
26/// These names cannot be used on Windows, even with an extension.
27pub fn is_windows_reserved(name: &str) -> bool {
28    [
29        "con", "prn", "aux", "nul", "com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8",
30        "com9", "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9",
31    ]
32    .contains(&name.to_ascii_lowercase().as_str())
33}
34
35/// An artifact with this name will conflict with one of Cargo's build directories.
36pub fn is_conflicting_artifact_name(name: &str) -> bool {
37    ["deps", "examples", "build", "incremental"].contains(&name)
38}
39
40/// Check the base requirements for a package name.
41///
42/// This can be used for other things than package names, to enforce some
43/// level of sanity. Note that package names have other restrictions
44/// elsewhere. `cargo new` has a few restrictions, such as checking for
45/// reserved names. crates.io has even more restrictions.
46pub fn validate_package_name(name: &str, what: &str, help: &str) -> SubstrateResult<()> {
47    let mut chars = name.chars();
48    if let Some(ch) = chars.next() {
49        if ch.is_digit(10) {
50            // A specific error for a potentially common case.
51            bail!(
52                "the name `{}` cannot be used as a {}, \
53                the name cannot start with a digit{}",
54                name,
55                what,
56                help
57            );
58        }
59        if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_') {
60            bail!(
61                "invalid character `{}` in {}: `{}`, \
62                the first character must be a Unicode XID start character \
63                (most letters or `_`){}",
64                ch,
65                what,
66                name,
67                help
68            );
69        }
70    }
71    for ch in chars {
72        if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-') {
73            bail!(
74                "invalid character `{}` in {}: `{}`, \
75                characters must be Unicode XID characters \
76                (numbers, `-`, `_`, or most letters){}",
77                ch,
78                what,
79                name,
80                help
81            );
82        }
83    }
84    Ok(())
85}
86
87/// Check the entire path for names reserved in Windows.
88pub fn is_windows_reserved_path(path: &Path) -> bool {
89    path.iter()
90        .filter_map(|component| component.to_str())
91        .any(|component| {
92            let stem = component.split('.').next().unwrap();
93            is_windows_reserved(stem)
94        })
95}
96
97/// Returns `true` if the name contains any glob pattern wildcards.
98pub fn is_glob_pattern<T: AsRef<str>>(name: T) -> bool {
99    name.as_ref().contains(&['*', '?', '[', ']'][..])
100}