dir.rs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. use std::io::{fs, IoResult};
  2. use file::{File, GREY};
  3. #[cfg(feature="git")] use ansi_term::ANSIString;
  4. #[cfg(feature="git")] use ansi_term::Colour::*;
  5. #[cfg(feature="git")] use git2;
  6. /// A **Dir** provides a cached list of the file paths in a directory that's
  7. /// being listed.
  8. ///
  9. /// This object gets passed to the Files themselves, in order for them to
  10. /// check the existence of surrounding files, then highlight themselves
  11. /// accordingly. (See `File#get_source_files`)
  12. pub struct Dir {
  13. contents: Vec<Path>,
  14. path: Path,
  15. git: Option<Git>,
  16. }
  17. impl Dir {
  18. /// Create a new Dir object filled with all the files in the directory
  19. /// pointed to by the given path. Fails if the directory can't be read, or
  20. /// isn't actually a directory.
  21. pub fn readdir(path: Path) -> IoResult<Dir> {
  22. fs::readdir(&path).map(|paths| Dir {
  23. contents: paths,
  24. path: path.clone(),
  25. git: Git::scan(&path).ok(),
  26. })
  27. }
  28. /// Produce a vector of File objects from an initialised directory,
  29. /// printing out an error if any of the Files fail to be created.
  30. pub fn files(&self) -> Vec<File> {
  31. let mut files = vec![];
  32. for path in self.contents.iter() {
  33. match File::from_path(path, Some(self)) {
  34. Ok(file) => files.push(file),
  35. Err(e) => println!("{}: {}", path.display(), e),
  36. }
  37. }
  38. files
  39. }
  40. /// Whether this directory contains a file with the given path.
  41. pub fn contains(&self, path: &Path) -> bool {
  42. self.contents.contains(path)
  43. }
  44. /// Append a path onto the path specified by this directory.
  45. pub fn join(&self, child: Path) -> Path {
  46. self.path.join(child)
  47. }
  48. /// Return whether there's a Git repository on or above this directory.
  49. pub fn has_git_repo(&self) -> bool {
  50. self.git.is_some()
  51. }
  52. /// Get a string describing the Git status of the given file.
  53. pub fn git_status(&self, path: &Path, prefix_lookup: bool) -> String {
  54. match (&self.git, prefix_lookup) {
  55. (&Some(ref git), false) => git.status(path),
  56. (&Some(ref git), true) => git.dir_status(path),
  57. (&None, _) => GREY.paint("--").to_string(),
  58. }
  59. }
  60. }
  61. /// Container of Git statuses for all the files in this folder's Git repository.
  62. #[cfg(feature="git")]
  63. struct Git {
  64. statuses: Vec<(String, git2::Status)>,
  65. }
  66. #[cfg(feature="git")]
  67. impl Git {
  68. /// Discover a Git repository on or above this directory, scanning it for
  69. /// the files' statuses if one is found.
  70. fn scan(path: &Path) -> Result<Git, git2::Error> {
  71. let repo = try!(git2::Repository::discover(path));
  72. let statuses = try!(repo.statuses(None));
  73. Ok(Git { statuses: statuses.iter().map(|e| (e.path().unwrap().to_string(), e.status())).collect() })
  74. }
  75. /// Get the status for the file at the given path, if present.
  76. fn status(&self, path: &Path) -> String {
  77. let status = self.statuses.iter()
  78. .find(|p| p.0 == path.as_str().unwrap());
  79. match status {
  80. Some(&(_, s)) => format!("{}{}", Git::index_status(s), Git::working_tree_status(s)),
  81. None => GREY.paint("--").to_string(),
  82. }
  83. }
  84. /// Get the combined status for all the files whose paths begin with the
  85. /// path that gets passed in. This is used for getting the status of
  86. /// directories, which don't really have an 'official' status.
  87. fn dir_status(&self, dir: &Path) -> String {
  88. let status = self.statuses.iter()
  89. .filter(|p| p.0.starts_with(dir.as_str().unwrap()))
  90. .fold(git2::Status::empty(), |a, b| a | b.1);
  91. match status {
  92. s => format!("{}{}", Git::index_status(s), Git::working_tree_status(s)),
  93. }
  94. }
  95. /// The character to display if the file has been modified, but not staged.
  96. fn working_tree_status(status: git2::Status) -> ANSIString<'static> {
  97. match status {
  98. s if s.contains(git2::STATUS_WT_NEW) => Green.paint("A"),
  99. s if s.contains(git2::STATUS_WT_MODIFIED) => Blue.paint("M"),
  100. s if s.contains(git2::STATUS_WT_DELETED) => Red.paint("D"),
  101. s if s.contains(git2::STATUS_WT_RENAMED) => Yellow.paint("R"),
  102. s if s.contains(git2::STATUS_WT_TYPECHANGE) => Purple.paint("T"),
  103. _ => GREY.paint("-"),
  104. }
  105. }
  106. /// The character to display if the file has been modified, and the change
  107. /// has been staged.
  108. fn index_status(status: git2::Status) -> ANSIString<'static> {
  109. match status {
  110. s if s.contains(git2::STATUS_INDEX_NEW) => Green.paint("A"),
  111. s if s.contains(git2::STATUS_INDEX_MODIFIED) => Blue.paint("M"),
  112. s if s.contains(git2::STATUS_INDEX_DELETED) => Red.paint("D"),
  113. s if s.contains(git2::STATUS_INDEX_RENAMED) => Yellow.paint("R"),
  114. s if s.contains(git2::STATUS_INDEX_TYPECHANGE) => Purple.paint("T"),
  115. _ => GREY.paint("-"),
  116. }
  117. }
  118. }
  119. #[cfg(not(feature="git"))]
  120. struct Git;
  121. #[cfg(not(feature="git"))]
  122. impl Git {
  123. fn scan(_: &Path) -> Result<Git, ()> {
  124. // Don't do anything without Git support
  125. Err(())
  126. }
  127. fn status(&self, _: &Path) -> String {
  128. // The Err above means that this should never happen
  129. panic!("Tried to access a Git repo without Git support!");
  130. }
  131. }