file.rs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. use std::io::fs;
  2. use std::io;
  3. use colours::{Plain, Style, Black, Red, Green, Yellow, Blue, Purple, Cyan, Fixed};
  4. use column::{Column, Permissions, FileName, FileSize, User, Group};
  5. use format::{format_metric_bytes, format_IEC_bytes};
  6. use unix::{get_user_name, get_group_name};
  7. use sort::SortPart;
  8. static MEDIA_TYPES: &'static [&'static str] = &[
  9. "png", "jpeg", "jpg", "gif", "bmp", "tiff", "tif",
  10. "ppm", "pgm", "pbm", "pnm", "webp", "raw", "arw",
  11. "svg", "pdf", "stl", "eps", "dvi", "ps" ];
  12. static COMPRESSED_TYPES: &'static [&'static str] = &[
  13. "zip", "tar", "Z", "gz", "bz2", "a", "ar", "7z",
  14. "iso", "dmg", "tc", "rar", "par" ];
  15. // Instead of working with Rust's Paths, we have our own File object
  16. // that holds the Path and various cached information. Each file is
  17. // definitely going to have its filename used at least once, its stat
  18. // information queried at least once, and its file extension extracted
  19. // at least once, so we may as well carry around that information with
  20. // the actual path.
  21. pub struct File<'a> {
  22. pub name: &'a str,
  23. pub ext: Option<&'a str>,
  24. pub path: &'a Path,
  25. pub stat: io::FileStat,
  26. pub parts: Vec<SortPart>,
  27. }
  28. impl<'a> File<'a> {
  29. pub fn from_path(path: &'a Path) -> File<'a> {
  30. // Getting the string from a filename fails whenever it's not
  31. // UTF-8 representable - just assume it is for now.
  32. let filename: &str = path.filename_str().unwrap();
  33. // Use lstat here instead of file.stat(), as it doesn't follow
  34. // symbolic links. Otherwise, the stat() call will fail if it
  35. // encounters a link that's target is non-existent.
  36. let stat: io::FileStat = match fs::lstat(path) {
  37. Ok(stat) => stat,
  38. Err(e) => fail!("Couldn't stat {}: {}", filename, e),
  39. };
  40. return File {
  41. path: path,
  42. stat: stat,
  43. name: filename,
  44. ext: File::ext(filename),
  45. parts: SortPart::split_into_parts(filename),
  46. };
  47. }
  48. fn ext(name: &'a str) -> Option<&'a str> {
  49. // The extension is the series of characters after a dot at
  50. // the end of a filename. This deliberately also counts
  51. // dotfiles - the ".git" folder has the extension "git".
  52. let re = regex!(r"\.([^.]+)$");
  53. re.captures(name).map(|caps| caps.at(1))
  54. }
  55. pub fn is_dotfile(&self) -> bool {
  56. self.name.starts_with(".")
  57. }
  58. fn is_tmpfile(&self) -> bool {
  59. self.name.ends_with("~") || (self.name.starts_with("#") && self.name.ends_with("#"))
  60. }
  61. pub fn display(&self, column: &Column) -> String {
  62. match *column {
  63. Permissions => self.permissions_string(),
  64. FileName => self.file_colour().paint(self.name),
  65. FileSize(use_iec) => self.file_size(use_iec),
  66. // Display the ID if the user/group doesn't exist, which
  67. // usually means it was deleted but its files weren't.
  68. User(uid) => {
  69. let style = if uid == self.stat.unstable.uid { Yellow.bold() } else { Plain };
  70. let string = get_user_name(self.stat.unstable.uid as i32).unwrap_or(self.stat.unstable.uid.to_str());
  71. return style.paint(string.as_slice());
  72. },
  73. Group => get_group_name(self.stat.unstable.gid as u32).unwrap_or(self.stat.unstable.gid.to_str()),
  74. }
  75. }
  76. fn file_size(&self, use_iec_prefixes: bool) -> String {
  77. // Don't report file sizes for directories. I've never looked
  78. // at one of those numbers and gained any information from it.
  79. if self.stat.kind == io::TypeDirectory {
  80. Black.bold().paint("-")
  81. } else {
  82. let (size, suffix) = if use_iec_prefixes {
  83. format_IEC_bytes(self.stat.size)
  84. } else {
  85. format_metric_bytes(self.stat.size)
  86. };
  87. return format!("{}{}", Green.bold().paint(size.as_slice()), Green.paint(suffix.as_slice()));
  88. }
  89. }
  90. fn type_char(&self) -> String {
  91. return match self.stat.kind {
  92. io::TypeFile => ".".to_string(),
  93. io::TypeDirectory => Blue.paint("d"),
  94. io::TypeNamedPipe => Yellow.paint("|"),
  95. io::TypeBlockSpecial => Purple.paint("s"),
  96. io::TypeSymlink => Cyan.paint("l"),
  97. _ => "?".to_string(),
  98. }
  99. }
  100. fn file_colour(&self) -> Style {
  101. if self.stat.kind == io::TypeDirectory {
  102. Blue.bold()
  103. }
  104. else if self.stat.perm.contains(io::UserExecute) {
  105. Green.bold()
  106. }
  107. else if self.is_tmpfile() {
  108. Fixed(244).normal() // midway between white and black - should show up as grey on all terminals
  109. }
  110. else if self.name.starts_with("README") {
  111. Yellow.bold().underline()
  112. }
  113. else if self.ext.is_some() && MEDIA_TYPES.iter().any(|&s| s == self.ext.unwrap()) {
  114. Purple.normal()
  115. }
  116. else if self.ext.is_some() && COMPRESSED_TYPES.iter().any(|&s| s == self.ext.unwrap()) {
  117. Red.normal()
  118. }
  119. else {
  120. Plain
  121. }
  122. }
  123. fn permissions_string(&self) -> String {
  124. let bits = self.stat.perm;
  125. return format!("{}{}{}{}{}{}{}{}{}{}",
  126. self.type_char(),
  127. // The first three are bold because they're the ones used
  128. // most often.
  129. File::permission_bit(bits, io::UserRead, "r", Yellow.bold()),
  130. File::permission_bit(bits, io::UserWrite, "w", Red.bold()),
  131. File::permission_bit(bits, io::UserExecute, "x", Green.bold().underline()),
  132. File::permission_bit(bits, io::GroupRead, "r", Yellow.normal()),
  133. File::permission_bit(bits, io::GroupWrite, "w", Red.normal()),
  134. File::permission_bit(bits, io::GroupExecute, "x", Green.normal()),
  135. File::permission_bit(bits, io::OtherRead, "r", Yellow.normal()),
  136. File::permission_bit(bits, io::OtherWrite, "w", Red.normal()),
  137. File::permission_bit(bits, io::OtherExecute, "x", Green.normal()),
  138. );
  139. }
  140. fn permission_bit(bits: io::FilePermission, bit: io::FilePermission, character: &'static str, style: Style) -> String {
  141. if bits.contains(bit) {
  142. style.paint(character.as_slice())
  143. } else {
  144. Black.bold().paint("-".as_slice())
  145. }
  146. }
  147. }