details.rs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  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. //!
  62. //!
  63. //! ## Extended Attributes and Errors
  64. //!
  65. //! Finally, files' extended attributes and any errors that occur while statting
  66. //! them can also be displayed as their children. It looks like this:
  67. //!
  68. //! ```text
  69. //! .rw-r--r-- 0 ben 3 Sep 13:26 forbidden
  70. //! └── <Permission denied (os error 13)>
  71. //! .rw-r--r--@ 0 ben 3 Sep 13:26 file_with_xattrs
  72. //! ├── another_greeting (len 2)
  73. //! └── greeting (len 5)
  74. //! ```
  75. //!
  76. //! These lines also have `None` cells, and the error string or attribute details
  77. //! are used in place of the filename.
  78. use std::io::{Write, Error as IOError, Result as IOResult};
  79. use std::ops::Add;
  80. use std::path::PathBuf;
  81. use std::sync::{Arc, Mutex, MutexGuard};
  82. use datetime::fmt::DateFormat;
  83. use datetime::{LocalDateTime, DatePiece};
  84. use datetime::TimeZone;
  85. use zoneinfo_compiled::{CompiledData, Result as TZResult};
  86. use unicode_width::UnicodeWidthStr;
  87. use locale;
  88. use users::{Users, Groups, UsersCache};
  89. use fs::{Dir, File, fields as f};
  90. use fs::feature::xattr::{Attribute, FileAttributes};
  91. use options::{FileFilter, RecurseOptions};
  92. use output::colours::Colours;
  93. use output::column::{Alignment, Column, Columns};
  94. use output::cell::{TextCell, TextCellContents};
  95. use output::tree::TreeTrunk;
  96. use output::file_name::{FileName, LinkStyle, Classify};
  97. /// With the **Details** view, the output gets formatted into columns, with
  98. /// each `Column` object showing some piece of information about the file,
  99. /// such as its size, or its permissions.
  100. ///
  101. /// To do this, the results have to be written to a table, instead of
  102. /// displaying each file immediately. Then, the width of each column can be
  103. /// calculated based on the individual results, and the fields are padded
  104. /// during output.
  105. ///
  106. /// Almost all the heavy lifting is done in a Table object, which handles the
  107. /// columns for each row.
  108. #[derive(PartialEq, Debug, Clone, Default)]
  109. pub struct Details {
  110. /// A Columns object that says which columns should be included in the
  111. /// output in the general case. Directories themselves can pick which
  112. /// columns are *added* to this list, such as the Git column.
  113. pub columns: Option<Columns>,
  114. /// Whether to recurse through directories with a tree view, and if so,
  115. /// which options to use. This field is only relevant here if the `tree`
  116. /// field of the RecurseOptions is `true`.
  117. pub recurse: Option<RecurseOptions>,
  118. /// How to sort and filter the files after getting their details.
  119. pub filter: FileFilter,
  120. /// Whether to show a header line or not.
  121. pub header: bool,
  122. /// Whether to show each file's extended attributes.
  123. pub xattr: bool,
  124. /// The colours to use to display information in the table, including the
  125. /// colour of the tree view symbols.
  126. pub colours: Colours,
  127. /// Whether to show a file type indiccator.
  128. pub classify: Classify,
  129. }
  130. /// The **environment** struct contains any data that could change between
  131. /// running instances of exa, depending on the user's computer's configuration.
  132. ///
  133. /// Any environment field should be able to be mocked up for test runs.
  134. pub struct Environment<U> { // where U: Users+Groups
  135. /// The year of the current time. This gets used to determine which date
  136. /// format to use.
  137. current_year: i64,
  138. /// Localisation rules for formatting numbers.
  139. numeric: locale::Numeric,
  140. /// Localisation rules for formatting timestamps.
  141. time: locale::Time,
  142. /// Date format for printing out timestamps that are in the current year.
  143. date_and_time: DateFormat<'static>,
  144. /// Date format for printing out timestamps that *aren’t*.
  145. date_and_year: DateFormat<'static>,
  146. /// The computer's current time zone. This gets used to determine how to
  147. /// offset files' timestamps.
  148. tz: Option<TimeZone>,
  149. /// Mapping cache of user IDs to usernames.
  150. users: Mutex<U>,
  151. }
  152. impl<U> Environment<U> {
  153. pub fn lock_users(&self) -> MutexGuard<U> {
  154. self.users.lock().unwrap()
  155. }
  156. }
  157. impl Default for Environment<UsersCache> {
  158. fn default() -> Self {
  159. let tz = determine_time_zone();
  160. if let Err(ref e) = tz {
  161. println!("Unable to determine time zone: {}", e);
  162. }
  163. let numeric = locale::Numeric::load_user_locale()
  164. .unwrap_or_else(|_| locale::Numeric::english());
  165. let time = locale::Time::load_user_locale()
  166. .unwrap_or_else(|_| locale::Time::english());
  167. // Some locales use a three-character wide month name (Jan to Dec);
  168. // others vary between three and four (1月 to 12月). We assume that
  169. // December is the month with the maximum width, and use the width of
  170. let december_width = UnicodeWidthStr::width(&*time.short_month_name(11));
  171. let date_and_time = match december_width {
  172. 4 => DateFormat::parse("{2>:D} {4>:M} {2>:h}:{02>:m}").unwrap(),
  173. _ => DateFormat::parse("{2>:D} {:M} {2>:h}:{02>:m}").unwrap(),
  174. };
  175. let date_and_year = match december_width {
  176. 4 => DateFormat::parse("{2>:D} {4>:M} {5>:Y}").unwrap(),
  177. _ => DateFormat::parse("{2>:D} {:M} {5>:Y}").unwrap()
  178. };
  179. Environment {
  180. current_year: LocalDateTime::now().year(),
  181. numeric: numeric,
  182. date_and_time: date_and_time,
  183. date_and_year: date_and_year,
  184. time: time,
  185. tz: tz.ok(),
  186. users: Mutex::new(UsersCache::new()),
  187. }
  188. }
  189. }
  190. fn determine_time_zone() -> TZResult<TimeZone> {
  191. TimeZone::from_file("/etc/localtime")
  192. }
  193. impl Details {
  194. /// Print the details of the given vector of files -- all of which will
  195. /// have been read from the given directory, if present -- to stdout.
  196. pub fn view<W: Write>(&self, dir: Option<&Dir>, files: Vec<File>, w: &mut W) -> IOResult<()> {
  197. // First, transform the Columns object into a vector of columns for
  198. // the current directory.
  199. let columns_for_dir = match self.columns {
  200. Some(cols) => cols.for_dir(dir),
  201. None => Vec::new(),
  202. };
  203. // Then, retrieve various environment variables.
  204. let env = Arc::new(Environment::<UsersCache>::default());
  205. // Build the table to put rows in.
  206. let mut table = Table {
  207. columns: &*columns_for_dir,
  208. opts: self,
  209. env: env,
  210. rows: Vec::new(),
  211. };
  212. // Next, add a header if the user requests it.
  213. if self.header { table.add_header() }
  214. // Then add files to the table and print it out.
  215. self.add_files_to_table(&mut table, files, 0);
  216. for cell in table.print_table() {
  217. writeln!(w, "{}", cell.strings())?;
  218. }
  219. Ok(())
  220. }
  221. /// Adds files to the table, possibly recursively. This is easily
  222. /// parallelisable, and uses a pool of threads.
  223. fn add_files_to_table<'dir, U: Users+Groups+Send>(&self, mut table: &mut Table<U>, src: Vec<File<'dir>>, depth: usize) {
  224. use num_cpus;
  225. use scoped_threadpool::Pool;
  226. use std::sync::{Arc, Mutex};
  227. use fs::feature::xattr;
  228. let mut pool = Pool::new(num_cpus::get() as u32);
  229. let mut file_eggs = Vec::new();
  230. struct Egg<'a> {
  231. cells: Vec<TextCell>,
  232. xattrs: Vec<Attribute>,
  233. errors: Vec<(IOError, Option<PathBuf>)>,
  234. dir: Option<Dir>,
  235. file: File<'a>,
  236. }
  237. impl<'a> AsRef<File<'a>> for Egg<'a> {
  238. fn as_ref(&self) -> &File<'a> {
  239. &self.file
  240. }
  241. }
  242. pool.scoped(|scoped| {
  243. let file_eggs = Arc::new(Mutex::new(&mut file_eggs));
  244. let table = Arc::new(&mut table);
  245. for file in src {
  246. let file_eggs = file_eggs.clone();
  247. let table = table.clone();
  248. scoped.execute(move || {
  249. let mut errors = Vec::new();
  250. let mut xattrs = Vec::new();
  251. if xattr::ENABLED {
  252. match file.path.attributes() {
  253. Ok(xs) => xattrs.extend(xs),
  254. Err(e) => errors.push((e, None)),
  255. };
  256. }
  257. let cells = table.cells_for_file(&file, !xattrs.is_empty());
  258. if !table.opts.xattr {
  259. xattrs.clear();
  260. }
  261. let mut dir = None;
  262. if let Some(r) = self.recurse {
  263. if file.is_directory() && r.tree && !r.is_too_deep(depth) {
  264. if let Ok(d) = file.to_dir(false) {
  265. dir = Some(d);
  266. }
  267. }
  268. };
  269. let egg = Egg { cells, xattrs, errors, dir, file };
  270. file_eggs.lock().unwrap().push(egg);
  271. });
  272. }
  273. });
  274. self.filter.sort_files(&mut file_eggs);
  275. let num_eggs = file_eggs.len();
  276. for (index, egg) in file_eggs.into_iter().enumerate() {
  277. let mut files = Vec::new();
  278. let mut errors = egg.errors;
  279. let row = Row {
  280. depth: depth,
  281. cells: Some(egg.cells),
  282. name: FileName::new(&egg.file, LinkStyle::FullLinkPaths, self.classify, &self.colours).paint().promote(),
  283. last: index == num_eggs - 1,
  284. };
  285. table.rows.push(row);
  286. if let Some(ref dir) = egg.dir {
  287. for file_to_add in dir.files() {
  288. match file_to_add {
  289. Ok(f) => files.push(f),
  290. Err((path, e)) => errors.push((e, Some(path)))
  291. }
  292. }
  293. self.filter.filter_child_files(&mut files);
  294. if !files.is_empty() {
  295. for xattr in egg.xattrs {
  296. table.add_xattr(xattr, depth + 1, false);
  297. }
  298. for (error, path) in errors {
  299. table.add_error(&error, depth + 1, false, path);
  300. }
  301. self.add_files_to_table(table, files, depth + 1);
  302. continue;
  303. }
  304. }
  305. let count = egg.xattrs.len();
  306. for (index, xattr) in egg.xattrs.into_iter().enumerate() {
  307. table.add_xattr(xattr, depth + 1, errors.is_empty() && index == count - 1);
  308. }
  309. let count = errors.len();
  310. for (index, (error, path)) in errors.into_iter().enumerate() {
  311. table.add_error(&error, depth + 1, index == count - 1, path);
  312. }
  313. }
  314. }
  315. }
  316. pub struct Row {
  317. /// Vector of cells to display.
  318. ///
  319. /// Most of the rows will be used to display files' metadata, so this will
  320. /// almost always be `Some`, containing a vector of cells. It will only be
  321. /// `None` for a row displaying an attribute or error, neither of which
  322. /// have cells.
  323. cells: Option<Vec<TextCell>>,
  324. /// This file's name, in coloured output. The name is treated separately
  325. /// from the other cells, as it never requires padding.
  326. name: TextCell,
  327. /// How many directories deep into the tree structure this is. Directories
  328. /// on top have depth 0.
  329. depth: usize,
  330. /// Whether this is the last entry in the directory. This flag is used
  331. /// when calculating the tree view.
  332. last: bool,
  333. }
  334. impl Row {
  335. /// Gets the Unicode display width of the indexed column, if present. If
  336. /// not, returns 0.
  337. fn column_width(&self, index: usize) -> usize {
  338. match self.cells {
  339. Some(ref cells) => *cells[index].width,
  340. None => 0,
  341. }
  342. }
  343. }
  344. /// A **Table** object gets built up by the view as it lists files and
  345. /// directories.
  346. pub struct Table<'a, U: 'a> { // where U: Users+Groups
  347. pub rows: Vec<Row>,
  348. pub columns: &'a [Column],
  349. pub opts: &'a Details,
  350. pub env: Arc<Environment<U>>,
  351. }
  352. impl<'a, U: Users+Groups+'a> Table<'a, U> {
  353. /// Add a dummy "header" row to the table, which contains the names of all
  354. /// the columns, underlined. This has dummy data for the cases that aren't
  355. /// actually used, such as the depth or list of attributes.
  356. pub fn add_header(&mut self) {
  357. let row = Row {
  358. depth: 0,
  359. cells: Some(self.columns.iter().map(|c| TextCell::paint_str(self.opts.colours.header, c.header())).collect()),
  360. name: TextCell::paint_str(self.opts.colours.header, "Name"),
  361. last: false,
  362. };
  363. self.rows.push(row);
  364. }
  365. fn add_error(&mut self, error: &IOError, depth: usize, last: bool, path: Option<PathBuf>) {
  366. let error_message = match path {
  367. Some(path) => format!("<{}: {}>", path.display(), error),
  368. None => format!("<{}>", error),
  369. };
  370. let row = Row {
  371. depth: depth,
  372. cells: None,
  373. name: TextCell::paint(self.opts.colours.broken_arrow, error_message),
  374. last: last,
  375. };
  376. self.rows.push(row);
  377. }
  378. fn add_xattr(&mut self, xattr: Attribute, depth: usize, last: bool) {
  379. let row = Row {
  380. depth: depth,
  381. cells: None,
  382. name: TextCell::paint(self.opts.colours.perms.attribute, format!("{} (len {})", xattr.name, xattr.size)),
  383. last: last,
  384. };
  385. self.rows.push(row);
  386. }
  387. pub fn filename(&self, file: File, links: LinkStyle) -> TextCellContents {
  388. FileName::new(&file, links, self.opts.classify, &self.opts.colours).paint()
  389. }
  390. pub fn add_file_with_cells(&mut self, cells: Vec<TextCell>, name_cell: TextCell, depth: usize, last: bool) {
  391. let row = Row {
  392. depth: depth,
  393. cells: Some(cells),
  394. name: name_cell,
  395. last: last,
  396. };
  397. self.rows.push(row);
  398. }
  399. /// Use the list of columns to find which cells should be produced for
  400. /// this file, per-column.
  401. pub fn cells_for_file(&self, file: &File, xattrs: bool) -> Vec<TextCell> {
  402. self.columns.iter()
  403. .map(|c| self.display(file, c, xattrs))
  404. .collect()
  405. }
  406. fn permissions_plus(&self, file: &File, xattrs: bool) -> f::PermissionsPlus {
  407. f::PermissionsPlus {
  408. file_type: file.type_char(),
  409. permissions: file.permissions(),
  410. xattrs: xattrs,
  411. }
  412. }
  413. fn display(&self, file: &File, column: &Column, xattrs: bool) -> TextCell {
  414. use output::column::TimeType::*;
  415. match *column {
  416. Column::Permissions => self.permissions_plus(file, xattrs).render(&self.opts.colours),
  417. Column::FileSize(fmt) => file.size().render(&self.opts.colours, fmt, &self.env.numeric),
  418. Column::Timestamp(Modified) => file.modified_time().render(&self.opts.colours, &self.env.tz, &self.env.date_and_time, &self.env.date_and_year, &self.env.time, self.env.current_year),
  419. Column::Timestamp(Created) => file.created_time().render( &self.opts.colours, &self.env.tz, &self.env.date_and_time, &self.env.date_and_year, &self.env.time, self.env.current_year),
  420. Column::Timestamp(Accessed) => file.accessed_time().render(&self.opts.colours, &self.env.tz, &self.env.date_and_time, &self.env.date_and_year, &self.env.time, self.env.current_year),
  421. Column::HardLinks => file.links().render(&self.opts.colours, &self.env.numeric),
  422. Column::Inode => file.inode().render(&self.opts.colours),
  423. Column::Blocks => file.blocks().render(&self.opts.colours),
  424. Column::User => file.user().render(&self.opts.colours, &*self.env.lock_users()),
  425. Column::Group => file.group().render(&self.opts.colours, &*self.env.lock_users()),
  426. Column::GitStatus => file.git_status().render(&self.opts.colours),
  427. }
  428. }
  429. /// Render the table as a vector of Cells, to be displayed on standard output.
  430. pub fn print_table(self) -> Vec<TextCell> {
  431. let mut tree_trunk = TreeTrunk::default();
  432. let mut cells = Vec::new();
  433. // Work out the list of column widths by finding the longest cell for
  434. // each column, then formatting each cell in that column to be the
  435. // width of that one.
  436. let column_widths: Vec<usize> = (0 .. self.columns.len())
  437. .map(|n| self.rows.iter().map(|row| row.column_width(n)).max().unwrap_or(0))
  438. .collect();
  439. let total_width: usize = self.columns.len() + column_widths.iter().fold(0, Add::add);
  440. for row in self.rows {
  441. let mut cell = TextCell::default();
  442. if let Some(cells) = row.cells {
  443. for (n, (this_cell, width)) in cells.into_iter().zip(column_widths.iter()).enumerate() {
  444. let padding = width - *this_cell.width;
  445. match self.columns[n].alignment() {
  446. Alignment::Left => { cell.append(this_cell); cell.add_spaces(padding); }
  447. Alignment::Right => { cell.add_spaces(padding); cell.append(this_cell); }
  448. }
  449. cell.add_spaces(1);
  450. }
  451. }
  452. else {
  453. cell.add_spaces(total_width)
  454. }
  455. let mut filename = TextCell::default();
  456. for tree_part in tree_trunk.new_row(row.depth, row.last) {
  457. filename.push(self.opts.colours.punctuation.paint(tree_part.ascii_art()), 4);
  458. }
  459. // If any tree characters have been printed, then add an extra
  460. // space, which makes the output look much better.
  461. if row.depth != 0 {
  462. filename.add_spaces(1);
  463. }
  464. // Print the name without worrying about padding.
  465. filename.append(row.name);
  466. cell.append(filename);
  467. cells.push(cell);
  468. }
  469. cells
  470. }
  471. }