use crate::span::Span;
use crate::syntax::ast::{Child, Decl, Node, TextNode, Value};
const FIELDS: &[(&str, &str)] = &[
("title", "Title"),
("drawing-number", "Dwg No."),
("revision", "Rev"),
("sheet-number", "Sheet"),
("date", "Date"),
("author", "Drawn"),
("approved", "Approved"),
("department", "Dept"),
("reference", "Reference"),
("document-type", "Type"),
("status", "Status"),
];
const COLUMNS: usize = 3;
pub(super) fn has_fields(style: &[Decl]) -> bool {
FIELDS.iter().any(|(k, _)| field_value(style, k).is_some())
}
pub(super) fn expand_fields(style: &mut Vec<Decl>, span: Span) -> Vec<Node> {
let cells: Vec<Node> = FIELDS
.iter()
.filter_map(|(key, cap)| {
field_value(style, key).map(|v| {
let cols = (*key == "title").then_some(COLUMNS);
field_cell(cap, &v, cols, span)
})
})
.collect();
style.retain(|d| !FIELDS.iter().any(|(k, _)| *k == d.name));
if !style.iter().any(|d| d.name == "columns") {
style.push(Decl {
name: "columns".into(),
groups: vec![vec![Value::Ident("auto".into())]; COLUMNS],
span,
});
}
cells
}
fn field_value(style: &[Decl], key: &str) -> Option<String> {
match style
.iter()
.find(|d| d.name == key)
.and_then(|d| d.groups.first())
.and_then(|g| g.first())
{
Some(Value::String(s)) => Some(s.clone()),
_ => None,
}
}
fn field_cell(caption: &str, value: &str, span_cols: Option<usize>, span: Span) -> Node {
let mut style = vec![Decl {
name: "padding".into(),
groups: vec![vec![Value::Number(2.0), Value::Number(6.0)]],
span,
}];
if let Some(cols) = span_cols {
style.push(Decl {
name: "span".into(),
groups: vec![vec![Value::Number(cols as f64), Value::Number(1.0)]],
span,
});
}
Node {
id: None,
ty: Some("cell".into()),
label: None,
classes: Vec::new(),
style,
style_span: None,
children: vec![
Child::Box(caption_field(caption, span)),
Child::Text(text(value, span)),
],
links: Vec::new(),
span,
}
}
fn caption_field(s: &str, span: Span) -> Node {
Node {
id: None,
ty: Some("field".into()),
label: Some(text(s, span)),
classes: Vec::new(),
style: Vec::new(),
style_span: None,
children: Vec::new(),
links: Vec::new(),
span,
}
}
fn text(s: &str, span: Span) -> TextNode {
TextNode {
text: s.to_string(),
style: Vec::new(),
style_span: None,
span,
}
}
#[cfg(test)]
mod tests {
use crate::syntax::ast::{Child, Node};
fn title_block(src: &str) -> Node {
let toks = crate::lexer::lex(src).expect("lex");
let file = crate::syntax::parser::parse(src, &toks).expect("parse");
let lowered = crate::desugar::desugar(&file).expect("desugar");
fn find(children: &[Child]) -> Option<Node> {
for c in children {
if let Child::Box(n) = c {
if n.classes.iter().any(|k| k == "lini-title-block") {
return Some(n.clone());
}
if let Some(hit) = find(&n.children) {
return Some(hit);
}
}
}
None
}
find(&lowered.instances).expect("a title block")
}
fn cells(n: &Node) -> Vec<&Node> {
n.children
.iter()
.filter_map(|c| match c {
Child::Box(b) if b.classes.iter().any(|k| k == "lini-cell") => Some(b),
_ => None,
})
.collect()
}
#[test]
fn present_fields_become_cells_and_absent_collapse() {
let tb = title_block(
"|page#p| [\n |drawing#v| [ |rect#r| { width: 10; height: 10 } ]\n |title-block| { title: \"T\"; drawing-number: \"D\"; revision: \"A\" }\n]\n",
);
let cs = cells(&tb);
assert_eq!(cs.len(), 3, "one cell per present field");
assert!(
cs[0].style.iter().any(|d| d.name == "span"),
"the title spans"
);
assert!(
tb.style.iter().any(|d| d.name == "columns"),
"the grid gets columns"
);
}
#[test]
fn no_field_keeps_the_plain_table_form() {
let tb = title_block(
"|page#p| [\n |drawing#v| [ |rect#r| { width: 10; height: 10 } ]\n |title-block| { columns: 40, auto } [ \"Scale\" \"1:1\" ]\n]\n",
);
assert_eq!(cells(&tb).len(), 2, "the two authored cells");
assert!(tb.style.iter().any(|d| d.name == "columns"));
}
}