Add support for GNU-compatible printf

This adds initial support for GNU find-compatible printf syntax. As far
as I can tell, it should implement all the specifiers, with the
following exceptions:

- Using zero as padding instead of space: GNU find isn't consistent with
  which numeric format specifiers support this.
- Specifying precision (`%x.yz`): I believe sparseness (%S)
  and seconds-since-epoch (`%A@` / `%C@` / `%T@`) are the only ones that
  allow this, making it only partially useful.
- Printing SELinux contexts (`%Z`): requires some extra dependencies and
  isn't within our scope.

uucore is added as a dependency, because it has several utility
functions that we need, the complexity of which is high enough that
using the extra dependency is quite helpful.

This also stubs out many of the specifiers for Windows, simply because I
have no idea how these should work there. (The defaults chosen should be
"good enough" for the moment, though.)

Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
This commit is contained in:
Ryan Gonzalez
2021-12-17 10:47:05 -06:00
parent 87ceb10073
commit 0ea822cecb
8 changed files with 1458 additions and 8 deletions
+1 -2
View File
@@ -3,5 +3,4 @@ target
.project
.cargo
.settings
test_data/links/link-f
test_data/links/link-d
test_data/links/link-*
Generated
+205
View File
@@ -1,5 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
@@ -9,6 +11,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "assert_cmd"
version = "2.0.2"
@@ -23,6 +34,17 @@ dependencies = [
"wait-timeout",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
@@ -58,6 +80,34 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
"num-traits",
"time",
"winapi",
]
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "difflib"
version = "0.4.0"
@@ -70,22 +120,44 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "dunce"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541"
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "filetime"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.2.10",
"winapi",
]
[[package]]
name = "findutils"
version = "0.2.0"
dependencies = [
"assert_cmd",
"chrono",
"filetime",
"glob",
"once_cell",
"predicates",
"regex",
"serial_test",
"tempfile",
"uucore",
"walkdir",
]
@@ -98,6 +170,15 @@ dependencies = [
"num-traits",
]
[[package]]
name = "getopts"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
dependencies = [
"unicode-width",
]
[[package]]
name = "getrandom"
version = "0.2.3"
@@ -115,6 +196,15 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "instant"
version = "0.1.9"
@@ -166,6 +256,16 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
@@ -175,6 +275,27 @@ dependencies = [
"autocfg",
]
[[package]]
name = "numtoa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]]
name = "once_cell"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
[[package]]
name = "os_display"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "748cc1d0dc55247316a5bedd8dc8c5478c8a0c2e2001176b38ce7c0ed732c7a5"
dependencies = [
"unicode-width",
]
[[package]]
name = "parking_lot"
version = "0.11.1"
@@ -309,6 +430,15 @@ dependencies = [
"bitflags",
]
[[package]]
name = "redox_termios"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
dependencies = [
"redox_syscall 0.2.10",
]
[[package]]
name = "regex"
version = "1.5.4"
@@ -387,6 +517,12 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a55ca5f3b68e41c979bf8c46a6f1da892ca4db8f94023ce0bd32407573b1ac0"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "syn"
version = "1.0.56"
@@ -412,18 +548,78 @@ dependencies = [
"winapi",
]
[[package]]
name = "termion"
version = "1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
dependencies = [
"libc",
"numtoa",
"redox_syscall 0.2.10",
"redox_termios",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "treeline"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41"
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "uucore"
version = "0.0.10"
source = "git+https://github.com/uutils/coreutils#5c0adb26a57e594b369e4ffd0fe9f54b9d4efd20"
dependencies = [
"clap",
"dunce",
"getopts",
"libc",
"once_cell",
"os_display",
"termion",
"time",
"wild",
"winapi",
]
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "wait-timeout"
version = "0.2.0"
@@ -450,6 +646,15 @@ version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wild"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "035793abb854745033f01a07647a79831eba29ec0be377205f2a25b0aa830020"
dependencies = [
"glob",
]
[[package]]
name = "winapi"
version = "0.3.9"
+4
View File
@@ -10,13 +10,17 @@ description = "Rust implementation of GNU findutils"
authors = ["uutils developers"]
[dependencies]
chrono = "0.4"
glob = "0.3"
walkdir = "2.3"
tempfile = "3"
regex = "1.5"
once_cell = "1.9"
uucore = { git = "https://github.com/uutils/coreutils", features = ["entries", "fs", "fsext"] }
[dev-dependencies]
assert_cmd = "2"
filetime = "0.2"
predicates = "2"
serial_test = "0.5"
+4 -4
View File
@@ -63,7 +63,7 @@ impl Matcher for DeleteMatcher {
#[cfg(test)]
mod tests {
use std::fs::File;
use std::fs::{create_dir, File};
use tempfile::Builder;
use super::*;
@@ -79,6 +79,7 @@ mod tests {
let temp_dir_path = temp_dir.path().to_string_lossy();
File::create(temp_dir.path().join("test")).expect("created test file");
create_dir(temp_dir.path().join("test_dir")).expect("created test directory");
let test_entry = get_dir_entry_for(&temp_dir_path, "test");
assert!(
matcher.matches(&test_entry, &mut deps.new_matcher_io()),
@@ -89,14 +90,13 @@ mod tests {
"DeleteMatcher should actually delete files it matches",
);
let temp_dir_name = temp_dir.path().file_name().unwrap().to_string_lossy();
let temp_dir_entry = get_dir_entry_for(&temp_dir_path, &temp_dir_name);
let temp_dir_entry = get_dir_entry_for(&temp_dir_path, "test_dir");
assert!(
matcher.matches(&temp_dir_entry, &mut deps.new_matcher_io()),
"DeleteMatcher should match directories",
);
assert!(
!temp_dir.path().exists(),
!temp_dir.path().join("test_dir").exists(),
"DeleteMatcher should actually delete (empty) directories it matches",
);
}
+15 -1
View File
@@ -10,6 +10,7 @@ mod logical_matchers;
mod name;
mod perm;
mod printer;
mod printf;
mod prune;
mod size;
mod time;
@@ -206,6 +207,13 @@ fn build_matcher_tree(
while i < args.len() {
let possible_submatcher = match args[i] {
"-print" => Some(printer::Printer::new_box()),
"-printf" => {
if i >= args.len() - 1 {
return Err(From::from(format!("missing argument to {}", args[i])));
}
i += 1;
Some(printf::Printf::new_box(args[i])?)
}
"-true" => Some(logical_matchers::TrueMatcher::new_box()),
"-false" => Some(logical_matchers::FalseMatcher::new_box()),
"-name" => {
@@ -423,7 +431,13 @@ mod tests {
pub fn get_dir_entry_for(directory: &str, filename: &str) -> DirEntry {
for wrapped_dir_entry in WalkDir::new(fix_up_slashes(directory)) {
let dir_entry = wrapped_dir_entry.unwrap();
if dir_entry.file_name().to_string_lossy() == filename {
if dir_entry
.path()
.strip_prefix(directory)
.unwrap()
.to_string_lossy()
== fix_up_slashes(filename)
{
return dir_entry;
}
}
File diff suppressed because it is too large Load Diff
+1
View File
@@ -172,6 +172,7 @@ If no path is supplied then the current working directory is used by default.
Early alpha implementation. Currently the only expressions supported are
-print
-printf
-name case-sensitive_filename_pattern
-iname case-insensitive_filename_pattern
-type type_char
+118 -1
View File
@@ -11,10 +11,20 @@
use assert_cmd::Command;
use predicates::prelude::*;
use serial_test::serial;
use std::env;
use std::fs::File;
use std::{env, io::ErrorKind};
use tempfile::Builder;
#[cfg(unix)]
use std::os::unix::fs::symlink;
#[cfg(windows)]
use std::os::windows::fs::{symlink_dir, symlink_file};
use common::test_helpers::*;
mod common;
#[serial(working_dir)]
#[test]
fn no_args() {
@@ -115,3 +125,110 @@ fn delete_on_dot_dir() {
assert!(temp_dir.path().exists(), "temp dir should still exist");
}
#[serial(working_dir)]
#[test]
fn find_printf() {
#[cfg(unix)]
{
if let Err(e) = symlink("abbbc", "test_data/links/link-f") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink("subdir", "test_data/links/link-d") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink("missing", "test_data/links/link-missing") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink("abbbc/x", "test_data/links/link-notdir") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink("link-loop", "test_data/links/link-loop") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
}
#[cfg(windows)]
{
if let Err(e) = symlink_file("abbbc", "test_data/links/link-f") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink_dir("subdir", "test_data/links/link-d") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink_file("missing", "test_data/links/link-missing") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
if let Err(e) = symlink_file("abbbc/x", "test_data/links/link-notdir") {
if e.kind() != ErrorKind::AlreadyExists {
panic!("Failed to create sym link: {:?}", e);
}
}
}
Command::cargo_bin("find")
.expect("found binary")
.args(&[
&fix_up_slashes("./test_data/simple"),
"-sorted",
"-printf",
"%f %d %h %H %p %P %y\n",
])
.assert()
.success()
.stderr(predicate::str::is_empty())
.stdout(predicate::str::diff(fix_up_slashes(
"simple 0 ./test_data ./test_data/simple \
./test_data/simple d\n\
abbbc 1 ./test_data/simple ./test_data/simple \
./test_data/simple/abbbc abbbc f\n\
subdir 1 ./test_data/simple ./test_data/simple \
./test_data/simple/subdir subdir d\n\
ABBBC 2 ./test_data/simple/subdir ./test_data/simple \
./test_data/simple/subdir/ABBBC subdir/ABBBC f\n",
)));
Command::cargo_bin("find")
.expect("found binary")
.args(&[
&fix_up_slashes("./test_data/links"),
"-sorted",
"-type",
"l",
"-printf",
"%f %l %y %Y\n",
])
.assert()
.success()
.stderr(predicate::str::is_empty())
.stdout(predicate::str::diff(
[
"link-d subdir l d\n",
"link-f abbbc l f\n",
#[cfg(unix)]
"link-loop link-loop l L\n",
"link-missing missing l N\n",
// We can't detect ENOTDIR on non-unix platforms yet.
#[cfg(not(unix))]
"link-notdir abbbc/x l ?\n",
#[cfg(unix)]
"link-notdir abbbc/x l N\n",
]
.join(""),
));
}