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 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
39 {
40 return js_sys::Date::now() as i64;
46 }
47 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
48 {
49 let now = std::time::SystemTime::now()
50 .duration_since(std::time::UNIX_EPOCH)
51 .expect("Time.unixMs: system clock error");
52 i64::try_from(now.as_millis()).expect("Time.unixMs: value out of i64 range")
53 }
54}
55
56pub fn time_sleep(ms: i64) {
57 if ms < 0 {
58 panic!("Time.sleep: ms must be non-negative");
59 }
60 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
67 {
68 let _ = ms;
69 return;
70 }
71 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
72 std::thread::sleep(std::time::Duration::from_millis(ms as u64));
73}
74
75pub fn string_slice(s: &str, from: i64, to: i64) -> String {
76 let start = from.max(0) as usize;
77 let end = to.max(0) as usize;
78 if start >= end {
79 return String::new();
80 }
81
82 let mut start_byte = None;
83 let mut end_byte = None;
84 let mut char_index = 0usize;
85
86 for (byte_index, _) in s.char_indices() {
87 if char_index == start {
88 start_byte = Some(byte_index);
89 }
90 if char_index == end {
91 end_byte = Some(byte_index);
92 break;
93 }
94 char_index += 1;
95 }
96
97 if start_byte.is_none() && char_index == start {
98 start_byte = Some(s.len());
99 }
100 if end_byte.is_none() && char_index == end {
101 end_byte = Some(s.len());
102 }
103
104 let start_byte = start_byte.unwrap_or(s.len());
105 let end_byte = end_byte.unwrap_or(s.len());
106 if start_byte >= end_byte {
107 return String::new();
108 }
109
110 s[start_byte..end_byte].to_string()
111}
112
113pub fn read_text(path: &str) -> Result<String, String> {
114 std::fs::read_to_string(path).map_err(|e| e.to_string())
115}
116
117pub fn write_text(path: &str, content: &str) -> Result<(), String> {
118 std::fs::write(path, content)
119 .map(|_| ())
120 .map_err(|e| e.to_string())
121}
122
123pub fn append_text(path: &str, content: &str) -> Result<(), String> {
124 use std::io::Write;
125
126 let mut file = std::fs::OpenOptions::new()
127 .create(true)
128 .append(true)
129 .open(path)
130 .map_err(|e| e.to_string())?;
131 file.write_all(content.as_bytes())
132 .map_err(|e| e.to_string())
133}
134
135pub fn path_exists(path: &str) -> bool {
136 std::path::Path::new(path).exists()
137}
138
139pub fn delete_file(path: &str) -> Result<(), String> {
140 let p = std::path::Path::new(path);
141 if p.is_dir() {
142 return Err(
143 "Disk.delete: path is a directory — use Disk.deleteDir to remove directories"
144 .to_string(),
145 );
146 }
147 std::fs::remove_file(p)
148 .map(|_| ())
149 .map_err(|e| e.to_string())
150}
151
152pub fn delete_dir(path: &str) -> Result<(), String> {
153 let p = std::path::Path::new(path);
154 if !p.is_dir() {
155 return Err(
156 "Disk.deleteDir: path is not a directory — use Disk.delete to remove files".to_string(),
157 );
158 }
159 std::fs::remove_dir_all(p)
160 .map(|_| ())
161 .map_err(|e| e.to_string())
162}
163
164pub fn list_dir(path: &str) -> Result<AverList<String>, String> {
165 let entries = std::fs::read_dir(path).map_err(|e| e.to_string())?;
166 let mut result = Vec::new();
167 for entry in entries {
168 let entry = entry.map_err(|e| e.to_string())?;
169 result.push(entry.file_name().to_string_lossy().into_owned());
170 }
171 Ok(AverList::from_vec(result))
172}
173
174pub fn make_dir(path: &str) -> Result<(), String> {
175 std::fs::create_dir_all(path)
176 .map(|_| ())
177 .map_err(|e| e.to_string())
178}
179
180pub fn env_get(key: &str) -> Option<String> {
181 std::env::var(key).ok()
182}
183
184pub fn env_set(key: &str, value: &str) -> Result<(), String> {
185 validate_env_key(key)?;
186 if value.contains('\0') {
187 return Err("Env.set: value must not contain NUL".to_string());
188 }
189
190 unsafe {
191 std::env::set_var(key, value);
192 }
193 Ok(())
194}
195
196pub fn cli_args() -> AverList<String> {
197 AverList::from_vec(std::env::args().skip(1).collect())
198}
199
200fn validate_env_key(key: &str) -> Result<(), String> {
201 if key.is_empty() {
202 return Err("Env.set: key must not be empty".to_string());
203 }
204 if key.contains('=') {
205 return Err("Env.set: key must not contain '='".to_string());
206 }
207 if key.contains('\0') {
208 return Err("Env.set: key must not contain NUL".to_string());
209 }
210 Ok(())
211}
212
213fn unix_parts_now() -> (i64, u32) {
214 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
215 {
216 let ms = js_sys::Date::now();
219 let secs = (ms / 1000.0) as i64;
220 let nanos = ((ms.rem_euclid(1000.0)) * 1_000_000.0) as u32;
221 return (secs, nanos);
222 }
223 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
224 {
225 match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
226 Ok(d) => (
227 i64::try_from(d.as_secs()).expect("Time.now: seconds out of i64 range"),
228 d.subsec_nanos(),
229 ),
230 Err(e) => {
231 let d = e.duration();
232 let secs = i64::try_from(d.as_secs()).expect("Time.now: seconds out of i64 range");
233 let nanos = d.subsec_nanos();
234 if nanos == 0 {
235 (-secs, 0)
236 } else {
237 (-(secs + 1), 1_000_000_000 - nanos)
238 }
239 }
240 }
241 }
242}
243
244fn format_utc_rfc3339_like(unix_secs: i64, nanos: u32) -> String {
245 let days = unix_secs.div_euclid(86_400);
246 let sod = unix_secs.rem_euclid(86_400);
247 let hour = sod / 3_600;
248 let minute = (sod % 3_600) / 60;
249 let second = sod % 60;
250 let millis = nanos / 1_000_000;
251 let (year, month, day) = civil_from_days(days);
252 format!(
253 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
254 year, month, day, hour, minute, second, millis
255 )
256}
257
258fn civil_from_days(days_since_epoch: i64) -> (i32, u32, u32) {
259 let z = days_since_epoch + 719_468;
260 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
261 let doe = z - era * 146_097;
262 let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
263 let y = yoe + era * 400;
264 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
265 let mp = (5 * doy + 2) / 153;
266 let day = doy - (153 * mp + 2) / 5 + 1;
267 let month = mp + if mp < 10 { 3 } else { -9 };
268 let year = y + if month <= 2 { 1 } else { 0 };
269 (year as i32, month as u32, day as u32)
270}