use std::borrow::Cow;
use crate::grid_attr;
#[rustfmt::skip]
pub fn attributes_tostring(attr: grid_attr) -> Cow<'static, str> {
if attr.is_empty() {
return Cow::Borrowed("none");
}
Cow::Owned(format!(
"{}{}{}{}{}{}{}{}{}{}{}{}{}{}",
if attr.intersects(grid_attr::GRID_ATTR_CHARSET) { "acs," } else { "" },
if attr.intersects(grid_attr::GRID_ATTR_BRIGHT) { "bright," } else { "" },
if attr.intersects(grid_attr::GRID_ATTR_DIM ) { "dim," } else { "" },
if attr.intersects(grid_attr::GRID_ATTR_UNDERSCORE) { "underscore," } else { "" },
if attr.intersects(grid_attr::GRID_ATTR_BLINK) { "blink," } else { "" },
if attr.intersects(grid_attr::GRID_ATTR_REVERSE ) { "reverse," } else { "" },
if attr.intersects(grid_attr::GRID_ATTR_HIDDEN) { "hidden," } else { "" },
if attr.intersects(grid_attr::GRID_ATTR_ITALICS ) { "italics," } else { "" },
if attr.intersects(grid_attr::GRID_ATTR_STRIKETHROUGH) { "strikethrough," } else { "" },
if attr.intersects(grid_attr::GRID_ATTR_UNDERSCORE_2) { "double-underscore," } else { "" },
if attr.intersects(grid_attr::GRID_ATTR_UNDERSCORE_3) { "curly-underscore," } else { "" },
if attr.intersects(grid_attr::GRID_ATTR_UNDERSCORE_4) { "dotted-underscore," } else { "" },
if attr.intersects(grid_attr::GRID_ATTR_UNDERSCORE_5) { "dashed-underscore," } else { "" },
if attr.intersects(grid_attr::GRID_ATTR_OVERLINE) { "overline," } else { "" },
))
}
pub fn attributes_fromstring(str: &str) -> Result<grid_attr, ()> {
struct table_entry {
name: &'static str,
attr: grid_attr,
}
#[rustfmt::skip]
const TABLE: [table_entry; 15] = [
table_entry { name: "acs", attr: grid_attr::GRID_ATTR_CHARSET, },
table_entry { name: "bright", attr: grid_attr::GRID_ATTR_BRIGHT, },
table_entry { name: "bold", attr: grid_attr::GRID_ATTR_BRIGHT, },
table_entry { name: "dim", attr: grid_attr::GRID_ATTR_DIM, },
table_entry { name: "underscore", attr: grid_attr::GRID_ATTR_UNDERSCORE, },
table_entry { name: "blink", attr: grid_attr::GRID_ATTR_BLINK, },
table_entry { name: "reverse", attr: grid_attr::GRID_ATTR_REVERSE, },
table_entry { name: "hidden", attr: grid_attr::GRID_ATTR_HIDDEN, },
table_entry { name: "italics", attr: grid_attr::GRID_ATTR_ITALICS, },
table_entry { name: "strikethrough", attr: grid_attr::GRID_ATTR_STRIKETHROUGH, },
table_entry { name: "double-underscore", attr: grid_attr::GRID_ATTR_UNDERSCORE_2, },
table_entry { name: "curly-underscore", attr: grid_attr::GRID_ATTR_UNDERSCORE_3, },
table_entry { name: "dotted-underscore", attr: grid_attr::GRID_ATTR_UNDERSCORE_4, },
table_entry { name: "dashed-underscore", attr: grid_attr::GRID_ATTR_UNDERSCORE_5, },
table_entry { name: "overline", attr: grid_attr::GRID_ATTR_OVERLINE, },
];
fn is_delim(b: u8) -> bool {
matches!(b, b' ' | b',' | b'|')
}
let bytes = str.as_bytes();
if bytes.is_empty() || is_delim(bytes[0]) {
return Err(());
}
if is_delim(*bytes.last().unwrap()) {
return Err(());
}
if str.eq_ignore_ascii_case("default") || str.eq_ignore_ascii_case("none") {
return Ok(grid_attr::empty());
}
let mut attr = grid_attr::empty();
let mut pos = 0usize;
loop {
let start = pos;
while pos < bytes.len() && !is_delim(bytes[pos]) {
pos += 1;
}
let token = &str[start..pos];
let Some(i) = TABLE.iter().position(|t| token.eq_ignore_ascii_case(t.name)) else {
return Err(());
};
attr |= TABLE[i].attr;
while pos < bytes.len() && is_delim(bytes[pos]) {
pos += 1;
}
if pos >= bytes.len() {
break;
}
}
Ok(attr)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_attributes_tostring_none() {
assert_eq!(attributes_tostring(grid_attr::empty()).as_ref(), "none");
}
#[test]
fn test_attributes_tostring_single() {
assert_eq!(
attributes_tostring(grid_attr::GRID_ATTR_BRIGHT).as_ref(),
"bright,"
);
assert_eq!(
attributes_tostring(grid_attr::GRID_ATTR_UNDERSCORE).as_ref(),
"underscore,"
);
}
#[test]
fn test_attributes_tostring_combined() {
let attr = grid_attr::GRID_ATTR_BRIGHT | grid_attr::GRID_ATTR_UNDERSCORE;
assert_eq!(attributes_tostring(attr).as_ref(), "bright,underscore,");
}
#[test]
fn test_attributes_fromstring_single() {
assert_eq!(
attributes_fromstring("bold"),
Ok(grid_attr::GRID_ATTR_BRIGHT)
);
assert_eq!(
attributes_fromstring("bright"),
Ok(grid_attr::GRID_ATTR_BRIGHT)
);
assert_eq!(
attributes_fromstring("underscore"),
Ok(grid_attr::GRID_ATTR_UNDERSCORE)
);
}
#[test]
fn test_attributes_fromstring_combined() {
let expected = grid_attr::GRID_ATTR_BRIGHT | grid_attr::GRID_ATTR_UNDERSCORE;
assert_eq!(attributes_fromstring("bright,underscore"), Ok(expected));
assert_eq!(attributes_fromstring("bold underscore"), Ok(expected));
assert_eq!(attributes_fromstring("bright|underscore"), Ok(expected));
}
#[test]
fn test_attributes_fromstring_none_and_default() {
assert_eq!(attributes_fromstring("none"), Ok(grid_attr::empty()));
assert_eq!(attributes_fromstring("default"), Ok(grid_attr::empty()));
}
#[test]
fn test_attributes_fromstring_invalid() {
assert_eq!(attributes_fromstring(""), Err(()));
assert_eq!(attributes_fromstring("bogus"), Err(()));
assert_eq!(attributes_fromstring("bright,"), Err(()));
assert_eq!(attributes_fromstring(",bright"), Err(()));
}
#[test]
fn test_attributes_fromstring_all_table_entries() {
let cases: [(&str, grid_attr); 15] = [
("acs", grid_attr::GRID_ATTR_CHARSET),
("bright", grid_attr::GRID_ATTR_BRIGHT),
("bold", grid_attr::GRID_ATTR_BRIGHT),
("dim", grid_attr::GRID_ATTR_DIM),
("underscore", grid_attr::GRID_ATTR_UNDERSCORE),
("blink", grid_attr::GRID_ATTR_BLINK),
("reverse", grid_attr::GRID_ATTR_REVERSE),
("hidden", grid_attr::GRID_ATTR_HIDDEN),
("italics", grid_attr::GRID_ATTR_ITALICS),
("strikethrough", grid_attr::GRID_ATTR_STRIKETHROUGH),
("double-underscore", grid_attr::GRID_ATTR_UNDERSCORE_2),
("curly-underscore", grid_attr::GRID_ATTR_UNDERSCORE_3),
("dotted-underscore", grid_attr::GRID_ATTR_UNDERSCORE_4),
("dashed-underscore", grid_attr::GRID_ATTR_UNDERSCORE_5),
("overline", grid_attr::GRID_ATTR_OVERLINE),
];
for (name, flag) in cases {
assert_eq!(attributes_fromstring(name), Ok(flag), "name = {name}");
}
}
#[test]
fn test_attributes_tostring_all_labels() {
let cases: [(grid_attr, &str); 14] = [
(grid_attr::GRID_ATTR_CHARSET, "acs,"),
(grid_attr::GRID_ATTR_BRIGHT, "bright,"),
(grid_attr::GRID_ATTR_DIM, "dim,"),
(grid_attr::GRID_ATTR_UNDERSCORE, "underscore,"),
(grid_attr::GRID_ATTR_BLINK, "blink,"),
(grid_attr::GRID_ATTR_REVERSE, "reverse,"),
(grid_attr::GRID_ATTR_HIDDEN, "hidden,"),
(grid_attr::GRID_ATTR_ITALICS, "italics,"),
(grid_attr::GRID_ATTR_STRIKETHROUGH, "strikethrough,"),
(grid_attr::GRID_ATTR_UNDERSCORE_2, "double-underscore,"),
(grid_attr::GRID_ATTR_UNDERSCORE_3, "curly-underscore,"),
(grid_attr::GRID_ATTR_UNDERSCORE_4, "dotted-underscore,"),
(grid_attr::GRID_ATTR_UNDERSCORE_5, "dashed-underscore,"),
(grid_attr::GRID_ATTR_OVERLINE, "overline,"),
];
for (flag, label) in cases {
assert_eq!(attributes_tostring(flag).as_ref(), label, "flag = {flag:?}");
}
}
#[test]
fn test_attributes_tostring_all_flags_order() {
let all = grid_attr::all();
assert_eq!(
attributes_tostring(all).as_ref(),
"acs,bright,dim,underscore,blink,reverse,hidden,italics,\
strikethrough,double-underscore,curly-underscore,\
dotted-underscore,dashed-underscore,overline,"
);
}
#[test]
fn test_attributes_fromstring_case_insensitive() {
assert_eq!(
attributes_fromstring("BRIGHT"),
Ok(grid_attr::GRID_ATTR_BRIGHT)
);
assert_eq!(
attributes_fromstring("Bold"),
Ok(grid_attr::GRID_ATTR_BRIGHT)
);
assert_eq!(
attributes_fromstring("OverLine"),
Ok(grid_attr::GRID_ATTR_OVERLINE)
);
assert_eq!(
attributes_fromstring("Double-Underscore"),
Ok(grid_attr::GRID_ATTR_UNDERSCORE_2)
);
}
#[test]
fn test_attributes_fromstring_mixed_delimiters() {
let expected = grid_attr::GRID_ATTR_CHARSET
| grid_attr::GRID_ATTR_DIM
| grid_attr::GRID_ATTR_BLINK
| grid_attr::GRID_ATTR_REVERSE;
assert_eq!(
attributes_fromstring("acs,dim|blink reverse"),
Ok(expected)
);
}
#[test]
fn test_attributes_fromstring_consecutive_delimiters_collapsed() {
assert_eq!(
attributes_fromstring("bright,,underscore"),
Ok(grid_attr::GRID_ATTR_BRIGHT | grid_attr::GRID_ATTR_UNDERSCORE)
);
assert_eq!(
attributes_fromstring("bright,underscore"),
attributes_fromstring("bright,,underscore")
);
assert_eq!(
attributes_fromstring("bright dim"),
Ok(grid_attr::GRID_ATTR_BRIGHT | grid_attr::GRID_ATTR_DIM)
);
assert_eq!(
attributes_fromstring("bright, |dim"),
Ok(grid_attr::GRID_ATTR_BRIGHT | grid_attr::GRID_ATTR_DIM)
);
}
#[test]
fn test_attributes_fromstring_delimiter_edges() {
assert_eq!(attributes_fromstring(","), Err(()));
assert_eq!(attributes_fromstring(" "), Err(()));
assert_eq!(attributes_fromstring("|"), Err(()));
assert_eq!(attributes_fromstring("bright dim "), Err(())); assert_eq!(attributes_fromstring("bright|dim|"), Err(())); assert_eq!(attributes_fromstring("|bright"), Err(())); assert_eq!(attributes_fromstring(" bright"), Err(())); }
#[test]
fn test_attributes_fromstring_one_bad_token_fails_all() {
assert_eq!(attributes_fromstring("bright,bogus,dim"), Err(()));
assert_eq!(attributes_fromstring("underscore reverse xyzzy"), Err(()));
}
#[test]
fn test_attributes_fromstring_repeat_is_idempotent() {
assert_eq!(
attributes_fromstring("bright,bright"),
Ok(grid_attr::GRID_ATTR_BRIGHT)
);
assert_eq!(
attributes_fromstring("dim|dim|dim"),
Ok(grid_attr::GRID_ATTR_DIM)
);
}
#[test]
fn test_attributes_tostring_fromstring_roundtrip() {
for flag in [
grid_attr::GRID_ATTR_CHARSET,
grid_attr::GRID_ATTR_BRIGHT,
grid_attr::GRID_ATTR_DIM,
grid_attr::GRID_ATTR_UNDERSCORE,
grid_attr::GRID_ATTR_BLINK,
grid_attr::GRID_ATTR_REVERSE,
grid_attr::GRID_ATTR_HIDDEN,
grid_attr::GRID_ATTR_ITALICS,
grid_attr::GRID_ATTR_STRIKETHROUGH,
grid_attr::GRID_ATTR_UNDERSCORE_2,
grid_attr::GRID_ATTR_UNDERSCORE_3,
grid_attr::GRID_ATTR_UNDERSCORE_4,
grid_attr::GRID_ATTR_UNDERSCORE_5,
grid_attr::GRID_ATTR_OVERLINE,
] {
let s = attributes_tostring(flag);
let trimmed = s.trim_end_matches(',');
assert_eq!(attributes_fromstring(trimmed), Ok(flag), "flag = {flag:?}");
}
}
#[test]
fn test_attributes_multiflag_roundtrip() {
let attr = grid_attr::GRID_ATTR_BRIGHT
| grid_attr::GRID_ATTR_UNDERSCORE
| grid_attr::GRID_ATTR_OVERLINE;
let s = attributes_tostring(attr);
assert_eq!(s.as_ref(), "bright,underscore,overline,");
let trimmed = s.trim_end_matches(',');
assert_eq!(attributes_fromstring(trimmed), Ok(attr));
}
#[test]
fn test_attributes_fromstring_default_none_case() {
for s in ["none", "None", "NONE", "nOnE", "default", "Default", "DEFAULT"] {
assert_eq!(attributes_fromstring(s), Ok(grid_attr::empty()), "s = {s}");
}
}
#[test]
fn test_attributes_fromstring_none_as_token_rejected() {
assert_eq!(attributes_fromstring("none,bright"), Err(()));
assert_eq!(attributes_fromstring("bright,none"), Err(()));
assert_eq!(attributes_fromstring("default|dim"), Err(()));
}
#[test]
fn test_attributes_fromstring_all_flags_roundtrip() {
let all = grid_attr::all();
let s = attributes_tostring(all);
let trimmed = s.trim_end_matches(',');
assert_eq!(attributes_fromstring(trimmed), Ok(all));
}
#[test]
fn test_attributes_acs_only() {
assert_eq!(attributes_fromstring("acs"), Ok(grid_attr::GRID_ATTR_CHARSET));
assert_eq!(
attributes_tostring(grid_attr::GRID_ATTR_CHARSET).as_ref(),
"acs,"
);
}
}