Skip to main content

geam_time/
lib.rs

1mod calendar;
2mod source;
3mod timestamp;
4
5pub use self::source::SystemTimeSource;
6#[cfg(test)]
7pub(crate) use geam_core::{
8    ExecutionError, HostError, HostModule, HostProviderSet, HostedExecution, InvariantError,
9    ModuleSource, PackageSource, ValueType, compile_typed_host_program, plan_host_program,
10};
11pub(crate) use geam_core::{
12    HostComponentProfile, HostFailure, HostProfile, HostProviderComponent,
13    HostProviderComponentRegistration, HostProviderModule, HostRegistrationError,
14};
15use geam_stdlib::{
16    Component as GleamStdlibComponent, GleamStdlibHostProfile, GleamStdlibRunState,
17    GleamStdlibStores, IoOutput,
18};
19use std::marker::PhantomData;
20use std::time::SystemTime;
21
22/// A caller-owned source for official Gleam wall-clock operations.
23pub trait TimeSource: 'static {
24    /// Returns the current wall-clock time.
25    fn system_time(&mut self) -> Result<SystemTime, HostFailure>;
26
27    /// Returns the current local offset from UTC in seconds.
28    fn local_offset_seconds(&mut self) -> Result<i32, HostFailure>;
29}
30
31/// A host profile that composes the official Gleam Time and standard-library components.
32pub trait GleamTimeHostProfile:
33    GleamStdlibHostProfile + HostComponentProfile<Component<Self::Source>>
34{
35    /// The concrete caller-owned wall-clock source.
36    type Source: TimeSource;
37}
38
39/// The statically composed provider component for the official Gleam Time package.
40#[derive(Debug, Clone, Copy)]
41pub struct Component<Source = SystemTimeSource>(PhantomData<fn() -> Source>);
42
43impl<Source> HostProviderComponent for Component<Source>
44where
45    Source: TimeSource,
46{
47    const ID: &'static str = "gleam_time";
48    type Stores = ();
49    type RunState = Source;
50}
51
52impl<Source> geam_core::__macro_support::ProviderPackage for Component<Source>
53where
54    Source: TimeSource,
55{
56    const PACKAGE: &'static str = "gleam_time";
57}
58
59/// External stores for the default combined standard-library and Time profile.
60#[derive(Default)]
61pub struct GleamTimeProfileStores {
62    stdlib: GleamStdlibStores,
63    time: (),
64}
65
66/// Caller-owned run state for the default Gleam Time profile.
67pub struct GleamTimeRunState<Source = SystemTimeSource> {
68    stdlib: GleamStdlibRunState,
69    source: Source,
70}
71
72/// The default profile for composing official standard-library and Time providers.
73#[derive(Debug, Clone, Copy)]
74pub struct GleamTimeProfile<Source = SystemTimeSource>(PhantomData<fn() -> Source>);
75
76impl<Source> GleamTimeRunState<Source> {
77    /// Combines caller-owned standard-library state and a wall-clock source.
78    pub fn new(stdlib: GleamStdlibRunState, source: Source) -> Self {
79        Self { stdlib, source }
80    }
81
82    /// Returns the standard-library state.
83    pub fn stdlib(&self) -> &GleamStdlibRunState {
84        &self.stdlib
85    }
86
87    /// Returns mutable standard-library state.
88    pub fn stdlib_mut(&mut self) -> &mut GleamStdlibRunState {
89        &mut self.stdlib
90    }
91
92    /// Returns the wall-clock source.
93    pub fn source(&self) -> &Source {
94        &self.source
95    }
96
97    /// Returns the mutable wall-clock source.
98    pub fn source_mut(&mut self) -> &mut Source {
99        &mut self.source
100    }
101}
102
103impl<Source> HostProfile for GleamTimeProfile<Source>
104where
105    Source: TimeSource,
106{
107    type RunState = GleamTimeRunState<Source>;
108    type ExternalStores = GleamTimeProfileStores;
109}
110
111impl<Source> HostComponentProfile<GleamStdlibComponent> for GleamTimeProfile<Source>
112where
113    Source: TimeSource,
114{
115    fn component_stores(stores: &Self::ExternalStores) -> &GleamStdlibStores {
116        &stores.stdlib
117    }
118
119    fn component_state(state: &mut Self::RunState) -> &mut GleamStdlibRunState {
120        &mut state.stdlib
121    }
122}
123
124impl<Source> HostComponentProfile<Component<Source>> for GleamTimeProfile<Source>
125where
126    Source: TimeSource,
127{
128    fn component_stores(stores: &Self::ExternalStores) -> &() {
129        &stores.time
130    }
131
132    fn component_state(state: &mut Self::RunState) -> &mut Source {
133        &mut state.source
134    }
135}
136
137impl<Source> GleamStdlibHostProfile for GleamTimeProfile<Source>
138where
139    Source: TimeSource,
140{
141    type Io = Vec<IoOutput>;
142}
143
144impl<Source> GleamTimeHostProfile for GleamTimeProfile<Source>
145where
146    Source: TimeSource,
147{
148    type Source = Source;
149}
150
151/// Registers the Rust providers for the official Gleam Time package.
152pub fn host_providers<Profile>() -> Result<Vec<HostProviderModule<Profile>>, HostRegistrationError>
153where
154    Profile: GleamTimeHostProfile,
155{
156    <Component<Profile::Source> as HostProviderComponentRegistration<Profile>>::providers()
157}
158
159impl<Profile, Source> HostProviderComponentRegistration<Profile> for Component<Source>
160where
161    Profile: GleamTimeHostProfile<Source = Source>,
162    Source: TimeSource,
163{
164    fn providers() -> Result<Vec<HostProviderModule<Profile>>, HostRegistrationError> {
165        [
166            calendar::host_provider::<Profile>,
167            timestamp::host_provider::<Profile>,
168        ]
169        .into_iter()
170        .map(|register| register())
171        .collect()
172    }
173}
174
175#[cfg(test)]
176mod effects;
177#[cfg(test)]
178mod test_support;
179
180#[cfg(test)]
181mod tests {
182    use super::test_support::ScriptedSource;
183    use super::{
184        Component, GleamTimeProfile, GleamTimeProfileStores, GleamTimeRunState, host_providers,
185    };
186    use crate::{
187        HostComponentProfile, HostProfile, HostProviderComponent, HostProviderComponentRegistration,
188    };
189    use geam_stdlib::{
190        Component as GleamStdlibComponent, GleamStdlibHostProfile, GleamStdlibRunState,
191        GleamStdlibStores, IoOutput,
192    };
193
194    struct CustomProfile;
195
196    #[derive(Default)]
197    struct CustomStores {
198        stdlib: GleamStdlibStores,
199        time: (),
200    }
201
202    struct CustomRunState {
203        stdlib: GleamStdlibRunState,
204        source: ScriptedSource,
205    }
206
207    impl HostProfile for CustomProfile {
208        type RunState = CustomRunState;
209        type ExternalStores = CustomStores;
210    }
211
212    impl HostComponentProfile<GleamStdlibComponent> for CustomProfile {
213        fn component_stores(stores: &Self::ExternalStores) -> &GleamStdlibStores {
214            &stores.stdlib
215        }
216
217        fn component_state(state: &mut Self::RunState) -> &mut GleamStdlibRunState {
218            &mut state.stdlib
219        }
220    }
221
222    impl GleamStdlibHostProfile for CustomProfile {
223        type Io = Vec<IoOutput>;
224    }
225
226    impl HostComponentProfile<Component<ScriptedSource>> for CustomProfile {
227        fn component_stores(stores: &Self::ExternalStores) -> &() {
228            &stores.time
229        }
230
231        fn component_state(state: &mut Self::RunState) -> &mut ScriptedSource {
232            &mut state.source
233        }
234    }
235
236    impl super::GleamTimeHostProfile for CustomProfile {
237        type Source = ScriptedSource;
238    }
239
240    #[test]
241    fn default_and_custom_profiles_project_owned_state_stores_source_and_io() {
242        let default_stores = GleamTimeProfileStores::default();
243        let custom_stores = CustomStores::default();
244        let mut default_state = GleamTimeRunState::new(
245            GleamStdlibRunState::from_seed([1; 32]),
246            ScriptedSource::default(),
247        );
248        let mut custom_state = CustomRunState {
249            stdlib: GleamStdlibRunState::from_seed([2; 32]),
250            source: ScriptedSource::default(),
251        };
252
253        assert!(std::ptr::eq(
254            <GleamTimeProfile<ScriptedSource> as HostComponentProfile<
255                GleamStdlibComponent,
256            >>::component_stores(&default_stores),
257            &default_stores.stdlib,
258        ));
259        assert!(std::ptr::eq(
260            <CustomProfile as HostComponentProfile<GleamStdlibComponent>>::component_stores(
261                &custom_stores,
262            ),
263            &custom_stores.stdlib,
264        ));
265        let default_stdlib = default_state.stdlib() as *const GleamStdlibRunState;
266        assert!(std::ptr::eq(
267            <GleamTimeProfile<ScriptedSource> as HostComponentProfile<
268                GleamStdlibComponent,
269            >>::component_state(&mut default_state),
270            default_stdlib,
271        ));
272        let custom_stdlib = &custom_state.stdlib as *const GleamStdlibRunState;
273        assert!(std::ptr::eq(
274            <CustomProfile as HostComponentProfile<GleamStdlibComponent>>::component_state(
275                &mut custom_state,
276            ),
277            custom_stdlib,
278        ));
279
280        let default_source = default_state.source() as *const ScriptedSource;
281        assert!(
282            std::ptr::eq(
283                <GleamTimeProfile<ScriptedSource> as HostComponentProfile<
284                    Component<ScriptedSource>,
285                >>::component_state(&mut default_state),
286                default_source,
287            )
288        );
289        let custom_source = &custom_state.source as *const ScriptedSource;
290        assert!(std::ptr::eq(
291            <CustomProfile as HostComponentProfile<Component<ScriptedSource>>>::component_state(
292                &mut custom_state,
293            ),
294            custom_source,
295        ));
296        assert!(
297            std::ptr::eq(
298                <GleamTimeProfile<ScriptedSource> as HostComponentProfile<
299                    Component<ScriptedSource>,
300                >>::component_stores(&default_stores),
301                &default_stores.time,
302            )
303        );
304        assert!(std::ptr::eq(
305            <CustomProfile as HostComponentProfile<Component<ScriptedSource>>>::component_stores(
306                &custom_stores,
307            ),
308            &custom_stores.time,
309        ));
310        assert!(default_state.stdlib_mut().take_io_outputs().is_empty());
311        assert!(default_state.stdlib().io_outputs().is_empty());
312        default_state.source_mut().offsets.push_back(Ok(3600));
313
314        assert!(default_state.stdlib().io_outputs().is_empty());
315        assert_eq!(default_state.source().offsets.len(), 1);
316    }
317
318    #[test]
319    fn registers_calendar_then_timestamp_without_external_types() {
320        assert_eq!(<Component as HostProviderComponent>::ID, "gleam_time");
321        let providers =
322            <Component as HostProviderComponentRegistration<GleamTimeProfile>>::providers()
323                .expect("Time component should register");
324        let facade =
325            host_providers::<GleamTimeProfile>().expect("official Time providers should register");
326        assert_eq!(
327            facade
328                .iter()
329                .map(|provider| provider.module().as_str())
330                .collect::<Vec<_>>(),
331            providers
332                .iter()
333                .map(|provider| provider.module().as_str())
334                .collect::<Vec<_>>(),
335        );
336
337        assert_eq!(
338            providers
339                .iter()
340                .map(|provider| (provider.package().as_str(), provider.module().as_str()))
341                .collect::<Vec<_>>(),
342            [
343                ("gleam_time", "gleam/time/calendar"),
344                ("gleam_time", "gleam/time/timestamp"),
345            ],
346        );
347        assert!(
348            providers
349                .iter()
350                .all(|provider| provider.external_types().count() == 0),
351        );
352    }
353}