Skip to main content

aver_rt/
runtime.rs

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