use crate::node::{Comment, Gui, GuiDisplay, GuiRole, Inspect};
use gantz_format::sexpr::quote;
use gantz_format::{Datum, FormatError, Sugar, SugarArgs, node_datum};
use gantz_nodetag::NodeTag;
#[derive(Clone, Copy, Debug, Default)]
pub struct EguiSugar;
const KEYWORD_TAG: &[(&str, &str)] = &[
("inspect", Inspect::TAG),
("comment", Comment::TAG),
("gui", Gui::TAG),
];
fn tag_for_keyword(kw: &str) -> Option<&'static str> {
KEYWORD_TAG
.iter()
.find(|(k, _)| *k == kw)
.map(|&(_, tag)| tag)
}
fn keyword_for_tag(tag: &str) -> Option<&'static str> {
KEYWORD_TAG
.iter()
.find(|(_, t)| *t == tag)
.map(|&(kw, _)| kw)
}
impl Sugar for EguiSugar {
fn read_spec(&self, head: &str, args: SugarArgs<'_>) -> Result<Option<Datum>, FormatError> {
let datum = match head {
"comment" => comment_spec(args)?,
"gui" => gui_spec(args)?,
_ => return Ok(None),
};
Ok(Some(datum))
}
fn read_bare(&self, keyword: &str) -> Option<Datum> {
tag_for_keyword(keyword).map(|tag| node_datum(tag, vec![]))
}
fn write_spec(&self, tag: &str, node: &Datum) -> Option<String> {
match tag {
"Comment" => Some(write_comment(node)),
"Gui" => Some(write_gui(node)),
other => keyword_for_tag(other).map(str::to_string),
}
}
fn keyword_for_tag(&self, tag: &str) -> Option<&str> {
keyword_for_tag(tag)
}
}
fn comment_spec(args: SugarArgs<'_>) -> Result<Datum, FormatError> {
let text = args
.str_at(0)
.ok_or_else(|| FormatError::malformed("comment requires text"))?;
let [w, h] = match (args.int_at(1)?, args.int_at(2)?) {
(Some(w), Some(h)) => [w.max(0) as u64, h.max(0) as u64],
_ => [100, 40],
};
Ok(node_datum(
"Comment",
vec![
("text", Datum::Str(text)),
("size", Datum::Seq(vec![Datum::U64(w), Datum::U64(h)])),
],
))
}
fn write_comment(node: &Datum) -> String {
let text = node.get("text").and_then(Datum::as_str).unwrap_or("");
let (w, h) = node
.get("size")
.and_then(Datum::as_seq)
.and_then(|a| Some((a.first()?.as_i64()?, a.get(1)?.as_i64()?)))
.unwrap_or((100, 40));
format!("(comment {} {w} {h})", quote(text))
}
fn gui_spec(args: SugarArgs<'_>) -> Result<Datum, FormatError> {
let role = match args.symbol_at(0) {
Some(s) => GuiRole::from_str(&s)
.ok_or_else(|| args.malformed_at(0, format!("unknown gui role `{s}`")))?,
None => GuiRole::default(),
};
let display = match args.keyword_symbol("display")? {
Some(s) => GuiDisplay::from_str(&s)
.ok_or_else(|| FormatError::malformed(format!("unknown gui display `{s}`")))?,
None => GuiDisplay::default(),
};
Ok(node_datum(
"Gui",
vec![
("role", Datum::Str(role.as_str().to_string())),
("display", Datum::Str(display.as_str().to_string())),
],
))
}
fn write_gui(node: &Datum) -> String {
let default_role = GuiRole::default().as_str();
let default_display = GuiDisplay::default().as_str();
let role = node
.get("role")
.and_then(Datum::as_str)
.unwrap_or(default_role);
let display = node
.get("display")
.and_then(Datum::as_str)
.unwrap_or(default_display);
match (role == default_role, display == default_display) {
(true, true) => "gui".to_string(),
(_, true) => format!("(gui {role})"),
(_, false) => format!("(gui {role} #:display {display})"),
}
}
#[cfg(test)]
mod tests {
use super::*;
use gantz_format::sexpr;
fn read_spec(text: &str) -> Option<Datum> {
let exprs = sexpr::read(text).expect("read");
let args = sexpr::list_args(&exprs[0]).expect("list");
let head = sexpr::as_symbol(&args[0]).expect("head");
EguiSugar
.read_spec(&head, SugarArgs::new(&args[1..], text))
.expect("read_spec")
}
#[test]
fn inspect_round_trips() {
let bare = EguiSugar.read_bare("inspect").expect("bare inspect");
assert_eq!(bare.get("type").and_then(Datum::as_str), Some("Inspect"));
assert_eq!(
EguiSugar.write_spec("Inspect", &bare).as_deref(),
Some("inspect")
);
}
#[test]
fn gui_round_trips() {
let s = EguiSugar;
let bare = s.read_bare("gui").expect("bare gui");
assert_eq!(bare.get("type").and_then(Datum::as_str), Some("Gui"));
assert_eq!(s.write_spec("Gui", &bare).as_deref(), Some("gui"));
let view = read_spec("(gui view)").expect("gui view");
assert_eq!(view.get("role").and_then(Datum::as_str), Some("view"));
assert_eq!(s.write_spec("Gui", &view).as_deref(), Some("(gui view)"));
let disp = read_spec("(gui body #:display compact)").expect("gui display");
assert_eq!(disp.get("role").and_then(Datum::as_str), Some("body"));
assert_eq!(disp.get("display").and_then(Datum::as_str), Some("compact"));
assert_eq!(
s.write_spec("Gui", &disp).as_deref(),
Some("(gui body #:display compact)"),
);
let only_disp = read_spec("(gui #:display label)").expect("gui only display");
assert_eq!(only_disp.get("role").and_then(Datum::as_str), Some("body"));
assert_eq!(
s.write_spec("Gui", &only_disp).as_deref(),
Some("(gui body #:display label)"),
);
}
#[test]
fn comment_round_trips() {
let s = EguiSugar;
let d = read_spec(r#"(comment "hi")"#).expect("comment");
assert_eq!(d.get("text").and_then(Datum::as_str), Some("hi"));
assert_eq!(
s.write_spec("Comment", &d).as_deref(),
Some(r#"(comment "hi" 100 40)"#),
);
let sized = read_spec(r#"(comment "note" 220 80)"#).expect("sized");
assert_eq!(
s.write_spec("Comment", &sized).as_deref(),
Some(r#"(comment "note" 220 80)"#),
);
}
}