roowho2_lib/proto/finger_protocol/
classic_formatter.rs1use chrono::{Duration, TimeDelta};
2
3use crate::proto::finger_protocol::{
4 FingerResponseStructuredUserEntry, FingerResponseUserSession, MailStatus,
5};
6
7pub fn classic_format_finger_response_structured_user_entry(
8 entry: &FingerResponseStructuredUserEntry,
9) -> String {
10 let mut result = String::new();
11
12 result += &format!(
13 "Login: {:<16}\t\t\tName: {}\n",
14 entry.username, entry.full_name
15 );
16 result += &format!(
17 "Directory: {:<24}\tShell: {}\n",
18 entry.home_dir.display(),
19 entry.shell.display()
20 );
21
22 if let Some(office) = &entry.office {
23 result += &format!("Office: {}\n", office);
24 }
25 if let Some(office_phone) = &entry.office_phone {
26 result += &format!("Office Phone: {}\n", office_phone);
27 }
28 if let Some(home_phone) = &entry.home_phone {
29 result += &format!("Home Phone: {}\n", home_phone);
30 }
31
32 if entry.never_logged_in {
33 result += "Never logged in.\n";
34 } else {
35 let max_tty_len = entry
36 .sessions
37 .iter()
38 .map(|s| s.tty.len())
39 .max()
40 .unwrap_or(0);
41
42 for session in &entry.sessions {
43 result += &format_session_for_finger(session, max_tty_len);
44 result += "\n";
45 }
46 }
47
48 if let Some(forward) = &entry.forward_status {
49 result += &format!("Mail forwarded to {}\n", forward);
50 }
51
52 if let Some(mail_status) = &entry.mail_status {
53 match mail_status {
54 MailStatus::NoMail => result += "No mail.\n",
55 MailStatus::NewMailReceived {
56 received_time,
57 unread_since,
58 } => {
59 result += &format!(
60 "New mail received {}\nUnread since {}\n",
61 received_time.format("%a %b %e %H:%M (%Z)"),
62 unread_since.format("%a %b %e %H:%M (%Z)")
63 );
64 }
65 MailStatus::MailLastRead(last_read) => {
66 result += &format!(
67 "Mail last read {}\n",
68 last_read.format("%a %b %e %H:%M (%Z)")
69 );
70 }
71 }
72 }
73
74 if let Some(pgp_key) = &entry.pgp_key {
75 result += &format!("PGP key:\n{}\n", pgp_key);
76 }
77
78 if let Some(project) = &entry.project {
79 result += &format!("Project:\n{}\n", project);
80 }
81
82 if let Some(plan) = &entry.plan {
83 result += &format!("Plan:\n{}\n", plan);
84 } else {
85 result += "No Plan.\n";
86 }
87
88 result.trim().to_string()
89}
90
91fn format_session_for_finger(session: &FingerResponseUserSession, max_tty_len: usize) -> String {
92 let mut result = String::new();
93
94 result += &format!(
95 "On since {} on {} from {}",
96 session.login_time.format("%a %b %e %H:%M (%Z)"),
97 session.tty,
98 session.host.as_deref().unwrap_or("unknown")
99 );
100
101 if let Some(idle_time) = session.idle_time {
102 result += &format!(
103 "\n{:width$}{} idle",
104 "",
105 format_idle_time_for_finger(idle_time),
106 width = max_tty_len - session.tty.len() + 3,
107 );
108 }
109
110 if !session.messages_on {
111 result += "\n (messages off)";
112 }
113
114 result
115}
116
117fn format_idle_time_for_finger(idle_time: TimeDelta) -> String {
118 debug_assert!(
119 idle_time.num_seconds() >= 0,
120 "Idle time should never be negative"
121 );
122
123 let mut result = String::new();
124
125 let days = idle_time.num_days();
126 let hours = (idle_time - Duration::days(days)).num_hours();
127 let minutes = (idle_time - Duration::days(days) - Duration::hours(hours)).num_minutes();
128 let seconds =
129 (idle_time - Duration::days(days) - Duration::hours(hours) - Duration::minutes(minutes))
130 .num_seconds();
131
132 if days > 0 {
133 result += &format!("{} day{} ", days, if days == 1 { "" } else { "s" });
134 }
135 if hours > 0 {
136 result += &format!("{} hour{} ", hours, if hours == 1 { "" } else { "s" });
137 }
138 if minutes > 0 && days == 0 {
139 result += &format!("{} minute{} ", minutes, if minutes == 1 { "" } else { "s" });
140 }
141 if seconds > 0 && days == 0 && hours == 0 {
142 result += &format!("{} second{} ", seconds, if seconds == 1 { "" } else { "s" });
143 }
144
145 result.trim().to_string()
146}
147
148#[cfg(test)]
149mod tests {
150 use chrono::{TimeZone, Utc};
151 use indoc::indoc;
152
153 use crate::proto::finger_protocol::FingerResponseUserSession;
154
155 use super::*;
156
157 #[test]
158 fn test_format_session_for_finger() {
159 let values = [
160 (
161 FingerResponseUserSession {
162 tty: "pts/14".to_string(),
163 login_time: Utc.with_ymd_and_hms(2026, 5, 12, 15, 39, 0).unwrap(),
164 host: Some(":pts/21:S.5".to_string()),
165 idle_time: Some(Duration::days(3) + Duration::hours(18)),
166 messages_on: true,
167 },
168 indoc! {
169 "
170 On since Tue May 12 15:39 (UTC) on pts/14 from :pts/21:S.5
171 3 days 18 hours idle
172 "
173 }
174 .trim()
175 .to_string(),
176 ),
177 (
178 FingerResponseUserSession {
179 tty: "pts/16".to_string(),
180 login_time: Utc.with_ymd_and_hms(2026, 5, 12, 15, 39, 0).unwrap(),
181 host: Some(":pts/21:S.6".to_string()),
182 idle_time: Some(Duration::hours(18) + Duration::minutes(47)),
183 messages_on: true,
184 },
185 indoc! {
186 "
187 On since Tue May 12 15:39 (UTC) on pts/16 from :pts/21:S.6
188 18 hours 47 minutes idle
189 "
190 }
191 .trim()
192 .to_string(),
193 ),
194 (
195 FingerResponseUserSession {
196 tty: "pts/21".to_string(),
197 login_time: Utc.with_ymd_and_hms(2026, 5, 12, 15, 39, 0).unwrap(),
198 host: Some("212.102.29.193".to_string()),
199 idle_time: Some(Duration::minutes(24) + Duration::seconds(22)),
200 messages_on: false,
201 },
202 indoc! {
203 "
204 On since Tue May 12 15:39 (UTC) on pts/21 from 212.102.29.193
205 24 minutes 22 seconds idle
206 (messages off)
207 "
208 }
209 .trim()
210 .to_string(),
211 ),
212 ];
213
214 for (session, expected) in values {
215 assert_eq!(
216 format_session_for_finger(&session, "pts/10".len()),
217 expected
218 );
219 }
220 }
221
222 #[test]
223 fn test_format_session_for_finger_long_tty_string() {
224 let session = FingerResponseUserSession {
225 tty: "pts/1".to_string(),
226 login_time: Utc.with_ymd_and_hms(2026, 5, 12, 15, 39, 0).unwrap(),
227 host: Some(":pts/21:S.5".to_string()),
228 idle_time: Some(Duration::hours(1) + Duration::minutes(30)),
229 messages_on: true,
230 };
231
232 let expected = indoc! {
233 "
234 On since Tue May 12 15:39 (UTC) on pts/1 from :pts/21:S.5
235 1 hour 30 minutes idle
236 "
237 }
238 .trim()
239 .to_string();
240
241 assert_eq!(
242 format_session_for_finger(&session, "pts/123".len()),
243 expected
244 );
245 }
246}