1use std::ffi::OsString;
5use std::fmt::{self, Write as _};
6
7#[derive(Debug, PartialEq)]
9pub enum Error {
10 DuplicateOption(String),
13
14 MissingArgValue(String),
16
17 MissingRequirements(MissingRequirements),
19
20 OptionsAfterHelp,
22
23 ParseArgument {
25 arg: String,
27 value: OsString,
29 msg: String,
31 },
32
33 UnknownArgument(OsString),
35
36 Other(String),
38}
39
40impl Error {
41 #[inline]
43 pub fn other<S: ToString>(msg: S) -> Self {
44 Self::Other(msg.to_string())
45 }
46}
47
48impl std::error::Error for Error {}
49
50impl fmt::Display for Error {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 use Error::*;
53
54 match &self {
55 DuplicateOption(arg) => write!(f, "Option '{}' can only be used once.", arg),
56 MissingArgValue(arg) => write!(f, "Option '{}' requires a value.", arg),
57 MissingRequirements(req) => req.fmt(f),
58 OptionsAfterHelp => {
59 write!(f, "Trailing options are not allowed after 'help' subcommand.")
60 }
61 ParseArgument { arg, value, msg } => {
62 let subj = if arg.starts_with('-') {
63 "option"
64 } else {
65 "argument"
66 };
67 write!(f, "Error parsing {} '{}' with value '{:?}': {}.", subj, arg, value, msg)
68 }
69 UnknownArgument(arg) => write!(f, "Unrecognized argument: {}", arg.to_string_lossy()),
70 Other(msg) => msg.fmt(f),
71 }
72 }
73}
74
75#[doc(hidden)]
77#[derive(Debug, Default, PartialEq)]
78pub struct MissingRequirements {
79 options: Vec<&'static str>,
80 subcommands: Option<Vec<&'static str>>,
81 positional_args: Vec<&'static str>,
82}
83
84impl MissingRequirements {
85 #[doc(hidden)]
87 pub fn missing_option(&mut self, name: &'static str) {
88 self.options.push(name)
89 }
90
91 #[doc(hidden)]
93 pub fn missing_subcommands(&mut self, commands: impl Iterator<Item = &'static str>) {
94 self.subcommands = Some(commands.collect());
95 }
96
97 #[doc(hidden)]
99 pub fn missing_positional_arg(&mut self, name: &'static str) {
100 self.positional_args.push(name)
101 }
102
103 #[doc(hidden)]
106 pub fn err_on_any(self) -> Result<(), Error> {
107 if self.options.is_empty() && self.subcommands.is_none() && self.positional_args.is_empty()
108 {
109 Ok(())
110 } else {
111 Err(Error::MissingRequirements(self))
112 }
113 }
114}
115
116const NEWLINE_INDENT: &str = "\n ";
117
118impl fmt::Display for MissingRequirements {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 if !self.positional_args.is_empty() {
121 f.write_str("Required positional arguments not provided:")?;
122 for arg in &self.positional_args {
123 f.write_str(NEWLINE_INDENT)?;
124 f.write_str(arg)?;
125 }
126 }
127
128 if !self.options.is_empty() {
129 if !self.positional_args.is_empty() {
130 f.write_char('\n')?;
131 }
132 f.write_str("Required options not provided:")?;
133 for option in &self.options {
134 f.write_str(NEWLINE_INDENT)?;
135 f.write_str(option)?;
136 }
137 }
138
139 if let Some(missing_subcommands) = &self.subcommands {
140 if !self.options.is_empty() {
141 f.write_char('\n')?;
142 }
143 f.write_str("One of the following subcommands must be present:")?;
144 f.write_str(NEWLINE_INDENT)?;
145 f.write_str("help")?;
146 for subcommand in missing_subcommands {
147 f.write_str(NEWLINE_INDENT)?;
148 f.write_str(subcommand)?;
149 }
150 }
151
152 f.write_char('\n')
153 }
154}