2017-07-22 17:06:05 +00:00
|
|
|
|
//! Parsing command-line strings into exa options.
|
|
|
|
|
//!
|
2017-07-24 07:34:50 +00:00
|
|
|
|
//! This module imports exa’s configuration types, such as `View` (the details
|
|
|
|
|
//! of displaying multiple files) and `DirAction` (what to do when encountering
|
|
|
|
|
//! a directory), and implements `deduce` methods on them so they can be
|
|
|
|
|
//! configured using command-line options.
|
|
|
|
|
//!
|
2017-07-22 17:06:05 +00:00
|
|
|
|
//!
|
|
|
|
|
//! ## Useless and overridden options
|
|
|
|
|
//!
|
|
|
|
|
//! Let’s say exa was invoked with just one argument: `exa --inode`. The
|
|
|
|
|
//! `--inode` option is used in the details view, where it adds the inode
|
|
|
|
|
//! column to the output. But because the details view is *only* activated with
|
|
|
|
|
//! the `--long` argument, adding `--inode` without it would not have any
|
|
|
|
|
//! effect.
|
|
|
|
|
//!
|
|
|
|
|
//! For a long time, exa’s philosophy was that the user should be warned
|
|
|
|
|
//! whenever they could be mistaken like this. If you tell exa to display the
|
|
|
|
|
//! inode, and it *doesn’t* display the inode, isn’t that more annoying than
|
|
|
|
|
//! having it throw an error back at you?
|
|
|
|
|
//!
|
|
|
|
|
//! However, this doesn’t take into account *configuration*. Say a user wants
|
|
|
|
|
//! to configure exa so that it lists inodes in the details view, but otherwise
|
|
|
|
|
//! functions normally. A common way to do this for command-line programs is to
|
|
|
|
|
//! define a shell alias that specifies the details they want to use every
|
|
|
|
|
//! time. For the inode column, the alias would be:
|
|
|
|
|
//!
|
|
|
|
|
//! `alias exa="exa --inode"`
|
|
|
|
|
//!
|
|
|
|
|
//! Using this alias means that although the inode column will be shown in the
|
|
|
|
|
//! details view, you’re now *only* allowed to use the details view, as any
|
|
|
|
|
//! other view type will result in an error. Oops!
|
|
|
|
|
//!
|
|
|
|
|
//! Another example is when an option is specified twice, such as `exa
|
|
|
|
|
//! --sort=Name --sort=size`. Did the user change their mind about sorting, and
|
|
|
|
|
//! accidentally specify the option twice?
|
|
|
|
|
//!
|
|
|
|
|
//! Again, exa rejected this case, throwing an error back to the user instead
|
|
|
|
|
//! of trying to guess how they want their output sorted. And again, this
|
|
|
|
|
//! doesn’t take into account aliases being used to set defaults. A user who
|
|
|
|
|
//! wants their files to be sorted case-insensitively may configure their shell
|
|
|
|
|
//! with the following:
|
|
|
|
|
//!
|
|
|
|
|
//! `alias exa="exa --sort=Name"`
|
|
|
|
|
//!
|
|
|
|
|
//! Just like the earlier example, the user now can’t use any other sort order,
|
|
|
|
|
//! because exa refuses to guess which one they meant. It’s *more* annoying to
|
|
|
|
|
//! have to go back and edit the command than if there were no error.
|
|
|
|
|
//!
|
|
|
|
|
//! Fortunately, there’s a heuristic for telling which options came from an
|
|
|
|
|
//! alias and which came from the actual command-line: aliased options are
|
|
|
|
|
//! nearer the beginning of the options array, and command-line options are
|
|
|
|
|
//! nearer the end. This means that after the options have been parsed, exa
|
|
|
|
|
//! needs to traverse them *backwards* to find the last-most-specified one.
|
|
|
|
|
//!
|
|
|
|
|
//! For example, invoking exa with `exa --sort=size` when that alias is present
|
|
|
|
|
//! would result in a full command-line of:
|
|
|
|
|
//!
|
|
|
|
|
//! `exa --sort=Name --sort=size`
|
|
|
|
|
//!
|
|
|
|
|
//! `--sort=size` should override `--sort=Name` because it’s closer to the end
|
|
|
|
|
//! of the arguments array. In fact, because there’s no way to tell where the
|
|
|
|
|
//! arguments came from -- it’s just a heuristic -- this will still work even
|
|
|
|
|
//! if no aliases are being used!
|
|
|
|
|
//!
|
|
|
|
|
//! Finally, this isn’t just useful when options could override each other.
|
2017-09-03 18:50:40 +00:00
|
|
|
|
//! Creating an alias `exal="exa --long --inode --header"` then invoking `exal
|
2017-07-22 17:06:05 +00:00
|
|
|
|
//! --grid --long` shouldn’t complain about `--long` being given twice when
|
|
|
|
|
//! it’s clear what the user wants.
|
|
|
|
|
|
|
|
|
|
|
2017-07-26 16:48:18 +00:00
|
|
|
|
use std::ffi::{OsStr, OsString};
|
2016-04-17 19:38:37 +00:00
|
|
|
|
|
2018-12-07 23:43:31 +00:00
|
|
|
|
use crate::fs::dir_action::DirAction;
|
|
|
|
|
use crate::fs::filter::FileFilter;
|
|
|
|
|
use crate::output::{View, Mode, details, grid_details};
|
2016-04-17 19:38:37 +00:00
|
|
|
|
|
2017-09-03 18:50:40 +00:00
|
|
|
|
mod style;
|
2016-04-17 19:38:37 +00:00
|
|
|
|
mod dir_action;
|
|
|
|
|
mod filter;
|
2017-07-24 07:34:50 +00:00
|
|
|
|
mod view;
|
2016-04-17 19:38:37 +00:00
|
|
|
|
|
|
|
|
|
mod help;
|
2017-06-23 21:50:29 +00:00
|
|
|
|
use self::help::HelpString;
|
2016-04-17 19:38:37 +00:00
|
|
|
|
|
2017-08-05 18:46:47 +00:00
|
|
|
|
mod version;
|
|
|
|
|
use self::version::VersionString;
|
|
|
|
|
|
2016-04-17 19:38:37 +00:00
|
|
|
|
mod misfire;
|
|
|
|
|
pub use self::misfire::Misfire;
|
|
|
|
|
|
2017-08-26 20:19:06 +00:00
|
|
|
|
pub mod vars;
|
2017-08-26 19:48:51 +00:00
|
|
|
|
pub use self::vars::Vars;
|
|
|
|
|
|
2017-07-12 11:03:07 +00:00
|
|
|
|
mod parser;
|
2017-07-26 16:48:18 +00:00
|
|
|
|
mod flags;
|
2017-08-05 18:11:00 +00:00
|
|
|
|
use self::parser::MatchedFlags;
|
2017-07-12 11:03:07 +00:00
|
|
|
|
|
2016-04-17 19:38:37 +00:00
|
|
|
|
|
|
|
|
|
/// These **options** represent a parsed, error-checked versions of the
|
|
|
|
|
/// user’s command-line options.
|
2017-07-05 19:16:04 +00:00
|
|
|
|
#[derive(Debug)]
|
2016-04-17 19:38:37 +00:00
|
|
|
|
pub struct Options {
|
|
|
|
|
|
|
|
|
|
/// The action to perform when encountering a directory rather than a
|
|
|
|
|
/// regular file.
|
|
|
|
|
pub dir_action: DirAction,
|
|
|
|
|
|
|
|
|
|
/// How to sort and filter files before outputting them.
|
|
|
|
|
pub filter: FileFilter,
|
|
|
|
|
|
|
|
|
|
/// The type of output to use (lines, grid, or details).
|
|
|
|
|
pub view: View,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Options {
|
|
|
|
|
|
2017-08-10 16:54:28 +00:00
|
|
|
|
/// Parse the given iterator of command-line strings into an Options
|
|
|
|
|
/// struct and a list of free filenames, using the environment variables
|
|
|
|
|
/// for extra options.
|
2016-04-17 19:38:37 +00:00
|
|
|
|
#[allow(unused_results)]
|
2017-08-13 10:14:58 +00:00
|
|
|
|
pub fn parse<'args, I, V>(args: I, vars: &V) -> Result<(Options, Vec<&'args OsStr>), Misfire>
|
2017-08-10 16:54:28 +00:00
|
|
|
|
where I: IntoIterator<Item=&'args OsString>,
|
|
|
|
|
V: Vars {
|
2018-12-07 23:43:31 +00:00
|
|
|
|
use crate::options::parser::{Matches, Strictness};
|
2016-04-17 19:38:37 +00:00
|
|
|
|
|
2017-08-26 20:19:06 +00:00
|
|
|
|
let strictness = match vars.get(vars::EXA_STRICT) {
|
2017-08-10 17:45:26 +00:00
|
|
|
|
None => Strictness::UseLastArguments,
|
|
|
|
|
Some(ref t) if t.is_empty() => Strictness::UseLastArguments,
|
|
|
|
|
_ => Strictness::ComplainAboutRedundantArguments,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let Matches { flags, frees } = match flags::ALL_ARGS.parse(args, strictness) {
|
2016-04-17 19:38:37 +00:00
|
|
|
|
Ok(m) => m,
|
|
|
|
|
Err(e) => return Err(Misfire::InvalidOptions(e)),
|
|
|
|
|
};
|
|
|
|
|
|
2017-08-05 18:11:00 +00:00
|
|
|
|
HelpString::deduce(&flags).map_err(Misfire::Help)?;
|
2017-08-05 18:46:47 +00:00
|
|
|
|
VersionString::deduce(&flags).map_err(Misfire::Version)?;
|
2016-04-17 19:38:37 +00:00
|
|
|
|
|
2017-08-10 16:54:28 +00:00
|
|
|
|
let options = Options::deduce(&flags, vars)?;
|
2017-08-05 18:11:00 +00:00
|
|
|
|
Ok((options, frees))
|
2016-04-17 19:38:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Whether the View specified in this set of options includes a Git
|
|
|
|
|
/// status column. It’s only worth trying to discover a repository if the
|
|
|
|
|
/// results will end up being displayed.
|
|
|
|
|
pub fn should_scan_for_git(&self) -> bool {
|
2017-06-24 21:39:15 +00:00
|
|
|
|
match self.view.mode {
|
2017-07-05 20:01:01 +00:00
|
|
|
|
Mode::Details(details::Options { table: Some(ref table), .. }) |
|
2017-09-01 18:13:47 +00:00
|
|
|
|
Mode::GridDetails(grid_details::Options { details: details::Options { table: Some(ref table), .. }, .. }) => table.extra_columns.git,
|
2016-04-17 19:38:37 +00:00
|
|
|
|
_ => false,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Determines the complete set of options based on the given command-line
|
|
|
|
|
/// arguments, after they’ve been parsed.
|
2017-08-13 10:14:58 +00:00
|
|
|
|
fn deduce<V: Vars>(matches: &MatchedFlags, vars: &V) -> Result<Options, Misfire> {
|
2017-03-31 16:09:32 +00:00
|
|
|
|
let dir_action = DirAction::deduce(matches)?;
|
|
|
|
|
let filter = FileFilter::deduce(matches)?;
|
2017-08-10 16:54:28 +00:00
|
|
|
|
let view = View::deduce(matches, vars)?;
|
2016-04-17 19:38:37 +00:00
|
|
|
|
|
2017-05-18 21:43:32 +00:00
|
|
|
|
Ok(Options { dir_action, view, filter })
|
2016-04-17 19:38:37 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-07-26 16:48:18 +00:00
|
|
|
|
|
2016-04-17 19:38:37 +00:00
|
|
|
|
#[cfg(test)]
|
2017-08-08 08:18:17 +00:00
|
|
|
|
pub mod test {
|
2017-08-26 19:48:51 +00:00
|
|
|
|
use super::{Options, Misfire, flags};
|
2018-12-07 23:43:31 +00:00
|
|
|
|
use crate::options::parser::{Arg, MatchedFlags};
|
2017-07-26 16:48:18 +00:00
|
|
|
|
use std::ffi::OsString;
|
2017-08-10 16:54:28 +00:00
|
|
|
|
|
2017-08-08 08:18:17 +00:00
|
|
|
|
#[derive(PartialEq, Debug)]
|
|
|
|
|
pub enum Strictnesses {
|
|
|
|
|
Last,
|
|
|
|
|
Complain,
|
|
|
|
|
Both,
|
|
|
|
|
}
|
|
|
|
|
|
Be stricter in strict mode
Now the code actually starts to use the Strictness flag that was added in the earlier commit! Well, the *code* doesn’t, but the tests do: the macros that create the test cases now have a parameter for which tests they should run. It’s usually ‘Both’ for both strict mode and default mode, but can be specified to only run in one, for when the results differ (usually when options override one another)
The downside to strict mode is that, now, *any* call to `matches.has` or `matches.get` could fail, because an option could have been specified twice, and this is the place where those are checked for. This makes the code a little less ergonomic in places, but that’s what the ? operator is for. The only place this has really had an effect is in `Classify::deduce`, which used to just return a boolean but can now fail.
In order to more thoroughly test the mode, some of the older parts of the code can now act more strict. For example, `TerminalColours::deduce` will now use the last-given option rather than searching for “colours” before “colors”.
Help and Version continue doing their own thing.
2017-08-09 08:21:29 +00:00
|
|
|
|
/// This function gets used by the other testing modules.
|
|
|
|
|
/// It can run with one or both strictness values: if told to run with
|
|
|
|
|
/// both, then both should resolve to the same result.
|
2017-08-09 12:36:06 +00:00
|
|
|
|
///
|
|
|
|
|
/// It returns a vector with one or two elements in.
|
|
|
|
|
/// These elements can then be tested with assert_eq or what have you.
|
|
|
|
|
pub fn parse_for_test<T, F>(inputs: &[&str], args: &'static [&'static Arg], strictnesses: Strictnesses, get: F) -> Vec<T>
|
|
|
|
|
where F: Fn(&MatchedFlags) -> T
|
2017-08-08 08:18:17 +00:00
|
|
|
|
{
|
|
|
|
|
use self::Strictnesses::*;
|
2018-12-07 23:43:31 +00:00
|
|
|
|
use crate::options::parser::{Args, Strictness};
|
2017-08-08 08:18:17 +00:00
|
|
|
|
|
|
|
|
|
let bits = inputs.into_iter().map(|&o| os(o)).collect::<Vec<OsString>>();
|
2017-10-31 05:24:31 +00:00
|
|
|
|
let mut result = Vec::new();
|
2017-08-08 08:18:17 +00:00
|
|
|
|
|
|
|
|
|
if strictnesses == Last || strictnesses == Both {
|
|
|
|
|
let results = Args(args).parse(bits.iter(), Strictness::UseLastArguments);
|
2017-10-31 05:24:31 +00:00
|
|
|
|
result.push(get(&results.unwrap().flags));
|
2017-08-08 08:18:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if strictnesses == Complain || strictnesses == Both {
|
|
|
|
|
let results = Args(args).parse(bits.iter(), Strictness::ComplainAboutRedundantArguments);
|
2017-10-31 05:24:31 +00:00
|
|
|
|
result.push(get(&results.unwrap().flags));
|
2017-08-08 08:18:17 +00:00
|
|
|
|
}
|
2017-08-09 12:36:06 +00:00
|
|
|
|
|
2017-10-31 05:24:31 +00:00
|
|
|
|
result
|
2017-08-08 08:18:17 +00:00
|
|
|
|
}
|
2016-04-17 19:38:37 +00:00
|
|
|
|
|
2017-07-26 16:48:18 +00:00
|
|
|
|
/// Creates an `OSStr` (used in tests)
|
|
|
|
|
#[cfg(test)]
|
2017-08-08 08:18:17 +00:00
|
|
|
|
fn os(input: &str) -> OsString {
|
2017-07-26 16:48:18 +00:00
|
|
|
|
let mut os = OsString::new();
|
|
|
|
|
os.push(input);
|
|
|
|
|
os
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-17 19:38:37 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn files() {
|
2017-07-26 16:48:18 +00:00
|
|
|
|
let args = [ os("this file"), os("that file") ];
|
2017-08-13 10:14:58 +00:00
|
|
|
|
let outs = Options::parse(&args, &None).unwrap().1;
|
2017-07-26 16:48:18 +00:00
|
|
|
|
assert_eq!(outs, vec![ &os("this file"), &os("that file") ])
|
2016-04-17 19:38:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn no_args() {
|
2017-07-26 16:48:18 +00:00
|
|
|
|
let nothing: Vec<OsString> = Vec::new();
|
2017-08-13 10:14:58 +00:00
|
|
|
|
let outs = Options::parse(¬hing, &None).unwrap().1;
|
2017-07-26 16:48:18 +00:00
|
|
|
|
assert!(outs.is_empty()); // Listing the `.` directory is done in main.rs
|
2016-04-17 19:38:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn long_across() {
|
2017-07-26 16:48:18 +00:00
|
|
|
|
let args = [ os("--long"), os("--across") ];
|
2017-08-13 10:14:58 +00:00
|
|
|
|
let opts = Options::parse(&args, &None);
|
2017-07-26 16:48:18 +00:00
|
|
|
|
assert_eq!(opts.unwrap_err(), Misfire::Useless(&flags::ACROSS, true, &flags::LONG))
|
2016-04-17 19:38:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn oneline_across() {
|
2017-07-26 16:48:18 +00:00
|
|
|
|
let args = [ os("--oneline"), os("--across") ];
|
2017-08-13 10:14:58 +00:00
|
|
|
|
let opts = Options::parse(&args, &None);
|
2017-07-26 16:48:18 +00:00
|
|
|
|
assert_eq!(opts.unwrap_err(), Misfire::Useless(&flags::ACROSS, true, &flags::ONE_LINE))
|
2016-04-17 19:38:37 +00:00
|
|
|
|
}
|
|
|
|
|
}
|