1use crate::error::Result;
2use crate::kdl::{KdlDocument, KdlEntry, KdlNode};
3use crate::spec::context::ParsingContext;
4use crate::spec::helpers::{string_entry, NodeHelper};
5use crate::SpecArg;
6use serde::Serialize;
7
8#[derive(Debug, Default, Clone, Serialize)]
10#[non_exhaustive]
11pub struct SpecClause {
12 pub name: String,
13 pub separator: String,
14 pub args: Vec<SpecArg>,
15 #[serde(skip_serializing_if = "Option::is_none")]
16 pub help: Option<String>,
17 #[serde(skip_serializing_if = "Option::is_none")]
18 pub help_long: Option<String>,
19 pub usage: String,
20}
21
22impl SpecClause {
23 pub(crate) fn parse(ctx: &ParsingContext, node: &NodeHelper) -> Result<Self> {
24 let mut clause = Self {
25 name: node.arg(0)?.ensure_string()?,
26 ..Self::default()
27 };
28 for (key, value) in node.props() {
29 match key {
30 "separator" => clause.separator = value.ensure_string()?,
31 "help" => clause.help = Some(value.ensure_string()?),
32 "help_long" | "long_help" => clause.help_long = Some(value.ensure_string()?),
33 key => bail_parse!(ctx, value.entry.span(), "unsupported clause key {key}"),
34 }
35 }
36 for child in node.children() {
37 match child.name() {
38 "arg" => clause.args.push(SpecArg::parse(ctx, &child)?),
39 key => bail_parse!(
40 ctx,
41 child.node.name().span(),
42 "unsupported clause child {key}"
43 ),
44 }
45 }
46 if clause.name.is_empty() {
47 bail_parse!(ctx, node.span(), "a clause needs a name");
48 }
49 if clause.separator.is_empty() {
50 bail_parse!(
51 ctx,
52 node.span(),
53 "clause {} needs a non-empty separator",
54 clause.name
55 );
56 }
57 if clause.separator.starts_with('-') {
58 bail_parse!(ctx, node.span(), "clause separator cannot start with `-`");
59 }
60 if clause.args.is_empty() {
61 bail_parse!(
62 ctx,
63 node.span(),
64 "clause {} needs at least one argument",
65 clause.name
66 );
67 }
68 clause.usage = clause.usage();
69 Ok(clause)
70 }
71
72 pub fn usage(&self) -> String {
73 let inner = self
74 .args
75 .iter()
76 .map(SpecArg::usage)
77 .collect::<Vec<_>>()
78 .join(" ");
79 format!("{inner} [{} {inner}]…", self.separator)
80 }
81}
82
83impl From<&SpecClause> for KdlNode {
84 fn from(clause: &SpecClause) -> Self {
85 let mut node = KdlNode::new("clause");
86 node.push(KdlEntry::new(clause.name.clone()));
87 node.push(string_entry(Some("separator"), &clause.separator));
88 if let Some(help) = &clause.help {
89 node.push(string_entry(Some("help"), help));
90 }
91 if let Some(help) = &clause.help_long {
92 node.push(string_entry(Some("help_long"), help));
93 }
94 let children = node.children_mut().get_or_insert_with(KdlDocument::new);
95 children
96 .nodes_mut()
97 .extend(clause.args.iter().map(Into::into));
98 node
99 }
100}