use super::controls::Slot;
use crate::render::escape;
#[derive(Clone, Copy, PartialEq, Eq)]
enum Tok {
Digit,
Letter,
Upper,
Any,
Lit(char),
}
fn tokens(pattern: &str) -> Vec<Tok> {
let mut out = Vec::with_capacity(pattern.len());
let mut escaped = false;
for c in pattern.chars() {
if escaped {
out.push(Tok::Lit(c));
escaped = false;
continue;
}
out.push(match c {
'\\' => {
escaped = true;
continue;
}
'9' => Tok::Digit,
'a' => Tok::Letter,
'A' => Tok::Upper,
'*' => Tok::Any,
c => Tok::Lit(c),
});
}
out
}
impl Tok {
fn takes(self, c: char) -> bool {
match self {
Tok::Digit => c.is_ascii_digit(),
Tok::Letter => c.is_alphabetic(),
Tok::Upper => c.is_alphabetic() && !c.is_lowercase(),
Tok::Any => c.is_alphanumeric(),
Tok::Lit(_) => false,
}
}
}
pub fn mask_fits(pattern: &str, raw: &str) -> bool {
let mut slots = tokens(pattern)
.into_iter()
.filter(|t| !matches!(t, Tok::Lit(_)));
let mut chars = raw.chars();
loop {
match (slots.next(), chars.next()) {
(None, None) => return true,
(Some(t), Some(c)) if t.takes(c) => {}
_ => return false,
}
}
}
pub fn mask_raw(pattern: &str, typed: &str) -> String {
let toks = tokens(pattern);
let mut raw = String::with_capacity(typed.len());
let mut at = 0usize;
'chars: for c in typed.trim().chars() {
while let Some(Tok::Lit(l)) = toks.get(at) {
at += 1;
if *l == c {
continue 'chars;
}
}
match toks.get(at) {
Some(Tok::Upper) if c.is_alphabetic() => raw.extend(c.to_uppercase()),
Some(t) if t.takes(c) => raw.push(c),
_ if !c.is_alphanumeric() => continue,
_ => return typed.to_string(),
}
at += 1;
}
raw
}
pub fn mask_example(pattern: &str) -> String {
tokens(pattern)
.into_iter()
.map(|t| if let Tok::Lit(c) = t { c } else { '_' })
.collect()
}
pub(crate) fn render_mask(out: &mut String, s: &Slot, pattern: &str) {
let text = match s.value {
crate::value::Value::Text(t) => t.as_str(),
_ => "",
};
let numeric = tokens(pattern)
.iter()
.all(|t| matches!(t, Tok::Digit | Tok::Lit(_)));
let example = mask_example(pattern);
out.push_str(s.label);
out.push_str(&format!(
r#"<div class="z-mask" data-zeyn-mask="{}" data-example="{}"><input type="text" class="z-mask-value" dir="ltr" inputmode="{}" autocomplete="off" spellcheck="false" {} value="{}"></div>"#,
escape(pattern),
escape(&example),
if numeric { "numeric" } else { "text" },
s.common,
escape(text)
));
}