use std::borrow::Cow;
use topcoat::{
context::Cx,
view::{AttributeValueViewParts, NodeViewParts, PartsWriter},
};
use crate::naming::{capitalize, derived_id, pluralize, sentence_case, slug_case, type_short_name};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Text(pub(super) Repr);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(super) enum Repr {
Plain(Cow<'static, str>),
Integer(i128),
Message(Box<Message>),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(super) struct Message {
pub(super) id: Cow<'static, str>,
pub(super) args: Vec<(Cow<'static, str>, Text)>,
pub(super) fallback: Option<Cow<'static, str>>,
}
impl Text {
#[must_use]
pub fn message(id: impl Into<Cow<'static, str>>) -> Self {
Self(Repr::Message(Box::new(Message {
id: id.into(),
args: Vec::new(),
fallback: None,
})))
}
#[must_use]
pub fn arg(mut self, name: impl Into<Cow<'static, str>>, value: impl Into<Self>) -> Self {
let name = name.into();
match &mut self.0 {
Repr::Message(message) => message.args.push((name, value.into())),
_ => debug_assert!(
false,
"`Text::arg({name:?})` on plain text, which reads none"
),
}
self
}
pub fn localize(&self, cx: &Cx) -> String {
let (catalog, locale) = super::catalog_and_locale(cx);
catalog.format(locale, self)
}
pub(crate) fn derived(id: String, fallback: String) -> Self {
Self(Repr::Message(Box::new(Message {
id: id.into(),
args: Vec::new(),
fallback: Some(fallback.into()),
})))
}
pub(crate) fn key(&self) -> String {
match &self.0 {
Repr::Plain(text) => text.to_lowercase(),
Repr::Integer(n) => n.to_string(),
Repr::Message(message) => message.id.to_string(),
}
}
pub(crate) fn field(name: &str) -> Self {
Self::derived(derived_id("field", name), capitalize(name))
}
pub(crate) fn option(enum_name: &str, variant: &str) -> Self {
let id = format!("option-{}.{variant}", enum_name.replace('_', "-"));
Self::derived(id, capitalize(variant))
}
pub(crate) fn model<M: ?Sized>() -> Self {
let noun = sentence_case(type_short_name::<M>());
Self::derived(derived_id("resource", &slug_case(&noun)), noun)
}
pub(crate) fn plural(&self) -> Self {
match &self.0 {
Repr::Plain(text) => Self::from(pluralize(text)),
Repr::Integer(_) => self.clone(),
Repr::Message(message) => Self(Repr::Message(Box::new(Message {
id: format!("{}.plural", message.id).into(),
args: message.args.clone(),
fallback: message
.fallback
.as_deref()
.map(|fallback| pluralize(fallback).into()),
}))),
}
}
pub(crate) fn english(&self) -> String {
super::Catalog::english().format(&super::ENGLISH, self)
}
}
pub fn option_label(enum_name: &str, variant: &str) -> Text {
Text::option(enum_name, variant)
}
impl NodeViewParts for Text {
fn into_view_parts(self, cx: &Cx, parts: &mut PartsWriter<'_>) {
parts.push_string(self.localize(cx));
}
}
impl NodeViewParts for &Text {
fn into_view_parts(self, cx: &Cx, parts: &mut PartsWriter<'_>) {
parts.push_string(self.localize(cx));
}
}
impl AttributeValueViewParts for Text {
fn attribute_present(&self) -> bool {
true
}
fn into_view_parts(self, cx: &Cx, parts: &mut PartsWriter<'_>) {
parts.push_string(self.localize(cx));
}
}
impl AttributeValueViewParts for &Text {
fn attribute_present(&self) -> bool {
true
}
fn into_view_parts(self, cx: &Cx, parts: &mut PartsWriter<'_>) {
parts.push_string(self.localize(cx));
}
}
#[cfg(test)]
impl PartialEq<&str> for Text {
fn eq(&self, other: &&str) -> bool {
self.english() == *other
}
}
impl Default for Text {
fn default() -> Self {
Self(Repr::Plain(Cow::Borrowed("")))
}
}
impl From<&str> for Text {
fn from(text: &str) -> Self {
Self(Repr::Plain(Cow::Owned(text.to_owned())))
}
}
impl From<&Text> for Text {
fn from(text: &Text) -> Self {
text.clone()
}
}
impl From<&String> for Text {
fn from(text: &String) -> Self {
Self(Repr::Plain(Cow::Owned(text.clone())))
}
}
impl From<String> for Text {
fn from(text: String) -> Self {
Self(Repr::Plain(Cow::Owned(text)))
}
}
impl From<Cow<'static, str>> for Text {
fn from(text: Cow<'static, str>) -> Self {
Self(Repr::Plain(text))
}
}
macro_rules! from_integer {
($($int:ty)*) => {$(
impl From<$int> for Text {
fn from(n: $int) -> Self {
Self(Repr::Integer(i128::from(n)))
}
}
)*};
}
from_integer!(i8 i16 i32 i64 u8 u16 u32 u64);
impl From<usize> for Text {
fn from(n: usize) -> Self {
Self(Repr::Integer(i128::try_from(n).unwrap_or(i128::MAX)))
}
}
impl From<isize> for Text {
fn from(n: isize) -> Self {
Self(Repr::Integer(i128::try_from(n).unwrap_or(i128::MAX)))
}
}
#[macro_export]
macro_rules! text {
($id:expr $(, $name:ident = $value:expr)* $(,)?) => {
$crate::Text::message($id)$(.arg(stringify!($name), $value))*
};
}
#[macro_export]
macro_rules! t {
($cx:expr, $id:expr $(, $name:ident = $value:expr)* $(,)?) => {
$crate::text!($id $(, $name = $value)*).localize($cx)
};
}