1use crate::errors::{FsError, Result};
2use chrono::{DateTime, NaiveDate, TimeZone, Utc};
3use humansize::{format_size, BINARY};
4
5pub fn parse_size(input: &str) -> Result<u64> {
7 let input = input.trim().to_uppercase();
8 let input = input.replace(" ", "");
9
10 let (num_str, unit) = input
12 .char_indices()
13 .find(|(_, c)| c.is_alphabetic())
14 .map(|(idx, _)| input.split_at(idx))
15 .unwrap_or((&input, ""));
16
17 let number: f64 = num_str.parse().map_err(|_| FsError::InvalidSize {
18 input: input.clone(),
19 })?;
20
21 let multiplier: u64 = match unit {
22 "" | "B" => 1,
23 "KB" => 1_000,
24 "KIB" => 1_024,
25 "MB" => 1_000_000,
26 "MIB" => 1_048_576,
27 "GB" => 1_000_000_000,
28 "GIB" => 1_073_741_824,
29 "TB" => 1_000_000_000_000,
30 "TIB" => 1_099_511_627_776,
31 _ => {
32 return Err(FsError::InvalidSize {
33 input: input.clone(),
34 })
35 }
36 };
37
38 Ok((number * multiplier as f64) as u64)
39}
40
41pub fn format_size_human(size: u64) -> String {
43 format_size(size, BINARY)
44}
45
46pub fn parse_date(input: &str) -> Result<DateTime<Utc>> {
48 if let Ok(dt) = DateTime::parse_from_rfc3339(input) {
50 return Ok(dt.with_timezone(&Utc));
51 }
52
53 if let Ok(date) = NaiveDate::parse_from_str(input, "%Y-%m-%d") {
55 return Utc
56 .from_local_datetime(&date.and_hms_opt(0, 0, 0).unwrap())
57 .single()
58 .ok_or_else(|| FsError::InvalidDate {
59 input: input.to_string(),
60 });
61 }
62
63 if let Some(relative_date) = parse_relative_date(input) {
65 return Ok(relative_date);
66 }
67
68 Err(FsError::InvalidDate {
69 input: input.to_string(),
70 })
71}
72
73fn parse_relative_date(input: &str) -> Option<DateTime<Utc>> {
75 use chrono::Duration;
76
77 let input = input.trim().to_lowercase();
78 let parts: Vec<&str> = input.split_whitespace().collect();
79
80 if parts.len() != 3 || parts[2] != "ago" {
82 return None;
83 }
84
85 let number: i64 = parts[0].parse().ok()?;
86 let unit = parts[1];
87
88 let now = Utc::now();
89
90 match unit {
91 "second" | "seconds" | "sec" | "secs" => Some(now - Duration::seconds(number)),
92 "minute" | "minutes" | "min" | "mins" => Some(now - Duration::minutes(number)),
93 "hour" | "hours" | "hr" | "hrs" => Some(now - Duration::hours(number)),
94 "day" | "days" => Some(now - Duration::days(number)),
95 "week" | "weeks" => Some(now - Duration::weeks(number)),
96 "month" | "months" => Some(now - Duration::days(number * 30)),
97 "year" | "years" => Some(now - Duration::days(number * 365)),
98 _ => None,
99 }
100}
101
102pub fn is_tty() -> bool {
104 crossterm::tty::IsTty::is_tty(&std::io::stdout())
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn test_parse_size() {
113 assert_eq!(parse_size("100").unwrap(), 100);
114 assert_eq!(parse_size("10KB").unwrap(), 10_000);
115 assert_eq!(parse_size("10KiB").unwrap(), 10_240);
116 assert_eq!(parse_size("2MB").unwrap(), 2_000_000);
117 assert_eq!(parse_size("2 MiB").unwrap(), 2_097_152);
118 assert_eq!(parse_size("1GB").unwrap(), 1_000_000_000);
119 assert_eq!(parse_size("1 GiB").unwrap(), 1_073_741_824);
120 assert!(parse_size("invalid").is_err());
121 assert!(parse_size("10XB").is_err());
122 }
123
124 #[test]
125 fn test_format_size_human() {
126 assert_eq!(format_size_human(0), "0 B");
127 assert_eq!(format_size_human(1023), "1023 B");
128 assert_eq!(format_size_human(1024), "1 KiB");
129 assert_eq!(format_size_human(1_048_576), "1 MiB");
130 }
131
132 #[test]
133 fn test_parse_date() {
134 let result = parse_date("2024-01-01");
136 assert!(result.is_ok());
137
138 let result = parse_date("2024-01-01T12:00:00Z");
140 assert!(result.is_ok());
141
142 assert!(parse_date("invalid").is_err());
144 }
145}