details.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. //! The **Details** output view displays each file as a row in a table.
  2. //!
  3. //! It's used in the following situations:
  4. //!
  5. //! - Most commonly, when using the `--long` command-line argument to display the
  6. //! details of each file, which requires using a table view to hold all the data;
  7. //! - When using the `--tree` argument, which uses the same table view to display
  8. //! each file on its own line, with the table providing the tree characters;
  9. //! - When using both the `--long` and `--grid` arguments, which constructs a
  10. //! series of tables to fit all the data on the screen.
  11. //!
  12. //! You will probably recognise it from the `ls --long` command. It looks like
  13. //! this:
  14. //!
  15. //! ```text
  16. //! .rw-r--r-- 9.6k ben 29 Jun 16:16 Cargo.lock
  17. //! .rw-r--r-- 547 ben 23 Jun 10:54 Cargo.toml
  18. //! .rw-r--r-- 1.1k ben 23 Nov 2014 LICENCE
  19. //! .rw-r--r-- 2.5k ben 21 May 14:38 README.md
  20. //! .rw-r--r-- 382k ben 8 Jun 21:00 screenshot.png
  21. //! drwxr-xr-x - ben 29 Jun 14:50 src
  22. //! drwxr-xr-x - ben 28 Jun 19:53 target
  23. //! ```
  24. //!
  25. //! The table is constructed by creating a `Table` value, which produces a `Row`
  26. //! value for each file. These rows can contain a vector of `Cell`s, or they can
  27. //! contain depth information for the tree view, or both. These are described
  28. //! below.
  29. //!
  30. //!
  31. //! ## Constructing Detail Views
  32. //!
  33. //! When using the `--long` command-line argument, the details of each file are
  34. //! displayed next to its name.
  35. //!
  36. //! The table holds a vector of all the column types. For each file and column, a
  37. //! `Cell` value containing the ANSI-coloured text and Unicode width of each cell
  38. //! is generated, with the row and column determined by indexing into both arrays.
  39. //!
  40. //! The column types vector does not actually include the filename. This is
  41. //! because the filename is always the rightmost field, and as such, it does not
  42. //! need to have its width queried or be padded with spaces.
  43. //!
  44. //! To illustrate the above:
  45. //!
  46. //! ```text
  47. //! โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  48. //! โ”‚ columns: [ Permissions, Size, User, Date(Modified) ] โ”‚
  49. //! โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
  50. //! โ”‚ rows: cells: filename: โ”‚
  51. //! โ”‚ row 1: [ ".rw-r--r--", "9.6k", "ben", "29 Jun 16:16" ] Cargo.lock โ”‚
  52. //! โ”‚ row 2: [ ".rw-r--r--", "547", "ben", "23 Jun 10:54" ] Cargo.toml โ”‚
  53. //! โ”‚ row 3: [ "drwxr-xr-x", "-", "ben", "29 Jun 14:50" ] src โ”‚
  54. //! โ”‚ row 4: [ "drwxr-xr-x", "-", "ben", "28 Jun 19:53" ] target โ”‚
  55. //! โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  56. //! ```
  57. //!
  58. //! Each column in the table needs to be resized to fit its widest argument. This
  59. //! means that we must wait until every row has been added to the table before it
  60. //! can be displayed, in order to make sure that every column is wide enough.
  61. use std::io::{Write, Error as IOError, Result as IOResult};
  62. use std::ops::Add;
  63. use std::path::PathBuf;
  64. use std::vec::IntoIter as VecIntoIter;
  65. use fs::{Dir, File};
  66. use fs::feature::xattr::{Attribute, FileAttributes};
  67. use options::{FileFilter, RecurseOptions};
  68. use output::colours::Colours;
  69. use output::column::Columns;
  70. use output::cell::TextCell;
  71. use output::tree::TreeTrunk;
  72. use output::file_name::{FileName, LinkStyle, Classify};
  73. use output::table::{Table, Environment, Row as TableRow};
  74. /// With the **Details** view, the output gets formatted into columns, with
  75. /// each `Column` object showing some piece of information about the file,
  76. /// such as its size, or its permissions.
  77. ///
  78. /// To do this, the results have to be written to a table, instead of
  79. /// displaying each file immediately. Then, the width of each column can be
  80. /// calculated based on the individual results, and the fields are padded
  81. /// during output.
  82. ///
  83. /// Almost all the heavy lifting is done in a Table object, which handles the
  84. /// columns for each row.
  85. #[derive(PartialEq, Debug, Clone, Default)]
  86. pub struct Options {
  87. /// A Columns object that says which columns should be included in the
  88. /// output in the general case. Directories themselves can pick which
  89. /// columns are *added* to this list, such as the Git column.
  90. pub columns: Option<Columns>,
  91. /// Whether to show a header line or not.
  92. pub header: bool,
  93. /// Whether to show each file's extended attributes.
  94. pub xattr: bool,
  95. }
  96. pub struct Render<'a> {
  97. pub dir: Option<&'a Dir>,
  98. pub files: Vec<File<'a>>,
  99. pub colours: &'a Colours,
  100. pub classify: Classify,
  101. pub opts: &'a Options,
  102. /// Whether to recurse through directories with a tree view, and if so,
  103. /// which options to use. This field is only relevant here if the `tree`
  104. /// field of the RecurseOptions is `true`.
  105. pub recurse: Option<RecurseOptions>,
  106. /// How to sort and filter the files after getting their details.
  107. pub filter: &'a FileFilter,
  108. }
  109. struct Egg<'a> {
  110. table_row: TableRow,
  111. xattrs: Vec<Attribute>,
  112. errors: Vec<(IOError, Option<PathBuf>)>,
  113. dir: Option<Dir>,
  114. file: &'a File<'a>,
  115. }
  116. impl<'a> AsRef<File<'a>> for Egg<'a> {
  117. fn as_ref(&self) -> &File<'a> {
  118. self.file
  119. }
  120. }
  121. impl<'a> Render<'a> {
  122. pub fn render<W: Write>(self, w: &mut W) -> IOResult<()> {
  123. let columns_for_dir = match self.opts.columns {
  124. Some(cols) => cols.for_dir(self.dir),
  125. None => Vec::new(),
  126. };
  127. let env = Environment::default();
  128. let mut table = Table::new(&columns_for_dir, &self.colours, &env);
  129. let mut rows = Vec::new();
  130. if self.opts.header {
  131. let header = table.header_row();
  132. rows.push(self.render_header(header));
  133. }
  134. self.add_files_to_table(&mut table, &mut rows, &self.files, 0);
  135. for row in self.iterate(&table, rows) {
  136. writeln!(w, "{}", row.strings())?
  137. }
  138. Ok(())
  139. }
  140. /// Adds files to the table, possibly recursively. This is easily
  141. /// parallelisable, and uses a pool of threads.
  142. fn add_files_to_table<'dir>(&self, mut table: &mut Table, rows: &mut Vec<Row>, src: &Vec<File<'dir>>, depth: usize) {
  143. use num_cpus;
  144. use scoped_threadpool::Pool;
  145. use std::sync::{Arc, Mutex};
  146. use fs::feature::xattr;
  147. let mut pool = Pool::new(num_cpus::get() as u32);
  148. let mut file_eggs = Vec::new();
  149. pool.scoped(|scoped| {
  150. let file_eggs = Arc::new(Mutex::new(&mut file_eggs));
  151. let table = Arc::new(Mutex::new(&mut table));
  152. for file in src {
  153. let file_eggs = file_eggs.clone();
  154. let table = table.clone();
  155. scoped.execute(move || {
  156. let mut errors = Vec::new();
  157. let mut xattrs = Vec::new();
  158. if xattr::ENABLED {
  159. match file.path.attributes() {
  160. Ok(xs) => xattrs.extend(xs),
  161. Err(e) => errors.push((e, None)),
  162. };
  163. }
  164. let table_row = table.lock().unwrap().row_for_file(&file, !xattrs.is_empty());
  165. if !self.opts.xattr {
  166. xattrs.clear();
  167. }
  168. let mut dir = None;
  169. if let Some(r) = self.recurse {
  170. if file.is_directory() && r.tree && !r.is_too_deep(depth) {
  171. if let Ok(d) = file.to_dir(false) {
  172. dir = Some(d);
  173. }
  174. }
  175. };
  176. let egg = Egg { table_row, xattrs, errors, dir, file };
  177. file_eggs.lock().unwrap().push(egg);
  178. });
  179. }
  180. });
  181. self.filter.sort_files(&mut file_eggs);
  182. let num_eggs = file_eggs.len();
  183. for (index, egg) in file_eggs.into_iter().enumerate() {
  184. let mut files = Vec::new();
  185. let mut errors = egg.errors;
  186. let row = Row {
  187. depth: depth,
  188. cells: Some(egg.table_row),
  189. name: FileName::new(&egg.file, LinkStyle::FullLinkPaths, self.classify, self.colours).paint().promote(),
  190. last: index == num_eggs - 1,
  191. };
  192. rows.push(row);
  193. if let Some(ref dir) = egg.dir {
  194. for file_to_add in dir.files(self.filter.dot_filter) {
  195. match file_to_add {
  196. Ok(f) => files.push(f),
  197. Err((path, e)) => errors.push((e, Some(path)))
  198. }
  199. }
  200. self.filter.filter_child_files(&mut files);
  201. if !files.is_empty() {
  202. for xattr in egg.xattrs {
  203. rows.push(self.render_xattr(xattr, depth + 1, false));
  204. }
  205. for (error, path) in errors {
  206. rows.push(self.render_error(&error, depth + 1, false, path));
  207. }
  208. self.add_files_to_table(table, rows, &files, depth + 1);
  209. continue;
  210. }
  211. }
  212. let count = egg.xattrs.len();
  213. for (index, xattr) in egg.xattrs.into_iter().enumerate() {
  214. rows.push(self.render_xattr(xattr, depth + 1, errors.is_empty() && index == count - 1));
  215. }
  216. let count = errors.len();
  217. for (index, (error, path)) in errors.into_iter().enumerate() {
  218. rows.push(self.render_error(&error, depth + 1, index == count - 1, path));
  219. }
  220. }
  221. }
  222. pub fn render_header(&self, header: TableRow) -> Row {
  223. Row {
  224. depth: 0,
  225. cells: Some(header),
  226. name: TextCell::paint_str(self.colours.header, "Name"),
  227. last: false,
  228. }
  229. }
  230. fn render_error(&self, error: &IOError, depth: usize, last: bool, path: Option<PathBuf>) -> Row {
  231. let error_message = match path {
  232. Some(path) => format!("<{}: {}>", path.display(), error),
  233. None => format!("<{}>", error),
  234. };
  235. Row {
  236. depth: depth,
  237. cells: None,
  238. name: TextCell::paint(self.colours.broken_arrow, error_message),
  239. last: last,
  240. }
  241. }
  242. fn render_xattr(&self, xattr: Attribute, depth: usize, last: bool) -> Row {
  243. Row {
  244. depth: depth,
  245. cells: None,
  246. name: TextCell::paint(self.colours.perms.attribute, format!("{} (len {})", xattr.name, xattr.size)),
  247. last: last,
  248. }
  249. }
  250. pub fn render_file(&self, cells: TableRow, name_cell: TextCell, depth: usize, last: bool) -> Row {
  251. Row {
  252. depth: depth,
  253. cells: Some(cells),
  254. name: name_cell,
  255. last: last,
  256. }
  257. }
  258. /// Render the table as a vector of Cells, to be displayed on standard output.
  259. pub fn iterate(&self, table: &'a Table<'a>, rows: Vec<Row>) -> Iter<'a> {
  260. Iter {
  261. tree_trunk: TreeTrunk::default(),
  262. total_width: table.columns_count() + table.widths().iter().fold(0, Add::add),
  263. table: table,
  264. inner: rows.into_iter(),
  265. colours: self.colours,
  266. }
  267. }
  268. }
  269. pub struct Iter<'a> {
  270. table: &'a Table<'a>,
  271. tree_trunk: TreeTrunk,
  272. total_width: usize,
  273. colours: &'a Colours,
  274. inner: VecIntoIter<Row>,
  275. }
  276. impl<'a> Iterator for Iter<'a> {
  277. type Item = TextCell;
  278. fn next(&mut self) -> Option<Self::Item> {
  279. self.inner.next().map(|row| {
  280. let mut cell =
  281. if let Some(cells) = row.cells {
  282. self.table.render(cells)
  283. }
  284. else {
  285. let mut cell = TextCell::default();
  286. cell.add_spaces(self.total_width);
  287. cell
  288. };
  289. let mut filename = TextCell::default();
  290. for tree_part in self.tree_trunk.new_row(row.depth, row.last) {
  291. filename.push(self.colours.punctuation.paint(tree_part.ascii_art()), 4);
  292. }
  293. // If any tree characters have been printed, then add an extra
  294. // space, which makes the output look much better.
  295. if row.depth != 0 {
  296. filename.add_spaces(1);
  297. }
  298. // Print the name without worrying about padding.
  299. filename.append(row.name);
  300. cell.append(filename);
  301. cell
  302. })
  303. }
  304. }
  305. pub struct Row {
  306. /// Vector of cells to display.
  307. ///
  308. /// Most of the rows will be used to display files' metadata, so this will
  309. /// almost always be `Some`, containing a vector of cells. It will only be
  310. /// `None` for a row displaying an attribute or error, neither of which
  311. /// have cells.
  312. pub cells: Option<TableRow>,
  313. /// This file's name, in coloured output. The name is treated separately
  314. /// from the other cells, as it never requires padding.
  315. pub name: TextCell,
  316. /// How many directories deep into the tree structure this is. Directories
  317. /// on top have depth 0.
  318. pub depth: usize,
  319. /// Whether this is the last entry in the directory. This flag is used
  320. /// when calculating the tree view.
  321. pub last: bool,
  322. }