From 4795d8c0f924535d3da9206f12b376df01855394 Mon Sep 17 00:00:00 2001 From: shale Date: Wed, 2 Apr 2025 22:43:22 -0400 Subject: [PATCH 01/14] initial commit with some of the starting code and documentation setup for the whereis addition --- src/uu/whereis/Cargo.toml | 19 ++++ src/uu/whereis/src/constants.rs | 95 ++++++++++++++++ src/uu/whereis/src/main.rs | 2 + src/uu/whereis/src/whereis.rs | 191 ++++++++++++++++++++++++++++++++ src/uu/whereis/whereis.md | 7 ++ 5 files changed, 314 insertions(+) create mode 100644 src/uu/whereis/Cargo.toml create mode 100644 src/uu/whereis/src/constants.rs create mode 100644 src/uu/whereis/src/main.rs create mode 100644 src/uu/whereis/src/whereis.rs create mode 100644 src/uu/whereis/whereis.md diff --git a/src/uu/whereis/Cargo.toml b/src/uu/whereis/Cargo.toml new file mode 100644 index 00000000..31d4539f --- /dev/null +++ b/src/uu/whereis/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "uu_whereis" +version = "0.0.1" +edition = "2021" + +[lib] +path = "src/whereis.rs" + +[[bin]] +name = "whereis" +path = "src/main.rs" + +[dependencies] +regex = { workspace = true } +uucore = { workspace = true } +clap = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +sysinfo = { workspace = true } diff --git a/src/uu/whereis/src/constants.rs b/src/uu/whereis/src/constants.rs new file mode 100644 index 00000000..8db41633 --- /dev/null +++ b/src/uu/whereis/src/constants.rs @@ -0,0 +1,95 @@ + +// List of directories, man, src, and binary + +pub const MANDIRS: [&str; 7] = [ + "/usr/man/*", + "/usr/share/man/*", + "/usr/X386/man/*", + "/usr/X11/man/*", + "/usr/TeX/man/*", + "/usr/interviews/man/mann", + "/usr/share/info", + // NULL +]; + +pub const SRCDIRS: [&str; 6] = [ + "/usr/src/*", + "/usr/src/lib/libc/*", + "/usr/src/lib/libc/net/*", + "/usr/src/ucb/pascal", + "/usr/src/ucb/pascal/utilities", + "/usr/src/undoc", + // NULL +]; + + +pub const BINDIRS: [&str; 46] = [ + "/usr/bin", + "/usr/sbin", + "/bin", + "/sbin", + +// #[cfg(...)] +/* +#if defined(MULTIARCHTRIPLET) + + "/lib/" MULTIARCHTRIPLET, + "/usr/lib/" MULTIARCHTRIPLET, + "/usr/local/lib/" MULTIARCHTRIPLET, + +#endif +*/ + +// #[cfg(not(...))] + + "/usr/lib", + "/usr/lib32", + "/usr/lib64", + "/etc", + "/usr/etc", + "/lib", + "/lib32", + "/lib64", + "/usr/games", + "/usr/games/bin", + "/usr/games/lib", + "/usr/emacs/etc", + "/usr/lib/emacs/*/etc", + "/usr/TeX/bin", + "/usr/tex/bin", + "/usr/interviews/bin/LINUX", + + "/usr/X11R6/bin", + "/usr/X386/bin", + "/usr/bin/X11", + "/usr/X11/bin", + "/usr/X11R5/bin", + + "/usr/local/bin", + "/usr/local/sbin", + "/usr/local/etc", + "/usr/local/lib", + "/usr/local/games", + "/usr/local/games/bin", + "/usr/local/emacs/etc", + "/usr/local/TeX/bin", + "/usr/local/tex/bin", + "/usr/local/bin/X11", + + "/usr/contrib", + "/usr/hosts", + "/usr/include", + + "/usr/g++-include", + + "/usr/ucb", + "/usr/old", + "/usr/new", + "/usr/local", + "/usr/libexec", + "/usr/share", + + "/opt/*/bin", + // NULL +]; + diff --git a/src/uu/whereis/src/main.rs b/src/uu/whereis/src/main.rs new file mode 100644 index 00000000..ec7f2233 --- /dev/null +++ b/src/uu/whereis/src/main.rs @@ -0,0 +1,2 @@ +uucore::bin!(uu_whereis); + diff --git a/src/uu/whereis/src/whereis.rs b/src/uu/whereis/src/whereis.rs new file mode 100644 index 00000000..4c156060 --- /dev/null +++ b/src/uu/whereis/src/whereis.rs @@ -0,0 +1,191 @@ +// This file is a part of the uutils util-linux package. + +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use clap::{crate_version, Arg, ArgAction, Command}; +use serde::Serialize; +use std::{fs, path::PathBuf}; +use uucore::{error::UResult, format_usage, help_about, help_usage}; + +mod constants; +use crate::constants::{SRCDIRS, BINDIRS, MANDIRS}; + +mod options { + pub const BYTES: &str = "bytes"; + pub const HEX: &str = "hex"; + pub const JSON: &str = "json"; +} + +const ABOUT: &str = help_about!("whereis.md"); +const USAGE: &str = help_usage!("whereis.md"); + +#[derive(Serialize, Clone, Debug)] +pub enum DirType { + MAN, + BIN, + SRC, + UNK, +} + +// Store the metadata for a file +#[derive(Serialize, Clone, Debug)] +pub struct WhDir { + #[serde(skip_serializing)] + #[serde(skip_deserializing)] + metadata: Option, + path: PathBuf, + type_of_dir: DirType +} + +impl WhDir { + + fn new (path: PathBuf, type_of_dir: DirType) -> Self { + Self { + metadata: fs::metadata(&path).ok(), + path, + type_of_dir, + } + } + +} + +// Use a vector to store the list of directories. +#[derive(Serialize)] +pub struct WhDirList { + list: Vec +} + +impl WhDirList { + + fn new () -> Self { + Self { + list: Vec::new(), + } + } + + fn construct_dir_list (&mut self, dir_type: DirType, paths: &[&str]) { + + for path in paths { + let pathbuf = PathBuf::from(path); + if !path.contains('*') { + self.add_dir (WhDir::new(pathbuf, dir_type.clone())); + } else { + self.add_sub_dirs(&pathbuf, dir_type.clone()); + } + } + } + + + fn add_dir (&mut self, dir: WhDir) { + + // use (ino) inode number and (st_dev) + if self.list.iter().any(|d| d.path == dir.path) { + return; + } + + if let Some(metadata) = &dir.metadata { + if metadata.permissions().readonly() { + self.list.push(dir); + } + } + } + + fn add_sub_dirs (&mut self, parent_dir: &PathBuf, dir_type: DirType) { + + if let Ok(entries) = fs::read_dir(parent_dir) { + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() { + self.add_dir(WhDir::new(path, dir_type.clone())); + } + } + } + } + + fn remove_dir (&mut self, dir: &WhDir) { + self.list.retain(|d| d.path != dir.path); + } + +} + +// The output options struct +struct OutputOptions { + bytes: bool, + json: bool, + _hex: bool, +} + +pub fn whereis_type_to_name (dir_type: &DirType) -> &'static str { + + match dir_type { + DirType::MAN => "man", + DirType::BIN => "bin", + DirType::SRC => "src", + DirType::UNK => "???", + } +} + + +// pub fn build_dir_list (dir_list: &WhDirList) -> None { } + + +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult <()> { + + let matches: clap::ArgMatches = uu_app().try_get_matches_from(args)?; + + let _output_opts = OutputOptions { + bytes: matches.get_flag(options::BYTES), + _hex: matches.get_flag(options::HEX), + json: matches.get_flag(options::JSON), + }; + + + Ok(()) +} + + +// Fix this, there is -b -B -m -M -s -S -f -u -g and -i +pub fn uu_app() -> Command { + Command::new(uucore::util_name()) + .version(crate_version!()) + .about(ABOUT) + .override_usage(format_usage(USAGE)) + .infer_long_args(true) + .arg( + Arg::new(options::HEX) + .short('x') + .long("hex") + .action(ArgAction::SetTrue) + .help( + "Use hexadecimal masks for CPU sets (for example 'ff'). \ + The default is to print the sets in list format (for example 0,1).", + ) + .required(false), + ) + .arg( + Arg::new(options::JSON) + .short('J') + .long("json") + .help( + "Use JSON output format for the default summary or extended output \ + (see --extended). For backward compatibility, JSON output follows the \ + default summary behavior for non-terminals (e.g., pipes) where \ + subsections are missing. See also --hierarchic.", + ) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::BYTES) + .short('B') + .long("bytes") + .action(ArgAction::SetTrue) + .help( + "Print the sizes in bytes rather than in a human-readable format. \ + The default is to print sizes in human-readable format (for example '512 KiB'). \ + Setting this flag instead prints the decimal amount of bytes with no suffix.", + ), + ) +} + diff --git a/src/uu/whereis/whereis.md b/src/uu/whereis/whereis.md new file mode 100644 index 00000000..8d69e0ec --- /dev/null +++ b/src/uu/whereis/whereis.md @@ -0,0 +1,7 @@ +# whereis + +``` +whereis [OPTION] ... +``` +Locates the binary, source and manual pages for a command. + From 3964a7477f14bbb7feeec410ef20c71370e08989 Mon Sep 17 00:00:00 2001 From: shale Date: Wed, 2 Apr 2025 22:43:45 -0400 Subject: [PATCH 02/14] added whereis and required fields to toml --- Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 09330eb6..22215d44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ feat_common_core = [ "renice", "rev", "setsid", + "whereis", ] [workspace.dependencies] @@ -94,6 +95,7 @@ mountpoint = { optional = true, version = "0.0.1", package = "uu_mountpoint", pa renice = { optional = true, version = "0.0.1", package = "uu_renice", path = "src/uu/renice" } rev = { optional = true, version = "0.0.1", package = "uu_rev", path = "src/uu/rev" } setsid = { optional = true, version = "0.0.1", package = "uu_setsid", path ="src/uu/setsid" } +whereis = { optional = true, version = "0.0.1", package = "uu_whereis", path = "src/uu/whereis" } [dev-dependencies] # dmesg test require fixed-boot-time feature turned on. From 55ef8392bee7b5e13d626e83d01fbc9c977b3de9 Mon Sep 17 00:00:00 2001 From: shale Date: Wed, 2 Apr 2025 22:45:26 -0400 Subject: [PATCH 03/14] whereis added to Cargo.lock file --- Cargo.lock | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 72a8529f..b920c71f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1025,6 +1025,7 @@ dependencies = [ "uu_renice", "uu_rev", "uu_setsid", + "uu_whereis", "uucore", "xattr", ] @@ -1178,6 +1179,18 @@ dependencies = [ "uucore", ] +[[package]] +name = "uu_whereis" +version = "0.0.1" +dependencies = [ + "clap", + "regex", + "serde", + "serde_json", + "sysinfo", + "uucore", +] + [[package]] name = "uucore" version = "0.0.30" From 7369c36fa2817200daa0df3ac3110a3208bf3263 Mon Sep 17 00:00:00 2001 From: shale Date: Mon, 7 Apr 2025 20:43:02 -0400 Subject: [PATCH 04/14] updated Cargo.toml and whereis.md with glob dependency and copy of original whereis man page for documentation --- src/uu/whereis/Cargo.toml | 1 + src/uu/whereis/whereis.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/uu/whereis/Cargo.toml b/src/uu/whereis/Cargo.toml index 31d4539f..f859d1b1 100644 --- a/src/uu/whereis/Cargo.toml +++ b/src/uu/whereis/Cargo.toml @@ -17,3 +17,4 @@ clap = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } sysinfo = { workspace = true } +glob = "0.3" diff --git a/src/uu/whereis/whereis.md b/src/uu/whereis/whereis.md index 8d69e0ec..35752b66 100644 --- a/src/uu/whereis/whereis.md +++ b/src/uu/whereis/whereis.md @@ -1,7 +1,7 @@ # whereis ``` -whereis [OPTION] ... +whereis [options] [-BMS directory... -f] name... ``` Locates the binary, source and manual pages for a command. From 0c0a01981fb375c73a5b3885d205cb7ce4a55bca Mon Sep 17 00:00:00 2001 From: shale Date: Mon, 7 Apr 2025 22:04:54 -0400 Subject: [PATCH 05/14] added mod_whereis to the tests, and glob to dependencies --- Cargo.lock | 1 + tests/tests.rs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index b920c71f..7ddfe397 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1184,6 +1184,7 @@ name = "uu_whereis" version = "0.0.1" dependencies = [ "clap", + "glob", "regex", "serde", "serde_json", diff --git a/tests/tests.rs b/tests/tests.rs index 0fb3ed14..db25f401 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -60,3 +60,7 @@ mod test_fsfreeze; #[cfg(feature = "mcookie")] #[path = "by-util/test_mcookie.rs"] mod test_mcookie; + +#[cfg(feature = "whereis")] +#[path = "by-util/test_whereis.rs"] +mod test_whereis; From d555e95728067a81981de70978333cf71caf1040 Mon Sep 17 00:00:00 2001 From: shale Date: Mon, 7 Apr 2025 22:05:37 -0400 Subject: [PATCH 06/14] completed initial version of whereis utility, and some very primitive tests to go along with it --- src/uu/whereis/src/whereis.rs | 352 +++++++++++++++++++++++++++------- tests/by-util/test_whereis.rs | 47 +++++ 2 files changed, 333 insertions(+), 66 deletions(-) create mode 100644 tests/by-util/test_whereis.rs diff --git a/src/uu/whereis/src/whereis.rs b/src/uu/whereis/src/whereis.rs index 4c156060..e264c6f8 100644 --- a/src/uu/whereis/src/whereis.rs +++ b/src/uu/whereis/src/whereis.rs @@ -1,29 +1,35 @@ // This file is a part of the uutils util-linux package. - // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. use clap::{crate_version, Arg, ArgAction, Command}; use serde::Serialize; -use std::{fs, path::PathBuf}; +use std::{fs, collections::HashSet, collections::HashMap, path::Path, path::PathBuf, os::unix::fs::MetadataExt}; use uucore::{error::UResult, format_usage, help_about, help_usage}; +use glob::glob; mod constants; use crate::constants::{SRCDIRS, BINDIRS, MANDIRS}; mod options { - pub const BYTES: &str = "bytes"; - pub const HEX: &str = "hex"; - pub const JSON: &str = "json"; + pub const BIN: &str = "binaries"; + pub const MAN: &str = "manuals"; + pub const SRC: &str = "sources"; + pub const PATH: &str = "lookups"; + + pub const SPECIFIED_BIN: &str = "listed binaries"; + pub const SPECIFIED_MAN: &str = "listed manuals"; + pub const SPECIFIED_SRC: &str = "listed sources"; } const ABOUT: &str = help_about!("whereis.md"); const USAGE: &str = help_usage!("whereis.md"); -#[derive(Serialize, Clone, Debug)] +// Directories are usually manual pages dirs, binary dirs or source dirs. Hopefully not unknown. +#[derive(Serialize, Copy, Clone, Debug, Eq, Hash, PartialEq)] pub enum DirType { - MAN, BIN, + MAN, SRC, UNK, } @@ -31,6 +37,7 @@ pub enum DirType { // Store the metadata for a file #[derive(Serialize, Clone, Debug)] pub struct WhDir { + #[serde(skip_serializing)] #[serde(skip_deserializing)] metadata: Option, @@ -50,10 +57,11 @@ impl WhDir { } -// Use a vector to store the list of directories. -#[derive(Serialize)] +// Use a vector to store the list of directories. Additionally keep a HashSet of the inode number and st_dev ID. +#[derive(Serialize, Debug)] pub struct WhDirList { - list: Vec + list: Vec, + seen_files: HashSet<(u64, u64)>, } impl WhDirList { @@ -61,11 +69,11 @@ impl WhDirList { fn new () -> Self { Self { list: Vec::new(), + seen_files: HashSet::new(), } } fn construct_dir_list (&mut self, dir_type: DirType, paths: &[&str]) { - for path in paths { let pathbuf = PathBuf::from(path); if !path.contains('*') { @@ -77,47 +85,66 @@ impl WhDirList { } + // Use (ino) inode number and (st_dev) ID of device containing the file to keep track of whats unique. fn add_dir (&mut self, dir: WhDir) { - - // use (ino) inode number and (st_dev) if self.list.iter().any(|d| d.path == dir.path) { return; } - if let Some(metadata) = &dir.metadata { - if metadata.permissions().readonly() { + if dir.metadata.is_some() { + let dev = dir.metadata.clone().unwrap().dev(); + let ino = dir.metadata.clone().unwrap().ino(); + + if self.seen_files.insert((dev, ino)) { self.list.push(dir); - } - } + } + } + } + + #[allow(dead_code)] + fn remove_dir (&mut self, dir: &WhDir) { + self.list.retain(|d| d.path != dir.path); } - fn add_sub_dirs (&mut self, parent_dir: &PathBuf, dir_type: DirType) { - if let Ok(entries) = fs::read_dir(parent_dir) { - for entry in entries.flatten() { - let path = entry.path(); - if path.is_dir() { + // TODO: We need to do something with the entry if an error occurs. + fn add_sub_dirs (&mut self, parent_dir: &PathBuf, dir_type: DirType) { + for entry in glob(&parent_dir.display().to_string()).expect("Failed to read glob pattern") { + match entry { + Ok(path) if path.is_dir() => { self.add_dir(WhDir::new(path, dir_type.clone())); - } - } - } + } + Ok(_) => todo!(), + Err(_e) => todo!(), + } + } } - fn remove_dir (&mut self, dir: &WhDir) { - self.list.retain(|d| d.path != dir.path); + // A debug function. + #[allow(dead_code)] + fn list_dirs (&self) { + for dir in &self.list { + let dir_type = whereis_type_to_name (dir.type_of_dir); + println!("{:?} : {:?}", dir_type, dir.path.display()); + } } -} -// The output options struct -struct OutputOptions { - bytes: bool, - json: bool, - _hex: bool, -} + fn lookup(&self, pattern: &str, dir_type: DirType) -> Vec { + let mut results = Vec::new(); + let pathbuf_pattern = PathBuf::from(pattern); -pub fn whereis_type_to_name (dir_type: &DirType) -> &'static str { + for dir in &self.list { + if dir.type_of_dir == dir_type { + find_in(&dir.path, &pathbuf_pattern, &mut results, dir.type_of_dir); + } + } + + results + } +} +pub fn whereis_type_to_name (dir_type: DirType) -> &'static str { match dir_type { DirType::MAN => "man", DirType::BIN => "bin", @@ -126,8 +153,108 @@ pub fn whereis_type_to_name (dir_type: &DirType) -> &'static str { } } +// Almost an exact ripoff from the C source. +fn filename_equal(cp: &PathBuf, dp: &str, dir_type: DirType) -> bool { + let cp_str = match cp.file_name().and_then(|s| s.to_str()) { + Some(s) => s, + None => return false, + }; + + let mut dp_trimmed = dp; + + if dir_type == DirType::SRC && dp_trimmed.starts_with("s.") { + return filename_equal(cp, &dp_trimmed[2..], dir_type); + } + + if dir_type == DirType::MAN { + for ext in [".Z", ".gz", ".xz", ".bz2", ".zst"] { + if let Some(stripped) = dp_trimmed.strip_suffix(ext) { + dp_trimmed = stripped; + break; + } + } + } + + let mut cp_chars = cp_str.chars(); + let mut dp_chars = dp_trimmed.chars(); + + loop { + match (cp_chars.next(), dp_chars.next()) { + (Some(c1), Some(c2)) if c1 == c2 => continue, + (None, None) => return true, // both ended + (None, Some('.')) if dir_type != DirType::BIN => { + // cp ended, dp has .section + return true; + } + _ => return false, + } + } +} + -// pub fn build_dir_list (dir_list: &WhDirList) -> None { } +fn find_in(dir: &Path, pathbuf: &PathBuf, results: &mut Vec, dir_type: DirType) { + if let Ok(entries) = fs::read_dir(dir) { + for entry in entries.flatten() { + let path = entry.path(); + if let Some(filename) = path.file_name().and_then(|f| f.to_str()) { + if filename_equal(pathbuf, filename, dir_type) { + results.push(path.display().to_string()); + } + } + } + } +} + +fn print_output (options: &OutputOptions, pattern: &str, results: Vec) { + let mut grouped: HashMap> = HashMap::new(); + + // Split results by type, grouping MAN, BIN and SRC. + for path in results { + if path.contains("/bin/") { + grouped.entry(DirType::BIN).or_default().push(path); + } else if path.contains("/man") || path.contains("/share/man") { + grouped.entry(DirType::MAN).or_default().push(path); + } else { + grouped.entry(DirType::SRC).or_default().push(path); + } + } + + print!("{}:", pattern); + + // If *any* of the search flags are set, print according to them + if options.search_bin || options.search_man || options.search_src { + if options.search_bin { + if let Some(paths) = grouped.get(&DirType::BIN) { + for path in paths { + print!(" {}", path); + } + } + } + if options.search_man { + if let Some(paths) = grouped.get(&DirType::MAN) { + for path in paths { + print!(" {}", path); + } + } + } + if options.search_src { + if let Some(paths) = grouped.get(&DirType::SRC) { + for path in paths { + print!(" {}", path); + } + } + } + } else { + // No -b/-m/-s flag given? Print everything + for paths in grouped.values() { + for path in paths { + print!(" {}", path); + } + } + } + + println!(); +} #[uucore::main] @@ -135,57 +262,150 @@ pub fn uumain(args: impl uucore::Args) -> UResult <()> { let matches: clap::ArgMatches = uu_app().try_get_matches_from(args)?; - let _output_opts = OutputOptions { - bytes: matches.get_flag(options::BYTES), - _hex: matches.get_flag(options::HEX), - json: matches.get_flag(options::JSON), - }; + let output_options = OutputOptions { + search_bin: matches.get_flag(options::BIN), + search_man: matches.get_flag(options::MAN), + search_src: matches.get_flag(options::SRC), + path_given: matches.get_flag(options::PATH), + + search_specific_bin: matches.get_flag(options::SPECIFIED_BIN), + search_specific_man: matches.get_flag(options::SPECIFIED_MAN), + search_specific_src: matches.get_flag(options::SPECIFIED_SRC), + }; + + let mut dir_list = WhDirList::new(); + + dir_list.construct_dir_list(DirType::BIN, &BINDIRS); + dir_list.construct_dir_list(DirType::MAN, &MANDIRS); + dir_list.construct_dir_list(DirType::SRC, &SRCDIRS); + + let names: Vec<_> = matches.get_many::("names") + .unwrap() + .map(|s| s.as_str()) + .collect(); + + // Search for the names that were passed into the program. + for pattern in names { + let mut results = dir_list.lookup(pattern, DirType::BIN); + results.append(&mut dir_list.lookup(pattern, DirType::MAN)); + results.append(&mut dir_list.lookup(pattern, DirType::SRC)); + + print_output(&output_options, pattern, results); + } - Ok(()) } + +// TODO: Implement the necessary behavior for path_given and other fields with the dead_code macro. +struct OutputOptions { + search_bin: bool, + search_man: bool, + search_src: bool, + + #[allow(dead_code)] + path_given: bool, + #[allow(dead_code)] + search_specific_bin: bool, + + #[allow(dead_code)] + search_specific_man: bool, + + #[allow(dead_code)] + search_specific_src: bool, +} -// Fix this, there is -b -B -m -M -s -S -f -u -g and -i pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .override_usage(format_usage(USAGE)) .infer_long_args(true) + + .arg( + Arg::new("names") + .help("The name of the program [s] to search for.") + .num_args(1..) + .required(true) + ) .arg( - Arg::new(options::HEX) - .short('x') - .long("hex") + Arg::new(options::BIN) + .short('b') + .long("binaries") .action(ArgAction::SetTrue) - .help( - "Use hexadecimal masks for CPU sets (for example 'ff'). \ - The default is to print the sets in list format (for example 0,1).", - ) + .help("Search for binaries.") .required(false), ) .arg( - Arg::new(options::JSON) - .short('J') - .long("json") - .help( - "Use JSON output format for the default summary or extended output \ - (see --extended). For backward compatibility, JSON output follows the \ - default summary behavior for non-terminals (e.g., pipes) where \ - subsections are missing. See also --hierarchic.", - ) - .action(ArgAction::SetTrue), + Arg::new(options::MAN) + .short('m') + .long("manual") + .help("Search for manuals.") + .action(ArgAction::SetTrue) + .required(false), ) .arg( - Arg::new(options::BYTES) + Arg::new(options::SRC) + .short('s') + .long("source") + .action(ArgAction::SetTrue) + .help("Search for sources.") + .action(ArgAction::SetTrue) + .required(false), + ) + .arg( + Arg::new(options::SPECIFIED_BIN) .short('B') - .long("bytes") + .long("bins") + .action(ArgAction::SetTrue) + .help( + "Limit the places where whereis searches for binaries, \ + by a whitespace-separated list of directories." + ) + .action(ArgAction::SetTrue) + .required(false), + ) + .arg( + Arg::new(options::SPECIFIED_MAN) + .short('M') + .long("mans") + .action(ArgAction::SetTrue) + .help( + "Limit the places where whereis searches for manuals and documentation in Info \ + format, by a whitespace-separated list of directories." + ) + .action(ArgAction::SetTrue) + .required(false), + ) + .arg( + Arg::new(options::SPECIFIED_SRC) + .short('S') + .long("sources") .action(ArgAction::SetTrue) .help( - "Print the sizes in bytes rather than in a human-readable format. \ - The default is to print sizes in human-readable format (for example '512 KiB'). \ - Setting this flag instead prints the decimal amount of bytes with no suffix.", - ), + "Limit the places where whereis searches for sources, by a whitespace-separated \ + list of directories." + ) + .action(ArgAction::SetTrue) + .required(false), ) + + // Want to rename this in the future. + .arg( + Arg::new(options::PATH) + .short('u') + .long("source path") + .action(ArgAction::SetTrue) + .help( + "Only show the command names that have unusual entries. A command is said to be \ + unusual if it does not have just one entry of each explicitly requested type. \ + Thus 'whereis -m -u *' asks for those files in the current directory which \ + have no documentation file, or more than one." + ) + .action(ArgAction::SetTrue) + .required(false), + ) + + } diff --git a/tests/by-util/test_whereis.rs b/tests/by-util/test_whereis.rs new file mode 100644 index 00000000..b5f46cec --- /dev/null +++ b/tests/by-util/test_whereis.rs @@ -0,0 +1,47 @@ +use crate::common::util::TestScenario; + +#[test] +#[cfg(target_os = "linux")] +fn test_basic_lookup() { + new_ucmd!().arg("gcc").succeeds().stdout_contains("/usr/bin/gcc"); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_bin_only() { + new_ucmd!().arg("-b").arg("ping").succeeds().stdout_contains("/usr/bin/ping"); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_man_only() { + new_ucmd!().arg("-m").arg("nmap").succeeds().stdout_contains("/usr/share/man/man1/nmap.1.gz"); +} + +#[test] +#[cfg(target_os = "linux")] +// dig doesn't seem to have any output when passing in the -s flag. +fn test_src_only() { + new_ucmd!().arg("-s").arg("dig").succeeds().stdout_contains(""); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_output() { + + let res = new_ucmd!().arg("ping").arg("gcc").succeeds(); + let stdout = res.no_stderr().stdout_str(); + + // Non-exhaustive list of fields we expect + // Check that 'ping' and 'gcc' have their paths listed + assert!(stdout.contains("ping:")); + assert!(stdout.contains("gcc:")); + + // Check that paths are printed next to the command name, as expected + assert!(stdout.contains("/usr/bin/ping")); + assert!(stdout.contains("/usr/bin/gcc")); + + assert!(stdout.contains("/usr/lib/gcc")); + assert!(stdout.contains("/usr/share/gcc")); +} + From 543ab63d160a6df2b635fe34a9ed38223a797bc3 Mon Sep 17 00:00:00 2001 From: shale Date: Mon, 7 Apr 2025 22:10:07 -0400 Subject: [PATCH 07/14] added TODO about the command line argument parsing, not all arguments are currently supported --- src/uu/whereis/src/whereis.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/whereis/src/whereis.rs b/src/uu/whereis/src/whereis.rs index e264c6f8..16bd119e 100644 --- a/src/uu/whereis/src/whereis.rs +++ b/src/uu/whereis/src/whereis.rs @@ -205,6 +205,7 @@ fn find_in(dir: &Path, pathbuf: &PathBuf, results: &mut Vec, dir_type: D } } +// TODO: Doesn't completely all possible options like specified_bin, etc. fn print_output (options: &OutputOptions, pattern: &str, results: Vec) { let mut grouped: HashMap> = HashMap::new(); From 3983bfdee833baab702d346baab5f95f29243264 Mon Sep 17 00:00:00 2001 From: shale Date: Mon, 7 Apr 2025 22:27:14 -0400 Subject: [PATCH 08/14] ran cargo fmt and cargo clippy --- src/uu/whereis/src/constants.rs | 160 ++++++++--------- src/uu/whereis/src/main.rs | 1 - src/uu/whereis/src/whereis.rs | 301 ++++++++++++++++---------------- tests/by-util/test_whereis.rs | 27 ++- 4 files changed, 240 insertions(+), 249 deletions(-) diff --git a/src/uu/whereis/src/constants.rs b/src/uu/whereis/src/constants.rs index 8db41633..b3b76193 100644 --- a/src/uu/whereis/src/constants.rs +++ b/src/uu/whereis/src/constants.rs @@ -1,95 +1,83 @@ - // List of directories, man, src, and binary -pub const MANDIRS: [&str; 7] = [ - "/usr/man/*", - "/usr/share/man/*", - "/usr/X386/man/*", - "/usr/X11/man/*", - "/usr/TeX/man/*", - "/usr/interviews/man/mann", - "/usr/share/info", - // NULL +pub const MANDIRS: [&str; 7] = [ + "/usr/man/*", + "/usr/share/man/*", + "/usr/X386/man/*", + "/usr/X11/man/*", + "/usr/TeX/man/*", + "/usr/interviews/man/mann", + "/usr/share/info", + // NULL ]; pub const SRCDIRS: [&str; 6] = [ - "/usr/src/*", - "/usr/src/lib/libc/*", - "/usr/src/lib/libc/net/*", - "/usr/src/ucb/pascal", - "/usr/src/ucb/pascal/utilities", - "/usr/src/undoc", - // NULL + "/usr/src/*", + "/usr/src/lib/libc/*", + "/usr/src/lib/libc/net/*", + "/usr/src/ucb/pascal", + "/usr/src/ucb/pascal/utilities", + "/usr/src/undoc", + // NULL ]; - pub const BINDIRS: [&str; 46] = [ - "/usr/bin", - "/usr/sbin", - "/bin", - "/sbin", - -// #[cfg(...)] -/* -#if defined(MULTIARCHTRIPLET) - - "/lib/" MULTIARCHTRIPLET, - "/usr/lib/" MULTIARCHTRIPLET, - "/usr/local/lib/" MULTIARCHTRIPLET, - -#endif -*/ - -// #[cfg(not(...))] - - "/usr/lib", - "/usr/lib32", - "/usr/lib64", - "/etc", - "/usr/etc", - "/lib", - "/lib32", - "/lib64", - "/usr/games", - "/usr/games/bin", - "/usr/games/lib", - "/usr/emacs/etc", - "/usr/lib/emacs/*/etc", - "/usr/TeX/bin", - "/usr/tex/bin", - "/usr/interviews/bin/LINUX", - - "/usr/X11R6/bin", - "/usr/X386/bin", - "/usr/bin/X11", - "/usr/X11/bin", - "/usr/X11R5/bin", - - "/usr/local/bin", - "/usr/local/sbin", - "/usr/local/etc", - "/usr/local/lib", - "/usr/local/games", - "/usr/local/games/bin", - "/usr/local/emacs/etc", - "/usr/local/TeX/bin", - "/usr/local/tex/bin", - "/usr/local/bin/X11", - - "/usr/contrib", - "/usr/hosts", - "/usr/include", - - "/usr/g++-include", - - "/usr/ucb", - "/usr/old", - "/usr/new", - "/usr/local", - "/usr/libexec", - "/usr/share", - - "/opt/*/bin", - // NULL + "/usr/bin", + "/usr/sbin", + "/bin", + "/sbin", + // #[cfg(...)] + /* + #if defined(MULTIARCHTRIPLET) + + "/lib/" MULTIARCHTRIPLET, + "/usr/lib/" MULTIARCHTRIPLET, + "/usr/local/lib/" MULTIARCHTRIPLET, + + #endif + */ + // #[cfg(not(...))] + "/usr/lib", + "/usr/lib32", + "/usr/lib64", + "/etc", + "/usr/etc", + "/lib", + "/lib32", + "/lib64", + "/usr/games", + "/usr/games/bin", + "/usr/games/lib", + "/usr/emacs/etc", + "/usr/lib/emacs/*/etc", + "/usr/TeX/bin", + "/usr/tex/bin", + "/usr/interviews/bin/LINUX", + "/usr/X11R6/bin", + "/usr/X386/bin", + "/usr/bin/X11", + "/usr/X11/bin", + "/usr/X11R5/bin", + "/usr/local/bin", + "/usr/local/sbin", + "/usr/local/etc", + "/usr/local/lib", + "/usr/local/games", + "/usr/local/games/bin", + "/usr/local/emacs/etc", + "/usr/local/TeX/bin", + "/usr/local/tex/bin", + "/usr/local/bin/X11", + "/usr/contrib", + "/usr/hosts", + "/usr/include", + "/usr/g++-include", + "/usr/ucb", + "/usr/old", + "/usr/new", + "/usr/local", + "/usr/libexec", + "/usr/share", + "/opt/*/bin", + // NULL ]; - diff --git a/src/uu/whereis/src/main.rs b/src/uu/whereis/src/main.rs index ec7f2233..5a25b320 100644 --- a/src/uu/whereis/src/main.rs +++ b/src/uu/whereis/src/main.rs @@ -1,2 +1 @@ uucore::bin!(uu_whereis); - diff --git a/src/uu/whereis/src/whereis.rs b/src/uu/whereis/src/whereis.rs index 16bd119e..2cf0b360 100644 --- a/src/uu/whereis/src/whereis.rs +++ b/src/uu/whereis/src/whereis.rs @@ -3,19 +3,22 @@ // file that was distributed with this source code. use clap::{crate_version, Arg, ArgAction, Command}; +use glob::glob; use serde::Serialize; -use std::{fs, collections::HashSet, collections::HashMap, path::Path, path::PathBuf, os::unix::fs::MetadataExt}; +use std::{ + collections::HashMap, collections::HashSet, fs, os::unix::fs::MetadataExt, path::Path, + path::PathBuf, +}; use uucore::{error::UResult, format_usage, help_about, help_usage}; -use glob::glob; mod constants; -use crate::constants::{SRCDIRS, BINDIRS, MANDIRS}; +use crate::constants::{BINDIRS, MANDIRS, SRCDIRS}; mod options { - pub const BIN: &str = "binaries"; - pub const MAN: &str = "manuals"; - pub const SRC: &str = "sources"; - pub const PATH: &str = "lookups"; + pub const BIN: &str = "binaries"; + pub const MAN: &str = "manuals"; + pub const SRC: &str = "sources"; + pub const PATH: &str = "lookups"; pub const SPECIFIED_BIN: &str = "listed binaries"; pub const SPECIFIED_MAN: &str = "listed manuals"; @@ -28,132 +31,125 @@ const USAGE: &str = help_usage!("whereis.md"); // Directories are usually manual pages dirs, binary dirs or source dirs. Hopefully not unknown. #[derive(Serialize, Copy, Clone, Debug, Eq, Hash, PartialEq)] pub enum DirType { - BIN, - MAN, - SRC, - UNK, + BIN, + MAN, + SRC, + UNK, } // Store the metadata for a file #[derive(Serialize, Clone, Debug)] pub struct WhDir { - - #[serde(skip_serializing)] - #[serde(skip_deserializing)] - metadata: Option, - path: PathBuf, - type_of_dir: DirType -} + #[serde(skip_serializing)] + #[serde(skip_deserializing)] + metadata: Option, + path: PathBuf, + type_of_dir: DirType, +} impl WhDir { - - fn new (path: PathBuf, type_of_dir: DirType) -> Self { - Self { - metadata: fs::metadata(&path).ok(), - path, - type_of_dir, - } - } - + fn new(path: PathBuf, type_of_dir: DirType) -> Self { + Self { + metadata: fs::metadata(&path).ok(), + path, + type_of_dir, + } + } } // Use a vector to store the list of directories. Additionally keep a HashSet of the inode number and st_dev ID. #[derive(Serialize, Debug)] -pub struct WhDirList { - list: Vec, - seen_files: HashSet<(u64, u64)>, +pub struct WhDirList { + list: Vec, + seen_files: HashSet<(u64, u64)>, } impl WhDirList { + fn new() -> Self { + Self { + list: Vec::new(), + seen_files: HashSet::new(), + } + } + + fn construct_dir_list(&mut self, dir_type: DirType, paths: &[&str]) { + for path in paths { + let pathbuf = PathBuf::from(path); + if !path.contains('*') { + self.add_dir(WhDir::new(pathbuf, dir_type)); + } else { + self.add_sub_dirs(&pathbuf, dir_type); + } + } + } + + // Use (ino) inode number and (st_dev) ID of device containing the file to keep track of whats unique. + fn add_dir(&mut self, dir: WhDir) { + if self.list.iter().any(|d| d.path == dir.path) { + return; + } + + if dir.metadata.is_some() { + let dev = dir.metadata.clone().unwrap().dev(); + let ino = dir.metadata.clone().unwrap().ino(); + + if self.seen_files.insert((dev, ino)) { + self.list.push(dir); + } + } + } - fn new () -> Self { - Self { - list: Vec::new(), - seen_files: HashSet::new(), - } - } - - fn construct_dir_list (&mut self, dir_type: DirType, paths: &[&str]) { - for path in paths { - let pathbuf = PathBuf::from(path); - if !path.contains('*') { - self.add_dir (WhDir::new(pathbuf, dir_type.clone())); - } else { - self.add_sub_dirs(&pathbuf, dir_type.clone()); - } - } - } - - - // Use (ino) inode number and (st_dev) ID of device containing the file to keep track of whats unique. - fn add_dir (&mut self, dir: WhDir) { - if self.list.iter().any(|d| d.path == dir.path) { - return; - } - - if dir.metadata.is_some() { - let dev = dir.metadata.clone().unwrap().dev(); - let ino = dir.metadata.clone().unwrap().ino(); - - if self.seen_files.insert((dev, ino)) { - self.list.push(dir); - } - } - } - - #[allow(dead_code)] - fn remove_dir (&mut self, dir: &WhDir) { - self.list.retain(|d| d.path != dir.path); - } - - - // TODO: We need to do something with the entry if an error occurs. - fn add_sub_dirs (&mut self, parent_dir: &PathBuf, dir_type: DirType) { - for entry in glob(&parent_dir.display().to_string()).expect("Failed to read glob pattern") { - match entry { - Ok(path) if path.is_dir() => { - self.add_dir(WhDir::new(path, dir_type.clone())); - } - Ok(_) => todo!(), - Err(_e) => todo!(), - } - } - } - - // A debug function. - #[allow(dead_code)] - fn list_dirs (&self) { - for dir in &self.list { - let dir_type = whereis_type_to_name (dir.type_of_dir); - println!("{:?} : {:?}", dir_type, dir.path.display()); - } - } - - - fn lookup(&self, pattern: &str, dir_type: DirType) -> Vec { - let mut results = Vec::new(); - let pathbuf_pattern = PathBuf::from(pattern); - - for dir in &self.list { - if dir.type_of_dir == dir_type { - find_in(&dir.path, &pathbuf_pattern, &mut results, dir.type_of_dir); - } - } - - results - } + #[allow(dead_code)] + fn remove_dir(&mut self, dir: &WhDir) { + self.list.retain(|d| d.path != dir.path); + } + + // TODO: We need to do something with the entry if an error occurs. + fn add_sub_dirs(&mut self, parent_dir: &Path, dir_type: DirType) { + for entry in glob(&parent_dir.display().to_string()).expect("Failed to read glob pattern") { + match entry { + Ok(path) if path.is_dir() => { + self.add_dir(WhDir::new(path, dir_type)); + } + Ok(_) => todo!(), + Err(_e) => todo!(), + } + } + } + + // A debug function. + #[allow(dead_code)] + fn list_dirs(&self) { + for dir in &self.list { + let dir_type = whereis_type_to_name(dir.type_of_dir); + println!("{:?} : {:?}", dir_type, dir.path.display()); + } + } + + fn lookup(&self, pattern: &str, dir_type: DirType) -> Vec { + let mut results = Vec::new(); + let pathbuf_pattern = PathBuf::from(pattern); + + for dir in &self.list { + if dir.type_of_dir == dir_type { + find_in(&dir.path, &pathbuf_pattern, &mut results, dir.type_of_dir); + } + } + + results + } } -pub fn whereis_type_to_name (dir_type: DirType) -> &'static str { - match dir_type { - DirType::MAN => "man", - DirType::BIN => "bin", - DirType::SRC => "src", - DirType::UNK => "???", - } +pub fn whereis_type_to_name(dir_type: DirType) -> &'static str { + match dir_type { + DirType::MAN => "man", + DirType::BIN => "bin", + DirType::SRC => "src", + DirType::UNK => "???", + } } -// Almost an exact ripoff from the C source. +// Almost an exact ripoff from the C source. fn filename_equal(cp: &PathBuf, dp: &str, dir_type: DirType) -> bool { let cp_str = match cp.file_name().and_then(|s| s.to_str()) { Some(s) => s, @@ -191,7 +187,6 @@ fn filename_equal(cp: &PathBuf, dp: &str, dir_type: DirType) -> bool { } } - fn find_in(dir: &Path, pathbuf: &PathBuf, results: &mut Vec, dir_type: DirType) { if let Ok(entries) = fs::read_dir(dir) { for entry in entries.flatten() { @@ -206,8 +201,8 @@ fn find_in(dir: &Path, pathbuf: &PathBuf, results: &mut Vec, dir_type: D } // TODO: Doesn't completely all possible options like specified_bin, etc. -fn print_output (options: &OutputOptions, pattern: &str, results: Vec) { - let mut grouped: HashMap> = HashMap::new(); +fn print_output(options: &OutputOptions, pattern: &str, results: Vec) { + let mut grouped: HashMap> = HashMap::new(); // Split results by type, grouping MAN, BIN and SRC. for path in results { @@ -257,62 +252,61 @@ fn print_output (options: &OutputOptions, pattern: &str, results: Vec) { println!(); } - #[uucore::main] -pub fn uumain(args: impl uucore::Args) -> UResult <()> { - - let matches: clap::ArgMatches = uu_app().try_get_matches_from(args)?; +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + let matches: clap::ArgMatches = uu_app().try_get_matches_from(args)?; let output_options = OutputOptions { - search_bin: matches.get_flag(options::BIN), - search_man: matches.get_flag(options::MAN), - search_src: matches.get_flag(options::SRC), - path_given: matches.get_flag(options::PATH), - - search_specific_bin: matches.get_flag(options::SPECIFIED_BIN), - search_specific_man: matches.get_flag(options::SPECIFIED_MAN), - search_specific_src: matches.get_flag(options::SPECIFIED_SRC), - }; + search_bin: matches.get_flag(options::BIN), + search_man: matches.get_flag(options::MAN), + search_src: matches.get_flag(options::SRC), + path_given: matches.get_flag(options::PATH), + + search_specific_bin: matches.get_flag(options::SPECIFIED_BIN), + search_specific_man: matches.get_flag(options::SPECIFIED_MAN), + search_specific_src: matches.get_flag(options::SPECIFIED_SRC), + }; - let mut dir_list = WhDirList::new(); + let mut dir_list = WhDirList::new(); - dir_list.construct_dir_list(DirType::BIN, &BINDIRS); - dir_list.construct_dir_list(DirType::MAN, &MANDIRS); - dir_list.construct_dir_list(DirType::SRC, &SRCDIRS); + dir_list.construct_dir_list(DirType::BIN, &BINDIRS); + dir_list.construct_dir_list(DirType::MAN, &MANDIRS); + dir_list.construct_dir_list(DirType::SRC, &SRCDIRS); - let names: Vec<_> = matches.get_many::("names") - .unwrap() - .map(|s| s.as_str()) - .collect(); + let names: Vec<_> = matches + .get_many::("names") + .unwrap() + .map(|s| s.as_str()) + .collect(); - // Search for the names that were passed into the program. - for pattern in names { - let mut results = dir_list.lookup(pattern, DirType::BIN); - results.append(&mut dir_list.lookup(pattern, DirType::MAN)); - results.append(&mut dir_list.lookup(pattern, DirType::SRC)); + // Search for the names that were passed into the program. + for pattern in names { + let mut results = dir_list.lookup(pattern, DirType::BIN); + results.append(&mut dir_list.lookup(pattern, DirType::MAN)); + results.append(&mut dir_list.lookup(pattern, DirType::SRC)); - print_output(&output_options, pattern, results); - } + print_output(&output_options, pattern, results); + } - Ok(()) + Ok(()) } - -// TODO: Implement the necessary behavior for path_given and other fields with the dead_code macro. + +// TODO: Implement the necessary behavior for path_given and other fields with the dead_code macro. struct OutputOptions { search_bin: bool, search_man: bool, search_src: bool, - #[allow(dead_code)] + #[allow(dead_code)] path_given: bool, - #[allow(dead_code)] + #[allow(dead_code)] search_specific_bin: bool, - #[allow(dead_code)] + #[allow(dead_code)] search_specific_man: bool, - #[allow(dead_code)] + #[allow(dead_code)] search_specific_src: bool, } @@ -406,7 +400,4 @@ pub fn uu_app() -> Command { .action(ArgAction::SetTrue) .required(false), ) - - } - diff --git a/tests/by-util/test_whereis.rs b/tests/by-util/test_whereis.rs index b5f46cec..f4ad4b3c 100644 --- a/tests/by-util/test_whereis.rs +++ b/tests/by-util/test_whereis.rs @@ -3,32 +3,46 @@ use crate::common::util::TestScenario; #[test] #[cfg(target_os = "linux")] fn test_basic_lookup() { - new_ucmd!().arg("gcc").succeeds().stdout_contains("/usr/bin/gcc"); + new_ucmd!() + .arg("gcc") + .succeeds() + .stdout_contains("/usr/bin/gcc"); } #[test] #[cfg(target_os = "linux")] fn test_bin_only() { - new_ucmd!().arg("-b").arg("ping").succeeds().stdout_contains("/usr/bin/ping"); + new_ucmd!() + .arg("-b") + .arg("ping") + .succeeds() + .stdout_contains("/usr/bin/ping"); } #[test] #[cfg(target_os = "linux")] fn test_man_only() { - new_ucmd!().arg("-m").arg("nmap").succeeds().stdout_contains("/usr/share/man/man1/nmap.1.gz"); + new_ucmd!() + .arg("-m") + .arg("nmap") + .succeeds() + .stdout_contains("/usr/share/man/man1/nmap.1.gz"); } #[test] #[cfg(target_os = "linux")] // dig doesn't seem to have any output when passing in the -s flag. fn test_src_only() { - new_ucmd!().arg("-s").arg("dig").succeeds().stdout_contains(""); + new_ucmd!() + .arg("-s") + .arg("dig") + .succeeds() + .stdout_contains(""); } #[test] #[cfg(target_os = "linux")] fn test_output() { - let res = new_ucmd!().arg("ping").arg("gcc").succeeds(); let stdout = res.no_stderr().stdout_str(); @@ -40,8 +54,7 @@ fn test_output() { // Check that paths are printed next to the command name, as expected assert!(stdout.contains("/usr/bin/ping")); assert!(stdout.contains("/usr/bin/gcc")); - + assert!(stdout.contains("/usr/lib/gcc")); assert!(stdout.contains("/usr/share/gcc")); } - From bde93c4c04b4825250a008cf7c3d31c58262a3ec Mon Sep 17 00:00:00 2001 From: spud0 <123281137+spud0@users.noreply.github.com> Date: Tue, 22 Apr 2025 23:01:55 -0400 Subject: [PATCH 09/14] fixed the if statement condition with path.contains for readability Co-authored-by: Daniel Hofstetter --- src/uu/whereis/src/whereis.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uu/whereis/src/whereis.rs b/src/uu/whereis/src/whereis.rs index 2cf0b360..aa0678aa 100644 --- a/src/uu/whereis/src/whereis.rs +++ b/src/uu/whereis/src/whereis.rs @@ -75,10 +75,10 @@ impl WhDirList { fn construct_dir_list(&mut self, dir_type: DirType, paths: &[&str]) { for path in paths { let pathbuf = PathBuf::from(path); - if !path.contains('*') { - self.add_dir(WhDir::new(pathbuf, dir_type)); - } else { + if path.contains('*') { self.add_sub_dirs(&pathbuf, dir_type); + } else { + self.add_dir(WhDir::new(pathbuf, dir_type)); } } } From 6b8395eba3e4b8df256831735c8765cf7c4973ba Mon Sep 17 00:00:00 2001 From: spud0 <123281137+spud0@users.noreply.github.com> Date: Tue, 22 Apr 2025 23:38:28 -0400 Subject: [PATCH 10/14] removed inaccurate comment about dig command Co-authored-by: Daniel Hofstetter --- tests/by-util/test_whereis.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/by-util/test_whereis.rs b/tests/by-util/test_whereis.rs index f4ad4b3c..596d8497 100644 --- a/tests/by-util/test_whereis.rs +++ b/tests/by-util/test_whereis.rs @@ -31,7 +31,6 @@ fn test_man_only() { #[test] #[cfg(target_os = "linux")] -// dig doesn't seem to have any output when passing in the -s flag. fn test_src_only() { new_ucmd!() .arg("-s") From 2172d65974f1dfcfa2a2ed2cd9246eff821cc5fe Mon Sep 17 00:00:00 2001 From: shale Date: Thu, 24 Apr 2025 13:12:50 -0400 Subject: [PATCH 11/14] changed test for dig to match output of dig, changed test for nmap since its not installed by default and added the header for the file --- tests/by-util/test_whereis.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/by-util/test_whereis.rs b/tests/by-util/test_whereis.rs index 596d8497..696bb97a 100644 --- a/tests/by-util/test_whereis.rs +++ b/tests/by-util/test_whereis.rs @@ -1,3 +1,8 @@ +// This file is part of the uutils util-linux package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + use crate::common::util::TestScenario; #[test] @@ -24,9 +29,9 @@ fn test_bin_only() { fn test_man_only() { new_ucmd!() .arg("-m") - .arg("nmap") + .arg("ls") .succeeds() - .stdout_contains("/usr/share/man/man1/nmap.1.gz"); + .stdout_contains("/usr/share/man"); } #[test] @@ -36,7 +41,7 @@ fn test_src_only() { .arg("-s") .arg("dig") .succeeds() - .stdout_contains(""); + .stdout_is("dig:\n"); } #[test] From 3a4f53bb556a51fea19747839445c83e7d0bbd5b Mon Sep 17 00:00:00 2001 From: shale Date: Thu, 24 Apr 2025 23:05:13 -0400 Subject: [PATCH 12/14] fixed the naming of the constants (MANDIR, etc) to include an underscore (MAN_DIRS, etc) --- src/uu/whereis/src/constants.rs | 6 +++--- src/uu/whereis/src/whereis.rs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/uu/whereis/src/constants.rs b/src/uu/whereis/src/constants.rs index b3b76193..1199fa1a 100644 --- a/src/uu/whereis/src/constants.rs +++ b/src/uu/whereis/src/constants.rs @@ -1,6 +1,6 @@ // List of directories, man, src, and binary -pub const MANDIRS: [&str; 7] = [ +pub const MAN_DIRS: [&str; 7] = [ "/usr/man/*", "/usr/share/man/*", "/usr/X386/man/*", @@ -11,7 +11,7 @@ pub const MANDIRS: [&str; 7] = [ // NULL ]; -pub const SRCDIRS: [&str; 6] = [ +pub const SRC_DIRS: [&str; 6] = [ "/usr/src/*", "/usr/src/lib/libc/*", "/usr/src/lib/libc/net/*", @@ -21,7 +21,7 @@ pub const SRCDIRS: [&str; 6] = [ // NULL ]; -pub const BINDIRS: [&str; 46] = [ +pub const BIN_DIRS: [&str; 46] = [ "/usr/bin", "/usr/sbin", "/bin", diff --git a/src/uu/whereis/src/whereis.rs b/src/uu/whereis/src/whereis.rs index aa0678aa..29a98f83 100644 --- a/src/uu/whereis/src/whereis.rs +++ b/src/uu/whereis/src/whereis.rs @@ -12,7 +12,7 @@ use std::{ use uucore::{error::UResult, format_usage, help_about, help_usage}; mod constants; -use crate::constants::{BINDIRS, MANDIRS, SRCDIRS}; +use crate::constants::{BIN_DIRS, MAN_DIRS, SRC_DIRS}; mod options { pub const BIN: &str = "binaries"; @@ -269,9 +269,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut dir_list = WhDirList::new(); - dir_list.construct_dir_list(DirType::BIN, &BINDIRS); - dir_list.construct_dir_list(DirType::MAN, &MANDIRS); - dir_list.construct_dir_list(DirType::SRC, &SRCDIRS); + dir_list.construct_dir_list(DirType::BIN, &BIN_DIRS); + dir_list.construct_dir_list(DirType::MAN, &MAN_DIRS); + dir_list.construct_dir_list(DirType::SRC, &SRC_DIRS); let names: Vec<_> = matches .get_many::("names") From 77104e0970d97bda657a7268bab4ce2df5c08185 Mon Sep 17 00:00:00 2001 From: shale Date: Thu, 24 Apr 2025 23:13:21 -0400 Subject: [PATCH 13/14] changed the uppercase enum states to UpperCamelCase and expanded the names --- src/uu/whereis/src/whereis.rs | 46 +++++++++++++++++------------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/uu/whereis/src/whereis.rs b/src/uu/whereis/src/whereis.rs index 29a98f83..4ef30e3f 100644 --- a/src/uu/whereis/src/whereis.rs +++ b/src/uu/whereis/src/whereis.rs @@ -31,10 +31,10 @@ const USAGE: &str = help_usage!("whereis.md"); // Directories are usually manual pages dirs, binary dirs or source dirs. Hopefully not unknown. #[derive(Serialize, Copy, Clone, Debug, Eq, Hash, PartialEq)] pub enum DirType { - BIN, - MAN, - SRC, - UNK, + Binary, + Manual, + Source, + Unknown, } // Store the metadata for a file @@ -142,10 +142,10 @@ impl WhDirList { pub fn whereis_type_to_name(dir_type: DirType) -> &'static str { match dir_type { - DirType::MAN => "man", - DirType::BIN => "bin", - DirType::SRC => "src", - DirType::UNK => "???", + DirType::Manual => "man", + DirType::Binary => "bin", + DirType::Source => "src", + DirType::Unknown => "???", } } @@ -158,11 +158,11 @@ fn filename_equal(cp: &PathBuf, dp: &str, dir_type: DirType) -> bool { let mut dp_trimmed = dp; - if dir_type == DirType::SRC && dp_trimmed.starts_with("s.") { + if dir_type == DirType::Source && dp_trimmed.starts_with("s.") { return filename_equal(cp, &dp_trimmed[2..], dir_type); } - if dir_type == DirType::MAN { + if dir_type == DirType::Manual { for ext in [".Z", ".gz", ".xz", ".bz2", ".zst"] { if let Some(stripped) = dp_trimmed.strip_suffix(ext) { dp_trimmed = stripped; @@ -178,7 +178,7 @@ fn filename_equal(cp: &PathBuf, dp: &str, dir_type: DirType) -> bool { match (cp_chars.next(), dp_chars.next()) { (Some(c1), Some(c2)) if c1 == c2 => continue, (None, None) => return true, // both ended - (None, Some('.')) if dir_type != DirType::BIN => { + (None, Some('.')) if dir_type != DirType::Binary => { // cp ended, dp has .section return true; } @@ -207,11 +207,11 @@ fn print_output(options: &OutputOptions, pattern: &str, results: Vec) { // Split results by type, grouping MAN, BIN and SRC. for path in results { if path.contains("/bin/") { - grouped.entry(DirType::BIN).or_default().push(path); + grouped.entry(DirType::Binary).or_default().push(path); } else if path.contains("/man") || path.contains("/share/man") { - grouped.entry(DirType::MAN).or_default().push(path); + grouped.entry(DirType::Manual).or_default().push(path); } else { - grouped.entry(DirType::SRC).or_default().push(path); + grouped.entry(DirType::Source).or_default().push(path); } } @@ -220,21 +220,21 @@ fn print_output(options: &OutputOptions, pattern: &str, results: Vec) { // If *any* of the search flags are set, print according to them if options.search_bin || options.search_man || options.search_src { if options.search_bin { - if let Some(paths) = grouped.get(&DirType::BIN) { + if let Some(paths) = grouped.get(&DirType::Binary) { for path in paths { print!(" {}", path); } } } if options.search_man { - if let Some(paths) = grouped.get(&DirType::MAN) { + if let Some(paths) = grouped.get(&DirType::Manual) { for path in paths { print!(" {}", path); } } } if options.search_src { - if let Some(paths) = grouped.get(&DirType::SRC) { + if let Some(paths) = grouped.get(&DirType::Source) { for path in paths { print!(" {}", path); } @@ -269,9 +269,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut dir_list = WhDirList::new(); - dir_list.construct_dir_list(DirType::BIN, &BIN_DIRS); - dir_list.construct_dir_list(DirType::MAN, &MAN_DIRS); - dir_list.construct_dir_list(DirType::SRC, &SRC_DIRS); + dir_list.construct_dir_list(DirType::Binary, &BIN_DIRS); + dir_list.construct_dir_list(DirType::Manual, &MAN_DIRS); + dir_list.construct_dir_list(DirType::Source, &SRC_DIRS); let names: Vec<_> = matches .get_many::("names") @@ -281,9 +281,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // Search for the names that were passed into the program. for pattern in names { - let mut results = dir_list.lookup(pattern, DirType::BIN); - results.append(&mut dir_list.lookup(pattern, DirType::MAN)); - results.append(&mut dir_list.lookup(pattern, DirType::SRC)); + let mut results = dir_list.lookup(pattern, DirType::Binary); + results.append(&mut dir_list.lookup(pattern, DirType::Manual)); + results.append(&mut dir_list.lookup(pattern, DirType::Source)); print_output(&output_options, pattern, results); } From 761ecfd46111f8e0f7716414897ae6e68d3aa9b0 Mon Sep 17 00:00:00 2001 From: shale Date: Thu, 24 Apr 2025 23:26:37 -0400 Subject: [PATCH 14/14] changed type_of_dir to dir_type for consistency --- src/uu/whereis/src/whereis.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/uu/whereis/src/whereis.rs b/src/uu/whereis/src/whereis.rs index 4ef30e3f..dc0d097f 100644 --- a/src/uu/whereis/src/whereis.rs +++ b/src/uu/whereis/src/whereis.rs @@ -44,15 +44,15 @@ pub struct WhDir { #[serde(skip_deserializing)] metadata: Option, path: PathBuf, - type_of_dir: DirType, + dir_type: DirType, } impl WhDir { - fn new(path: PathBuf, type_of_dir: DirType) -> Self { + fn new(path: PathBuf, dir_type: DirType) -> Self { Self { metadata: fs::metadata(&path).ok(), path, - type_of_dir, + dir_type, } } } @@ -121,7 +121,7 @@ impl WhDirList { #[allow(dead_code)] fn list_dirs(&self) { for dir in &self.list { - let dir_type = whereis_type_to_name(dir.type_of_dir); + let dir_type = whereis_type_to_name(dir.dir_type); println!("{:?} : {:?}", dir_type, dir.path.display()); } } @@ -131,8 +131,8 @@ impl WhDirList { let pathbuf_pattern = PathBuf::from(pattern); for dir in &self.list { - if dir.type_of_dir == dir_type { - find_in(&dir.path, &pathbuf_pattern, &mut results, dir.type_of_dir); + if dir.dir_type == dir_type { + find_in(&dir.path, &pathbuf_pattern, &mut results, dir.dir_type); } }