view.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. use std::env::var_os;
  2. use getopts;
  3. use output::Colours;
  4. use output::{Grid, Details, GridDetails, Lines};
  5. use output::column::{Columns, TimeTypes, SizeFormat};
  6. use output::file_name::Classify;
  7. use options::{FileFilter, DirAction, Misfire};
  8. use term::dimensions;
  9. use fs::feature::xattr;
  10. /// The **view** contains all information about how to format output.
  11. #[derive(PartialEq, Debug, Clone)]
  12. pub enum View {
  13. Details(Details),
  14. Grid(Grid),
  15. GridDetails(GridDetails),
  16. Lines(Lines),
  17. }
  18. impl View {
  19. /// Determine which view to use and all of that view’s arguments.
  20. pub fn deduce(matches: &getopts::Matches, filter: FileFilter, dir_action: DirAction) -> Result<View, Misfire> {
  21. use options::misfire::Misfire::*;
  22. let colour_scale = || {
  23. matches.opt_present("color-scale") || matches.opt_present("colour-scale")
  24. };
  25. let long = || {
  26. if matches.opt_present("across") && !matches.opt_present("grid") {
  27. Err(Useless("across", true, "long"))
  28. }
  29. else if matches.opt_present("oneline") {
  30. Err(Useless("oneline", true, "long"))
  31. }
  32. else {
  33. let term_colours = TerminalColours::deduce(matches)?;
  34. let colours = match term_colours {
  35. TerminalColours::Always => Colours::colourful(colour_scale()),
  36. TerminalColours::Never => Colours::plain(),
  37. TerminalColours::Automatic => {
  38. if dimensions().is_some() {
  39. Colours::colourful(colour_scale())
  40. }
  41. else {
  42. Colours::plain()
  43. }
  44. },
  45. };
  46. let details = Details {
  47. columns: Some(Columns::deduce(matches)?),
  48. header: matches.opt_present("header"),
  49. recurse: dir_action.recurse_options(),
  50. filter: filter.clone(),
  51. xattr: xattr::ENABLED && matches.opt_present("extended"),
  52. colours: colours,
  53. classify: Classify::deduce(matches),
  54. };
  55. Ok(details)
  56. }
  57. };
  58. let long_options_scan = || {
  59. for option in &[ "binary", "bytes", "inode", "links", "header", "blocks", "time", "group" ] {
  60. if matches.opt_present(option) {
  61. return Err(Useless(option, false, "long"));
  62. }
  63. }
  64. if cfg!(feature="git") && matches.opt_present("git") {
  65. Err(Useless("git", false, "long"))
  66. }
  67. else if matches.opt_present("level") && !matches.opt_present("recurse") && !matches.opt_present("tree") {
  68. Err(Useless2("level", "recurse", "tree"))
  69. }
  70. else if xattr::ENABLED && matches.opt_present("extended") {
  71. Err(Useless("extended", false, "long"))
  72. }
  73. else {
  74. Ok(())
  75. }
  76. };
  77. let other_options_scan = || {
  78. let classify = Classify::deduce(matches);
  79. let term_colours = TerminalColours::deduce(matches)?;
  80. let term_width = TerminalWidth::deduce()?;
  81. if let Some(&width) = term_width.as_ref() {
  82. let colours = match term_colours {
  83. TerminalColours::Always
  84. | TerminalColours::Automatic => Colours::colourful(colour_scale()),
  85. TerminalColours::Never => Colours::plain(),
  86. };
  87. if matches.opt_present("oneline") {
  88. if matches.opt_present("across") {
  89. Err(Useless("across", true, "oneline"))
  90. }
  91. else {
  92. let lines = Lines {
  93. colours: colours,
  94. classify: classify,
  95. };
  96. Ok(View::Lines(lines))
  97. }
  98. }
  99. else if matches.opt_present("tree") {
  100. let details = Details {
  101. columns: None,
  102. header: false,
  103. recurse: dir_action.recurse_options(),
  104. filter: filter.clone(), // TODO: clone
  105. xattr: false,
  106. colours: colours,
  107. classify: classify,
  108. };
  109. Ok(View::Details(details))
  110. }
  111. else {
  112. let grid = Grid {
  113. across: matches.opt_present("across"),
  114. console_width: width,
  115. colours: colours,
  116. classify: classify,
  117. };
  118. Ok(View::Grid(grid))
  119. }
  120. }
  121. else {
  122. // If the terminal width couldn’t be matched for some reason, such
  123. // as the program’s stdout being connected to a file, then
  124. // fallback to the lines view.
  125. let colours = match term_colours {
  126. TerminalColours::Always => Colours::colourful(colour_scale()),
  127. TerminalColours::Never | TerminalColours::Automatic => Colours::plain(),
  128. };
  129. if matches.opt_present("tree") {
  130. let details = Details {
  131. columns: None,
  132. header: false,
  133. recurse: dir_action.recurse_options(),
  134. filter: filter.clone(),
  135. xattr: false,
  136. colours: colours,
  137. classify: classify,
  138. };
  139. Ok(View::Details(details))
  140. }
  141. else {
  142. let lines = Lines {
  143. colours: colours,
  144. classify: classify,
  145. };
  146. Ok(View::Lines(lines))
  147. }
  148. }
  149. };
  150. if matches.opt_present("long") {
  151. let long_options = long()?;
  152. if matches.opt_present("grid") {
  153. match other_options_scan() {
  154. Ok(View::Grid(grid)) => return Ok(View::GridDetails(GridDetails { grid: grid, details: long_options })),
  155. Ok(lines) => return Ok(lines),
  156. Err(e) => return Err(e),
  157. };
  158. }
  159. else {
  160. return Ok(View::Details(long_options));
  161. }
  162. }
  163. long_options_scan()?;
  164. other_options_scan()
  165. }
  166. }
  167. /// The width of the terminal requested by the user.
  168. #[derive(PartialEq, Debug)]
  169. enum TerminalWidth {
  170. /// The user requested this specific number of columns.
  171. Set(usize),
  172. /// The terminal was found to have this number of columns.
  173. Terminal(usize),
  174. /// The user didn’t request any particular terminal width.
  175. Unset,
  176. }
  177. impl TerminalWidth {
  178. /// Determine a requested terminal width from the command-line arguments.
  179. ///
  180. /// Returns an error if a requested width doesn’t parse to an integer.
  181. fn deduce() -> Result<TerminalWidth, Misfire> {
  182. if let Some(columns) = var_os("COLUMNS").and_then(|s| s.into_string().ok()) {
  183. match columns.parse() {
  184. Ok(width) => Ok(TerminalWidth::Set(width)),
  185. Err(e) => Err(Misfire::FailedParse(e)),
  186. }
  187. }
  188. else if let Some((width, _)) = dimensions() {
  189. Ok(TerminalWidth::Terminal(width))
  190. }
  191. else {
  192. Ok(TerminalWidth::Unset)
  193. }
  194. }
  195. fn as_ref(&self) -> Option<&usize> {
  196. match *self {
  197. TerminalWidth::Set(ref width)
  198. | TerminalWidth::Terminal(ref width) => Some(width),
  199. TerminalWidth::Unset => None,
  200. }
  201. }
  202. }
  203. impl Columns {
  204. fn deduce(matches: &getopts::Matches) -> Result<Columns, Misfire> {
  205. Ok(Columns {
  206. size_format: SizeFormat::deduce(matches)?,
  207. time_types: TimeTypes::deduce(matches)?,
  208. inode: matches.opt_present("inode"),
  209. links: matches.opt_present("links"),
  210. blocks: matches.opt_present("blocks"),
  211. group: matches.opt_present("group"),
  212. git: cfg!(feature="git") && matches.opt_present("git"),
  213. })
  214. }
  215. }
  216. impl SizeFormat {
  217. /// Determine which file size to use in the file size column based on
  218. /// the user’s options.
  219. ///
  220. /// The default mode is to use the decimal prefixes, as they are the
  221. /// most commonly-understood, and don’t involve trying to parse large
  222. /// strings of digits in your head. Changing the format to anything else
  223. /// involves the `--binary` or `--bytes` flags, and these conflict with
  224. /// each other.
  225. fn deduce(matches: &getopts::Matches) -> Result<SizeFormat, Misfire> {
  226. let binary = matches.opt_present("binary");
  227. let bytes = matches.opt_present("bytes");
  228. match (binary, bytes) {
  229. (true, true ) => Err(Misfire::Conflict("binary", "bytes")),
  230. (true, false) => Ok(SizeFormat::BinaryBytes),
  231. (false, true ) => Ok(SizeFormat::JustBytes),
  232. (false, false) => Ok(SizeFormat::DecimalBytes),
  233. }
  234. }
  235. }
  236. impl TimeTypes {
  237. /// Determine which of a file’s time fields should be displayed for it
  238. /// based on the user’s options.
  239. ///
  240. /// There are two separate ways to pick which fields to show: with a
  241. /// flag (such as `--modified`) or with a parameter (such as
  242. /// `--time=modified`). An error is signaled if both ways are used.
  243. ///
  244. /// It’s valid to show more than one column by passing in more than one
  245. /// option, but passing *no* options means that the user just wants to
  246. /// see the default set.
  247. fn deduce(matches: &getopts::Matches) -> Result<TimeTypes, Misfire> {
  248. let possible_word = matches.opt_str("time");
  249. let modified = matches.opt_present("modified");
  250. let created = matches.opt_present("created");
  251. let accessed = matches.opt_present("accessed");
  252. if let Some(word) = possible_word {
  253. if modified {
  254. return Err(Misfire::Useless("modified", true, "time"));
  255. }
  256. else if created {
  257. return Err(Misfire::Useless("created", true, "time"));
  258. }
  259. else if accessed {
  260. return Err(Misfire::Useless("accessed", true, "time"));
  261. }
  262. match &*word {
  263. "mod" | "modified" => Ok(TimeTypes { accessed: false, modified: true, created: false }),
  264. "acc" | "accessed" => Ok(TimeTypes { accessed: true, modified: false, created: false }),
  265. "cr" | "created" => Ok(TimeTypes { accessed: false, modified: false, created: true }),
  266. otherwise => Err(Misfire::bad_argument("time", otherwise,
  267. &["modified", "accessed", "created"])),
  268. }
  269. }
  270. else if modified || created || accessed {
  271. Ok(TimeTypes { accessed: accessed, modified: modified, created: created })
  272. }
  273. else {
  274. Ok(TimeTypes::default())
  275. }
  276. }
  277. }
  278. /// Under what circumstances we should display coloured, rather than plain,
  279. /// output to the terminal.
  280. ///
  281. /// By default, we want to display the colours when stdout can display them.
  282. /// Turning them on when output is going to, say, a pipe, would make programs
  283. /// such as `grep` or `more` not work properly. So the `Automatic` mode does
  284. /// this check and only displays colours when they can be truly appreciated.
  285. #[derive(PartialEq, Debug)]
  286. enum TerminalColours {
  287. /// Display them even when output isn’t going to a terminal.
  288. Always,
  289. /// Display them when output is going to a terminal, but not otherwise.
  290. Automatic,
  291. /// Never display them, even when output is going to a terminal.
  292. Never,
  293. }
  294. impl Default for TerminalColours {
  295. fn default() -> TerminalColours {
  296. TerminalColours::Automatic
  297. }
  298. }
  299. impl TerminalColours {
  300. /// Determine which terminal colour conditions to use.
  301. fn deduce(matches: &getopts::Matches) -> Result<TerminalColours, Misfire> {
  302. if let Some(word) = matches.opt_str("color").or_else(|| matches.opt_str("colour")) {
  303. match &*word {
  304. "always" => Ok(TerminalColours::Always),
  305. "auto" | "automatic" => Ok(TerminalColours::Automatic),
  306. "never" => Ok(TerminalColours::Never),
  307. otherwise => Err(Misfire::bad_argument("color", otherwise,
  308. &["always", "auto", "never"]))
  309. }
  310. }
  311. else {
  312. Ok(TerminalColours::default())
  313. }
  314. }
  315. }
  316. impl Classify {
  317. fn deduce(matches: &getopts::Matches) -> Classify {
  318. if matches.opt_present("classify") { Classify::AddFileIndicators }
  319. else { Classify::JustFilenames }
  320. }
  321. }