mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
refactor(touch): Move to clap + add a test (#1629)
This commit is contained in:
parent
5efaa0bf32
commit
bfba889f8e
4 changed files with 129 additions and 93 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2160,8 +2160,8 @@ dependencies = [
|
||||||
name = "uu_touch"
|
name = "uu_touch"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"filetime 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
"filetime 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"uucore 0.0.4",
|
"uucore 0.0.4",
|
||||||
"uucore_procs 0.0.4",
|
"uucore_procs 0.0.4",
|
||||||
|
|
|
@ -16,7 +16,7 @@ path = "src/touch.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
filetime = "0.2.1"
|
filetime = "0.2.1"
|
||||||
getopts = "0.2.18"
|
clap = "2.33"
|
||||||
time = "0.1.40"
|
time = "0.1.40"
|
||||||
uucore = { version=">=0.0.4", package="uucore", path="../../uucore", features=["libc"] }
|
uucore = { version=">=0.0.4", package="uucore", path="../../uucore", features=["libc"] }
|
||||||
uucore_procs = { version=">=0.0.4", package="uucore_procs", path="../../uucore_procs" }
|
uucore_procs = { version=">=0.0.4", package="uucore_procs", path="../../uucore_procs" }
|
||||||
|
|
|
@ -8,20 +8,31 @@
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) filetime strptime utcoff strs datetime MMDDhhmm
|
// spell-checker:ignore (ToDO) filetime strptime utcoff strs datetime MMDDhhmm
|
||||||
|
|
||||||
|
extern crate clap;
|
||||||
pub extern crate filetime;
|
pub extern crate filetime;
|
||||||
extern crate getopts;
|
|
||||||
extern crate time;
|
extern crate time;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate uucore;
|
extern crate uucore;
|
||||||
|
|
||||||
|
use clap::{App, Arg};
|
||||||
use filetime::*;
|
use filetime::*;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::Error;
|
use std::io::Error;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
static NAME: &str = "touch";
|
|
||||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
static ABOUT: &str = "Update the access and modification times of each FILE to the current time.";
|
||||||
|
static OPT_ACCESS: &str = "access";
|
||||||
|
static OPT_CURRENT: &str = "current";
|
||||||
|
static OPT_DATE: &str = "date";
|
||||||
|
static OPT_MODIFICATION: &str = "modification";
|
||||||
|
static OPT_NO_CREATE: &str = "no-create";
|
||||||
|
static OPT_NO_DEREF: &str = "no-dereference";
|
||||||
|
static OPT_REFERENCE: &str = "reference";
|
||||||
|
static OPT_TIME: &str = "time";
|
||||||
|
|
||||||
|
static ARG_FILES: &str = "files";
|
||||||
|
|
||||||
// Since touch's date/timestamp parsing doesn't account for timezone, the
|
// Since touch's date/timestamp parsing doesn't account for timezone, the
|
||||||
// returned value from time::strptime() is UTC. We get system's timezone to
|
// returned value from time::strptime() is UTC. We get system's timezone to
|
||||||
|
@ -41,97 +52,108 @@ macro_rules! local_tm_to_filetime(
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
fn get_usage() -> String {
|
||||||
|
format!("{0} [OPTION]... [USER]", executable!())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
let args = args.collect_str();
|
let usage = get_usage();
|
||||||
|
|
||||||
let mut opts = getopts::Options::new();
|
let matches = App::new(executable!())
|
||||||
|
.version(VERSION)
|
||||||
|
.about(ABOUT)
|
||||||
|
.usage(&usage[..])
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(OPT_ACCESS)
|
||||||
|
.short("a")
|
||||||
|
.help("change only the access time"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(OPT_CURRENT)
|
||||||
|
.short("t")
|
||||||
|
.help("use [[CC]YY]MMDDhhmm[.ss] instead of the current time")
|
||||||
|
.value_name("STAMP")
|
||||||
|
.takes_value(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(OPT_DATE)
|
||||||
|
.short("d")
|
||||||
|
.long(OPT_DATE)
|
||||||
|
.help("parse argument and use it instead of current time")
|
||||||
|
.value_name("STRING"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(OPT_MODIFICATION)
|
||||||
|
.short("m")
|
||||||
|
.help("change only the modification time"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(OPT_NO_CREATE)
|
||||||
|
.short("c")
|
||||||
|
.long(OPT_NO_CREATE)
|
||||||
|
.help("do not create any files"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(OPT_NO_DEREF)
|
||||||
|
.short("h")
|
||||||
|
.long(OPT_NO_DEREF)
|
||||||
|
.help(
|
||||||
|
"affect each symbolic link instead of any referenced file \
|
||||||
|
(only for systems that can change the timestamps of a symlink)",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(OPT_REFERENCE)
|
||||||
|
.short("r")
|
||||||
|
.long(OPT_REFERENCE)
|
||||||
|
.help("use this file's times instead of the current time")
|
||||||
|
.value_name("FILE"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(OPT_TIME)
|
||||||
|
.long(OPT_TIME)
|
||||||
|
.help(
|
||||||
|
"change only the specified time: \"access\", \"atime\", or \
|
||||||
|
\"use\" are equivalent to -a; \"modify\" or \"mtime\" are \
|
||||||
|
equivalent to -m",
|
||||||
|
)
|
||||||
|
.value_name("WORD")
|
||||||
|
.possible_values(&["access", "atime", "use"])
|
||||||
|
.takes_value(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(ARG_FILES)
|
||||||
|
.multiple(true)
|
||||||
|
.takes_value(true)
|
||||||
|
.min_values(1),
|
||||||
|
)
|
||||||
|
.get_matches_from(args);
|
||||||
|
|
||||||
opts.optflag("a", "", "change only the access time");
|
let files: Vec<String> = matches
|
||||||
opts.optflag("c", "no-create", "do not create any files");
|
.values_of(ARG_FILES)
|
||||||
opts.optopt(
|
.map(|v| v.map(ToString::to_string).collect())
|
||||||
"d",
|
.unwrap_or_default();
|
||||||
"date",
|
|
||||||
"parse argument and use it instead of current time",
|
|
||||||
"STRING",
|
|
||||||
);
|
|
||||||
opts.optflag(
|
|
||||||
"h",
|
|
||||||
"no-dereference",
|
|
||||||
"affect each symbolic link instead of any referenced file \
|
|
||||||
(only for systems that can change the timestamps of a symlink)",
|
|
||||||
);
|
|
||||||
opts.optflag("m", "", "change only the modification time");
|
|
||||||
opts.optopt(
|
|
||||||
"r",
|
|
||||||
"reference",
|
|
||||||
"use this file's times instead of the current time",
|
|
||||||
"FILE",
|
|
||||||
);
|
|
||||||
opts.optopt(
|
|
||||||
"t",
|
|
||||||
"",
|
|
||||||
"use [[CC]YY]MMDDhhmm[.ss] instead of the current time",
|
|
||||||
"STAMP",
|
|
||||||
);
|
|
||||||
opts.optopt(
|
|
||||||
"",
|
|
||||||
"time",
|
|
||||||
"change only the specified time: \"access\", \"atime\", or \
|
|
||||||
\"use\" are equivalent to -a; \"modify\" or \"mtime\" are \
|
|
||||||
equivalent to -m",
|
|
||||||
"WORD",
|
|
||||||
);
|
|
||||||
opts.optflag("h", "help", "display this help and exit");
|
|
||||||
opts.optflag("V", "version", "output version information and exit");
|
|
||||||
|
|
||||||
let matches = match opts.parse(&args[1..]) {
|
if matches.is_present(OPT_DATE)
|
||||||
Ok(m) => m,
|
&& (matches.is_present(OPT_REFERENCE) || matches.is_present(OPT_CURRENT))
|
||||||
Err(e) => panic!("Invalid options\n{}", e),
|
|| matches.is_present(OPT_REFERENCE)
|
||||||
};
|
&& (matches.is_present(OPT_DATE) || matches.is_present(OPT_CURRENT))
|
||||||
|
|| matches.is_present(OPT_CURRENT)
|
||||||
if matches.opt_present("version") {
|
&& (matches.is_present(OPT_DATE) || matches.is_present(OPT_REFERENCE))
|
||||||
println!("{} {}", NAME, VERSION);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches.opt_present("help") || matches.free.is_empty() {
|
|
||||||
println!("{} {}", NAME, VERSION);
|
|
||||||
println!();
|
|
||||||
println!("Usage: {} [OPTION]... FILE...", NAME);
|
|
||||||
println!();
|
|
||||||
println!(
|
|
||||||
"{}",
|
|
||||||
opts.usage(
|
|
||||||
"Update the access and modification times of \
|
|
||||||
each FILE to the current time."
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if matches.free.is_empty() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches.opt_present("date")
|
|
||||||
&& matches.opts_present(&["reference".to_owned(), "t".to_owned()])
|
|
||||||
|| matches.opt_present("reference")
|
|
||||||
&& matches.opts_present(&["date".to_owned(), "t".to_owned()])
|
|
||||||
|| matches.opt_present("t")
|
|
||||||
&& matches.opts_present(&["date".to_owned(), "reference".to_owned()])
|
|
||||||
{
|
{
|
||||||
panic!("Invalid options: cannot specify reference time from more than one source");
|
panic!("Invalid options: cannot specify reference time from more than one source");
|
||||||
}
|
}
|
||||||
|
|
||||||
let (mut atime, mut mtime) = if matches.opt_present("reference") {
|
let (mut atime, mut mtime) = if matches.is_present(OPT_REFERENCE) {
|
||||||
stat(
|
stat(
|
||||||
&matches.opt_str("reference").unwrap()[..],
|
&matches.value_of(OPT_REFERENCE).unwrap()[..],
|
||||||
!matches.opt_present("no-dereference"),
|
!matches.is_present(OPT_NO_DEREF),
|
||||||
)
|
)
|
||||||
} else if matches.opts_present(&["date".to_owned(), "t".to_owned()]) {
|
} else if matches.is_present(OPT_DATE) || matches.is_present(OPT_CURRENT) {
|
||||||
let timestamp = if matches.opt_present("date") {
|
let timestamp = if matches.is_present(OPT_DATE) {
|
||||||
parse_date(matches.opt_str("date").unwrap().as_ref())
|
parse_date(matches.value_of(OPT_DATE).unwrap().as_ref())
|
||||||
} else {
|
} else {
|
||||||
parse_timestamp(matches.opt_str("t").unwrap().as_ref())
|
parse_timestamp(matches.value_of(OPT_CURRENT).unwrap().as_ref())
|
||||||
};
|
};
|
||||||
(timestamp, timestamp)
|
(timestamp, timestamp)
|
||||||
} else {
|
} else {
|
||||||
|
@ -139,12 +161,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
(now, now)
|
(now, now)
|
||||||
};
|
};
|
||||||
|
|
||||||
for filename in &matches.free {
|
for filename in &files {
|
||||||
let path = &filename[..];
|
let path = &filename[..];
|
||||||
|
|
||||||
if !Path::new(path).exists() {
|
if !Path::new(path).exists() {
|
||||||
// no-dereference included here for compatibility
|
// no-dereference included here for compatibility
|
||||||
if matches.opts_present(&["no-create".to_owned(), "no-dereference".to_owned()]) {
|
if matches.is_present(OPT_NO_CREATE) || matches.is_present(OPT_NO_DEREF) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,18 +176,24 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Minor optimization: if no reference time was specified, we're done.
|
// Minor optimization: if no reference time was specified, we're done.
|
||||||
if !matches.opts_present(&["date".to_owned(), "reference".to_owned(), "t".to_owned()]) {
|
if !(matches.is_present(OPT_DATE)
|
||||||
|
|| matches.is_present(OPT_REFERENCE)
|
||||||
|
|| matches.is_present(OPT_CURRENT))
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If changing "only" atime or mtime, grab the existing value of the other.
|
// If changing "only" atime or mtime, grab the existing value of the other.
|
||||||
// Note that "-a" and "-m" may be passed together; this is not an xor.
|
// Note that "-a" and "-m" may be passed together; this is not an xor.
|
||||||
if matches.opts_present(&["a".to_owned(), "m".to_owned(), "time".to_owned()]) {
|
if matches.is_present(OPT_ACCESS)
|
||||||
let st = stat(path, !matches.opt_present("no-dereference"));
|
|| matches.is_present(OPT_MODIFICATION)
|
||||||
let time = matches.opt_strs("time");
|
|| matches.is_present(OPT_TIME)
|
||||||
|
{
|
||||||
|
let st = stat(path, !matches.is_present(OPT_NO_DEREF));
|
||||||
|
let time = matches.value_of(OPT_TIME).unwrap_or("");
|
||||||
|
|
||||||
if !(matches.opt_present("a")
|
if !(matches.is_present(OPT_ACCESS)
|
||||||
|| time.contains(&"access".to_owned())
|
|| time.contains(&"access".to_owned())
|
||||||
|| time.contains(&"atime".to_owned())
|
|| time.contains(&"atime".to_owned())
|
||||||
|| time.contains(&"use".to_owned()))
|
|| time.contains(&"use".to_owned()))
|
||||||
|
@ -173,7 +201,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
atime = st.0;
|
atime = st.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !(matches.opt_present("m")
|
if !(matches.is_present(OPT_MODIFICATION)
|
||||||
|| time.contains(&"modify".to_owned())
|
|| time.contains(&"modify".to_owned())
|
||||||
|| time.contains(&"mtime".to_owned()))
|
|| time.contains(&"mtime".to_owned()))
|
||||||
{
|
{
|
||||||
|
@ -181,7 +209,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches.opt_present("h") {
|
if matches.is_present(OPT_NO_DEREF) {
|
||||||
if let Err(e) = set_symlink_file_times(path, atime, mtime) {
|
if let Err(e) = set_symlink_file_times(path, atime, mtime) {
|
||||||
show_warning!("cannot touch '{}': {}", path, e);
|
show_warning!("cannot touch '{}': {}", path, e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,6 +195,14 @@ fn test_touch_set_only_atime() {
|
||||||
assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), 45240);
|
assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), 45240);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_touch_set_only_mtime_failed() {
|
||||||
|
let (_at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let file = "test_touch_set_only_mtime";
|
||||||
|
|
||||||
|
ucmd.args(&["-t", "2015010112342", "-m", file]).fails();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_touch_set_only_mtime() {
|
fn test_touch_set_only_mtime() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue