1use crate::{AverDisplay, AverList, aver_display};
2
3pub fn console_print<T: AverDisplay>(val: &T) {
4 println!("{}", aver_display(val));
5}
6
7pub fn console_error<T: AverDisplay>(val: &T) {
8 eprintln!("{}", aver_display(val));
9}
10
11pub fn console_warn<T: AverDisplay>(val: &T) {
12 eprintln!("[warn] {}", aver_display(val));
13}
14
15pub fn read_line() -> Result<String, String> {
16 let mut buf = String::new();
17 match std::io::stdin().read_line(&mut buf) {
18 Ok(0) => Err("EOF".to_string()),
19 Ok(_) => {
20 if buf.ends_with('\n') {
21 buf.pop();
22 if buf.ends_with('\r') {
23 buf.pop();
24 }
25 }
26 Ok(buf)
27 }
28 Err(e) => Err(e.to_string()),
29 }
30}
31
32pub fn time_now() -> String {
33 let (secs, nanos) = unix_parts_now();
34 format_utc_rfc3339_like(secs, nanos)
35}
36
37pub fn time_unix_ms() -> i64 {
38 let now = std::time::SystemTime::now()
39 .duration_since(std::time::UNIX_EPOCH)
40 .expect("Time.unixMs: system clock error");
41 i64::try_from(now.as_millis()).expect("Time.unixMs: value out of i64 range")
42}
43
44pub fn time_sleep(ms: i64) {
45 if ms < 0 {
46 panic!("Time.sleep: ms must be non-negative");
47 }
48 std::thread::sleep(std::time::Duration::from_millis(ms as u64));
49}
50
51pub fn string_slice(s: &str, from: i64, to: i64) -> String {
52 let chars: Vec<char> = s.chars().collect();
53 let len = chars.len() as i64;
54 let start = from.max(0) as usize;
55 let end = to.min(len) as usize;
56 if start >= end || start >= chars.len() {
57 return String::new();
58 }
59 chars[start..end].iter().collect()
60}
61
62pub fn read_text(path: &str) -> Result<String, String> {
63 std::fs::read_to_string(path).map_err(|e| e.to_string())
64}
65
66pub fn write_text(path: &str, content: &str) -> Result<(), String> {
67 std::fs::write(path, content)
68 .map(|_| ())
69 .map_err(|e| e.to_string())
70}
71
72pub fn append_text(path: &str, content: &str) -> Result<(), String> {
73 use std::io::Write;
74
75 let mut file = std::fs::OpenOptions::new()
76 .create(true)
77 .append(true)
78 .open(path)
79 .map_err(|e| e.to_string())?;
80 file.write_all(content.as_bytes()).map_err(|e| e.to_string())
81}
82
83pub fn path_exists(path: &str) -> bool {
84 std::path::Path::new(path).exists()
85}
86
87pub fn delete_file(path: &str) -> Result<(), String> {
88 let p = std::path::Path::new(path);
89 if p.is_dir() {
90 return Err(
91 "Disk.delete: path is a directory — use Disk.deleteDir to remove directories"
92 .to_string(),
93 );
94 }
95 std::fs::remove_file(p)
96 .map(|_| ())
97 .map_err(|e| e.to_string())
98}
99
100pub fn delete_dir(path: &str) -> Result<(), String> {
101 let p = std::path::Path::new(path);
102 if !p.is_dir() {
103 return Err(
104 "Disk.deleteDir: path is not a directory — use Disk.delete to remove files"
105 .to_string(),
106 );
107 }
108 std::fs::remove_dir_all(p)
109 .map(|_| ())
110 .map_err(|e| e.to_string())
111}
112
113pub fn list_dir(path: &str) -> Result<AverList<String>, String> {
114 let entries = std::fs::read_dir(path).map_err(|e| e.to_string())?;
115 let mut result = Vec::new();
116 for entry in entries {
117 let entry = entry.map_err(|e| e.to_string())?;
118 result.push(entry.file_name().to_string_lossy().into_owned());
119 }
120 Ok(AverList::from_vec(result))
121}
122
123pub fn make_dir(path: &str) -> Result<(), String> {
124 std::fs::create_dir_all(path)
125 .map(|_| ())
126 .map_err(|e| e.to_string())
127}
128
129pub fn env_get(key: &str) -> Option<String> {
130 std::env::var(key).ok()
131}
132
133pub fn env_set(key: &str, value: &str) -> Result<(), String> {
134 validate_env_key(key)?;
135 if value.contains('\0') {
136 return Err("Env.set: value must not contain NUL".to_string());
137 }
138
139 unsafe {
140 std::env::set_var(key, value);
141 }
142 Ok(())
143}
144
145fn validate_env_key(key: &str) -> Result<(), String> {
146 if key.is_empty() {
147 return Err("Env.set: key must not be empty".to_string());
148 }
149 if key.contains('=') {
150 return Err("Env.set: key must not contain '='".to_string());
151 }
152 if key.contains('\0') {
153 return Err("Env.set: key must not contain NUL".to_string());
154 }
155 Ok(())
156}
157
158fn unix_parts_now() -> (i64, u32) {
159 match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
160 Ok(d) => (
161 i64::try_from(d.as_secs()).expect("Time.now: seconds out of i64 range"),
162 d.subsec_nanos(),
163 ),
164 Err(e) => {
165 let d = e.duration();
166 let secs = i64::try_from(d.as_secs()).expect("Time.now: seconds out of i64 range");
167 let nanos = d.subsec_nanos();
168 if nanos == 0 {
169 (-secs, 0)
170 } else {
171 (-(secs + 1), 1_000_000_000 - nanos)
172 }
173 }
174 }
175}
176
177fn format_utc_rfc3339_like(unix_secs: i64, nanos: u32) -> String {
178 let days = unix_secs.div_euclid(86_400);
179 let sod = unix_secs.rem_euclid(86_400);
180 let hour = sod / 3_600;
181 let minute = (sod % 3_600) / 60;
182 let second = sod % 60;
183 let millis = nanos / 1_000_000;
184 let (year, month, day) = civil_from_days(days);
185 format!(
186 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
187 year, month, day, hour, minute, second, millis
188 )
189}
190
191fn civil_from_days(days_since_epoch: i64) -> (i32, u32, u32) {
192 let z = days_since_epoch + 719_468;
193 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
194 let doe = z - era * 146_097;
195 let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
196 let y = yoe + era * 400;
197 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
198 let mp = (5 * doy + 2) / 153;
199 let day = doy - (153 * mp + 2) / 5 + 1;
200 let month = mp + if mp < 10 { 3 } else { -9 };
201 let year = y + if month <= 2 { 1 } else { 0 };
202 (year as i32, month as u32, day as u32)
203}