mirror of
https://github.com/uutils/procps.git
synced 2026-06-10 16:14:00 -07:00
w: Format time and get cmdline (#41)
* w: Format time and get cmdline This change allows w to display time in a similar way to the original procps w. Additionally, w now fetches the cmdline for each entry.
This commit is contained in:
Generated
+257
-111
File diff suppressed because it is too large
Load Diff
@@ -48,6 +48,9 @@ xattr = "1.3.1"
|
||||
tempfile = "3.9.0"
|
||||
rand = { version = "0.8", features = ["small_rng"] }
|
||||
bytesize = "1.3.0"
|
||||
chrono = { version = "0.4.37", default-features = false, features = [
|
||||
"clock",
|
||||
] }
|
||||
|
||||
[dependencies]
|
||||
clap = { workspace = true }
|
||||
@@ -73,6 +76,7 @@ tempfile = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
uucore = { workspace = true, features = ["entries", "process", "signals"] }
|
||||
chrono = { workspace = true }
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
xattr = { workspace = true }
|
||||
|
||||
@@ -14,6 +14,9 @@ categories = ["command-line-utilities"]
|
||||
[dependencies]
|
||||
uucore = { workspace = true, features = ["utmpx"] }
|
||||
clap = { workspace = true }
|
||||
chrono = { workspace = true, default-features = false, features = [
|
||||
"clock",
|
||||
] }
|
||||
|
||||
[lib]
|
||||
path = "src/w.rs"
|
||||
|
||||
+65
-4
@@ -3,9 +3,10 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use chrono::{self, Datelike};
|
||||
use clap::crate_version;
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use std::process;
|
||||
use std::{fs, path::Path, process};
|
||||
#[cfg(not(windows))]
|
||||
use uucore::utmpx::Utmpx;
|
||||
use uucore::{error::UResult, format_usage, help_about, help_usage};
|
||||
@@ -23,6 +24,28 @@ struct UserInfo {
|
||||
command: String,
|
||||
}
|
||||
|
||||
fn format_time(time: String) -> Result<String, chrono::format::ParseError> {
|
||||
let mut t: String = time;
|
||||
// Trim the seconds off of timezone offset, as chrono can't parse the time with it present
|
||||
if let Some(time_offset) = t.rfind(':') {
|
||||
t = t.drain(..time_offset).collect();
|
||||
}
|
||||
// If login time day is not current day, format like Sat16, or Fri06
|
||||
let current_dt = chrono::Local::now().fixed_offset();
|
||||
let dt = chrono::DateTime::parse_from_str(&t, "%Y-%m-%d %H:%M:%S%.f %:z")?;
|
||||
|
||||
if current_dt.day() != dt.day() {
|
||||
Ok(dt.format("%a%d").to_string())
|
||||
} else {
|
||||
Ok(dt.format("%H:%M").to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_cmdline(pid: i32) -> Result<String, std::io::Error> {
|
||||
let cmdline_path = Path::new("/proc").join(pid.to_string()).join("cmdline");
|
||||
fs::read_to_string(cmdline_path)
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn fetch_user_info() -> Result<Vec<UserInfo>, std::io::Error> {
|
||||
let mut user_info_list = Vec::new();
|
||||
@@ -31,11 +54,11 @@ fn fetch_user_info() -> Result<Vec<UserInfo>, std::io::Error> {
|
||||
let user_info = UserInfo {
|
||||
user: entry.user(),
|
||||
terminal: entry.tty_device(),
|
||||
login_time: format!("{}", entry.login_time()), // Needs formatting
|
||||
login_time: format_time(entry.login_time().to_string()).unwrap_or_default(),
|
||||
idle_time: String::new(), // Placeholder, needs actual implementation
|
||||
jcpu: String::new(), // Placeholder, needs actual implementation
|
||||
pcpu: String::new(), // Placeholder, needs actual implementation
|
||||
command: String::new(), // Placeholder, needs actual implementation
|
||||
command: fetch_cmdline(entry.pid()).unwrap_or_default(),
|
||||
};
|
||||
user_info_list.push(user_info);
|
||||
}
|
||||
@@ -58,7 +81,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
match fetch_user_info() {
|
||||
Ok(user_info) => {
|
||||
if !no_header {
|
||||
println!("USER\tTTY\t\tLOGIN@\t\tIDLE\tJCPU\tPCPU\tWHAT");
|
||||
println!("USER\tTTY\tLOGIN@\tIDLE\tJCPU\tPCPU\tWHAT");
|
||||
}
|
||||
for user in user_info {
|
||||
println!(
|
||||
@@ -139,3 +162,41 @@ pub fn uu_app() -> Command {
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{fetch_cmdline, format_time};
|
||||
use chrono;
|
||||
use std::{fs, path::Path, process};
|
||||
|
||||
#[test]
|
||||
fn test_format_time() {
|
||||
let unix_epoc = chrono::Local::now()
|
||||
.format("%Y-%m-%d %H:%M:%S%.6f %::z")
|
||||
.to_string();
|
||||
let unix_formatted = format_time(unix_epoc).unwrap();
|
||||
assert!(unix_formatted.contains(':') && unix_formatted.chars().count() == 5);
|
||||
// Test a date that is 5 days ago
|
||||
let td = chrono::Local::now().fixed_offset()
|
||||
- chrono::TimeDelta::new(60 * 60 * 24 * 5, 0).unwrap();
|
||||
// Pre-format time, so it's similar to how utmpx returns it
|
||||
let pre_formatted = format!("{}", td.format("%Y-%m-%d %H:%M:%S%.6f %::z"));
|
||||
|
||||
assert_eq!(
|
||||
format_time(pre_formatted).unwrap(),
|
||||
td.format("%a%d").to_string()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Get PID of current process and use that for cmdline testing
|
||||
fn test_fetch_cmdline() {
|
||||
// uucore's utmpx returns an i32, so we cast to that to mimic it.
|
||||
let pid = process::id() as i32;
|
||||
let path = Path::new("/proc").join(pid.to_string()).join("cmdline");
|
||||
assert_eq!(
|
||||
fs::read_to_string(path).unwrap(),
|
||||
fetch_cmdline(pid).unwrap()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+22
-1
@@ -17,5 +17,26 @@ fn test_no_header() {
|
||||
|
||||
let result = cmd.stdout_str();
|
||||
|
||||
assert!(!result.contains("USER\tTTY\t\tLOGIN@\t\tIDLE\tJCPU\tPCPU\tWHAT"));
|
||||
assert!(!result.contains("USER\tTTY\tLOGIN@\tIDLE\tJCPU\tPCPU\tWHAT"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_output_format() {
|
||||
// Use no header to simplify testing
|
||||
let cmd = new_ucmd!().arg("--no-header").succeeds();
|
||||
let output_lines = cmd.stdout_str().lines();
|
||||
|
||||
for line in output_lines {
|
||||
let line_vec: Vec<String> = line.split_whitespace().map(|s| String::from(s)).collect();
|
||||
// Check the time formatting, this should be the third entry in list
|
||||
// For now, we are just going to check that that length of time is 5 and it has a colon, else
|
||||
// it is possible that a time can look like Fri13, so it can start with a letter and end
|
||||
// with a number
|
||||
assert!(
|
||||
(line_vec[2].contains(":") && line_vec[2].chars().count() == 5)
|
||||
|| (line_vec[2].starts_with(char::is_alphabetic)
|
||||
&& line_vec[2].ends_with(char::is_numeric)
|
||||
&& line_vec[2].chars().count() == 5)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user