sheesy_tools/substitute/
spec.rs

1use atty;
2use failure::{Error, ResultExt};
3
4use std::ffi::OsStr;
5use std::fmt;
6use std::fs::create_dir_all;
7use std::fs::{File, OpenOptions};
8use std::io::{self, stdin, stdout};
9use std::path::PathBuf;
10
11#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
12pub enum StreamOrPath {
13    Stream,
14    Path(PathBuf),
15}
16
17impl StreamOrPath {
18    pub fn is_stream(&self) -> bool {
19        match *self {
20            StreamOrPath::Stream => true,
21            StreamOrPath::Path(_) => false,
22        }
23    }
24
25    pub fn name(&self) -> &str {
26        match *self {
27            StreamOrPath::Stream => "stream",
28            StreamOrPath::Path(ref p) => p.to_str().unwrap_or("<file-path-is-not-unicode>"),
29        }
30    }
31
32    pub fn short_name(&self) -> &str {
33        match *self {
34            StreamOrPath::Stream => "stream",
35            StreamOrPath::Path(ref p) => p.file_stem().map_or("<invalid-file-stem>", |s| {
36                s.to_str().unwrap_or("<file-stem-is-not-unicode>")
37            }),
38        }
39    }
40
41    pub fn open_as_output(&self, append: bool) -> Result<Box<dyn io::Write>, Error> {
42        Ok(match *self {
43            StreamOrPath::Stream => Box::new(stdout()),
44            StreamOrPath::Path(ref p) => {
45                if let Some(dir) = p.parent() {
46                    create_dir_all(dir)
47                        .context(format!("Could not create directory leading towards '{}'", p.display(),))?;
48                }
49                Box::new(
50                    OpenOptions::new()
51                        .create(true)
52                        .truncate(!append)
53                        .write(true)
54                        .append(append)
55                        .open(p)
56                        .context(format!("Could not open '{}' for writing", p.display()))?,
57                )
58            }
59        })
60    }
61
62    pub fn open_as_input(&self) -> Result<Box<dyn io::Read>, Error> {
63        Ok(match *self {
64            StreamOrPath::Stream => {
65                if atty::is(atty::Stream::Stdin) {
66                    bail!("Cannot read from standard input while a terminal is connected")
67                } else {
68                    Box::new(stdin())
69                }
70            }
71            StreamOrPath::Path(ref p) => {
72                Box::new(File::open(p).context(format!("Could not open '{}' for reading", p.display()))?)
73            }
74        })
75    }
76}
77
78impl<'a> From<&'a OsStr> for StreamOrPath {
79    fn from(p: &'a OsStr) -> Self {
80        StreamOrPath::Path(PathBuf::from(p))
81    }
82}
83
84impl<'a> From<&'a str> for StreamOrPath {
85    fn from(s: &str) -> Self {
86        use self::StreamOrPath::*;
87        if s.is_empty() {
88            Stream
89        } else {
90            Path(PathBuf::from(s))
91        }
92    }
93}
94
95impl fmt::Display for StreamOrPath {
96    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
97        use self::StreamOrPath::*;
98        match *self {
99            Stream => Ok(()),
100            Path(ref p) => p.display().fmt(f),
101        }
102    }
103}
104
105#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
106pub struct Spec {
107    pub src: StreamOrPath,
108    pub dst: StreamOrPath,
109}
110
111impl Spec {
112    pub fn sep() -> char {
113        ':'
114    }
115}
116
117impl fmt::Display for Spec {
118    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
119        use self::fmt::Write;
120        use self::StreamOrPath::*;
121        match (&self.src, &self.dst) {
122            (&Stream, &Stream) => f.write_char(Spec::sep()),
123            (&Path(ref p), &Stream) => p.display().fmt(f),
124            (&Stream, &Path(ref p)) => f.write_char(Spec::sep()).and(p.display().fmt(f)),
125            (&Path(ref p1), &Path(ref p2)) => p1
126                .display()
127                .fmt(f)
128                .and(f.write_char(Spec::sep()))
129                .and(p2.display().fmt(f)),
130        }
131    }
132}
133
134impl<'a> From<&'a str> for Spec {
135    fn from(src: &'a str) -> Self {
136        use self::StreamOrPath::*;
137        let mut it = src.splitn(2, Spec::sep());
138        match (it.next(), it.next()) {
139            (None, Some(_)) | (None, None) => unreachable!(),
140            (Some(p), None) => Spec {
141                src: StreamOrPath::from(p),
142                dst: Stream,
143            },
144            (Some(p1), Some(p2)) => Spec {
145                src: StreamOrPath::from(p1),
146                dst: StreamOrPath::from(p2),
147            },
148        }
149    }
150}