1 //! Minimal, flexible command-line parser
2 //!
3 //! As opposed to a declarative parser, this processes arguments as a stream of tokens. As lexing
4 //! a command-line is not context-free, we rely on the caller to decide how to interpret the
5 //! arguments.
6 //!
7 //! # Examples
8 //!
9 //! ```rust
10 //! # use std::path::PathBuf;
11 //! # type BoxedError = Box<dyn std::error::Error + Send + Sync>;
12 //! #[derive(Debug)]
13 //! struct Args {
14 //! paths: Vec<PathBuf>,
15 //! color: Color,
16 //! verbosity: usize,
17 //! }
18 //!
19 //! #[derive(Debug)]
20 //! enum Color {
21 //! Always,
22 //! Auto,
23 //! Never,
24 //! }
25 //!
26 //! impl Color {
27 //! fn parse(s: Option<&clap_lex::RawOsStr>) -> Result<Self, BoxedError> {
28 //! let s = s.map(|s| s.to_str().ok_or(s));
29 //! match s {
30 //! Some(Ok("always")) | Some(Ok("")) | None => {
31 //! Ok(Color::Always)
32 //! }
33 //! Some(Ok("auto")) => {
34 //! Ok(Color::Auto)
35 //! }
36 //! Some(Ok("never")) => {
37 //! Ok(Color::Never)
38 //! }
39 //! Some(invalid) => {
40 //! Err(format!("Invalid value for `--color`, {:?}", invalid).into())
41 //! }
42 //! }
43 //! }
44 //! }
45 //!
46 //! fn parse_args(
47 //! raw: impl IntoIterator<Item=impl Into<std::ffi::OsString>>
48 //! ) -> Result<Args, BoxedError> {
49 //! let mut args = Args {
50 //! paths: Vec::new(),
51 //! color: Color::Auto,
52 //! verbosity: 0,
53 //! };
54 //!
55 //! let raw = clap_lex::RawArgs::new(raw);
56 //! let mut cursor = raw.cursor();
57 //! raw.next(&mut cursor); // Skip the bin
58 //! while let Some(arg) = raw.next(&mut cursor) {
59 //! if arg.is_escape() {
60 //! args.paths.extend(raw.remaining(&mut cursor).map(PathBuf::from));
61 //! } else if arg.is_stdio() {
62 //! args.paths.push(PathBuf::from("-"));
63 //! } else if let Some((long, value)) = arg.to_long() {
64 //! match long {
65 //! Ok("verbose") => {
66 //! if let Some(value) = value {
67 //! return Err(format!("`--verbose` does not take a value, got `{:?}`", value).into());
68 //! }
69 //! args.verbosity += 1;
70 //! }
71 //! Ok("color") => {
72 //! args.color = Color::parse(value)?;
73 //! }
74 //! _ => {
75 //! return Err(
76 //! format!("Unexpected flag: --{}", arg.display()).into()
77 //! );
78 //! }
79 //! }
80 //! } else if let Some(mut shorts) = arg.to_short() {
81 //! while let Some(short) = shorts.next_flag() {
82 //! match short {
83 //! Ok('v') => {
84 //! args.verbosity += 1;
85 //! }
86 //! Ok('c') => {
87 //! let value = shorts.next_value_os();
88 //! args.color = Color::parse(value)?;
89 //! }
90 //! Ok(c) => {
91 //! return Err(format!("Unexpected flag: -{}", c).into());
92 //! }
93 //! Err(e) => {
94 //! return Err(format!("Unexpected flag: -{}", e.to_str_lossy()).into());
95 //! }
96 //! }
97 //! }
98 //! } else {
99 //! args.paths.push(PathBuf::from(arg.to_value_os().to_os_str().into_owned()));
100 //! }
101 //! }
102 //!
103 //! Ok(args)
104 //! }
105 //!
106 //! let args = parse_args(["bin", "--hello", "world"]);
107 //! println!("{:?}", args);
108 //! ```
109
110 use std::ffi::OsStr;
111 use std::ffi::OsString;
112
113 pub use std::io::SeekFrom;
114
115 pub use os_str_bytes::RawOsStr;
116 pub use os_str_bytes::RawOsString;
117
118 /// Command-line arguments
119 #[derive(Default, Clone, Debug, PartialEq, Eq)]
120 pub struct RawArgs {
121 items: Vec<OsString>,
122 }
123
124 impl RawArgs {
125 //// Create an argument list to parse
126 ///
127 /// **NOTE:** The argument returned will be the current binary.
128 ///
129 /// # Example
130 ///
131 /// ```rust,no_run
132 /// # use std::path::PathBuf;
133 /// let raw = clap_lex::RawArgs::from_args();
134 /// let mut cursor = raw.cursor();
135 /// let _bin = raw.next_os(&mut cursor);
136 ///
137 /// let mut paths = raw.remaining(&mut cursor).map(PathBuf::from).collect::<Vec<_>>();
138 /// println!("{:?}", paths);
139 /// ```
from_args() -> Self140 pub fn from_args() -> Self {
141 Self::new(std::env::args_os())
142 }
143
144 //// Create an argument list to parse
145 ///
146 /// # Example
147 ///
148 /// ```rust,no_run
149 /// # use std::path::PathBuf;
150 /// let raw = clap_lex::RawArgs::new(["bin", "foo.txt"]);
151 /// let mut cursor = raw.cursor();
152 /// let _bin = raw.next_os(&mut cursor);
153 ///
154 /// let mut paths = raw.remaining(&mut cursor).map(PathBuf::from).collect::<Vec<_>>();
155 /// println!("{:?}", paths);
156 /// ```
new(iter: impl IntoIterator<Item = impl Into<std::ffi::OsString>>) -> Self157 pub fn new(iter: impl IntoIterator<Item = impl Into<std::ffi::OsString>>) -> Self {
158 let iter = iter.into_iter();
159 Self::from(iter)
160 }
161
162 /// Create a cursor for walking the arguments
163 ///
164 /// # Example
165 ///
166 /// ```rust,no_run
167 /// # use std::path::PathBuf;
168 /// let raw = clap_lex::RawArgs::new(["bin", "foo.txt"]);
169 /// let mut cursor = raw.cursor();
170 /// let _bin = raw.next_os(&mut cursor);
171 ///
172 /// let mut paths = raw.remaining(&mut cursor).map(PathBuf::from).collect::<Vec<_>>();
173 /// println!("{:?}", paths);
174 /// ```
cursor(&self) -> ArgCursor175 pub fn cursor(&self) -> ArgCursor {
176 ArgCursor::new()
177 }
178
179 /// Advance the cursor, returning the next [`ParsedArg`]
next(&self, cursor: &mut ArgCursor) -> Option<ParsedArg<'_>>180 pub fn next(&self, cursor: &mut ArgCursor) -> Option<ParsedArg<'_>> {
181 self.next_os(cursor).map(ParsedArg::new)
182 }
183
184 /// Advance the cursor, returning a raw argument value.
next_os(&self, cursor: &mut ArgCursor) -> Option<&OsStr>185 pub fn next_os(&self, cursor: &mut ArgCursor) -> Option<&OsStr> {
186 let next = self.items.get(cursor.cursor).map(|s| s.as_os_str());
187 cursor.cursor = cursor.cursor.saturating_add(1);
188 next
189 }
190
191 /// Return the next [`ParsedArg`]
peek(&self, cursor: &ArgCursor) -> Option<ParsedArg<'_>>192 pub fn peek(&self, cursor: &ArgCursor) -> Option<ParsedArg<'_>> {
193 self.peek_os(cursor).map(ParsedArg::new)
194 }
195
196 /// Return a raw argument value.
peek_os(&self, cursor: &ArgCursor) -> Option<&OsStr>197 pub fn peek_os(&self, cursor: &ArgCursor) -> Option<&OsStr> {
198 self.items.get(cursor.cursor).map(|s| s.as_os_str())
199 }
200
201 /// Return all remaining raw arguments, advancing the cursor to the end
202 ///
203 /// # Example
204 ///
205 /// ```rust,no_run
206 /// # use std::path::PathBuf;
207 /// let raw = clap_lex::RawArgs::new(["bin", "foo.txt"]);
208 /// let mut cursor = raw.cursor();
209 /// let _bin = raw.next_os(&mut cursor);
210 ///
211 /// let mut paths = raw.remaining(&mut cursor).map(PathBuf::from).collect::<Vec<_>>();
212 /// println!("{:?}", paths);
213 /// ```
remaining(&self, cursor: &mut ArgCursor) -> impl Iterator<Item = &OsStr>214 pub fn remaining(&self, cursor: &mut ArgCursor) -> impl Iterator<Item = &OsStr> {
215 let remaining = self.items[cursor.cursor..].iter().map(|s| s.as_os_str());
216 cursor.cursor = self.items.len();
217 remaining
218 }
219
220 /// Adjust the cursor's position
seek(&self, cursor: &mut ArgCursor, pos: SeekFrom)221 pub fn seek(&self, cursor: &mut ArgCursor, pos: SeekFrom) {
222 let pos = match pos {
223 SeekFrom::Start(pos) => pos,
224 SeekFrom::End(pos) => (self.items.len() as i64).saturating_add(pos).max(0) as u64,
225 SeekFrom::Current(pos) => (cursor.cursor as i64).saturating_add(pos).max(0) as u64,
226 };
227 let pos = (pos as usize).min(self.items.len());
228 cursor.cursor = pos;
229 }
230
231 /// Inject arguments before the [`RawArgs::next`]
insert( &mut self, cursor: &ArgCursor, insert_items: impl IntoIterator<Item = impl Into<OsString>>, )232 pub fn insert(
233 &mut self,
234 cursor: &ArgCursor,
235 insert_items: impl IntoIterator<Item = impl Into<OsString>>,
236 ) {
237 self.items.splice(
238 cursor.cursor..cursor.cursor,
239 insert_items.into_iter().map(Into::into),
240 );
241 }
242
243 /// Any remaining args?
is_end(&self, cursor: &ArgCursor) -> bool244 pub fn is_end(&self, cursor: &ArgCursor) -> bool {
245 self.peek_os(cursor).is_none()
246 }
247 }
248
249 impl<I, T> From<I> for RawArgs
250 where
251 I: Iterator<Item = T>,
252 T: Into<OsString>,
253 {
from(val: I) -> Self254 fn from(val: I) -> Self {
255 Self {
256 items: val.map(|x| x.into()).collect(),
257 }
258 }
259 }
260
261 /// Position within [`RawArgs`]
262 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
263 pub struct ArgCursor {
264 cursor: usize,
265 }
266
267 impl ArgCursor {
new() -> Self268 fn new() -> Self {
269 Self { cursor: 0 }
270 }
271 }
272
273 /// Command-line Argument
274 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
275 pub struct ParsedArg<'s> {
276 inner: std::borrow::Cow<'s, RawOsStr>,
277 utf8: Option<&'s str>,
278 }
279
280 impl<'s> ParsedArg<'s> {
new(inner: &'s OsStr) -> Self281 fn new(inner: &'s OsStr) -> Self {
282 let utf8 = inner.to_str();
283 let inner = RawOsStr::new(inner);
284 Self { inner, utf8 }
285 }
286
287 /// Argument is length of 0
is_empty(&self) -> bool288 pub fn is_empty(&self) -> bool {
289 self.inner.as_ref().is_empty()
290 }
291
292 /// Does the argument look like a stdio argument (`-`)
is_stdio(&self) -> bool293 pub fn is_stdio(&self) -> bool {
294 self.inner.as_ref() == "-"
295 }
296
297 /// Does the argument look like an argument escape (`--`)
is_escape(&self) -> bool298 pub fn is_escape(&self) -> bool {
299 self.inner.as_ref() == "--"
300 }
301
302 /// Does the argument look like a number
is_number(&self) -> bool303 pub fn is_number(&self) -> bool {
304 self.to_value()
305 .map(|s| s.parse::<f64>().is_ok())
306 .unwrap_or_default()
307 }
308
309 /// Treat as a long-flag
to_long(&self) -> Option<(Result<&str, &RawOsStr>, Option<&RawOsStr>)>310 pub fn to_long(&self) -> Option<(Result<&str, &RawOsStr>, Option<&RawOsStr>)> {
311 if let Some(raw) = self.utf8 {
312 let remainder = raw.strip_prefix("--")?;
313 if remainder.is_empty() {
314 debug_assert!(self.is_escape());
315 return None;
316 }
317
318 let (flag, value) = if let Some((p0, p1)) = remainder.split_once('=') {
319 (p0, Some(p1))
320 } else {
321 (remainder, None)
322 };
323 let flag = Ok(flag);
324 let value = value.map(RawOsStr::from_str);
325 Some((flag, value))
326 } else {
327 let raw = self.inner.as_ref();
328 let remainder = raw.strip_prefix("--")?;
329 if remainder.is_empty() {
330 debug_assert!(self.is_escape());
331 return None;
332 }
333
334 let (flag, value) = if let Some((p0, p1)) = remainder.split_once('=') {
335 (p0, Some(p1))
336 } else {
337 (remainder, None)
338 };
339 let flag = flag.to_str().ok_or(flag);
340 Some((flag, value))
341 }
342 }
343
344 /// Can treat as a long-flag
is_long(&self) -> bool345 pub fn is_long(&self) -> bool {
346 self.inner.as_ref().starts_with("--") && !self.is_escape()
347 }
348
349 /// Treat as a short-flag
to_short(&self) -> Option<ShortFlags<'_>>350 pub fn to_short(&self) -> Option<ShortFlags<'_>> {
351 if let Some(remainder_os) = self.inner.as_ref().strip_prefix('-') {
352 if remainder_os.starts_with('-') {
353 None
354 } else if remainder_os.is_empty() {
355 debug_assert!(self.is_stdio());
356 None
357 } else {
358 let remainder = self.utf8.map(|s| &s[1..]);
359 Some(ShortFlags::new(remainder_os, remainder))
360 }
361 } else {
362 None
363 }
364 }
365
366 /// Can treat as a short-flag
is_short(&self) -> bool367 pub fn is_short(&self) -> bool {
368 self.inner.as_ref().starts_with('-')
369 && !self.is_stdio()
370 && !self.inner.as_ref().starts_with("--")
371 }
372
373 /// Treat as a value
374 ///
375 /// **NOTE:** May return a flag or an escape.
to_value_os(&self) -> &RawOsStr376 pub fn to_value_os(&self) -> &RawOsStr {
377 self.inner.as_ref()
378 }
379
380 /// Treat as a value
381 ///
382 /// **NOTE:** May return a flag or an escape.
to_value(&self) -> Result<&str, &RawOsStr>383 pub fn to_value(&self) -> Result<&str, &RawOsStr> {
384 self.utf8.ok_or_else(|| self.inner.as_ref())
385 }
386
387 /// Safely print an argument that may contain non-UTF8 content
388 ///
389 /// This may perform lossy conversion, depending on the platform. If you would like an implementation which escapes the path please use Debug instead.
display(&self) -> impl std::fmt::Display + '_390 pub fn display(&self) -> impl std::fmt::Display + '_ {
391 self.inner.to_str_lossy()
392 }
393 }
394
395 /// Walk through short flags within a [`ParsedArg`]
396 #[derive(Clone, Debug)]
397 pub struct ShortFlags<'s> {
398 inner: &'s RawOsStr,
399 utf8_prefix: std::str::CharIndices<'s>,
400 invalid_suffix: Option<&'s RawOsStr>,
401 }
402
403 impl<'s> ShortFlags<'s> {
new(inner: &'s RawOsStr, utf8: Option<&'s str>) -> Self404 fn new(inner: &'s RawOsStr, utf8: Option<&'s str>) -> Self {
405 let (utf8_prefix, invalid_suffix) = if let Some(utf8) = utf8 {
406 (utf8, None)
407 } else {
408 split_nonutf8_once(inner)
409 };
410 let utf8_prefix = utf8_prefix.char_indices();
411 Self {
412 inner,
413 utf8_prefix,
414 invalid_suffix,
415 }
416 }
417
418 /// Move the iterator forward by `n` short flags
advance_by(&mut self, n: usize) -> Result<(), usize>419 pub fn advance_by(&mut self, n: usize) -> Result<(), usize> {
420 for i in 0..n {
421 self.next().ok_or(i)?.map_err(|_| i)?;
422 }
423 Ok(())
424 }
425
426 /// No short flags left
is_empty(&self) -> bool427 pub fn is_empty(&self) -> bool {
428 self.invalid_suffix.is_none() && self.utf8_prefix.as_str().is_empty()
429 }
430
431 /// Does the short flag look like a number
432 ///
433 /// Ideally call this before doing any iterator
is_number(&self) -> bool434 pub fn is_number(&self) -> bool {
435 self.invalid_suffix.is_none() && self.utf8_prefix.as_str().parse::<f64>().is_ok()
436 }
437
438 /// Advance the iterator, returning the next short flag on success
439 ///
440 /// On error, returns the invalid-UTF8 value
next_flag(&mut self) -> Option<Result<char, &'s RawOsStr>>441 pub fn next_flag(&mut self) -> Option<Result<char, &'s RawOsStr>> {
442 if let Some((_, flag)) = self.utf8_prefix.next() {
443 return Some(Ok(flag));
444 }
445
446 if let Some(suffix) = self.invalid_suffix {
447 self.invalid_suffix = None;
448 return Some(Err(suffix));
449 }
450
451 None
452 }
453
454 /// Advance the iterator, returning everything left as a value
next_value_os(&mut self) -> Option<&'s RawOsStr>455 pub fn next_value_os(&mut self) -> Option<&'s RawOsStr> {
456 if let Some((index, _)) = self.utf8_prefix.next() {
457 self.utf8_prefix = "".char_indices();
458 self.invalid_suffix = None;
459 return Some(&self.inner[index..]);
460 }
461
462 if let Some(suffix) = self.invalid_suffix {
463 self.invalid_suffix = None;
464 return Some(suffix);
465 }
466
467 None
468 }
469 }
470
471 impl<'s> Iterator for ShortFlags<'s> {
472 type Item = Result<char, &'s RawOsStr>;
473
next(&mut self) -> Option<Self::Item>474 fn next(&mut self) -> Option<Self::Item> {
475 self.next_flag()
476 }
477 }
478
split_nonutf8_once(b: &RawOsStr) -> (&str, Option<&RawOsStr>)479 fn split_nonutf8_once(b: &RawOsStr) -> (&str, Option<&RawOsStr>) {
480 match std::str::from_utf8(b.as_raw_bytes()) {
481 Ok(s) => (s, None),
482 Err(err) => {
483 let (valid, after_valid) = b.split_at(err.valid_up_to());
484 let valid = std::str::from_utf8(valid.as_raw_bytes()).unwrap();
485 (valid, Some(after_valid))
486 }
487 }
488 }
489