details.rs 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969
  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::string::ToString;
  82. use std::sync::{Arc, Mutex};
  83. use ansi_term::Style;
  84. use datetime::fmt::DateFormat;
  85. use datetime::{LocalDateTime, DatePiece};
  86. use datetime::TimeZone;
  87. use zoneinfo_compiled::{CompiledData, Result as TZResult};
  88. use locale;
  89. use users::{Users, Groups, UsersCache};
  90. use fs::{Dir, File, fields as f};
  91. use fs::feature::xattr::{Attribute, FileAttributes};
  92. use options::{FileFilter, RecurseOptions};
  93. use output::colours::Colours;
  94. use output::column::{Alignment, Column, Columns, SizeFormat};
  95. use output::cell::{TextCell, DisplayWidth};
  96. use output::tree::TreeTrunk;
  97. use super::filename;
  98. /// With the **Details** view, the output gets formatted into columns, with
  99. /// each `Column` object showing some piece of information about the file,
  100. /// such as its size, or its permissions.
  101. ///
  102. /// To do this, the results have to be written to a table, instead of
  103. /// displaying each file immediately. Then, the width of each column can be
  104. /// calculated based on the individual results, and the fields are padded
  105. /// during output.
  106. ///
  107. /// Almost all the heavy lifting is done in a Table object, which handles the
  108. /// columns for each row.
  109. #[derive(PartialEq, Debug, Clone, Default)]
  110. pub struct Details {
  111. /// A Columns object that says which columns should be included in the
  112. /// output in the general case. Directories themselves can pick which
  113. /// columns are *added* to this list, such as the Git column.
  114. pub columns: Option<Columns>,
  115. /// Whether to recurse through directories with a tree view, and if so,
  116. /// which options to use. This field is only relevant here if the `tree`
  117. /// field of the RecurseOptions is `true`.
  118. pub recurse: Option<RecurseOptions>,
  119. /// How to sort and filter the files after getting their details.
  120. pub filter: FileFilter,
  121. /// Whether to show a header line or not.
  122. pub header: bool,
  123. /// Whether to show each file's extended attributes.
  124. pub xattr: bool,
  125. /// The colours to use to display information in the table, including the
  126. /// colour of the tree view symbols.
  127. pub colours: Colours,
  128. }
  129. /// The **environment** struct contains any data that could change between
  130. /// running instances of exa, depending on the user's computer's configuration.
  131. ///
  132. /// Any environment field should be able to be mocked up for test runs.
  133. pub struct Environment<U: Users+Groups> {
  134. /// The year of the current time. This gets used to determine which date
  135. /// format to use.
  136. current_year: i64,
  137. /// Localisation rules for formatting numbers.
  138. numeric: locale::Numeric,
  139. /// Localisation rules for formatting timestamps.
  140. time: locale::Time,
  141. /// The computer's current time zone. This gets used to determine how to
  142. /// offset files' timestamps.
  143. tz: Option<TimeZone>,
  144. /// Mapping cache of user IDs to usernames.
  145. users: Mutex<U>,
  146. }
  147. impl Default for Environment<UsersCache> {
  148. fn default() -> Self {
  149. let tz = determine_time_zone();
  150. if let Err(ref e) = tz {
  151. println!("Unable to determine time zone: {}", e);
  152. }
  153. Environment {
  154. current_year: LocalDateTime::now().year(),
  155. numeric: locale::Numeric::load_user_locale().unwrap_or_else(|_| locale::Numeric::english()),
  156. time: locale::Time::load_user_locale().unwrap_or_else(|_| locale::Time::english()),
  157. tz: tz.ok(),
  158. users: Mutex::new(UsersCache::new()),
  159. }
  160. }
  161. }
  162. fn determine_time_zone() -> TZResult<TimeZone> {
  163. TimeZone::from_file("/etc/localtime")
  164. }
  165. impl Details {
  166. /// Print the details of the given vector of files -- all of which will
  167. /// have been read from the given directory, if present -- to stdout.
  168. pub fn view<W: Write>(&self, dir: Option<&Dir>, files: Vec<File>, w: &mut W) -> IOResult<()> {
  169. // First, transform the Columns object into a vector of columns for
  170. // the current directory.
  171. let columns_for_dir = match self.columns {
  172. Some(cols) => cols.for_dir(dir),
  173. None => Vec::new(),
  174. };
  175. // Then, retrieve various environment variables.
  176. let env = Arc::new(Environment::<UsersCache>::default());
  177. // Build the table to put rows in.
  178. let mut table = Table {
  179. columns: &*columns_for_dir,
  180. opts: &self,
  181. env: env,
  182. rows: Vec::new(),
  183. };
  184. // Next, add a header if the user requests it.
  185. if self.header { table.add_header() }
  186. // Then add files to the table and print it out.
  187. self.add_files_to_table(&mut table, files, 0);
  188. for cell in table.print_table() {
  189. writeln!(w, "{}", cell.strings())?;
  190. }
  191. Ok(())
  192. }
  193. /// Adds files to the table, possibly recursively. This is easily
  194. /// parallelisable, and uses a pool of threads.
  195. fn add_files_to_table<'dir, U: Users+Groups+Send>(&self, mut table: &mut Table<U>, src: Vec<File<'dir>>, depth: usize) {
  196. use num_cpus;
  197. use scoped_threadpool::Pool;
  198. use std::sync::{Arc, Mutex};
  199. use fs::feature::xattr;
  200. let mut pool = Pool::new(num_cpus::get() as u32);
  201. let mut file_eggs = Vec::new();
  202. struct Egg<'a> {
  203. cells: Vec<TextCell>,
  204. xattrs: Vec<Attribute>,
  205. errors: Vec<(IOError, Option<PathBuf>)>,
  206. dir: Option<Dir>,
  207. file: File<'a>,
  208. }
  209. impl<'a> AsRef<File<'a>> for Egg<'a> {
  210. fn as_ref(&self) -> &File<'a> {
  211. &self.file
  212. }
  213. }
  214. pool.scoped(|scoped| {
  215. let file_eggs = Arc::new(Mutex::new(&mut file_eggs));
  216. let table = Arc::new(&mut table);
  217. for file in src {
  218. let file_eggs = file_eggs.clone();
  219. let table = table.clone();
  220. scoped.execute(move || {
  221. let mut errors = Vec::new();
  222. let mut xattrs = Vec::new();
  223. if xattr::ENABLED {
  224. match file.path.attributes() {
  225. Ok(xs) => xattrs.extend(xs),
  226. Err(e) => errors.push((e, None)),
  227. };
  228. }
  229. let cells = table.cells_for_file(&file, !xattrs.is_empty());
  230. if !table.opts.xattr {
  231. xattrs.clear();
  232. }
  233. let mut dir = None;
  234. if let Some(r) = self.recurse {
  235. if file.is_directory() && r.tree && !r.is_too_deep(depth) {
  236. if let Ok(d) = file.to_dir(false) {
  237. dir = Some(d);
  238. }
  239. }
  240. };
  241. let egg = Egg {
  242. cells: cells,
  243. xattrs: xattrs,
  244. errors: errors,
  245. dir: dir,
  246. file: file,
  247. };
  248. file_eggs.lock().unwrap().push(egg);
  249. });
  250. }
  251. });
  252. self.filter.sort_files(&mut file_eggs);
  253. let num_eggs = file_eggs.len();
  254. for (index, egg) in file_eggs.into_iter().enumerate() {
  255. let mut files = Vec::new();
  256. let mut errors = egg.errors;
  257. let mut width = DisplayWidth::from(&*egg.file.name);
  258. if egg.file.dir.is_none() {
  259. if let Some(ref parent) = egg.file.path.parent() {
  260. width = width + 1 + DisplayWidth::from(parent.to_string_lossy().as_ref());
  261. }
  262. }
  263. let name = TextCell {
  264. contents: filename(&egg.file, &self.colours, true),
  265. width: width,
  266. };
  267. let row = Row {
  268. depth: depth,
  269. cells: Some(egg.cells),
  270. name: name,
  271. last: index == num_eggs - 1,
  272. };
  273. table.rows.push(row);
  274. if let Some(ref dir) = egg.dir {
  275. for file_to_add in dir.files() {
  276. match file_to_add {
  277. Ok(f) => files.push(f),
  278. Err((path, e)) => errors.push((e, Some(path)))
  279. }
  280. }
  281. self.filter.filter_child_files(&mut files);
  282. if !files.is_empty() {
  283. for xattr in egg.xattrs {
  284. table.add_xattr(xattr, depth + 1, false);
  285. }
  286. for (error, path) in errors {
  287. table.add_error(&error, depth + 1, false, path);
  288. }
  289. self.add_files_to_table(table, files, depth + 1);
  290. continue;
  291. }
  292. }
  293. let count = egg.xattrs.len();
  294. for (index, xattr) in egg.xattrs.into_iter().enumerate() {
  295. table.add_xattr(xattr, depth + 1, errors.is_empty() && index == count - 1);
  296. }
  297. let count = errors.len();
  298. for (index, (error, path)) in errors.into_iter().enumerate() {
  299. table.add_error(&error, depth + 1, index == count - 1, path);
  300. }
  301. }
  302. }
  303. }
  304. pub struct Row {
  305. /// Vector of cells to display.
  306. ///
  307. /// Most of the rows will be used to display files' metadata, so this will
  308. /// almost always be `Some`, containing a vector of cells. It will only be
  309. /// `None` for a row displaying an attribute or error, neither of which
  310. /// have cells.
  311. cells: Option<Vec<TextCell>>,
  312. /// This file's name, in coloured output. The name is treated separately
  313. /// from the other cells, as it never requires padding.
  314. name: TextCell,
  315. /// How many directories deep into the tree structure this is. Directories
  316. /// on top have depth 0.
  317. depth: usize,
  318. /// Whether this is the last entry in the directory. This flag is used
  319. /// when calculating the tree view.
  320. last: bool,
  321. }
  322. impl Row {
  323. /// Gets the Unicode display width of the indexed column, if present. If
  324. /// not, returns 0.
  325. fn column_width(&self, index: usize) -> usize {
  326. match self.cells {
  327. Some(ref cells) => *cells[index].width,
  328. None => 0,
  329. }
  330. }
  331. }
  332. /// A **Table** object gets built up by the view as it lists files and
  333. /// directories.
  334. pub struct Table<'a, U: Users+Groups+'a> {
  335. pub rows: Vec<Row>,
  336. pub columns: &'a [Column],
  337. pub opts: &'a Details,
  338. pub env: Arc<Environment<U>>,
  339. }
  340. impl<'a, U: Users+Groups+'a> Table<'a, U> {
  341. /// Add a dummy "header" row to the table, which contains the names of all
  342. /// the columns, underlined. This has dummy data for the cases that aren't
  343. /// actually used, such as the depth or list of attributes.
  344. pub fn add_header(&mut self) {
  345. let row = Row {
  346. depth: 0,
  347. cells: Some(self.columns.iter().map(|c| TextCell::paint_str(self.opts.colours.header, c.header())).collect()),
  348. name: TextCell::paint_str(self.opts.colours.header, "Name"),
  349. last: false,
  350. };
  351. self.rows.push(row);
  352. }
  353. fn add_error(&mut self, error: &IOError, depth: usize, last: bool, path: Option<PathBuf>) {
  354. let error_message = match path {
  355. Some(path) => format!("<{}: {}>", path.display(), error),
  356. None => format!("<{}>", error),
  357. };
  358. let row = Row {
  359. depth: depth,
  360. cells: None,
  361. name: TextCell::paint(self.opts.colours.broken_arrow, error_message),
  362. last: last,
  363. };
  364. self.rows.push(row);
  365. }
  366. fn add_xattr(&mut self, xattr: Attribute, depth: usize, last: bool) {
  367. let row = Row {
  368. depth: depth,
  369. cells: None,
  370. name: TextCell::paint(self.opts.colours.perms.attribute, format!("{} (len {})", xattr.name, xattr.size)),
  371. last: last,
  372. };
  373. self.rows.push(row);
  374. }
  375. pub fn filename_cell(&self, file: File, links: bool) -> TextCell {
  376. let mut width = DisplayWidth::from(&*file.name);
  377. if file.dir.is_none() {
  378. if let Some(ref parent) = file.path.parent() {
  379. width = width + 1 + DisplayWidth::from(parent.to_string_lossy().as_ref());
  380. }
  381. }
  382. TextCell {
  383. contents: filename(&file, &self.opts.colours, links),
  384. width: width,
  385. }
  386. }
  387. pub fn add_file_with_cells(&mut self, cells: Vec<TextCell>, name_cell: TextCell, depth: usize, last: bool) {
  388. let row = Row {
  389. depth: depth,
  390. cells: Some(cells),
  391. name: name_cell,
  392. last: last,
  393. };
  394. self.rows.push(row);
  395. }
  396. /// Use the list of columns to find which cells should be produced for
  397. /// this file, per-column.
  398. pub fn cells_for_file(&self, file: &File, xattrs: bool) -> Vec<TextCell> {
  399. self.columns.clone().iter()
  400. .map(|c| self.display(file, c, xattrs))
  401. .collect()
  402. }
  403. fn display(&self, file: &File, column: &Column, xattrs: bool) -> TextCell {
  404. use output::column::TimeType::*;
  405. match *column {
  406. Column::Permissions => self.render_permissions(file.type_char(), file.permissions(), xattrs),
  407. Column::FileSize(fmt) => self.render_size(file.size(), fmt),
  408. Column::Timestamp(Modified) => self.render_time(file.modified_time()),
  409. Column::Timestamp(Created) => self.render_time(file.created_time()),
  410. Column::Timestamp(Accessed) => self.render_time(file.accessed_time()),
  411. Column::HardLinks => self.render_links(file.links()),
  412. Column::Inode => self.render_inode(file.inode()),
  413. Column::Blocks => self.render_blocks(file.blocks()),
  414. Column::User => self.render_user(file.user()),
  415. Column::Group => self.render_group(file.group()),
  416. Column::GitStatus => self.render_git_status(file.git_status()),
  417. }
  418. }
  419. fn render_permissions(&self, file_type: f::Type, permissions: f::Permissions, xattrs: bool) -> TextCell {
  420. let perms = self.opts.colours.perms;
  421. let types = self.opts.colours.filetypes;
  422. let bit = |bit, chr: &'static str, style: Style| {
  423. if bit { style.paint(chr) } else { self.opts.colours.punctuation.paint("-") }
  424. };
  425. let type_char = match file_type {
  426. f::Type::File => types.normal.paint("."),
  427. f::Type::Directory => types.directory.paint("d"),
  428. f::Type::Pipe => types.pipe.paint("|"),
  429. f::Type::Link => types.symlink.paint("l"),
  430. f::Type::CharDevice => types.device.paint("c"),
  431. f::Type::BlockDevice => types.device.paint("b"),
  432. f::Type::Socket => types.socket.paint("s"),
  433. f::Type::Special => types.special.paint("?"),
  434. };
  435. let x_colour = if file_type.is_regular_file() { perms.user_execute_file }
  436. else { perms.user_execute_other };
  437. let mut chars = vec![
  438. type_char,
  439. bit(permissions.user_read, "r", perms.user_read),
  440. bit(permissions.user_write, "w", perms.user_write),
  441. bit(permissions.user_execute, "x", x_colour),
  442. bit(permissions.group_read, "r", perms.group_read),
  443. bit(permissions.group_write, "w", perms.group_write),
  444. bit(permissions.group_execute, "x", perms.group_execute),
  445. bit(permissions.other_read, "r", perms.other_read),
  446. bit(permissions.other_write, "w", perms.other_write),
  447. bit(permissions.other_execute, "x", perms.other_execute),
  448. ];
  449. if xattrs {
  450. chars.push(perms.attribute.paint("@"));
  451. }
  452. // As these are all ASCII characters, we can guarantee that they’re
  453. // all going to be one character wide, and don’t need to compute the
  454. // cell’s display width.
  455. let width = DisplayWidth::from(chars.len());
  456. TextCell {
  457. contents: chars.into(),
  458. width: width,
  459. }
  460. }
  461. fn render_links(&self, links: f::Links) -> TextCell {
  462. let style = if links.multiple { self.opts.colours.links.multi_link_file }
  463. else { self.opts.colours.links.normal };
  464. TextCell::paint(style, self.env.numeric.format_int(links.count))
  465. }
  466. fn render_blocks(&self, blocks: f::Blocks) -> TextCell {
  467. match blocks {
  468. f::Blocks::Some(blk) => TextCell::paint(self.opts.colours.blocks, blk.to_string()),
  469. f::Blocks::None => TextCell::blank(self.opts.colours.punctuation),
  470. }
  471. }
  472. fn render_inode(&self, inode: f::Inode) -> TextCell {
  473. TextCell::paint(self.opts.colours.inode, inode.0.to_string())
  474. }
  475. fn render_size(&self, size: f::Size, size_format: SizeFormat) -> TextCell {
  476. use number_prefix::{binary_prefix, decimal_prefix};
  477. use number_prefix::{Prefixed, Standalone, PrefixNames};
  478. let size = match size {
  479. f::Size::Some(s) => s,
  480. f::Size::None => return TextCell::blank(self.opts.colours.punctuation),
  481. };
  482. let result = match size_format {
  483. SizeFormat::DecimalBytes => decimal_prefix(size as f64),
  484. SizeFormat::BinaryBytes => binary_prefix(size as f64),
  485. SizeFormat::JustBytes => {
  486. let string = self.env.numeric.format_int(size);
  487. return TextCell::paint(self.opts.colours.file_size(size), string);
  488. },
  489. };
  490. let (prefix, n) = match result {
  491. Standalone(b) => return TextCell::paint(self.opts.colours.file_size(b as u64), b.to_string()),
  492. Prefixed(p, n) => (p, n)
  493. };
  494. let symbol = prefix.symbol();
  495. let number = if n < 10f64 { self.env.numeric.format_float(n, 1) }
  496. else { self.env.numeric.format_int(n as isize) };
  497. // The numbers and symbols are guaranteed to be written in ASCII, so
  498. // we can skip the display width calculation.
  499. let width = DisplayWidth::from(number.len() + symbol.len());
  500. TextCell {
  501. width: width,
  502. contents: vec![
  503. self.opts.colours.file_size(size).paint(number),
  504. self.opts.colours.size.unit.paint(symbol),
  505. ].into(),
  506. }
  507. }
  508. #[allow(trivial_numeric_casts)]
  509. fn render_time(&self, timestamp: f::Time) -> TextCell {
  510. // TODO(ogham): This method needs some serious de-duping!
  511. // zoned and local times have different types at the moment,
  512. // so it's tricky.
  513. if let Some(ref tz) = self.env.tz {
  514. let date = tz.to_zoned(LocalDateTime::at(timestamp.0 as i64));
  515. let datestamp = if date.year() == self.env.current_year {
  516. DATE_AND_TIME.format(&date, &self.env.time)
  517. }
  518. else {
  519. DATE_AND_YEAR.format(&date, &self.env.time)
  520. };
  521. TextCell::paint(self.opts.colours.date, datestamp)
  522. }
  523. else {
  524. let date = LocalDateTime::at(timestamp.0 as i64);
  525. let datestamp = if date.year() == self.env.current_year {
  526. DATE_AND_TIME.format(&date, &self.env.time)
  527. }
  528. else {
  529. DATE_AND_YEAR.format(&date, &self.env.time)
  530. };
  531. TextCell::paint(self.opts.colours.date, datestamp)
  532. }
  533. }
  534. fn render_git_status(&self, git: f::Git) -> TextCell {
  535. let git_char = |status| match status {
  536. f::GitStatus::NotModified => self.opts.colours.punctuation.paint("-"),
  537. f::GitStatus::New => self.opts.colours.git.new.paint("N"),
  538. f::GitStatus::Modified => self.opts.colours.git.modified.paint("M"),
  539. f::GitStatus::Deleted => self.opts.colours.git.deleted.paint("D"),
  540. f::GitStatus::Renamed => self.opts.colours.git.renamed.paint("R"),
  541. f::GitStatus::TypeChange => self.opts.colours.git.typechange.paint("T"),
  542. };
  543. TextCell {
  544. width: DisplayWidth::from(2),
  545. contents: vec![
  546. git_char(git.staged),
  547. git_char(git.unstaged)
  548. ].into(),
  549. }
  550. }
  551. fn render_user(&self, user: f::User) -> TextCell {
  552. let users = self.env.users.lock().unwrap();
  553. let user_name = match users.get_user_by_uid(user.0) {
  554. Some(user) => user.name().to_owned(),
  555. None => user.0.to_string(),
  556. };
  557. let style = if users.get_current_uid() == user.0 { self.opts.colours.users.user_you }
  558. else { self.opts.colours.users.user_someone_else };
  559. TextCell::paint(style, user_name)
  560. }
  561. fn render_group(&self, group: f::Group) -> TextCell {
  562. use users::os::unix::GroupExt;
  563. let mut style = self.opts.colours.users.group_not_yours;
  564. let users = self.env.users.lock().unwrap();
  565. let group = match users.get_group_by_gid(group.0) {
  566. Some(g) => (*g).clone(),
  567. None => return TextCell::paint(style, group.0.to_string()),
  568. };
  569. let current_uid = users.get_current_uid();
  570. if let Some(current_user) = users.get_user_by_uid(current_uid) {
  571. if current_user.primary_group_id() == group.gid()
  572. || group.members().contains(&current_user.name().to_owned()) {
  573. style = self.opts.colours.users.group_yours;
  574. }
  575. }
  576. TextCell::paint(style, group.name().to_owned())
  577. }
  578. /// Render the table as a vector of Cells, to be displayed on standard output.
  579. pub fn print_table(self) -> Vec<TextCell> {
  580. let mut tree_trunk = TreeTrunk::default();
  581. let mut cells = Vec::new();
  582. // Work out the list of column widths by finding the longest cell for
  583. // each column, then formatting each cell in that column to be the
  584. // width of that one.
  585. let column_widths: Vec<usize> = (0 .. self.columns.len())
  586. .map(|n| self.rows.iter().map(|row| row.column_width(n)).max().unwrap_or(0))
  587. .collect();
  588. let total_width: usize = self.columns.len() + column_widths.iter().fold(0, Add::add);
  589. for row in self.rows {
  590. let mut cell = TextCell::default();
  591. if let Some(cells) = row.cells {
  592. for (n, (this_cell, width)) in cells.into_iter().zip(column_widths.iter()).enumerate() {
  593. let padding = width - *this_cell.width;
  594. match self.columns[n].alignment() {
  595. Alignment::Left => { cell.append(this_cell); cell.add_spaces(padding); }
  596. Alignment::Right => { cell.add_spaces(padding); cell.append(this_cell); }
  597. }
  598. cell.add_spaces(1);
  599. }
  600. }
  601. else {
  602. cell.add_spaces(total_width)
  603. }
  604. let mut filename = TextCell::default();
  605. for tree_part in tree_trunk.new_row(row.depth, row.last) {
  606. filename.push(self.opts.colours.punctuation.paint(tree_part.ascii_art()), 4);
  607. }
  608. // If any tree characters have been printed, then add an extra
  609. // space, which makes the output look much better.
  610. if row.depth != 0 {
  611. filename.add_spaces(1);
  612. }
  613. // Print the name without worrying about padding.
  614. filename.append(row.name);
  615. cell.append(filename);
  616. cells.push(cell);
  617. }
  618. cells
  619. }
  620. }
  621. lazy_static! {
  622. static ref DATE_AND_TIME: DateFormat<'static> =
  623. DateFormat::parse("{2>:D} {:M} {2>:h}:{02>:m}").unwrap();
  624. static ref DATE_AND_YEAR: DateFormat<'static> =
  625. DateFormat::parse("{2>:D} {:M} {5>:Y}").unwrap();
  626. }
  627. #[cfg(test)]
  628. pub mod test {
  629. pub use super::{Table, Environment, Details};
  630. pub use std::sync::Mutex;
  631. pub use fs::{File, fields as f};
  632. pub use output::column::{Column, Columns};
  633. pub use output::cell::TextCell;
  634. pub use users::{User, Group, uid_t, gid_t};
  635. pub use users::mock::MockUsers;
  636. pub use users::os::unix::{UserExt, GroupExt};
  637. pub use ansi_term::Style;
  638. pub use ansi_term::Colour::*;
  639. impl Default for Environment<MockUsers> {
  640. fn default() -> Self {
  641. use locale;
  642. use users::mock::MockUsers;
  643. use std::sync::Mutex;
  644. Environment {
  645. current_year: 1234,
  646. numeric: locale::Numeric::english(),
  647. time: locale::Time::english(),
  648. tz: None,
  649. users: Mutex::new(MockUsers::with_current_uid(0)),
  650. }
  651. }
  652. }
  653. pub fn new_table<'a>(columns: &'a [Column], details: &'a Details, users: MockUsers) -> Table<'a, MockUsers> {
  654. use std::sync::Arc;
  655. Table {
  656. columns: columns,
  657. opts: details,
  658. env: Arc::new(Environment { users: Mutex::new(users), ..Environment::default() }),
  659. rows: Vec::new(),
  660. }
  661. }
  662. mod users {
  663. #![allow(unused_results)]
  664. use super::*;
  665. #[test]
  666. fn named() {
  667. let columns = Columns::default().for_dir(None);
  668. let mut details = Details::default();
  669. details.colours.users.user_you = Red.bold();
  670. let mut users = MockUsers::with_current_uid(1000);
  671. users.add_user(User::new(1000, "enoch", 100));
  672. let table = new_table(&columns, &details, users);
  673. let user = f::User(1000);
  674. let expected = TextCell::paint_str(Red.bold(), "enoch");
  675. assert_eq!(expected, table.render_user(user))
  676. }
  677. #[test]
  678. fn unnamed() {
  679. let columns = Columns::default().for_dir(None);
  680. let mut details = Details::default();
  681. details.colours.users.user_you = Cyan.bold();
  682. let users = MockUsers::with_current_uid(1000);
  683. let table = new_table(&columns, &details, users);
  684. let user = f::User(1000);
  685. let expected = TextCell::paint_str(Cyan.bold(), "1000");
  686. assert_eq!(expected, table.render_user(user));
  687. }
  688. #[test]
  689. fn different_named() {
  690. let columns = Columns::default().for_dir(None);
  691. let mut details = Details::default();
  692. details.colours.users.user_someone_else = Green.bold();
  693. let table = new_table(&columns, &details, MockUsers::with_current_uid(0));
  694. table.env.users.lock().unwrap().add_user(User::new(1000, "enoch", 100));
  695. let user = f::User(1000);
  696. let expected = TextCell::paint_str(Green.bold(), "enoch");
  697. assert_eq!(expected, table.render_user(user));
  698. }
  699. #[test]
  700. fn different_unnamed() {
  701. let columns = Columns::default().for_dir(None);
  702. let mut details = Details::default();
  703. details.colours.users.user_someone_else = Red.normal();
  704. let table = new_table(&columns, &details, MockUsers::with_current_uid(0));
  705. let user = f::User(1000);
  706. let expected = TextCell::paint_str(Red.normal(), "1000");
  707. assert_eq!(expected, table.render_user(user));
  708. }
  709. #[test]
  710. fn overflow() {
  711. let columns = Columns::default().for_dir(None);
  712. let mut details = Details::default();
  713. details.colours.users.user_someone_else = Blue.underline();
  714. let table = new_table(&columns, &details, MockUsers::with_current_uid(0));
  715. let user = f::User(2_147_483_648);
  716. let expected = TextCell::paint_str(Blue.underline(), "2147483648");
  717. assert_eq!(expected, table.render_user(user));
  718. }
  719. }
  720. mod groups {
  721. #![allow(unused_results)]
  722. use super::*;
  723. #[test]
  724. fn named() {
  725. let columns = Columns::default().for_dir(None);
  726. let mut details = Details::default();
  727. details.colours.users.group_not_yours = Fixed(101).normal();
  728. let mut users = MockUsers::with_current_uid(1000);
  729. users.add_group(Group::new(100, "folk"));
  730. let table = new_table(&columns, &details, users);
  731. let group = f::Group(100);
  732. let expected = TextCell::paint_str(Fixed(101).normal(), "folk");
  733. assert_eq!(expected, table.render_group(group))
  734. }
  735. #[test]
  736. fn unnamed() {
  737. let columns = Columns::default().for_dir(None);
  738. let mut details = Details::default();
  739. details.colours.users.group_not_yours = Fixed(87).normal();
  740. let users = MockUsers::with_current_uid(1000);
  741. let table = new_table(&columns, &details, users);
  742. let group = f::Group(100);
  743. let expected = TextCell::paint_str(Fixed(87).normal(), "100");
  744. assert_eq!(expected, table.render_group(group));
  745. }
  746. #[test]
  747. fn primary() {
  748. let columns = Columns::default().for_dir(None);
  749. let mut details = Details::default();
  750. details.colours.users.group_yours = Fixed(64).normal();
  751. let mut users = MockUsers::with_current_uid(2);
  752. users.add_user(User::new(2, "eve", 100));
  753. users.add_group(Group::new(100, "folk"));
  754. let table = new_table(&columns, &details, users);
  755. let group = f::Group(100);
  756. let expected = TextCell::paint_str(Fixed(64).normal(), "folk");
  757. assert_eq!(expected, table.render_group(group))
  758. }
  759. #[test]
  760. fn secondary() {
  761. let columns = Columns::default().for_dir(None);
  762. let mut details = Details::default();
  763. details.colours.users.group_yours = Fixed(31).normal();
  764. let mut users = MockUsers::with_current_uid(2);
  765. users.add_user(User::new(2, "eve", 666));
  766. let test_group = Group::new(100, "folk").add_member("eve");
  767. users.add_group(test_group);
  768. let table = new_table(&columns, &details, users);
  769. let group = f::Group(100);
  770. let expected = TextCell::paint_str(Fixed(31).normal(), "folk");
  771. assert_eq!(expected, table.render_group(group))
  772. }
  773. #[test]
  774. fn overflow() {
  775. let columns = Columns::default().for_dir(None);
  776. let mut details = Details::default();
  777. details.colours.users.group_not_yours = Blue.underline();
  778. let table = new_table(&columns, &details, MockUsers::with_current_uid(0));
  779. let group = f::Group(2_147_483_648);
  780. let expected = TextCell::paint_str(Blue.underline(), "2147483648");
  781. assert_eq!(expected, table.render_group(group));
  782. }
  783. }
  784. }