From 959e80ebc51c5b2acc5ca5f70a2da08a84eeef40 Mon Sep 17 00:00:00 2001 From: caomengxuan666 <2507560089@qq.com> Date: Thu, 4 Jun 2026 20:30:22 +0800 Subject: [PATCH 1/2] Add Windows-native which command --- Cargo.lock | 9 ++ Cargo.toml | 1 + deps/ntwhich/Cargo.toml | 21 +++ deps/ntwhich/src/main.rs | 3 + deps/ntwhich/src/which.rs | 278 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 312 insertions(+) create mode 100644 deps/ntwhich/Cargo.toml create mode 100644 deps/ntwhich/src/main.rs create mode 100644 deps/ntwhich/src/which.rs diff --git a/Cargo.lock b/Cargo.lock index 79e71f5..fea33e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -497,6 +497,7 @@ dependencies = [ "uu_unlink", "uu_uptime", "uu_wc", + "uu_which", "uu_yes", "uucore", "windows-sys 0.59.0", @@ -3444,6 +3445,14 @@ dependencies = [ "uucore", ] +[[package]] +name = "uu_which" +version = "2026.5.29" +dependencies = [ + "clap", + "uucore", +] + [[package]] name = "uu_yes" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 5e97da5..c4932f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,6 +125,7 @@ uptime = { package = "uu_uptime", path = "deps/coreutils/src/uu/uptime" } findutils = { package = "findutils", path = "deps/findutils" } grep = { package = "uu_grep", path = "deps/grep" } ntfind = { package = "find", path = "deps/ntfind" } +ntwhich = { package = "uu_which", path = "deps/ntwhich" } # For registry access in main.rs [dependencies.windows-sys] diff --git a/deps/ntwhich/Cargo.toml b/deps/ntwhich/Cargo.toml new file mode 100644 index 0000000..7a55c14 --- /dev/null +++ b/deps/ntwhich/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "uu_which" +description = "which ~ locate a command in PATH" +repository = "https://github.com/microsoft/coreutils" +version = "2026.5.29" +license = "MIT" +edition = "2024" +rust-version = "1.88.0" +publish = false + +[lib] +path = "src/which.rs" +doctest = false + +[dependencies] +clap = { version = "4.5", features = ["wrap_help", "cargo"] } +uucore = { path = "../coreutils/src/uucore" } + +[[bin]] +name = "which" +path = "src/main.rs" diff --git a/deps/ntwhich/src/main.rs b/deps/ntwhich/src/main.rs new file mode 100644 index 0000000..499d16f --- /dev/null +++ b/deps/ntwhich/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + std::process::exit(uu_which::uumain(uucore::args_os())); +} diff --git a/deps/ntwhich/src/which.rs b/deps/ntwhich/src/which.rs new file mode 100644 index 0000000..a96be76 --- /dev/null +++ b/deps/ntwhich/src/which.rs @@ -0,0 +1,278 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::collections::HashSet; +use std::env; +use std::ffi::{OsStr, OsString}; +use std::io::{self, Write as _}; +use std::path::{Path, PathBuf}; + +use clap::{Arg, ArgAction, Command}; +use uucore::Args; + +const VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub fn uumain(args: impl Args) -> i32 { + match uumain_impl(args) { + Ok(code) => code, + Err(err) => { + let _ = writeln!(io::stderr(), "which: {err}"); + 1 + } + } +} + +fn uumain_impl(args: impl Args) -> Result { + let matches = match uu_app().try_get_matches_from(args) { + Ok(matches) => matches, + Err(err) => { + let _ = err.print(); + return Ok(if err.use_stderr() { 1 } else { 0 }); + } + }; + + let all = matches.get_flag("all"); + let Some(commands) = matches.get_many::("commands") else { + return Err("missing command operand".to_string()); + }; + + let mut all_found = true; + for command in commands { + let hits = find_command(command, all); + if hits.is_empty() { + all_found = false; + continue; + } + + for hit in hits { + println!("{}", hit.display()); + } + } + + Ok(if all_found { 0 } else { 1 }) +} + +pub fn uu_app() -> Command { + Command::new("which") + .version(VERSION) + .about("Locate a command in PATH.") + .override_usage("which [OPTION]... COMMAND...") + .arg( + Arg::new("all") + .short('a') + .long("all") + .help("print all matching pathnames of each command") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("commands") + .value_name("COMMAND") + .num_args(1..) + .value_parser(clap::value_parser!(OsString)), + ) +} + +fn find_command(command: &OsStr, all: bool) -> Vec { + let mut hits = Vec::new(); + let mut seen = HashSet::new(); + let pathext = pathext(); + + let mut push_hit = |path: PathBuf| { + let key = normalize_seen_key(&path); + if seen.insert(key) { + hits.push(path); + } + }; + + if has_path_separator(Path::new(command)) { + for candidate in candidates(PathBuf::from(command), &pathext) { + if is_regular_file(&candidate) { + push_hit(candidate); + if !all { + return hits; + } + } + } + return hits; + } + + let Some(path_var) = env::var_os("PATH") else { + return hits; + }; + + for dir in env::split_paths(&path_var) { + if dir.as_os_str().is_empty() { + continue; + } + + let base = dir.join(command); + for candidate in candidates(base, &pathext) { + if is_regular_file(&candidate) { + push_hit(candidate); + if !all { + return hits; + } + } + } + } + + hits +} + +fn candidates(base: PathBuf, pathext: &[OsString]) -> Vec { + let mut out = vec![base.clone()]; + if base.extension().is_some() { + return out; + } + + for ext in pathext { + let mut candidate = base.clone(); + candidate.as_mut_os_string().push(ext); + out.push(candidate); + } + + out +} + +fn pathext() -> Vec { + let Some(value) = env::var_os("PATHEXT") else { + return default_pathext(); + }; + + let mut out = Vec::new(); + for ext in value.to_string_lossy().split(';') { + if ext.is_empty() { + continue; + } + + let ext = if ext.starts_with('.') { + ext.to_string() + } else { + format!(".{ext}") + }; + out.push(OsString::from(ext)); + } + + if out.is_empty() { + default_pathext() + } else { + out + } +} + +fn default_pathext() -> Vec { + [".COM", ".EXE", ".BAT", ".CMD"] + .into_iter() + .map(OsString::from) + .collect() +} + +fn has_path_separator(path: &Path) -> bool { + let path = path.as_os_str().to_string_lossy(); + path.contains('/') || path.contains('\\') +} + +fn is_regular_file(path: &Path) -> bool { + path.metadata().is_ok_and(|metadata| metadata.is_file()) +} + +fn normalize_seen_key(path: &Path) -> String { + path.as_os_str().to_string_lossy().to_lowercase() +} + +#[cfg(test)] +mod tests { + use std::fs; + use std::sync::Mutex; + use std::time::{SystemTime, UNIX_EPOCH}; + + use super::*; + + static TEST_LOCK: Mutex<()> = Mutex::new(()); + + struct EnvGuard { + key: &'static str, + old: Option, + } + + impl EnvGuard { + fn set(key: &'static str, value: impl Into) -> Self { + let old = env::var_os(key); + unsafe { + env::set_var(key, value.into()); + } + Self { key, old } + } + } + + impl Drop for EnvGuard { + fn drop(&mut self) { + unsafe { + if let Some(old) = &self.old { + env::set_var(self.key, old); + } else { + env::remove_var(self.key); + } + } + } + } + + fn temp_dir() -> PathBuf { + let unique = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + let path = env::temp_dir().join(format!("ntwhich-test-{unique}")); + fs::create_dir(&path).unwrap(); + path + } + + #[test] + fn finds_first_match_with_pathext() { + let _lock = TEST_LOCK.lock().unwrap(); + let dir = temp_dir(); + fs::write(dir.join("tool.EXE"), []).unwrap(); + let _path = EnvGuard::set("PATH", dir.as_os_str()); + let _pathext = EnvGuard::set("PATHEXT", ".EXE"); + + let hits = find_command(OsStr::new("tool"), false); + + assert_eq!(hits, vec![dir.join("tool.EXE")]); + fs::remove_dir_all(dir).unwrap(); + } + + #[test] + fn all_returns_all_matches() { + let _lock = TEST_LOCK.lock().unwrap(); + let dir1 = temp_dir(); + let dir2 = temp_dir(); + fs::write(dir1.join("tool.CMD"), []).unwrap(); + fs::write(dir2.join("tool.CMD"), []).unwrap(); + let joined = env::join_paths([dir1.as_os_str(), dir2.as_os_str()]).unwrap(); + let _path = EnvGuard::set("PATH", joined); + let _pathext = EnvGuard::set("PATHEXT", ".CMD"); + + let hits = find_command(OsStr::new("tool"), true); + + assert_eq!(hits, vec![dir1.join("tool.CMD"), dir2.join("tool.CMD")]); + fs::remove_dir_all(dir1).unwrap(); + fs::remove_dir_all(dir2).unwrap(); + } + + #[test] + fn searches_explicit_relative_path() { + let _lock = TEST_LOCK.lock().unwrap(); + let dir = temp_dir(); + let old_dir = env::current_dir().unwrap(); + fs::create_dir(dir.join("bin")).unwrap(); + fs::write(dir.join("bin").join("tool.EXE"), []).unwrap(); + let _pathext = EnvGuard::set("PATHEXT", ".EXE"); + env::set_current_dir(&dir).unwrap(); + + let hits = find_command(OsStr::new("bin/tool"), false); + + env::set_current_dir(old_dir).unwrap(); + assert_eq!(hits, vec![PathBuf::from("bin/tool.EXE")]); + fs::remove_dir_all(dir).unwrap(); + } +} From 08fa736072e2e85b32b114856019c59fd12db018 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 21 Aug 2026 01:18:57 +0200 Subject: [PATCH 2/2] Fix several issues --- Cargo.lock | 2 +- Cargo.toml | 2 +- deps/ntwhich/src/main.rs | 3 - deps/ntwhich/src/which.rs | 278 ---------------- deps/which/Cargo.lock | 502 +++++++++++++++++++++++++++++ deps/{ntwhich => which}/Cargo.toml | 17 +- deps/which/src/lib.rs | 143 ++++++++ deps/which/src/main.rs | 4 + 8 files changed, 659 insertions(+), 292 deletions(-) delete mode 100644 deps/ntwhich/src/main.rs delete mode 100644 deps/ntwhich/src/which.rs create mode 100644 deps/which/Cargo.lock rename deps/{ntwhich => which}/Cargo.toml (61%) create mode 100644 deps/which/src/lib.rs create mode 100644 deps/which/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 57c11a9..a7ff908 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3448,7 +3448,7 @@ dependencies = [ [[package]] name = "uu_which" -version = "2026.5.29" +version = "0.0.0" dependencies = [ "clap", "uucore", diff --git a/Cargo.toml b/Cargo.toml index d094aa1..890e6da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,7 +127,7 @@ uptime = { package = "uu_uptime", path = "deps/coreutils/src/uu/uptime" } findutils = { package = "findutils", path = "deps/findutils" } grep = { package = "uu_grep", path = "deps/grep" } ntfind = { package = "find", path = "deps/ntfind" } -ntwhich = { package = "uu_which", path = "deps/ntwhich" } +which = { package = "uu_which", path = "deps/which" } # For registry access in main.rs [dependencies.windows-sys] diff --git a/deps/ntwhich/src/main.rs b/deps/ntwhich/src/main.rs deleted file mode 100644 index 499d16f..0000000 --- a/deps/ntwhich/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - std::process::exit(uu_which::uumain(uucore::args_os())); -} diff --git a/deps/ntwhich/src/which.rs b/deps/ntwhich/src/which.rs deleted file mode 100644 index a96be76..0000000 --- a/deps/ntwhich/src/which.rs +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use std::collections::HashSet; -use std::env; -use std::ffi::{OsStr, OsString}; -use std::io::{self, Write as _}; -use std::path::{Path, PathBuf}; - -use clap::{Arg, ArgAction, Command}; -use uucore::Args; - -const VERSION: &str = env!("CARGO_PKG_VERSION"); - -pub fn uumain(args: impl Args) -> i32 { - match uumain_impl(args) { - Ok(code) => code, - Err(err) => { - let _ = writeln!(io::stderr(), "which: {err}"); - 1 - } - } -} - -fn uumain_impl(args: impl Args) -> Result { - let matches = match uu_app().try_get_matches_from(args) { - Ok(matches) => matches, - Err(err) => { - let _ = err.print(); - return Ok(if err.use_stderr() { 1 } else { 0 }); - } - }; - - let all = matches.get_flag("all"); - let Some(commands) = matches.get_many::("commands") else { - return Err("missing command operand".to_string()); - }; - - let mut all_found = true; - for command in commands { - let hits = find_command(command, all); - if hits.is_empty() { - all_found = false; - continue; - } - - for hit in hits { - println!("{}", hit.display()); - } - } - - Ok(if all_found { 0 } else { 1 }) -} - -pub fn uu_app() -> Command { - Command::new("which") - .version(VERSION) - .about("Locate a command in PATH.") - .override_usage("which [OPTION]... COMMAND...") - .arg( - Arg::new("all") - .short('a') - .long("all") - .help("print all matching pathnames of each command") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("commands") - .value_name("COMMAND") - .num_args(1..) - .value_parser(clap::value_parser!(OsString)), - ) -} - -fn find_command(command: &OsStr, all: bool) -> Vec { - let mut hits = Vec::new(); - let mut seen = HashSet::new(); - let pathext = pathext(); - - let mut push_hit = |path: PathBuf| { - let key = normalize_seen_key(&path); - if seen.insert(key) { - hits.push(path); - } - }; - - if has_path_separator(Path::new(command)) { - for candidate in candidates(PathBuf::from(command), &pathext) { - if is_regular_file(&candidate) { - push_hit(candidate); - if !all { - return hits; - } - } - } - return hits; - } - - let Some(path_var) = env::var_os("PATH") else { - return hits; - }; - - for dir in env::split_paths(&path_var) { - if dir.as_os_str().is_empty() { - continue; - } - - let base = dir.join(command); - for candidate in candidates(base, &pathext) { - if is_regular_file(&candidate) { - push_hit(candidate); - if !all { - return hits; - } - } - } - } - - hits -} - -fn candidates(base: PathBuf, pathext: &[OsString]) -> Vec { - let mut out = vec![base.clone()]; - if base.extension().is_some() { - return out; - } - - for ext in pathext { - let mut candidate = base.clone(); - candidate.as_mut_os_string().push(ext); - out.push(candidate); - } - - out -} - -fn pathext() -> Vec { - let Some(value) = env::var_os("PATHEXT") else { - return default_pathext(); - }; - - let mut out = Vec::new(); - for ext in value.to_string_lossy().split(';') { - if ext.is_empty() { - continue; - } - - let ext = if ext.starts_with('.') { - ext.to_string() - } else { - format!(".{ext}") - }; - out.push(OsString::from(ext)); - } - - if out.is_empty() { - default_pathext() - } else { - out - } -} - -fn default_pathext() -> Vec { - [".COM", ".EXE", ".BAT", ".CMD"] - .into_iter() - .map(OsString::from) - .collect() -} - -fn has_path_separator(path: &Path) -> bool { - let path = path.as_os_str().to_string_lossy(); - path.contains('/') || path.contains('\\') -} - -fn is_regular_file(path: &Path) -> bool { - path.metadata().is_ok_and(|metadata| metadata.is_file()) -} - -fn normalize_seen_key(path: &Path) -> String { - path.as_os_str().to_string_lossy().to_lowercase() -} - -#[cfg(test)] -mod tests { - use std::fs; - use std::sync::Mutex; - use std::time::{SystemTime, UNIX_EPOCH}; - - use super::*; - - static TEST_LOCK: Mutex<()> = Mutex::new(()); - - struct EnvGuard { - key: &'static str, - old: Option, - } - - impl EnvGuard { - fn set(key: &'static str, value: impl Into) -> Self { - let old = env::var_os(key); - unsafe { - env::set_var(key, value.into()); - } - Self { key, old } - } - } - - impl Drop for EnvGuard { - fn drop(&mut self) { - unsafe { - if let Some(old) = &self.old { - env::set_var(self.key, old); - } else { - env::remove_var(self.key); - } - } - } - } - - fn temp_dir() -> PathBuf { - let unique = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_nanos(); - let path = env::temp_dir().join(format!("ntwhich-test-{unique}")); - fs::create_dir(&path).unwrap(); - path - } - - #[test] - fn finds_first_match_with_pathext() { - let _lock = TEST_LOCK.lock().unwrap(); - let dir = temp_dir(); - fs::write(dir.join("tool.EXE"), []).unwrap(); - let _path = EnvGuard::set("PATH", dir.as_os_str()); - let _pathext = EnvGuard::set("PATHEXT", ".EXE"); - - let hits = find_command(OsStr::new("tool"), false); - - assert_eq!(hits, vec![dir.join("tool.EXE")]); - fs::remove_dir_all(dir).unwrap(); - } - - #[test] - fn all_returns_all_matches() { - let _lock = TEST_LOCK.lock().unwrap(); - let dir1 = temp_dir(); - let dir2 = temp_dir(); - fs::write(dir1.join("tool.CMD"), []).unwrap(); - fs::write(dir2.join("tool.CMD"), []).unwrap(); - let joined = env::join_paths([dir1.as_os_str(), dir2.as_os_str()]).unwrap(); - let _path = EnvGuard::set("PATH", joined); - let _pathext = EnvGuard::set("PATHEXT", ".CMD"); - - let hits = find_command(OsStr::new("tool"), true); - - assert_eq!(hits, vec![dir1.join("tool.CMD"), dir2.join("tool.CMD")]); - fs::remove_dir_all(dir1).unwrap(); - fs::remove_dir_all(dir2).unwrap(); - } - - #[test] - fn searches_explicit_relative_path() { - let _lock = TEST_LOCK.lock().unwrap(); - let dir = temp_dir(); - let old_dir = env::current_dir().unwrap(); - fs::create_dir(dir.join("bin")).unwrap(); - fs::write(dir.join("bin").join("tool.EXE"), []).unwrap(); - let _pathext = EnvGuard::set("PATHEXT", ".EXE"); - env::set_current_dir(&dir).unwrap(); - - let hits = find_command(OsStr::new("bin/tool"), false); - - env::set_current_dir(old_dir).unwrap(); - assert_eq!(hits, vec![PathBuf::from("bin/tool.EXE")]); - fs::remove_dir_all(dir).unwrap(); - } -} diff --git a/deps/which/Cargo.lock b/deps/which/Cargo.lock new file mode 100644 index 0000000..4631799 --- /dev/null +++ b/deps/which/Cargo.lock @@ -0,0 +1,502 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "clap" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fluent" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8137a6d5a2c50d6b0ebfcb9aaa91a28154e0a70605f112d30cb0cd4a78670477" +dependencies = [ + "fluent-bundle", + "unic-langid", +] + +[[package]] +name = "fluent-bundle" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01203cb8918f5711e73891b347816d932046f95f54207710bda99beaeb423bf4" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rustc-hash", + "self_cell", + "smallvec", + "unic-langid", +] + +[[package]] +name = "fluent-langneg" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eebbe59450baee8282d71676f3bfed5689aeab00b27545e83e5f14b1195e8b0" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "fluent-syntax" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198" +dependencies = [ + "memchr", + "thiserror", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "intl-memoizer" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "310da2e345f5eb861e7a07ee182262e94975051db9e4223e909ba90f392f163f" +dependencies = [ + "type-map", + "unic-langid", +] + +[[package]] +name = "intl_pluralrules" +version = "7.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "os_display" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad5fd71b79026fb918650dde6d125000a233764f1c2f1659a1c71118e33ea08f" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "self_cell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "terminal_size" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +dependencies = [ + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "serde_core", + "zerovec", +] + +[[package]] +name = "type-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" +dependencies = [ + "rustc-hash", +] + +[[package]] +name = "unic-langid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ba52c9b05311f4f6e62d5d9d46f094bd6e84cb8df7b3ef952748d752a7d05" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce1bf08044d4b7a94028c93786f8566047edc11110595914de93362559bc658" +dependencies = [ + "tinystr", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uu_which" +version = "0.0.0" +dependencies = [ + "clap", + "uucore", +] + +[[package]] +name = "uucore" +version = "0.8.0" +dependencies = [ + "clap", + "fluent", + "fluent-syntax", + "nix", + "os_display", + "rustc-hash", + "rustix", + "thiserror", + "unic-langid", + "uucore_procs", + "wild", +] + +[[package]] +name = "uucore_procs" +version = "0.8.0" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "wild" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3131afc8c575281e1e80f36ed6a092aa502c08b18ed7524e86fbbb12bb410e1" +dependencies = [ + "glob", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "serde", + "zerofrom", +] diff --git a/deps/ntwhich/Cargo.toml b/deps/which/Cargo.toml similarity index 61% rename from deps/ntwhich/Cargo.toml rename to deps/which/Cargo.toml index 7a55c14..b7cecaf 100644 --- a/deps/ntwhich/Cargo.toml +++ b/deps/which/Cargo.toml @@ -1,21 +1,20 @@ [package] name = "uu_which" -description = "which ~ locate a command in PATH" +description = "which command for Windows" repository = "https://github.com/microsoft/coreutils" -version = "2026.5.29" -license = "MIT" edition = "2024" rust-version = "1.88.0" +version = "0.0.0" +license = "MIT" publish = false [lib] -path = "src/which.rs" -doctest = false - -[dependencies] -clap = { version = "4.5", features = ["wrap_help", "cargo"] } -uucore = { path = "../coreutils/src/uucore" } +path = "src/lib.rs" [[bin]] name = "which" path = "src/main.rs" + +[dependencies] +clap = { version = "4.5", features = ["wrap_help", "cargo", "color"] } +uucore = { path = "../coreutils/src/uucore" } diff --git a/deps/which/src/lib.rs b/deps/which/src/lib.rs new file mode 100644 index 0000000..03c9909 --- /dev/null +++ b/deps/which/src/lib.rs @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::env; +use std::ffi::{OsStr, OsString}; +use std::io::{self, Write as _}; +use std::path::{Component, Path}; + +use clap::{Arg, ArgAction, Command}; +use uucore::Args; +use uucore::error::UResult; + +pub fn uu_app() -> Command { + Command::new("which") + .version(env!("CARGO_PKG_VERSION")) + .about("Locate a command in PATH.") + .override_usage("which [OPTION]... COMMAND...") + .arg( + Arg::new("all") + .short('a') + .long("all") + .help("print all matching pathnames of each command") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("commands") + .value_name("COMMAND") + .num_args(1..) + .value_parser(clap::value_parser!(OsString)), + ) +} + +pub fn uumain(args: impl Args) -> i32 { + match uumain_impl(args) { + Ok(code) => code, + Err(err) => { + let code = err.code(); + _ = writeln!(io::stderr(), "which: {err}"); + code + } + } +} + +fn uumain_impl(args: impl Args) -> UResult { + let matches = + uucore::clap_localization::handle_clap_result_with_exit_code(uu_app(), args, 255)?; + let all = matches.get_flag("all"); + let commands = matches.get_many::("commands").unwrap_or_default(); + + let path_string = env::var_os("PATH").unwrap_or_default(); + let path = env::split_paths(&path_string).collect::>(); + let pathext = env::var_os("PATHEXT").map(|mut pathext| { + pathext.make_ascii_lowercase(); + pathext + }); + let pathext = pathext + .as_deref() + .unwrap_or_else(|| OsStr::new(".com;.exe;.bat;.cmd")) + .as_encoded_bytes() + .split(|byte| *byte == b';') + .filter(|entry| !entry.is_empty()) + // Semicolons are single-byte characters in the self-synchronizing OsStr encoding. + .map(|entry| unsafe { OsStr::from_encoded_bytes_unchecked(entry) }) + .collect::>(); + + let mut out = io::BufWriter::new(io::stdout().lock()); + let mut err = io::BufWriter::new(io::stderr().lock()); + let mut missing = 0; + + for command in commands { + let mut components = Path::new(command).components(); + + if let Some(Component::Normal(file_name)) = components.next_back() + && let file_path = components.as_path() + && !file_path.as_os_str().is_empty() + { + if !find_at(Path::new(command), &pathext, all, &mut out) { + print_failure(&mut err, file_name, file_path.as_os_str()); + missing += 1; + } + } else { + let mut found = false; + for dir in &path { + found |= find_at(&dir.join(command), &pathext, all, &mut out); + } + if !found { + print_failure(&mut err, command, &path_string); + missing += 1; + } + } + } + + _ = out.flush()?; + Ok(missing & 0xff as i32) +} + +fn find_at(base: &Path, pathext: &[&OsStr], all: bool, out: &mut impl io::Write) -> bool { + let mut found = false; + + if base.extension().is_some() { + if has_pathext(&base, pathext) && base.is_file() { + found = true; + print_hit(out, &base); + } + } else { + for ext in pathext { + let mut candidate = base.to_path_buf(); + if !ext.as_encoded_bytes().starts_with(b".") { + candidate.as_mut_os_string().push("."); + } + candidate.as_mut_os_string().push(ext); + if candidate.is_file() { + found = true; + print_hit(out, &candidate); + if !all { + break; + } + } + } + } + + found +} + +fn has_pathext(path: &Path, pathext: &[&OsStr]) -> bool { + let Some(extension) = path.extension() else { + return false; + }; + let extension = extension.as_encoded_bytes(); + pathext.iter().any(|candidate| { + let candidate = candidate.as_encoded_bytes(); + let candidate = candidate.strip_prefix(b".").unwrap_or(candidate); + candidate.eq_ignore_ascii_case(extension) + }) +} + +fn print_hit(out: &mut impl io::Write, path: &Path) { + _ = writeln!(out, "{}", path.display()); +} + +fn print_failure(err: &mut impl io::Write, name: &OsStr, path: &OsStr) { + _ = writeln!(err, "which: no {} in ({})", name.display(), path.display()); +} diff --git a/deps/which/src/main.rs b/deps/which/src/main.rs new file mode 100644 index 0000000..afd59a7 --- /dev/null +++ b/deps/which/src/main.rs @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +uucore::bin!(uu_which);