1#[cfg(feature = "clap")]
2use itertools::Itertools;
3use kdl::{KdlDocument, KdlEntry, KdlNode};
4use serde::Serialize;
5use std::fmt::Display;
6use std::hash::Hash;
7use std::str::FromStr;
8
9use crate::error::UsageErr;
10use crate::spec::context::ParsingContext;
11use crate::spec::helpers::NodeHelper;
12use crate::spec::is_false;
13use crate::{string, SpecChoices};
14
15#[derive(Debug, Default, Clone, Serialize, PartialEq, Eq, strum::EnumString, strum::Display)]
16#[strum(serialize_all = "snake_case")]
17pub enum SpecDoubleDashChoices {
18 Automatic,
20 #[default]
22 Optional,
23 Required,
25}
26
27#[derive(Debug, Default, Clone, Serialize)]
28pub struct SpecArg {
29 pub name: String,
30 pub usage: String,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub help: Option<String>,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 pub help_long: Option<String>,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub help_md: Option<String>,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub help_first_line: Option<String>,
39 pub required: bool,
40 pub double_dash: SpecDoubleDashChoices,
41 #[serde(skip_serializing_if = "is_false")]
42 pub var: bool,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 pub var_min: Option<usize>,
45 #[serde(skip_serializing_if = "Option::is_none")]
46 pub var_max: Option<usize>,
47 pub hide: bool,
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub default: Option<String>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub choices: Option<SpecChoices>,
52}
53
54impl SpecArg {
55 pub(crate) fn parse(ctx: &ParsingContext, node: &NodeHelper) -> Result<Self, UsageErr> {
56 let mut arg: SpecArg = node.arg(0)?.ensure_string()?.parse()?;
57 for (k, v) in node.props() {
58 match k {
59 "help" => arg.help = Some(v.ensure_string()?),
60 "long_help" => arg.help_long = Some(v.ensure_string()?),
61 "help_long" => arg.help_long = Some(v.ensure_string()?),
62 "help_md" => arg.help_md = Some(v.ensure_string()?),
63 "required" => arg.required = v.ensure_bool()?,
64 "double_dash" => arg.double_dash = v.ensure_string()?.parse()?,
65 "var" => arg.var = v.ensure_bool()?,
66 "hide" => arg.hide = v.ensure_bool()?,
67 "var_min" => arg.var_min = v.ensure_usize().map(Some)?,
68 "var_max" => arg.var_max = v.ensure_usize().map(Some)?,
69 "default" => arg.default = v.ensure_string().map(Some)?,
70 k => bail_parse!(ctx, v.entry.span(), "unsupported arg key {k}"),
71 }
72 }
73 if arg.default.is_some() {
74 arg.required = false;
75 }
76 for child in node.children() {
77 match child.name() {
78 "choices" => arg.choices = Some(SpecChoices::parse(ctx, &child)?),
79 k => bail_parse!(ctx, child.node.name().span(), "unsupported arg child {k}"),
80 }
81 }
82 arg.usage = arg.usage();
83 if let Some(help) = &arg.help {
84 arg.help_first_line = Some(string::first_line(help));
85 }
86 Ok(arg)
87 }
88}
89
90impl SpecArg {
91 pub fn usage(&self) -> String {
92 let name = if self.double_dash == SpecDoubleDashChoices::Required {
93 format!("-- {}", self.name)
94 } else {
95 self.name.clone()
96 };
97 let mut name = if self.required {
98 format!("<{}>", name)
99 } else {
100 format!("[{}]", name)
101 };
102 if self.var {
103 name = format!("{}…", name);
104 }
105 name
106 }
107}
108
109impl From<&SpecArg> for KdlNode {
110 fn from(arg: &SpecArg) -> Self {
111 let mut node = KdlNode::new("arg");
112 node.push(KdlEntry::new(arg.usage()));
113 if let Some(desc) = &arg.help {
114 node.push(KdlEntry::new_prop("help", desc.clone()));
115 }
116 if let Some(desc) = &arg.help_long {
117 node.push(KdlEntry::new_prop("help_long", desc.clone()));
118 }
119 if let Some(desc) = &arg.help_md {
120 node.push(KdlEntry::new_prop("help_md", desc.clone()));
121 }
122 if !arg.required {
123 node.push(KdlEntry::new_prop("required", false));
124 }
125 if arg.double_dash == SpecDoubleDashChoices::Automatic {
126 node.push(KdlEntry::new_prop(
127 "double_dash",
128 arg.double_dash.to_string(),
129 ));
130 }
131 if arg.var {
132 node.push(KdlEntry::new_prop("var", true));
133 }
134 if let Some(min) = arg.var_min {
135 node.push(KdlEntry::new_prop("var_min", min as i128));
136 }
137 if let Some(max) = arg.var_max {
138 node.push(KdlEntry::new_prop("var_max", max as i128));
139 }
140 if arg.hide {
141 node.push(KdlEntry::new_prop("hide", true));
142 }
143 if let Some(default) = &arg.default {
144 node.push(KdlEntry::new_prop("default", default.clone()));
145 }
146 if let Some(choices) = &arg.choices {
147 let children = node.children_mut().get_or_insert_with(KdlDocument::new);
148 children.nodes_mut().push(choices.into());
149 }
150 node
151 }
152}
153
154impl From<&str> for SpecArg {
155 fn from(input: &str) -> Self {
156 let mut arg = SpecArg {
157 name: input.to_string(),
158 required: true,
159 ..Default::default()
160 };
161 if let Some(name) = arg
162 .name
163 .strip_suffix("...")
164 .or_else(|| arg.name.strip_suffix("…"))
165 {
166 arg.var = true;
167 arg.name = name.to_string();
168 }
169 let first = arg.name.chars().next().unwrap_or_default();
170 let last = arg.name.chars().last().unwrap_or_default();
171 match (first, last) {
172 ('[', ']') => {
173 arg.name = arg.name[1..arg.name.len() - 1].to_string();
174 arg.required = false;
175 }
176 ('<', '>') => {
177 arg.name = arg.name[1..arg.name.len() - 1].to_string();
178 }
179 _ => {}
180 }
181 if let Some(name) = arg.name.strip_prefix("-- ") {
182 arg.double_dash = SpecDoubleDashChoices::Required;
183 arg.name = name.to_string();
184 }
185 arg
186 }
187}
188impl FromStr for SpecArg {
189 type Err = UsageErr;
190 fn from_str(input: &str) -> std::result::Result<Self, UsageErr> {
191 Ok(input.into())
192 }
193}
194
195#[cfg(feature = "clap")]
196impl From<&clap::Arg> for SpecArg {
197 fn from(arg: &clap::Arg) -> Self {
198 let required = arg.is_required_set();
199 let help = arg.get_help().map(|s| s.to_string());
200 let help_long = arg.get_long_help().map(|s| s.to_string());
201 let help_first_line = help.as_ref().map(|s| string::first_line(s));
202 let hide = arg.is_hide_set();
203 let var = matches!(
204 arg.get_action(),
205 clap::ArgAction::Count | clap::ArgAction::Append
206 );
207 let choices = arg
208 .get_possible_values()
209 .iter()
210 .flat_map(|v| v.get_name_and_aliases().map(|s| s.to_string()))
211 .collect::<Vec<_>>();
212 let mut arg = Self {
213 name: arg
214 .get_value_names()
215 .unwrap_or_default()
216 .first()
217 .cloned()
218 .unwrap_or_default()
219 .to_string(),
220 usage: "".into(),
221 required,
222 double_dash: if arg.is_last_set() {
223 SpecDoubleDashChoices::Required
224 } else if arg.is_trailing_var_arg_set() {
225 SpecDoubleDashChoices::Automatic
226 } else {
227 SpecDoubleDashChoices::Optional
228 },
229 help,
230 help_long,
231 help_md: None,
232 help_first_line,
233 var,
234 var_max: None,
235 var_min: None,
236 hide,
237 default: if arg.get_default_values().is_empty() {
238 None
239 } else {
240 Some(
241 arg.get_default_values()
242 .iter()
243 .map(|v| v.to_string_lossy().to_string())
244 .join("|"),
245 )
246 },
247 choices: None,
248 };
249 if !choices.is_empty() {
250 arg.choices = Some(SpecChoices { choices });
251 }
252
253 arg
254 }
255}
256
257impl Display for SpecArg {
258 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259 write!(f, "{}", self.usage())
260 }
261}
262impl PartialEq for SpecArg {
263 fn eq(&self, other: &Self) -> bool {
264 self.name == other.name
265 }
266}
267impl Eq for SpecArg {}
268impl Hash for SpecArg {
269 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
270 self.name.hash(state);
271 }
272}