common.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. use crate::ResultExt;
  2. use std;
  3. use std::ffi::OsStr;
  4. use std::fs::{self, File};
  5. use std::io::{self, BufWriter, Write};
  6. use std::path::{Component, Path, PathBuf};
  7. use term;
  8. use walkdir;
  9. /// Returns true if the path has a filename indicating that it is a high-desity
  10. /// "retina" icon. Specifically, returns true the the file stem ends with
  11. /// "@2x" (a convention specified by the [Apple developer docs](
  12. /// https://developer.apple.com/library/mac/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Optimizing/Optimizing.html)).
  13. pub fn is_retina<P: AsRef<Path>>(path: P) -> bool {
  14. path
  15. .as_ref()
  16. .file_stem()
  17. .and_then(OsStr::to_str)
  18. .map(|stem| stem.ends_with("@2x"))
  19. .unwrap_or(false)
  20. }
  21. /// Creates a new file at the given path, creating any parent directories as
  22. /// needed.
  23. pub fn create_file(path: &Path) -> crate::Result<BufWriter<File>> {
  24. if let Some(parent) = path.parent() {
  25. fs::create_dir_all(&parent).chain_err(|| format!("Failed to create directory {:?}", parent))?;
  26. }
  27. let file = File::create(path).chain_err(|| format!("Failed to create file {:?}", path))?;
  28. Ok(BufWriter::new(file))
  29. }
  30. #[cfg(unix)]
  31. fn symlink_dir(src: &Path, dst: &Path) -> io::Result<()> {
  32. std::os::unix::fs::symlink(src, dst)
  33. }
  34. #[cfg(windows)]
  35. fn symlink_dir(src: &Path, dst: &Path) -> io::Result<()> {
  36. std::os::windows::fs::symlink_dir(src, dst)
  37. }
  38. #[cfg(unix)]
  39. fn symlink_file(src: &Path, dst: &Path) -> io::Result<()> {
  40. std::os::unix::fs::symlink(src, dst)
  41. }
  42. #[cfg(windows)]
  43. fn symlink_file(src: &Path, dst: &Path) -> io::Result<()> {
  44. std::os::windows::fs::symlink_file(src, dst)
  45. }
  46. /// Copies a regular file from one path to another, creating any parent
  47. /// directories of the destination path as necessary. Fails if the source path
  48. /// is a directory or doesn't exist.
  49. pub fn copy_file(from: &Path, to: &Path) -> crate::Result<()> {
  50. if !from.exists() {
  51. bail!("{:?} does not exist", from);
  52. }
  53. if !from.is_file() {
  54. bail!("{:?} is not a file", from);
  55. }
  56. let dest_dir = to.parent().expect("No data in parent");
  57. fs::create_dir_all(dest_dir).chain_err(|| format!("Failed to create {:?}", dest_dir))?;
  58. fs::copy(from, to).chain_err(|| format!("Failed to copy {:?} to {:?}", from, to))?;
  59. Ok(())
  60. }
  61. /// Recursively copies a directory file from one path to another, creating any
  62. /// parent directories of the destination path as necessary. Fails if the
  63. /// source path is not a directory or doesn't exist, or if the destination path
  64. /// already exists.
  65. pub fn copy_dir(from: &Path, to: &Path) -> crate::Result<()> {
  66. if !from.exists() {
  67. bail!("{:?} does not exist", from);
  68. }
  69. if !from.is_dir() {
  70. bail!("{:?} is not a directory", from);
  71. }
  72. if to.exists() {
  73. bail!("{:?} already exists", to);
  74. }
  75. let parent = to.parent().expect("No data in parent");
  76. fs::create_dir_all(parent).chain_err(|| format!("Failed to create {:?}", parent))?;
  77. for entry in walkdir::WalkDir::new(from) {
  78. let entry = entry?;
  79. debug_assert!(entry.path().starts_with(from));
  80. let rel_path = entry.path().strip_prefix(from)?;
  81. let dest_path = to.join(rel_path);
  82. if entry.file_type().is_symlink() {
  83. let target = fs::read_link(entry.path())?;
  84. if entry.path().is_dir() {
  85. symlink_dir(&target, &dest_path)?;
  86. } else {
  87. symlink_file(&target, &dest_path)?;
  88. }
  89. } else if entry.file_type().is_dir() {
  90. fs::create_dir(dest_path)?;
  91. } else {
  92. fs::copy(entry.path(), dest_path)?;
  93. }
  94. }
  95. Ok(())
  96. }
  97. /// Given a path (absolute or relative) to a resource file, returns the
  98. /// relative path from the bundle resources directory where that resource
  99. /// should be stored.
  100. pub fn resource_relpath(path: &Path) -> PathBuf {
  101. let mut dest = PathBuf::new();
  102. for component in path.components() {
  103. match component {
  104. Component::Prefix(_) => {}
  105. Component::RootDir => dest.push("_root_"),
  106. Component::CurDir => {}
  107. Component::ParentDir => dest.push("_up_"),
  108. Component::Normal(string) => dest.push(string),
  109. }
  110. }
  111. dest
  112. }
  113. /// Prints a message to stderr, in the same format that `cargo` uses,
  114. /// indicating that we are creating a bundle with the given filename.
  115. pub fn print_bundling(filename: &str) -> crate::Result<()> {
  116. print_progress("Bundling", filename)
  117. }
  118. /// Prints a message to stderr, in the same format that `cargo` uses,
  119. /// indicating that we have finished the the given bundles.
  120. pub fn print_finished(output_paths: &Vec<PathBuf>) -> crate::Result<()> {
  121. let pluralised = if output_paths.len() == 1 {
  122. "bundle"
  123. } else {
  124. "bundles"
  125. };
  126. let msg = format!("{} {} at:", output_paths.len(), pluralised);
  127. print_progress("Finished", &msg)?;
  128. for path in output_paths {
  129. println!(" {}", path.display());
  130. }
  131. Ok(())
  132. }
  133. fn safe_term_attr<T: term::Terminal + ?Sized>(
  134. output: &mut Box<T>,
  135. attr: term::Attr,
  136. ) -> term::Result<()> {
  137. match output.supports_attr(attr) {
  138. true => output.attr(attr),
  139. false => Ok(()),
  140. }
  141. }
  142. fn print_progress(step: &str, msg: &str) -> crate::Result<()> {
  143. if let Some(mut output) = term::stderr() {
  144. safe_term_attr(&mut output, term::Attr::Bold)?;
  145. output.fg(term::color::GREEN)?;
  146. write!(output, " {}", step)?;
  147. output.reset()?;
  148. write!(output, " {}\n", msg)?;
  149. output.flush()?;
  150. Ok(())
  151. } else {
  152. let mut output = io::stderr();
  153. write!(output, " {}", step)?;
  154. write!(output, " {}\n", msg)?;
  155. output.flush()?;
  156. Ok(())
  157. }
  158. }
  159. /// Prints a warning message to stderr, in the same format that `cargo` uses.
  160. pub fn print_warning(message: &str) -> crate::Result<()> {
  161. if let Some(mut output) = term::stderr() {
  162. safe_term_attr(&mut output, term::Attr::Bold)?;
  163. output.fg(term::color::YELLOW)?;
  164. write!(output, "warning:")?;
  165. output.reset()?;
  166. write!(output, " {}\n", message)?;
  167. output.flush()?;
  168. Ok(())
  169. } else {
  170. let mut output = io::stderr();
  171. write!(output, "warning:")?;
  172. write!(output, " {}\n", message)?;
  173. output.flush()?;
  174. Ok(())
  175. }
  176. }
  177. /// Prints a Info message to stderr.
  178. pub fn print_info(message: &str) -> crate::Result<()> {
  179. if let Some(mut output) = term::stderr() {
  180. safe_term_attr(&mut output, term::Attr::Bold)?;
  181. output.fg(term::color::GREEN)?;
  182. write!(output, "info:")?;
  183. output.reset()?;
  184. write!(output, " {}\n", message)?;
  185. output.flush()?;
  186. Ok(())
  187. } else {
  188. let mut output = io::stderr();
  189. write!(output, "info:")?;
  190. write!(output, " {}\n", message)?;
  191. output.flush()?;
  192. Ok(())
  193. }
  194. }
  195. /// Prints an error to stderr, in the same format that `cargo` uses.
  196. pub fn print_error(error: &crate::Error) -> crate::Result<()> {
  197. if let Some(mut output) = term::stderr() {
  198. safe_term_attr(&mut output, term::Attr::Bold)?;
  199. output.fg(term::color::RED)?;
  200. write!(output, "error:")?;
  201. output.reset()?;
  202. safe_term_attr(&mut output, term::Attr::Bold)?;
  203. writeln!(output, " {}", error)?;
  204. output.reset()?;
  205. for cause in error.iter().skip(1) {
  206. writeln!(output, " Caused by: {}", cause)?;
  207. }
  208. if let Some(backtrace) = error.backtrace() {
  209. writeln!(output, "{:?}", backtrace)?;
  210. }
  211. output.flush()?;
  212. std::process::exit(1)
  213. } else {
  214. let mut output = io::stderr();
  215. write!(output, "error:")?;
  216. writeln!(output, " {}", error)?;
  217. for cause in error.iter().skip(1) {
  218. writeln!(output, " Caused by: {}", cause)?;
  219. }
  220. if let Some(backtrace) = error.backtrace() {
  221. writeln!(output, "{:?}", backtrace)?;
  222. }
  223. output.flush()?;
  224. std::process::exit(1)
  225. }
  226. }
  227. #[cfg(test)]
  228. mod tests {
  229. use super::{copy_dir, create_file, is_retina, resource_relpath, symlink_file};
  230. use std;
  231. use std::io::Write;
  232. use std::path::PathBuf;
  233. use tempfile;
  234. #[test]
  235. fn create_file_with_parent_dirs() {
  236. let tmp = tempfile::tempdir().expect("Unable to create temp dir");
  237. assert!(!tmp.path().join("parent").exists());
  238. {
  239. let mut file =
  240. create_file(&tmp.path().join("parent/file.txt")).expect("Failed to create file");
  241. write!(file, "Hello, world!\n").expect("unable to write file");
  242. }
  243. assert!(tmp.path().join("parent").is_dir());
  244. assert!(tmp.path().join("parent/file.txt").is_file());
  245. }
  246. #[test]
  247. fn copy_dir_with_symlinks() {
  248. // Create a directory structure that looks like this:
  249. // ${TMP}/orig/
  250. // sub/
  251. // file.txt
  252. // link -> sub/file.txt
  253. let tmp = tempfile::tempdir().expect("unable to create tempdir");
  254. {
  255. let mut file =
  256. create_file(&tmp.path().join("orig/sub/file.txt")).expect("Unable to create file");
  257. write!(file, "Hello, world!\n").expect("Unable to write to file");
  258. }
  259. symlink_file(
  260. &PathBuf::from("sub/file.txt"),
  261. &tmp.path().join("orig/link"),
  262. )
  263. .expect("Failed to create symlink");
  264. assert_eq!(
  265. std::fs::read(tmp.path().join("orig/link"))
  266. .expect("Failed to read file")
  267. .as_slice(),
  268. b"Hello, world!\n"
  269. );
  270. // Copy ${TMP}/orig to ${TMP}/parent/copy, and make sure that the
  271. // directory structure, file, and symlink got copied correctly.
  272. copy_dir(&tmp.path().join("orig"), &tmp.path().join("parent/copy"))
  273. .expect("Failed to copy dir");
  274. assert!(tmp.path().join("parent/copy").is_dir());
  275. assert!(tmp.path().join("parent/copy/sub").is_dir());
  276. assert!(tmp.path().join("parent/copy/sub/file.txt").is_file());
  277. assert_eq!(
  278. std::fs::read(tmp.path().join("parent/copy/sub/file.txt"))
  279. .expect("Failed to read file")
  280. .as_slice(),
  281. b"Hello, world!\n"
  282. );
  283. assert!(tmp.path().join("parent/copy/link").exists());
  284. assert_eq!(
  285. std::fs::read_link(tmp.path().join("parent/copy/link")).expect("Failed to read from symlink"),
  286. PathBuf::from("sub/file.txt")
  287. );
  288. assert_eq!(
  289. std::fs::read(tmp.path().join("parent/copy/link"))
  290. .expect("Failed to read from file")
  291. .as_slice(),
  292. b"Hello, world!\n"
  293. );
  294. }
  295. #[test]
  296. fn retina_icon_paths() {
  297. assert!(!is_retina("data/icons/512x512.png"));
  298. assert!(is_retina("data/icons/512x512@2x.png"));
  299. }
  300. #[test]
  301. fn resource_relative_paths() {
  302. assert_eq!(
  303. resource_relpath(&PathBuf::from("./data/images/button.png")),
  304. PathBuf::from("data/images/button.png")
  305. );
  306. assert_eq!(
  307. resource_relpath(&PathBuf::from("../../images/wheel.png")),
  308. PathBuf::from("_up_/_up_/images/wheel.png")
  309. );
  310. assert_eq!(
  311. resource_relpath(&PathBuf::from("/home/ferris/crab.png")),
  312. PathBuf::from("_root_/home/ferris/crab.png")
  313. );
  314. }
  315. }