view.rs 13 KB

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