use std::collections::HashSet;
use std::fmt::Display;
use std::num::ParseIntError;
use std::ops::Sub;
use std::process;
use chrono::{DateTime, Duration, TimeZone, Utc};
use clap::parser::ValuesRef;
use clap::{Arg, ArgAction, ArgMatches, Command};
use clap_complete::Shell;
use indexmap::IndexSet;
use regex::{Match, Regex};
use crate::calendar::Calendar;
use crate::error::{MyError, MyResult};
use crate::metadata::Metadata;
use crate::system::{Entry, Flags, System, EXEC_MASK};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum DirKind {
Asc,
Desc,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum OrderKind {
Dir,
Group,
Name,
Ext,
Size(DirKind),
Time(DirKind),
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum RecentKind {
None,
Sec(i64),
Min(i64),
Hour(i64),
Day(i64),
Week(i64),
Month(i64),
Year(i64),
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum ExecKind {
None,
User,
Other,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum FileKind {
File(ExecKind),
Dir,
Link(bool), Other,
}
pub struct Config {
pub curr_time: DateTime<Utc>,
pub min_depth: Option<usize>,
pub max_depth: Option<usize>,
pub show_indent: bool,
pub all_files: bool,
pub all_recurse: bool,
pub case_sensitive: Option<bool>,
pub order_files: Vec<OrderKind>,
pub order_name: bool,
pub filter_recent: RecentKind,
pub filter_types: Option<HashSet<FileKind>>,
pub show_total: bool,
pub show_pretty: bool,
pub show_utc: bool,
#[cfg(unix)]
pub show_owner: bool,
pub only_path: bool,
pub escape_path: bool,
pub null_path: bool,
pub abs_path: bool,
#[cfg(windows)]
pub win_path: bool,
#[cfg(windows)]
pub win_ver: bool,
pub completion: Option<Shell>,
pub patterns: Vec<String>,
}
impl OrderKind {
fn from(ch: char) -> Option<OrderKind> {
match ch {
'd' => Some(Self::Dir),
'n' => Some(Self::Name),
'e' => Some(Self::Ext),
's' => Some(Self::Size(DirKind::Asc)),
't' => Some(Self::Time(DirKind::Asc)),
_ => None,
}
}
fn ascending(self: Self) -> OrderKind {
match self {
Self::Size(_) => Self::Size(DirKind::Asc),
Self::Time(_) => Self::Time(DirKind::Asc),
any => any,
}
}
fn descending(self: Self) -> OrderKind {
match self {
Self::Size(_) => Self::Size(DirKind::Desc),
Self::Time(_) => Self::Time(DirKind::Desc),
any => any,
}
}
}
impl RecentKind {
fn from(kind: &str, count: i64) -> RecentKind {
match kind {
"h" => Self::Hour(count),
"d" => Self::Day(count),
"w" => Self::Week(count),
"m" => Self::Month(count),
"y" => Self::Year(count),
_ => Self::None,
}
}
}
impl RecentKind {
pub fn subtract_from<Tz: TimeZone>(&self, curr_time: &DateTime<Utc>, zone: &Tz) -> Option<DateTime<Utc>> {
match self {
RecentKind::None => None,
RecentKind::Sec(count) => Some(curr_time.sub(Duration::seconds(*count))),
RecentKind::Min(count) => Some(curr_time.sub(Duration::minutes(*count))),
RecentKind::Hour(count) => Some(curr_time.sub(Duration::hours(*count))),
RecentKind::Day(count) => Some(curr_time.sub(Duration::days(*count))),
RecentKind::Week(count) => Some(curr_time.sub(Duration::weeks(*count))),
RecentKind::Month(count) => Some(Calendar::from_time(curr_time, zone).subtract_month(*count, zone)),
RecentKind::Year(count) => Some(Calendar::from_time(curr_time, zone).subtract_year(*count, zone)),
}
}
pub fn from_times<Tz: TimeZone>(
file_time: &DateTime<Utc>,
curr_time: &DateTime<Utc>,
curr_calendar: &Calendar,
zone: &Tz,
) -> RecentKind {
let delta = curr_time.signed_duration_since(file_time);
let count = delta.num_days();
if count > 0 {
let file_calendar = Calendar::from_time(file_time, zone);
if let Some(count) = file_calendar.num_years_to(&curr_calendar) {
return RecentKind::Year(count);
}
if let Some(count) = file_calendar.num_months_to(&curr_calendar) {
return RecentKind::Month(count);
}
return RecentKind::Day(count);
}
let count = delta.num_hours();
if count > 0 {
return RecentKind::Hour(count);
}
let count = delta.num_minutes();
if count > 0 {
return RecentKind::Min(count);
}
let count = delta.num_seconds();
if count >= 0 {
return RecentKind::Sec(count);
}
return RecentKind::None;
}
}
impl FileKind {
pub fn from_char(file_type: char) -> Option<Vec<FileKind>> {
match file_type {
'f' => Some(vec![
Self::File(ExecKind::None),
Self::File(ExecKind::User),
Self::File(ExecKind::Other),
]),
'd' => Some(vec![Self::Dir]),
'l' => Some(vec![Self::Link(false), Self::Link(true)]),
_ => None,
}
}
#[allow(unused_variables)]
pub fn from_type<F, E, I, S>(system: &S, file_type: F, file_data: &Metadata<F>) -> FileKind where
F: Flags + Copy,
E: Entry<F>,
I: Iterator<Item = MyResult<E>>,
S: System<F, E, I>,
{
if file_type.is_file() {
#[cfg(unix)]
if (file_data.file_mode & system.get_mask(file_data.owner_uid, file_data.owner_gid)) != 0 {
Self::File(ExecKind::User)
} else if (file_data.file_mode & EXEC_MASK) != 0 {
Self::File(ExecKind::Other)
} else {
Self::File(ExecKind::None)
}
#[cfg(not(unix))]
if (file_data.file_mode & EXEC_MASK) != 0 {
Self::File(ExecKind::User)
} else {
Self::File(ExecKind::None)
}
} else if file_type.is_dir() {
Self::Dir
} else if file_type.is_symlink() {
Self::Link(true)
} else {
Self::Other
}
}
}
const RECURSE_SHORT: &'static str = "Find files in subdirectories";
const DEPTH_SHORT: &'static str = "Find files to maximum depth M-N";
const INDENT_SHORT: &'static str = "Indent files in subdirectories";
const ALL_FILES_SHORT: &'static str = "Show .* and __*__ files (twice to recurse)";
const WITH_CASE_SHORT: &'static str = "Force case sensitive match";
const NO_CASE_SHORT: &'static str = "Force case insensitive match";
const ORDER_SHORT: &'static str = "Sort files by order [dnest][+-]...";
const RECENT_SHORT: &'static str = "Include recent files [hdwmy][N]...";
const TYPE_SHORT: &'static str = "Include files by type [fdl]...";
const TOTAL_SHORT: &'static str = "Show total file size";
const UTC_SHORT: &'static str = "Show times in UTC";
#[cfg(unix)]
const OWNER_SHORT: &'static str = "Show user and group";
const ONLY_PATH_SHORT: &'static str = "Show paths only (twice to show all attributes)";
const NULL_PATH_SHORT: &'static str = "Show paths only (with null separator for xargs)";
const ABS_PATH_SHORT: &'static str = "Show absolute paths";
#[cfg(windows)]
const WIN_PATH_SHORT: &'static str = "Show Windows paths";
#[cfg(windows)]
const WIN_VER_SHORT: &'static str = "Show Windows versions";
#[cfg(debug_assertions)]
const NOW_SHORT: &'static str = "Set current time for readme examples";
#[cfg(debug_assertions)]
const TERMINAL_SHORT: &'static str = "Force terminal output for readme examples";
const COMPLETION_SHORT: &'static str = "Create completion script";
const PATTERNS_SHORT: &'static str = "File matching patterns";
const RECURSE_LONG: &'static str = "\
Find files in subdirectories";
const DEPTH_LONG: &'static str = "\
Find files to maximum depth:
Use \"-d4\" or \"-d-4\" to find files up to depth 4
Use \"-d2-4\" to find files at depth 2, 3 or 4
Use \"-d2-\" to find files at depth 2 and beyond";
const INDENT_LONG: &'static str = "\
Indent files in subdirectories";
const ALL_FILES_LONG: &'static str = "\
Show all files:
Use \"-a\" to show hidden files and directories
Use \"-aa\" to recurse into hidden directories
Include Unix hidden files like \".bashrc\"
Include Python cache directories \"__pycache__\"";
const WITH_CASE_LONG: &'static str = "\
Force case sensitive match on Windows";
const NO_CASE_LONG: &'static str = "\
Force case insensitive match on Linux";
const ORDER_LONG: &'static str = "\
Sort files by order:
Use \"-od\" to sort files by directory
Use \"-on\" to sort files by filename
Use \"-oe\" to sort files by extension
Use \"-os\" to sort files by size (increasing)
Use \"-os-\" to sort files by size (decreasing)
Use \"-ot\" to sort files by time (increasing)
Use \"-ot-\" to sort files by time (decreasing)
Use \"-oest\" to sort files by extension then size then time";
const RECENT_LONG: &'static str = "\
Include recent files:
Use \"-rh\" to include one hour old files
Use \"-rd\" to include one day old files
Use \"-rw2\" to include two week old files
Use \"-rm6\" to include six month old files
Use \"-ry10\" to include ten year old files";
const TYPE_LONG: &'static str = "\
Include files by type:
Use \"-tf\" to include files
Use \"-td\" to include directories
Use \"-tl\" to include links";
const TOTAL_LONG: &'static str = "\
Show total file size, and number of files and directories";
const UTC_LONG: &'static str = "\
Show times in UTC";
#[cfg(unix)]
const OWNER_LONG: &'static str = "\
Show user and group on Linux";
const ONLY_PATH_LONG: &'static str = "\
Show paths only:
Use \"-x\" to show paths only
Use \"-xx\" to show all attributes
By default show all attributes when writing to the console
By default show escaped paths when writing to a file";
const NULL_PATH_LONG: &'static str = "\
Show paths only with null separator for xargs";
const ABS_PATH_LONG: &'static str = "\
Show absolute paths";
#[cfg(windows)]
const WIN_PATH_LONG: &'static str = "\
Show Windows paths:
C:\\Path\\file.txt not /c/Path/file.txt in Bash";
#[cfg(windows)]
const WIN_VER_LONG: &'static str = "\
Show Windows versions (*.exe and *.dll only)";
#[cfg(debug_assertions)]
const NOW_LONG: &'static str = "\
Set current time for readme examples, e.g. \"2024-01-01T00:00:00Z\"";
#[cfg(debug_assertions)]
const TERMINAL_LONG: &'static str = "\
Force terminal output for readme examples; disables piped output";
const COMPLETION_LONG: &'static str = "\
Create completion script:
Use \"--completion bash\" to create script for Bash
Use \"--completion ps\" to create script for PowerShell";
const PATTERNS_LONG: &'static str = "\
File matching patterns";
impl Config {
pub fn new(name: String, args: Vec<String>, piped: bool) -> MyResult<Config> {
let mut command = Self::create_command(name.clone());
let matches = Self::create_matches(&mut command, args)?;
let config = Self::create_config(&mut command, matches, piped)?;
if let Some(completion) = config.completion {
Self::create_completion(&mut command, name, completion);
process::exit(1);
}
return Ok(config);
}
pub fn default() -> Config {
Self {
curr_time: Self::create_epoch(),
min_depth: None,
max_depth: None,
show_indent: false,
all_files: false,
all_recurse: false,
case_sensitive: None,
order_files: Vec::new(),
order_name: false,
filter_recent: RecentKind::None,
filter_types: None,
show_total: false,
show_pretty: false,
show_utc: false,
#[cfg(unix)]
show_owner: false,
only_path: false,
escape_path: false,
null_path: false,
abs_path: false,
#[cfg(windows)]
win_path: false,
#[cfg(windows)]
win_ver: false,
completion: None,
patterns: Vec::new(),
}
}
fn create_epoch() -> DateTime<Utc> {
Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap()
}
pub fn start_time<Tz: TimeZone>(&self, zone: &Tz) -> Option<DateTime<Utc>> {
return self.filter_recent.subtract_from(&self.curr_time, zone);
}
fn create_command(name: String) -> Command {
let mut index = 0;
let command = Command::new(name)
.version(clap::crate_version!())
.about(clap::crate_description!())
.author(clap::crate_authors!());
let command = command.arg(Self::create_arg("recurse", &mut index)
.long("recurse")
.short('s')
.action(ArgAction::SetTrue)
.help(RECURSE_SHORT)
.long_help(RECURSE_LONG));
let command = command.arg(Self::create_arg("depth", &mut index)
.long("depth")
.short('d')
.action(ArgAction::Set)
.value_name("DEPTH")
.help(DEPTH_SHORT)
.long_help(DEPTH_LONG));
let command = command.arg(Self::create_arg("indent", &mut index)
.long("indent")
.short('i')
.action(ArgAction::SetTrue)
.help(INDENT_SHORT)
.long_help(INDENT_LONG));
let command = command.arg(Self::create_arg("all-files", &mut index)
.long("all-files")
.short('a')
.action(ArgAction::Count)
.help(ALL_FILES_SHORT)
.long_help(ALL_FILES_LONG));
let command = command.arg(Self::create_arg("case", &mut index)
.long("case")
.action(ArgAction::SetTrue)
.help(WITH_CASE_SHORT)
.long_help(WITH_CASE_LONG));
let command = command.arg(Self::create_arg("no-case", &mut index)
.long("no-case")
.action(ArgAction::SetTrue)
.help(NO_CASE_SHORT)
.long_help(NO_CASE_LONG));
let command = command.arg(Self::create_arg("order", &mut index)
.long("order")
.short('o')
.action(ArgAction::Set)
.value_name("ORDER")
.help(ORDER_SHORT)
.long_help(ORDER_LONG));
let command = command.arg(Self::create_arg("recent", &mut index)
.long("recent")
.short('r')
.action(ArgAction::Set)
.value_name("RECENT")
.help(RECENT_SHORT)
.long_help(RECENT_LONG));
let command = command.arg(Self::create_arg("type", &mut index)
.long("type")
.short('t')
.action(ArgAction::Append)
.value_name("TYPE")
.help(TYPE_SHORT)
.long_help(TYPE_LONG));
let command = command.arg(Self::create_arg("total", &mut index)
.long("total")
.action(ArgAction::SetTrue)
.help(TOTAL_SHORT)
.long_help(TOTAL_LONG));
let command = command.arg(Self::create_arg("utc", &mut index)
.long("utc")
.short('u')
.action(ArgAction::SetTrue)
.help(UTC_SHORT)
.long_help(UTC_LONG));
#[cfg(unix)]
let command = command.arg(Self::create_arg("owner", &mut index)
.long("owner")
.action(ArgAction::SetTrue)
.help(OWNER_SHORT)
.long_help(OWNER_LONG));
let command = command.arg(Self::create_arg("only-path", &mut index)
.long("only-path")
.short('x')
.action(ArgAction::Count)
.help(ONLY_PATH_SHORT)
.long_help(ONLY_PATH_LONG));
let command = command.arg(Self::create_arg("null-path", &mut index)
.long("null-path")
.short('z')
.action(ArgAction::SetTrue)
.help(NULL_PATH_SHORT)
.long_help(NULL_PATH_LONG));
let command = command.arg(Self::create_arg("abs-path", &mut index)
.long("abs-path")
.short('q')
.action(ArgAction::SetTrue)
.help(ABS_PATH_SHORT)
.long_help(ABS_PATH_LONG));
#[cfg(windows)]
let command = command.arg(Self::create_arg("win-path", &mut index)
.long("win-path")
.short('w')
.action(ArgAction::SetTrue)
.help(WIN_PATH_SHORT)
.long_help(WIN_PATH_LONG));
#[cfg(windows)]
let command = command.arg(Self::create_arg("win-ver", &mut index)
.long("win-ver")
.short('v')
.action(ArgAction::SetTrue)
.help(WIN_VER_SHORT)
.long_help(WIN_VER_LONG));
#[cfg(debug_assertions)]
let command = command.arg(Self::create_arg("now", &mut index)
.long("now")
.action(ArgAction::Set)
.value_name("NOW")
.help(NOW_SHORT)
.long_help(NOW_LONG));
#[cfg(debug_assertions)]
let command = command.arg(Self::create_arg("terminal", &mut index)
.long("terminal")
.action(ArgAction::SetTrue)
.help(TERMINAL_SHORT)
.long_help(TERMINAL_LONG));
let command = command.arg(Self::create_arg("completion", &mut index)
.long("completion")
.action(ArgAction::Set)
.value_name("SHELL")
.value_parser(["bash", "ps"])
.hide_possible_values(true)
.help(COMPLETION_SHORT)
.long_help(COMPLETION_LONG));
let command = command.arg(Self::create_arg("patterns", &mut index)
.action(ArgAction::Append)
.default_value(".")
.help(PATTERNS_SHORT)
.long_help(PATTERNS_LONG));
return command;
}
fn create_arg(name: &'static str, index: &mut usize) -> Arg {
*index += 1;
return Arg::new(name).display_order(*index);
}
fn create_matches(command: &mut Command, args: Vec<String>) -> clap::error::Result<ArgMatches> {
match command.try_get_matches_from_mut(args) {
Ok(found) => Ok(found),
Err(error) => match error.kind() {
clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion => {
let error = error.to_string();
let error = error.trim_end();
eprintln!("{error}");
process::exit(1);
},
_ => Err(error),
}
}
}
fn create_config(command: &mut Command, matches: ArgMatches, piped: bool) -> MyResult<Config> {
#[cfg(debug_assertions)]
let curr_time = Self::parse_time(matches.get_one("now"))?;
#[cfg(not(debug_assertions))]
let curr_time = Utc::now();
let (min_depth, max_depth) = Self::parse_depth(command, matches.get_one("depth"), matches.get_flag("recurse"))?;
let show_indent = matches.get_flag("indent");
let (all_files, all_recurse) = Self::parse_all(matches.get_count("all-files"));
let case_sensitive = Self::parse_case(matches.get_flag("case"), matches.get_flag("no-case"));
let (order_files, order_name) = Self::parse_order(command, matches.get_one("order"), max_depth, show_indent)?;
let filter_recent = Self::parse_recent(command, matches.get_one("recent"))?;
let filter_types = Self::parse_types(command, matches.get_many("type"))?;
let show_total = matches.get_flag("total");
let show_utc = matches.get_flag("utc");
#[cfg(unix)]
let show_owner = matches.get_flag("owner");
let only_path = matches.get_count("only-path");
let null_path = matches.get_flag("null-path");
#[cfg(debug_assertions)]
let piped = piped && !matches.get_flag("terminal");
let (show_pretty, only_path, escape_path) = Self::parse_only(only_path, null_path, piped);
let abs_path = matches.get_flag("abs-path");
#[cfg(windows)]
let win_path = matches.get_flag("win-path");
#[cfg(windows)]
let win_ver = matches.get_flag("win-ver");
let completion = Self::parse_completion(command, matches.get_one("completion"))?;
let patterns = Self::parse_values(matches.get_many("patterns"));
let config = Self {
curr_time,
min_depth,
max_depth,
show_indent,
all_files,
all_recurse,
case_sensitive,
order_files,
order_name,
filter_recent,
filter_types,
show_total,
show_pretty,
show_utc,
#[cfg(unix)]
show_owner,
only_path,
escape_path,
null_path,
abs_path,
#[cfg(windows)]
win_path,
#[cfg(windows)]
win_ver,
completion,
patterns,
};
return Ok(config);
}
#[cfg(debug_assertions)]
fn parse_time(value: Option<&String>) -> MyResult<DateTime<Utc>> {
if let Some(value) = value {
let time = DateTime::parse_from_rfc3339(value)?;
return Ok(time.to_utc());
} else {
return Ok(Utc::now());
}
}
fn parse_depth(
command: &mut Command,
value: Option<&String>,
recurse: bool,
) -> MyResult<(Option<usize>, Option<usize>)> {
if recurse {
Ok((Some(1), None))
} else if let Some(value) = value {
match value.parse::<usize>() {
Ok(value) => Ok((Some(1), Some(value + 1))),
Err(_) => {
let re = Regex::new("^(\\d+)?-(\\d+)?$")?;
match re.captures(value) {
Some(captures) => {
let min_depth = Self::parse_integer(captures.get(1))?;
let max_depth = Self::parse_integer(captures.get(2))?;
let min_depth = min_depth.map(|x| x + 1).or(Some(1));
let max_depth = max_depth.map(|x| x + 1);
Ok((min_depth, max_depth))
}
None => Err(Self::make_error(command, "depth", value)),
}
}
}
} else {
Ok((Some(1), Some(1)))
}
}
fn parse_integer(matched: Option<Match>) -> Result<Option<usize>, ParseIntError> {
match matched {
Some(m) => m.as_str().parse().map(|x| Some(x)),
None => Ok(None),
}
}
fn parse_all(all_files: u8) -> (bool, bool) {
match all_files {
0 => (false, false),
1 => (true, false),
_ => (true, true),
}
}
fn parse_case(enabled: bool, disabled: bool) -> Option<bool> {
if enabled {
Some(true)
} else if disabled {
Some(false)
} else {
None
}
}
fn parse_order(
command: &mut Command,
value: Option<&String>,
max_depth: Option<usize>,
show_indent: bool,
) -> MyResult<(Vec<OrderKind>, bool)> {
let mut order_files = IndexSet::new();
let mut order_name = false;
if show_indent {
order_files.insert(OrderKind::Dir);
}
if let Some(value) = value {
let mut order = None;
for ch in value.chars() {
if ch.is_alphabetic() {
if let Some(order) = order {
order_files.insert(order);
}
order = OrderKind::from(ch);
if order == None {
return Err(Self::make_error(command, "order", value));
}
if order == Some(OrderKind::Name) {
order_name = true;
}
} else if ch == '+' {
order = order.map(|x| x.ascending());
} else if ch == '-' {
order = order.map(|x| x.descending());
} else {
return Err(Self::make_error(command, "order", value));
}
}
if let Some(order) = order {
order_files.insert(order);
}
}
if order_files.is_empty() {
if let Some(max_depth) = max_depth {
if max_depth <= 1 {
order_files.insert(OrderKind::Group);
}
}
}
order_files.insert(OrderKind::Dir);
order_files.insert(OrderKind::Name);
return Ok((order_files.into_iter().collect(), order_name));
}
fn parse_recent(command: &mut Command, value: Option<&String>) -> MyResult<RecentKind> {
if let Some(value) = value {
let re = Regex::new("^([hdwmy])(\\d+)?$")?;
return match re.captures(value) {
Some(captures) => {
let count = captures.get(2)
.map(|x| x.as_str())
.map(|x| x.parse())
.unwrap_or(Ok(1))?;
let recent = captures.get(1)
.map(|x| x.as_str())
.map(|x| RecentKind::from(x, count))
.unwrap_or(RecentKind::None);
Ok(recent)
}
None => Err(Self::make_error(command, "recent", value)),
}
}
return Ok(RecentKind::None);
}
fn parse_types(command: &mut Command, values: Option<ValuesRef<String>>) -> MyResult<Option<HashSet<FileKind>>> {
if let Some(values) = values {
let mut results = HashSet::new();
for value in values.flat_map(|x| x.chars()) {
if let Some(result) = FileKind::from_char(value) {
results.extend(result);
} else {
return Err(Self::make_error(command, "type", value));
}
}
return Ok(Some(results));
}
return Ok(None);
}
fn parse_only(only_path: u8, null_path: bool, piped: bool) -> (bool, bool, bool) {
if null_path {
(false, true, false)
} else {
match only_path {
0 => (true, piped, piped),
1 => (false, true, false),
_ => (false, false, false),
}
}
}
fn parse_values(values: Option<ValuesRef<String>>) -> Vec<String> {
values.unwrap_or_default().map(String::to_string).collect()
}
fn parse_completion(command: &mut Command, value: Option<&String>) -> MyResult<Option<Shell>> {
let value = value.map(String::as_ref);
match value {
Some("bash") => Ok(Some(Shell::Bash)),
Some("ps") => Ok(Some(Shell::PowerShell)),
Some(value) => Err(Self::make_error(command, "completion", value)),
None => Ok(None),
}
}
fn create_completion(command: &mut Command, name: String, value: Shell) {
let mut stdout = std::io::stdout();
clap_complete::generate(value, command, name, &mut stdout);
}
fn make_error<T: Display>(command: &mut Command, option: &str, value: T) -> MyError {
let message = format!("Invalid {option} option: {value}");
let error = command.error(clap::error::ErrorKind::ValueValidation, message);
return MyError::Clap(error);
}
}
#[cfg(test)]
#[allow(unexpected_cfgs)]
mod tests {
use std::hash::Hash;
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn test_depth_is_handled() {
let args = vec!["ex"];
let config = create_config(args);
assert_eq!(Some(1), config.min_depth);
assert_eq!(Some(1), config.max_depth);
let args = vec!["ex", "-d4"];
let config = create_config(args);
assert_eq!(Some(1), config.min_depth);
assert_eq!(Some(5), config.max_depth);
let args = vec!["ex", "-d-4"];
let config = create_config(args);
assert_eq!(Some(1), config.min_depth);
assert_eq!(Some(5), config.max_depth);
let args = vec!["ex", "-d2-4"];
let config = create_config(args);
assert_eq!(Some(3), config.min_depth);
assert_eq!(Some(5), config.max_depth);
let args = vec!["ex", "-d2-"];
let config = create_config(args);
assert_eq!(Some(3), config.min_depth);
assert_eq!(None, config.max_depth);
let args = vec!["ex", "-s"];
let config = create_config(args);
assert_eq!(Some(1), config.min_depth);
assert_eq!(None, config.max_depth);
let args = vec!["ex", "-s", "-d4"];
let config = create_config(args);
assert_eq!(Some(1), config.min_depth);
assert_eq!(None, config.max_depth);
let args = vec!["ex", "--recurse"];
let config = create_config(args);
assert_eq!(Some(1), config.min_depth);
assert_eq!(None, config.max_depth);
let args = vec!["ex", "--depth=4"];
let config = create_config(args);
assert_eq!(Some(1), config.min_depth);
assert_eq!(Some(5), config.max_depth);
}
#[test]
fn test_indent_is_handled() {
let args = vec!["ex"];
let config = create_config(args);
assert_eq!(false, config.show_indent);
let args = vec!["ex", "-i"];
let config = create_config(args);
assert_eq!(true, config.show_indent);
let args = vec!["ex", "--indent"];
let config = create_config(args);
assert_eq!(true, config.show_indent);
}
#[test]
fn test_allfiles_is_handled() {
let args = vec!["ex"];
let config = create_config(args);
assert_eq!(false, config.all_files);
assert_eq!(false, config.all_recurse);
let args = vec!["ex", "-a"];
let config = create_config(args);
assert_eq!(true, config.all_files);
assert_eq!(false, config.all_recurse);
let args = vec!["ex", "-aa"];
let config = create_config(args);
assert_eq!(true, config.all_files);
assert_eq!(true, config.all_recurse);
let args = vec!["ex", "-a", "-a"];
let config = create_config(args);
assert_eq!(true, config.all_files);
assert_eq!(true, config.all_recurse);
let args = vec!["ex", "--all-files"];
let config = create_config(args);
assert_eq!(true, config.all_files);
assert_eq!(false, config.all_recurse);
}
#[test]
fn test_case_is_handled() {
let args = vec!["ex"];
let config = create_config(args);
assert_eq!(None, config.case_sensitive);
let args = vec!["ex", "--case"];
let config = create_config(args);
assert_eq!(Some(true), config.case_sensitive);
let args = vec!["ex", "--case", "--no-case"];
let config = create_config(args);
assert_eq!(Some(true), config.case_sensitive);
let args = vec!["ex", "--no-case"];
let config = create_config(args);
assert_eq!(Some(false), config.case_sensitive);
let args = vec!["ex", "--no-case", "--case"];
let config = create_config(args);
assert_eq!(Some(true), config.case_sensitive);
}
#[test]
fn test_order_is_handled() {
let expected = vec![OrderKind::Group, OrderKind::Dir, OrderKind::Name];
let args = vec!["ex"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-od"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Name, OrderKind::Dir];
let args = vec!["ex", "-on"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(true, config.order_name);
let expected = vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-oe"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Size(DirKind::Asc), OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-os"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Size(DirKind::Asc), OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-os+"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Size(DirKind::Desc), OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-os-"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Time(DirKind::Asc), OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-ot"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Time(DirKind::Asc), OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-ot+"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Time(DirKind::Desc), OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-ot-"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Dir, OrderKind::Ext, OrderKind::Size(DirKind::Asc), OrderKind::Time(DirKind::Asc), OrderKind::Name];
let args = vec!["ex", "--order=dest"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Ext, OrderKind::Size(DirKind::Asc), OrderKind::Time(DirKind::Asc), OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "--order=est"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Ext, OrderKind::Size(DirKind::Asc), OrderKind::Time(DirKind::Asc), OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "--order=e+s+t+"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Ext, OrderKind::Size(DirKind::Desc), OrderKind::Time(DirKind::Desc), OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "--order=e-s-t-"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
}
#[test]
fn test_order_defaults_to_group_with_no_recursion() {
let expected = vec![OrderKind::Group, OrderKind::Dir, OrderKind::Name];
let args = vec!["ex"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Group, OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-d0"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-d1"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-d2"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-s"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-oe"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-oe", "-d0"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-oe", "-d1"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-oe", "-d2"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-oe", "-s"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
}
#[test]
fn test_order_defaults_to_directory_with_indent() {
let expected = vec![OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-i"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-i", "-d0"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-i", "-d1"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-i", "-d2"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Dir, OrderKind::Name];
let args = vec!["ex", "-i", "-s"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Dir, OrderKind::Ext, OrderKind::Name];
let args = vec!["ex", "-i", "-oe"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Dir, OrderKind::Ext, OrderKind::Name];
let args = vec!["ex", "-i", "-oe", "-d0"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Dir, OrderKind::Ext, OrderKind::Name];
let args = vec!["ex", "-i", "-oe", "-d1"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Dir, OrderKind::Ext, OrderKind::Name];
let args = vec!["ex", "-i", "-oe", "-d2"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
let expected = vec![OrderKind::Dir, OrderKind::Ext, OrderKind::Name];
let args = vec!["ex", "-i", "-oe", "-s"];
let config = create_config(args);
assert_eq!(expected, config.order_files);
assert_eq!(false, config.order_name);
}
#[test]
fn test_recent_is_handled() {
let args = vec!["ex"];
let config = create_config(args);
assert_eq!(RecentKind::None, config.filter_recent);
let args = vec!["ex", "-rh"];
let config = create_config(args);
assert_eq!(RecentKind::Hour(1), config.filter_recent);
let args = vec!["ex", "-rd"];
let config = create_config(args);
assert_eq!(RecentKind::Day(1), config.filter_recent);
let args = vec!["ex", "-rw2"];
let config = create_config(args);
assert_eq!(RecentKind::Week(2), config.filter_recent);
let args = vec!["ex", "-rm6"];
let config = create_config(args);
assert_eq!(RecentKind::Month(6), config.filter_recent);
let args = vec!["ex", "--recent=y10"];
let config = create_config(args);
assert_eq!(RecentKind::Year(10), config.filter_recent);
}
#[test]
fn test_none_is_subtracted_from_time() {
let now = create_time(2023, 7, 1, 0, 0, 0);
assert_eq!(None, RecentKind::None.subtract_from(&now, &Utc));
}
#[test]
fn test_second_is_subtracted_from_time() {
let now = create_time(2023, 7, 1, 0, 0, 0);
assert_eq!(Some(create_time(2023, 7, 1, 0, 0, 0)), RecentKind::Sec(0).subtract_from(&now, &Utc));
assert_eq!(Some(create_time(2023, 6, 30, 23, 59, 59)), RecentKind::Sec(1).subtract_from(&now, &Utc));
assert_eq!(Some(create_time(2023, 6, 30, 23, 59, 58)), RecentKind::Sec(2).subtract_from(&now, &Utc));
}
#[test]
fn test_minute_is_subtracted_from_time() {
let now = create_time(2023, 7, 1, 0, 0, 0);
assert_eq!(Some(create_time(2023, 7, 1, 0, 0, 0)), RecentKind::Min(0).subtract_from(&now, &Utc));
assert_eq!(Some(create_time(2023, 6, 30, 23, 59, 0)), RecentKind::Min(1).subtract_from(&now, &Utc));
assert_eq!(Some(create_time(2023, 6, 30, 23, 58, 0)), RecentKind::Min(2).subtract_from(&now, &Utc));
}
#[test]
fn test_hour_is_subtracted_from_time() {
let now = create_time(2023, 7, 1, 0, 0, 0);
assert_eq!(Some(create_time(2023, 7, 1, 0, 0, 0)), RecentKind::Hour(0).subtract_from(&now, &Utc));
assert_eq!(Some(create_time(2023, 6, 30, 23, 0, 0)), RecentKind::Hour(1).subtract_from(&now, &Utc));
assert_eq!(Some(create_time(2023, 6, 30, 22, 0, 0)), RecentKind::Hour(2).subtract_from(&now, &Utc));
}
#[test]
fn test_day_is_subtracted_from_time() {
let now = create_time(2023, 7, 1, 0, 0, 0);
assert_eq!(Some(create_time(2023, 7, 1, 0, 0, 0)), RecentKind::Day(0).subtract_from(&now, &Utc));
assert_eq!(Some(create_time(2023, 6, 30, 0, 0, 0)), RecentKind::Day(1).subtract_from(&now, &Utc));
assert_eq!(Some(create_time(2023, 6, 29, 0, 0, 0)), RecentKind::Day(2).subtract_from(&now, &Utc));
}
#[test]
fn test_week_is_subtracted_from_time() {
let now = create_time(2023, 7, 1, 0, 0, 0);
assert_eq!(Some(create_time(2023, 7, 1, 0, 0, 0)), RecentKind::Week(0).subtract_from(&now, &Utc));
assert_eq!(Some(create_time(2023, 6, 24, 0, 0, 0)), RecentKind::Week(1).subtract_from(&now, &Utc));
assert_eq!(Some(create_time(2023, 6, 17, 0, 0, 0)), RecentKind::Week(2).subtract_from(&now, &Utc));
}
#[test]
fn test_month_is_subtracted_from_time() {
let now = create_time(2023, 3, 31, 0, 0, 0);
assert_eq!(Some(create_time(2023, 3, 31, 0, 0, 0)), RecentKind::Month(0).subtract_from(&now, &Utc));
assert_eq!(Some(create_time(2023, 3, 1, 0, 0, 0)), RecentKind::Month(1).subtract_from(&now, &Utc));
assert_eq!(Some(create_time(2023, 1, 31, 0, 0, 0)), RecentKind::Month(2).subtract_from(&now, &Utc));
assert_eq!(Some(create_time(2022, 12, 31, 0, 0, 0)), RecentKind::Month(3).subtract_from(&now, &Utc));
assert_eq!(Some(create_time(2022, 12, 1, 0, 0, 0)), RecentKind::Month(4).subtract_from(&now, &Utc));
assert_eq!(Some(create_time(2022, 10, 31, 0, 0, 0)), RecentKind::Month(5).subtract_from(&now, &Utc));
}
#[test]
fn test_year_is_subtracted_from_time() {
let now = create_time(2024, 2, 29, 0, 0, 0);
assert_eq!(Some(create_time(2024, 2, 29, 0, 0, 0)), RecentKind::Year(0).subtract_from(&now, &Utc));
assert_eq!(Some(create_time(2023, 3, 1, 0, 0, 0)), RecentKind::Year(1).subtract_from(&now, &Utc));
assert_eq!(Some(create_time(2022, 3, 1, 0, 0, 0)), RecentKind::Year(2).subtract_from(&now, &Utc));
assert_eq!(Some(create_time(2021, 3, 1, 0, 0, 0)), RecentKind::Year(3).subtract_from(&now, &Utc));
assert_eq!(Some(create_time(2020, 2, 29, 0, 0, 0)), RecentKind::Year(4).subtract_from(&now, &Utc));
}
#[test]
fn test_type_is_handled() {
let args = vec!["ex"];
let config = create_config(args);
assert_eq!(None, config.filter_types);
let args = vec!["ex", "-tf"];
let config = create_config(args);
let expected = create_set(&[
FileKind::File(ExecKind::None),
FileKind::File(ExecKind::User),
FileKind::File(ExecKind::Other),
]);
assert_eq!(Some(expected), config.filter_types);
let args = vec!["ex", "-td"];
let config = create_config(args);
let expected = create_set(&[FileKind::Dir]);
assert_eq!(Some(expected), config.filter_types);
let args = vec!["ex", "-tf", "-td"];
let config = create_config(args);
let expected = create_set(&[
FileKind::File(ExecKind::None),
FileKind::File(ExecKind::User),
FileKind::File(ExecKind::Other),
FileKind::Dir,
]);
assert_eq!(Some(expected), config.filter_types);
let args = vec!["ex", "-tfd"];
let config = create_config(args);
let expected = create_set(&[
FileKind::File(ExecKind::None),
FileKind::File(ExecKind::User),
FileKind::File(ExecKind::Other),
FileKind::Dir,
]);
assert_eq!(Some(expected), config.filter_types);
let args = vec!["ex", "--type=l"];
let config = create_config(args);
let expected = create_set(&[
FileKind::Link(false),
FileKind::Link(true),
]);
assert_eq!(Some(expected), config.filter_types);
}
#[test]
fn test_total_is_handled() {
let args = vec!["ex"];
let config = create_config(args);
assert_eq!(false, config.show_total);
let args = vec!["ex", "--total"];
let config = create_config(args);
assert_eq!(true, config.show_total);
}
#[test]
fn test_utc_is_handled() {
let args = vec!["ex"];
let config = create_config(args);
assert_eq!(false, config.show_utc);
let args = vec!["ex", "-u"];
let config = create_config(args);
assert_eq!(true, config.show_utc);
let args = vec!["ex", "--utc"];
let config = create_config(args);
assert_eq!(true, config.show_utc);
}
#[test]
#[cfg(unix)]
fn test_owner_is_handled() {
let args = vec!["ex"];
let config = create_config(args);
assert_eq!(false, config.show_owner);
let args = vec!["ex", "--owner"];
let config = create_config(args);
assert_eq!(true, config.show_owner);
}
#[test]
fn test_only_path_is_handled_with_terminal() {
let args = vec!["ex"];
let config = create_config_with_piped(args, false);
assert_eq!(true, config.show_pretty);
assert_eq!(false, config.only_path); assert_eq!(false, config.escape_path); assert_eq!(false, config.null_path);
let args = vec!["ex", "-x"];
let config = create_config_with_piped(args, false);
assert_eq!(false, config.show_pretty);
assert_eq!(true, config.only_path);
assert_eq!(false, config.escape_path);
assert_eq!(false, config.null_path);
let args = vec!["ex", "-x", "-x"];
let config = create_config_with_piped(args, false);
assert_eq!(false, config.show_pretty);
assert_eq!(false, config.only_path);
assert_eq!(false, config.escape_path);
assert_eq!(false, config.null_path);
let args = vec!["ex", "-xx"];
let config = create_config_with_piped(args, false);
assert_eq!(false, config.show_pretty);
assert_eq!(false, config.only_path);
assert_eq!(false, config.escape_path);
assert_eq!(false, config.null_path);
let args = vec!["ex", "--only-path"];
let config = create_config_with_piped(args, false);
assert_eq!(false, config.show_pretty);
assert_eq!(true, config.only_path);
assert_eq!(false, config.escape_path);
assert_eq!(false, config.null_path);
}
#[test]
fn test_only_path_is_handled_with_piped() {
let args = vec!["ex"];
let config = create_config_with_piped(args, true);
assert_eq!(true, config.show_pretty);
assert_eq!(true, config.only_path); assert_eq!(true, config.escape_path); assert_eq!(false, config.null_path);
let args = vec!["ex", "-x"];
let config = create_config_with_piped(args, true);
assert_eq!(false, config.show_pretty);
assert_eq!(true, config.only_path);
assert_eq!(false, config.escape_path);
assert_eq!(false, config.null_path);
let args = vec!["ex", "-x", "-x"];
let config = create_config_with_piped(args, true);
assert_eq!(false, config.show_pretty);
assert_eq!(false, config.only_path);
assert_eq!(false, config.escape_path);
assert_eq!(false, config.null_path);
let args = vec!["ex", "-xx"];
let config = create_config_with_piped(args, true);
assert_eq!(false, config.show_pretty);
assert_eq!(false, config.only_path);
assert_eq!(false, config.escape_path);
assert_eq!(false, config.null_path);
let args = vec!["ex", "--only-path"];
let config = create_config_with_piped(args, true);
assert_eq!(false, config.show_pretty);
assert_eq!(true, config.only_path);
assert_eq!(false, config.escape_path);
assert_eq!(false, config.null_path);
}
#[test]
#[cfg(debug_assertions)]
fn test_only_path_is_handled_with_override() {
let args = vec!["ex", "--terminal"];
let config = create_config_with_piped(args, true);
assert_eq!(true, config.show_pretty);
assert_eq!(false, config.only_path); assert_eq!(false, config.escape_path); assert_eq!(false, config.null_path);
let args = vec!["ex", "--terminal", "-x"];
let config = create_config_with_piped(args, true);
assert_eq!(false, config.show_pretty);
assert_eq!(true, config.only_path);
assert_eq!(false, config.escape_path);
assert_eq!(false, config.null_path);
let args = vec!["ex", "--terminal", "-x", "-x"];
let config = create_config_with_piped(args, true);
assert_eq!(false, config.show_pretty);
assert_eq!(false, config.only_path);
assert_eq!(false, config.escape_path);
assert_eq!(false, config.null_path);
let args = vec!["ex", "--terminal", "-xx"];
let config = create_config_with_piped(args, true);
assert_eq!(false, config.show_pretty);
assert_eq!(false, config.only_path);
assert_eq!(false, config.escape_path);
assert_eq!(false, config.null_path);
let args = vec!["ex", "--terminal", "--only-path"];
let config = create_config_with_piped(args, true);
assert_eq!(false, config.show_pretty);
assert_eq!(true, config.only_path);
assert_eq!(false, config.escape_path);
assert_eq!(false, config.null_path);
}
#[test]
fn test_null_path_is_handled() {
let args = vec!["ex"];
let config = create_config_with_piped(args, false);
assert_eq!(true, config.show_pretty);
assert_eq!(false, config.only_path);
assert_eq!(false, config.escape_path);
assert_eq!(false, config.null_path);
let args = vec!["ex", "-z"];
let config = create_config_with_piped(args, false);
assert_eq!(false, config.show_pretty);
assert_eq!(true, config.only_path);
assert_eq!(false, config.escape_path);
assert_eq!(true, config.null_path);
let args = vec!["ex", "--null-path"];
let config = create_config_with_piped(args, false);
assert_eq!(false, config.show_pretty);
assert_eq!(true, config.only_path);
assert_eq!(false, config.escape_path);
assert_eq!(true, config.null_path);
}
#[test]
fn test_abs_path_is_handled() {
let args = vec!["ex"];
let config = create_config(args);
assert_eq!(false, config.abs_path);
let args = vec!["ex", "-q"];
let config = create_config(args);
assert_eq!(true, config.abs_path);
let args = vec!["ex", "--abs-path"];
let config = create_config(args);
assert_eq!(true, config.abs_path);
}
#[test]
#[cfg(windows)]
fn test_win_path_is_handled() {
let args = vec!["ex"];
let config = create_config(args);
assert_eq!(false, config.win_path);
let args = vec!["ex", "-w"];
let config = create_config(args);
assert_eq!(true, config.win_path);
let args = vec!["ex", "--win-path"];
let config = create_config(args);
assert_eq!(true, config.win_path);
}
#[test]
#[cfg(windows)]
fn test_win_ver_is_handled() {
let args = vec!["ex"];
let config = create_config(args);
assert_eq!(false, config.win_ver);
let args = vec!["ex", "-v"];
let config = create_config(args);
assert_eq!(true, config.win_ver);
let args = vec!["ex", "--win-ver"];
let config = create_config(args);
assert_eq!(true, config.win_ver);
}
#[test]
fn test_patterns_are_handled() {
let expected = vec!["."];
let args = vec!["ex"];
let config = create_config(args);
assert_eq!(expected, config.patterns);
let expected = vec!["file1"];
let args = vec!["ex", "file1"];
let config = create_config(args);
assert_eq!(expected, config.patterns);
let expected = vec!["file1", "file2"];
let args = vec!["ex", "file1", "file2"];
let config = create_config(args);
assert_eq!(expected, config.patterns);
}
#[test]
#[should_panic(expected = "Invalid depth option: foo")]
fn test_unexpected_depth_causes_error() {
let args = vec!["ex", "-dfoo"];
create_config(args);
}
#[test]
#[should_panic(expected = "Invalid order option: foo")]
fn test_unexpected_order_causes_error() {
let args = vec!["ex", "-ofoo"];
create_config(args);
}
#[test]
#[should_panic(expected = "Invalid recent option: foo")]
fn test_unexpected_recent_causes_error() {
let args = vec!["ex", "-rfoo"];
create_config(args);
}
#[test]
#[should_panic(expected = "Invalid type option: x")]
fn test_unexpected_type_causes_error() {
let args = vec!["ex", "-tfxd"];
create_config(args);
}
fn create_config(args: Vec<&str>) -> Config {
create_config_with_piped(args, false)
}
fn create_config_with_piped(args: Vec<&str>, piped: bool) -> Config {
let mut command = Config::create_command(String::from("ex"));
let args = args.into_iter().map(String::from).collect();
let matches = Config::create_matches(&mut command, args).unwrap_or_else(handle_error);
let config = Config::create_config(&mut command, matches, piped).unwrap_or_else(handle_error);
return config;
}
fn create_time(year: i32, month: u32, day: u32, hour: u32, minute: u32, second: u32) -> DateTime<Utc> {
let date = NaiveDate::from_ymd_opt(year, month, day).unwrap();
let time = NaiveTime::from_hms_opt(hour, minute, second).unwrap();
return DateTime::from_naive_utc_and_offset(NaiveDateTime::new(date, time), Utc);
}
fn create_set<T: Clone + Eq + Hash>(values: &[T]) -> HashSet<T> {
return values.iter().map(T::clone).collect();
}
fn handle_error<T, E: Display>(err: E)-> T {
panic!("{}", err);
}
impl Config {
pub fn with_curr_time(
mut self,
year: i32,
month: u32,
day: u32,
hour: u32,
min: u32,
sec: u32,
) -> Config {
self.curr_time = Utc.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap();
return self;
}
pub fn with_min_depth(mut self, min_depth: usize) -> Config {
self.min_depth = Some(min_depth);
return self;
}
pub fn with_max_depth(mut self, max_depth: usize) -> Config {
self.max_depth = Some(max_depth);
return self;
}
pub fn with_show_indent(mut self, show_indent: bool) -> Config {
self.show_indent = show_indent;
return self;
}
#[cfg(disabled)]
pub fn with_all_files(mut self, all_files: bool) -> Config {
self.all_files = all_files;
return self;
}
#[cfg(disabled)]
pub fn with_all_recurse(mut self, all_recurse: bool) -> Config {
self.all_recurse = all_recurse;
return self;
}
pub fn with_case_sensitive(mut self, case_sensitive: bool) -> Config {
self.case_sensitive = Some(case_sensitive);
return self;
}
pub fn with_order_files(mut self, order_files: Vec<OrderKind>) -> Config {
self.order_files = order_files;
return self;
}
pub fn with_order_name(mut self, order_name: bool) -> Config {
self.order_name = order_name;
return self;
}
pub fn with_filter_recent(mut self, filter_recent: RecentKind) -> Config {
self.filter_recent = filter_recent;
return self;
}
pub fn with_filter_types(mut self, filter_types: Vec<FileKind>) -> Config {
self.filter_types = Some(filter_types.into_iter().collect());
return self;
}
pub fn with_show_total(mut self, show_total: bool) -> Config {
self.show_total = show_total;
return self;
}
pub fn with_show_pretty(mut self, show_pretty: bool) -> Config {
self.show_pretty = show_pretty;
return self;
}
pub fn with_show_utc(mut self, show_utc: bool) -> Config {
self.show_utc = show_utc;
return self;
}
#[cfg(unix)]
pub fn with_show_owner(mut self, show_owner: bool) -> Config {
self.show_owner = show_owner;
return self;
}
pub fn with_only_path(mut self, only_path: bool) -> Config {
self.only_path = only_path;
return self;
}
pub fn with_escape_path(mut self, escape_path: bool) -> Config {
self.escape_path = escape_path;
return self;
}
#[cfg(disabled)]
pub fn with_null_path(mut self, null_path: bool) -> Config {
self.null_path = null_path;
return self;
}
pub fn with_abs_path(mut self, abs_path: bool) -> Config {
self.abs_path = abs_path;
return self;
}
#[cfg(windows)]
#[cfg(disabled)]
pub fn with_win_path(mut self, win_path: bool) -> Config {
self.win_path = win_path;
return self;
}
#[cfg(windows)]
pub fn with_win_ver(mut self, win_ver: bool) -> Config {
self.win_ver = win_ver;
return self;
}
#[cfg(disabled)]
pub fn with_completion(mut self, completion: Shell) -> Config {
self.completion = Some(completion);
return self;
}
pub fn with_patterns(mut self, patterns: Vec<&str>) -> Config {
self.patterns = patterns.into_iter().map(String::from).collect();
return self;
}
}
}