Skip to main content

mf2_host_web/
lib.rs

1//! `mf2-host-web` — the [`Host`] of `mf2-runtime` in the browser
2//! (`wasm32-unknown-unknown`, its only target): NFC through `String.prototype.normalize`
3//! and the shortest round-trip text of a float through `Number.prototype
4//! .toString`, both via `js-sys`, so the wasm carries no normalization
5//! tables and no float-printing code.
6//!
7//! Dates and numbers (`intl`) come as
8//! more hosts, not as more methods of [`HOST`]: a host method is linked
9//! whenever its host is (it is in the `Host` vtable), so a date or number
10//! method on [`HOST`] would cost every client that has the feature on,
11//! dates or numbers or not. An application names the host its corpus
12//! needs (`mf2-build` picks it):
13//!
14//! | Static | Feature | Adds |
15//! |---|---|---|
16//! | [`HOST`] | — | NFC, float text |
17//! | [`ZONES_HOST`] | `time-zones` | `Host::zone_offset` from the browser's zone data (`datetime-icu` clients: named zones) |
18//! | [`INTL_HOST`] | `datetime-intl` | that, and `Host::format_date_time` through `Intl.DateTimeFormat` |
19//! | `NUMBERS_HOST`, `IntlNumbers(&host)` | `intl` | numbers through `Intl.NumberFormat` and `Intl.PluralRules` (`Host::numbers`) over another host |
20//!
21//! # The user guide
22//!
23//! The [Rust MF2 book](https://evancarroll.github.io/rust-mf2/) is the user
24//! guide: how the crates fit together, web and native applications, the
25//! command line, and what 2.x promises.
26//! An application reaches this crate through
27//! [`mf2`](https://docs.rs/mf2), as `mf2::host_web` (feature `host-web`).
28
29#![warn(missing_docs)]
30// docs.rs (`cargo xtask docs-rs`): each feature-gated item says which features it needs.
31#![cfg_attr(docsrs, feature(doc_cfg))]
32#![no_std]
33#![forbid(unsafe_code)]
34
35extern crate alloc;
36
37#[cfg(feature = "time-zones")]
38mod dates;
39#[cfg(feature = "intl")]
40mod numbers;
41
42use alloc::string::String;
43
44use js_sys::{JsString, Number};
45use mf2_runtime::Host;
46
47#[cfg(feature = "intl")]
48pub use numbers::{IntlNumbers, NUMBERS_HOST};
49
50/// The browser host.
51#[derive(Clone, Copy, Default, Debug)]
52pub struct WebHost;
53
54/// The browser host, for [`mf2_runtime::FormatContext::new`].
55pub static HOST: WebHost = WebHost;
56
57/// The browser host with time-zone offsets from the browser's zone data.
58#[cfg(feature = "time-zones")]
59#[derive(Clone, Copy, Default, Debug)]
60pub struct ZonesWebHost;
61
62/// [`ZonesWebHost`], for [`mf2_runtime::FormatContext::new`].
63#[cfg(feature = "time-zones")]
64pub static ZONES_HOST: ZonesWebHost = ZonesWebHost;
65
66/// The browser host with dates through `Intl.DateTimeFormat` and zone
67/// offsets from the browser's zone data (`datetime-intl`).
68#[cfg(feature = "datetime-intl")]
69#[derive(Clone, Copy, Default, Debug)]
70pub struct IntlWebHost;
71
72/// [`IntlWebHost`], for [`mf2_runtime::FormatContext::new`].
73#[cfg(feature = "datetime-intl")]
74pub static INTL_HOST: IntlWebHost = IntlWebHost;
75
76impl Host for WebHost {
77    fn nfc<'a>(&self, s: &'a str, buf: &'a mut String) -> &'a str {
78        let normalized = JsString::from(s).normalize("NFC");
79        buf.clear();
80        match normalized.as_string() {
81            Some(text) => {
82                *buf = text;
83                buf
84            }
85            None => s,
86        }
87    }
88
89    fn f64_to_text<'b>(&self, x: f64, buf: &'b mut [u8; 32]) -> Option<&'b str> {
90        if !x.is_finite() {
91            return None;
92        }
93        let text = Number::from(x).to_string_with_radix(10).ok()?.as_string()?;
94        let out = buf.get_mut(..text.len())?;
95        out.copy_from_slice(text.as_bytes());
96        core::str::from_utf8(out).ok()
97    }
98}
99
100#[cfg(feature = "time-zones")]
101impl Host for ZonesWebHost {
102    fn nfc<'a>(&self, s: &'a str, buf: &'a mut String) -> &'a str {
103        WebHost.nfc(s, buf)
104    }
105
106    fn f64_to_text<'b>(&self, x: f64, buf: &'b mut [u8; 32]) -> Option<&'b str> {
107        WebHost.f64_to_text(x, buf)
108    }
109
110    fn zone_offset(&self, zone: &str, epoch_ms: i64) -> Option<i32> {
111        dates::zone_offset(zone, epoch_ms)
112    }
113}
114
115#[cfg(feature = "datetime-intl")]
116impl Host for IntlWebHost {
117    fn nfc<'a>(&self, s: &'a str, buf: &'a mut String) -> &'a str {
118        WebHost.nfc(s, buf)
119    }
120
121    fn f64_to_text<'b>(&self, x: f64, buf: &'b mut [u8; 32]) -> Option<&'b str> {
122        WebHost.f64_to_text(x, buf)
123    }
124
125    fn zone_offset(&self, zone: &str, epoch_ms: i64) -> Option<i32> {
126        dates::zone_offset(zone, epoch_ms)
127    }
128
129    fn format_date_time(
130        &self,
131        locale: &str,
132        request: &mf2_runtime::DateTimeRequest<'_>,
133        out: &mut dyn mf2_runtime::Sink,
134    ) -> bool {
135        dates::format(locale, request, out)
136    }
137}