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}