///|
/// The rule of an application name that a text breaks.
pub enum AppNameRule {
/// The name is empty.
Empty
/// The first character is not an ASCII letter.
FirstNotAsciiLetter
/// The name holds a character that is not an ASCII letter, an ASCII digit,
/// `-` or `_`.
CharNotAllowed(Char)
} derive(Eq, Debug)
///|
pub extend AppNameRule with Eq::{equal, not_equal}
///|
pub extend AppNameRule with @debug.Debug::{to_repr}
///|
/// A text that is not a valid application name. Callers can match the cases,
/// but only `AppName::parse` makes them.
pub suberror AppNameError {
/// The text `text` breaks the rule `rule`.
InvalidAppName(text~ : String, rule~ : AppNameRule)
} derive(Debug)
///|
pub extend AppNameError with @debug.Debug::{to_repr}
///|
/// Text for people that describes the fault.
pub fn AppNameError::message(self : AppNameError) -> String {
let InvalidAppName(text~, rule~) = self
let reason = match rule {
Empty => "the name is empty"
FirstNotAsciiLetter => "the name must start with an ASCII letter"
CharNotAllowed(char) =>
"the character \{char.to_string().escape()} is not allowed; use ASCII letters, digits, '-' and '_'"
}
"Invalid application name \{text.escape()}: \{reason}"
}
///|
/// The name of an application. It starts with an ASCII letter, and holds only
/// ASCII letters, digits, `-` and `_`. So it is safe in file and directory
/// names, and it gives a valid environment-variable prefix.
pub struct AppName {
priv text : String
} derive(Eq, Hash, Debug)
///|
pub extend AppName with Eq::{equal, not_equal}
///|
pub extend AppName with Hash::{hash, hash_combine}
///|
pub extend AppName with @debug.Debug::{to_repr}
///|
/// Parse an application name, for example `acme` or `my-tool`.
pub fn AppName::parse(text : String) -> AppName raise AppNameError {
guard text.iter().head() is Some(first) else {
raise InvalidAppName(text~, rule=Empty)
}
guard is_ascii_letter(first) else {
raise InvalidAppName(text~, rule=FirstNotAsciiLetter)
}
for char in text {
guard is_name_char(char) else {
raise InvalidAppName(text~, rule=CharNotAllowed(char))
}
}
{ text, }
}
///|
/// The name as text, which `AppName::parse` reads back to an equal name.
pub impl Show for AppName with output(self, logger) {
logger.write_string(self.text)
}
///|
pub extend AppName with Show::{output, to_string}
///|
fn is_ascii_letter(char : Char) -> Bool {
(char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z')
}
///|
/// An ASCII letter, an ASCII digit, `-` or `_`.
fn is_name_char(char : Char) -> Bool {
is_ascii_letter(char) || (char >= '0' && char <= '9') || char == '-' || char == '_'
}