use alloc::borrow::Cow;
use alloc::string::String;
use alloc::sync::Arc;
use core::num::NonZero;
use mf2_runtime::{CustomValue, DateTime};
use crate::arg::{ArgValue, DateTimeValue, Text};
#[diagnostic::on_unimplemented(
message = "`{Self}` is not a message argument",
label = "neither a number, a string, a date, a path nor a type with `Display`",
note = "a `tr!` argument is an integer, a float, a `bool`, a `char`, text (`&str`, `String`, \
`Cow<'static, str>`, `Arc<str>`), a date (`DateTimeValue`, `SystemTime`, jiff's \
types with `native`), a path, a signal of one of these, or any type with `Display`, \
as its text",
note = "for a type of your own, implement `Display` to pass its text, or `mf2::IntoArg` to \
pass it as a number, a date or a value a custom function reads"
)]
pub trait IntoArg {
fn into_arg(self) -> ArgValue;
}
impl IntoArg for ArgValue {
#[inline]
fn into_arg(self) -> ArgValue {
self
}
}
#[diagnostic::do_not_recommend]
impl<T: IntoArg + Copy> IntoArg for &T {
#[inline]
fn into_arg(self) -> ArgValue {
(*self).into_arg()
}
}
macro_rules! as_from {
($($t:ty),* $(,)?) => {$(
impl IntoArg for $t {
#[inline]
fn into_arg(self) -> ArgValue {
ArgValue::from(self)
}
}
)*};
}
as_from!(
i8,
i16,
i32,
i64,
u8,
u16,
u32,
f32,
f64,
char,
&str,
String,
Arc<str>,
Text,
DateTimeValue,
);
impl IntoArg for DateTime<'_> {
#[inline]
fn into_arg(self) -> ArgValue {
ArgValue::from(self)
}
}
impl<C: CustomValue + Send + Sync + 'static> IntoArg for Arc<C> {
#[inline]
fn into_arg(self) -> ArgValue {
ArgValue::from(self)
}
}
impl IntoArg for u64 {
fn into_arg(self) -> ArgValue {
match i64::try_from(self) {
Ok(n) => ArgValue::Int(n),
Err(_) => exact(false, u128::from(self)),
}
}
}
impl IntoArg for i128 {
fn into_arg(self) -> ArgValue {
match i64::try_from(self) {
Ok(n) => ArgValue::Int(n),
Err(_) => exact(self < 0, self.unsigned_abs()),
}
}
}
impl IntoArg for u128 {
fn into_arg(self) -> ArgValue {
match i64::try_from(self) {
Ok(n) => ArgValue::Int(n),
Err(_) => exact(false, self),
}
}
}
impl IntoArg for usize {
fn into_arg(self) -> ArgValue {
match i64::try_from(self) {
Ok(n) => ArgValue::Int(n),
Err(_) => exact(false, u128::try_from(self).unwrap_or(u128::MAX)),
}
}
}
impl IntoArg for isize {
fn into_arg(self) -> ArgValue {
match i64::try_from(self) {
Ok(n) => ArgValue::Int(n),
Err(_) => exact(
self < 0,
u128::try_from(self.unsigned_abs()).unwrap_or(u128::MAX),
),
}
}
}
macro_rules! non_zero {
($($t:ty),* $(,)?) => {$(
impl IntoArg for NonZero<$t> {
#[inline]
fn into_arg(self) -> ArgValue {
self.get().into_arg()
}
}
)*};
}
non_zero!(
i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize
);
impl IntoArg for bool {
#[inline]
fn into_arg(self) -> ArgValue {
ArgValue::str_static(if self { "true" } else { "false" })
}
}
impl IntoArg for Cow<'static, str> {
#[inline]
fn into_arg(self) -> ArgValue {
match self {
Cow::Borrowed(s) => ArgValue::str_static(s),
Cow::Owned(s) => ArgValue::from(s),
}
}
}
#[inline(never)]
fn exact(negative: bool, mut n: u128) -> ArgValue {
let mut buf = [b'0'; 40];
let mut len = 0;
for slot in buf.iter_mut().rev() {
*slot = b'0' | u8::try_from(n % 10).unwrap_or(0);
n /= 10;
len += 1;
if n == 0 {
break;
}
}
if negative
&& let Some(slot) = buf
.len()
.checked_sub(len + 1)
.and_then(|at| buf.get_mut(at))
{
*slot = b'-';
len += 1;
}
let digits = buf
.get(buf.len().saturating_sub(len)..)
.and_then(|d| core::str::from_utf8(d).ok())
.unwrap_or("0");
ArgValue::Decimal(Text::Shared(Arc::from(digits)))
}
#[inline]
pub(crate) fn display<T: core::fmt::Display + ?Sized>(value: &T) -> ArgValue {
use alloc::string::ToString;
ArgValue::from(value.to_string())
}
#[cfg(any(
feature = "host-std",
feature = "native",
all(
any(feature = "ssr", feature = "hydrate", feature = "csr"),
any(feature = "leptos", feature = "leptos-0-8")
)
))]
mod with_std {
use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
use super::{ArgValue, Cow, DateTimeValue, IntoArg};
fn lossy(s: &OsStr) -> ArgValue {
match s.to_string_lossy() {
Cow::Borrowed(s) => ArgValue::from(s),
Cow::Owned(s) => ArgValue::from(s),
}
}
fn owned(s: OsString) -> ArgValue {
match s.into_string() {
Ok(s) => ArgValue::from(s),
Err(s) => lossy(&s),
}
}
impl IntoArg for &Path {
fn into_arg(self) -> ArgValue {
lossy(self.as_os_str())
}
}
impl IntoArg for PathBuf {
fn into_arg(self) -> ArgValue {
owned(self.into_os_string())
}
}
impl IntoArg for &OsStr {
fn into_arg(self) -> ArgValue {
lossy(self)
}
}
impl IntoArg for OsString {
fn into_arg(self) -> ArgValue {
owned(self)
}
}
impl From<&PathBuf> for ArgValue {
fn from(p: &PathBuf) -> ArgValue {
lossy(p.as_os_str())
}
}
impl From<&OsString> for ArgValue {
fn from(s: &OsString) -> ArgValue {
lossy(s)
}
}
impl IntoArg for SystemTime {
fn into_arg(self) -> ArgValue {
let ms = match self.duration_since(UNIX_EPOCH) {
Ok(after) => i64::try_from(after.as_millis()).ok(),
Err(before) => {
let d = before.duration();
let ms = d.as_millis() + u128::from(d.subsec_nanos() % 1_000_000 != 0);
i64::try_from(ms).ok().map(|ms| -ms)
}
};
ms.and_then(DateTimeValue::instant)
.map_or(ArgValue::Unset, ArgValue::from)
}
}
}
#[cfg(feature = "native")]
mod with_jiff {
use alloc::string::String;
use jiff::Zoned;
use jiff::civil;
use mf2_runtime::{Date, DateTime, Time};
use super::{ArgValue, DateTimeValue, IntoArg};
fn date(d: civil::Date) -> Option<Date> {
Date::new(
i32::from(d.year()),
u8::try_from(d.month()).ok()?,
u8::try_from(d.day()).ok()?,
)
}
fn time(t: civil::Time) -> Option<Time> {
Time::new(
u8::try_from(t.hour()).ok()?,
u8::try_from(t.minute()).ok()?,
u8::try_from(t.second()).ok()?,
u16::try_from(t.millisecond()).ok()?,
)
}
fn floating(dt: civil::DateTime) -> Option<DateTime<'static>> {
Some(DateTime::floating(date(dt.date())?, time(dt.time())?))
}
impl IntoArg for jiff::Timestamp {
fn into_arg(self) -> ArgValue {
i64::try_from(self.as_nanosecond().div_euclid(1_000_000))
.ok()
.and_then(DateTimeValue::instant)
.map_or(ArgValue::Unset, ArgValue::from)
}
}
impl IntoArg for Zoned {
fn into_arg(self) -> ArgValue {
zoned(&self)
}
}
impl From<&Zoned> for ArgValue {
fn from(z: &Zoned) -> ArgValue {
zoned(z)
}
}
fn zoned(z: &Zoned) -> ArgValue {
let at = floating(z.datetime()).and_then(|dt| dt.with_offset(z.offset().seconds()));
let Some(at) = at else {
return z.timestamp().into_arg();
};
let value = DateTimeValue::from(at);
match z.time_zone().iana_name() {
Some(name) => ArgValue::from(value.with_zone(String::from(name))),
None => ArgValue::from(value),
}
}
impl IntoArg for civil::DateTime {
fn into_arg(self) -> ArgValue {
floating(self).map_or(ArgValue::Unset, ArgValue::from)
}
}
impl IntoArg for civil::Date {
fn into_arg(self) -> ArgValue {
date(self).map_or(ArgValue::Unset, |d| {
ArgValue::from(DateTime::floating(d, Time::MIDNIGHT))
})
}
}
}