ort_openrouter_cli/common/
utils.rs1extern crate alloc;
8use alloc::ffi::CString;
9use alloc::string::{String, ToString};
10use alloc::vec::Vec;
11
12use core::ffi::{c_str::CStr, c_void};
13
14use crate::libc;
15
16pub(crate) fn to_ascii(mut num: usize, buf: &mut [u8]) -> usize {
21 if num == 0 {
22 buf[0] = b'0';
23 buf[1] = 0;
24 return 2;
25 }
26
27 let mut div = 1usize;
28 while num / div >= 10 {
29 div *= 10;
30 }
31
32 let mut i = 0usize;
33 while div != 0 {
34 buf[i] = b'0' + (num / div) as u8;
35 num %= div;
36 div /= 10;
37 i += 1;
38 }
39 buf[i] = b'\n';
40 i += 1;
41 buf[i] = 0;
42 i + 1
43}
44
45pub(crate) fn num_to_string(mut num: usize) -> String {
46 if num == 0 {
47 return "0".to_string();
48 }
49
50 let mut buf: [u8; 20] = [0; 20];
51 let mut div = 1usize;
52 while num / div >= 10 {
53 div *= 10;
54 }
55
56 let mut i = 0usize;
57 while div != 0 {
58 buf[i] = b'0' + (num / div) as u8;
59 num %= div;
60 div /= 10;
61 i += 1;
62 }
63
64 unsafe { String::from_utf8_unchecked(buf[..i].into()) }
65}
66
67pub(crate) fn float_to_string(mut f: f64, significant_digits: usize) -> String {
70 if f.is_nan() {
71 return "NaN".into();
72 }
73 if f.is_infinite() {
74 return if f < 0.0 { "-inf".into() } else { "inf".into() };
75 }
76
77 let mut result = String::new();
78
79 if f < 0.0 {
80 result.push('-');
81 f = -f;
82 }
83
84 let mut integer_part = f as u64;
86 let mut fraction_part = f - (integer_part as f64);
87
88 if integer_part == 0 {
90 result.push('0');
91 } else {
92 let mut buffer = [0u8; 20]; let mut idx = 0;
94 while integer_part > 0 {
95 buffer[idx] = (integer_part % 10) as u8 + b'0';
96 integer_part /= 10;
97 idx += 1;
98 }
99 while idx > 0 {
100 idx -= 1;
101 result.push(buffer[idx] as char);
102 }
103 }
104
105 if significant_digits > 0 {
106 result.push('.');
107
108 for _ in 0..significant_digits {
109 fraction_part *= 10.0;
110 let digit = fraction_part as u8; result.push((digit + b'0') as char);
112 fraction_part -= digit as f64;
113 }
114 }
115
116 result
117}
118
119#[allow(unused)]
120pub(crate) fn print_hex(prefix: &CStr, v: &[u8]) {
121 let hex: alloc::string::String = v
122 .iter()
123 .map(|b| alloc::format!(" {:02x}", b))
124 .collect::<Vec<_>>()
125 .join("");
126 print_string(prefix, &hex);
127}
128
129#[allow(unused)]
130pub(crate) fn print_string(prefix: &CStr, s: &str) {
131 let msg = CString::new(zclean(&mut s.to_string())).unwrap();
132 unsafe { libc::printf(c"%s%s\n".as_ptr(), prefix.as_ptr(), msg.as_ptr()) };
133}
134
135pub(crate) fn zclean(s: &mut str) -> &str {
138 for byte in unsafe { s.as_bytes_mut() } {
139 if *byte == 0 {
140 *byte = b'_';
141 }
142 }
143 s
144}
145
146pub(crate) fn slug(s: &str) -> String {
147 let mut out = String::with_capacity(16);
148 out.extend(s.chars().map(|c| {
149 if c.is_alphanumeric() {
150 c.to_lowercase().next().unwrap_or('-')
151 } else {
152 '-'
153 }
154 }));
155 out
156}
157
158pub(crate) fn last_filename() -> String {
160 let mut buf: [u8; 3] = [0; 3];
162 let buf_len = tmux_pane_id(&mut buf);
163
164 let mut out = String::with_capacity(16);
165 out.push_str("last-");
166 out.push_str(unsafe { str::from_utf8_unchecked(&buf[..buf_len]) });
168 out.push_str(".json");
169
170 out
171}
172
173pub fn tmux_pane_id(buf: &mut [u8]) -> usize {
177 let v = get_env(c"TMUX_PANE");
178 if v.is_empty() {
179 buf[0] = b'0';
180 return 1;
181 }
182 let id_len = v.count_bytes() - 1;
184 buf[..id_len].copy_from_slice(&v.to_bytes()[1..]);
185 id_len
186}
187
188pub(crate) fn get_env(cs: &CStr) -> &'static CStr {
191 let value_ptr = unsafe { libc::getenv(cs.as_ptr()) };
193 if value_ptr.is_null() {
194 return c"";
195 }
196 unsafe { CStr::from_ptr(value_ptr) }
198}
199
200pub(crate) fn ensure_dir_exists(dir: &str) {
202 let cs = CString::new(dir).unwrap();
203 if !path_exists(cs.as_ref()) {
204 unsafe { libc::mkdir(cs.as_ptr(), 0o755) };
205 }
206}
207
208pub(crate) fn path_exists(path: &CStr) -> bool {
210 unsafe { libc::access(path.as_ptr(), libc::F_OK) == 0 }
211}
212
213pub(crate) fn filename_read_to_string(filename: &str) -> Result<String, &'static str> {
215 let cs = CString::new(filename).unwrap();
216 let fd = unsafe { libc::open(cs.as_ptr(), libc::O_RDONLY) };
217 if fd < 0 {
218 return Err("NOT FOUND");
219 }
220
221 let mut content = Vec::new();
222 let mut buffer = [0u8; 4096];
223
224 loop {
225 let bytes_read =
226 unsafe { libc::read(fd, buffer.as_mut_ptr() as *mut c_void, buffer.len()) };
227
228 if bytes_read < 0 {
229 let _ = unsafe { libc::close(fd) };
230 return Err("READ ERROR");
231 }
232 if bytes_read == 0 {
233 break;
234 }
235 let bytes_read = bytes_read as usize; content.extend_from_slice(&buffer[..bytes_read]);
237 }
238
239 let out = String::from_utf8_lossy(&content);
240 Ok(out.into_owned().to_string())
241}
242
243#[cfg(test)]
244mod tests {
245 use super::float_to_string;
246
247 #[test]
248 fn nan_and_infinity() {
249 assert_eq!(float_to_string(f64::NAN, 3), "NaN");
250 assert_eq!(float_to_string(f64::INFINITY, 3), "inf");
251 assert_eq!(float_to_string(f64::NEG_INFINITY, 3), "-inf");
252 }
253
254 #[test]
255 fn sign_and_integer_part() {
256 assert_eq!(float_to_string(-2.5, 1), "-2.5");
257 assert_eq!(float_to_string(0.0, 0), "0");
258 assert_eq!(float_to_string(-0.0, 2), "0.00"); assert_eq!(float_to_string(12345.0, 0), "12345");
260 }
261
262 #[test]
263 fn fractional_digits_truncate_not_round() {
264 assert_eq!(float_to_string(1.875, 2), "1.87");
266 }
267
268 #[test]
269 fn fractional_leading_zeros() {
270 assert_eq!(float_to_string(0.015625, 3), "0.015");
272 }
273
274 #[test]
275 fn no_decimal_point_when_zero_digits() {
276 assert_eq!(float_to_string(3.75, 0), "3");
277 }
278}