patharg/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2//! Most CLI commands that take file paths as arguments follow the convention
3//! of treating a path of `-` (a single hyphen/dash) as referring to either
4//! standard input or standard output (depending on whether the path is read
5//! from or written to). The `patharg` crate lets your programs follow this
6//! convention too: it provides [`InputArg`] and [`OutputArg`] types that wrap
7//! command-line arguments, with methods for reading from/writing to either the
8//! given path or — if the argument is just a hyphen — the appropriate standard
9//! stream.
10//!
11//! `InputArg` and `OutputArg` implement `From<OsString>`, `From<String>`, and
12//! `FromStr`, so you can use them seamlessly with your favorite Rust source of
13//! command-line arguments, be it [`clap`][], [`lexopt`][], plain old
14//! [`std::env::args`]/[`std::env::args_os`], or whatever else is out there.
15//! The source repository contains examples of two of these:
16//!
17//! - [`flipcase`][] and [`tokio-flipcase`][] show how to use this crate with
18//! `clap`.
19//!
20//! - [`revchars`][] and [`tokio-revchars`][] show how to use this crate with
21//! `lexopt`.
22//!
23//! [`clap`]: https://crates.io/crates/clap
24//! [`lexopt`]: https://crates.io/crates/lexopt
25//! [`flipcase`]: https://github.com/jwodder/patharg/tree/master/examples/flipcase/
26//! [`tokio-flipcase`]: https://github.com/jwodder/patharg/tree/master/examples/tokio-flipcase/
27//! [`revchars`]: https://github.com/jwodder/patharg/tree/master/examples/revchars/
28//! [`tokio-revchars`]: https://github.com/jwodder/patharg/tree/master/examples/tokio-revchars/
29//!
30//! Features
31//! ========
32//!
33//! The `patharg` crate has the following optional features. None of them are
34//! enabled by default.
35//!
36//! - `serde` — Enables serialization & deserialization of `InputArg` and
37//! `OutputArg` values with [`serde`]
38//!
39//! - `tokio` — Enables using `InputArg` and `OutputArg` values for
40//! asynchronous I/O with [`tokio`]
41//!
42//! Comparison with clio
43//! ====================
44//!
45//! The only other library I am aware of that provides similar functionality to
46//! `patharg` is [`clio`][]. Compared to `clio`, `patharg` aims to be a much
47//! simpler, smaller library that doesn't try to be too clever. Major
48//! differences between the libraries include:
49//!
50//! - When a `clio` path instance is created, `clio` will either (depending on
51//! the type used) open the path immediately — which can lead to empty files
52//! being needlessly left behind if an output file is constructed during
53//! argument processing but an error occurs before the file is actually used
54//! — or else check that the path can be opened — which is vulnerable to
55//! TOCTTOU bugs. `patharg` does no such thing.
56//!
57//! - `clio` supports reading from & writing to HTTP(S) URLs and has special
58//! treatment for FIFOs. `patharg` sees no need for such excesses.
59//!
60//! - `patharg` has a feature for allowing async I/O with [`tokio`]. `clio`
61//! does not.
62//!
63//! - `patharg` has optional support for [`serde`]. `clio` does not.
64//!
65//! [`clio`]: https://crates.io/crates/clio
66
67use cfg_if::cfg_if;
68use either::Either;
69use std::ffi::OsString;
70use std::fmt;
71use std::fs;
72use std::io::{self, BufRead, BufReader, Read, StdinLock, StdoutLock, Write};
73use std::path::{Path, PathBuf};
74use std::str::FromStr;
75
76cfg_if! {
77 if #[cfg(feature = "serde")] {
78 use serde::de::Deserializer;
79 use serde::ser::Serializer;
80 use serde::{Deserialize, Serialize};
81 }
82}
83
84cfg_if! {
85 if #[cfg(feature = "tokio")] {
86 use tokio::io::{AsyncReadExt, AsyncWriteExt, AsyncBufReadExt};
87 use tokio_util::either::Either as AsyncEither;
88 use tokio_stream::wrappers::LinesStream;
89 }
90}
91
92/// An input path that can refer to either standard input or a file system path
93#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
94pub enum InputArg {
95 /// Refers to standard input.
96 ///
97 /// This is the variant returned by `InputArg::default()`.
98 #[default]
99 Stdin,
100
101 /// Refers to a file system path (stored in `.0`)
102 Path(PathBuf),
103}
104
105impl InputArg {
106 /// Construct an `InputArg` from a string, usually one taken from
107 /// command-line arguments. If the string equals `"-"` (i.e., it contains
108 /// only a single hyphen/dash), [`InputArg::Stdin`] is returned; otherwise,
109 /// an [`InputArg::Path`] is returned.
110 ///
111 /// # Example
112 ///
113 /// ```
114 /// use patharg::InputArg;
115 /// use std::path::PathBuf;
116 ///
117 /// let p1 = InputArg::from_arg("-");
118 /// assert_eq!(p1, InputArg::Stdin);
119 ///
120 /// let p2 = InputArg::from_arg("./-");
121 /// assert_eq!(p2, InputArg::Path(PathBuf::from("./-")));
122 /// ```
123 pub fn from_arg<S: Into<PathBuf>>(arg: S) -> InputArg {
124 let arg = arg.into();
125 if arg == Path::new("-") {
126 InputArg::Stdin
127 } else {
128 InputArg::Path(arg)
129 }
130 }
131
132 /// Returns true if the input arg is the `Stdin` variant of `InputArg`.
133 ///
134 /// # Example
135 ///
136 /// ```
137 /// use patharg::InputArg;
138 ///
139 /// let p1 = InputArg::from_arg("-");
140 /// assert!(p1.is_stdin());
141 ///
142 /// let p2 = InputArg::from_arg("file.txt");
143 /// assert!(!p2.is_stdin());
144 /// ```
145 pub fn is_stdin(&self) -> bool {
146 self == &InputArg::Stdin
147 }
148
149 /// Returns true if the input arg is the `Path` variant of `InputArg`.
150 ///
151 /// # Example
152 ///
153 /// ```
154 /// use patharg::InputArg;
155 ///
156 /// let p1 = InputArg::from_arg("-");
157 /// assert!(!p1.is_path());
158 ///
159 /// let p2 = InputArg::from_arg("file.txt");
160 /// assert!(p2.is_path());
161 /// ```
162 pub fn is_path(&self) -> bool {
163 matches!(self, InputArg::Path(_))
164 }
165
166 /// Retrieve a reference to the inner [`PathBuf`]. If the input arg is
167 /// the `Stdin` variant, this returns `None`.
168 ///
169 /// # Example
170 ///
171 /// ```
172 /// use patharg::InputArg;
173 /// use std::path::PathBuf;
174 ///
175 /// let p1 = InputArg::from_arg("-");
176 /// assert_eq!(p1.path_ref(), None);
177 ///
178 /// let p2 = InputArg::from_arg("file.txt");
179 /// assert_eq!(p2.path_ref(), Some(&PathBuf::from("file.txt")));
180 /// ```
181 pub fn path_ref(&self) -> Option<&PathBuf> {
182 match self {
183 InputArg::Stdin => None,
184 InputArg::Path(p) => Some(p),
185 }
186 }
187
188 /// Retrieve a mutable reference to the inner [`PathBuf`]. If the input
189 /// arg is the `Stdin` variant, this returns `None`.
190 ///
191 /// # Example
192 ///
193 /// ```
194 /// use patharg::InputArg;
195 /// use std::path::PathBuf;
196 ///
197 /// let mut p1 = InputArg::from_arg("-");
198 /// assert_eq!(p1.path_mut(), None);
199 ///
200 /// let mut p2 = InputArg::from_arg("file.txt");
201 /// assert_eq!(p2.path_mut(), Some(&mut PathBuf::from("file.txt")));
202 /// ```
203 pub fn path_mut(&mut self) -> Option<&mut PathBuf> {
204 match self {
205 InputArg::Stdin => None,
206 InputArg::Path(p) => Some(p),
207 }
208 }
209
210 /// Consume the input arg and return the inner [`PathBuf`]. If the input
211 /// arg is the `Stdin` variant, this returns `None`.
212 ///
213 /// # Example
214 ///
215 /// ```
216 /// use patharg::InputArg;
217 /// use std::path::PathBuf;
218 ///
219 /// let p1 = InputArg::from_arg("-");
220 /// assert_eq!(p1.into_path(), None);
221 ///
222 /// let p2 = InputArg::from_arg("file.txt");
223 /// assert_eq!(p2.into_path(), Some(PathBuf::from("file.txt")));
224 /// ```
225 pub fn into_path(self) -> Option<PathBuf> {
226 match self {
227 InputArg::Stdin => None,
228 InputArg::Path(p) => Some(p),
229 }
230 }
231
232 /// Open the input arg for reading.
233 ///
234 /// If the input arg is the `Stdin` variant, this returns a locked
235 /// reference to stdin. Otherwise, if the path arg is a `Path` variant,
236 /// the given path is opened for reading.
237 ///
238 /// The returned reader implements [`std::io::BufRead`].
239 ///
240 /// # Errors
241 ///
242 /// Has the same error conditions as [`std::fs::File::open`].
243 ///
244 /// # Example
245 ///
246 /// ```no_run
247 /// use patharg::InputArg;
248 /// use std::env::args_os;
249 /// use std::io::{self, Read};
250 ///
251 /// fn main() -> io::Result<()> {
252 /// let infile = args_os().nth(1)
253 /// .map(InputArg::from_arg)
254 /// .unwrap_or_default();
255 /// let mut f = infile.open()?;
256 /// let mut buffer = [0; 16];
257 /// let n = f.read(&mut buffer)?;
258 /// println!("First {} bytes: {:?}", n, &buffer[..n]);
259 /// Ok(())
260 /// }
261 /// ```
262 pub fn open(&self) -> io::Result<InputArgReader> {
263 Ok(match self {
264 InputArg::Stdin => Either::Left(io::stdin().lock()),
265 InputArg::Path(p) => Either::Right(BufReader::new(fs::File::open(p)?)),
266 })
267 }
268
269 /// Read the entire contents of the input arg into a bytes vector.
270 ///
271 /// If the input arg is the `Stdin` variant, the entire contents of stdin
272 /// are read. Otherwise, if the input arg is a `Path` variant, the
273 /// contents of the given path are read.
274 ///
275 /// # Errors
276 ///
277 /// Has the same error conditions as [`std::io::Read::read_to_end`] and
278 /// [`std::fs::read`].
279 ///
280 /// # Example
281 ///
282 /// ```no_run
283 /// use patharg::InputArg;
284 /// use std::env::args_os;
285 /// use std::io;
286 ///
287 /// fn main() -> io::Result<()> {
288 /// let infile = args_os().nth(1)
289 /// .map(InputArg::from_arg)
290 /// .unwrap_or_default();
291 /// let input = infile.read()?;
292 /// println!("Read {} bytes from input", input.len());
293 /// Ok(())
294 /// }
295 /// ```
296 pub fn read(&self) -> io::Result<Vec<u8>> {
297 match self {
298 InputArg::Stdin => {
299 let mut vec = Vec::new();
300 io::stdin().lock().read_to_end(&mut vec)?;
301 Ok(vec)
302 }
303 InputArg::Path(p) => fs::read(p),
304 }
305 }
306
307 /// Read the entire contents of the input arg into a string.
308 ///
309 /// If the input arg is the `Stdin` variant, the entire contents of stdin
310 /// are read. Otherwise, if the input arg is a `Path` variant, the
311 /// contents of the given path are read.
312 ///
313 /// # Errors
314 ///
315 /// Has the same error conditions as [`std::io::read_to_string`] and
316 /// [`std::fs::read_to_string`].
317 ///
318 /// # Example
319 ///
320 /// ```no_run
321 /// use patharg::InputArg;
322 /// use std::env::args_os;
323 /// use std::io;
324 ///
325 /// fn main() -> io::Result<()> {
326 /// let infile = args_os().nth(1)
327 /// .map(InputArg::from_arg)
328 /// .unwrap_or_default();
329 /// let input = infile.read_to_string()?;
330 /// println!("Read {} characters from input", input.len());
331 /// Ok(())
332 /// }
333 /// ```
334 pub fn read_to_string(&self) -> io::Result<String> {
335 match self {
336 InputArg::Stdin => io::read_to_string(io::stdin().lock()),
337 InputArg::Path(p) => fs::read_to_string(p),
338 }
339 }
340
341 /// Return an iterator over the lines of the input arg.
342 ///
343 /// If the input arg is the `Stdin` variant, this locks stdin and returns
344 /// an iterator over its lines; the lock is released once the iterator is
345 /// dropped. Otherwise, if the input arg is a `Path` variant, the given
346 /// path is opened for reading, and an iterator over its lines is returned.
347 ///
348 /// The returned iterator yields instances of `std::io::Result<String>`,
349 /// where each individual item has the same error conditions as
350 /// [`std::io::BufRead::read_line()`].
351 ///
352 /// # Errors
353 ///
354 /// Has the same error conditions as [`InputArg::open()`].
355 ///
356 /// # Example
357 ///
358 /// ```no_run
359 /// use patharg::InputArg;
360 /// use std::env::args_os;
361 /// use std::io;
362 ///
363 /// fn main() -> io::Result<()> {
364 /// let infile = args_os().nth(1)
365 /// .map(InputArg::from_arg)
366 /// .unwrap_or_default();
367 /// for (i, r) in infile.lines()?.enumerate() {
368 /// let line = r?;
369 /// println!("Line {} is {} characters long.", i + 1, line.len());
370 /// }
371 /// Ok(())
372 /// }
373 /// ```
374 pub fn lines(&self) -> io::Result<Lines> {
375 Ok(self.open()?.lines())
376 }
377}
378
379#[cfg(feature = "tokio")]
380#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
381impl InputArg {
382 /// Asynchronously open the input arg for reading.
383 ///
384 /// If the input arg is the `Stdin` variant, this returns a reference to
385 /// stdin. Otherwise, if the path arg is a `Path` variant, the given path
386 /// is opened for reading.
387 ///
388 /// The returned reader implements [`tokio::io::AsyncRead`].
389 ///
390 /// # Errors
391 ///
392 /// Has the same error conditions as [`tokio::fs::File::open`].
393 ///
394 /// # Example
395 ///
396 /// ```no_run
397 /// use patharg::InputArg;
398 /// use std::env::args_os;
399 /// use tokio::io::AsyncReadExt;
400 ///
401 /// #[tokio::main]
402 /// async fn main() -> std::io::Result<()> {
403 /// let infile = args_os().nth(1)
404 /// .map(InputArg::from_arg)
405 /// .unwrap_or_default();
406 /// let mut f = infile.async_open().await?;
407 /// let mut buffer = [0; 16];
408 /// let n = f.read(&mut buffer).await?;
409 /// println!("First {} bytes: {:?}", n, &buffer[..n]);
410 /// Ok(())
411 /// }
412 /// ```
413 pub async fn async_open(&self) -> io::Result<AsyncInputArgReader> {
414 Ok(match self {
415 InputArg::Stdin => AsyncEither::Left(tokio::io::stdin()),
416 InputArg::Path(p) => AsyncEither::Right(tokio::fs::File::open(p).await?),
417 })
418 }
419
420 /// Asynchronously read the entire contents of the input arg into a bytes
421 /// vector.
422 ///
423 /// If the input arg is the `Stdin` variant, the entire contents of stdin
424 /// are read. Otherwise, if the input arg is a `Path` variant, the
425 /// contents of the given path are read.
426 ///
427 /// # Errors
428 ///
429 /// Has the same error conditions as
430 /// [`tokio::io::AsyncReadExt::read_to_end`] and [`tokio::fs::read`].
431 ///
432 /// # Example
433 ///
434 /// ```no_run
435 /// use patharg::InputArg;
436 /// use std::env::args_os;
437 ///
438 /// #[tokio::main]
439 /// async fn main() -> std::io::Result<()> {
440 /// let infile = args_os().nth(1)
441 /// .map(InputArg::from_arg)
442 /// .unwrap_or_default();
443 /// let input = infile.async_read().await?;
444 /// println!("Read {} bytes from input", input.len());
445 /// Ok(())
446 /// }
447 /// ```
448 pub async fn async_read(&self) -> io::Result<Vec<u8>> {
449 match self {
450 InputArg::Stdin => {
451 let mut vec = Vec::new();
452 tokio::io::stdin().read_to_end(&mut vec).await?;
453 Ok(vec)
454 }
455 InputArg::Path(p) => tokio::fs::read(p).await,
456 }
457 }
458
459 /// Asynchronously read the entire contents of the input arg into a string.
460 ///
461 /// If the input arg is the `Stdin` variant, the entire contents of stdin
462 /// are read. Otherwise, if the input arg is a `Path` variant, the
463 /// contents of the given path are read.
464 ///
465 /// # Errors
466 ///
467 /// Has the same error conditions as
468 /// [`tokio::io::AsyncReadExt::read_to_string`] and
469 /// [`tokio::fs::read_to_string`].
470 ///
471 /// # Example
472 ///
473 /// ```no_run
474 /// use patharg::InputArg;
475 /// use std::env::args_os;
476 ///
477 /// #[tokio::main]
478 /// async fn main() -> std::io::Result<()> {
479 /// let infile = args_os().nth(1)
480 /// .map(InputArg::from_arg)
481 /// .unwrap_or_default();
482 /// let input = infile.async_read_to_string().await?;
483 /// println!("Read {} characters from input", input.len());
484 /// Ok(())
485 /// }
486 /// ```
487 pub async fn async_read_to_string(&self) -> io::Result<String> {
488 match self {
489 InputArg::Stdin => {
490 let mut s = String::new();
491 tokio::io::stdin().read_to_string(&mut s).await?;
492 Ok(s)
493 }
494 InputArg::Path(p) => tokio::fs::read_to_string(p).await,
495 }
496 }
497
498 /// Return a stream over the lines of the input arg.
499 ///
500 /// If the input arg is the `Stdin` variant, this returns a stream over the
501 /// lines of stdin. Otherwise, if the input arg is a `Path` variant, the
502 /// given path is opened for reading, and a stream over its lines is
503 /// returned.
504 ///
505 /// The returned stream yields instances of `std::io::Result<String>`,
506 /// where each individual item has the same error conditions as
507 /// [`tokio::io::AsyncBufReadExt::read_line()`].
508 ///
509 /// # Errors
510 ///
511 /// Has the same error conditions as [`InputArg::async_open()`].
512 ///
513 /// # Example
514 ///
515 /// ```no_run
516 /// use patharg::InputArg;
517 /// use std::env::args_os;
518 /// use tokio_stream::StreamExt;
519 ///
520 /// #[tokio::main]
521 /// async fn main() -> std::io::Result<()> {
522 /// let infile = args_os().nth(1)
523 /// .map(InputArg::from_arg)
524 /// .unwrap_or_default();
525 /// let mut i = 1;
526 /// let mut stream = infile.async_lines().await?;
527 /// while let Some(r) = stream.next().await {
528 /// let line = r?;
529 /// println!("Line {} is {} characters long.", i, line.len());
530 /// i += 1;
531 /// }
532 /// Ok(())
533 /// }
534 /// ```
535 pub async fn async_lines(&self) -> io::Result<AsyncLines> {
536 Ok(LinesStream::new(
537 tokio::io::BufReader::new(self.async_open().await?).lines(),
538 ))
539 }
540}
541
542impl fmt::Display for InputArg {
543 /// Displays [`InputArg::Stdin`] as `-` (a single hyphen/dash) or as
544 /// `<stdin>` if the `{:#}` format is used. Always displays
545 /// [`InputArg::Path`] using [`std::path::Path::display()`].
546 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
547 match self {
548 // IMPORTANT: The default Display of Stdin has to round-trip back
549 // to Stdin so that InputArg will work properly when used with
550 // clap's `default_value_t`.
551 InputArg::Stdin => {
552 if f.alternate() {
553 write!(f, "<stdin>")
554 } else {
555 write!(f, "-")
556 }
557 }
558 InputArg::Path(p) => write!(f, "{}", p.display()),
559 }
560 }
561}
562
563impl<S: Into<PathBuf>> From<S> for InputArg {
564 /// Convert a string to an [`InputArg`] using [`InputArg::from_arg()`].
565 fn from(s: S) -> InputArg {
566 InputArg::from_arg(s)
567 }
568}
569
570impl FromStr for InputArg {
571 type Err = std::convert::Infallible;
572
573 /// Convert a string to an [`InputArg`] using [`InputArg::from_arg()`].
574 fn from_str(s: &str) -> Result<InputArg, Self::Err> {
575 Ok(InputArg::from_arg(s))
576 }
577}
578
579impl From<InputArg> for OsString {
580 /// Convert an [`InputArg`] back to an `OsString`: `InputArg::Stdin`
581 /// becomes `"-"`, and `InputArg::Path(p)` becomes `p.into()`.
582 fn from(arg: InputArg) -> OsString {
583 match arg {
584 InputArg::Stdin => OsString::from("-"),
585 InputArg::Path(p) => p.into(),
586 }
587 }
588}
589
590#[cfg(feature = "serde")]
591#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
592impl Serialize for InputArg {
593 /// Serializes [`InputArg::Stdin`] as `"-"` (a string containing a single
594 /// hyphen/dash). Serializes [`InputArg::Path`] as the inner [`PathBuf`];
595 /// this will fail if the path is not valid UTF-8.
596 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
597 match self {
598 InputArg::Stdin => "-".serialize(serializer),
599 InputArg::Path(p) => p.serialize(serializer),
600 }
601 }
602}
603
604#[cfg(feature = "serde")]
605#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
606impl<'de> Deserialize<'de> for InputArg {
607 /// Deserializes a [`PathBuf`] and converts it to an `InputArg` with
608 /// [`InputArg::from_arg()`].
609 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
610 where
611 D: Deserializer<'de>,
612 {
613 PathBuf::deserialize(deserializer).map(InputArg::from_arg)
614 }
615}
616
617/// An output path that can refer to either standard output or a file system
618/// path
619#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
620pub enum OutputArg {
621 /// Refers to standard output.
622 ///
623 /// This is the variant returned by `OutputArg::default()`.
624 #[default]
625 Stdout,
626
627 /// Refers to a file system path (stored in `.0`)
628 Path(PathBuf),
629}
630
631impl OutputArg {
632 /// Construct a `OutputArg` from a string, usually one taken from
633 /// command-line arguments. If the string equals `"-"` (i.e., it contains
634 /// only a single hyphen/dash), [`OutputArg::Stdout`] is returned;
635 /// otherwise, an [`OutputArg::Path`] is returned.
636 ///
637 /// # Example
638 ///
639 /// ```
640 /// use patharg::OutputArg;
641 /// use std::path::PathBuf;
642 ///
643 /// let p1 = OutputArg::from_arg("-");
644 /// assert_eq!(p1, OutputArg::Stdout);
645 ///
646 /// let p2 = OutputArg::from_arg("./-");
647 /// assert_eq!(p2, OutputArg::Path(PathBuf::from("./-")));
648 /// ```
649 pub fn from_arg<S: Into<PathBuf>>(arg: S) -> OutputArg {
650 let arg = arg.into();
651 if arg == Path::new("-") {
652 OutputArg::Stdout
653 } else {
654 OutputArg::Path(arg)
655 }
656 }
657
658 /// Returns true if the output arg is the `Stdout` variant of `OutputArg`.
659 ///
660 /// # Example
661 ///
662 /// ```
663 /// use patharg::OutputArg;
664 ///
665 /// let p1 = OutputArg::from_arg("-");
666 /// assert!(p1.is_stdout());
667 ///
668 /// let p2 = OutputArg::from_arg("file.txt");
669 /// assert!(!p2.is_stdout());
670 /// ```
671 pub fn is_stdout(&self) -> bool {
672 self == &OutputArg::Stdout
673 }
674
675 /// Returns true if the output arg is the `Path` variant of `OutputArg`.
676 ///
677 /// # Example
678 ///
679 /// ```
680 /// use patharg::OutputArg;
681 ///
682 /// let p1 = OutputArg::from_arg("-");
683 /// assert!(!p1.is_path());
684 ///
685 /// let p2 = OutputArg::from_arg("file.txt");
686 /// assert!(p2.is_path());
687 /// ```
688 pub fn is_path(&self) -> bool {
689 matches!(self, OutputArg::Path(_))
690 }
691
692 /// Retrieve a reference to the inner [`PathBuf`]. If the output arg is
693 /// the `Stdout` variant, this returns `None`.
694 ///
695 /// # Example
696 ///
697 /// ```
698 /// use patharg::OutputArg;
699 /// use std::path::PathBuf;
700 ///
701 /// let p1 = OutputArg::from_arg("-");
702 /// assert_eq!(p1.path_ref(), None);
703 ///
704 /// let p2 = OutputArg::from_arg("file.txt");
705 /// assert_eq!(p2.path_ref(), Some(&PathBuf::from("file.txt")));
706 /// ```
707 pub fn path_ref(&self) -> Option<&PathBuf> {
708 match self {
709 OutputArg::Stdout => None,
710 OutputArg::Path(p) => Some(p),
711 }
712 }
713
714 /// Retrieve a mutable reference to the inner [`PathBuf`]. If the output
715 /// arg is the `Stdout` variant, this returns `None`.
716 ///
717 /// # Example
718 ///
719 /// ```
720 /// use patharg::OutputArg;
721 /// use std::path::PathBuf;
722 ///
723 /// let mut p1 = OutputArg::from_arg("-");
724 /// assert_eq!(p1.path_mut(), None);
725 ///
726 /// let mut p2 = OutputArg::from_arg("file.txt");
727 /// assert_eq!(p2.path_mut(), Some(&mut PathBuf::from("file.txt")));
728 /// ```
729 pub fn path_mut(&mut self) -> Option<&mut PathBuf> {
730 match self {
731 OutputArg::Stdout => None,
732 OutputArg::Path(p) => Some(p),
733 }
734 }
735
736 /// Consume the output arg and return the inner [`PathBuf`]. If the output
737 /// arg is the `Stdout` variant, this returns `None`.
738 ///
739 /// # Example
740 ///
741 /// ```
742 /// use patharg::OutputArg;
743 /// use std::path::PathBuf;
744 ///
745 /// let p1 = OutputArg::from_arg("-");
746 /// assert_eq!(p1.into_path(), None);
747 ///
748 /// let p2 = OutputArg::from_arg("file.txt");
749 /// assert_eq!(p2.into_path(), Some(PathBuf::from("file.txt")));
750 /// ```
751 pub fn into_path(self) -> Option<PathBuf> {
752 match self {
753 OutputArg::Stdout => None,
754 OutputArg::Path(p) => Some(p),
755 }
756 }
757
758 /// Open the output arg for writing.
759 ///
760 /// If the output arg is the `Stdout` variant, this returns a locked
761 /// reference to stdout. Otherwise, if the output arg is a `Path` variant,
762 /// the given path is opened for writing; if the path does not exist, it is
763 /// created.
764 ///
765 /// The returned writer implements [`std::io::Write`].
766 ///
767 /// # Errors
768 ///
769 /// Has the same error conditions as [`std::fs::File::create`].
770 ///
771 /// # Example
772 ///
773 /// ```no_run
774 /// use patharg::OutputArg;
775 /// use std::env::args_os;
776 /// use std::io::{self, Write};
777 ///
778 /// fn main() -> io::Result<()> {
779 /// let outfile = args_os().nth(1)
780 /// .map(OutputArg::from_arg)
781 /// .unwrap_or_default();
782 /// let mut f = outfile.create()?;
783 /// // The "{}" is replaced by either the output filepath or a hyphen.
784 /// write!(&mut f, "I am writing to {}.", outfile)?;
785 /// Ok(())
786 /// }
787 /// ```
788 pub fn create(&self) -> io::Result<OutputArgWriter> {
789 Ok(match self {
790 OutputArg::Stdout => Either::Left(io::stdout().lock()),
791 OutputArg::Path(p) => Either::Right(fs::File::create(p)?),
792 })
793 }
794
795 /// Write a slice as the entire contents of the output arg.
796 ///
797 /// If the output arg is the `Stdout` variant, the given data is written to
798 /// stdout. Otherwise, if the output arg is a `Path` variant, the contents
799 /// of the given path are replaced with the given data; if the path does
800 /// not exist, it is created first.
801 ///
802 /// # Errors
803 ///
804 /// Has the same error conditions as [`std::io::Write::write_all`] and
805 /// [`std::fs::write`].
806 ///
807 /// # Example
808 ///
809 /// ```no_run
810 /// use patharg::OutputArg;
811 /// use std::env::args_os;
812 /// use std::io;
813 ///
814 /// fn main() -> io::Result<()> {
815 /// let outfile = args_os().nth(1)
816 /// .map(OutputArg::from_arg)
817 /// .unwrap_or_default();
818 /// outfile.write("This is the output arg's new content.\n")?;
819 /// Ok(())
820 /// }
821 /// ```
822 pub fn write<C: AsRef<[u8]>>(&self, contents: C) -> io::Result<()> {
823 match self {
824 OutputArg::Stdout => io::stdout().lock().write_all(contents.as_ref()),
825 OutputArg::Path(p) => fs::write(p, contents),
826 }
827 }
828}
829
830#[cfg(feature = "tokio")]
831#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
832impl OutputArg {
833 /// Asynchronously open the output arg for writing.
834 ///
835 /// If the output arg is the `Stdout` variant, this returns a reference to
836 /// stdout. Otherwise, if the output arg is a `Path` variant, the given
837 /// path is opened for writing; if the path does not exist, it is created.
838 ///
839 /// The returned writer implements [`tokio::io::AsyncWrite`].
840 ///
841 /// # Errors
842 ///
843 /// Has the same error conditions as [`tokio::fs::File::create`].
844 ///
845 /// # Example
846 ///
847 /// ```no_run
848 /// use patharg::OutputArg;
849 /// use std::env::args_os;
850 /// use tokio::io::AsyncWriteExt;
851 ///
852 /// #[tokio::main]
853 /// async fn main() -> std::io::Result<()> {
854 /// let outfile = args_os().nth(1)
855 /// .map(OutputArg::from_arg)
856 /// .unwrap_or_default();
857 /// let mut f = outfile.async_create().await?;
858 /// // The "{}" is replaced by either the output filepath or a hyphen.
859 /// let msg = format!("I am writing to {}.\n", outfile);
860 /// f.write_all(msg.as_ref()).await?;
861 /// Ok(())
862 /// }
863 /// ```
864 pub async fn async_create(&self) -> io::Result<AsyncOutputArgWriter> {
865 Ok(match self {
866 OutputArg::Stdout => AsyncEither::Left(tokio::io::stdout()),
867 OutputArg::Path(p) => AsyncEither::Right(tokio::fs::File::create(p).await?),
868 })
869 }
870
871 /// Asynchronously write a slice as the entire contents of the output arg.
872 ///
873 /// If the output arg is the `Stdout` variant, the given data is written to
874 /// stdout. Otherwise, if the output arg is a `Path` variant, the contents
875 /// of the given path are replaced with the given data; if the path does
876 /// not exist, it is created first.
877 ///
878 /// # Errors
879 ///
880 /// Has the same error conditions as
881 /// [`tokio::io::AsyncWriteExt::write_all`] and [`tokio::fs::write`].
882 ///
883 /// # Example
884 ///
885 /// ```no_run
886 /// use patharg::OutputArg;
887 /// use std::env::args_os;
888 ///
889 /// #[tokio::main]
890 /// async fn main() -> std::io::Result<()> {
891 /// let outfile = args_os().nth(1)
892 /// .map(OutputArg::from_arg)
893 /// .unwrap_or_default();
894 /// outfile.async_write("This is the output arg's new content.\n").await?;
895 /// Ok(())
896 /// }
897 /// ```
898 #[allow(clippy::future_not_send)] // The Future is Send if C is Send
899 pub async fn async_write<C: AsRef<[u8]>>(&self, contents: C) -> io::Result<()> {
900 match self {
901 OutputArg::Stdout => {
902 let mut stdout = tokio::io::stdout();
903 stdout.write_all(contents.as_ref()).await?;
904 stdout.flush().await
905 }
906 OutputArg::Path(p) => tokio::fs::write(p, contents).await,
907 }
908 }
909}
910
911impl fmt::Display for OutputArg {
912 /// Displays [`OutputArg::Stdout`] as `-` (a single hyphen/dash) or as
913 /// `<stdout>` if the `{:#}` format is used. Always displays
914 /// [`OutputArg::Path`] using [`std::path::Path::display()`].
915 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
916 match self {
917 // IMPORTANT: The default Display of Stdout has to round-trip back
918 // to Stdout so that OutputArg will work properly when used with
919 // clap's `default_value_t`.
920 OutputArg::Stdout => {
921 if f.alternate() {
922 write!(f, "<stdout>")
923 } else {
924 write!(f, "-")
925 }
926 }
927 OutputArg::Path(p) => write!(f, "{}", p.display()),
928 }
929 }
930}
931
932impl<S: Into<PathBuf>> From<S> for OutputArg {
933 /// Convert an string to an [`OutputArg`] using [`OutputArg::from_arg()`].
934 fn from(s: S) -> OutputArg {
935 OutputArg::from_arg(s)
936 }
937}
938
939impl FromStr for OutputArg {
940 type Err = std::convert::Infallible;
941
942 /// Convert a string to an [`OutputArg`] using [`OutputArg::from_arg()`].
943 fn from_str(s: &str) -> Result<OutputArg, Self::Err> {
944 Ok(OutputArg::from_arg(s))
945 }
946}
947
948impl From<OutputArg> for OsString {
949 /// Convert an [`OutputArg`] back to an `OsString`: `OutputArg::Stdout`
950 /// becomes `"-"`, and `OutputArg::Path(p)` becomes `p.into()`.
951 fn from(arg: OutputArg) -> OsString {
952 match arg {
953 OutputArg::Stdout => OsString::from("-"),
954 OutputArg::Path(p) => p.into(),
955 }
956 }
957}
958
959#[cfg(feature = "serde")]
960#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
961impl Serialize for OutputArg {
962 /// Serializes [`OutputArg::Stdout`] as `"-"` (a string containing a single
963 /// hyphen/dash). Serializes [`OutputArg::Path`] as the inner [`PathBuf`];
964 /// this will fail if the path is not valid UTF-8.
965 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
966 match self {
967 OutputArg::Stdout => "-".serialize(serializer),
968 OutputArg::Path(p) => p.serialize(serializer),
969 }
970 }
971}
972
973#[cfg(feature = "serde")]
974#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
975impl<'de> Deserialize<'de> for OutputArg {
976 /// Deserializes a [`PathBuf`] and converts it to an `OutputArg` with
977 /// [`OutputArg::from_arg()`].
978 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
979 where
980 D: Deserializer<'de>,
981 {
982 PathBuf::deserialize(deserializer).map(OutputArg::from_arg)
983 }
984}
985
986/// The type of the readers returned by [`InputArg::open()`].
987///
988/// This type implements [`std::io::BufRead`].
989pub type InputArgReader = Either<StdinLock<'static>, BufReader<fs::File>>;
990
991/// The type of the writers returned by [`OutputArg::create()`].
992///
993/// This type implements [`std::io::Write`].
994pub type OutputArgWriter = Either<StdoutLock<'static>, fs::File>;
995
996/// The type of the iterators returned by [`InputArg::lines()`].
997///
998/// This iterator yields instances of `std::io::Result<String>`.
999pub type Lines = io::Lines<InputArgReader>;
1000
1001cfg_if! {
1002 if #[cfg(feature = "tokio")] {
1003 /// The type of the asynchronous readers returned by
1004 /// [`InputArg::async_open()`].
1005 ///
1006 /// This type implements [`tokio::io::AsyncRead`].
1007 #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
1008 pub type AsyncInputArgReader = AsyncEither<tokio::io::Stdin, tokio::fs::File>;
1009
1010 /// The type of the asynchronous writers returned by
1011 /// [`OutputArg::async_create()`].
1012 ///
1013 /// This type implements [`tokio::io::AsyncWrite`].
1014 #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
1015 pub type AsyncOutputArgWriter = AsyncEither<tokio::io::Stdout, tokio::fs::File>;
1016
1017 /// The type of the streams returned by [`InputArg::async_lines()`].
1018 ///
1019 /// This stream yields instances of `std::io::Result<String>`.
1020 #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
1021 pub type AsyncLines = LinesStream<tokio::io::BufReader<AsyncInputArgReader>>;
1022 }
1023}
1024
1025#[cfg(test)]
1026mod tests {
1027 use super::*;
1028 use std::ffi::OsStr;
1029
1030 mod inputarg {
1031 use super::*;
1032
1033 #[test]
1034 fn test_assert_stdin_from_osstring() {
1035 let s = OsString::from("-");
1036 let p = InputArg::from(s);
1037 assert!(p.is_stdin());
1038 assert!(!p.is_path());
1039 }
1040
1041 #[test]
1042 fn test_assert_path_from_osstring() {
1043 let s = OsString::from("./-");
1044 let p = InputArg::from(s);
1045 assert!(!p.is_stdin());
1046 assert!(p.is_path());
1047 }
1048
1049 #[test]
1050 fn test_assert_stdin_from_osstr() {
1051 let s = OsStr::new("-");
1052 let p = InputArg::from(s);
1053 assert!(p.is_stdin());
1054 assert!(!p.is_path());
1055 }
1056
1057 #[test]
1058 fn test_assert_path_from_osstr() {
1059 let s = OsStr::new("./-");
1060 let p = InputArg::from(s);
1061 assert!(!p.is_stdin());
1062 assert!(p.is_path());
1063 }
1064
1065 #[test]
1066 fn test_assert_stdin_from_pathbuf() {
1067 let s = PathBuf::from("-");
1068 let p = InputArg::from(s);
1069 assert!(p.is_stdin());
1070 assert!(!p.is_path());
1071 }
1072
1073 #[test]
1074 fn test_assert_path_from_pathbuf() {
1075 let s = PathBuf::from("./-");
1076 let p = InputArg::from(s);
1077 assert!(!p.is_stdin());
1078 assert!(p.is_path());
1079 }
1080
1081 #[test]
1082 fn test_assert_stdin_from_path() {
1083 let s = Path::new("-");
1084 let p = InputArg::from(s);
1085 assert!(p.is_stdin());
1086 assert!(!p.is_path());
1087 }
1088
1089 #[test]
1090 fn test_assert_path_from_path() {
1091 let s = Path::new("./-");
1092 let p = InputArg::from(s);
1093 assert!(!p.is_stdin());
1094 assert!(p.is_path());
1095 }
1096
1097 #[test]
1098 fn test_assert_stdin_from_string() {
1099 let s = String::from("-");
1100 let p = InputArg::from(s);
1101 assert!(p.is_stdin());
1102 assert!(!p.is_path());
1103 }
1104
1105 #[test]
1106 fn test_assert_path_from_string() {
1107 let s = String::from("./-");
1108 let p = InputArg::from(s);
1109 assert!(!p.is_stdin());
1110 assert!(p.is_path());
1111 }
1112
1113 #[test]
1114 fn test_assert_stdin_from_str() {
1115 let p = InputArg::from("-");
1116 assert!(p.is_stdin());
1117 assert!(!p.is_path());
1118 }
1119
1120 #[test]
1121 fn test_assert_path_from_str() {
1122 let p = InputArg::from("./-");
1123 assert!(!p.is_stdin());
1124 assert!(p.is_path());
1125 }
1126
1127 #[test]
1128 fn test_assert_parse_stdin() {
1129 let p = "-".parse::<InputArg>().unwrap();
1130 assert!(p.is_stdin());
1131 assert!(!p.is_path());
1132 }
1133
1134 #[test]
1135 fn test_assert_parse_path() {
1136 let p = "./-".parse::<InputArg>().unwrap();
1137 assert!(!p.is_stdin());
1138 assert!(p.is_path());
1139 }
1140
1141 #[test]
1142 fn test_default() {
1143 assert_eq!(InputArg::default(), InputArg::Stdin);
1144 }
1145
1146 #[test]
1147 fn test_stdin_path_ref() {
1148 let p = InputArg::Stdin;
1149 assert_eq!(p.path_ref(), None);
1150 }
1151
1152 #[test]
1153 fn test_path_path_ref() {
1154 let p = InputArg::Path(PathBuf::from("-"));
1155 assert_eq!(p.path_ref(), Some(&PathBuf::from("-")));
1156 }
1157
1158 #[test]
1159 fn test_stdin_path_mut() {
1160 let mut p = InputArg::Stdin;
1161 assert_eq!(p.path_mut(), None);
1162 }
1163
1164 #[test]
1165 fn test_path_path_mut() {
1166 let mut p = InputArg::Path(PathBuf::from("-"));
1167 assert_eq!(p.path_mut(), Some(&mut PathBuf::from("-")));
1168 }
1169
1170 #[test]
1171 fn test_stdin_into_path() {
1172 let p = InputArg::Stdin;
1173 assert_eq!(p.into_path(), None);
1174 }
1175
1176 #[test]
1177 fn test_path_into_path() {
1178 let p = InputArg::Path(PathBuf::from("-"));
1179 assert_eq!(p.into_path(), Some(PathBuf::from("-")));
1180 }
1181
1182 #[test]
1183 fn test_display_stdin() {
1184 let p = InputArg::Stdin;
1185 assert_eq!(p.to_string(), "-");
1186 }
1187
1188 #[test]
1189 fn test_display_alternate_stdin() {
1190 let p = InputArg::Stdin;
1191 assert_eq!(format!("{p:#}"), "<stdin>");
1192 }
1193
1194 #[test]
1195 fn test_display_path() {
1196 let p = InputArg::from_arg("./-");
1197 assert_eq!(p.to_string(), "./-");
1198 }
1199
1200 #[test]
1201 fn test_stdin_into_osstring() {
1202 let p = InputArg::Stdin;
1203 assert_eq!(OsString::from(p), OsString::from("-"));
1204 }
1205
1206 #[test]
1207 fn test_path_into_osstring() {
1208 let p = InputArg::Path(PathBuf::from("./-"));
1209 assert_eq!(OsString::from(p), OsString::from("./-"));
1210 }
1211
1212 #[cfg(feature = "serde")]
1213 mod serding {
1214 use super::*;
1215
1216 #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
1217 struct Input {
1218 path: InputArg,
1219 }
1220
1221 #[test]
1222 fn test_stdin_to_json() {
1223 let val = Input {
1224 path: InputArg::Stdin,
1225 };
1226 assert_eq!(serde_json::to_string(&val).unwrap(), r#"{"path":"-"}"#);
1227 }
1228
1229 #[test]
1230 fn test_path_to_json() {
1231 let val = Input {
1232 path: InputArg::Path(PathBuf::from("foo.txt")),
1233 };
1234 assert_eq!(
1235 serde_json::to_string(&val).unwrap(),
1236 r#"{"path":"foo.txt"}"#
1237 );
1238 }
1239
1240 #[test]
1241 fn test_stdin_from_json() {
1242 let s = r#"{"path": "-"}"#;
1243 assert_eq!(
1244 serde_json::from_str::<Input>(s).unwrap(),
1245 Input {
1246 path: InputArg::Stdin
1247 }
1248 );
1249 }
1250
1251 #[test]
1252 fn test_path_from_json() {
1253 let s = r#"{"path": "./-"}"#;
1254 assert_eq!(
1255 serde_json::from_str::<Input>(s).unwrap(),
1256 Input {
1257 path: InputArg::Path(PathBuf::from("./-"))
1258 }
1259 );
1260 }
1261 }
1262 }
1263
1264 mod outputarg {
1265 use super::*;
1266
1267 #[test]
1268 fn test_assert_stdout_from_osstring() {
1269 let s = OsString::from("-");
1270 let p = OutputArg::from(s);
1271 assert!(p.is_stdout());
1272 assert!(!p.is_path());
1273 }
1274
1275 #[test]
1276 fn test_assert_path_from_osstring() {
1277 let s = OsString::from("./-");
1278 let p = OutputArg::from(s);
1279 assert!(!p.is_stdout());
1280 assert!(p.is_path());
1281 }
1282
1283 #[test]
1284 fn test_assert_stdout_from_osstr() {
1285 let s = OsStr::new("-");
1286 let p = OutputArg::from(s);
1287 assert!(p.is_stdout());
1288 assert!(!p.is_path());
1289 }
1290
1291 #[test]
1292 fn test_assert_path_from_osstr() {
1293 let s = OsStr::new("./-");
1294 let p = OutputArg::from(s);
1295 assert!(!p.is_stdout());
1296 assert!(p.is_path());
1297 }
1298
1299 #[test]
1300 fn test_assert_stdout_from_pathbuf() {
1301 let s = PathBuf::from("-");
1302 let p = OutputArg::from(s);
1303 assert!(p.is_stdout());
1304 assert!(!p.is_path());
1305 }
1306
1307 #[test]
1308 fn test_assert_path_from_pathbuf() {
1309 let s = PathBuf::from("./-");
1310 let p = OutputArg::from(s);
1311 assert!(!p.is_stdout());
1312 assert!(p.is_path());
1313 }
1314
1315 #[test]
1316 fn test_assert_stdout_from_path() {
1317 let s = Path::new("-");
1318 let p = OutputArg::from(s);
1319 assert!(p.is_stdout());
1320 assert!(!p.is_path());
1321 }
1322
1323 #[test]
1324 fn test_assert_path_from_path() {
1325 let s = Path::new("./-");
1326 let p = OutputArg::from(s);
1327 assert!(!p.is_stdout());
1328 assert!(p.is_path());
1329 }
1330
1331 #[test]
1332 fn test_assert_stdout_from_string() {
1333 let s = String::from("-");
1334 let p = OutputArg::from(s);
1335 assert!(p.is_stdout());
1336 assert!(!p.is_path());
1337 }
1338
1339 #[test]
1340 fn test_assert_path_from_string() {
1341 let s = String::from("./-");
1342 let p = OutputArg::from(s);
1343 assert!(!p.is_stdout());
1344 assert!(p.is_path());
1345 }
1346
1347 #[test]
1348 fn test_assert_stdout_from_str() {
1349 let p = OutputArg::from("-");
1350 assert!(p.is_stdout());
1351 assert!(!p.is_path());
1352 }
1353
1354 #[test]
1355 fn test_assert_path_from_str() {
1356 let p = OutputArg::from("./-");
1357 assert!(!p.is_stdout());
1358 assert!(p.is_path());
1359 }
1360
1361 #[test]
1362 fn test_assert_parse_stdin() {
1363 let p = "-".parse::<OutputArg>().unwrap();
1364 assert!(p.is_stdout());
1365 assert!(!p.is_path());
1366 }
1367
1368 #[test]
1369 fn test_assert_parse_path() {
1370 let p = "./-".parse::<OutputArg>().unwrap();
1371 assert!(!p.is_stdout());
1372 assert!(p.is_path());
1373 }
1374
1375 #[test]
1376 fn test_default() {
1377 assert_eq!(OutputArg::default(), OutputArg::Stdout);
1378 }
1379
1380 #[test]
1381 fn test_stdout_path_ref() {
1382 let p = OutputArg::Stdout;
1383 assert_eq!(p.path_ref(), None);
1384 }
1385
1386 #[test]
1387 fn test_path_path_ref() {
1388 let p = OutputArg::Path(PathBuf::from("-"));
1389 assert_eq!(p.path_ref(), Some(&PathBuf::from("-")));
1390 }
1391
1392 #[test]
1393 fn test_stdout_path_mut() {
1394 let mut p = OutputArg::Stdout;
1395 assert_eq!(p.path_mut(), None);
1396 }
1397
1398 #[test]
1399 fn test_path_path_mut() {
1400 let mut p = OutputArg::Path(PathBuf::from("-"));
1401 assert_eq!(p.path_mut(), Some(&mut PathBuf::from("-")));
1402 }
1403
1404 #[test]
1405 fn test_stdout_into_path() {
1406 let p = OutputArg::Stdout;
1407 assert_eq!(p.into_path(), None);
1408 }
1409
1410 #[test]
1411 fn test_path_into_path() {
1412 let p = OutputArg::Path(PathBuf::from("-"));
1413 assert_eq!(p.into_path(), Some(PathBuf::from("-")));
1414 }
1415
1416 #[test]
1417 fn test_display_stdout() {
1418 let p = OutputArg::Stdout;
1419 assert_eq!(p.to_string(), "-");
1420 }
1421
1422 #[test]
1423 fn test_display_alternate_stdout() {
1424 let p = OutputArg::Stdout;
1425 assert_eq!(format!("{p:#}"), "<stdout>");
1426 }
1427
1428 #[test]
1429 fn test_display_path() {
1430 let p = OutputArg::from_arg("./-");
1431 assert_eq!(p.to_string(), "./-");
1432 }
1433
1434 #[test]
1435 fn test_stdout_into_osstring() {
1436 let p = OutputArg::Stdout;
1437 assert_eq!(OsString::from(p), OsString::from("-"));
1438 }
1439
1440 #[test]
1441 fn test_path_into_osstring() {
1442 let p = OutputArg::Path(PathBuf::from("./-"));
1443 assert_eq!(OsString::from(p), OsString::from("./-"));
1444 }
1445
1446 #[cfg(feature = "tokio")]
1447 #[test]
1448 fn test_async_write_is_send_if_content_is_send() {
1449 fn require_send<T: Send>(_t: T) {}
1450 let p = OutputArg::default();
1451 let fut = p.async_write(b"This arg is Send.");
1452 require_send(fut);
1453 }
1454
1455 #[cfg(feature = "serde")]
1456 mod serding {
1457 use super::*;
1458
1459 #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
1460 struct Output {
1461 path: OutputArg,
1462 }
1463
1464 #[test]
1465 fn test_stdout_to_json() {
1466 let val = Output {
1467 path: OutputArg::Stdout,
1468 };
1469 assert_eq!(serde_json::to_string(&val).unwrap(), r#"{"path":"-"}"#);
1470 }
1471
1472 #[test]
1473 fn test_path_to_json() {
1474 let val = Output {
1475 path: OutputArg::Path(PathBuf::from("foo.txt")),
1476 };
1477 assert_eq!(
1478 serde_json::to_string(&val).unwrap(),
1479 r#"{"path":"foo.txt"}"#
1480 );
1481 }
1482
1483 #[test]
1484 fn test_stdout_from_json() {
1485 let s = r#"{"path": "-"}"#;
1486 assert_eq!(
1487 serde_json::from_str::<Output>(s).unwrap(),
1488 Output {
1489 path: OutputArg::Stdout
1490 }
1491 );
1492 }
1493
1494 #[test]
1495 fn test_path_from_json() {
1496 let s = r#"{"path": "./-"}"#;
1497 assert_eq!(
1498 serde_json::from_str::<Output>(s).unwrap(),
1499 Output {
1500 path: OutputArg::Path(PathBuf::from("./-"))
1501 }
1502 );
1503 }
1504 }
1505 }
1506}