dia_args/
args.rs

1/*
2==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--
3
4Dia-Args
5
6Copyright (C) 2018-2019, 2021-2025  Anonymous
7
8There are several releases over multiple years,
9they are listed as ranges, such as: "2018-2019".
10
11This program is free software: you can redistribute it and/or modify
12it under the terms of the GNU Lesser General Public License as published by
13the Free Software Foundation, either version 3 of the License, or
14(at your option) any later version.
15
16This program is distributed in the hope that it will be useful,
17but WITHOUT ANY WARRANTY; without even the implied warranty of
18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19GNU Lesser General Public License for more details.
20
21You should have received a copy of the GNU Lesser General Public License
22along with this program.  If not, see <https://www.gnu.org/licenses/>.
23
24::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--
25*/
26
27//! # Arguments
28
29mod stream;
30
31use {
32    alloc::{
33        collections::{
34            BTreeMap,
35            VecDeque,
36            btree_map::Entry,
37        },
38        sync::Arc,
39    },
40    core::{
41        any::TypeId,
42        fmt::Debug,
43        mem,
44        str::FromStr,
45    },
46    std::{
47        env,
48        fs::File,
49        io::{BufReader, Error, ErrorKind, Read},
50        path::Path,
51        sync::RwLock,
52    },
53    crate::{MergeOption, Result},
54    self::{
55        kind::Kind,
56        stream::*,
57        value::Value,
58    },
59};
60
61#[cfg(test)]
62mod tests;
63
64mod kind;
65mod value;
66
67type Options = BTreeMap<String, Vec<Value>>;
68
69const TRUE_AS_STR: &str = "true";
70const FALSE_AS_STR: &str = "false";
71
72/// # Description of argument file format, in English
73///
74/// This constant can be useful if you want to include in your program's documentation. It has a title and its own content. So you don't have to
75/// prefix with any message/description.
76///
77/// ## Examples
78///
79/// ```
80/// use std::borrow::Cow;
81/// use dia_args::{
82///     DIA_ARGS_FILE_FORMAT, DIA_ARGS_FILE_NAME,
83///     docs::{Cfg, Docs, I18n},
84/// };
85///
86/// let docs = format!(
87///     concat!(
88///         // Here is program documentation.
89///         "This program does something.\n\n",
90///         "Options can be set via either command line or from {:?} file at root",
91///         " directory of the program. Options set via command line will",
92///         " override the ones from file.\n\n",
93///
94///         // Here is description of argument file format. Note that we don't need
95///         // a title for it.
96///         "{}",
97///     ),
98///     DIA_ARGS_FILE_NAME, DIA_ARGS_FILE_FORMAT,
99/// );
100/// Docs::new(Cow::Borrowed("Some Program"), Cow::Owned(docs)).print()?;
101///
102/// # Ok::<_, std::io::Error>(())
103/// ```
104pub const DIA_ARGS_FILE_FORMAT: &str = concat!(
105    "Argument file format:\n\n",
106    "- Empty lines or lines starting with `#` will be ignored.\n",
107    "- Each command, argument, or option must be placed on a separate line.\n",
108    "- Option key and value are separated by either: equal symbol `=` (can have leading/trailing white spaces), or at least one white space.",
109    ' ', "Key and value will be trimmed.",
110);
111
112#[test]
113fn test_dia_args_file_format() -> Result<()> {
114    use crate::docs::Docs;
115
116    Docs::new("Some Program".into(), format!("This program does something.\n\n{}", DIA_ARGS_FILE_FORMAT).into()).print()?;
117
118    Ok(())
119}
120
121const DASH: char = '-';
122
123/// # Arguments
124///
125/// ## Examples
126///
127/// ```
128/// use dia_args;
129///
130/// let mut args = dia_args::parse_strings(["run", "--debug", "--port=99"])?;
131/// assert_eq!(args.take(&["--debug"])?, Some(true));
132/// assert_eq!(args.take::<u16>(&["--port"])?.unwrap(), 99);
133///
134/// # dia_args::Result::Ok(())
135/// ```
136#[derive(Default, Debug)]
137pub struct Args {
138    args: VecDeque<Value>,
139    options: Options,
140    sub_args: Vec<String>,
141}
142
143impl Args {
144
145    /// # Gets the command
146    pub fn cmd(&self) -> Option<&str> {
147        match self.args.front() {
148            None => None,
149            Some(Value::Owned(s)) => Some(s.as_ref()),
150            Some(Value::Borrowed(_)) => None,
151        }
152    }
153
154    /// # Sub arguments
155    pub fn sub_args(&self) -> &[String] {
156        &self.sub_args
157    }
158
159    /// # Checks if there are no arguments/options/sub arguments
160    pub fn is_empty(&self) -> bool {
161        self.options.is_empty() && self.sub_args.is_empty() && self.args.iter().filter(|a| match a {
162            Value::Owned(_) => true,
163            Value::Borrowed(a) => match a.try_read() {
164                Ok(a) => a.is_some(),
165                Err(_) => true,
166            },
167        }).count() == usize::MIN
168    }
169
170    /// # Transforms into sub command
171    ///
172    /// For example:
173    ///
174    /// - Command line:
175    ///     ```shell
176    ///     ~> program help version 1
177    ///     ```
178    ///
179    /// - Parsed as:
180    ///     ```code
181    ///     help version 1
182    ///     ```
183    ///
184    /// - After calling this function:
185    ///     ```code
186    ///     version 1
187    ///     ```
188    ///
189    /// ```
190    /// use dia_args;
191    ///
192    /// const CMD_VERSION: &str = "version";
193    ///
194    /// let (cmd, args) = dia_args::parse()?.try_into_sub_cmd()?;
195    /// match cmd.as_ref().map(|s| s.as_str()) {
196    ///     Some(CMD_VERSION) => if args.is_empty() {
197    ///         println!("Version: ...");
198    ///     } else {
199    ///         eprintln!("{:?} command doesn't take arguments", CMD_VERSION);
200    ///     },
201    ///     Some(other) => eprintln!("Command {:?} not supported", other),
202    ///     None => eprintln!("Missing command"),
203    /// };
204    ///
205    /// # Ok::<_, std::io::Error>(())
206    /// ```
207    pub fn try_into_sub_cmd(mut self) -> Result<(Option<String>, Self)> {
208        self.clean_up_args()?;
209        match self.args.pop_front() {
210            None => Ok((None, self)),
211            Some(Value::Owned(s)) => Ok((Some(s), self)),
212            Some(Value::Borrowed(_)) => Err(Error::new(ErrorKind::Unsupported, "The first argument is not owned")),
213        }
214    }
215
216    /// # Takes an option using given keys
217    ///
218    /// ## Examples
219    ///
220    /// ```
221    /// use dia_args;
222    ///
223    /// let mut args = dia_args::parse_strings(["--type", "rs"])?;
224    /// assert_eq!(args.take::<String>(&["--type"])?.unwrap(), "rs");
225    /// assert!(args.take::<String>(&["--type"])?.is_none());
226    ///
227    /// # Ok::<_, std::io::Error>(())
228    /// ```
229    pub fn take<T>(&mut self, keys: &[&str]) -> Result<Option<T>> where T: FromStr + 'static, <T as FromStr>::Err: Debug {
230        let mut result = None;
231        for key in keys {
232            let parse = |s| T::from_str(s).map_err(|err|
233                Error::new(ErrorKind::InvalidData, format!("Failed parsing value {s:?} of {key:?}: {err:?}"))
234            );
235            if let Some(mut values) = self.options.remove(*key) {
236                if result.is_some() {
237                    return Err(Error::new(ErrorKind::InvalidData, format!("Duplicate value for {key:?}")));
238                }
239                match values.len() {
240                    0 => if TypeId::of::<T>() == TypeId::of::<bool>() {
241                        result = Some(T::from_str(TRUE_AS_STR).map_err(|_| err!())?);
242                    } else {
243                        return Err(Error::new(ErrorKind::InvalidData, format!("Missing value for {key:?}")));
244                    },
245                    1 => match values.remove(usize::MIN) {
246                        Value::Owned(v) => result = Some(parse(&v)?),
247                        Value::Borrowed(v) => {
248                            let mut v = v.try_write().map_err(|e| err!("{e}"))?;
249                            let s = v.take().ok_or_else(|| err!())?;
250                            if TypeId::of::<T>() == TypeId::of::<bool>() {
251                                match s.as_str() {
252                                    TRUE_AS_STR | FALSE_AS_STR => {
253                                        result = Some(parse(&s)?);
254                                        drop(v);
255                                        self.clean_up_args()?;
256                                    },
257                                    _ => {
258                                        *v = Some(s);
259                                        result = Some(T::from_str(TRUE_AS_STR).map_err(|_| err!())?);
260                                    },
261                                };
262                            } else {
263                                result = Some(parse(&s)?);
264                                drop(v);
265                                self.clean_up_args()?;
266                            }
267                        },
268                    },
269                    _ => return Err(Error::new(ErrorKind::InvalidData, format!("Expected 1 value, got: {values:?}"))),
270                };
271            }
272        }
273        Ok(result)
274    }
275
276    /// # Cleans up args
277    fn clean_up_args(&mut self) -> Result<()> {
278        let args = VecDeque::with_capacity(self.args.len());
279        self.args = mem::take(&mut self.args).into_iter().try_fold(args, |mut result, next| {
280            match &next {
281                Value::Owned(_) => result.push_back(next),
282                Value::Borrowed(a) => if a.try_read().map_err(|e| err!("{e}"))?.is_some() {
283                    result.push_back(next);
284                },
285            };
286            Result::Ok(result)
287        })?;
288        Ok(())
289    }
290
291    /// # Takes an option using given keys
292    ///
293    /// ## Examples
294    ///
295    /// ```
296    /// use dia_args;
297    ///
298    /// let mut args = dia_args::parse_strings(["-l", "c", "-l", "c++"])?;
299    /// let mut languages = args.take_vec::<String>(&["-l"])?.unwrap();
300    /// languages.sort();
301    /// assert_eq!(languages, &["c", "c++"]);
302    /// assert!(args.is_empty());
303    ///
304    /// # Ok::<_, std::io::Error>(())
305    /// ```
306    pub fn take_vec<T>(&mut self, keys: &[&str]) -> Result<Option<Vec<T>>> where T: FromStr, <T as FromStr>::Err: Debug {
307        let mut result: Option<Vec<_>> = None;
308
309        for key in keys {
310            if let Some(values) = self.options.remove(*key) {
311                let count = values.len();
312                for (index, value) in values.into_iter().enumerate() {
313                    let value = match value {
314                        Value::Owned(s) => s,
315                        Value::Borrowed(s) => {
316                            let s = s.try_write().map_err(|e| err!("{e}"))?.take().ok_or_else(|| err!())?;
317                            self.clean_up_args()?;
318                            s
319                        },
320                    };
321                    let value = T::from_str(&value)
322                        .map_err(|err| Error::new(ErrorKind::InvalidData, format!("Failed parsing value {value:?} of {key:?}: {err:?}")))?;
323                    match result.as_mut() {
324                        Some(result) => {
325                            if index == usize::MIN {
326                                result.reserve(count);
327                            }
328                            result.push(value);
329                        },
330                        None => result = Some({
331                            let mut result = Vec::with_capacity(count);
332                            result.push(value);
333                            result
334                        }),
335                    };
336                }
337            }
338        }
339
340        Ok(result)
341    }
342
343    /// # Takes arguments out
344    ///
345    /// ## Notes
346    ///
347    /// This function might return an error if:
348    ///
349    /// -   There is at least one option left.
350    /// -   *And* an argument is not owned.
351    ///
352    /// ## Examples
353    ///
354    /// ```
355    /// use dia_args;
356    ///
357    /// let mut args = dia_args::parse_strings(["do", "this", "--debug"])?;
358    /// assert_eq!(args.take_args()?, &["do", "this"]);
359    ///
360    /// # Ok::<_, std::io::Error>(())
361    /// ```
362    pub fn take_args(&mut self) -> Result<Vec<String>> {
363        {
364            let has_options = self.options.is_empty() == false;
365            for a in &self.args {
366                match a {
367                    Value::Owned(_) => continue,
368                    Value::Borrowed(a) => if has_options {
369                        return Err(Error::new(ErrorKind::InvalidData, format!("This argument is not owned: {a:?}")));
370                    },
371                };
372            }
373        }
374
375        let result = Vec::with_capacity(self.args.len());
376        self.args.drain(..).try_fold(result, |mut result, next| {
377            match next {
378                Value::Owned(s) => result.push(s),
379                Value::Borrowed(s) => if let Some(s) = s.try_write().map_err(|e| err!("{e}"))?.take() {
380                    result.push(s);
381                },
382            };
383            Ok(result)
384        })
385    }
386
387    /// # Takes sub arguments out
388    ///
389    /// ## Examples
390    ///
391    /// ```
392    /// use dia_args;
393    ///
394    /// let mut args = dia_args::parse_strings(
395    ///     ["eat", "chicken", "--", "with", "ronnie-coleman"]
396    /// )?;
397    /// assert_eq!(args.take_sub_args(), &["with", "ronnie-coleman"]);
398    ///
399    /// # Ok::<_, std::io::Error>(())
400    /// ```
401    pub fn take_sub_args(&mut self) -> Vec<String> {
402        mem::take(&mut self.sub_args)
403    }
404
405    /// # Merges _options_ with other
406    ///
407    /// - This function works on _options_, not commands/sub arguments...
408    /// - Other's options will be taken out, if conditions are met.
409    /// - Result is number of items merged.
410    ///
411    /// ## Parameters
412    ///
413    /// - `filter`:
414    ///
415    ///     + If you provide some sets of keys, only those (from other) are accepted.
416    ///     + If you provide an empty slice, or any of its items is empty, an error is returned.
417    ///
418    /// ## Examples
419    ///
420    /// Your program allows the user to set options from file. Later you want to give the user new ability to set options via command line,
421    /// overwriting the ones from file. Then this function can help.
422    ///
423    /// ```
424    /// use dia_args::MergeOption;
425    ///
426    /// const OPTION_DEBUG: &[&str] = &["-d", "--debug"];
427    /// const OPTION_PORT: &[&str] = &["--port"];
428    ///
429    /// // Here in test, we're parsing from strings.
430    /// // In real code, you might want to use dia_args::parse_file()
431    /// let mut args_from_file = dia_args::parse_strings(
432    ///     ["--debug=false", "--port=6789"]
433    /// )?;
434    ///
435    /// // Command line arguments
436    /// let mut cmd_line_args = dia_args::parse_strings(
437    ///     ["-d=true", "--address", "localhost"]
438    /// )?;
439    ///
440    /// // Merge
441    /// let count = cmd_line_args.merge_options(
442    ///     &mut args_from_file, &[OPTION_DEBUG, OPTION_PORT], MergeOption::IgnoreExisting,
443    /// )?;
444    /// assert_eq!(count, 1);
445    ///
446    /// // Verify
447    /// assert_eq!(cmd_line_args.take(OPTION_DEBUG)?, Some(true));
448    /// assert_eq!(cmd_line_args.take::<String>(&["--address"])?.unwrap(), "localhost");
449    /// assert_eq!(cmd_line_args.take::<u16>(OPTION_PORT)?, Some(6789));
450    ///
451    /// # Ok::<_, std::io::Error>(())
452    /// ```
453    pub fn merge_options(&mut self, other: &mut Self, filter: &[&[&str]], merge_option: MergeOption) -> Result<usize> {
454        if filter.is_empty() || filter.iter().any(|keys| keys.is_empty()) {
455            return Err(Error::new(ErrorKind::InvalidInput, format!("Invalid filter: {:?}", filter)));
456        }
457
458        let mut count = 0;
459
460        for (key, other_value) in mem::take(&mut other.options) {
461            let keys = {
462                match filter.iter().find(|keys| keys.contains(&key.as_str())) {
463                    Some(keys) => keys,
464                    None => {
465                        other.options.insert(key, other_value);
466                        continue;
467                    },
468                }
469            };
470            match merge_option {
471                MergeOption::TakeAll => keys.iter().for_each(|k| drop(self.options.remove(*k))),
472                MergeOption::IgnoreExisting => if keys.iter().any(|k| self.options.contains_key(*k)) {
473                    other.options.insert(key, other_value);
474                    continue;
475                },
476            };
477
478            self.options.insert(key, other_value);
479            count += 1;
480        }
481
482        Ok(count)
483    }
484
485}
486
487/// # Parses from an iterator of strings
488pub fn parse_strings<S, I>(args: I) -> Result<Args> where S: AsRef<str>, I: IntoIterator<Item=S> {
489    let args = args.into_iter();
490    let mut result = Args {
491        args: VecDeque::with_capacity(args.size_hint().0),
492        options: BTreeMap::new(),
493        sub_args: Vec::new(),
494    };
495
496    let mut args = args.peekable();
497    while let Some(arg) = args.next() {
498        let arg = arg.as_ref();
499        match Kind::parse(arg)? {
500            Kind::Command => {
501                let arg = arg.trim();
502                if arg.is_empty() == false {
503                    result.args.push_back(Value::Owned(arg.to_string()));
504                }
505            },
506            Kind::ShortOption | Kind::LongOption => {
507                let value = match args.peek().map(|s| s.as_ref().starts_with(DASH)) {
508                    Some(true) | None => None,
509                    Some(false) => {
510                        let value = Arc::new(RwLock::new(Some(args.next().map(|s| s.as_ref().to_string()).ok_or_else(||
511                            // This shouldn't happen, but it's better than ::unwrap()
512                            Error::new(ErrorKind::InvalidData, format!("Missing value for {:?}", &arg))
513                        )?)));
514                        result.args.push_back(Value::Borrowed(value.clone()));
515                        Some(Value::Borrowed(value))
516                    },
517                };
518                add_option(&mut result.options, arg.to_string(), value)?;
519            },
520            Kind::ShortOptionWithValue { option, value } | Kind::LongOptionWithValue { option, value } => {
521                add_option(&mut result.options, option, Some(Value::Owned(value)))?;
522            },
523            Kind::SubArgsSeparator => {
524                result.sub_args = args.map(|s| s.as_ref().to_string()).collect();
525                break;
526            },
527        };
528    }
529
530    Ok(result)
531}
532
533/// # Adds option
534fn add_option(options: &mut Options, option: String, value: Option<Value>) -> Result<()> {
535    match options.entry(option) {
536        Entry::Vacant(vacant) => drop(match value {
537            None => vacant.insert(vec!()),
538            Some(value) => vacant.insert(vec!(value)),
539        }),
540        Entry::Occupied(mut occupied) => match value {
541            None => return Err(Error::new(ErrorKind::InvalidData, format!("Expected 1 value for {:?}", occupied.key()))),
542            Some(value) => occupied.get_mut().push(value),
543        },
544    };
545    Ok(())
546}
547
548/// # Parses from process' arguments
549pub fn parse() -> Result<Args> {
550    parse_strings(env::args().skip(1))
551}
552
553/// # Parses from file
554///
555/// ## Rules
556///
557/// ### Default file
558///
559/// - Default file is a file named [`DIA_ARGS_FILE_NAME`][const:DIA_ARGS_FILE_NAME] within directory of the program. On Unix, if the program
560///   is a symlink, its parent directory is used. The parent directory of the original file is ***not*** used.<sup><a id='::&1' href='#::1'>
561///   `[1]`</a></sup>
562/// - If `None` is given, default file will be used.
563/// - If the file does not exist, `None` is returned.
564///
565/// ### Limits and syntax
566///
567/// - If `max_size` is zero, an error is returned. If `None`, [`MAX_DIA_ARGS_FILE_SIZE`][const:MAX_DIA_ARGS_FILE_SIZE] will be used. If the
568///   file's size is larger than provided value, an error is returned.
569/// - Empty lines or lines starting with `#` will be ignored.
570/// - Each command, argument, or option must be placed on a separate line.
571/// - Normally, a shell will remove leading/trailing marks such as `"..."` or `'...'`. ***However*** those are _not_ required in this file. So
572///   you can separate options like these:
573///
574///     ```shell
575///     --passphrase=secret passphrase with white-spaces in it
576///     --passphrase        =       secret passphrase with white-spaces in it
577///     --passphrase        secret passphrase with white-spaces in it
578///     ```
579///
580///   They're all the same. Also, note that values will be trimmed.
581///
582/// ---
583///
584/// 1. <a id='::1' href='#::&1'>`^^`</a> In theory, that is the goal. However [`env::current_exe()`][fn:env/current_exe] function might
585///    return the original file (not the symlink). In that case, the parent directory of the original file will be used.
586///
587/// [const:DIA_ARGS_FILE_NAME]: constant.DIA_ARGS_FILE_NAME.html
588/// [const:MAX_DIA_ARGS_FILE_SIZE]: constant.MAX_DIA_ARGS_FILE_SIZE.html
589/// [fn:env/current_exe]: https://doc.rust-lang.org/std/env/fn.current_exe.html
590pub fn parse_file<P>(file: Option<P>, max_size: Option<u64>) -> Result<Option<Args>> where P: AsRef<Path> {
591    // NOTES:
592    //
593    // - If you change file format, update documentation of this function *and* DIA_ARGS_FILE_FORMAT constant.
594
595    let max_size = max_size.unwrap_or(crate::MAX_DIA_ARGS_FILE_SIZE);
596    if max_size == 0 {
597        return Err(Error::new(ErrorKind::InvalidInput, "max_size must be larger than 0"));
598    }
599
600    let current_exe = env::current_exe()?;
601    let file = match file.map(|f| f.as_ref().to_path_buf()) {
602        Some(file) => file,
603        None => match current_exe.parent() {
604            Some(dir) => dir.join(crate::DIA_ARGS_FILE_NAME),
605            None => return Err(Error::new(ErrorKind::NotFound, "Could not find parent directory of the program")),
606        },
607    };
608    let file = if file.exists() {
609        if file.is_file() {
610            match file.canonicalize() {
611                Ok(file) => file,
612                Err(err) => return Err(Error::new(err.kind(), format!("Failed getting canonical path of {file:?}: {err:?}"))),
613            }
614        } else {
615            return Err(Error::new(ErrorKind::InvalidInput, format!("Not a file: {file:?}")));
616        }
617    } else {
618        return Ok(None);
619    };
620
621    // Check file size
622    let file_size = file
623        .metadata().map_err(|e| Error::new(ErrorKind::Other, format!("Failed getting file size of {file:?}: {e:?}")))?
624        .len();
625    if file_size > max_size {
626        return Err(Error::new(ErrorKind::InvalidInput, format!("File too large: {file:?} (max allowed: {max_size})")));
627    }
628
629    let mut args: Vec<String> = vec![];
630    for line in read_file_to_string(file)?.lines() {
631        let line = line.trim();
632        if line.is_empty() || line.starts_with('#') {
633            continue;
634        }
635        if line.starts_with(DASH) == false || false == ['=', ' ', '\t'].iter().any(|separator| match line.find(*separator) {
636            Some(idx) => {
637                args.push(format!("{}={}", line[..idx].trim(), line[idx + 1..].trim()));
638                true
639            },
640            None => false,
641        }) {
642            args.push(line.to_owned());
643        }
644    }
645
646    parse_strings(args.into_iter()).map(|args| Some(args))
647}
648
649/// # Reads file to string
650fn read_file_to_string<P>(file: P) -> Result<String> where P: AsRef<Path> {
651    const BUF_SIZE: usize = 8 * 1024;
652
653    let file = file.as_ref();
654    let limit = file.metadata()?.len();
655    let mut reader = BufReader::new(File::open(file)?).take(limit);
656
657    let mut buf = [0; BUF_SIZE];
658    let mut data = Vec::with_capacity(limit.try_into().map_err(|_| err!())?);
659    loop {
660        match reader.read(&mut buf)? {
661            0 => return String::from_utf8(data).map_err(|e| Error::new(ErrorKind::InvalidData, e)),
662            read => data.extend(&buf[..read]),
663        };
664    }
665}
666
667/// # Parses a stream of strings, separated by null byte (`0`)
668///
669/// This function can be useful for securely passing/parsing arguments across processes.
670///
671/// ## Notes
672///
673/// - If `max_size` is zero, an error is returned. If `None`, [`MAX_DIA_ARGS_FILE_SIZE`][const:MAX_DIA_ARGS_FILE_SIZE] will be used. If the
674///   stream has more data than provided value, an error is returned.
675/// - If the stream contains an invalid UTF-8 string, an error is returned.
676/// - The stream is used as-is. So you might want to use [`BufReader`][struct:BufReader].
677///
678/// ## Examples
679///
680/// ```
681/// let stream = b"run\0--faster=true";
682///
683/// let mut args = dia_args::parse_stream(&mut &stream[..], None)?;
684/// assert_eq!(args.cmd(), Some("run"));
685/// assert_eq!(args.take(&["--faster"])?, Some(true));
686///
687/// # Ok::<_, std::io::Error>(())
688/// ```
689///
690/// [const:MAX_DIA_ARGS_FILE_SIZE]: constant.MAX_DIA_ARGS_FILE_SIZE.html
691/// [struct:BufReader]: https://doc.rust-lang.org/std/io/struct.BufReader.html
692pub fn parse_stream<R>(stream: &mut R, max_size: Option<u64>) -> Result<Args> where R: Read {
693    let max_size = max_size.unwrap_or(crate::MAX_DIA_ARGS_FILE_SIZE);
694    if max_size == 0 {
695        return Err(Error::new(ErrorKind::InvalidInput, "max_size must be larger than 0"));
696    }
697
698    let mut strings = Vec::with_capacity(128);
699    for s in Stream::make(stream, max_size)? {
700        strings.push(s?);
701    }
702    parse_strings(strings.into_iter())
703}