mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-11-16 09:17:09 +00:00
Merge branch 'help!'
This commit is contained in:
commit
763e833b6f
@ -5,28 +5,44 @@ use std::env::args_os;
|
|||||||
use std::io::{stdout, stderr, Write, ErrorKind};
|
use std::io::{stdout, stderr, Write, ErrorKind};
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = args_os().skip(1);
|
let args = args_os().skip(1);
|
||||||
let mut stdout = stdout();
|
match Exa::new(args, &mut stdout()) {
|
||||||
|
|
||||||
match Exa::new(args, &mut stdout) {
|
|
||||||
Ok(mut exa) => {
|
Ok(mut exa) => {
|
||||||
match exa.run() {
|
match exa.run() {
|
||||||
Ok(exit_status) => exit(exit_status),
|
Ok(exit_status) => exit(exit_status),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
match e.kind() {
|
match e.kind() {
|
||||||
ErrorKind::BrokenPipe => exit(0),
|
ErrorKind::BrokenPipe => exit(exits::SUCCESS),
|
||||||
_ => {
|
_ => {
|
||||||
writeln!(stderr(), "{}", e).unwrap();
|
writeln!(stderr(), "{}", e).unwrap();
|
||||||
exit(1);
|
exit(exits::RUNTIME_ERROR);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
Err(e) => {
|
|
||||||
|
Err(ref e) if e.is_error() => {
|
||||||
writeln!(stderr(), "{}", e).unwrap();
|
writeln!(stderr(), "{}", e).unwrap();
|
||||||
exit(e.error_code());
|
exit(exits::OPTIONS_ERROR);
|
||||||
|
},
|
||||||
|
|
||||||
|
Err(ref e) => {
|
||||||
|
writeln!(stdout(), "{}", e).unwrap();
|
||||||
|
exit(exits::SUCCESS);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extern crate libc;
|
||||||
|
#[allow(trivial_numeric_casts)]
|
||||||
|
mod exits {
|
||||||
|
use libc::{self, c_int};
|
||||||
|
|
||||||
|
pub const SUCCESS: c_int = libc::EXIT_SUCCESS;
|
||||||
|
pub const RUNTIME_ERROR: c_int = libc::EXIT_FAILURE;
|
||||||
|
pub const OPTIONS_ERROR: c_int = 3 as c_int;
|
||||||
|
}
|
||||||
|
@ -225,6 +225,11 @@ impl SortField {
|
|||||||
/// argument. This will return `Err` if the option is there, but does not
|
/// argument. This will return `Err` if the option is there, but does not
|
||||||
/// correspond to a valid field.
|
/// correspond to a valid field.
|
||||||
fn deduce(matches: &getopts::Matches) -> Result<SortField, Misfire> {
|
fn deduce(matches: &getopts::Matches) -> Result<SortField, Misfire> {
|
||||||
|
|
||||||
|
const SORTS: &[&str] = &[ "name", "Name", "size", "extension",
|
||||||
|
"Extension", "modified", "accessed",
|
||||||
|
"created", "inode", "none" ];
|
||||||
|
|
||||||
if let Some(word) = matches.opt_str("sort") {
|
if let Some(word) = matches.opt_str("sort") {
|
||||||
match &*word {
|
match &*word {
|
||||||
"name" | "filename" => Ok(SortField::Name(SortCase::Sensitive)),
|
"name" | "filename" => Ok(SortField::Name(SortCase::Sensitive)),
|
||||||
@ -237,10 +242,7 @@ impl SortField {
|
|||||||
"cr" | "created" => Ok(SortField::CreatedDate),
|
"cr" | "created" => Ok(SortField::CreatedDate),
|
||||||
"none" => Ok(SortField::Unsorted),
|
"none" => Ok(SortField::Unsorted),
|
||||||
"inode" => Ok(SortField::FileInode),
|
"inode" => Ok(SortField::FileInode),
|
||||||
field => Err(Misfire::bad_argument("sort", field, &[
|
field => Err(Misfire::bad_argument("sort", field, SORTS))
|
||||||
"name", "Name", "size", "extension", "Extension",
|
|
||||||
"modified", "accessed", "created", "inode", "none"]
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
pub static OPTIONS: &str = r##"
|
|
||||||
|
static OPTIONS: &str = r##"
|
||||||
-?, --help show list of command-line options
|
-?, --help show list of command-line options
|
||||||
-v, --version show version of exa
|
-v, --version show version of exa
|
||||||
|
|
||||||
@ -23,10 +25,9 @@ FILTERING AND SORTING OPTIONS
|
|||||||
-I, --ignore-glob GLOBS glob patterns (pipe-separated) of files to ignore
|
-I, --ignore-glob GLOBS glob patterns (pipe-separated) of files to ignore
|
||||||
Valid sort fields: name, Name, extension, Extension, size,
|
Valid sort fields: name, Name, extension, Extension, size,
|
||||||
modified, accessed, created, inode, none
|
modified, accessed, created, inode, none
|
||||||
|
|
||||||
"##;
|
"##;
|
||||||
|
|
||||||
pub static LONG_OPTIONS: &str = r##"
|
static LONG_OPTIONS: &str = r##"
|
||||||
LONG VIEW OPTIONS
|
LONG VIEW OPTIONS
|
||||||
-b, --binary list file sizes with binary prefixes
|
-b, --binary list file sizes with binary prefixes
|
||||||
-B, --bytes list file sizes in bytes, without any prefixes
|
-B, --bytes list file sizes in bytes, without any prefixes
|
||||||
@ -39,8 +40,36 @@ LONG VIEW OPTIONS
|
|||||||
-S, --blocks show number of file system blocks
|
-S, --blocks show number of file system blocks
|
||||||
-t, --time FIELD which timestamp field to list (modified, accessed, created)
|
-t, --time FIELD which timestamp field to list (modified, accessed, created)
|
||||||
-u, --accessed use the accessed timestamp field
|
-u, --accessed use the accessed timestamp field
|
||||||
-U, --created use the created timestamp field
|
-U, --created use the created timestamp field"##;
|
||||||
"##;
|
|
||||||
|
|
||||||
pub static GIT_HELP: &str = r##" --git list each file's Git status, if tracked"##;
|
static GIT_HELP: &str = r##" --git list each file's Git status, if tracked"##;
|
||||||
pub static EXTENDED_HELP: &str = r##" -@, --extended list each file's extended attributes and sizes"##;
|
static EXTENDED_HELP: &str = r##" -@, --extended list each file's extended attributes and sizes"##;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub struct HelpString {
|
||||||
|
pub only_long: bool,
|
||||||
|
pub git: bool,
|
||||||
|
pub xattrs: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for HelpString {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
try!(write!(f, "Usage:\n exa [options] [files...]\n"));
|
||||||
|
|
||||||
|
if !self.only_long {
|
||||||
|
try!(write!(f, "{}", OPTIONS));
|
||||||
|
}
|
||||||
|
|
||||||
|
try!(write!(f, "{}", LONG_OPTIONS));
|
||||||
|
|
||||||
|
if self.git {
|
||||||
|
try!(write!(f, "\n{}", GIT_HELP));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.xattrs {
|
||||||
|
try!(write!(f, "\n{}", EXTENDED_HELP));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,10 +4,12 @@ use std::num::ParseIntError;
|
|||||||
use getopts;
|
use getopts;
|
||||||
use glob;
|
use glob;
|
||||||
|
|
||||||
|
use options::help::HelpString;
|
||||||
|
|
||||||
|
|
||||||
/// A list of legal choices for an argument-taking option
|
/// A list of legal choices for an argument-taking option
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub struct Choices(Vec<&'static str>);
|
pub struct Choices(&'static [&'static str]);
|
||||||
|
|
||||||
impl fmt::Display for Choices {
|
impl fmt::Display for Choices {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
@ -28,7 +30,7 @@ pub enum Misfire {
|
|||||||
|
|
||||||
/// The user asked for help. This isn’t strictly an error, which is why
|
/// The user asked for help. This isn’t strictly an error, which is why
|
||||||
/// this enum isn’t named Error!
|
/// this enum isn’t named Error!
|
||||||
Help(String),
|
Help(HelpString),
|
||||||
|
|
||||||
/// The user wanted the version number.
|
/// The user wanted the version number.
|
||||||
Version,
|
Version,
|
||||||
@ -54,11 +56,11 @@ pub enum Misfire {
|
|||||||
impl Misfire {
|
impl Misfire {
|
||||||
|
|
||||||
/// The OS return code this misfire should signify.
|
/// The OS return code this misfire should signify.
|
||||||
pub fn error_code(&self) -> i32 {
|
pub fn is_error(&self) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
Misfire::Help(_) => 0,
|
Misfire::Help(_) => false,
|
||||||
Misfire::Version => 0,
|
Misfire::Version => false,
|
||||||
_ => 3,
|
_ => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,10 +68,10 @@ impl Misfire {
|
|||||||
/// argument. This has to use one of the `getopts` failure
|
/// argument. This has to use one of the `getopts` failure
|
||||||
/// variants--it’s meant to take just an option name, rather than an
|
/// variants--it’s meant to take just an option name, rather than an
|
||||||
/// option *and* an argument, but it works just as well.
|
/// option *and* an argument, but it works just as well.
|
||||||
pub fn bad_argument(option: &str, otherwise: &str, legal: &[&'static str]) -> Misfire {
|
pub fn bad_argument(option: &str, otherwise: &str, legal: &'static [&'static str]) -> Misfire {
|
||||||
Misfire::BadArgument(getopts::Fail::UnrecognizedOption(format!(
|
Misfire::BadArgument(getopts::Fail::UnrecognizedOption(format!(
|
||||||
"--{} {}",
|
"--{} {}",
|
||||||
option, otherwise)), Choices(legal.into()))
|
option, otherwise)), Choices(legal))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ mod filter;
|
|||||||
pub use self::filter::{FileFilter, SortField, SortCase};
|
pub use self::filter::{FileFilter, SortField, SortCase};
|
||||||
|
|
||||||
mod help;
|
mod help;
|
||||||
use self::help::*;
|
use self::help::HelpString;
|
||||||
|
|
||||||
mod misfire;
|
mod misfire;
|
||||||
pub use self::misfire::Misfire;
|
pub use self::misfire::Misfire;
|
||||||
@ -103,25 +103,13 @@ impl Options {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if matches.opt_present("help") {
|
if matches.opt_present("help") {
|
||||||
let mut help_string = "Usage:\n exa [options] [files...]\n".to_owned();
|
let help = HelpString {
|
||||||
|
only_long: matches.opt_present("long"),
|
||||||
|
git: cfg!(feature="git"),
|
||||||
|
xattrs: xattr::ENABLED,
|
||||||
|
};
|
||||||
|
|
||||||
if !matches.opt_present("long") {
|
return Err(Misfire::Help(help));
|
||||||
help_string.push_str(OPTIONS);
|
|
||||||
}
|
|
||||||
|
|
||||||
help_string.push_str(LONG_OPTIONS);
|
|
||||||
|
|
||||||
if cfg!(feature="git") {
|
|
||||||
help_string.push_str(GIT_HELP);
|
|
||||||
help_string.push('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
if xattr::ENABLED {
|
|
||||||
help_string.push_str(EXTENDED_HELP);
|
|
||||||
help_string.push('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
return Err(Misfire::Help(help_string));
|
|
||||||
}
|
}
|
||||||
else if matches.opt_present("version") {
|
else if matches.opt_present("version") {
|
||||||
return Err(Misfire::Version);
|
return Err(Misfire::Version);
|
||||||
|
@ -294,12 +294,12 @@ impl TimeTypes {
|
|||||||
return Err(Misfire::Useless("accessed", true, "time"));
|
return Err(Misfire::Useless("accessed", true, "time"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static TIMES: &[& str] = &["modified", "accessed", "created"];
|
||||||
match &*word {
|
match &*word {
|
||||||
"mod" | "modified" => Ok(TimeTypes { accessed: false, modified: true, created: false }),
|
"mod" | "modified" => Ok(TimeTypes { accessed: false, modified: true, created: false }),
|
||||||
"acc" | "accessed" => Ok(TimeTypes { accessed: true, modified: false, created: false }),
|
"acc" | "accessed" => Ok(TimeTypes { accessed: true, modified: false, created: false }),
|
||||||
"cr" | "created" => Ok(TimeTypes { accessed: false, modified: false, created: true }),
|
"cr" | "created" => Ok(TimeTypes { accessed: false, modified: false, created: true }),
|
||||||
otherwise => Err(Misfire::bad_argument("time", otherwise,
|
otherwise => Err(Misfire::bad_argument("time", otherwise, TIMES))
|
||||||
&["modified", "accessed", "created"])),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if modified || created || accessed {
|
else if modified || created || accessed {
|
||||||
@ -342,13 +342,14 @@ impl TerminalColours {
|
|||||||
|
|
||||||
/// Determine which terminal colour conditions to use.
|
/// Determine which terminal colour conditions to use.
|
||||||
fn deduce(matches: &getopts::Matches) -> Result<TerminalColours, Misfire> {
|
fn deduce(matches: &getopts::Matches) -> Result<TerminalColours, Misfire> {
|
||||||
|
const COLOURS: &[&str] = &["always", "auto", "never"];
|
||||||
|
|
||||||
if let Some(word) = matches.opt_str("color").or_else(|| matches.opt_str("colour")) {
|
if let Some(word) = matches.opt_str("color").or_else(|| matches.opt_str("colour")) {
|
||||||
match &*word {
|
match &*word {
|
||||||
"always" => Ok(TerminalColours::Always),
|
"always" => Ok(TerminalColours::Always),
|
||||||
"auto" | "automatic" => Ok(TerminalColours::Automatic),
|
"auto" | "automatic" => Ok(TerminalColours::Automatic),
|
||||||
"never" => Ok(TerminalColours::Never),
|
"never" => Ok(TerminalColours::Never),
|
||||||
otherwise => Err(Misfire::bad_argument("color", otherwise,
|
otherwise => Err(Misfire::bad_argument("color", otherwise, COLOURS))
|
||||||
&["always", "auto", "never"]))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
42
xtests/help
Normal file
42
xtests/help
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
Usage:
|
||||||
|
exa [options] [files...]
|
||||||
|
|
||||||
|
-?, --help show list of command-line options
|
||||||
|
-v, --version show version of exa
|
||||||
|
|
||||||
|
DISPLAY OPTIONS
|
||||||
|
-1, --oneline display one entry per line
|
||||||
|
-l, --long display extended file metadata as a table
|
||||||
|
-G, --grid display entries as a grid (default)
|
||||||
|
-x, --across sort the grid across, rather than downwards
|
||||||
|
-R, --recurse recurse into directories
|
||||||
|
-T, --tree recurse into directories as a tree
|
||||||
|
-F, --classify display type indicator by file names
|
||||||
|
--colo[u]r=WHEN when to use terminal colours (always, auto, never)
|
||||||
|
--colo[u]r-scale highlight levels of file sizes distinctly
|
||||||
|
|
||||||
|
FILTERING AND SORTING OPTIONS
|
||||||
|
-a, --all don't hide hidden and 'dot' files
|
||||||
|
-d, --list-dirs list directories like regular files
|
||||||
|
-r, --reverse reverse the sort order
|
||||||
|
-s, --sort SORT_FIELD which field to sort by:
|
||||||
|
--group-directories-first list directories before other files
|
||||||
|
-I, --ignore-glob GLOBS glob patterns (pipe-separated) of files to ignore
|
||||||
|
Valid sort fields: name, Name, extension, Extension, size,
|
||||||
|
modified, accessed, created, inode, none
|
||||||
|
|
||||||
|
LONG VIEW OPTIONS
|
||||||
|
-b, --binary list file sizes with binary prefixes
|
||||||
|
-B, --bytes list file sizes in bytes, without any prefixes
|
||||||
|
-g, --group list each file's group
|
||||||
|
-h, --header add a header row to each column
|
||||||
|
-H, --links list each file's number of hard links
|
||||||
|
-i, --inode list each file's inode number
|
||||||
|
-L, --level DEPTH limit the depth of recursion
|
||||||
|
-m, --modified use the modified timestamp field
|
||||||
|
-S, --blocks show number of file system blocks
|
||||||
|
-t, --time FIELD which timestamp field to list (modified, accessed, created)
|
||||||
|
-u, --accessed use the accessed timestamp field
|
||||||
|
-U, --created use the created timestamp field
|
||||||
|
--git list each file's Git status, if tracked
|
||||||
|
-@, --extended list each file's extended attributes and sizes
|
18
xtests/help_long
Normal file
18
xtests/help_long
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
Usage:
|
||||||
|
exa [options] [files...]
|
||||||
|
|
||||||
|
LONG VIEW OPTIONS
|
||||||
|
-b, --binary list file sizes with binary prefixes
|
||||||
|
-B, --bytes list file sizes in bytes, without any prefixes
|
||||||
|
-g, --group list each file's group
|
||||||
|
-h, --header add a header row to each column
|
||||||
|
-H, --links list each file's number of hard links
|
||||||
|
-i, --inode list each file's inode number
|
||||||
|
-L, --level DEPTH limit the depth of recursion
|
||||||
|
-m, --modified use the modified timestamp field
|
||||||
|
-S, --blocks show number of file system blocks
|
||||||
|
-t, --time FIELD which timestamp field to list (modified, accessed, created)
|
||||||
|
-u, --accessed use the accessed timestamp field
|
||||||
|
-U, --created use the created timestamp field
|
||||||
|
--git list each file's Git status, if tracked
|
||||||
|
-@, --extended list each file's extended attributes and sizes
|
@ -109,5 +109,8 @@ $exa $testcases/links/* -1 | diff -q - $results/links_1_files || exit 1
|
|||||||
$exa $testcases/git/additions -l --git 2>&1 | diff -q - $results/git_additions || exit 1
|
$exa $testcases/git/additions -l --git 2>&1 | diff -q - $results/git_additions || exit 1
|
||||||
$exa $testcases/git/edits -l --git 2>&1 | diff -q - $results/git_edits || exit 1
|
$exa $testcases/git/edits -l --git 2>&1 | diff -q - $results/git_edits || exit 1
|
||||||
|
|
||||||
|
# And finally...
|
||||||
|
$exa --help | diff -q - $results/help || exit 1
|
||||||
|
$exa --help --long | diff -q - $results/help_long || exit 1
|
||||||
|
|
||||||
echo "All the tests passed!"
|
echo "All the tests passed!"
|
||||||
|
Loading…
Reference in New Issue
Block a user