koto_runtime/core_lib/
os.rs

1//! The `os` core library module
2
3mod command;
4
5use self::command::Command;
6use crate::{derive::*, prelude::*, Result};
7use chrono::prelude::*;
8use instant::Instant;
9
10/// Initializes the `os` core library module
11pub fn make_module() -> KMap {
12    use KValue::Number;
13
14    let result = KMap::with_type("core.os");
15
16    result.add_fn("command", |ctx| match ctx.args() {
17        [KValue::Str(command)] => Ok(Command::make_value(command)),
18        unexpected => unexpected_args("|String|", unexpected),
19    });
20
21    result.add_fn("name", |ctx| match ctx.args() {
22        [] => Ok(std::env::consts::OS.into()),
23        unexpected => unexpected_args("||", unexpected),
24    });
25
26    result.add_fn("process_id", |ctx| match ctx.args() {
27        [] => {
28            #[cfg(target_arch = "wasm32")]
29            {
30                // `process::id()` panics on wasm targets
31                return runtime_error!(crate::ErrorKind::UnsupportedPlatform);
32            }
33
34            #[cfg(not(target_arch = "wasm32"))]
35            {
36                Ok(std::process::id().into())
37            }
38        }
39        unexpected => unexpected_args("||", unexpected),
40    });
41
42    result.add_fn("start_timer", |ctx| match ctx.args() {
43        [] => Ok(Timer::now()),
44        unexpected => unexpected_args("||", unexpected),
45    });
46
47    result.add_fn("time", |ctx| match ctx.args() {
48        [] => Ok(DateTime::now()),
49        [Number(seconds)] => DateTime::from_seconds(seconds.into(), None),
50        [Number(seconds), Number(offset)] => {
51            DateTime::from_seconds(seconds.into(), Some(offset.into()))
52        }
53        unexpected => unexpected_args("||, or |Number|, or |Number, Number|", unexpected),
54    });
55
56    result
57}
58
59/// The underlying data type returned by `os.time()`
60#[derive(Clone, Debug, KotoCopy, KotoType)]
61pub struct DateTime(chrono::DateTime<FixedOffset>);
62
63#[koto_impl(runtime = crate)]
64impl DateTime {
65    fn with_chrono_datetime(time: chrono::DateTime<FixedOffset>) -> KValue {
66        KObject::from(Self(time)).into()
67    }
68
69    fn now() -> KValue {
70        Self::with_chrono_datetime(Local::now().fixed_offset())
71    }
72
73    fn from_seconds(seconds: f64, maybe_offset: Option<i64>) -> Result<KValue> {
74        let seconds_i64 = seconds as i64;
75        let sub_nanos = (seconds.fract() * 1.0e9) as u32;
76        match chrono::DateTime::from_timestamp(seconds_i64, sub_nanos) {
77            Some(utc) => {
78                let offset = match maybe_offset {
79                    Some(offset) => match FixedOffset::east_opt(offset as i32) {
80                        Some(offset) => offset,
81                        None => return runtime_error!("time offset is out of range: {offset}"),
82                    },
83                    None => *Local::now().offset(),
84                };
85                let local = utc.with_timezone(&offset);
86                Ok(Self::with_chrono_datetime(local))
87            }
88            None => runtime_error!("timestamp in seconds is out of range: {seconds}"),
89        }
90    }
91
92    #[koto_method]
93    fn day(&self) -> KValue {
94        self.0.day().into()
95    }
96
97    #[koto_method]
98    fn hour(&self) -> KValue {
99        self.0.hour().into()
100    }
101
102    #[koto_method]
103    fn minute(&self) -> KValue {
104        self.0.minute().into()
105    }
106
107    #[koto_method]
108    fn month(&self) -> KValue {
109        self.0.month().into()
110    }
111
112    #[koto_method]
113    fn second(&self) -> KValue {
114        self.0.second().into()
115    }
116
117    #[koto_method]
118    fn nanosecond(&self) -> KValue {
119        self.0.nanosecond().into()
120    }
121
122    #[koto_method]
123    fn timestamp(&self) -> KValue {
124        let seconds = self.0.timestamp() as f64;
125        let sub_nanos = self.0.timestamp_subsec_nanos();
126        (seconds + sub_nanos as f64 / 1.0e9).into()
127    }
128
129    #[koto_method]
130    fn timezone_offset(&self) -> KValue {
131        self.0.offset().local_minus_utc().into()
132    }
133
134    #[koto_method]
135    fn timezone_string(&self) -> KValue {
136        self.0.format("%z").to_string().into()
137    }
138
139    #[koto_method]
140    fn year(&self) -> KValue {
141        self.0.year().into()
142    }
143}
144
145impl KotoObject for DateTime {
146    fn display(&self, ctx: &mut DisplayContext) -> Result<()> {
147        ctx.append(self.0.format("%F %T").to_string());
148        Ok(())
149    }
150}
151
152/// The underlying data type returned by `os.start_timer()`
153#[derive(Clone, Debug, KotoCopy, KotoType)]
154pub struct Timer(Instant);
155
156#[koto_impl(runtime = crate)]
157impl Timer {
158    fn now() -> KValue {
159        let timer = Self(Instant::now());
160        KObject::from(timer).into()
161    }
162
163    fn elapsed_seconds(&self) -> f64 {
164        self.0.elapsed().as_secs_f64()
165    }
166
167    #[koto_method]
168    fn elapsed(&self) -> KValue {
169        self.elapsed_seconds().into()
170    }
171}
172
173impl KotoObject for Timer {
174    fn display(&self, ctx: &mut DisplayContext) -> Result<()> {
175        ctx.append(format!("Timer({:.3}s)", self.elapsed_seconds()));
176        Ok(())
177    }
178
179    fn subtract(&self, rhs: &KValue) -> Result<KValue> {
180        match rhs {
181            KValue::Object(o) if o.is_a::<Self>() => {
182                let rhs = o.cast::<Self>().unwrap();
183
184                let result = if self.0 >= rhs.0 {
185                    self.0.duration_since(rhs.0).as_secs_f64()
186                } else {
187                    -(rhs.0.duration_since(self.0).as_secs_f64())
188                };
189
190                Ok(result.into())
191            }
192            unexpected => unexpected_type(Self::type_static(), unexpected),
193        }
194    }
195}