bash_builtins/
args.rs

1//! Module to implement the arguments processor.
2
3use crate::{ffi, Error};
4use std::ffi::CStr;
5use std::marker::PhantomData;
6use std::mem;
7use std::os::raw::c_int;
8use std::str::Utf8Error;
9
10/// This structure provides access to the command-line arguments passed to the
11/// builtin.
12///
13/// An instance for this type is sent as an argument of [`Builtin::call`].
14///
15/// # Options
16///
17/// The [`options`] method parses the arguments with the `internal_getopt`
18/// function provided by bash, like most builtins, and extract them as values
19/// of a type implementing [`BuiltinOptions`].
20///
21/// If the builtin does not expect any option, call to [`no_options`] before
22/// doing anything else.
23///
24/// # Free Arguments
25///
26/// The iterators returned by [`raw_arguments`], [`string_arguments`], and
27/// [`path_arguments`] yield the argument values.
28///
29/// If you use [`options`] before any of the `<type>_arguments` methods, the
30/// first item of the iteration is the first argument after the last parsed
31/// option.
32///
33/// The example below shows how to extract options and then process free
34/// arguments.
35///
36/// If the builtin accepts options, but no free arguments, call to
37/// [`finished`] after [`options`].
38///
39/// If the builtin does not expect any option or free argument, call to
40/// [`finished`] after [`no_options`].
41///
42/// # Example
43///
44/// ```
45/// use std::io::{stdout, BufWriter, Write};
46/// use bash_builtins::{Args, Builtin, BuiltinOptions, Result};
47///
48/// struct SomeName;
49///
50/// #[derive(BuiltinOptions)]
51/// enum Opt {
52///     #[opt = 'f']
53///     Foo,
54///
55///     #[opt = 'b']
56///     Bar(i64),
57/// }
58///
59/// impl Builtin for SomeName {
60///     fn call(&mut self, args: &mut Args) -> Result<()> {
61///         let mut foo = false;
62///         let mut bar = 0;
63///
64///         for option in args.options() {
65///             match option? {
66///                 Opt::Foo => foo = true,
67///                 Opt::Bar(b) => bar = b,
68///             }
69///         }
70///
71///         let stdout_handle = stdout();
72///         let mut output = BufWriter::new(stdout_handle.lock());
73///
74///         writeln!(&mut output, "{}, {}", foo, bar)?;
75///
76///         for path in args.path_arguments() {
77///             writeln!(&mut output, "{}", path.display())?;
78///         }
79///
80///         Ok(())
81///     }
82/// }
83/// ```
84///
85/// [`Builtin::call`]: crate::Builtin::call
86/// [`BuiltinOptions`]: bash_builtins_macro::BuiltinOptions
87/// [`finished`]: Args::finished
88/// [`no_options`]: Args::no_options
89/// [`options`]: Args::options
90/// [`path_arguments`]: Args::path_arguments
91/// [`raw_arguments`]: Args::raw_arguments
92/// [`string_arguments`]: Args::string_arguments
93pub struct Args {
94    word_list: *const ffi::WordList,
95    reset_pending: bool,
96}
97
98impl Args {
99    /// Create a new instance to wrap the `word_list` created by bash.
100    ///
101    /// # Safety
102    ///
103    /// The method is unsafe for two reasons:
104    ///
105    /// * The caller has to provide a valid `word_list` pointer, which
106    ///   is sent by bash when the builtin function is invoked.
107    ///
108    /// * This `Args` instance has to be dropped before returning to bash,
109    ///   since the `word_list` address will be reused by bash to something
110    ///   else.
111    ///
112    ///   This requirement is accomplished by using a mutable reference
113    ///   (`&mut Args`) when calling to `Builtin::call`. Thus, users can't
114    ///   keep a copy of this `Args` instance.
115    #[doc(hidden)]
116    pub unsafe fn new(word_list: *const ffi::WordList) -> Args {
117        Args {
118            word_list,
119            reset_pending: true,
120        }
121    }
122
123    /// Returns `true` if there are no more arguments.
124    pub fn is_empty(&self) -> bool {
125        self.word_list.is_null()
126    }
127
128    /// Returns an iterator to parse the command-line arguments with the
129    /// `getops` function provided by bash.
130    ///
131    /// The generic type `T` implements the [`BuiltinOptions`] trait. See its
132    /// documentation for details on how to create the parser.
133    ///
134    /// # Example
135    ///
136    /// See the [example](struct.Args.html#example) in the [`Args`](self::Args)
137    /// documentation.
138    ///
139    /// [`BuiltinOptions`]: derive.BuiltinOptions.html
140    pub fn options<'a, T>(&'a mut self) -> impl Iterator<Item = crate::Result<T>> + 'a
141    where
142        T: crate::BuiltinOptions<'a> + 'a,
143    {
144        self.ensure_reset();
145        OptionsIterator {
146            args: self,
147            phantom: PhantomData,
148        }
149    }
150
151    /// Returns an iterator to get the arguments passed to the builtin.
152    ///
153    /// Each item is an instance of [`CStr`], and its lifetime is bound to the
154    /// [`Args`] instance.
155    ///
156    /// If this method is called after [`options`](Args::options), the first
157    /// item of the iteration is the first argument after the last parsed
158    /// option.
159    ///
160    /// It is recommended to use [`path_arguments`] if the builtin expects file
161    /// names as arguments, or [`string_arguments`] if it expects valid UTF-8
162    /// strings.
163    ///
164    /// # Example
165    ///
166    /// See [`path_arguments`] for an example.
167    ///
168    /// [`CStr`]: std::ffi::CStr
169    /// [`path_arguments`]: Args::path_arguments
170    /// [`string_arguments`]: Args::string_arguments
171    pub fn raw_arguments(&mut self) -> impl Iterator<Item = &'_ CStr> {
172        self.ensure_reset();
173
174        WordListIterator(self)
175    }
176
177    /// Like [`raw_arguments`], but items are [`Path`] instances.
178    ///
179    /// Use this iterator if the builtin arguments are file names.
180    ///
181    /// # Example
182    ///
183    /// ```
184    /// use std::path::Path;
185    /// use bash_builtins::{Args, Builtin, Result};
186    ///
187    /// struct SomeName;
188    ///
189    /// impl Builtin for SomeName {
190    ///     fn call(&mut self, args: &mut Args) -> Result<()> {
191    ///         args.no_options()?;
192    ///
193    ///         for path in args.path_arguments() {
194    ///             process(path)?;
195    ///         }
196    ///
197    ///         Ok(())
198    ///     }
199    /// }
200    ///
201    /// fn process(path: &Path) -> Result<()> {
202    ///     // …
203    /// #   let _ = path;
204    ///     Ok(())
205    /// }
206    /// ```
207    ///
208    /// [`raw_arguments`]: Args::raw_arguments
209    /// [`Path`]: std::path::Path
210    #[cfg(unix)]
211    #[cfg_attr(docsrs, doc(cfg(unix)))]
212    pub fn path_arguments(&mut self) -> impl Iterator<Item = &'_ std::path::Path> {
213        use std::ffi::OsStr;
214        use std::os::unix::ffi::OsStrExt;
215        use std::path::Path;
216
217        self.raw_arguments()
218            .map(|a| Path::new(OsStr::from_bytes(a.to_bytes())))
219    }
220
221    /// Like [`raw_arguments`], but each item is a string reference if the
222    /// argument contains valid UTF-8 data, or a [`Utf8Error`] otherwise.
223    ///
224    /// [`Utf8Error`]: std::str::Utf8Error
225    /// [`raw_arguments`]: Args::raw_arguments
226    pub fn string_arguments(&mut self) -> impl Iterator<Item = Result<&'_ str, Utf8Error>> {
227        self.raw_arguments()
228            .map(|a| std::str::from_utf8(a.to_bytes()))
229    }
230
231    /// Returns an error if there are more arguments to be processed.
232    ///
233    /// If the builtin accepts options but no free arguments, then this method
234    /// should be called after [`options`].
235    ///
236    /// # Example
237    ///
238    /// ```
239    /// # use bash_builtins::{Args, Builtin, BuiltinOptions, Result};
240    /// # struct SomeName;
241    /// #[derive(BuiltinOptions)]
242    /// enum Opt {
243    ///     // Builtin options.
244    /// #   #[opt = 'a'] A,
245    /// }
246    ///
247    /// impl Builtin for SomeName {
248    ///     fn call(&mut self, args: &mut Args) -> Result<()> {
249    ///         for option in args.options() {
250    ///             match option? {
251    ///                 // Parse options.
252    /// #               Opt::A => ()
253    ///             }
254    ///         }
255    ///
256    ///         // This builtin does not accept free arguments.
257    ///         args.finished()?;
258    ///
259    /// #       fn run_builtin_with_options() -> Result<()> { Ok(()) }
260    ///         run_builtin_with_options()?;
261    ///
262    ///         Ok(())
263    ///     }
264    /// }
265    /// ```
266    ///
267    /// [`options`]: Args::options
268    pub fn finished(&mut self) -> crate::Result<()> {
269        if self.word_list.is_null() {
270            Ok(())
271        } else {
272            crate::log::error("too many arguments");
273            Err(Error::Usage)
274        }
275    }
276
277    /// Returns an error if any option is passed as the first argument.
278    ///
279    /// If the builtin expects no options, then call this method before doing
280    /// anything else.
281    ///
282    /// It uses the `no_options` function provided by bash. The special option
283    /// `--help` is handled properly.
284    ///
285    /// # Example
286    ///
287    /// ```
288    /// // Builtin to convert to uppercase its arguments.
289    ///
290    /// # use std::io::{self, BufWriter, Write};
291    /// # use bash_builtins::{Args, Builtin, Result};
292    /// # struct Upcase;
293    /// impl Builtin for Upcase {
294    ///     fn call(&mut self, args: &mut Args) -> Result<()> {
295    ///         args.no_options()?;
296    ///
297    ///         let stdout_handle = io::stdout();
298    ///         let mut output = BufWriter::new(stdout_handle.lock());
299    ///
300    ///         for argument in args.string_arguments() {
301    ///             writeln!(&mut output, "{}", argument?.to_uppercase())?;
302    ///         }
303    ///
304    ///         Ok(())
305    ///     }
306    /// }
307    /// ```
308    pub fn no_options(&mut self) -> crate::Result<()> {
309        if unsafe { ffi::no_options(self.word_list) } == 0 {
310            Ok(())
311        } else {
312            Err(Error::Usage)
313        }
314    }
315
316    /// Reset `internal_getopt` state.
317    #[inline]
318    fn ensure_reset(&mut self) {
319        if mem::take(&mut self.reset_pending) {
320            unsafe {
321                ffi::reset_internal_getopt();
322                ffi::list_optarg = std::ptr::null();
323                ffi::list_optopt = 0;
324            };
325        }
326    }
327}
328
329struct WordListIterator<'a>(&'a mut Args);
330
331impl<'a> Iterator for WordListIterator<'a> {
332    type Item = &'a CStr;
333
334    fn next(&mut self) -> Option<Self::Item> {
335        if self.0.word_list.is_null() {
336            return None;
337        }
338
339        let word = unsafe {
340            let current = &*self.0.word_list;
341            self.0.word_list = current.next;
342            CStr::from_ptr((*current.word).word)
343        };
344
345        Some(word)
346    }
347}
348
349/// Trait implemented by the `BuiltinOptions` derive macro.
350#[doc(hidden)]
351pub trait BuiltinOptions<'a>: Sized {
352    fn options() -> &'static [u8];
353
354    fn from_option(opt: c_int, arg: Option<&'a CStr>) -> crate::Result<Self>;
355}
356
357struct OptionsIterator<'a, T> {
358    args: &'a mut Args,
359    phantom: PhantomData<T>,
360}
361
362impl<'a, T: BuiltinOptions<'a>> Iterator for OptionsIterator<'a, T> {
363    type Item = crate::Result<T>;
364
365    fn next(&mut self) -> Option<Self::Item> {
366        let opt =
367            unsafe { ffi::internal_getopt(self.args.word_list, T::options().as_ptr().cast()) };
368
369        match opt {
370            ffi::GETOPT_EOF => {
371                self.args.word_list = unsafe { ffi::loptend };
372                None
373            }
374
375            ffi::GETOPT_HELP => {
376                crate::log::show_help();
377                Some(Err(Error::Usage))
378            }
379
380            _ => Some(T::from_option(opt, unsafe { Self::optarg() })),
381        }
382    }
383}
384
385impl<'a, T> OptionsIterator<'a, T> {
386    unsafe fn optarg() -> Option<&'a CStr> {
387        let optarg = ffi::list_optarg;
388        if optarg.is_null() {
389            None
390        } else {
391            Some(::std::ffi::CStr::from_ptr(optarg))
392        }
393    }
394}
395
396#[cfg(test)]
397mod tests {
398    use super::*;
399    use ffi::{WordDesc, WordList};
400
401    #[test]
402    fn update_word_list_after_arguments() {
403        let words = [
404            WordDesc {
405                word: b"abc\0".as_ptr().cast(),
406                flags: 0,
407            },
408            WordDesc {
409                word: b"def\0".as_ptr().cast(),
410                flags: 0,
411            },
412        ];
413
414        let wl1 = WordList {
415            word: &words[1],
416            next: std::ptr::null(),
417        };
418
419        let wl0 = WordList {
420            word: &words[0],
421            next: &wl1,
422        };
423
424        let mut args = unsafe { Args::new(&wl0) };
425
426        let mut string_args = args.string_arguments();
427        assert_eq!(string_args.next(), Some(Ok("abc")));
428        assert_eq!(string_args.next(), Some(Ok("def")));
429        assert_eq!(string_args.next(), None);
430        drop(string_args);
431
432        args.finished().unwrap();
433    }
434}