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.

161 lines
5.1KB

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