#![warn(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![no_std]
#![forbid(unsafe_code)]
#![deny(
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::panic
)]
mod intl;
mod localize;
mod measure;
pub use measure::{CURRENCY, CurrencyFunction, UNIT, UnitFunction};
use mf2_runtime::{
Dir, ErrorSink, FnContext, FormatError, Function, INTL_NUMBERS, Number, NumberOut, NumberSpec,
Options, Sink, SubPartSink, Value,
};
use localize::{Layout, Out};
#[derive(Clone, Copy)]
pub struct NumberFunction {
spec: NumberSpec,
layout: Layout,
}
impl NumberFunction {
const fn new(spec: NumberSpec, layout: Layout) -> Self {
NumberFunction { spec, layout }
}
}
pub static NUMBER: NumberFunction = NumberFunction::new(NumberSpec::NUMBER, Layout::Decimal);
pub static INTEGER: NumberFunction = NumberFunction::new(NumberSpec::INTEGER, Layout::Decimal);
pub static OFFSET: NumberFunction = NumberFunction::new(NumberSpec::OFFSET, Layout::Decimal);
pub static PERCENT: NumberFunction = NumberFunction::new(NumberSpec::PERCENT, Layout::Percent);
impl Function for NumberFunction {
fn resolve<'a>(
&self,
cx: &FnContext<'_>,
operand: Option<&Value<'a>>,
options: &Options<'_, 'a>,
errs: &mut dyn ErrorSink,
) -> Option<Value<'a>> {
Number::resolve(self.spec, cx, operand, options, errs).map(Value::Number)
}
fn formattable(&self, _cx: &FnContext<'_>, value: &Value<'_>) -> Result<(), FormatError> {
match value {
Value::Number(_) => Ok(()),
_ => Err(FormatError::MessageFunctionError),
}
}
fn format(&self, cx: &FnContext<'_>, value: &Value<'_>, out: &mut dyn Sink) {
if INTL_NUMBERS {
return intl::format(cx, value, self.layout, NumberOut::Text(out));
}
if let Value::Number(n) = value
&& let Some(d) = n.digits()
{
localize::write(
cx.catalog(),
&d,
n.grouping(),
self.layout,
&mut Out::Text(out),
);
}
}
fn format_parts(&self, cx: &FnContext<'_>, value: &Value<'_>, out: &mut dyn SubPartSink) {
if INTL_NUMBERS {
return intl::format(cx, value, self.layout, NumberOut::Parts(out));
}
if let Value::Number(n) = value
&& let Some(d) = n.digits()
{
localize::write(
cx.catalog(),
&d,
n.grouping(),
self.layout,
&mut Out::Parts(out),
);
}
}
fn part_kind(&self) -> &'static str {
"number"
}
fn dir(&self, _cx: &FnContext<'_>, _value: &Value<'_>) -> Dir {
Dir::Ltr
}
fn selectable(&self, value: &Value<'_>) -> bool {
matches!(value, Value::Number(n) if n.is_selectable())
}
fn matches(
&self,
cx: &FnContext<'_>,
value: &Value<'_>,
key: &str,
errs: &mut dyn ErrorSink,
) -> bool {
match value {
Value::Number(n) => n.matches(cx, key, errs),
_ => false,
}
}
fn better_than(&self, _cx: &FnContext<'_>, _value: &Value<'_>, key1: &str, key2: &str) -> bool {
Number::better_than(key1, key2)
}
}
impl core::fmt::Debug for NumberFunction {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("NumberFunction")
}
}
#[derive(Clone, Copy, Default, Debug)]
pub struct Unannotated;
pub static NUMBERS: Unannotated = Unannotated;
impl Function for Unannotated {
fn resolve<'a>(
&self,
_cx: &FnContext<'_>,
_operand: Option<&Value<'a>>,
_options: &Options<'_, 'a>,
errs: &mut dyn ErrorSink,
) -> Option<Value<'a>> {
errs.error(FormatError::MessageFunctionError);
None
}
fn format(&self, cx: &FnContext<'_>, value: &Value<'_>, out: &mut dyn Sink) {
if INTL_NUMBERS {
return intl::format_unannotated(cx, value, NumberOut::Text(out));
}
if let Some(n) = value.to_number(cx.host()) {
localize::write(
cx.catalog(),
&n.exact_digits(),
None,
Layout::Decimal,
&mut Out::Text(out),
);
}
}
fn format_parts(&self, cx: &FnContext<'_>, value: &Value<'_>, out: &mut dyn SubPartSink) {
if INTL_NUMBERS {
return intl::format_unannotated(cx, value, NumberOut::Parts(out));
}
if let Some(n) = value.to_number(cx.host()) {
localize::write(
cx.catalog(),
&n.exact_digits(),
None,
Layout::Decimal,
&mut Out::Parts(out),
);
}
}
fn part_kind(&self) -> &'static str {
"number"
}
fn dir(&self, _cx: &FnContext<'_>, _value: &Value<'_>) -> Dir {
Dir::Ltr
}
}