1use std::collections::BTreeMap;
31use std::fs::File;
32use std::io::Write;
33use std::path::Path;
34use std::sync::Mutex;
35use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
36
37pub struct Logger {
39 file: Option<Mutex<File>>,
40 echo: bool,
41 tz_offset_s: i64,
42}
43
44impl Logger {
45 pub fn new(path: Option<&Path>, echo: bool) -> std::io::Result<Self> {
47 let file = match path {
48 Some(p) => Some(Mutex::new(File::create(p)?)),
49 None => None,
50 };
51 Ok(Self { file, echo, tz_offset_s: local_tz_offset() })
52 }
53
54 pub fn line(&self, s: &str) {
56 if let Some(f) = &self.file {
57 if let Ok(mut f) = f.lock() {
58 let _ = writeln!(f, "{s}");
59 let _ = f.flush();
60 }
61 }
62 if self.echo {
63 println!("{s}");
64 }
65 }
66
67 pub fn stamped(&self, s: &str) {
69 self.line(&format!("[{}] {s}", self.clock()));
70 }
71
72 pub fn stamped_at(&self, at: SystemTime, s: &str) {
74 self.line(&format!("[{}] {s}", self.clock_at(at)));
75 }
76
77 pub fn clock(&self) -> String {
79 self.clock_at(SystemTime::now())
80 }
81
82 pub fn clock_at(&self, at: SystemTime) -> String {
84 let t = at.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() as i64 + self.tz_offset_s;
85 let d = t.rem_euclid(86400);
86 format!("{:02}:{:02}:{:02}", d / 3600, (d / 60) % 60, d % 60)
87 }
88}
89
90fn local_tz_offset() -> i64 {
92 let out = std::process::Command::new("date").arg("+%z").output();
93 let Ok(out) = out else { return 0 };
94 let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
95 if s.len() != 5 {
96 return 0;
97 }
98 let sign = if s.starts_with('-') { -1 } else { 1 };
99 let h: i64 = s[1..3].parse().unwrap_or(0);
100 let m: i64 = s[3..5].parse().unwrap_or(0);
101 sign * (h * 3600 + m * 60)
102}
103
104pub struct Timeline {
106 file: Mutex<File>,
107 events: Mutex<Vec<(f64, String)>>,
108}
109
110impl Timeline {
111 pub fn new(path: &Path) -> std::io::Result<Self> {
113 Ok(Self { file: Mutex::new(File::create(path)?), events: Mutex::new(Vec::new()) })
114 }
115
116 pub fn ev(&self, log: &Logger, s: &str) {
118 let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default();
119 let line = format!("{}.{:09} {s}", now.as_secs(), now.subsec_nanos());
120 if let Ok(mut f) = self.file.lock() {
121 let _ = writeln!(f, "{line}");
122 let _ = f.flush();
123 }
124 if let Ok(mut e) = self.events.lock() {
125 e.push((now.as_secs_f64(), s.to_string()));
126 }
127 log.stamped(s);
128 }
129
130 pub fn relative(&self) -> Vec<(f64, String)> {
132 let e = self.events.lock().map(|e| e.clone()).unwrap_or_default();
133 let t0 = e.first().map(|x| x.0).unwrap_or(0.0);
134 e.into_iter().map(|(t, s)| (t - t0, s)).collect()
135 }
136}
137
138#[derive(Default)]
140pub struct StageTimes {
141 m: Mutex<BTreeMap<String, (Duration, usize)>>,
142}
143
144impl StageTimes {
145 pub fn add(&self, stage: &str, d: Duration) {
147 if let Ok(mut m) = self.m.lock() {
148 let e = m.entry(stage.to_string()).or_default();
149 e.0 += d;
150 e.1 += 1;
151 }
152 }
153
154 pub fn snapshot(&self) -> BTreeMap<String, (Duration, usize)> {
156 self.m.lock().map(|m| m.clone()).unwrap_or_default()
157 }
158}
159
160pub fn timed<T>(log: &Logger, times: &StageTimes, stage: &str, what: &str, f: impl FnOnce() -> T) -> T {
162 let t = Instant::now();
163 let r = f();
164 let d = t.elapsed();
165 times.add(stage, d);
166 log.stamped(&format!("[time] {stage} {what}: {:.3}s", d.as_secs_f64()));
167 r
168}
169
170pub fn py_float(x: f64, digits: i32) -> String {
172 let p = 10f64.powi(digits);
173 let r = (x * p).round() / p;
174 let r = if r == 0.0 { 0.0 } else { r };
175 if !r.is_finite() {
176 return if r.is_nan() {
177 "NaN".into()
178 } else if r > 0.0 {
179 "Infinity".into()
180 } else {
181 "-Infinity".into()
182 };
183 }
184 let s = format!("{r}");
185 if s.contains('.') || s.contains('e') {
186 s
187 } else {
188 format!("{s}.0")
189 }
190}
191
192#[derive(Clone, Debug)]
194pub enum Json {
195 Null,
197 Bool(bool),
199 Int(i64),
201 Num(String),
203 Str(String),
205 Arr(Vec<Json>),
207 Obj(Vec<(String, Json)>),
209}
210
211impl Json {
212 pub fn obj(items: Vec<(&str, Json)>) -> Json {
214 Json::Obj(items.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
215 }
216 pub fn f(x: f64, digits: i32) -> Json {
218 Json::Num(py_float(x, digits))
219 }
220 pub fn of(x: Option<f64>, digits: i32) -> Json {
222 x.map_or(Json::Null, |v| Json::f(v, digits))
223 }
224 pub fn ints(v: &[usize]) -> Json {
226 Json::Arr(v.iter().map(|x| Json::Int(*x as i64)).collect())
227 }
228
229 pub fn dump(&self) -> String {
231 let mut s = String::new();
232 self.write(&mut s, 0);
233 s
234 }
235
236 fn write(&self, s: &mut String, ind: usize) {
237 match self {
238 Json::Null => s.push_str("null"),
239 Json::Bool(b) => s.push_str(if *b { "true" } else { "false" }),
240 Json::Int(i) => s.push_str(&i.to_string()),
241 Json::Num(n) => s.push_str(n),
242 Json::Str(t) => push_str_lit(s, t),
243 Json::Arr(v) => {
244 if v.is_empty() {
245 s.push_str("[]");
246 return;
247 }
248 s.push_str("[\n");
249 for (i, x) in v.iter().enumerate() {
250 s.push_str(&" ".repeat(ind + 1));
251 x.write(s, ind + 1);
252 if i + 1 < v.len() {
253 s.push(',');
254 }
255 s.push('\n');
256 }
257 s.push_str(&" ".repeat(ind));
258 s.push(']');
259 }
260 Json::Obj(v) => {
261 if v.is_empty() {
262 s.push_str("{}");
263 return;
264 }
265 s.push_str("{\n");
266 for (i, (k, x)) in v.iter().enumerate() {
267 s.push_str(&" ".repeat(ind + 1));
268 push_str_lit(s, k);
269 s.push_str(": ");
270 x.write(s, ind + 1);
271 if i + 1 < v.len() {
272 s.push(',');
273 }
274 s.push('\n');
275 }
276 s.push_str(&" ".repeat(ind));
277 s.push('}');
278 }
279 }
280 }
281
282 pub fn py_repr(&self) -> String {
284 match self {
285 Json::Null => "None".into(),
286 Json::Bool(b) => (if *b { "True" } else { "False" }).into(),
287 Json::Int(i) => i.to_string(),
288 Json::Num(n) => n.clone(),
289 Json::Str(t) => format!("'{}'", t.replace('\\', "\\\\").replace('\'', "\\'")),
290 Json::Arr(v) => format!("[{}]", v.iter().map(|x| x.py_repr()).collect::<Vec<_>>().join(", ")),
291 Json::Obj(v) => {
292 format!("{{{}}}", v.iter().map(|(k, x)| format!("'{k}': {}", x.py_repr())).collect::<Vec<_>>().join(", "))
293 }
294 }
295 }
296
297 pub fn get(&self, key: &str) -> Option<&Json> {
299 match self {
300 Json::Obj(v) => v.iter().find(|(k, _)| k == key).map(|(_, x)| x),
301 _ => None,
302 }
303 }
304
305 pub fn as_f64(&self) -> Option<f64> {
307 match self {
308 Json::Int(i) => Some(*i as f64),
309 Json::Num(n) => n.parse().ok(),
310 _ => None,
311 }
312 }
313
314 pub fn parse(text: &str) -> Result<Json, String> {
316 let mut p = JsonParser { s: text.as_bytes(), i: 0 };
317 let v = p.value()?;
318 p.ws();
319 if p.i != p.s.len() {
320 return Err(format!("JSON: {} 바이트에 뒤따르는 내용", p.i));
321 }
322 Ok(v)
323 }
324}
325
326struct JsonParser<'a> {
327 s: &'a [u8],
328 i: usize,
329}
330
331impl JsonParser<'_> {
332 fn ws(&mut self) {
333 while self.i < self.s.len() && self.s[self.i].is_ascii_whitespace() {
334 self.i += 1;
335 }
336 }
337
338 fn eat(&mut self, c: u8) -> Result<(), String> {
339 self.ws();
340 if self.s.get(self.i) == Some(&c) {
341 self.i += 1;
342 Ok(())
343 } else {
344 Err(format!("JSON: {} 바이트에서 '{}' 필요", self.i, c as char))
345 }
346 }
347
348 fn lit(&mut self, w: &str, v: Json) -> Result<Json, String> {
349 if self.s[self.i..].starts_with(w.as_bytes()) {
350 self.i += w.len();
351 Ok(v)
352 } else {
353 Err(format!("JSON: {} 바이트의 값을 읽을 수 없음", self.i))
354 }
355 }
356
357 fn value(&mut self) -> Result<Json, String> {
358 self.ws();
359 match self.s.get(self.i) {
360 None => Err("JSON: 내용이 끝남".into()),
361 Some(b'{') => {
362 self.i += 1;
363 let mut items = Vec::new();
364 self.ws();
365 if self.s.get(self.i) == Some(&b'}') {
366 self.i += 1;
367 return Ok(Json::Obj(items));
368 }
369 loop {
370 self.ws();
371 let k = self.string()?;
372 self.eat(b':')?;
373 items.push((k, self.value()?));
374 self.ws();
375 match self.s.get(self.i) {
376 Some(b',') => self.i += 1,
377 Some(b'}') => {
378 self.i += 1;
379 return Ok(Json::Obj(items));
380 }
381 _ => return Err(format!("JSON: {} 바이트에서 ',' 또는 '}}' 필요", self.i)),
382 }
383 }
384 }
385 Some(b'[') => {
386 self.i += 1;
387 let mut items = Vec::new();
388 self.ws();
389 if self.s.get(self.i) == Some(&b']') {
390 self.i += 1;
391 return Ok(Json::Arr(items));
392 }
393 loop {
394 items.push(self.value()?);
395 self.ws();
396 match self.s.get(self.i) {
397 Some(b',') => self.i += 1,
398 Some(b']') => {
399 self.i += 1;
400 return Ok(Json::Arr(items));
401 }
402 _ => return Err(format!("JSON: {} 바이트에서 ',' 또는 ']' 필요", self.i)),
403 }
404 }
405 }
406 Some(b'"') => self.string().map(Json::Str),
407 Some(b't') => self.lit("true", Json::Bool(true)),
408 Some(b'f') => self.lit("false", Json::Bool(false)),
409 Some(b'n') => self.lit("null", Json::Null),
410 Some(_) => {
411 let st = self.i;
412 while self.i < self.s.len() && matches!(self.s[self.i], b'0'..=b'9' | b'-' | b'+' | b'.' | b'e' | b'E') {
413 self.i += 1;
414 }
415 let t = std::str::from_utf8(&self.s[st..self.i]).unwrap_or("");
416 if t.is_empty() || t.parse::<f64>().is_err() {
417 return Err(format!("JSON: {st} 바이트의 값을 읽을 수 없음"));
418 }
419 Ok(t.parse::<i64>().map_or_else(|_| Json::Num(t.to_string()), Json::Int))
420 }
421 }
422 }
423
424 fn string(&mut self) -> Result<String, String> {
425 self.eat(b'"')?;
426 let mut out: Vec<u8> = Vec::new();
427 loop {
428 let Some(&c) = self.s.get(self.i) else { return Err("JSON: 문자열이 끝나지 않음".into()) };
429 self.i += 1;
430 match c {
431 b'"' => return String::from_utf8(out).map_err(|e| e.to_string()),
432 b'\\' => {
433 let Some(&e) = self.s.get(self.i) else { return Err("JSON: 문자열이 끝나지 않음".into()) };
434 self.i += 1;
435 match e {
436 b'n' => out.push(b'\n'),
437 b't' => out.push(b'\t'),
438 b'r' => out.push(b'\r'),
439 b'b' => out.push(8),
440 b'f' => out.push(12),
441 b'u' => {
442 let h = self.s.get(self.i..self.i + 4).and_then(|h| std::str::from_utf8(h).ok());
443 let c = h.and_then(|h| u32::from_str_radix(h, 16).ok()).and_then(char::from_u32).unwrap_or('\u{fffd}');
444 self.i += 4;
445 let mut b = [0u8; 4];
446 out.extend_from_slice(c.encode_utf8(&mut b).as_bytes());
447 }
448 other => out.push(other),
449 }
450 }
451 c => out.push(c),
452 }
453 }
454 }
455}
456
457fn push_str_lit(s: &mut String, t: &str) {
458 s.push('"');
459 for c in t.chars() {
460 match c {
461 '"' => s.push_str("\\\""),
462 '\\' => s.push_str("\\\\"),
463 '\n' => s.push_str("\\n"),
464 '\t' => s.push_str("\\t"),
465 c if (c as u32) < 0x20 => s.push_str(&format!("\\u{:04x}", c as u32)),
466 c => s.push(c),
467 }
468 }
469 s.push('"');
470}
471
472#[cfg(test)]
473mod tests {
474 use super::*;
475
476 #[test]
477 fn py_float_matches_python_repr() {
478 assert_eq!(py_float(1.0, 3), "1.0");
479 assert_eq!(py_float(12.345, 1), "12.3");
480 assert_eq!(py_float(-0.0001, 2), "0.0");
481 assert_eq!(py_float(0.987654, 3), "0.988");
482 }
483
484 #[test]
485 fn json_parse_reads_nested_values() {
486 let j = Json::parse(r#" {"note": "a\"b\u00e9", "cameras": {"1": [1607.5, 1611, 1e2, -0.08], "2": []}, "ok": true, "n": null} "#)
487 .unwrap();
488 assert!(matches!(j.get("note"), Some(Json::Str(s)) if s == "a\"bé"));
489 let Some(Json::Arr(v)) = j.get("cameras").and_then(|c| c.get("1")) else { panic!() };
490 let f: Vec<f64> = v.iter().filter_map(Json::as_f64).collect();
491 assert_eq!(f, vec![1607.5, 1611.0, 100.0, -0.08]);
492 assert!(matches!(j.get("ok"), Some(Json::Bool(true))) && matches!(j.get("n"), Some(Json::Null)));
493 assert!(Json::parse("{\"a\": }").is_err() && Json::parse("[1, 2").is_err() && Json::parse("1 2").is_err());
494 let d = Json::obj(vec![("a", Json::Int(1)), ("b", Json::Arr(vec![Json::f(0.5, 2), Json::Null]))]);
496 assert_eq!(Json::parse(&d.dump()).unwrap().dump(), d.dump());
497 }
498
499 #[test]
500 fn json_dump_indent1() {
501 let j = Json::obj(vec![("a", Json::Int(1)), ("b", Json::Arr(vec![])), ("c", Json::Arr(vec![Json::Null]))]);
502 assert_eq!(j.dump(), "{\n \"a\": 1,\n \"b\": [],\n \"c\": [\n null\n ]\n}");
503 assert_eq!(j.py_repr(), "{'a': 1, 'b': [], 'c': [None]}");
504 }
505}