exa.rs 5.1 KB

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