mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27:44 +00:00
Merge pull request #7274 from jfinkels/touch-obsolete-posix-args
touch: support obsolete POSIX timestamp argument
This commit is contained in:
commit
d86a7fb593
2 changed files with 108 additions and 20 deletions
|
@ -135,12 +135,62 @@ fn filetime_to_datetime(ft: &FileTime) -> Option<DateTime<Local>> {
|
||||||
Some(DateTime::from_timestamp(ft.unix_seconds(), ft.nanoseconds())?.into())
|
Some(DateTime::from_timestamp(ft.unix_seconds(), ft.nanoseconds())?.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether all characters in the string are digits.
|
||||||
|
fn all_digits(s: &str) -> bool {
|
||||||
|
s.as_bytes().iter().all(u8::is_ascii_digit)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a two-digit year string to the corresponding number.
|
||||||
|
///
|
||||||
|
/// `s` must be of length two or more. The last two bytes of `s` are
|
||||||
|
/// assumed to be the two digits of the year.
|
||||||
|
fn get_year(s: &str) -> u8 {
|
||||||
|
let bytes = s.as_bytes();
|
||||||
|
let n = bytes.len();
|
||||||
|
let y1 = bytes[n - 2] - b'0';
|
||||||
|
let y2 = bytes[n - 1] - b'0';
|
||||||
|
10 * y1 + y2
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the first filename should be interpreted as a timestamp.
|
||||||
|
fn is_first_filename_timestamp(
|
||||||
|
reference: Option<&OsString>,
|
||||||
|
date: Option<&str>,
|
||||||
|
timestamp: &Option<String>,
|
||||||
|
files: &[&String],
|
||||||
|
) -> bool {
|
||||||
|
match std::env::var("_POSIX2_VERSION") {
|
||||||
|
Ok(s) if s == "199209" => {
|
||||||
|
if timestamp.is_none() && reference.is_none() && date.is_none() && files.len() >= 2 {
|
||||||
|
let s = files[0];
|
||||||
|
all_digits(s)
|
||||||
|
&& (s.len() == 8 || (s.len() == 10 && (69..=99).contains(&get_year(s))))
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cycle the last two characters to the beginning of the string.
|
||||||
|
///
|
||||||
|
/// `s` must have length at least two.
|
||||||
|
fn shr2(s: &str) -> String {
|
||||||
|
let n = s.len();
|
||||||
|
let (a, b) = s.split_at(n - 2);
|
||||||
|
let mut result = String::with_capacity(n);
|
||||||
|
result.push_str(b);
|
||||||
|
result.push_str(a);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let matches = uu_app().try_get_matches_from(args)?;
|
let matches = uu_app().try_get_matches_from(args)?;
|
||||||
|
|
||||||
let files: Vec<InputFile> = matches
|
let mut filenames: Vec<&String> = matches
|
||||||
.get_many::<OsString>(ARG_FILES)
|
.get_many::<String>(ARG_FILES)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
USimpleError::new(
|
USimpleError::new(
|
||||||
1,
|
1,
|
||||||
|
@ -150,6 +200,38 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
})?
|
})?
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let no_deref = matches.get_flag(options::NO_DEREF);
|
||||||
|
|
||||||
|
let reference = matches.get_one::<OsString>(options::sources::REFERENCE);
|
||||||
|
let date = matches
|
||||||
|
.get_one::<String>(options::sources::DATE)
|
||||||
|
.map(|date| date.to_owned());
|
||||||
|
|
||||||
|
let mut timestamp = matches
|
||||||
|
.get_one::<String>(options::sources::TIMESTAMP)
|
||||||
|
.map(|t| t.to_owned());
|
||||||
|
|
||||||
|
if is_first_filename_timestamp(reference, date.as_deref(), ×tamp, &filenames) {
|
||||||
|
timestamp = if filenames[0].len() == 10 {
|
||||||
|
Some(shr2(filenames[0]))
|
||||||
|
} else {
|
||||||
|
Some(filenames[0].to_string())
|
||||||
|
};
|
||||||
|
filenames = filenames[1..].to_vec();
|
||||||
|
}
|
||||||
|
|
||||||
|
let source = if let Some(reference) = reference {
|
||||||
|
Source::Reference(PathBuf::from(reference))
|
||||||
|
} else if let Some(ts) = timestamp {
|
||||||
|
Source::Timestamp(parse_timestamp(&ts)?)
|
||||||
|
} else {
|
||||||
|
Source::Now
|
||||||
|
};
|
||||||
|
|
||||||
|
let files: Vec<InputFile> = filenames
|
||||||
|
.into_iter()
|
||||||
.map(|filename| {
|
.map(|filename| {
|
||||||
if filename == "-" {
|
if filename == "-" {
|
||||||
InputFile::Stdout
|
InputFile::Stdout
|
||||||
|
@ -159,23 +241,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let no_deref = matches.get_flag(options::NO_DEREF);
|
|
||||||
|
|
||||||
let reference = matches.get_one::<OsString>(options::sources::REFERENCE);
|
|
||||||
let timestamp = matches.get_one::<String>(options::sources::TIMESTAMP);
|
|
||||||
|
|
||||||
let source = if let Some(reference) = reference {
|
|
||||||
Source::Reference(PathBuf::from(reference))
|
|
||||||
} else if let Some(ts) = timestamp {
|
|
||||||
Source::Timestamp(parse_timestamp(ts)?)
|
|
||||||
} else {
|
|
||||||
Source::Now
|
|
||||||
};
|
|
||||||
|
|
||||||
let date = matches
|
|
||||||
.get_one::<String>(options::sources::DATE)
|
|
||||||
.map(|date| date.to_owned());
|
|
||||||
|
|
||||||
let opts = Options {
|
let opts = Options {
|
||||||
no_create: matches.get_flag(options::NO_CREATE),
|
no_create: matches.get_flag(options::NO_CREATE),
|
||||||
no_deref,
|
no_deref,
|
||||||
|
@ -275,7 +340,6 @@ pub fn uu_app() -> Command {
|
||||||
Arg::new(ARG_FILES)
|
Arg::new(ARG_FILES)
|
||||||
.action(ArgAction::Append)
|
.action(ArgAction::Append)
|
||||||
.num_args(1..)
|
.num_args(1..)
|
||||||
.value_parser(ValueParser::os_string())
|
|
||||||
.value_hint(clap::ValueHint::AnyPath),
|
.value_hint(clap::ValueHint::AnyPath),
|
||||||
)
|
)
|
||||||
.group(
|
.group(
|
||||||
|
|
|
@ -917,3 +917,27 @@ fn test_touch_reference_symlink_with_no_deref() {
|
||||||
// Times should be taken from the symlink, not the destination
|
// Times should be taken from the symlink, not the destination
|
||||||
assert_eq!((time, time), get_symlink_times(&at, arg));
|
assert_eq!((time, time), get_symlink_times(&at, arg));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_obsolete_posix_format() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
ucmd.env("_POSIX2_VERSION", "199209")
|
||||||
|
.env("POSIXLY_CORRECT", "1")
|
||||||
|
.args(&["01010000", "11111111"])
|
||||||
|
.succeeds()
|
||||||
|
.no_output();
|
||||||
|
assert!(at.file_exists("11111111"));
|
||||||
|
assert!(!at.file_exists("01010000"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_obsolete_posix_format_with_year() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
ucmd.env("_POSIX2_VERSION", "199209")
|
||||||
|
.env("POSIXLY_CORRECT", "1")
|
||||||
|
.args(&["0101000090", "11111111"])
|
||||||
|
.succeeds()
|
||||||
|
.no_output();
|
||||||
|
assert!(at.file_exists("11111111"));
|
||||||
|
assert!(!at.file_exists("0101000090"));
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue