You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

185 lines
5.8KB

  1. use std::fs::{copy, create_dir_all, read_dir, File};
  2. use std::io::prelude::*;
  3. use std::path::{Path, PathBuf};
  4. use std::time::SystemTime;
  5. use walkdir::WalkDir;
  6. use errors::{Error, Result};
  7. pub fn is_path_in_directory(parent: &Path, path: &Path) -> Result<bool> {
  8. let canonical_path = path
  9. .canonicalize()
  10. .map_err(|e| format!("Failed to canonicalize {}: {}", path.display(), e))?;
  11. let canonical_parent = parent
  12. .canonicalize()
  13. .map_err(|e| format!("Failed to canonicalize {}: {}", parent.display(), e))?;
  14. Ok(canonical_path.starts_with(canonical_parent))
  15. }
  16. /// Create a file with the content given
  17. pub fn create_file(path: &Path, content: &str) -> Result<()> {
  18. let mut file =
  19. File::create(&path).map_err(|e| Error::chain(format!("Failed to create {:?}", path), e))?;
  20. file.write_all(content.as_bytes())?;
  21. Ok(())
  22. }
  23. /// Create a directory at the given path if it doesn't exist already
  24. pub fn ensure_directory_exists(path: &Path) -> Result<()> {
  25. if !path.exists() {
  26. create_directory(path)?;
  27. }
  28. Ok(())
  29. }
  30. /// Very similar to `create_dir` from the std except it checks if the folder
  31. /// exists before creating it
  32. pub fn create_directory(path: &Path) -> Result<()> {
  33. if !path.exists() {
  34. create_dir_all(path).map_err(|e| {
  35. Error::chain(format!("Was not able to create folder {}", path.display()), e)
  36. })?;
  37. }
  38. Ok(())
  39. }
  40. /// Return the content of a file, with error handling added
  41. pub fn read_file(path: &Path) -> Result<String> {
  42. let mut content = String::new();
  43. File::open(path)
  44. .map_err(|e| Error::chain(format!("Failed to open '{:?}'", path.display()), e))?
  45. .read_to_string(&mut content)?;
  46. // Remove utf-8 BOM if any.
  47. if content.starts_with("\u{feff}") {
  48. content.drain(..3);
  49. }
  50. Ok(content)
  51. }
  52. /// Return the content of a file, with error handling added.
  53. /// The default error message is overwritten by the message given.
  54. /// That means it is allocation 2 strings, oh well
  55. pub fn read_file_with_error(path: &Path, message: &str) -> Result<String> {
  56. let res = read_file(&path);
  57. if res.is_ok() {
  58. return res;
  59. }
  60. let mut err = Error::msg(message);
  61. err.source = res.unwrap_err().source;
  62. Err(err)
  63. }
  64. /// Looks into the current folder for the path and see if there's anything that is not a .md
  65. /// file. Those will be copied next to the rendered .html file
  66. pub fn find_related_assets(path: &Path) -> Vec<PathBuf> {
  67. let mut assets = vec![];
  68. for entry in read_dir(path).unwrap().filter_map(std::result::Result::ok) {
  69. let entry_path = entry.path();
  70. if entry_path.is_file() {
  71. match entry_path.extension() {
  72. Some(e) => match e.to_str() {
  73. Some("md") => continue,
  74. _ => assets.push(entry_path.to_path_buf()),
  75. },
  76. None => continue,
  77. }
  78. }
  79. }
  80. assets
  81. }
  82. /// Copy a file but takes into account where to start the copy as
  83. /// there might be folders we need to create on the way
  84. pub fn copy_file(src: &Path, dest: &PathBuf, base_path: &PathBuf, hard_link: bool) -> Result<()> {
  85. let relative_path = src.strip_prefix(base_path).unwrap();
  86. let target_path = dest.join(relative_path);
  87. if let Some(parent_directory) = target_path.parent() {
  88. create_dir_all(parent_directory)?;
  89. }
  90. if hard_link {
  91. std::fs::hard_link(src, target_path)?
  92. } else {
  93. copy(src, target_path)?;
  94. }
  95. Ok(())
  96. }
  97. pub fn copy_directory(src: &PathBuf, dest: &PathBuf, hard_link: bool) -> Result<()> {
  98. for entry in WalkDir::new(src).into_iter().filter_map(std::result::Result::ok) {
  99. let relative_path = entry.path().strip_prefix(src).unwrap();
  100. let target_path = dest.join(relative_path);
  101. if entry.path().is_dir() {
  102. if !target_path.exists() {
  103. create_directory(&target_path)?;
  104. }
  105. } else {
  106. copy_file(entry.path(), dest, src, hard_link)?;
  107. }
  108. }
  109. Ok(())
  110. }
  111. pub fn get_file_time(path: &Path) -> Option<SystemTime> {
  112. path.metadata().ok().and_then(|meta| {
  113. Some(match (meta.created().ok(), meta.modified().ok()) {
  114. (Some(tc), Some(tm)) => tc.max(tm),
  115. (Some(tc), None) => tc,
  116. (None, Some(tm)) => tm,
  117. (None, None) => return None,
  118. })
  119. })
  120. }
  121. /// Compares source and target files' timestamps and returns true if the source file
  122. /// has been created _or_ updated after the target file has
  123. pub fn file_stale<PS, PT>(p_source: PS, p_target: PT) -> bool
  124. where
  125. PS: AsRef<Path>,
  126. PT: AsRef<Path>,
  127. {
  128. let p_source = p_source.as_ref();
  129. let p_target = p_target.as_ref();
  130. if !p_target.exists() {
  131. return true;
  132. }
  133. let time_source = get_file_time(p_source);
  134. let time_target = get_file_time(p_target);
  135. time_source.and_then(|ts| time_target.map(|tt| ts > tt)).unwrap_or(true)
  136. }
  137. #[cfg(test)]
  138. mod tests {
  139. use std::fs::File;
  140. use tempfile::tempdir;
  141. use super::find_related_assets;
  142. #[test]
  143. fn can_find_related_assets() {
  144. let tmp_dir = tempdir().expect("create temp dir");
  145. File::create(tmp_dir.path().join("index.md")).unwrap();
  146. File::create(tmp_dir.path().join("example.js")).unwrap();
  147. File::create(tmp_dir.path().join("graph.jpg")).unwrap();
  148. File::create(tmp_dir.path().join("fail.png")).unwrap();
  149. let assets = find_related_assets(tmp_dir.path());
  150. assert_eq!(assets.len(), 3);
  151. assert_eq!(assets.iter().filter(|p| p.extension().unwrap() != "md").count(), 3);
  152. assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "example.js").count(), 1);
  153. assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "graph.jpg").count(), 1);
  154. assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "fail.png").count(), 1);
  155. }
  156. }