1use std::{
2 borrow::Cow,
3 path::{Path, PathBuf},
4};
5
6#[derive(Debug, Clone)]
7pub enum InputFile {
8 Real(PathBuf),
9 Stdin(Box<[u8]>),
10}
11
12impl Default for InputFile {
13 fn default() -> Self {
14 Self::Stdin(Box::from([]))
15 }
16}
17
18impl InputFile {
19 pub fn file_name(&self) -> &str {
20 match self {
21 Self::Real(path) => {
22 path.file_name().and_then(|name| name.to_str()).unwrap_or("<noname>")
23 }
24 Self::Stdin(_) => "<noname>",
25 }
26 }
27
28 pub fn bytes(&self) -> Option<Cow<'_, [u8]>> {
29 match self {
30 Self::Real(path) => std::fs::read(path).ok().map(Cow::Owned),
31 Self::Stdin(bytes) => Some(Cow::Borrowed(bytes)),
32 }
33 }
34
35 pub fn from_path<P: AsRef<Path>>(path: P) -> Self {
39 let path = path.as_ref();
40 Self::Real(path.to_path_buf())
41 }
42
43 pub fn from_stdin() -> Result<Self, std::io::Error> {
47 use std::io::Read;
48
49 let mut input = Vec::with_capacity(1024);
50 std::io::stdin().read_to_end(&mut input)?;
51 Ok(Self::Stdin(input.into_boxed_slice()))
52 }
53}
54
55#[cfg(feature = "std")]
56impl clap::builder::ValueParserFactory for InputFile {
57 type Parser = InputFileParser;
58
59 fn value_parser() -> Self::Parser {
60 InputFileParser
61 }
62}
63
64#[doc(hidden)]
65#[derive(Clone)]
66#[cfg(feature = "std")]
67pub struct InputFileParser;
68
69#[cfg(feature = "std")]
70impl clap::builder::TypedValueParser for InputFileParser {
71 type Value = InputFile;
72
73 fn parse_ref(
74 &self,
75 _cmd: &clap::Command,
76 _arg: Option<&clap::Arg>,
77 value: &std::ffi::OsStr,
78 ) -> Result<Self::Value, clap::error::Error> {
79 use clap::error::{Error, ErrorKind};
80
81 let input_file = match value.to_str() {
82 Some("-") => InputFile::from_stdin().map_err(|err| Error::raw(ErrorKind::Io, err))?,
83 Some(_) | None => InputFile::from_path(PathBuf::from(value)),
84 };
85
86 match &input_file {
87 InputFile::Real(path) => {
88 if !path.exists() {
89 return Err(Error::raw(
90 ErrorKind::ValueValidation,
91 format!("invalid input '{}': file does not exist", path.display()),
92 ));
93 }
94 }
95 InputFile::Stdin(_) => (),
96 }
97
98 Ok(input_file)
99 }
100}