Krolyxon
Commit 1dcac38

rmptuisrcutil.rs

96 lines · 2.8 KB · Rust

1//! Small helpers shared across modules.
2
3use std::{env, path::PathBuf, time::Duration};
4
5/// Replaces a leading `~` with the home directory.
6pub fn expand_tilde(path: &str) -> PathBuf {
7 match path.strip_prefix('~') {
8 Some(rest) if rest.is_empty() || rest.starts_with('/') => {
9 env::var_os("HOME").map(PathBuf::from).map_or_else(
10 || PathBuf::from(path),
11 |home| home.join(rest.trim_start_matches('/')),
12 )
13 }
14 _ => PathBuf::from(path),
15 }
16}
17
18/// `$XDG_CACHE_HOME/rmptui`, falling back to `~/.cache/rmptui`.
19pub fn cache_dir() -> PathBuf {
20 env::var_os("XDG_CACHE_HOME")
21 .filter(|dir| !dir.is_empty())
22 .map(PathBuf::from)
23 .unwrap_or_else(|| expand_tilde("~/.cache"))
24 .join("rmptui")
25}
26
27/// Formats a duration as `m:ss` or `h:mm:ss`.
28pub fn format_duration(duration: Duration) -> String {
29 let secs = duration.as_secs();
30 let (h, m, s) = (secs / 3600, secs / 60 % 60, secs % 60);
31 if h > 0 {
32 format!("{h}:{m:02}:{s:02}")
33 } else {
34 format!("{m}:{s:02}")
35 }
36}
37
38/// Spells out a duration like ncmpcpp: `1 hour, 3 minutes, 56 seconds`.
39pub fn format_length(duration: Duration) -> String {
40 let secs = duration.as_secs();
41 let parts = [
42 (secs / 86_400, "day"),
43 (secs / 3600 % 24, "hour"),
44 (secs / 60 % 60, "minute"),
45 (secs % 60, "second"),
46 ];
47 let words: Vec<String> = parts
48 .iter()
49 .filter(|(n, _)| *n > 0)
50 .map(|(n, unit)| format!("{n} {unit}{}", if *n == 1 { "" } else { "s" }))
51 .collect();
52 if words.is_empty() {
53 "0 seconds".to_owned()
54 } else {
55 words.join(", ")
56 }
57}
58
59/// `1 item` / `3 items`.
60pub fn plural(count: usize, noun: &str) -> String {
61 format!("{count} {noun}{}", if count == 1 { "" } else { "s" })
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn formats_lengths() {
70 assert_eq!(
71 format_length(Duration::from_secs(236)),
72 "3 minutes, 56 seconds"
73 );
74 assert_eq!(format_length(Duration::from_secs(3601)), "1 hour, 1 second");
75 assert_eq!(format_length(Duration::ZERO), "0 seconds");
76 assert_eq!(plural(1, "item"), "1 item");
77 }
78
79 #[test]
80 fn formats_durations() {
81 assert_eq!(format_duration(Duration::from_secs(5)), "0:05");
82 assert_eq!(format_duration(Duration::from_secs(605)), "10:05");
83 assert_eq!(format_duration(Duration::from_secs(3725)), "1:02:05");
84 }
85
86 #[test]
87 fn expands_tilde() {
88 let home = env::var("HOME").unwrap();
89 assert_eq!(
90 expand_tilde("~/music"),
91 PathBuf::from(format!("{home}/music"))
92 );
93 assert_eq!(expand_tilde("/abs"), PathBuf::from("/abs"));
94 assert_eq!(expand_tilde("~user/x"), PathBuf::from("~user/x"));
95 }
96}