From 0e8a4582d05219b4fb571b263201ade6d9c5a0c4 Mon Sep 17 00:00:00 2001 From: Chester Liu Date: Fri, 26 Mar 2021 16:50:34 +0800 Subject: [PATCH] Restore --- src/fs/file.rs | 147 ++++++++++++++++++--------------- src/fs/filter.rs | 25 ++++-- src/options/parser.rs | 187 ++++++++++++++++++++---------------------- src/output/table.rs | 16 +++- 4 files changed, 198 insertions(+), 177 deletions(-) diff --git a/src/fs/file.rs b/src/fs/file.rs index 269815e..2ef3ff4 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -71,21 +71,14 @@ impl<'dir> File<'dir> { FN: Into> { 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> { @@ -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> 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>), @@ -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; diff --git a/src/fs/filter.rs b/src/fs/filter.rs index d444bc5..ea99700 100644 --- a/src/fs/filter.rs +++ b/src/fs/filter.rs @@ -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 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")); diff --git a/src/options/parser.rs b/src/options/parser.rs index c53f064..34a7ef8 100644 --- a/src/options/parser.rs +++ b/src/options/parser.rs @@ -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), } + /// 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, @@ -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, ParseError> where I: IntoIterator { - #[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 }, @@ -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) -> 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); } -} +} \ No newline at end of file diff --git a/src/output/table.rs b/src/output/table.rs index 22d69fb..aa1ed1c 100644 --- a/src/output/table.rs +++ b/src/output/table.rs @@ -49,6 +49,7 @@ pub struct Columns { } impl Columns { + pub fn collect(&self, actually_enable_git: bool) -> Vec { 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, theme: &'a Theme, @@ -488,6 +499,7 @@ impl<'a, 'f> Table<'a> { } } + pub struct TableWidths(Vec); impl Deref for TableWidths {