1use std::{
2 ffi::OsString,
3 fmt,
4 io::{self, IsTerminal, Write, sink, stderr, stdout},
5 process,
6 str::Utf8Error,
7};
8
9use crate::{
10 Context,
11 desc::{ArgumentDesc, ArgumentName},
12 types::Color,
13 writer::{BOLD, DEFAULT_LINE_WIDTH, ITALIC, RED, RESET, Writer, YELLOW},
14};
15
16#[derive(Debug)]
25pub struct Error {
26 pub(crate) kind: ErrorKind,
27 pub(crate) context: Context,
28}
29
30impl Error {
31 pub fn report_and_exit(&self) -> ! {
33 let mut reporter = self.reporter();
34 if self.is_fatal() {
35 reporter = reporter.output(stderr());
36 } else {
37 reporter = reporter.output(stdout());
38 };
39 reporter.report_and_exit()
40 }
41
42 pub fn reporter(&self) -> Reporter<'_> {
44 Reporter {
45 error: self,
46 color: self.context.color(),
47 wrap_width: DEFAULT_LINE_WIDTH,
48 writer: Writer::io(sink()),
49 }
50 }
51
52 fn report_impl(&self, f: &mut Writer) -> io::Result<()> {
53 match &self.kind {
54 ErrorKind::VersionRequested => {
55 let fmt = self.context.root_desc.0.0.version_formatter;
56 let version = fmt(&self.context);
57 write!(f, "{version}")?;
58 if !version.ends_with('\n') {
59 writeln!(f)?;
60 }
61 }
62 ErrorKind::HelpRequested => {
63 self.context
64 .desc
65 .help()
66 .with_invocation(&self.context.command_chain)
67 .write_to(f)?;
68 }
69 _ => {
70 write!(f, "{RED}error{RESET}: ")?;
71 f.set_indentation("error: ".len());
72 self.write_error(f)?;
73 write!(f, "\n\n")?;
74 let mut usage = self
75 .context
76 .desc
77 .usage()
78 .with_invocation(&self.context.command_chain)
79 .with_prefix(format!("{BOLD}usage{RESET}: "));
80 if let Some(arg) = self.kind.arg_index() {
81 usage = usage.highlight_arg(arg);
82 }
83 if self.kind.related_to_subcommand() {
84 usage = usage.highlight_subcommand();
85 }
86 usage.write_to(f)?;
87 writeln!(f)?;
88 }
89 }
90
91 f.flush()
92 }
93
94 fn arg(&self, index: usize) -> &ArgumentDesc {
95 &self.context.desc.args()[index]
96 }
97
98 fn argname(&self, index: usize) -> impl fmt::Display {
99 struct Disp<'a>(&'a ArgumentName);
100 impl fmt::Display for Disp<'_> {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 write!(f, "`{YELLOW}{ITALIC}{}{RESET}`", self.0)
103 }
104 }
105 Disp(self.arg(index).name())
106 }
107
108 fn write_error(&self, f: &mut Writer) -> io::Result<()> {
109 match &self.kind {
110 ErrorKind::HelpRequested => write!(f, "help requested"),
111 ErrorKind::VersionRequested => write!(f, "version requested"),
112
113 ErrorKind::Utf8Error(utf8_error) => write!(f, "{utf8_error}"),
114 ErrorKind::ValueParseError { error, arg } => write!(
115 f,
116 "invalid value for argument {}: {error}",
117 self.argname(*arg)
118 ),
119 ErrorKind::UnexpectedArgValue(arg) => {
120 write!(f, "flag {} does not take a value", self.argname(*arg))
121 }
122 ErrorKind::UnexpectedArg { arg } => {
123 write!(f, "unexpected {arg}")?;
124 Ok(())
125 }
126 ErrorKind::DuplicateArg(arg) => {
127 write!(f, "duplicate argument {}", self.argname(*arg))
128 }
129 ErrorKind::MissingArg(arg) => {
130 write!(f, "missing argument {}", self.argname(*arg))
131 }
132 ErrorKind::MissingArgValue(arg) => {
133 write!(f, "argument {} requires a value", self.argname(*arg))
134 }
135 ErrorKind::MissingSubcommand => write!(f, "a subcommand is required"),
136 ErrorKind::UnknownSubcommand(cmd) => {
137 write!(f, "unknown subcommand `{}`", cmd.display())
138 }
139 }
140 }
141
142 fn is_fatal(&self) -> bool {
143 match &self.kind {
144 ErrorKind::HelpRequested | ErrorKind::VersionRequested => false,
145 _ => true,
146 }
147 }
148}
149
150#[derive(Debug)]
155pub enum ErrorKind {
156 HelpRequested,
157 VersionRequested,
158
159 Utf8Error(Utf8Error),
160 ValueParseError {
168 arg: usize,
169 error: Box<dyn std::error::Error>,
170 },
171 UnexpectedArgValue(usize),
173 UnexpectedArg {
174 arg: String,
175 },
176 DuplicateArg(usize),
179 MissingArg(usize),
182 MissingArgValue(usize),
183 MissingSubcommand,
185 UnknownSubcommand(OsString),
188}
189
190impl ErrorKind {
191 fn arg_index(&self) -> Option<usize> {
192 match self {
193 ErrorKind::MissingArg(arg)
194 | ErrorKind::MissingArgValue(arg)
195 | ErrorKind::DuplicateArg(arg)
196 | ErrorKind::UnexpectedArgValue(arg)
197 | ErrorKind::ValueParseError { arg, .. } => Some(*arg),
198
199 ErrorKind::HelpRequested
200 | ErrorKind::VersionRequested
201 | ErrorKind::Utf8Error(..)
202 | ErrorKind::UnexpectedArg { .. }
203 | ErrorKind::MissingSubcommand
204 | ErrorKind::UnknownSubcommand(..) => None,
205 }
206 }
207
208 fn related_to_subcommand(&self) -> bool {
209 match self {
210 ErrorKind::MissingSubcommand | ErrorKind::UnknownSubcommand(..) => true,
211
212 ErrorKind::MissingArg(..)
213 | ErrorKind::MissingArgValue(..)
214 | ErrorKind::DuplicateArg(..)
215 | ErrorKind::HelpRequested
216 | ErrorKind::VersionRequested
217 | ErrorKind::Utf8Error(..)
218 | ErrorKind::ValueParseError { .. }
219 | ErrorKind::UnexpectedArgValue { .. }
220 | ErrorKind::UnexpectedArg { .. } => false,
221 }
222 }
223}
224
225impl fmt::Display for Error {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 self.report_impl(&mut Writer::display(f))
228 .map_err(|_| fmt::Error)
229 }
230}
231impl std::error::Error for Error {}
232
233const EX_OK: i32 = 0;
235
236const EX_USAGE: i32 = 64;
243
244pub struct Reporter<'a> {
248 error: &'a Error,
249 color: Color,
250 wrap_width: usize,
251 writer: Writer<'a>,
252}
253
254impl<'a> Reporter<'a> {
255 pub fn color(self, color: Color) -> Self {
265 Self { color, ..self }
266 }
267
268 pub fn wrap_width(self, width: usize) -> Self {
274 Self {
275 wrap_width: width,
276 ..self
277 }
278 }
279
280 pub fn output<W: io::Write + IsTerminal + 'a>(self, w: W) -> Self {
285 Self {
286 writer: Writer::fd(w),
287 ..self
288 }
289 }
290
291 pub fn raw_output<W: io::Write + 'a>(self, w: W) -> Self {
296 Self {
297 writer: Writer::io(w),
298 ..self
299 }
300 }
301
302 pub fn report(mut self) -> io::Result<()> {
304 match self.color {
305 Color::Never => self.writer.force_color(false),
306 Color::Always => self.writer.force_color(true),
307 _ => {}
308 }
309 self.writer.set_max_line_width(self.wrap_width);
310 self.error.report_impl(&mut self.writer)
311 }
312
313 pub fn report_and_exit(self) -> ! {
321 let code = if self.error.is_fatal() {
322 EX_USAGE
323 } else {
324 EX_OK
325 };
326 self.report().ok();
327 process::exit(code);
328 }
329}