1
Fork 0
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:
Sylvestre Ledru 2020-11-21 09:53:13 +01:00 committed by GitHub
parent 5efaa0bf32
commit bfba889f8e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 129 additions and 93 deletions

2
Cargo.lock generated
View file

@ -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",

View file

@ -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" }

View file

@ -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();
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",
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)",
);
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",
),
)
.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",
"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..]) {
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;
}
.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);
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()])
let files: Vec<String> = matches
.values_of(ARG_FILES)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
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);
}

View file

@ -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!();