mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-11-15 16:57:08 +00:00
Restore
This commit is contained in:
parent
6a642d0f32
commit
0e8a4582d0
147
src/fs/file.rs
147
src/fs/file.rs
@ -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 file’s 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 doesn’t 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;
|
||||
|
@ -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 {
|
||||
|
||||
/// Don’t apply any sorting. This is usually used as an optimisation in
|
||||
/// scripts, where the order doesn’t 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 isn’t 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
|
||||
/// don’t parse correctly are returned separately.
|
||||
@ -317,8 +324,8 @@ impl IgnorePatterns {
|
||||
// Almost all glob patterns are valid, so it’s 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 won’t 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 {
|
||||
// isn’t probably means it’s 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"));
|
||||
|
@ -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 doesn’t 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 there’s 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 user’s 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 {
|
||||
// doesn’t 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 {
|
||||
// doesn’t exist.
|
||||
if ! parsing {
|
||||
frees.push(arg)
|
||||
} else if arg == "--" {
|
||||
}
|
||||
else if arg == "--" {
|
||||
parsing = false;
|
||||
}
|
||||
|
||||
// If the string starts with *two* dashes then it’s 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 there’s an equals in it, then the string before the
|
||||
// equals will be the flag’s 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 there’s 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 it’s 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 there’s 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 {
|
||||
// There’s no way to give two values in a cluster like this:
|
||||
// it’s 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 there’s 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, it’s 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 user’s command-line strings.
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct Matches<'args> {
|
||||
|
||||
/// The flags that were parsed from the user’s input.
|
||||
pub flags: MatchedFlags<'args> ,
|
||||
pub flags: MatchedFlags<'args>,
|
||||
|
||||
/// All the strings that weren’t 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 user’s 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 wasn’t, 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 user’s input that meant it couldn’t 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 there’s 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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 file’s 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 column’s 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 user’s computer’s 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 {
|
||||
|
Loading…
Reference in New Issue
Block a user