use super::basic_parser::{Many, Token};
#[cfg_attr(test, derive(Clone, PartialEq, Eq))]
pub struct Username(pub String);
impl Token for Username {
fn construct(text: String) -> Result<Self, String> {
Ok(Username(text))
}
fn accept(c: char) -> bool {
c.is_ascii_alphanumeric() || ".-_".contains(c)
}
fn accept_1st(c: char) -> bool {
c != '_' && Self::accept(c)
}
}
impl Many for Username {}
pub struct Digits(pub u32);
impl Token for Digits {
const MAX_LEN: usize = 9;
fn construct(s: String) -> Result<Self, String> {
Ok(Digits(s.parse().unwrap()))
}
fn accept(c: char) -> bool {
c.is_ascii_digit()
}
}
pub struct Numeric(pub String);
impl Token for Numeric {
const MAX_LEN: usize = 38;
fn construct(s: String) -> Result<Self, String> {
Ok(Numeric(s))
}
fn accept(c: char) -> bool {
c.is_ascii_hexdigit()
}
}
pub struct Hostname(pub String);
impl std::ops::Deref for Hostname {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Token for Hostname {
fn construct(text: String) -> Result<Self, String> {
Ok(Hostname(text))
}
fn accept(c: char) -> bool {
c.is_ascii_alphanumeric() || ".-_".contains(c)
}
}
impl Many for Hostname {}
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
pub enum Meta<T> {
All,
Only(T),
Alias(String),
}
impl<T: Token> Token for Meta<T> {
fn construct(s: String) -> Result<Self, String> {
Ok(if s == "ALL" {
Meta::All
} else if s.starts_with(AliasName::accept_1st) && s.chars().skip(1).all(AliasName::accept) {
Meta::Alias(s)
} else {
Meta::Only(T::construct(s)?)
})
}
const MAX_LEN: usize = T::MAX_LEN;
fn accept(c: char) -> bool {
T::accept(c) || c.is_uppercase()
}
fn accept_1st(c: char) -> bool {
T::accept_1st(c) || c.is_uppercase()
}
const ESCAPE: char = T::ESCAPE;
fn escaped(c: char) -> bool {
T::escaped(c)
}
}
impl<T: Many> Many for Meta<T> {
const SEP: char = T::SEP;
const LIMIT: usize = T::LIMIT;
}
pub struct AliasName(pub String);
impl Token for AliasName {
fn construct(s: String) -> Result<Self, String> {
Ok(AliasName(s))
}
fn accept_1st(c: char) -> bool {
c.is_ascii_uppercase() || c.is_ascii_digit()
}
fn accept(c: char) -> bool {
Self::accept_1st(c) || c == '_'
}
}
pub type Command = (glob::Pattern, Option<Box<[String]>>);
impl Token for Command {
const MAX_LEN: usize = 1024;
fn construct(s: String) -> Result<Self, String> {
let cvt_err = |pat: Result<_, glob::PatternError>| {
pat.map_err(|err| format!("wildcard pattern error {err}"))
};
let mut cmd_iter = s.split_whitespace();
let mut cmd = cmd_iter.next().unwrap().to_string();
let mut args = cmd_iter.map(String::from).collect::<Vec<String>>();
let argpat = if args.is_empty() {
None
} else {
if args.last().map(|x| -> &str { x }) == Some("\"\"") {
args.pop();
}
Some(args.into_boxed_slice())
};
let is_dir = cmd.ends_with('/') && {
cmd.pop();
true
};
if let Ok(real_cmd) = std::fs::canonicalize(&cmd) {
cmd = real_cmd
.to_str()
.ok_or("non-UTF8 characters in filesystem")?
.to_string();
}
if is_dir {
cmd.push_str("/*");
}
Ok((cvt_err(glob::Pattern::new(&cmd))?, argpat))
}
fn accept_1st(c: char) -> bool {
c == '/' || c == 's'
}
fn accept(c: char) -> bool {
!Self::escaped(c) && !c.is_control()
}
const ESCAPE: char = '\\';
fn escaped(c: char) -> bool {
matches!(c, '\\' | ',' | ':' | '=' | '#')
}
}
impl Many for Command {}
pub struct DefaultName(pub String);
impl Token for DefaultName {
fn construct(text: String) -> Result<Self, String> {
Ok(DefaultName(text))
}
fn accept(c: char) -> bool {
c.is_ascii_alphanumeric() || c == '_'
}
}
pub struct EnvVar(pub String);
impl Token for EnvVar {
fn construct(text: String) -> Result<Self, String> {
Ok(EnvVar(text))
}
fn accept(c: char) -> bool {
!c.is_control() && !c.is_whitespace() && !Self::escaped(c)
}
const ESCAPE: char = '\\';
fn escaped(c: char) -> bool {
matches!(c, '\\' | '=' | '#' | '"')
}
}
pub struct QuotedText(pub String);
impl Token for QuotedText {
const MAX_LEN: usize = 1024;
fn construct(s: String) -> Result<Self, String> {
Ok(QuotedText(s))
}
fn accept(c: char) -> bool {
!Self::escaped(c)
}
const ESCAPE: char = '\\';
fn escaped(c: char) -> bool {
matches!(c, '\\' | '"') || c.is_control()
}
}
pub struct IncludePath(pub String);
impl Token for IncludePath {
const MAX_LEN: usize = 1024;
fn construct(s: String) -> Result<Self, String> {
Ok(IncludePath(s))
}
fn accept(c: char) -> bool {
!c.is_control() && !Self::escaped(c)
}
const ESCAPE: char = '\\';
fn escaped(c: char) -> bool {
matches!(c, '\\' | '"' | ' ')
}
}
pub struct StringParameter(pub String);
impl Token for StringParameter {
const MAX_LEN: usize = QuotedText::MAX_LEN;
fn construct(s: String) -> Result<Self, String> {
Ok(StringParameter(s))
}
fn accept(c: char) -> bool {
!c.is_control() && !Self::escaped(c)
}
const ESCAPE: char = '\\';
fn escaped(c: char) -> bool {
matches!(c, '\\' | '"' | ' ' | '#' | ',')
}
}
#[derive(Clone)]
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
pub enum ChDir {
Path(std::path::PathBuf),
Any,
}
impl Token for ChDir {
const MAX_LEN: usize = 1024;
fn construct(s: String) -> Result<Self, String> {
if s == "*" {
Ok(ChDir::Any)
} else if s.contains('*') {
Err("path cannot contain '*'".to_string())
} else {
Ok(ChDir::Path(s.into()))
}
}
fn accept(c: char) -> bool {
!c.is_control() && !Self::escaped(c)
}
fn accept_1st(c: char) -> bool {
"~/*".contains(c)
}
const ESCAPE: char = '\\';
fn escaped(c: char) -> bool {
matches!(c, '\\' | '"' | ' ')
}
}