file.rs 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. use std::io::fs;
  2. use std::io;
  3. use colours::{Plain, Style, Black, Red, Green, Yellow, Blue, Purple, Cyan};
  4. use column::{Column, Permissions, FileName, FileSize, User, Group};
  5. use format::{formatBinaryBytes, formatDecimalBytes};
  6. use unix::{get_user_name, get_group_name};
  7. static MEDIA_TYPES: &'static [&'static str] = &[
  8. "png", "jpeg", "jpg", "gif", "bmp", "tiff", "tif",
  9. "ppm", "pgm", "pbm", "pnm", "webp", "raw", "arw",
  10. "svg", "pdf", "stl", "eps", "dvi", "ps" ];
  11. static COMPRESSED_TYPES: &'static [&'static str] = &[
  12. "zip", "tar", "Z", "gz", "bz2", "a", "ar", "7z",
  13. "iso", "dmg", "tc", "rar", "par" ];
  14. // Each file is definitely going to get `stat`ted at least once, if
  15. // only to determine what kind of file it is, so carry the `stat`
  16. // result around with the file for safe keeping.
  17. pub struct File<'a> {
  18. pub name: &'a str,
  19. pub ext: Option<&'a str>,
  20. pub path: &'a Path,
  21. pub stat: io::FileStat,
  22. }
  23. impl<'a> File<'a> {
  24. pub fn from_path(path: &'a Path) -> File<'a> {
  25. let filename: &str = path.filename_str().unwrap();
  26. // We have to use lstat here instad of file.stat(), as it
  27. // doesn't follow symbolic links. Otherwise, the stat() call
  28. // will fail if it encounters a link that's target is
  29. // non-existent.
  30. let stat: io::FileStat = match fs::lstat(path) {
  31. Ok(stat) => stat,
  32. Err(e) => fail!("Couldn't stat {}: {}", filename, e),
  33. };
  34. return File {
  35. path: path,
  36. stat: stat,
  37. name: filename,
  38. ext: File::ext(filename),
  39. };
  40. }
  41. fn ext(name: &'a str) -> Option<&'a str> {
  42. let re = regex!(r"\.(.+)$");
  43. re.captures(name).map(|caps| caps.at(1))
  44. }
  45. pub fn is_dotfile(&self) -> bool {
  46. self.name.starts_with(".")
  47. }
  48. pub fn display(&self, column: &Column) -> StrBuf {
  49. match *column {
  50. Permissions => self.permissions(),
  51. FileName => self.file_colour().paint(self.name.as_slice()),
  52. FileSize(si) => self.file_size(si),
  53. User => get_user_name(self.stat.unstable.uid as i32).unwrap_or(self.stat.unstable.uid.to_str()),
  54. Group => get_group_name(self.stat.unstable.gid as u32).unwrap_or(self.stat.unstable.gid.to_str()),
  55. }
  56. }
  57. fn file_size(&self, si: bool) -> StrBuf {
  58. // Don't report file sizes for directories. I've never looked
  59. // at one of those numbers and gained any information from it.
  60. if self.stat.kind == io::TypeDirectory {
  61. Black.bold().paint("---")
  62. } else {
  63. let sizeStr = if si {
  64. formatBinaryBytes(self.stat.size)
  65. } else {
  66. formatDecimalBytes(self.stat.size)
  67. };
  68. return Green.bold().paint(sizeStr.as_slice());
  69. }
  70. }
  71. fn type_char(&self) -> StrBuf {
  72. return match self.stat.kind {
  73. io::TypeFile => ".".to_strbuf(),
  74. io::TypeDirectory => Blue.paint("d"),
  75. io::TypeNamedPipe => Yellow.paint("|"),
  76. io::TypeBlockSpecial => Purple.paint("s"),
  77. io::TypeSymlink => Cyan.paint("l"),
  78. _ => "?".to_owned(),
  79. }
  80. }
  81. fn file_colour(&self) -> Style {
  82. if self.stat.kind == io::TypeDirectory {
  83. Blue.normal()
  84. }
  85. else if self.stat.perm.contains(io::UserExecute) {
  86. Green.bold()
  87. }
  88. else if self.name.ends_with("~") {
  89. Black.bold()
  90. }
  91. else if self.name.starts_with("README") {
  92. Yellow.bold().underline()
  93. }
  94. else if self.ext.is_some() && MEDIA_TYPES.iter().any(|&s| s == self.ext.unwrap()) {
  95. Purple.normal()
  96. }
  97. else if self.ext.is_some() && COMPRESSED_TYPES.iter().any(|&s| s == self.ext.unwrap()) {
  98. Red.normal()
  99. }
  100. else {
  101. Plain
  102. }
  103. }
  104. fn permissions(&self) -> StrBuf {
  105. let bits = self.stat.perm;
  106. return format!("{}{}{}{}{}{}{}{}{}{}",
  107. self.type_char(),
  108. bit(bits, io::UserRead, "r", Yellow.bold()),
  109. bit(bits, io::UserWrite, "w", Red.bold()),
  110. bit(bits, io::UserExecute, "x", Green.bold().underline()),
  111. bit(bits, io::GroupRead, "r", Yellow.normal()),
  112. bit(bits, io::GroupWrite, "w", Red.normal()),
  113. bit(bits, io::GroupExecute, "x", Green.normal()),
  114. bit(bits, io::OtherRead, "r", Yellow.normal()),
  115. bit(bits, io::OtherWrite, "w", Red.normal()),
  116. bit(bits, io::OtherExecute, "x", Green.normal()),
  117. );
  118. }
  119. }
  120. fn bit(bits: io::FilePermission, bit: io::FilePermission, other: &'static str, style: Style) -> StrBuf {
  121. if bits.contains(bit) {
  122. style.paint(other.as_slice())
  123. } else {
  124. Black.bold().paint("-".as_slice())
  125. }
  126. }