mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 19:47:45 +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"
|
||||
version = "0.0.1"
|
||||
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)",
|
||||
"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)",
|
||||
"uucore 0.0.4",
|
||||
"uucore_procs 0.0.4",
|
||||
|
|
|
@ -16,7 +16,7 @@ path = "src/touch.rs"
|
|||
|
||||
[dependencies]
|
||||
filetime = "0.2.1"
|
||||
getopts = "0.2.18"
|
||||
clap = "2.33"
|
||||
time = "0.1.40"
|
||||
uucore = { version=">=0.0.4", package="uucore", path="../../uucore", features=["libc"] }
|
||||
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
|
||||
|
||||
extern crate clap;
|
||||
pub extern crate filetime;
|
||||
extern crate getopts;
|
||||
extern crate time;
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use filetime::*;
|
||||
use std::fs::{self, File};
|
||||
use std::io::Error;
|
||||
use std::path::Path;
|
||||
|
||||
static NAME: &str = "touch";
|
||||
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
|
||||
// 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 {
|
||||
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");
|
||||
opts.optflag("c", "no-create", "do not create any files");
|
||||
opts.optopt(
|
||||
"d",
|
||||
"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 files: Vec<String> = matches
|
||||
.values_of(ARG_FILES)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let matches = match opts.parse(&args[1..]) {
|
||||
Ok(m) => m,
|
||||
Err(e) => panic!("Invalid options\n{}", e),
|
||||
};
|
||||
|
||||
if matches.opt_present("version") {
|
||||
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()])
|
||||
if matches.is_present(OPT_DATE)
|
||||
&& (matches.is_present(OPT_REFERENCE) || matches.is_present(OPT_CURRENT))
|
||||
|| matches.is_present(OPT_REFERENCE)
|
||||
&& (matches.is_present(OPT_DATE) || matches.is_present(OPT_CURRENT))
|
||||
|| matches.is_present(OPT_CURRENT)
|
||||
&& (matches.is_present(OPT_DATE) || matches.is_present(OPT_REFERENCE))
|
||||
{
|
||||
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(
|
||||
&matches.opt_str("reference").unwrap()[..],
|
||||
!matches.opt_present("no-dereference"),
|
||||
&matches.value_of(OPT_REFERENCE).unwrap()[..],
|
||||
!matches.is_present(OPT_NO_DEREF),
|
||||
)
|
||||
} else if matches.opts_present(&["date".to_owned(), "t".to_owned()]) {
|
||||
let timestamp = if matches.opt_present("date") {
|
||||
parse_date(matches.opt_str("date").unwrap().as_ref())
|
||||
} else if matches.is_present(OPT_DATE) || matches.is_present(OPT_CURRENT) {
|
||||
let timestamp = if matches.is_present(OPT_DATE) {
|
||||
parse_date(matches.value_of(OPT_DATE).unwrap().as_ref())
|
||||
} else {
|
||||
parse_timestamp(matches.opt_str("t").unwrap().as_ref())
|
||||
parse_timestamp(matches.value_of(OPT_CURRENT).unwrap().as_ref())
|
||||
};
|
||||
(timestamp, timestamp)
|
||||
} else {
|
||||
|
@ -139,12 +161,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
(now, now)
|
||||
};
|
||||
|
||||
for filename in &matches.free {
|
||||
for filename in &files {
|
||||
let path = &filename[..];
|
||||
|
||||
if !Path::new(path).exists() {
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
@ -154,18 +176,24 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
};
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
if matches.opts_present(&["a".to_owned(), "m".to_owned(), "time".to_owned()]) {
|
||||
let st = stat(path, !matches.opt_present("no-dereference"));
|
||||
let time = matches.opt_strs("time");
|
||||
if matches.is_present(OPT_ACCESS)
|
||||
|| matches.is_present(OPT_MODIFICATION)
|
||||
|| 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(&"atime".to_owned())
|
||||
|| time.contains(&"use".to_owned()))
|
||||
|
@ -173,7 +201,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
atime = st.0;
|
||||
}
|
||||
|
||||
if !(matches.opt_present("m")
|
||||
if !(matches.is_present(OPT_MODIFICATION)
|
||||
|| time.contains(&"modify".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) {
|
||||
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);
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn test_touch_set_only_mtime() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue