lib/common/mod.rs
1pub mod util;
2
3use contracts::*;
4use derive_getters::Getters;
5use failure::Fail;
6use itertools::Itertools;
7use std::{
8 convert::{AsRef, From},
9 fmt::{self, Display},
10 iter::IntoIterator,
11 ops::Deref,
12 path::{Path, PathBuf},
13 str::FromStr,
14};
15use strum::IntoEnumIterator;
16use strum_macros::EnumIter;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)]
19pub enum Platform {
20 Windows,
21 Macos,
22 Linux,
23 Wsl,
24}
25use Platform::*;
26
27impl Platform {
28 /// Returns the valid strings corresponding to `self`
29 pub fn strs(&self) -> &[&'static str] {
30 match self {
31 Windows => &["win", "windows"],
32 Macos => &["mac", "macos"],
33 Linux => &["linux"],
34 Wsl => &["wsl"],
35 }
36 }
37}
38
39#[derive(Debug, Fail)]
40#[fail(display = "unsupported platform \"{}\"", input)]
41pub struct PlatformParseError {
42 input: String,
43}
44
45impl FromStr for Platform {
46 type Err = PlatformParseError;
47
48 fn from_str(s: &str) -> Result<Platform, Self::Err> {
49 let s = s.trim().to_lowercase();
50
51 for platform in Platform::iter() {
52 if platform.strs().contains(&s.as_str()) {
53 return Ok(platform);
54 }
55 }
56
57 Err(PlatformParseError { input: s })
58 }
59}
60
61/// Represents an (owned) path which must be absolute
62#[derive(Clone, Debug, Eq, Hash, PartialEq)]
63pub struct AbsolutePath {
64 path: PathBuf,
65}
66
67impl Display for AbsolutePath {
68 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
69 f.pad(&util::home_to_tilde(&self.path).display().to_string())
70 }
71}
72
73// I'd like to have a blanket `impl From<P> where P: AsRef<Path> for
74// AbsolutePath`, but that won't work until you can add a `P != AbsolutePath`
75// constraint. Otherwise, you run up against the blanket `impl From<T> for T`.
76// See https://github.com/rust-lang/rfcs/issues/1834.
77//
78// For now, I'll just have to deal with writing relevant impls by hand.
79
80impl From<PathBuf> for AbsolutePath {
81 #[pre(path.is_absolute())]
82 fn from(path: PathBuf) -> Self {
83 AbsolutePath { path }
84 }
85}
86
87impl From<&Path> for AbsolutePath {
88 #[pre(path.is_absolute())]
89 fn from(path: &Path) -> Self {
90 AbsolutePath {
91 path: path.to_path_buf(),
92 }
93 }
94}
95
96impl From<&str> for AbsolutePath {
97 fn from(path: &str) -> Self {
98 AbsolutePath::from(Path::new(path))
99 }
100}
101
102impl Deref for AbsolutePath {
103 type Target = PathBuf;
104
105 fn deref(&self) -> &Self::Target {
106 &self.path
107 }
108}
109
110impl AsRef<Path> for AbsolutePath {
111 fn as_ref(&self) -> &Path {
112 self.path.as_ref()
113 }
114}
115
116/// Represents the location of a dotfile (the source) and the
117/// location of the symlink pointing to the source (the destination) as a pair
118/// of absolute paths to the two files.
119#[derive(Debug, Getters)]
120pub struct Item {
121 source: AbsolutePath,
122 dest: AbsolutePath,
123}
124
125impl Item {
126 pub fn new(source: impl Into<AbsolutePath>, dest: impl Into<AbsolutePath>) -> Self {
127 Item {
128 source: source.into(),
129 dest: dest.into(),
130 }
131 }
132}
133
134/// Just a wrapper for pretty-printing `Item`s
135///
136/// This type is not meant to be constructed directly. Instead,
137/// use `FormattedItems::from_items`.
138#[derive(Debug)]
139pub struct FormattedItem {
140 item: Item,
141 width: usize,
142}
143
144impl Deref for FormattedItem {
145 type Target = Item;
146
147 fn deref(&self) -> &Self::Target {
148 &self.item
149 }
150}
151
152impl Display for FormattedItem {
153 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
154 f.pad(&format!(
155 "{:width$} -> {}",
156 self.source(),
157 self.dest(),
158 width = self.width
159 ))
160 }
161}
162
163// Just a convenient wrapper for multiple `FormattedItem`s
164#[derive(Debug)]
165pub struct FormattedItems {
166 formatted_items: Vec<FormattedItem>,
167}
168
169impl FormattedItems {
170 /// Formats items in a group to ensure uniform formatting.
171 ///
172 /// For example:
173 /// ```
174 /// # use crate::lib::common::{Item, FormattedItems};
175 /// let items_short = vec![
176 /// Item::new("/home/tkadur/.dotfiles/file1", "/home/tkadur/.file1"),
177 /// Item::new("/home/tkadur/.dotfiles/file2", "/home/tkadur/.file2"),
178 /// ];
179 ///
180 /// # let str_short_expected = [
181 /// # "/home/tkadur/.dotfiles/file1 -> /home/tkadur/.file1",
182 /// # "/home/tkadur/.dotfiles/file2 -> /home/tkadur/.file2"
183 /// # ].join("\n");
184 ///
185 /// // Produces the following:
186 /// //
187 /// // /home/tkadur/.dotfiles/file1 -> /home/tkadur/.file1
188 /// // /home/tkadur/.dotfiles/file2 -> /home/tkadur/.file2
189 /// let str_short = FormattedItems::from_items(items_short).to_string();
190 ///
191 /// # assert_eq!(
192 /// # str_short_expected,
193 /// # str_short,
194 /// # );
195 ///
196 /// let items_long = vec![
197 /// Item::new("/home/tkadur/.dotfiles/file1", "/home/tkadur/.file1"),
198 /// Item::new("/home/tkadur/.dotfiles/file2", "/home/tkadur/.file2"),
199 /// Item::new(
200 /// "/home/tkadur/.dotfiles/file_long",
201 /// "/home/tkadur/.file_long",
202 /// ),
203 /// Item::new(
204 /// "/home/tkadur/.dotfiles/file_even_longer",
205 /// "/home/tkadur/.file_even_longer",
206 /// ),
207 /// ];
208 ///
209 /// // Produces the following:
210 /// //
211 /// // /home/tkadur/.dotfiles/file1 -> /home/tkadur/.file1
212 /// // /home/tkadur/.dotfiles/file2 -> /home/tkadur/.file2
213 /// // /home/tkadur/.dotfiles/file_long -> /home/tkadur/.file_long
214 /// // /home/tkadur/.dotfiles/file_even_longer -> /home/tkadur/.file_even_longer
215 /// let str_long = FormattedItems::from_items(items_long).to_string();
216 ///
217 /// # let str_long_expected = [
218 /// # "/home/tkadur/.dotfiles/file1 -> /home/tkadur/.file1",
219 /// # "/home/tkadur/.dotfiles/file2 -> /home/tkadur/.file2",
220 /// # "/home/tkadur/.dotfiles/file_long -> /home/tkadur/.file_long",
221 /// # "/home/tkadur/.dotfiles/file_even_longer -> /home/tkadur/.file_even_longer",
222 /// # ]
223 /// # .join("\n");
224 ///
225 /// # assert_eq!(
226 /// # str_long_expected,
227 /// # str_long,
228 /// # );
229 /// ```
230 pub fn from_items(items: Vec<Item>) -> Self {
231 let width = items
232 .iter()
233 .map(|item| item.source().to_string().len())
234 .max()
235 .unwrap_or(0);
236
237 let formatted_items = items
238 .into_iter()
239 .map(|item| FormattedItem { item, width })
240 .collect();
241
242 FormattedItems { formatted_items }
243 }
244}
245
246impl IntoIterator for FormattedItems {
247 type IntoIter = <Vec<FormattedItem> as IntoIterator>::IntoIter;
248 type Item = <Vec<FormattedItem> as IntoIterator>::Item;
249
250 fn into_iter(self) -> Self::IntoIter {
251 self.formatted_items.into_iter()
252 }
253}
254
255impl<'a> IntoIterator for &'a FormattedItems {
256 type IntoIter = <&'a Vec<FormattedItem> as IntoIterator>::IntoIter;
257 type Item = <&'a Vec<FormattedItem> as IntoIterator>::Item;
258
259 fn into_iter(self) -> Self::IntoIter {
260 self.formatted_items.iter()
261 }
262}
263
264impl Display for FormattedItems {
265 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
266 f.pad(&self.formatted_items.iter().join("\n"))
267 }
268}