summaryrefslogtreecommitdiffstats
path: root/src/options/view.rs
diff options
context:
space:
mode:
authorBenjamin Sago <ogham@bsago.me>2016-04-17 20:38:37 +0100
committerBenjamin Sago <ogham@bsago.me>2016-04-17 20:38:37 +0100
commite9e1161cec18dd04a68a3147897093a5abc062bb (patch)
tree6e0781d2a26ec3e60b83f6e62f5460352ac94292 /src/options/view.rs
parentb44ae1b56b3e0f83c5ab314b96b221274b3c5d3c (diff)
Split up the options module
The original options was becoming a bit unwieldy, and would have been even more so if I added the same amount of comments. So this commit splits it up. There's no extra hiding going on here, or rearranging things within the module: (almost) everything now has to be marked 'pub' to let other sub-modules in the new options module to see it.
Diffstat (limited to 'src/options/view.rs')
-rw-r--r--src/options/view.rs355
1 files changed, 355 insertions, 0 deletions
diff --git a/src/options/view.rs b/src/options/view.rs
new file mode 100644
index 0000000..e4f2721
--- /dev/null
+++ b/src/options/view.rs
@@ -0,0 +1,355 @@
+use std::env::var_os;
+
+use getopts;
+
+use output::Colours;
+use output::{Grid, Details, GridDetails, Lines};
+use options::{FileFilter, DirAction, Misfire};
+use output::column::{Columns, TimeTypes, SizeFormat};
+use term::dimensions;
+use fs::feature::xattr;
+
+
+/// The **view** contains all information about how to format output.
+#[derive(PartialEq, Debug, Copy, Clone)]
+pub enum View {
+ Details(Details),
+ Grid(Grid),
+ GridDetails(GridDetails),
+ Lines(Lines),
+}
+
+impl View {
+
+ /// Determine which view to use and all of that view’s arguments.
+ pub fn deduce(matches: &getopts::Matches, filter: FileFilter, dir_action: DirAction) -> Result<View, Misfire> {
+ use options::misfire::Misfire::*;
+
+ let long = || {
+ if matches.opt_present("across") && !matches.opt_present("grid") {
+ Err(Useless("across", true, "long"))
+ }
+ else if matches.opt_present("oneline") {
+ Err(Useless("oneline", true, "long"))
+ }
+ else {
+ let term_colours = try!(TerminalColours::deduce(matches));
+ let colours = match term_colours {
+ TerminalColours::Always => Colours::colourful(),
+ TerminalColours::Never => Colours::plain(),
+ TerminalColours::Automatic => {
+ if dimensions().is_some() {
+ Colours::colourful()
+ }
+ else {
+ Colours::plain()
+ }
+ },
+ };
+
+ let details = Details {
+ columns: Some(try!(Columns::deduce(matches))),
+ header: matches.opt_present("header"),
+ recurse: dir_action.recurse_options(),
+ filter: filter,
+ xattr: xattr::ENABLED && matches.opt_present("extended"),
+ colours: colours,
+ };
+
+ Ok(details)
+ }
+ };
+
+ let long_options_scan = || {
+ for option in &[ "binary", "bytes", "inode", "links", "header", "blocks", "time", "group" ] {
+ if matches.opt_present(option) {
+ return Err(Useless(option, false, "long"));
+ }
+ }
+
+ if cfg!(feature="git") && matches.opt_present("git") {
+ Err(Useless("git", false, "long"))
+ }
+ else if matches.opt_present("level") && !matches.opt_present("recurse") && !matches.opt_present("tree") {
+ Err(Useless2("level", "recurse", "tree"))
+ }
+ else if xattr::ENABLED && matches.opt_present("extended") {
+ Err(Useless("extended", false, "long"))
+ }
+ else {
+ Ok(())
+ }
+ };
+
+ let other_options_scan = || {
+ let term_colours = try!(TerminalColours::deduce(matches));
+ let term_width = try!(TerminalWidth::deduce(matches));
+
+ if let Some(&width) = term_width.as_ref() {
+ let colours = match term_colours {
+ TerminalColours::Always => Colours::colourful(),
+ TerminalColours::Never => Colours::plain(),
+ TerminalColours::Automatic => Colours::colourful(),
+ };
+
+ if matches.opt_present("oneline") {
+ if matches.opt_present("across") {
+ Err(Useless("across", true, "oneline"))
+ }
+ else {
+ let lines = Lines {
+ colours: colours,
+ };
+
+ Ok(View::Lines(lines))
+ }
+ }
+ else if matches.opt_present("tree") {
+ let details = Details {
+ columns: None,
+ header: false,
+ recurse: dir_action.recurse_options(),
+ filter: filter,
+ xattr: false,
+ colours: colours,
+ };
+
+ Ok(View::Details(details))
+ }
+ else {
+ let grid = Grid {
+ across: matches.opt_present("across"),
+ console_width: width,
+ colours: colours,
+ };
+
+ Ok(View::Grid(grid))
+ }
+ }
+ else {
+ // If the terminal width couldn’t be matched for some reason, such
+ // as the program’s stdout being connected to a file, then
+ // fallback to the lines view.
+
+ let colours = match term_colours {
+ TerminalColours::Always => Colours::colourful(),
+ TerminalColours::Never => Colours::plain(),
+ TerminalColours::Automatic => Colours::plain(),
+ };
+
+ if matches.opt_present("tree") {
+ let details = Details {
+ columns: None,
+ header: false,
+ recurse: dir_action.recurse_options(),
+ filter: filter,
+ xattr: false,
+ colours: colours,
+ };
+
+ Ok(View::Details(details))
+ }
+ else {
+ let lines = Lines {
+ colours: colours,
+ };
+
+ Ok(View::Lines(lines))
+ }
+ }
+ };
+
+ if matches.opt_present("long") {
+ let long_options = try!(long());
+
+ if matches.opt_present("grid") {
+ match other_options_scan() {
+ Ok(View::Grid(grid)) => return Ok(View::GridDetails(GridDetails { grid: grid, details: long_options })),
+ Ok(lines) => return Ok(lines),
+ Err(e) => return Err(e),
+ };
+ }
+ else {
+ return Ok(View::Details(long_options));
+ }
+ }
+
+ try!(long_options_scan());
+
+ other_options_scan()
+ }
+}
+
+
+/// The width of the terminal requested by the user.
+#[derive(PartialEq, Debug)]
+enum TerminalWidth {
+
+ /// The user requested this specific number of columns.
+ Set(usize),
+
+ /// The terminal was found to have this number of columns.
+ Terminal(usize),
+
+ /// The user didn’t request any particular terminal width.
+ Unset,
+}
+
+impl TerminalWidth {
+
+ /// Determine a requested terminal width from the command-line arguments.
+ ///
+ /// Returns an error if a requested width doesn’t parse to an integer.
+ fn deduce(_: &getopts::Matches) -> Result<TerminalWidth, Misfire> {
+ if let Some(columns) = var_os("COLUMNS").and_then(|s| s.into_string().ok()) {
+ match columns.parse() {
+ Ok(width) => Ok(TerminalWidth::Set(width)),
+ Err(e) => Err(Misfire::FailedParse(e)),
+ }
+ }
+ else if let Some((width, _)) = dimensions() {
+ Ok(TerminalWidth::Terminal(width))
+ }
+ else {
+ Ok(TerminalWidth::Unset)
+ }
+ }
+
+ fn as_ref(&self) -> Option<&usize> {
+ match *self {
+ TerminalWidth::Set(ref width) => Some(width),
+ TerminalWidth::Terminal(ref width) => Some(width),
+ TerminalWidth::Unset => None,
+ }
+ }
+}
+
+
+impl Columns {
+ fn deduce(matches: &getopts::Matches) -> Result<Columns, Misfire> {
+ Ok(Columns {
+ size_format: try!(SizeFormat::deduce(matches)),
+ time_types: try!(TimeTypes::deduce(matches)),
+ inode: matches.opt_present("inode"),
+ links: matches.opt_present("links"),
+ blocks: matches.opt_present("blocks"),
+ group: matches.opt_present("group"),
+ git: cfg!(feature="git") && matches.opt_present("git"),
+ })
+ }
+}
+
+
+impl SizeFormat {
+
+ /// Determine which file size to use in the file size column based on
+ /// the user’s options.
+ ///
+ /// The default mode is to use the decimal prefixes, as they are the
+ /// most commonly-understood, and don’t involve trying to parse large
+ /// strings of digits in your head. Changing the format to anything else
+ /// involves the `--binary` or `--bytes` flags, and these conflict with
+ /// each other.
+ fn deduce(matches: &getopts::Matches) -> Result<SizeFormat, Misfire> {
+ let binary = matches.opt_present("binary");
+ let bytes = matches.opt_present("bytes");
+
+ match (binary, bytes) {
+ (true, true ) => Err(Misfire::Conflict("binary", "bytes")),
+ (true, false) => Ok(SizeFormat::BinaryBytes),
+ (false, true ) => Ok(SizeFormat::JustBytes),
+ (false, false) => Ok(SizeFormat::DecimalBytes),
+ }
+ }
+}
+
+
+impl TimeTypes {
+
+ /// Determine which of a file’s time fields should be displayed for it
+ /// based on the user’s options.
+ ///
+ /// There are two separate ways to pick which fields to show: with a
+ /// flag (such as `--modified`) or with a parameter (such as
+ /// `--time=modified`). An error is signaled if both ways are used.
+ ///
+ /// It’s valid to show more than one column by passing in more than one
+ /// option, but passing *no* options means that the user just wants to
+ /// see the default set.
+ fn deduce(matches: &getopts::Matches) -> Result<TimeTypes, Misfire> {
+ let possible_word = matches.opt_str("time");
+ let modified = matches.opt_present("modified");
+ let created = matches.opt_present("created");
+ let accessed = matches.opt_present("accessed");
+
+ if let Some(word) = possible_word {
+ if modified {
+ return Err(Misfire::Useless("modified", true, "time"));
+ }
+ else if created {
+ return Err(Misfire::Useless("created", true, "time"));
+ }
+ else if accessed {
+ return Err(Misfire::Useless("accessed", true, "time"));
+ }
+
+ match &*word {
+ "mod" | "modified" => Ok(TimeTypes { accessed: false, modified: true, created: false }),
+ "acc" | "accessed" => Ok(TimeTypes { accessed: true, modified: false, created: false }),
+ "cr" | "created" => Ok(TimeTypes { accessed: false, modified: false, created: true }),
+ otherwise => Err(Misfire::bad_argument("time", otherwise)),
+ }
+ }
+ else if modified || created || accessed {
+ Ok(TimeTypes { accessed: accessed, modified: modified, created: created })
+ }
+ else {
+ Ok(TimeTypes::default())
+ }
+ }
+}
+
+
+/// Under what circumstances we should display coloured, rather than plain,
+/// output to the terminal.
+///
+/// By default, we want to display the colours when stdout can display them.
+/// Turning them on when output is going to, say, a pipe, would make programs
+/// such as `grep` or `more` not work properly. So the `Automatic` mode does
+/// this check and only displays colours when they can be truly appreciated.
+#[derive(PartialEq, Debug)]
+enum TerminalColours {
+
+ /// Display them even when output isn’t going to a terminal.
+ Always,
+
+ /// Display them when output is going to a terminal, but not otherwise.
+ Automatic,
+
+ /// Never display them, even when output is going to a terminal.
+ Never,
+}
+
+impl Default for TerminalColours {
+ fn default() -> TerminalColours {
+ TerminalColours::Automatic
+ }
+}
+
+impl TerminalColours {
+
+ /// Determine which terminal colour conditions to use.
+ fn deduce(matches: &getopts::Matches) -> Result<TerminalColours, Misfire> {
+ if let Some(word) = matches.opt_str("color").or(matches.opt_str("colour")) {
+ match &*word {
+ "always" => Ok(TerminalColours::Always),
+ "auto" | "automatic" => Ok(TerminalColours::Automatic),
+ "never" => Ok(TerminalColours::Never),
+ otherwise => Err(Misfire::bad_argument("color", otherwise))
+ }
+ }
+ else {
+ Ok(TerminalColours::default())
+ }
+ }
+}