details.rs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907
  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. //! .rw-r--r-- 9.6k ben 29 Jun 16:16 Cargo.lock
  16. //! .rw-r--r-- 547 ben 23 Jun 10:54 Cargo.toml
  17. //! .rw-r--r-- 1.1k ben 23 Nov 2014 LICENCE
  18. //! .rw-r--r-- 2.5k ben 21 May 14:38 README.md
  19. //! .rw-r--r-- 382k ben 8 Jun 21:00 screenshot.png
  20. //! drwxr-xr-x - ben 29 Jun 14:50 src
  21. //! drwxr-xr-x - ben 28 Jun 19:53 target
  22. //!
  23. //! The table is constructed by creating a `Table` value, which produces a `Row`
  24. //! value for each file. These rows can contain a vector of `Cell`s, or they can
  25. //! contain depth information for the tree view, or both. These are described
  26. //! below.
  27. //!
  28. //!
  29. //! ## Constructing Detail Views
  30. //!
  31. //! When using the `--long` command-line argument, the details of each file are
  32. //! displayed next to its name.
  33. //!
  34. //! The table holds a vector of all the column types. For each file and column, a
  35. //! `Cell` value containing the ANSI-coloured text and Unicode width of each cell
  36. //! is generated, with the row and column determined by indexing into both arrays.
  37. //!
  38. //! The column types vector does not actually include the filename. This is
  39. //! because the filename is always the rightmost field, and as such, it does not
  40. //! need to have its width queried or be padded with spaces.
  41. //!
  42. //! To illustrate the above:
  43. //!
  44. //! โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  45. //! โ”‚ columns: [ Permissions, Size, User, Date(Modified) ] โ”‚
  46. //! โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
  47. //! โ”‚ rows: cells: filename: โ”‚
  48. //! โ”‚ row 1: [ ".rw-r--r--", "9.6k", "ben", "29 Jun 16:16" ] Cargo.lock โ”‚
  49. //! โ”‚ row 2: [ ".rw-r--r--", "547", "ben", "23 Jun 10:54" ] Cargo.toml โ”‚
  50. //! โ”‚ row 3: [ "drwxr-xr-x", "-", "ben", "29 Jun 14:50" ] src โ”‚
  51. //! โ”‚ row 4: [ "drwxr-xr-x", "-", "ben", "28 Jun 19:53" ] target โ”‚
  52. //! โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  53. //!
  54. //! Each column in the table needs to be resized to fit its widest argument. This
  55. //! means that we must wait until every row has been added to the table before it
  56. //! can be displayed, in order to make sure that every column is wide enough.
  57. //!
  58. //!
  59. //! ## Constructing Tree Views
  60. //!
  61. //! When using the `--tree` argument, instead of a vector of cells, each row has a
  62. //! `depth` field that indicates how far deep in the tree it is: the top level has
  63. //! depth 0, its children have depth 1, and *their* children have depth 2, and so
  64. //! on.
  65. //!
  66. //! On top of this, it also has a `last` field that specifies whether this is the
  67. //! last row of this particular consecutive set of rows. This doesn't affect the
  68. //! file's information; it's just used to display a different set of Unicode tree
  69. //! characters! The resulting table looks like this:
  70. //!
  71. //! โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  72. //! โ”‚ Depth โ”‚ Last โ”‚ Output โ”‚
  73. //! โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
  74. //! โ”‚ 0 โ”‚ โ”‚ documents โ”‚
  75. //! โ”‚ 1 โ”‚ false โ”‚ โ”œโ”€โ”€ this_file.txt โ”‚
  76. //! โ”‚ 1 โ”‚ false โ”‚ โ”œโ”€โ”€ that_file.txt โ”‚
  77. //! โ”‚ 1 โ”‚ false โ”‚ โ”œโ”€โ”€ features โ”‚
  78. //! โ”‚ 2 โ”‚ false โ”‚ โ”‚ โ”œโ”€โ”€ feature_1.rs โ”‚
  79. //! โ”‚ 2 โ”‚ false โ”‚ โ”‚ โ”œโ”€โ”€ feature_2.rs โ”‚
  80. //! โ”‚ 2 โ”‚ true โ”‚ โ”‚ โ””โ”€โ”€ feature_3.rs โ”‚
  81. //! โ”‚ 1 โ”‚ true โ”‚ โ””โ”€โ”€ pictures โ”‚
  82. //! โ”‚ 2 โ”‚ false โ”‚ โ”œโ”€โ”€ garden.jpg โ”‚
  83. //! โ”‚ 2 โ”‚ false โ”‚ โ”œโ”€โ”€ flowers.jpg โ”‚
  84. //! โ”‚ 2 โ”‚ false โ”‚ โ”œโ”€โ”€ library.png โ”‚
  85. //! โ”‚ 2 โ”‚ true โ”‚ โ””โ”€โ”€ space.tiff โ”‚
  86. //! โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  87. //!
  88. //! Creating the table like this means that each file has to be tested to see if
  89. //! it's the last one in the group. This is usually done by putting all the files
  90. //! in a vector beforehand, getting its length, then comparing the index of each
  91. //! file to see if it's the last one. (As some files may not be successfully
  92. //! `stat`ted, we don't know how many files are going to exist in each directory)
  93. //!
  94. //! These rows have a `None` value for their vector of cells, instead of a `Some`
  95. //! vector containing any. It's possible to have *both* a vector of cells and
  96. //! depth and last flags when the user specifies `--tree` *and* `--long`.
  97. //!
  98. //!
  99. //! ## Extended Attributes and Errors
  100. //!
  101. //! Finally, files' extended attributes and any errors that occur while statting
  102. //! them can also be displayed as their children. It looks like this:
  103. //!
  104. //! .rw-r--r-- 0 ben 3 Sep 13:26 forbidden
  105. //! โ””โ”€โ”€ <Permission denied (os error 13)>
  106. //! .rw-r--r--@ 0 ben 3 Sep 13:26 file_with_xattrs
  107. //! โ”œโ”€โ”€ another_greeting (len 2)
  108. //! โ””โ”€โ”€ greeting (len 5)
  109. //!
  110. //! These lines also have `None` cells, and the error string or attribute details
  111. //! are used in place of the filename.
  112. use std::error::Error;
  113. use std::io;
  114. use std::path::PathBuf;
  115. use std::string::ToString;
  116. use colours::Colours;
  117. use column::{Alignment, Column, Cell};
  118. use dir::Dir;
  119. use feature::xattr::{Attribute, FileAttributes};
  120. use file::fields as f;
  121. use file::File;
  122. use options::{Columns, FileFilter, RecurseOptions, SizeFormat};
  123. use ansi_term::{ANSIString, ANSIStrings, Style};
  124. use datetime::local::{LocalDateTime, DatePiece};
  125. use datetime::format::{DateFormat};
  126. use datetime::zoned::{TimeZone};
  127. use locale;
  128. use number_prefix::{binary_prefix, decimal_prefix, Prefixed, Standalone, PrefixNames};
  129. use users::{OSUsers, Users};
  130. use users::mock::MockUsers;
  131. use super::filename;
  132. /// With the **Details** view, the output gets formatted into columns, with
  133. /// each `Column` object showing some piece of information about the file,
  134. /// such as its size, or its permissions.
  135. ///
  136. /// To do this, the results have to be written to a table, instead of
  137. /// displaying each file immediately. Then, the width of each column can be
  138. /// calculated based on the individual results, and the fields are padded
  139. /// during output.
  140. ///
  141. /// Almost all the heavy lifting is done in a Table object, which handles the
  142. /// columns for each row.
  143. #[derive(PartialEq, Debug, Copy, Clone, Default)]
  144. pub struct Details {
  145. /// A Columns object that says which columns should be included in the
  146. /// output in the general case. Directories themselves can pick which
  147. /// columns are *added* to this list, such as the Git column.
  148. pub columns: Option<Columns>,
  149. /// Whether to recurse through directories with a tree view, and if so,
  150. /// which options to use. This field is only relevant here if the `tree`
  151. /// field of the RecurseOptions is `true`.
  152. pub recurse: Option<RecurseOptions>,
  153. /// How to sort and filter the files after getting their details.
  154. pub filter: FileFilter,
  155. /// Whether to show a header line or not.
  156. pub header: bool,
  157. /// Whether to show each file's extended attributes.
  158. pub xattr: bool,
  159. /// The colours to use to display information in the table, including the
  160. /// colour of the tree view symbols.
  161. pub colours: Colours,
  162. }
  163. impl Details {
  164. /// Print the details of the given vector of files -- all of which will
  165. /// have been read from the given directory, if present -- to stdout.
  166. pub fn view(&self, dir: Option<&Dir>, files: Vec<File>) {
  167. // First, transform the Columns object into a vector of columns for
  168. // the current directory.
  169. let columns_for_dir = match self.columns {
  170. Some(cols) => cols.for_dir(dir),
  171. None => Vec::new(),
  172. };
  173. // Next, add a header if the user requests it.
  174. let mut table = Table::with_options(self.colours, columns_for_dir);
  175. if self.header { table.add_header() }
  176. // Then add files to the table and print it out.
  177. self.add_files_to_table(&mut table, files, 0);
  178. for cell in table.print_table() {
  179. println!("{}", cell.text);
  180. }
  181. }
  182. /// Adds files to the table, possibly recursively. This is easily
  183. /// parallelisable, and uses a pool of threads.
  184. fn add_files_to_table<'dir, U: Users+Send>(&self, mut table: &mut Table<U>, src: Vec<File<'dir>>, depth: usize) {
  185. use num_cpus;
  186. use scoped_threadpool::Pool;
  187. use std::sync::{Arc, Mutex};
  188. let mut pool = Pool::new(num_cpus::get() as u32);
  189. let mut file_eggs = Vec::new();
  190. struct Egg<'_> {
  191. cells: Vec<Cell>,
  192. name: Cell,
  193. xattrs: Vec<Attribute>,
  194. errors: Vec<(io::Error, Option<PathBuf>)>,
  195. dir: Option<Dir>,
  196. file: Arc<File<'_>>,
  197. }
  198. pool.scoped(|scoped| {
  199. let file_eggs = Arc::new(Mutex::new(&mut file_eggs));
  200. let table = Arc::new(Mutex::new(&mut table));
  201. for file in src.into_iter() {
  202. let file: Arc<File> = Arc::new(file);
  203. let file_eggs = file_eggs.clone();
  204. let table = table.clone();
  205. scoped.execute(move || {
  206. let mut errors = Vec::new();
  207. let mut xattrs = Vec::new();
  208. match file.path.attributes() {
  209. Ok(xs) => {
  210. if self.xattr {
  211. for xattr in xs {
  212. xattrs.push(xattr);
  213. }
  214. }
  215. },
  216. Err(e) => {
  217. if self.xattr {
  218. errors.push((e, None));
  219. }
  220. },
  221. };
  222. let cells = table.lock().unwrap().cells_for_file(&file, !xattrs.is_empty());
  223. let name = Cell {
  224. text: filename(&file, &self.colours, true),
  225. length: file.file_name_width()
  226. };
  227. let mut dir = None;
  228. if let Some(r) = self.recurse {
  229. if file.is_directory() && r.tree && !r.is_too_deep(depth) {
  230. if let Ok(d) = file.to_dir(false) {
  231. dir = Some(d);
  232. }
  233. }
  234. };
  235. let egg = Egg {
  236. cells: cells,
  237. name: name,
  238. xattrs: xattrs,
  239. errors: errors,
  240. dir: dir,
  241. file: file,
  242. };
  243. file_eggs.lock().unwrap().push(egg);
  244. });
  245. }
  246. });
  247. file_eggs.sort_by(|a, b| self.filter.compare_files(&*a.file, &*b.file));
  248. let num_eggs = file_eggs.len();
  249. for (index, egg) in file_eggs.into_iter().enumerate() {
  250. let mut files = Vec::new();
  251. let mut errors = egg.errors;
  252. let row = Row {
  253. depth: depth,
  254. cells: Some(egg.cells),
  255. name: egg.name,
  256. last: index == num_eggs - 1,
  257. };
  258. table.rows.push(row);
  259. if let Some(ref dir) = egg.dir {
  260. for file_to_add in dir.files() {
  261. match file_to_add {
  262. Ok(f) => files.push(f),
  263. Err((path, e)) => errors.push((e, Some(path)))
  264. }
  265. }
  266. self.filter.filter_files(&mut files);
  267. if !files.is_empty() {
  268. for xattr in egg.xattrs {
  269. table.add_xattr(xattr, depth + 1, false);
  270. }
  271. for (error, path) in errors {
  272. table.add_error(&error, depth + 1, false, path);
  273. }
  274. self.add_files_to_table(table, files, depth + 1);
  275. continue;
  276. }
  277. }
  278. let count = egg.xattrs.len();
  279. for (index, xattr) in egg.xattrs.into_iter().enumerate() {
  280. table.add_xattr(xattr, depth + 1, errors.is_empty() && index == count - 1);
  281. }
  282. let count = errors.len();
  283. for (index, (error, path)) in errors.into_iter().enumerate() {
  284. table.add_error(&error, depth + 1, index == count - 1, path);
  285. }
  286. }
  287. }
  288. }
  289. struct Row {
  290. /// Vector of cells to display.
  291. ///
  292. /// Most of the rows will be used to display files' metadata, so this will
  293. /// almost always be `Some`, containing a vector of cells. It will only be
  294. /// `None` for a row displaying an attribute or error, neither of which
  295. /// have cells.
  296. cells: Option<Vec<Cell>>,
  297. // Did You Know?
  298. // A Vec<Cell> and an Option<Vec<Cell>> actually have the same byte size!
  299. /// This file's name, in coloured output. The name is treated separately
  300. /// from the other cells, as it never requires padding.
  301. name: Cell,
  302. /// How many directories deep into the tree structure this is. Directories
  303. /// on top have depth 0.
  304. depth: usize,
  305. /// Whether this is the last entry in the directory. This flag is used
  306. /// when calculating the tree view.
  307. last: bool,
  308. }
  309. impl Row {
  310. /// Gets the Unicode display width of the indexed column, if present. If
  311. /// not, returns 0.
  312. fn column_width(&self, index: usize) -> usize {
  313. match self.cells {
  314. Some(ref cells) => cells[index].length,
  315. None => 0,
  316. }
  317. }
  318. }
  319. /// A **Table** object gets built up by the view as it lists files and
  320. /// directories.
  321. pub struct Table<U> {
  322. columns: Vec<Column>,
  323. rows: Vec<Row>,
  324. time: locale::Time,
  325. numeric: locale::Numeric,
  326. tz: TimeZone,
  327. users: U,
  328. colours: Colours,
  329. current_year: i64,
  330. }
  331. impl Default for Table<MockUsers> {
  332. fn default() -> Table<MockUsers> {
  333. Table {
  334. columns: Columns::default().for_dir(None),
  335. rows: Vec::new(),
  336. time: locale::Time::english(),
  337. numeric: locale::Numeric::english(),
  338. tz: TimeZone::localtime().unwrap(),
  339. users: MockUsers::with_current_uid(0),
  340. colours: Colours::default(),
  341. current_year: 1234,
  342. }
  343. }
  344. }
  345. impl Table<OSUsers> {
  346. /// Create a new, empty Table object, setting the caching fields to their
  347. /// empty states.
  348. pub fn with_options(colours: Colours, columns: Vec<Column>) -> Table<OSUsers> {
  349. Table {
  350. columns: columns,
  351. rows: Vec::new(),
  352. time: locale::Time::load_user_locale().unwrap_or_else(|_| locale::Time::english()),
  353. numeric: locale::Numeric::load_user_locale().unwrap_or_else(|_| locale::Numeric::english()),
  354. tz: TimeZone::localtime().unwrap(),
  355. users: OSUsers::empty_cache(),
  356. colours: colours,
  357. current_year: LocalDateTime::now().year(),
  358. }
  359. }
  360. }
  361. impl<U> Table<U> where U: Users {
  362. /// Add a dummy "header" row to the table, which contains the names of all
  363. /// the columns, underlined. This has dummy data for the cases that aren't
  364. /// actually used, such as the depth or list of attributes.
  365. pub fn add_header(&mut self) {
  366. let row = Row {
  367. depth: 0,
  368. cells: Some(self.columns.iter().map(|c| Cell::paint(self.colours.header, c.header())).collect()),
  369. name: Cell::paint(self.colours.header, "Name"),
  370. last: false,
  371. };
  372. self.rows.push(row);
  373. }
  374. fn add_error(&mut self, error: &io::Error, depth: usize, last: bool, path: Option<PathBuf>) {
  375. let error_message = match path {
  376. Some(path) => format!("<{}: {}>", path.display(), error),
  377. None => format!("<{}>", error),
  378. };
  379. let row = Row {
  380. depth: depth,
  381. cells: None,
  382. name: Cell::paint(self.colours.broken_arrow, &error_message),
  383. last: last,
  384. };
  385. self.rows.push(row);
  386. }
  387. fn add_xattr(&mut self, xattr: Attribute, depth: usize, last: bool) {
  388. let row = Row {
  389. depth: depth,
  390. cells: None,
  391. name: Cell::paint(self.colours.perms.attribute, &format!("{} (len {})", xattr.name, xattr.size)),
  392. last: last,
  393. };
  394. self.rows.push(row);
  395. }
  396. pub fn add_file_with_cells(&mut self, cells: Vec<Cell>, file: &File, depth: usize, last: bool, links: bool) {
  397. let row = Row {
  398. depth: depth,
  399. cells: Some(cells),
  400. name: Cell { text: filename(file, &self.colours, links), length: file.file_name_width() },
  401. last: last,
  402. };
  403. self.rows.push(row);
  404. }
  405. /// Use the list of columns to find which cells should be produced for
  406. /// this file, per-column.
  407. pub fn cells_for_file(&mut self, file: &File, xattrs: bool) -> Vec<Cell> {
  408. self.columns.clone().iter()
  409. .map(|c| self.display(file, c, xattrs))
  410. .collect()
  411. }
  412. fn display(&mut self, file: &File, column: &Column, xattrs: bool) -> Cell {
  413. match *column {
  414. Column::Permissions => self.render_permissions(file.permissions(), xattrs),
  415. Column::FileSize(fmt) => self.render_size(file.size(), fmt),
  416. Column::Timestamp(t) => self.render_time(file.timestamp(t)),
  417. Column::HardLinks => self.render_links(file.links()),
  418. Column::Inode => self.render_inode(file.inode()),
  419. Column::Blocks => self.render_blocks(file.blocks()),
  420. Column::User => self.render_user(file.user()),
  421. Column::Group => self.render_group(file.group()),
  422. Column::GitStatus => self.render_git_status(file.git_status()),
  423. }
  424. }
  425. fn render_permissions(&self, permissions: f::Permissions, xattrs: bool) -> Cell {
  426. let c = self.colours.perms;
  427. let bit = |bit, chr: &'static str, style: Style| {
  428. if bit { style.paint(chr) } else { self.colours.punctuation.paint("-") }
  429. };
  430. let file_type = match permissions.file_type {
  431. f::Type::File => self.colours.filetypes.normal.paint("."),
  432. f::Type::Directory => self.colours.filetypes.directory.paint("d"),
  433. f::Type::Pipe => self.colours.filetypes.special.paint("|"),
  434. f::Type::Link => self.colours.filetypes.symlink.paint("l"),
  435. f::Type::Special => self.colours.filetypes.special.paint("?"),
  436. };
  437. let x_colour = if let f::Type::File = permissions.file_type { c.user_execute_file }
  438. else { c.user_execute_other };
  439. let mut columns = vec![
  440. file_type,
  441. bit(permissions.user_read, "r", c.user_read),
  442. bit(permissions.user_write, "w", c.user_write),
  443. bit(permissions.user_execute, "x", x_colour),
  444. bit(permissions.group_read, "r", c.group_read),
  445. bit(permissions.group_write, "w", c.group_write),
  446. bit(permissions.group_execute, "x", c.group_execute),
  447. bit(permissions.other_read, "r", c.other_read),
  448. bit(permissions.other_write, "w", c.other_write),
  449. bit(permissions.other_execute, "x", c.other_execute),
  450. ];
  451. if xattrs {
  452. columns.push(c.attribute.paint("@"));
  453. }
  454. Cell {
  455. text: ANSIStrings(&columns).to_string(),
  456. length: columns.len(),
  457. }
  458. }
  459. fn render_links(&self, links: f::Links) -> Cell {
  460. let style = if links.multiple { self.colours.links.multi_link_file }
  461. else { self.colours.links.normal };
  462. Cell::paint(style, &self.numeric.format_int(links.count))
  463. }
  464. fn render_blocks(&self, blocks: f::Blocks) -> Cell {
  465. match blocks {
  466. f::Blocks::Some(blocks) => Cell::paint(self.colours.blocks, &blocks.to_string()),
  467. f::Blocks::None => Cell::paint(self.colours.punctuation, "-"),
  468. }
  469. }
  470. fn render_inode(&self, inode: f::Inode) -> Cell {
  471. Cell::paint(self.colours.inode, &inode.0.to_string())
  472. }
  473. fn render_size(&self, size: f::Size, size_format: SizeFormat) -> Cell {
  474. if let f::Size::Some(offset) = size {
  475. let result = match size_format {
  476. SizeFormat::DecimalBytes => decimal_prefix(offset as f64),
  477. SizeFormat::BinaryBytes => binary_prefix(offset as f64),
  478. SizeFormat::JustBytes => return Cell::paint(self.colours.size.numbers, &self.numeric.format_int(offset)),
  479. };
  480. match result {
  481. Standalone(bytes) => Cell::paint(self.colours.size.numbers, &*bytes.to_string()),
  482. Prefixed(prefix, n) => {
  483. let number = if n < 10f64 { self.numeric.format_float(n, 1) } else { self.numeric.format_int(n as isize) };
  484. let symbol = prefix.symbol();
  485. Cell {
  486. text: ANSIStrings( &[ self.colours.size.numbers.paint(&number[..]), self.colours.size.unit.paint(symbol) ]).to_string(),
  487. length: number.len() + symbol.len(),
  488. }
  489. }
  490. }
  491. }
  492. else {
  493. Cell::paint(self.colours.punctuation, "-")
  494. }
  495. }
  496. fn render_time(&self, timestamp: f::Time) -> Cell {
  497. let date = self.tz.at(LocalDateTime::at(timestamp.0 as i64));
  498. let format = if date.year() == self.current_year {
  499. DateFormat::parse("{2>:D} {:M} {2>:h}:{02>:m}").unwrap()
  500. }
  501. else {
  502. DateFormat::parse("{2>:D} {:M} {5>:Y}").unwrap()
  503. };
  504. Cell::paint(self.colours.date, &format.format(&date, &self.time))
  505. }
  506. fn render_git_status(&self, git: f::Git) -> Cell {
  507. Cell {
  508. text: ANSIStrings(&[ self.render_git_char(git.staged),
  509. self.render_git_char(git.unstaged) ]).to_string(),
  510. length: 2,
  511. }
  512. }
  513. fn render_git_char(&self, status: f::GitStatus) -> ANSIString {
  514. match status {
  515. f::GitStatus::NotModified => self.colours.punctuation.paint("-"),
  516. f::GitStatus::New => self.colours.git.new.paint("N"),
  517. f::GitStatus::Modified => self.colours.git.modified.paint("M"),
  518. f::GitStatus::Deleted => self.colours.git.deleted.paint("D"),
  519. f::GitStatus::Renamed => self.colours.git.renamed.paint("R"),
  520. f::GitStatus::TypeChange => self.colours.git.typechange.paint("T"),
  521. }
  522. }
  523. fn render_user(&mut self, user: f::User) -> Cell {
  524. let user_name = match self.users.get_user_by_uid(user.0) {
  525. Some(user) => user.name,
  526. None => user.0.to_string(),
  527. };
  528. let style = if self.users.get_current_uid() == user.0 { self.colours.users.user_you }
  529. else { self.colours.users.user_someone_else };
  530. Cell::paint(style, &*user_name)
  531. }
  532. fn render_group(&mut self, group: f::Group) -> Cell {
  533. let mut style = self.colours.users.group_not_yours;
  534. let group_name = match self.users.get_group_by_gid(group.0) {
  535. Some(group) => {
  536. let current_uid = self.users.get_current_uid();
  537. if let Some(current_user) = self.users.get_user_by_uid(current_uid) {
  538. if current_user.primary_group == group.gid || group.members.contains(&current_user.name) {
  539. style = self.colours.users.group_yours;
  540. }
  541. }
  542. group.name
  543. },
  544. None => group.0.to_string(),
  545. };
  546. Cell::paint(style, &*group_name)
  547. }
  548. /// Render the table as a vector of Cells, to be displayed on standard output.
  549. pub fn print_table(&self) -> Vec<Cell> {
  550. let mut stack = Vec::new();
  551. let mut cells = Vec::new();
  552. // Work out the list of column widths by finding the longest cell for
  553. // each column, then formatting each cell in that column to be the
  554. // width of that one.
  555. let column_widths: Vec<usize> = (0 .. self.columns.len())
  556. .map(|n| self.rows.iter().map(|row| row.column_width(n)).max().unwrap_or(0))
  557. .collect();
  558. let total_width: usize = self.columns.len() + column_widths.iter().sum::<usize>();
  559. for row in self.rows.iter() {
  560. let mut cell = Cell::empty();
  561. if let Some(ref cells) = row.cells {
  562. for (n, width) in column_widths.iter().enumerate() {
  563. match self.columns[n].alignment() {
  564. Alignment::Left => { cell.append(&cells[n]); cell.add_spaces(width - cells[n].length); }
  565. Alignment::Right => { cell.add_spaces(width - cells[n].length); cell.append(&cells[n]); }
  566. }
  567. cell.add_spaces(1);
  568. }
  569. }
  570. else {
  571. cell.add_spaces(total_width)
  572. }
  573. let mut filename = String::new();
  574. let mut filename_length = 0;
  575. // A stack tracks which tree characters should be printed. It's
  576. // necessary to maintain information about the previously-printed
  577. // lines, as the output will change based on whether the
  578. // *previous* entry was the last in its directory.
  579. stack.resize(row.depth + 1, TreePart::Edge);
  580. stack[row.depth] = if row.last { TreePart::Corner } else { TreePart::Edge };
  581. for i in 1 .. row.depth + 1 {
  582. filename.push_str(&*self.colours.punctuation.paint(stack[i].ascii_art()).to_string());
  583. filename_length += 4;
  584. }
  585. stack[row.depth] = if row.last { TreePart::Blank } else { TreePart::Line };
  586. // If any tree characters have been printed, then add an extra
  587. // space, which makes the output look much better.
  588. if row.depth != 0 {
  589. filename.push(' ');
  590. filename_length += 1;
  591. }
  592. // Print the name without worrying about padding.
  593. filename.push_str(&*row.name.text);
  594. filename_length += row.name.length;
  595. cell.append(&Cell { text: filename, length: filename_length });
  596. cells.push(cell);
  597. }
  598. cells
  599. }
  600. }
  601. #[derive(PartialEq, Debug, Clone)]
  602. enum TreePart {
  603. /// Rightmost column, *not* the last in the directory.
  604. Edge,
  605. /// Not the rightmost column, and the directory has not finished yet.
  606. Line,
  607. /// Rightmost column, and the last in the directory.
  608. Corner,
  609. /// Not the rightmost column, and the directory *has* finished.
  610. Blank,
  611. }
  612. impl TreePart {
  613. fn ascii_art(&self) -> &'static str {
  614. match *self {
  615. TreePart::Edge => "โ”œโ”€โ”€",
  616. TreePart::Line => "โ”‚ ",
  617. TreePart::Corner => "โ””โ”€โ”€",
  618. TreePart::Blank => " ",
  619. }
  620. }
  621. }
  622. #[cfg(test)]
  623. pub mod test {
  624. pub use super::Table;
  625. pub use file::File;
  626. pub use file::fields as f;
  627. pub use column::{Cell, Column};
  628. pub use users::{User, Group, uid_t, gid_t};
  629. pub use users::mock::MockUsers;
  630. pub use ansi_term::Style;
  631. pub use ansi_term::Colour::*;
  632. pub fn newser(uid: uid_t, name: &str, group: gid_t) -> User {
  633. User {
  634. uid: uid,
  635. name: name.to_string(),
  636. primary_group: group,
  637. home_dir: String::new(),
  638. shell: String::new(),
  639. }
  640. }
  641. // These tests create a new, default Table object, then fill in the
  642. // expected style in a certain way. This means we can check that the
  643. // right style is being used, as otherwise, it would just be plain.
  644. //
  645. // Doing things with fields is way easier than having to fake the entire
  646. // Metadata struct, which is what I was doing before!
  647. mod users {
  648. #![allow(unused_results)]
  649. use super::*;
  650. #[test]
  651. fn named() {
  652. let mut table = Table::default();
  653. table.colours.users.user_you = Red.bold();
  654. let mut users = MockUsers::with_current_uid(1000);
  655. users.add_user(newser(1000, "enoch", 100));
  656. table.users = users;
  657. let user = f::User(1000);
  658. let expected = Cell::paint(Red.bold(), "enoch");
  659. assert_eq!(expected, table.render_user(user))
  660. }
  661. #[test]
  662. fn unnamed() {
  663. let mut table = Table::default();
  664. table.colours.users.user_you = Cyan.bold();
  665. let users = MockUsers::with_current_uid(1000);
  666. table.users = users;
  667. let user = f::User(1000);
  668. let expected = Cell::paint(Cyan.bold(), "1000");
  669. assert_eq!(expected, table.render_user(user));
  670. }
  671. #[test]
  672. fn different_named() {
  673. let mut table = Table::default();
  674. table.colours.users.user_someone_else = Green.bold();
  675. table.users.add_user(newser(1000, "enoch", 100));
  676. let user = f::User(1000);
  677. let expected = Cell::paint(Green.bold(), "enoch");
  678. assert_eq!(expected, table.render_user(user));
  679. }
  680. #[test]
  681. fn different_unnamed() {
  682. let mut table = Table::default();
  683. table.colours.users.user_someone_else = Red.normal();
  684. let user = f::User(1000);
  685. let expected = Cell::paint(Red.normal(), "1000");
  686. assert_eq!(expected, table.render_user(user));
  687. }
  688. #[test]
  689. fn overflow() {
  690. let mut table = Table::default();
  691. table.colours.users.user_someone_else = Blue.underline();
  692. let user = f::User(2_147_483_648);
  693. let expected = Cell::paint(Blue.underline(), "2147483648");
  694. assert_eq!(expected, table.render_user(user));
  695. }
  696. }
  697. mod groups {
  698. #![allow(unused_results)]
  699. use super::*;
  700. #[test]
  701. fn named() {
  702. let mut table = Table::default();
  703. table.colours.users.group_not_yours = Fixed(101).normal();
  704. let mut users = MockUsers::with_current_uid(1000);
  705. users.add_group(Group { gid: 100, name: "folk".to_string(), members: vec![] });
  706. table.users = users;
  707. let group = f::Group(100);
  708. let expected = Cell::paint(Fixed(101).normal(), "folk");
  709. assert_eq!(expected, table.render_group(group))
  710. }
  711. #[test]
  712. fn unnamed() {
  713. let mut table = Table::default();
  714. table.colours.users.group_not_yours = Fixed(87).normal();
  715. let users = MockUsers::with_current_uid(1000);
  716. table.users = users;
  717. let group = f::Group(100);
  718. let expected = Cell::paint(Fixed(87).normal(), "100");
  719. assert_eq!(expected, table.render_group(group));
  720. }
  721. #[test]
  722. fn primary() {
  723. let mut table = Table::default();
  724. table.colours.users.group_yours = Fixed(64).normal();
  725. let mut users = MockUsers::with_current_uid(2);
  726. users.add_user(newser(2, "eve", 100));
  727. users.add_group(Group { gid: 100, name: "folk".to_string(), members: vec![] });
  728. table.users = users;
  729. let group = f::Group(100);
  730. let expected = Cell::paint(Fixed(64).normal(), "folk");
  731. assert_eq!(expected, table.render_group(group))
  732. }
  733. #[test]
  734. fn secondary() {
  735. let mut table = Table::default();
  736. table.colours.users.group_yours = Fixed(31).normal();
  737. let mut users = MockUsers::with_current_uid(2);
  738. users.add_user(newser(2, "eve", 666));
  739. users.add_group(Group { gid: 100, name: "folk".to_string(), members: vec![ "eve".to_string() ] });
  740. table.users = users;
  741. let group = f::Group(100);
  742. let expected = Cell::paint(Fixed(31).normal(), "folk");
  743. assert_eq!(expected, table.render_group(group))
  744. }
  745. #[test]
  746. fn overflow() {
  747. let mut table = Table::default();
  748. table.colours.users.group_not_yours = Blue.underline();
  749. let group = f::Group(2_147_483_648);
  750. let expected = Cell::paint(Blue.underline(), "2147483648");
  751. assert_eq!(expected, table.render_group(group));
  752. }
  753. }
  754. }