This commit is contained in:
Chester Liu 2021-03-26 16:50:34 +08:00
parent 6a642d0f32
commit 0e8a4582d0
4 changed files with 198 additions and 177 deletions

View File

@ -71,21 +71,14 @@ impl<'dir> File<'dir> {
FN: Into<Option<String>>
{
let parent_dir = parent_dir.into();
let name = filename.into().unwrap_or_else(|| File::filename(&path));
let ext = File::ext(&path);
let name = filename.into().unwrap_or_else(|| File::filename(&path));
let ext = File::ext(&path);
debug!("Statting file {:?}", &path);
let metadata = std::fs::symlink_metadata(&path)?;
let metadata = std::fs::symlink_metadata(&path)?;
let is_all_all = false;
Ok(File {
path,
parent_dir,
metadata,
ext,
name,
is_all_all,
})
Ok(File { path, parent_dir, metadata, ext, name, is_all_all })
}
pub fn new_aa_current(parent_dir: &'dir Dir) -> io::Result<File<'dir>> {
@ -93,7 +86,7 @@ impl<'dir> File<'dir> {
let ext = File::ext(&path);
debug!("Statting file {:?}", &path);
let metadata = std::fs::symlink_metadata(&path)?;
let metadata = std::fs::symlink_metadata(&path)?;
let is_all_all = true;
let parent_dir = Some(parent_dir);
@ -116,12 +109,9 @@ impl<'dir> File<'dir> {
/// use the last component as the name.
pub fn filename(path: &Path) -> String {
if let Some(back) = path.components().next_back() {
let name = back.as_os_str().to_string_lossy().to_string();
#[cfg(unix)]
return name;
#[cfg(windows)]
return name;
} else {
back.as_os_str().to_string_lossy().to_string()
}
else {
// use the path as fallback
error!("Path {:?} has no last component", path);
path.display().to_string()
@ -225,11 +215,14 @@ impl<'dir> File<'dir> {
fn reorient_target_path(&self, path: &Path) -> PathBuf {
if path.is_absolute() {
path.to_path_buf()
} else if let Some(dir) = self.parent_dir {
}
else if let Some(dir) = self.parent_dir {
dir.join(&*path)
} else if let Some(parent) = self.path.parent() {
}
else if let Some(parent) = self.path.parent() {
parent.join(&*path)
} else {
}
else {
self.path.join(&*path)
}
}
@ -245,14 +238,15 @@ impl<'dir> File<'dir> {
/// existed. If this file cannot be read at all, returns the error that
/// we got when we tried to read it.
pub fn link_target(&self) -> FileTarget<'dir> {
// We need to be careful to treat the path actually pointed to by
// this file — which could be absolute or relative — to the path
// we actually look up and turn into a `File` — which needs to be
// absolute to be accessible from any directory.
debug!("Reading link {:?}", &self.path);
let path = match std::fs::read_link(&self.path) {
Ok(p) => p,
Err(e) => return FileTarget::Err(e),
Ok(p) => p,
Err(e) => return FileTarget::Err(e),
};
let absolute_path = self.reorient_target_path(&path);
@ -261,7 +255,7 @@ impl<'dir> File<'dir> {
// follow links.
match std::fs::metadata(&absolute_path) {
Ok(metadata) => {
let ext = File::ext(&path);
let ext = File::ext(&path);
let name = File::filename(&path);
let file = File { parent_dir: None, path, ext, metadata, name, is_all_all: false };
FileTarget::Ok(Box::new(file))
@ -303,7 +297,8 @@ impl<'dir> File<'dir> {
pub fn blocks(&self) -> f::Blocks {
if self.is_file() || self.is_link() {
f::Blocks::Some(self.metadata.blocks())
} else {
}
else {
f::Blocks::None
}
}
@ -330,19 +325,19 @@ impl<'dir> File<'dir> {
/// usually just have a file size of zero.
pub fn size(&self) -> f::Size {
if self.is_directory() {
return f::Size::None;
};
#[cfg(unix)]
{
if self.is_char_device() || self.is_block_device() {
let dev = self.metadata.rdev();
return f::Size::DeviceIDs(f::DeviceIDs {
major: (dev / 256) as u8,
minor: (dev % 256) as u8,
});
};
f::Size::None
}
#[cfg(unix)]
else if self.is_char_device() || self.is_block_device() {
let dev = self.metadata.rdev();
f::Size::DeviceIDs(f::DeviceIDs {
major: (dev / 256) as u8,
minor: (dev % 256) as u8,
})
}
else {
f::Size::Some(self.metadata.len())
}
return f::Size::Some(self.metadata.len());
}
/// This files last modified timestamp, if available on this platform.
@ -389,9 +384,11 @@ impl<'dir> File<'dir> {
pub fn type_char(&self) -> f::Type {
if self.is_file() {
f::Type::File
} else if self.is_directory() {
}
else if self.is_directory() {
f::Type::Directory
} else {
}
else {
f::Type::Special
}
}
@ -399,19 +396,26 @@ impl<'dir> File<'dir> {
pub fn type_char(&self) -> f::Type {
if self.is_file() {
f::Type::File
} else if self.is_directory() {
}
else if self.is_directory() {
f::Type::Directory
} else if self.is_pipe() {
}
else if self.is_pipe() {
f::Type::Pipe
} else if self.is_link() {
}
else if self.is_link() {
f::Type::Link
} else if self.is_char_device() {
}
else if self.is_char_device() {
f::Type::CharDevice
} else if self.is_block_device() {
}
else if self.is_block_device() {
f::Type::BlockDevice
} else if self.is_socket() {
}
else if self.is_socket() {
f::Type::Socket
} else {
}
else {
f::Type::Special
}
}
@ -423,21 +427,21 @@ impl<'dir> File<'dir> {
let has_bit = |bit| bits & bit == bit;
f::Permissions {
user_read: has_bit(modes::USER_READ),
user_write: has_bit(modes::USER_WRITE),
user_execute: has_bit(modes::USER_EXECUTE),
user_read: has_bit(modes::USER_READ),
user_write: has_bit(modes::USER_WRITE),
user_execute: has_bit(modes::USER_EXECUTE),
group_read: has_bit(modes::GROUP_READ),
group_write: has_bit(modes::GROUP_WRITE),
group_execute: has_bit(modes::GROUP_EXECUTE),
group_read: has_bit(modes::GROUP_READ),
group_write: has_bit(modes::GROUP_WRITE),
group_execute: has_bit(modes::GROUP_EXECUTE),
other_read: has_bit(modes::OTHER_READ),
other_write: has_bit(modes::OTHER_WRITE),
other_execute: has_bit(modes::OTHER_EXECUTE),
other_read: has_bit(modes::OTHER_READ),
other_write: has_bit(modes::OTHER_WRITE),
other_execute: has_bit(modes::OTHER_EXECUTE),
sticky: has_bit(modes::STICKY),
setgid: has_bit(modes::SETGID),
setuid: has_bit(modes::SETUID),
sticky: has_bit(modes::STICKY),
setgid: has_bit(modes::SETGID),
setuid: has_bit(modes::SETUID),
}
}
@ -458,14 +462,17 @@ impl<'dir> File<'dir> {
}
}
impl<'a> AsRef<File<'a>> for File<'a> {
fn as_ref(&self) -> &File<'a> {
self
}
}
/// The result of following a symlink.
pub enum FileTarget<'dir> {
/// The symlink pointed at a file that exists.
Ok(Box<File<'dir>>),
@ -484,6 +491,7 @@ pub enum FileTarget<'dir> {
}
impl<'dir> FileTarget<'dir> {
/// Whether this link doesnt lead to a file, for whatever reason. This
/// gets used to determine how to highlight the link in grid views.
pub fn is_broken(&self) -> bool {
@ -491,6 +499,7 @@ impl<'dir> FileTarget<'dir> {
}
}
/// More readable aliases for the permission bits exposed by libc.
#[allow(trivial_numeric_casts)]
#[cfg(unix)]
@ -500,23 +509,24 @@ mod modes {
// from `metadata.permissions().mode()` is always `u32`.
pub type Mode = u32;
pub const USER_READ: Mode = libc::S_IRUSR as Mode;
pub const USER_WRITE: Mode = libc::S_IWUSR as Mode;
pub const USER_EXECUTE: Mode = libc::S_IXUSR as Mode;
pub const USER_READ: Mode = libc::S_IRUSR as Mode;
pub const USER_WRITE: Mode = libc::S_IWUSR as Mode;
pub const USER_EXECUTE: Mode = libc::S_IXUSR as Mode;
pub const GROUP_READ: Mode = libc::S_IRGRP as Mode;
pub const GROUP_WRITE: Mode = libc::S_IWGRP as Mode;
pub const GROUP_READ: Mode = libc::S_IRGRP as Mode;
pub const GROUP_WRITE: Mode = libc::S_IWGRP as Mode;
pub const GROUP_EXECUTE: Mode = libc::S_IXGRP as Mode;
pub const OTHER_READ: Mode = libc::S_IROTH as Mode;
pub const OTHER_WRITE: Mode = libc::S_IWOTH as Mode;
pub const OTHER_READ: Mode = libc::S_IROTH as Mode;
pub const OTHER_WRITE: Mode = libc::S_IWOTH as Mode;
pub const OTHER_EXECUTE: Mode = libc::S_IXOTH as Mode;
pub const STICKY: Mode = libc::S_ISVTX as Mode;
pub const SETGID: Mode = libc::S_ISGID as Mode;
pub const SETUID: Mode = libc::S_ISUID as Mode;
pub const STICKY: Mode = libc::S_ISVTX as Mode;
pub const SETGID: Mode = libc::S_ISGID as Mode;
pub const SETUID: Mode = libc::S_ISUID as Mode;
}
#[cfg(test)]
mod ext_test {
use super::File;
@ -538,6 +548,7 @@ mod ext_test {
}
}
#[cfg(test)]
mod filename_test {
use super::File;

View File

@ -26,6 +26,7 @@ use crate::fs::File;
/// performing the comparison.
#[derive(PartialEq, Debug, Clone)]
pub struct FileFilter {
/// Whether directories should be listed first, and other types of file
/// second. Some users prefer it like this.
pub list_dirs_first: bool,
@ -137,9 +138,11 @@ impl FileFilter {
}
}
/// User-supplied field to sort by.
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum SortField {
/// Dont apply any sorting. This is usually used as an optimisation in
/// scripts, where the order doesnt matter.
Unsorted,
@ -221,6 +224,7 @@ pub enum SortField {
/// effects they have.
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum SortCase {
/// Sort files case-sensitively with uppercase first, with A coming
/// before a.
ABCabc,
@ -230,6 +234,7 @@ pub enum SortCase {
}
impl SortField {
/// Compares two files to determine the order they should be listed in,
/// depending on the search field.
///
@ -289,6 +294,7 @@ impl SortField {
}
}
/// The **ignore patterns** are a list of globs that are tested against
/// each filename, and if any of them match, that file isnt displayed.
/// This lets a user hide, say, text files by ignoring `*.txt`.
@ -308,6 +314,7 @@ impl FromIterator<glob::Pattern> for IgnorePatterns {
}
impl IgnorePatterns {
/// Create a new list from the input glob strings, turning the inputs that
/// are valid glob patterns into an `IgnorePatterns`. The inputs that
/// dont parse correctly are returned separately.
@ -317,8 +324,8 @@ impl IgnorePatterns {
// Almost all glob patterns are valid, so its worth pre-allocating
// the vector with enough space for all of them.
let mut patterns = match iter.size_hint() {
(_, Some(count)) => Vec::with_capacity(count),
_ => Vec::new(),
(_, Some(count)) => Vec::with_capacity(count),
_ => Vec::new(),
};
// Similarly, assume there wont be any errors.
@ -327,7 +334,7 @@ impl IgnorePatterns {
for input in iter {
match glob::Pattern::new(input) {
Ok(pat) => patterns.push(pat),
Err(e) => errors.push(e),
Err(e) => errors.push(e),
}
}
@ -353,9 +360,11 @@ impl IgnorePatterns {
// isnt probably means its in the wrong place
}
/// Whether to ignore or display files that are mentioned in `.gitignore` files.
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum GitIgnore {
/// Ignore files that Git would ignore. This means doing a check for a
/// `.gitignore` file, possibly recursively up the filesystem tree.
CheckAndIgnore,
@ -384,23 +393,23 @@ mod test_ignores {
#[test]
fn ignores_a_glob() {
let (pats, fails) = IgnorePatterns::parse_from_iter(vec!["*.mp3"]);
let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "*.mp3" ]);
assert!(fails.is_empty());
assert_eq!(false, pats.is_ignored("nothing"));
assert_eq!(true, pats.is_ignored("test.mp3"));
assert_eq!(true, pats.is_ignored("test.mp3"));
}
#[test]
fn ignores_an_exact_filename() {
let (pats, fails) = IgnorePatterns::parse_from_iter(vec!["nothing"]);
let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "nothing" ]);
assert!(fails.is_empty());
assert_eq!(true, pats.is_ignored("nothing"));
assert_eq!(true, pats.is_ignored("nothing"));
assert_eq!(false, pats.is_ignored("test.mp3"));
}
#[test]
fn ignores_both() {
let (pats, fails) = IgnorePatterns::parse_from_iter(vec!["nothing", "*.mp3"]);
let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "nothing", "*.mp3" ]);
assert!(fails.is_empty());
assert_eq!(true, pats.is_ignored("nothing"));
assert_eq!(true, pats.is_ignored("test.mp3"));

View File

@ -27,7 +27,7 @@
//! command-line options, as all the options and their values (such as
//! `--sort size`) are guaranteed to just be 8-bit ASCII.
use std::borrow::Cow;
use std::ffi::{OsStr, OsString};
use std::fmt;
@ -79,6 +79,7 @@ impl fmt::Display for Flag {
/// Whether redundant arguments should be considered a problem.
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum Strictness {
/// Throw an error when an argument doesnt do anything, either because
/// it requires another argument to be specified, or because two conflict.
ComplainAboutRedundantArguments,
@ -92,6 +93,7 @@ pub enum Strictness {
/// arguments.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum TakesValue {
/// This flag has to be followed by a value.
/// If theres a fixed set of possible values, they can be printed out
/// with the error text.
@ -104,9 +106,11 @@ pub enum TakesValue {
Optional(Option<Values>),
}
/// An **argument** can be matched by one of the users input strings.
#[derive(PartialEq, Debug, Copy, Clone)]
pub struct Arg {
/// The short argument that matches it, if any.
pub short: Option<ShortArg>,
@ -130,20 +134,19 @@ impl fmt::Display for Arg {
}
}
/// Literally just several args.
#[derive(PartialEq, Debug)]
pub struct Args(pub &'static [&'static Arg]);
impl Args {
/// Iterates over the given list of command-line arguments and parses
/// them into a list of matched flags and free strings.
pub fn parse<'args, I>(&self, inputs: I, strictness: Strictness) -> Result<Matches<'args>, ParseError>
where I: IntoIterator<Item = &'args OsStr>
{
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;
#[cfg(windows)]
use os_str_bytes::{OsStrBytes, OsStringBytes};
let mut parsing = true;
@ -156,7 +159,7 @@ impl Args {
// doesnt have one in its string so it needs the next one.
let mut inputs = inputs.into_iter();
while let Some(arg) = inputs.next() {
let bytes = arg.to_bytes();
let bytes = arg.as_bytes();
// Stop parsing if one of the arguments is the literal string “--”.
// This allows a file named “--arg” to be specified by passing in
@ -164,18 +167,20 @@ impl Args {
// doesnt exist.
if ! parsing {
frees.push(arg)
} else if arg == "--" {
}
else if arg == "--" {
parsing = false;
}
// If the string starts with *two* dashes then its a long argument.
else if bytes.starts_with(b"--") {
let long_arg_name = OsStrBytes::from_bytes(&bytes[2..]).unwrap();
let long_arg_name = OsStr::from_bytes(&bytes[2..]);
// If theres an equals in it, then the string before the
// equals will be the flags name, and the string after it
// will be its value.
if let Some((before, after)) = split_on_equals(&long_arg_name) {
let arg = self.lookup_long(&*before)?;
if let Some((before, after)) = split_on_equals(long_arg_name) {
let arg = self.lookup_long(before)?;
let flag = Flag::Long(arg.long);
match arg.takes_value {
TakesValue::Necessary(_) |
@ -183,10 +188,11 @@ impl Args {
TakesValue::Forbidden => return Err(ParseError::ForbiddenValue { flag }),
}
}
// If theres no equals, then the entire string (apart from
// the dashes) is the argument name.
else {
let arg = self.lookup_long(&*long_arg_name)?;
let arg = self.lookup_long(long_arg_name)?;
let flag = Flag::Long(arg.long);
match arg.takes_value {
TakesValue::Forbidden => {
@ -194,25 +200,28 @@ impl Args {
}
TakesValue::Necessary(values) => {
if let Some(next_arg) = inputs.next() {
result_flags.push((flag, Some(next_arg.clone())));
} else {
return Err(ParseError::NeedsValue { flag, values });
result_flags.push((flag, Some(next_arg)));
}
else {
return Err(ParseError::NeedsValue { flag, values })
}
}
TakesValue::Optional(_) => {
if let Some(next_arg) = inputs.next() {
result_flags.push((flag, Some(next_arg.clone())));
} else {
result_flags.push((flag, Some(next_arg)));
}
else {
result_flags.push((flag, None));
}
}
}
}
}
// If the string starts with *one* dash then its one or more
// short arguments.
else if bytes.starts_with(b"-") && arg != "-" {
let short_arg = OsStr::from_bytes(&bytes[1..]).unwrap();
let short_arg = OsStr::from_bytes(&bytes[1..]);
// If theres an equals in it, then the argument immediately
// before the equals was the one that has the value, with the
@ -226,9 +235,8 @@ impl Args {
// Theres no way to give two values in a cluster like this:
// its an error if any of the first set of arguments actually
// takes a value.
if let Some((before, after)) = split_on_equals(&short_arg) {
let bytes = before.to_bytes();
let (arg_with_value, other_args) = bytes.split_last().unwrap();
if let Some((before, after)) = split_on_equals(short_arg) {
let (arg_with_value, other_args) = before.as_bytes().split_last().unwrap();
// Process the characters immediately following the dash...
for byte in other_args {
@ -258,6 +266,7 @@ impl Args {
}
}
}
// If theres no equals, then every character is parsed as
// its own short argument. However, if any of the arguments
// takes a value, then the *rest* of the string is used as
@ -281,15 +290,14 @@ impl Args {
TakesValue::Necessary(values) |
TakesValue::Optional(values) => {
if index < bytes.len() - 1 {
let remnants = &bytes[index + 1..];
result_flags.push((
flag,
Some(OsStringBytes::from_bytes(remnants).unwrap()),
));
let remnants = &bytes[index+1 ..];
result_flags.push((flag, Some(OsStr::from_bytes(remnants))));
break;
} else if let Some(next_arg) = inputs.next() {
result_flags.push((flag, Some(next_arg.clone())));
} else {
}
else if let Some(next_arg) = inputs.next() {
result_flags.push((flag, Some(next_arg)));
}
else {
match arg.takes_value {
TakesValue::Forbidden => {
unreachable!()
@ -307,19 +315,14 @@ impl Args {
}
}
}
// Otherwise, its a free string, usually a file name.
else {
frees.push(arg)
}
}
Ok(Matches {
frees,
flags: MatchedFlags {
flags: result_flags,
strictness,
},
})
Ok(Matches { frees, flags: MatchedFlags { flags: result_flags, strictness } })
}
fn lookup_short(&self, short: ShortArg) -> Result<&Arg, ParseError> {
@ -337,19 +340,22 @@ impl Args {
}
}
/// The **matches** are the result of parsing the users command-line strings.
#[derive(PartialEq, Debug)]
pub struct Matches<'args> {
/// The flags that were parsed from the users input.
pub flags: MatchedFlags<'args> ,
pub flags: MatchedFlags<'args>,
/// All the strings that werent matched as arguments, as well as anything
/// after the special "--" string.
/// after the special “--” string.
pub frees: Vec<&'args OsStr>,
}
#[derive(PartialEq, Debug)]
pub struct MatchedFlags {
pub struct MatchedFlags<'args> {
/// The individual flags from the users input, in the order they were
/// originally given.
///
@ -362,7 +368,8 @@ pub struct MatchedFlags {
strictness: Strictness,
}
impl MatchedFlags {
impl<'a> MatchedFlags<'a> {
/// Whether the given argument was specified.
/// Returns `true` if it was, `false` if it wasnt, and an error in
/// strict mode if it was specified more than once.
@ -442,8 +449,7 @@ impl MatchedFlags {
/// Counts the number of occurrences of the given argument, even in
/// strict mode.
pub fn count(&self, arg: &Arg) -> usize {
self.flags
.iter()
self.flags.iter()
.filter(|tuple| tuple.0.matches(arg))
.count()
}
@ -455,10 +461,12 @@ impl MatchedFlags {
}
}
/// A problem with the users input that meant it couldnt be parsed into a
/// coherent list of arguments.
#[derive(PartialEq, Debug)]
pub enum ParseError {
/// A flag that has to take a value was not given one.
NeedsValue { flag: Flag, values: Option<Values> },
@ -487,12 +495,14 @@ impl fmt::Display for ParseError {
}
}
/// Splits a string on its `=` character, returning the two substrings on
/// either side. Returns `None` if theres no equals or a string is missing.
fn split_on_equals<'a>(input: &'a Cow<OsStr>) -> Option<(OsString, OsString)> {
if let Some(index) = input.to_bytes().iter().position(|elem| *elem == b'=') {
let bytes = input.to_bytes();
let (before, after) = bytes.split_at(index);
fn split_on_equals(input: &OsStr) -> Option<(&OsStr, &OsStr)> {
use std::os::unix::ffi::OsStrExt;
if let Some(index) = input.as_bytes().iter().position(|elem| *elem == b'=') {
let (before, after) = input.as_bytes().split_at(index);
// The after string contains the = that we need to remove.
if ! before.is_empty() && after.len() >= 2 {
@ -540,6 +550,7 @@ mod split_test {
test_split!(more: "this=that=other" => "this", "that=other");
}
#[cfg(test)]
mod parse_test {
use super::*;
@ -580,31 +591,16 @@ mod parse_test {
};
}
const SUGGESTIONS: Values = &["example"];
const SUGGESTIONS: Values = &[ "example" ];
static TEST_ARGS: &[&Arg] = &[
&Arg {
short: Some(b'l'),
long: "long",
takes_value: TakesValue::Forbidden,
},
&Arg {
short: Some(b'v'),
long: "verbose",
takes_value: TakesValue::Forbidden,
},
&Arg {
short: Some(b'c'),
long: "count",
takes_value: TakesValue::Necessary(None),
},
&Arg {
short: Some(b't'),
long: "type",
takes_value: TakesValue::Necessary(Some(SUGGESTIONS)),
},
&Arg { short: Some(b'l'), long: "long", takes_value: TakesValue::Forbidden },
&Arg { short: Some(b'v'), long: "verbose", takes_value: TakesValue::Forbidden },
&Arg { short: Some(b'c'), long: "count", takes_value: TakesValue::Necessary(None) },
&Arg { short: Some(b't'), long: "type", takes_value: TakesValue::Necessary(Some(SUGGESTIONS)) }
];
// Just filenames
test!(empty: [] => frees: [], flags: []);
test!(one_arg: ["exa"] => frees: [ "exa" ], flags: []);
@ -616,6 +612,7 @@ mod parse_test {
test!(two_arg_l: ["--", "--long"] => frees: [ "--long" ], flags: []);
test!(two_arg_s: ["--", "-l"] => frees: [ "-l" ], flags: []);
// Long args
test!(long: ["--long"] => frees: [], flags: [ (Flag::Long("long"), None) ]);
test!(long_then: ["--long", "4"] => frees: [ "4" ], flags: [ (Flag::Long("long"), None) ]);
@ -624,13 +621,14 @@ mod parse_test {
// Long args with values
test!(bad_equals: ["--long=equals"] => error ForbiddenValue { flag: Flag::Long("long") });
test!(no_arg: ["--count"] => error NeedsValue { flag: Flag::Long("count"), values: None });
test!(arg_equals: ["--count=4"] => frees: [], flags: [ (Flag::Long("count"), Some(OsString::from("4"))) ]);
test!(arg_then: ["--count", "4"] => frees: [], flags: [ (Flag::Long("count"), Some(OsString::from("4"))) ]);
test!(arg_equals: ["--count=4"] => frees: [], flags: [ (Flag::Long("count"), Some(OsStr::new("4"))) ]);
test!(arg_then: ["--count", "4"] => frees: [], flags: [ (Flag::Long("count"), Some(OsStr::new("4"))) ]);
// Long args with values and suggestions
test!(no_arg_s: ["--type"] => error NeedsValue { flag: Flag::Long("type"), values: Some(SUGGESTIONS) });
test!(arg_equals_s: ["--type=exa"] => frees: [], flags: [ (Flag::Long("type"), Some(OsString::from("exa"))) ]);
test!(arg_then_s: ["--type", "exa"] => frees: [], flags: [ (Flag::Long("type"), Some(OsString::from("exa"))) ]);
test!(arg_equals_s: ["--type=exa"] => frees: [], flags: [ (Flag::Long("type"), Some(OsStr::new("exa"))) ]);
test!(arg_then_s: ["--type", "exa"] => frees: [], flags: [ (Flag::Long("type"), Some(OsStr::new("exa"))) ]);
// Short args
test!(short: ["-l"] => frees: [], flags: [ (Flag::Short(b'l'), None) ]);
@ -641,17 +639,18 @@ mod parse_test {
// Short args with values
test!(bad_short: ["-l=equals"] => error ForbiddenValue { flag: Flag::Short(b'l') });
test!(short_none: ["-c"] => error NeedsValue { flag: Flag::Short(b'c'), values: None });
test!(short_arg_eq: ["-c=4"] => frees: [], flags: [(Flag::Short(b'c'), Some(OsString::from("4"))) ]);
test!(short_arg_then: ["-c", "4"] => frees: [], flags: [(Flag::Short(b'c'), Some(OsString::from("4"))) ]);
test!(short_two_together: ["-lctwo"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsString::from("two"))) ]);
test!(short_two_equals: ["-lc=two"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsString::from("two"))) ]);
test!(short_two_next: ["-lc", "two"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsString::from("two"))) ]);
test!(short_arg_eq: ["-c=4"] => frees: [], flags: [(Flag::Short(b'c'), Some(OsStr::new("4"))) ]);
test!(short_arg_then: ["-c", "4"] => frees: [], flags: [(Flag::Short(b'c'), Some(OsStr::new("4"))) ]);
test!(short_two_together: ["-lctwo"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]);
test!(short_two_equals: ["-lc=two"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]);
test!(short_two_next: ["-lc", "two"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]);
// Short args with values and suggestions
test!(short_none_s: ["-t"] => error NeedsValue { flag: Flag::Short(b't'), values: Some(SUGGESTIONS) });
test!(short_two_together_s: ["-texa"] => frees: [], flags: [(Flag::Short(b't'), Some(OsString::from("exa"))) ]);
test!(short_two_equals_s: ["-t=exa"] => frees: [], flags: [(Flag::Short(b't'), Some(OsString::from("exa"))) ]);
test!(short_two_next_s: ["-t", "exa"] => frees: [], flags: [(Flag::Short(b't'), Some(OsString::from("exa"))) ]);
test!(short_two_together_s: ["-texa"] => frees: [], flags: [(Flag::Short(b't'), Some(OsStr::new("exa"))) ]);
test!(short_two_equals_s: ["-t=exa"] => frees: [], flags: [(Flag::Short(b't'), Some(OsStr::new("exa"))) ]);
test!(short_two_next_s: ["-t", "exa"] => frees: [], flags: [(Flag::Short(b't'), Some(OsStr::new("exa"))) ]);
// Unknown args
test!(unknown_long: ["--quiet"] => error UnknownArgument { attempt: OsString::from("quiet") });
@ -662,6 +661,7 @@ mod parse_test {
test!(unknown_short_2nd_eq: ["-lq=shhh"] => error UnknownShortArgument { attempt: b'q' });
}
#[cfg(test)]
mod matches_test {
use super::*;
@ -680,16 +680,9 @@ mod matches_test {
};
}
static VERBOSE: Arg = Arg {
short: Some(b'v'),
long: "verbose",
takes_value: TakesValue::Forbidden,
};
static COUNT: Arg = Arg {
short: Some(b'c'),
long: "count",
takes_value: TakesValue::Necessary(None),
};
static VERBOSE: Arg = Arg { short: Some(b'v'), long: "verbose", takes_value: TakesValue::Forbidden };
static COUNT: Arg = Arg { short: Some(b'c'), long: "count", takes_value: TakesValue::Necessary(None) };
test!(short_never: [], has VERBOSE => false);
test!(short_once: [(Flag::Short(b'v'), None)], has VERBOSE => true);
@ -698,16 +691,17 @@ mod matches_test {
test!(long_twice: [(Flag::Long("verbose"), None), (Flag::Long("verbose"), None)], has VERBOSE => true);
test!(long_mixed: [(Flag::Long("verbose"), None), (Flag::Short(b'v'), None)], has VERBOSE => true);
#[test]
fn only_count() {
let everything = OsString::from("everything");
let flags = MatchedFlags {
flags: vec![(Flag::Short(b'c'), Some(everything.clone()))],
flags: vec![ (Flag::Short(b'c'), Some(&*everything)) ],
strictness: Strictness::UseLastArguments,
};
assert_eq!(flags.get(&COUNT), Ok(Some(everything)));
assert_eq!(flags.get(&COUNT), Ok(Some(&*everything)));
}
#[test]
@ -716,23 +710,18 @@ mod matches_test {
let nothing = OsString::from("nothing");
let flags = MatchedFlags {
flags: vec![
(Flag::Short(b'c'), Some(everything)),
(Flag::Short(b'c'), Some(nothing.clone())),
],
flags: vec![ (Flag::Short(b'c'), Some(&*everything)),
(Flag::Short(b'c'), Some(&*nothing)) ],
strictness: Strictness::UseLastArguments,
};
assert_eq!(flags.get(&COUNT), Ok(Some(nothing)));
assert_eq!(flags.get(&COUNT), Ok(Some(&*nothing)));
}
#[test]
fn no_count() {
let flags = MatchedFlags {
flags: Vec::new(),
strictness: Strictness::UseLastArguments,
};
let flags = MatchedFlags { flags: Vec::new(), strictness: Strictness::UseLastArguments };
assert_eq!(flags.has(&COUNT).unwrap(), false);
}
}
}

View File

@ -49,6 +49,7 @@ pub struct Columns {
}
impl Columns {
pub fn collect(&self, actually_enable_git: bool) -> Vec<Column> {
let mut columns = Vec::with_capacity(4);
@ -118,6 +119,7 @@ impl Columns {
}
}
/// A table contains these.
#[derive(Debug, Copy, Clone)]
pub enum Column {
@ -149,6 +151,7 @@ pub enum Alignment {
}
impl Column {
/// Get the alignment this column should use.
#[cfg(unix)]
pub fn alignment(self) -> Alignment {
@ -195,9 +198,11 @@ impl Column {
}
}
/// Formatting options for file sizes.
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum SizeFormat {
/// Format the file size using **decimal** prefixes, such as “kilo”,
/// “mega”, or “giga”.
DecimalBytes,
@ -216,6 +221,7 @@ impl Default for SizeFormat {
}
}
/// The types of a files time fields. These three fields are standard
/// across most (all?) operating systems.
#[derive(PartialEq, Debug, Copy, Clone)]
@ -235,6 +241,7 @@ pub enum TimeType {
}
impl TimeType {
/// Returns the text to use for a columns heading in the columns output.
pub fn header(self) -> &'static str {
match self {
@ -255,12 +262,13 @@ impl TimeType {
#[allow(clippy::struct_excessive_bools)]
pub struct TimeTypes {
pub modified: bool,
pub changed: bool,
pub changed: bool,
pub accessed: bool,
pub created: bool,
pub created: bool,
}
impl Default for TimeTypes {
/// By default, display just the modified time. This is the most
/// common option, which is why it has this shorthand.
fn default() -> Self {
@ -273,11 +281,13 @@ impl Default for TimeTypes {
}
}
/// The **environment** struct contains any data that could change between
/// running instances of exa, depending on the users computers configuration.
///
/// Any environment field should be able to be mocked up for test runs.
pub struct Environment {
/// Localisation rules for formatting numbers.
numeric: locale::Numeric,
@ -330,6 +340,7 @@ lazy_static! {
static ref ENVIRONMENT: Environment = Environment::load_all();
}
pub struct Table<'a> {
columns: Vec<Column>,
theme: &'a Theme,
@ -488,6 +499,7 @@ impl<'a, 'f> Table<'a> {
}
}
pub struct TableWidths(Vec<usize>);
impl Deref for TableWidths {