1use std::collections::HashMap;
13
14use super::{value::InputValue, App};
15use crate::{ArgInfo, ArgKind, ArgType, Command, Error, Result};
16
17#[derive(Debug, Clone, PartialEq, Default)]
19pub struct Parsed {
20 commands: Vec<String>,
22 args: HashMap<String, InputValue>,
24 help_requested: bool,
26 version_requested: bool,
28}
29
30impl Parsed {
31 pub fn commands(&self) -> &[String] {
33 &self.commands
34 }
35
36 pub fn get(&self, name: &str) -> Option<&InputValue> {
38 self.args.get(name)
39 }
40
41 pub fn is_help(&self) -> bool {
43 self.help_requested
44 }
45
46 pub fn is_version(&self) -> bool {
48 self.version_requested
49 }
50
51 pub fn args(&self) -> &HashMap<String, InputValue> {
53 &self.args
54 }
55}
56
57pub struct Parser<'a> {
59 tokens: Vec<String>,
61 position: usize,
63 app: &'a App,
65}
66
67impl<'a> Parser<'a> {
68 pub fn new(app: &'a App, tokens: Vec<String>) -> Self {
69 Self {
70 tokens,
71 position: 1, app,
73 }
74 }
75
76 fn next(&mut self) -> Option<String> {
78 if self.position < self.tokens.len() {
79 let token = self.tokens[self.position].clone();
80 self.position += 1;
81 Some(token)
82 } else {
83 None
84 }
85 }
86
87 fn has_more(&self) -> bool {
89 self.position < self.tokens.len()
90 }
91
92 pub fn parse(&mut self) -> Result<Parsed> {
94 let mut commands = vec![self.app.info.name.clone()];
95 let mut args: HashMap<String, InputValue> = HashMap::new();
96 let mut positional_candidates: Vec<String> = Vec::new();
97 let mut help_requested = false;
98 let mut version_requested = false;
99 let mut force_positional = false;
100
101 let mut command_path: Vec<usize> = Vec::new();
104
105 let mut merged_args = self.collect_args_by_path(&command_path);
107
108 while self.has_more() {
109 let token = self.next().unwrap();
110
111 if token == "--" {
113 force_positional = true;
114 continue;
115 }
116
117 if force_positional {
118 positional_candidates.push(token);
119 continue;
120 }
121
122 if token == "--help" || token == "-h" {
124 help_requested = true;
125 continue;
126 }
127 if token == "--version" || token == "-V" {
128 version_requested = true;
129 continue;
130 }
131
132 if token.starts_with("--") {
134 let (name, inline_value) = self.parse_long_option(&token);
135 let arg_info = self.find_arg_by_long(&merged_args, &name)?;
136
137 let value = self.resolve_option_value(&arg_info, inline_value)?;
138 self.insert_arg_value(&mut args, &arg_info, value)?;
139 continue;
140 }
141
142 if token.starts_with('-') && token.len() == 2 {
144 let short_char = token.chars().nth(1).unwrap();
145 let arg_info = self.find_arg_by_short(&merged_args, short_char)?;
146
147 let value = self.resolve_option_value(&arg_info, None)?;
148 self.insert_arg_value(&mut args, &arg_info, value)?;
149 continue;
150 }
151
152 let matched_idx = if command_path.is_empty() {
154 self.find_top_level_command_idx(&token)
156 } else {
157 self.find_subcommand_idx(&command_path, &token)
159 };
160
161 if let Some(idx) = matched_idx {
162 commands.push(token);
163 command_path.push(idx);
164 merged_args = self.collect_args_by_path(&command_path);
166 continue;
167 }
168
169 positional_candidates.push(token);
171 }
172
173 self.fill_positional_args(&merged_args, &positional_candidates, &mut args)?;
175
176 if !help_requested && !version_requested {
178 self.apply_defaults_and_validate(&merged_args, &mut args)?;
179 }
180
181 Ok(Parsed {
182 commands,
183 args,
184 help_requested,
185 version_requested,
186 })
187 }
188
189 fn collect_args_by_path(&self, command_path: &[usize]) -> Vec<ArgInfo> {
192 let mut result: Vec<ArgInfo> = Vec::new();
194 let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
195
196 if command_path.is_empty() {
198 for arg in &self.app.root.args {
199 if !seen.contains(&arg.info.name) {
200 result.push(arg.info.clone());
201 seen.insert(arg.info.name.clone());
202 }
203 }
204 } else {
205 if let Some(cmd) = self.get_command_by_path(command_path) {
207 for arg in &cmd.args {
208 if !seen.contains(&arg.info.name) {
209 result.push(arg.info.clone());
210 seen.insert(arg.info.name.clone());
211 }
212 }
213 }
214 }
215
216 result
217 }
218
219 fn get_command_by_path(&self, path: &[usize]) -> Option<&Command> {
221 if path.is_empty() {
222 return None;
223 }
224
225 let mut cmd = self.app.root.subcommands.get(path[0])?;
226 for &idx in &path[1..] {
227 cmd = cmd.subcommands.get(idx)?;
228 }
229 Some(cmd)
230 }
231
232 fn find_top_level_command_idx(&self, name: &str) -> Option<usize> {
234 self.app
235 .root
236 .subcommands
237 .iter()
238 .position(|cmd| cmd.info.name == name || cmd.info.aliases.contains(&name.to_string()))
239 }
240
241 fn find_subcommand_idx(&self, parent_path: &[usize], name: &str) -> Option<usize> {
243 let parent = self.get_command_by_path(parent_path)?;
244 parent
245 .subcommands
246 .iter()
247 .position(|sub| sub.info.name == name || sub.info.aliases.contains(&name.to_string()))
248 }
249
250 fn parse_long_option(&self, token: &str) -> (String, Option<String>) {
252 let without_prefix = &token[2..]; if let Some(eq_pos) = without_prefix.find('=') {
254 let name = without_prefix[..eq_pos].to_string();
255 let value = without_prefix[eq_pos + 1..].to_string();
256 (name, Some(value))
257 } else {
258 (without_prefix.to_string(), None)
259 }
260 }
261
262 fn find_arg_by_long(&self, args: &[ArgInfo], name: &str) -> Result<ArgInfo> {
264 for arg in args {
265 match &arg.kind {
266 ArgKind::Flag { long, aliases, .. } | ArgKind::Option { long, aliases, .. } => {
267 if long == name || aliases.contains(&name.to_string()) {
268 return Ok(arg.clone());
269 }
270 }
271 ArgKind::Positional => {}
272 }
273 }
274 Err(Error::InvalidFlag(format!("Unknown option: --{}", name)))
275 }
276
277 fn find_arg_by_short(&self, args: &[ArgInfo], short_char: char) -> Result<ArgInfo> {
279 for arg in args {
280 match &arg.kind {
281 ArgKind::Flag { short, .. } | ArgKind::Option { short, .. } => {
282 if *short == Some(short_char) {
283 return Ok(arg.clone());
284 }
285 }
286 ArgKind::Positional => {}
287 }
288 }
289 Err(Error::InvalidFlag(format!(
290 "Unknown option: -{}",
291 short_char
292 )))
293 }
294
295 fn resolve_option_value(
297 &mut self,
298 arg: &ArgInfo,
299 inline_value: Option<String>,
300 ) -> Result<InputValue> {
301 match &arg.kind {
302 ArgKind::Flag { .. } => {
303 Ok(InputValue::Bool(true))
305 }
306 ArgKind::Option { .. } => {
307 let value_str = if let Some(v) = inline_value {
308 v
309 } else {
310 self.next().ok_or_else(|| Error::InvalidFlagValue {
312 flag: arg.name.clone(),
313 message: "Missing value".to_string(),
314 })?
315 };
316 self.parse_value(&value_str, &arg.schema.ty)
317 }
318 ArgKind::Positional => unreachable!(),
319 }
320 }
321
322 fn parse_value(&self, value: &str, ty: &ArgType) -> Result<InputValue> {
324 match ty {
325 ArgType::String => Ok(InputValue::String(value.to_string())),
326 ArgType::Int => value.parse::<i64>().map(InputValue::Int).map_err(|_| {
327 Error::InvalidArgumentValue(format!("Cannot parse '{}' as integer", value))
328 }),
329 ArgType::Float => value.parse::<f64>().map(InputValue::Float).map_err(|_| {
330 Error::InvalidArgumentValue(format!("Cannot parse '{}' as float", value))
331 }),
332 ArgType::Bool => value.parse::<bool>().map(InputValue::Bool).map_err(|_| {
333 Error::InvalidArgumentValue(format!("Cannot parse '{}' as boolean", value))
334 }),
335 }
336 }
337
338 fn insert_arg_value(
340 &self,
341 args: &mut HashMap<String, InputValue>,
342 arg: &ArgInfo,
343 value: InputValue,
344 ) -> Result<()> {
345 if arg.schema.multiple {
346 let arr = args
348 .entry(arg.name.clone())
349 .or_insert_with(|| InputValue::Array(Vec::new()));
350 if let InputValue::Array(vec) = arr {
351 vec.push(Box::new(value));
352 }
353 } else {
354 args.insert(arg.name.clone(), value);
355 }
356 Ok(())
357 }
358
359 fn fill_positional_args(
361 &self,
362 merged_args: &[ArgInfo],
363 candidates: &[String],
364 args: &mut HashMap<String, InputValue>,
365 ) -> Result<()> {
366 let positional_args: Vec<&ArgInfo> = merged_args
368 .iter()
369 .filter(|a| matches!(a.kind, ArgKind::Positional))
370 .collect();
371
372 if positional_args.is_empty() {
373 if !candidates.is_empty() {
374 return Err(Error::TooManyArguments);
375 }
376 return Ok(());
377 }
378
379 let last_idx = positional_args.len() - 1;
380 let mut candidate_idx = 0;
381
382 for (idx, arg) in positional_args.iter().enumerate() {
383 if idx == last_idx && arg.schema.multiple {
384 let remaining: Vec<InputValue> = candidates[candidate_idx..]
386 .iter()
387 .map(|s| self.parse_value(s, &arg.schema.ty))
388 .collect::<Result<Vec<_>>>()?;
389 if !remaining.is_empty() {
390 args.insert(
391 arg.name.clone(),
392 InputValue::Array(remaining.into_iter().map(Box::new).collect()),
393 );
394 }
395 candidate_idx = candidates.len(); } else {
397 if candidate_idx < candidates.len() {
399 let value = self.parse_value(&candidates[candidate_idx], &arg.schema.ty)?;
400 args.insert(arg.name.clone(), value);
401 candidate_idx += 1;
402 }
403 }
404 }
405
406 if candidate_idx < candidates.len() {
408 return Err(Error::TooManyArguments);
409 }
410
411 Ok(())
412 }
413
414 fn apply_defaults_and_validate(
416 &self,
417 merged_args: &[ArgInfo],
418 args: &mut HashMap<String, InputValue>,
419 ) -> Result<()> {
420 for arg in merged_args {
421 if args.contains_key(&arg.name) {
422 continue;
423 }
424
425 if let Some(env_var) = &arg.env {
427 if let Ok(env_value) = std::env::var(env_var) {
428 let value = self.parse_value(&env_value, &arg.schema.ty)?;
429 args.insert(arg.name.clone(), value);
430 continue;
431 }
432 }
433
434 if let Some(default) = &arg.schema.default_value {
436 let value = self.parse_value(default, &arg.schema.ty)?;
437 args.insert(arg.name.clone(), value);
438 continue;
439 }
440
441 if arg.schema.required {
443 return Err(Error::MissingArgument(arg.name.clone()));
444 }
445 }
446
447 Ok(())
448 }
449}