exa.rs 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. use std::io::fs;
  2. use std::io;
  3. use std::os;
  4. use colours::{Plain, Style, Black, Red, Green, Yellow, Blue, Purple, Cyan};
  5. mod colours;
  6. fn main() {
  7. match os::args().as_slice() {
  8. [] => unreachable!(),
  9. [_] => { list(Path::new(".")) },
  10. [_, ref p] => { list(Path::new(p.as_slice())) },
  11. _ => { fail!("args?") },
  12. }
  13. }
  14. enum Permissions {
  15. Permissions,
  16. }
  17. enum FileName {
  18. FileName,
  19. }
  20. struct FileSize {
  21. useSIPrefixes: bool,
  22. }
  23. trait Column {
  24. fn display(&self, stat: &io::FileStat, filename: &str) -> ~str;
  25. }
  26. impl Column for FileName {
  27. fn display(&self, stat: &io::FileStat, filename: &str) -> ~str {
  28. file_colour(stat, filename).paint(filename.to_owned())
  29. }
  30. }
  31. impl Column for Permissions {
  32. fn display(&self, stat: &io::FileStat, filename: &str) -> ~str {
  33. let bits = stat.perm;
  34. return format!("{}{}{}{}{}{}{}{}{}{}",
  35. type_char(stat.kind),
  36. bit(bits, io::UserRead, ~"r", Yellow.bold()),
  37. bit(bits, io::UserWrite, ~"w", Red.bold()),
  38. bit(bits, io::UserExecute, ~"x", Green.bold().underline()),
  39. bit(bits, io::GroupRead, ~"r", Yellow.normal()),
  40. bit(bits, io::GroupWrite, ~"w", Red.normal()),
  41. bit(bits, io::GroupExecute, ~"x", Green.normal()),
  42. bit(bits, io::OtherRead, ~"r", Yellow.normal()),
  43. bit(bits, io::OtherWrite, ~"w", Red.normal()),
  44. bit(bits, io::OtherExecute, ~"x", Green.normal()),
  45. );
  46. }
  47. }
  48. impl Column for FileSize {
  49. fn display(&self, stat: &io::FileStat, filename: &str) -> ~str {
  50. let sizeStr = if self.useSIPrefixes {
  51. formatBytes(stat.size, 1024, ~[ "B ", "KiB", "MiB", "GiB", "TiB" ])
  52. } else {
  53. formatBytes(stat.size, 1000, ~[ "B ", "KB", "MB", "GB", "TB" ])
  54. };
  55. return if stat.kind == io::TypeDirectory {
  56. Green.normal()
  57. } else {
  58. Green.bold()
  59. }.paint(sizeStr);
  60. }
  61. }
  62. fn formatBytes(mut amount: u64, kilo: u64, prefixes: ~[&str]) -> ~str {
  63. let mut prefix = 0;
  64. while amount > kilo {
  65. amount /= kilo;
  66. prefix += 1;
  67. }
  68. return format!("{:4}{}", amount, prefixes[prefix]);
  69. }
  70. // Each file is definitely going to get `stat`ted at least once, if
  71. // only to determine what kind of file it is, so carry the `stat`
  72. // result around with the file for safe keeping.
  73. struct File<'a> {
  74. name: &'a str,
  75. path: &'a Path,
  76. stat: io::FileStat,
  77. }
  78. impl<'a> File<'a> {
  79. fn from_path(path: &'a Path) -> File<'a> {
  80. let filename: &str = path.filename_str().unwrap();
  81. // We have to use lstat here instad of file.stat(), as it
  82. // doesn't follow symbolic links. Otherwise, the stat() call
  83. // will fail if it encounters a link that's target is
  84. // non-existent.
  85. let stat: io::FileStat = match fs::lstat(path) {
  86. Ok(stat) => stat,
  87. Err(e) => fail!("Couldn't stat {}: {}", filename, e),
  88. };
  89. return File { path: path, stat: stat, name: filename };
  90. }
  91. }
  92. fn list(path: Path) {
  93. let mut files = match fs::readdir(&path) {
  94. Ok(files) => files,
  95. Err(e) => fail!("readdir: {}", e),
  96. };
  97. files.sort_by(|a, b| a.filename_str().cmp(&b.filename_str()));
  98. for subpath in files.iter() {
  99. let file = File::from_path(subpath);
  100. let columns = ~[
  101. ~Permissions as ~Column,
  102. ~FileSize { useSIPrefixes: false } as ~Column,
  103. ~FileName as ~Column
  104. ];
  105. let mut cells = columns.iter().map(|c| c.display(&file.stat, file.name));
  106. let mut first = true;
  107. for cell in cells {
  108. if first {
  109. first = false;
  110. } else {
  111. print!(" ");
  112. }
  113. print!("{}", cell);
  114. }
  115. print!("\n");
  116. }
  117. }
  118. fn file_colour(stat: &io::FileStat, filename: &str) -> Style {
  119. if stat.kind == io::TypeDirectory {
  120. Blue.normal()
  121. } else if stat.perm & io::UserExecute == io::UserExecute {
  122. Green.normal()
  123. } else if filename.ends_with("~") {
  124. Black.bold()
  125. } else {
  126. Plain
  127. }
  128. }
  129. fn bit(bits: u32, bit: u32, other: ~str, style: Style) -> ~str {
  130. if bits & bit == bit {
  131. style.paint(other)
  132. } else {
  133. Black.bold().paint(~"-")
  134. }
  135. }
  136. fn type_char(t: io::FileType) -> ~str {
  137. return match t {
  138. io::TypeFile => ~".",
  139. io::TypeDirectory => Blue.paint("d"),
  140. io::TypeNamedPipe => Yellow.paint("|"),
  141. io::TypeBlockSpecial => Purple.paint("s"),
  142. io::TypeSymlink => Cyan.paint("l"),
  143. _ => ~"?",
  144. }
  145. }