1use std::fmt::{Display, Formatter};
2
3use kdl::{KdlEntry, KdlNode};
4use serde::Serialize;
5
6use crate::error::Result;
7use crate::spec::context::ParsingContext;
8use crate::spec::helpers::{string_entry, NodeHelper};
9
10#[derive(Debug, Default, Clone, Serialize)]
16#[non_exhaustive]
17pub struct SpecView {
18 pub id: String,
20 pub name: String,
22 pub bin: String,
24 pub root: String,
26 pub all_globals: bool,
28 pub globals: Vec<String>,
30}
31
32impl SpecView {
33 pub(crate) fn parse(ctx: &ParsingContext, node: &NodeHelper<'_>) -> Result<Self> {
34 let id = node.arg(0)?.ensure_string()?;
35 let mut view = Self {
36 name: id.clone(),
37 bin: id.clone(),
38 id,
39 ..Self::default()
40 };
41 for (key, value) in node.props() {
42 match key {
43 "name" => view.name = value.ensure_string()?,
44 "bin" => view.bin = value.ensure_string()?,
45 "root" => view.root = value.ensure_string()?,
46 "globals" => view.all_globals = value.ensure_bool()?,
47 key => bail_parse!(ctx, value.entry.span(), "unsupported view key {key}"),
48 }
49 }
50 for child in node.children() {
51 match child.name() {
52 "global" => {
53 for selector in child.args() {
54 view.globals.push(selector.ensure_string()?);
55 }
56 }
57 key => bail_parse!(
58 ctx,
59 child.node.name().span(),
60 "unsupported view value {key}"
61 ),
62 }
63 }
64 if view.id.is_empty() {
65 bail_parse!(ctx, node.span(), "a view needs a non-empty identifier");
66 }
67 if view.name.is_empty() || view.bin.is_empty() {
68 bail_parse!(ctx, node.span(), "a view's name and bin cannot be empty");
69 }
70 if view.root.trim().is_empty() {
71 bail_parse!(
72 ctx,
73 node.span(),
74 "a view needs the command path it promotes in `root`"
75 );
76 }
77 Ok(view)
78 }
79}
80
81impl Display for SpecView {
82 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
83 let mut node = KdlNode::new("view");
84 node.push(string_entry(None, &self.id));
85 if self.name != self.id {
86 node.push(string_entry(Some("name"), &self.name));
87 }
88 if self.bin != self.name {
89 node.push(string_entry(Some("bin"), &self.bin));
90 }
91 node.push(string_entry(Some("root"), &self.root));
92 if self.all_globals {
93 node.push(KdlEntry::new_prop("globals", true));
94 }
95 if !self.globals.is_empty() {
96 let mut children = kdl::KdlDocument::new();
97 let mut global = KdlNode::new("global");
98 for selector in &self.globals {
99 global.push(string_entry(None, selector));
100 }
101 children.nodes_mut().push(global);
102 node.set_children(children);
103 }
104 write!(f, "{node}")
105 }
106}