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}