Repository: uutils/procps (rust)

Issue:
Add command `pidof`
```
       pidof - find the process ID of a running program
```

Pull request:
pidof: Add command `pidof`
fix #93

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index d71f454..ef2eaff 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -136,18 +136,18 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.5.9"
+version = "4.5.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462"
+checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d"
 dependencies = [
  "clap_builder",
 ]
 
 [[package]]
 name = "clap_builder"
-version = "4.5.9"
+version = "4.5.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942"
+checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708"
 dependencies = [
  "anstream",
  "anstyle",
@@ -158,9 +158,9 @@ dependencies = [
 
 [[package]]
 name = "clap_complete"
-version = "4.5.8"
+version = "4.5.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b4be9c4c4b1f30b78d8a750e0822b6a6102d97e62061c583a6c1dea2dfb33ae"
+checksum = "1d598e88f6874d4b888ed40c71efbcbf4076f1dfbae128a08a8c9e45f710605d"
 dependencies = [
  "clap",
 ]
@@ -524,6 +524,7 @@ dependencies = [
  "textwrap",
  "uu_free",
  "uu_pgrep",
+ "uu_pidof",
  "uu_pmap",
  "uu_pwdx",
  "uu_slabtop",
@@ -733,9 +734,9 @@ dependencies = [
 
 [[package]]
 name = "sysinfo"
-version = "0.30.13"
+version = "0.30.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3"
+checksum = "732ffa00f53e6b2af46208fba5718d9662a421049204e156328b66791ffa15ae"
 dependencies = [
  "cfg-if",
  "core-foundation-sys",
@@ -867,6 +868,15 @@ dependencies = [
  "walkdir",
 ]
 
+[[package]]
+name = "uu_pidof"
+version = "0.0.1"
+dependencies = [
+ "clap",
+ "uu_pgrep",
+ "uucore",
+]
+
 [[package]]
 name = "uu_pmap"
 version = "0.0.1"
diff --git a/Cargo.toml b/Cargo.toml
index 68c615f..6049cb9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,7 +25,7 @@ build = "build.rs"
 default = ["feat_common_core"]
 uudoc = []
 
-feat_common_core = ["pwdx", "free", "w", "watch", "pmap", "slabtop", "pgrep"]
+feat_common_core = ["pwdx", "free", "w", "watch", "pmap", "slabtop", "pgrep", "pidof"]
 
 [workspace.dependencies]
 uucore = "0.0.27"
@@ -63,6 +63,7 @@ watch = { optional = true, version = "0.0.1", package = "uu_watch", path = "src/
 pmap = { optional = true, version = "0.0.1", package = "uu_pmap", path = "src/uu/pmap" }
 slabtop = { optional = true, version = "0.0.1", package = "uu_slabtop", path = "src/uu/slabtop" }
 pgrep = { optional = true, version = "0.0.1", package = "uu_pgrep", path = "src/uu/pgrep" }
+pidof = { optional = true, version = "0.0.1", package = "uu_pidof", path = "src/uu/pidof" }
 
 [dev-dependencies]
 pretty_assertions = "1.4.0"
diff --git a/src/uu/pidof/Cargo.toml b/src/uu/pidof/Cargo.toml
new file mode 100644
index 0000000..cf9df16
--- /dev/null
+++ b/src/uu/pidof/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "uu_pidof"
+version = "0.0.1"
+edition = "2021"
+authors = ["uutils developers"]
+license = "MIT"
+description = "pidof ~ (uutils) Find the process ID of a running program"
+
+homepage = "https://github.com/uutils/procps"
+repository = "https://github.com/uutils/procps/tree/main/src/uu/pmap"
+keywords = ["acl", "uutils", "cross-platform", "cli", "utility"]
+categories = ["command-line-utilities"]
+
+
+[dependencies]
+uucore = { workspace = true }
+clap = { workspace = true }
+uu_pgrep = { path = "../pgrep" }
+
+[lib]
+path = "src/pidof.rs"
+
+[[bin]]
+name = "pidof"
+path = "src/main.rs"
diff --git a/src/uu/pidof/pidof.md b/src/uu/pidof/pidof.md
new file mode 100644
index 0000000..97dd16c
--- /dev/null
+++ b/src/uu/pidof/pidof.md
@@ -0,0 +1,7 @@
+# pidof
+
+```
+pidof [-s] [-c] [-n] [-x] [-z] [-o omitpid[,omitpid...]]  [-o omitpid[,omitpid...]...]  [-d sep] program [program...]
+```
+
+Find the process ID of a running program
diff --git a/src/uu/pidof/src/main.rs b/src/uu/pidof/src/main.rs
new file mode 100644
index 0000000..f3fecd6
--- /dev/null
+++ b/src/uu/pidof/src/main.rs
@@ -0,0 +1,1 @@
+uucore::bin!(uu_pidof);
diff --git a/src/uu/pidof/src/pidof.rs b/src/uu/pidof/src/pidof.rs
new file mode 100644
index 0000000..e0dbb12
--- /dev/null
+++ b/src/uu/pidof/src/pidof.rs
@@ -0,0 +1,162 @@
+// This file is part of the uutils procps package.
+//
+// For the full copyright and license information, please view the LICENSE
+// file that was distributed with this source code.
+
+use std::path::PathBuf;
+
+use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
+use uu_pgrep::process::{walk_process, ProcessInformation};
+use uucore::{error::UResult, format_usage, help_about, help_usage};
+
+const ABOUT: &str = help_about!("pidof.md");
+const USAGE: &str = help_usage!("pidof.md");
+
+#[uucore::main]
+pub fn uumain(args: impl uucore::Args) -> UResult<()> {
+    let matches = uu_app().try_get_matches_from(args)?;
+
+    let arg_program_name = matches.get_many::<String>("program-name");
+    let arg_separator = matches.get_one::<String>("d").unwrap();
+
+    if arg_program_name.is_none() {
+        uucore::error::set_exit_code(1);
+        return Ok(());
+    };
+
+    let collected = collect_matched_pids(&matches);
+
+    if collected.is_empty() {
+        uucore::error::set_exit_code(1);
+        return Ok(());
+    };
+
+    let output = collected
+        .into_iter()
+        .map(|it| it.pid.to_string())
+        .collect::<Vec<_>>()
+        .join(arg_separator);
+
+    let flag_quiet = matches.get_flag("q");
+    if !flag_quiet {
+        println!("{output}");
+    }
+
+    Ok(())
+}
+
+fn get_executable_name(process: &mut ProcessInformation) -> String {
+    let binding = process.cmdline.split(' ').collect::<Vec<_>>();
+    let mut path = binding.first().unwrap().to_string();
+
+    if path.is_empty() {
+        path.clone_from(&process.status()["Name"]);
+    };
+
+    PathBuf::from(path)
+        .file_name()
+        .unwrap()
+        .to_str()
+        .unwrap()
+        .to_string()
+}
+
+fn collect_matched_pids(matches: &ArgMatches) -> Vec<ProcessInformation> {
+    let program_names: Vec<_> = matches
+        .get_many::<String>("program-name")
+        .unwrap()
+        .cloned()
+        .collect();
+
+    let collected = walk_process().collect::<Vec<_>>();
+    let arg_omit_pid = matches
+        .get_many::<usize>("o")
+        .unwrap_or_default()
+        .copied()
+        .collect::<Vec<_>>();
+
+    let mut processed = Vec::new();
+    for mut process in collected {
+        let contains = program_names.contains(&get_executable_name(&mut process));
+        let should_omit = arg_omit_pid.contains(&process.pid);
+
+        if contains && !should_omit {
+            processed.push(process)
+        }
+    }
+
+    processed.sort_by(|a, b| b.pid.cmp(&a.pid));
+
+    let flag_s = matches.get_flag("s");
+    if flag_s {
+        match processed.first() {
+            Some(first) => vec![first.clone()],
+            None => Vec::new(),
+        }
+    } else {
+        processed
+    }
+}
+
+#[allow(clippy::cognitive_complexity)]
+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("program-name")
+                .help("Program name.")
+                .required(true)
+                .index(1)
+                .action(ArgAction::Append),
+        )
+        // .arg(
+        //     Arg::new("c")
+        //         .short('c')
+        //         .help("Return PIDs with the same root directory")
+        //         .action(ArgAction::SetTrue),
+        // )
+        .arg(
+            Arg::new("d")
+                .short('d')
+                .help("Use the provided character as output separator")
+                .action(ArgAction::Set)
+                .value_name("sep")
+                .default_value(" ")
+                .hide_default_value(true),
+        )
+        // .arg(
+        //     Arg::new("n")
+        //         .short('n')
+        //         .help("Avoid using stat system function on network shares")
+        //         .action(ArgAction::SetTrue),
+        // )
+        .arg(
+            Arg::new("o")
+                .short('o')
+                .help("Omit results with a given PID")
+                .action(ArgAction::Append)
+                .value_parser(clap::value_parser!(usize))
+                .value_name("omitpid"),
+        )
+        .arg(
+            Arg::new("q")
+                .short('q')
+                .help("Quiet mode. Do not display output")
+                .action(ArgAction::SetTrue),
+        )
+        .arg(
+            Arg::new("s")
+                .short('s')
+                .help("Only return one PID")
+                .action(ArgAction::SetTrue),
+        )
+    // .arg(
+    //     Arg::new("x")
+    //         .short('x')
+    //         .help("Return PIDs of shells running scripts with a matching name")
+    //         .action(ArgAction::SetTrue),
+    // )
+}
diff --git a/src/uu/slabtop/slabtop.md b/src/uu/slabtop/slabtop.md
index 884bbb8..d1b3cf6 100644
--- a/src/uu/slabtop/slabtop.md
+++ b/src/uu/slabtop/slabtop.md
@@ -5,18 +5,3 @@ slabtop [options]
 ```
 
 Display kernel slab cache information in real time
-
-## After Help
-
-The following are valid sort criteria:
-
-* `a`    sort by number of active objects
-* `b`    sort by objects per slab
-* `c`    sort by cache size
-* `l`    sort by number of slabs
-* `v`    sort by (non display) number of active slabs
-* `n`    sort by name
-* `o`    sort by number of objects (the default)
-* `p`    sort by (non display) pages per slab
-* `s`    sort by object size
-* `u`    sort by cache utilization
diff --git a/src/uu/slabtop/src/slabtop.rs b/src/uu/slabtop/src/slabtop.rs
index b48f90e..73c77cc 100644
--- a/src/uu/slabtop/src/slabtop.rs
+++ b/src/uu/slabtop/src/slabtop.rs
@@ -5,10 +5,9 @@
 
 use crate::parse::SlabInfo;
 use clap::{arg, crate_version, ArgAction, Command};
-use uucore::{error::UResult, format_usage, help_about, help_section, help_usage};
+use uucore::{error::UResult, format_usage, help_about, help_usage};
 
 const ABOUT: &str = help_about!("slabtop.md");
-const AFTER_HELP: &str = help_section!("after help", "slabtop.md");
 const USAGE: &str = help_usage!("slabtop.md");
 
 mod parse;
@@ -122,7 +121,6 @@ fn output_list(info: &SlabInfo) {
     }
 }
 
-#[allow(clippy::cognitive_complexity)]
 pub fn uu_app() -> Command {
     Command::new(uucore::util_name())
         .version(crate_version!())
@@ -134,5 +132,17 @@ pub fn uu_app() -> Command {
             arg!(-o --once          "only display once, then exit").action(ArgAction::SetTrue),
             arg!(-s --sort  <char>  "specify sort criteria by character (see below)"),
         ])
-        .after_help(AFTER_HELP)
+        .after_help(
+            r"The following are valid sort criteria:
+ a: sort by number of active objects
+ b: sort by objects per slab
+ c: sort by cache size
+ l: sort by number of slabs
+ v: sort by (non display) number of active slabs
+ n: sort by name
+ o: sort by number of objects (the default)
+ p: sort by (non display) pages per slab
+ s: sort by object size
+ u: sort by cache utilization",
+        )
 }


Test patch:
diff --git a/tests/by-util/test_pidof.rs b/tests/by-util/test_pidof.rs
new file mode 100644
index 0000000..4c15eb9
--- /dev/null
+++ b/tests/by-util/test_pidof.rs
@@ -0,0 +1,35 @@
+// This file is part of the uutils procps 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]
+fn test_invalid_arg() {
+    new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
+}
+
+#[test]
+#[cfg(target_os = "linux")]
+fn test_find_init() {
+    new_ucmd!().arg("init").succeeds();
+}
+
+#[test]
+#[cfg(target_os = "linux")]
+fn test_find_kthreadd() {
+    new_ucmd!().arg("kthreadd").succeeds();
+}
+
+#[test]
+#[cfg(target_os = "linux")]
+fn test_no_program() {
+    new_ucmd!().fails().code_is(1);
+}
+
+#[test]
+#[cfg(target_os = "linux")]
+fn test_no_pid_found() {
+    new_ucmd!().arg("NO_THIS_PROGRAM").fails().code_is(1);
+}
diff --git a/tests/tests.rs b/tests/tests.rs
index 3a5e8ce..9dd4e1e 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -32,3 +32,7 @@ mod test_slabtop;
 #[cfg(feature = "pgrep")]
 #[path = "by-util/test_pgrep.rs"]
 mod test_pgrep;
+
+#[cfg(feature = "pidof")]
+#[path = "by-util/test_pidof.rs"]
+mod test_pidof;