sheesy_tools/substitute/
spec.rs1use 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}