details.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  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::path::PathBuf;
  63. use std::vec::IntoIter as VecIntoIter;
  64. use fs::{Dir, File};
  65. use fs::dir_action::RecurseOptions;
  66. use fs::filter::FileFilter;
  67. use fs::feature::xattr::{Attribute, FileAttributes};
  68. use output::colours::Colours;
  69. use output::cell::TextCell;
  70. use output::tree::{TreeTrunk, TreeParams, TreeDepth};
  71. use output::file_name::FileStyle;
  72. use output::table::{Table, Options as TableOptions, Row as TableRow};
  73. /// With the **Details** view, the output gets formatted into columns, with
  74. /// each `Column` object showing some piece of information about the file,
  75. /// such as its size, or its permissions.
  76. ///
  77. /// To do this, the results have to be written to a table, instead of
  78. /// displaying each file immediately. Then, the width of each column can be
  79. /// calculated based on the individual results, and the fields are padded
  80. /// during output.
  81. ///
  82. /// Almost all the heavy lifting is done in a Table object, which handles the
  83. /// columns for each row.
  84. #[derive(Debug)]
  85. pub struct Options {
  86. /// Options specific to drawing a table.
  87. ///
  88. /// Directories themselves can pick which columns are *added* to this
  89. /// list, such as the Git column.
  90. pub table: Option<TableOptions>,
  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 style: &'a FileStyle,
  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: Option<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 mut rows = Vec::new();
  124. if let Some(ref table) = self.opts.table {
  125. let mut table = Table::new(&table, self.dir, &self.colours);
  126. if self.opts.header {
  127. let header = table.header_row();
  128. table.add_widths(&header);
  129. rows.push(self.render_header(header));
  130. }
  131. // This is weird, but I can't find a way around it:
  132. // https://internals.rust-lang.org/t/should-option-mut-t-implement-copy/3715/6
  133. let mut table = Some(table);
  134. self.add_files_to_table(&mut table, &mut rows, &self.files, TreeDepth::root());
  135. for row in self.iterate_with_table(table.unwrap(), rows) {
  136. writeln!(w, "{}", row.strings())?
  137. }
  138. }
  139. else {
  140. self.add_files_to_table(&mut None, &mut rows, &self.files, TreeDepth::root());
  141. for row in self.iterate(rows) {
  142. writeln!(w, "{}", row.strings())?
  143. }
  144. }
  145. Ok(())
  146. }
  147. /// Adds files to the table, possibly recursively. This is easily
  148. /// parallelisable, and uses a pool of threads.
  149. fn add_files_to_table<'dir>(&self, table: &mut Option<Table<'a>>, rows: &mut Vec<Row>, src: &Vec<File<'dir>>, depth: TreeDepth) {
  150. use num_cpus;
  151. use scoped_threadpool::Pool;
  152. use std::sync::{Arc, Mutex};
  153. use fs::feature::xattr;
  154. let mut pool = Pool::new(num_cpus::get() as u32);
  155. let mut file_eggs = Vec::new();
  156. pool.scoped(|scoped| {
  157. let file_eggs = Arc::new(Mutex::new(&mut file_eggs));
  158. let table = table.as_ref();
  159. for file in src {
  160. let file_eggs = file_eggs.clone();
  161. scoped.execute(move || {
  162. let mut errors = Vec::new();
  163. let mut xattrs = Vec::new();
  164. if xattr::ENABLED {
  165. match file.path.attributes() {
  166. Ok(xs) => xattrs.extend(xs),
  167. Err(e) => errors.push((e, None)),
  168. };
  169. }
  170. let table_row = table.as_ref().map(|t| t.row_for_file(&file, !xattrs.is_empty()));
  171. if !self.opts.xattr {
  172. xattrs.clear();
  173. }
  174. let mut dir = None;
  175. if let Some(r) = self.recurse {
  176. if file.is_directory() && r.tree && !r.is_too_deep(depth.0) {
  177. match file.to_dir(false) {
  178. Ok(d) => { dir = Some(d); },
  179. Err(e) => { errors.push((e, None)) },
  180. }
  181. }
  182. };
  183. let egg = Egg { table_row, xattrs, errors, dir, file };
  184. file_eggs.lock().unwrap().push(egg);
  185. });
  186. }
  187. });
  188. self.filter.sort_files(&mut file_eggs);
  189. for (tree_params, egg) in depth.iterate_over(file_eggs.into_iter()) {
  190. let mut files = Vec::new();
  191. let mut errors = egg.errors;
  192. if let (Some(ref mut t), Some(ref row)) = (table.as_mut(), egg.table_row.as_ref()) {
  193. t.add_widths(row);
  194. }
  195. let row = Row {
  196. tree: tree_params,
  197. cells: egg.table_row,
  198. name: self.style.for_file(&egg.file, self.colours)
  199. .with_link_paths()
  200. .paint().promote(),
  201. };
  202. rows.push(row);
  203. if let Some(ref dir) = egg.dir {
  204. for file_to_add in dir.files(self.filter.dot_filter) {
  205. match file_to_add {
  206. Ok(f) => files.push(f),
  207. Err((path, e)) => errors.push((e, Some(path)))
  208. }
  209. }
  210. self.filter.filter_child_files(&mut files);
  211. if !files.is_empty() {
  212. for xattr in egg.xattrs {
  213. rows.push(self.render_xattr(xattr, TreeParams::new(depth.deeper(), false)));
  214. }
  215. for (error, path) in errors {
  216. rows.push(self.render_error(&error, TreeParams::new(depth.deeper(), false), path));
  217. }
  218. self.add_files_to_table(table, rows, &files, depth.deeper());
  219. continue;
  220. }
  221. }
  222. let count = egg.xattrs.len();
  223. for (index, xattr) in egg.xattrs.into_iter().enumerate() {
  224. rows.push(self.render_xattr(xattr, TreeParams::new(depth.deeper(), errors.is_empty() && index == count - 1)));
  225. }
  226. let count = errors.len();
  227. for (index, (error, path)) in errors.into_iter().enumerate() {
  228. rows.push(self.render_error(&error, TreeParams::new(depth.deeper(), index == count - 1), path));
  229. }
  230. }
  231. }
  232. pub fn render_header(&self, header: TableRow) -> Row {
  233. Row {
  234. tree: TreeParams::new(TreeDepth::root(), false),
  235. cells: Some(header),
  236. name: TextCell::paint_str(self.colours.header, "Name"),
  237. }
  238. }
  239. fn render_error(&self, error: &IOError, tree: TreeParams, path: Option<PathBuf>) -> Row {
  240. let error_message = match path {
  241. Some(path) => format!("<{}: {}>", path.display(), error),
  242. None => format!("<{}>", error),
  243. };
  244. let name = TextCell::paint(self.colours.broken_arrow, error_message);
  245. Row { cells: None, name, tree }
  246. }
  247. fn render_xattr(&self, xattr: Attribute, tree: TreeParams) -> Row {
  248. let name = TextCell::paint(self.colours.perms.attribute, format!("{} (len {})", xattr.name, xattr.size));
  249. Row { cells: None, name, tree }
  250. }
  251. pub fn render_file(&self, cells: TableRow, name: TextCell, tree: TreeParams) -> Row {
  252. Row { cells: Some(cells), name, tree }
  253. }
  254. pub fn iterate_with_table(&'a self, table: Table<'a>, rows: Vec<Row>) -> TableIter<'a> {
  255. TableIter {
  256. tree_trunk: TreeTrunk::default(),
  257. total_width: table.widths().total(),
  258. table: table,
  259. inner: rows.into_iter(),
  260. colours: self.colours,
  261. }
  262. }
  263. pub fn iterate(&'a self, rows: Vec<Row>) -> Iter<'a> {
  264. Iter {
  265. tree_trunk: TreeTrunk::default(),
  266. inner: rows.into_iter(),
  267. colours: self.colours,
  268. }
  269. }
  270. }
  271. pub struct Row {
  272. /// Vector of cells to display.
  273. ///
  274. /// Most of the rows will be used to display files' metadata, so this will
  275. /// almost always be `Some`, containing a vector of cells. It will only be
  276. /// `None` for a row displaying an attribute or error, neither of which
  277. /// have cells.
  278. pub cells: Option<TableRow>,
  279. /// This file's name, in coloured output. The name is treated separately
  280. /// from the other cells, as it never requires padding.
  281. pub name: TextCell,
  282. /// Information used to determine which symbols to display in a tree.
  283. pub tree: TreeParams,
  284. }
  285. pub struct TableIter<'a> {
  286. table: Table<'a>,
  287. tree_trunk: TreeTrunk,
  288. total_width: usize,
  289. colours: &'a Colours,
  290. inner: VecIntoIter<Row>,
  291. }
  292. impl<'a> Iterator for TableIter<'a> {
  293. type Item = TextCell;
  294. fn next(&mut self) -> Option<Self::Item> {
  295. self.inner.next().map(|row| {
  296. let mut cell =
  297. if let Some(cells) = row.cells {
  298. self.table.render(cells)
  299. }
  300. else {
  301. let mut cell = TextCell::default();
  302. cell.add_spaces(self.total_width);
  303. cell
  304. };
  305. for tree_part in self.tree_trunk.new_row(row.tree) {
  306. cell.push(self.colours.punctuation.paint(tree_part.ascii_art()), 4);
  307. }
  308. // If any tree characters have been printed, then add an extra
  309. // space, which makes the output look much better.
  310. if !row.tree.is_at_root() {
  311. cell.add_spaces(1);
  312. }
  313. cell.append(row.name);
  314. cell
  315. })
  316. }
  317. }
  318. pub struct Iter<'a> {
  319. tree_trunk: TreeTrunk,
  320. colours: &'a Colours,
  321. inner: VecIntoIter<Row>,
  322. }
  323. impl<'a> Iterator for Iter<'a> {
  324. type Item = TextCell;
  325. fn next(&mut self) -> Option<Self::Item> {
  326. self.inner.next().map(|row| {
  327. let mut cell = TextCell::default();
  328. for tree_part in self.tree_trunk.new_row(row.tree) {
  329. cell.push(self.colours.punctuation.paint(tree_part.ascii_art()), 4);
  330. }
  331. // If any tree characters have been printed, then add an extra
  332. // space, which makes the output look much better.
  333. if !row.tree.is_at_root() {
  334. cell.add_spaces(1);
  335. }
  336. cell.append(row.name);
  337. cell
  338. })
  339. }
  340. }