Skip to main content

usage/spec/
complete.rs

1use kdl::{KdlEntry, KdlNode};
2use serde::{Deserialize, Serialize};
3
4use crate::error::UsageErr;
5use crate::spec::context::ParsingContext;
6use crate::spec::helpers::{string_entry, NodeHelper};
7use crate::spec::is_false;
8
9#[derive(Debug, Default, Clone, Serialize, Deserialize)]
10#[non_exhaustive]
11pub struct SpecComplete {
12    pub name: String,
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub run: Option<String>,
15    #[serde(skip_serializing_if = "is_false")]
16    pub descriptions: bool,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub type_: Option<String>,
19}
20
21impl SpecComplete {
22    /// A completer for the arg or flag named `name`.
23    pub fn new(name: impl Into<String>) -> Self {
24        Self {
25            name: name.into(),
26            ..Default::default()
27        }
28    }
29
30    /// Shell command whose output supplies the completions.
31    pub fn run(mut self, run: impl Into<String>) -> Self {
32        self.run = Some(run.into());
33        self
34    }
35
36    /// Whether the completer emits descriptions alongside values.
37    pub fn descriptions(mut self, descriptions: bool) -> Self {
38        self.descriptions = descriptions;
39        self
40    }
41}
42
43impl SpecComplete {
44    pub(crate) fn parse(ctx: &ParsingContext, node: &NodeHelper) -> Result<Self, UsageErr> {
45        let mut config = Self::default();
46        node.ensure_arg_len(1..=1)?;
47        config.name = node.arg(0)?.ensure_string()?.to_string().to_lowercase();
48        for (k, v) in node.props() {
49            match k {
50                "run" => {
51                    if config.type_.is_some() {
52                        bail_parse!(ctx, v.entry.span(), "can set run or type, not both")
53                    }
54                    config.run = Some(v.ensure_string()?.to_string())
55                }
56                "descriptions" => config.descriptions = v.ensure_bool()?,
57                "type" => {
58                    if config.run.is_some() {
59                        bail_parse!(ctx, v.entry.span(), "can set run or type, not both")
60                    }
61                    config.type_ = Some(v.ensure_string()?.to_string())
62                }
63                k => bail_parse!(ctx, v.entry.span(), "unsupported complete key {k}"),
64            }
65        }
66        Ok(config)
67    }
68}
69
70impl From<&SpecComplete> for KdlNode {
71    fn from(complete: &SpecComplete) -> Self {
72        let mut node = KdlNode::new("complete");
73        node.push(string_entry(None, &complete.name));
74        if let Some(run) = &complete.run {
75            node.push(string_entry(Some("run"), run));
76        }
77        if let Some(type_) = &complete.type_ {
78            node.push(string_entry(Some("type"), type_));
79        }
80        if complete.descriptions {
81            node.push(KdlEntry::new_prop("descriptions", true));
82        }
83        node
84    }
85}