use mf2_catalog::{Catalog, StrRef};
use mf2_model::{Dir, MarkupKind};
use crate::function::{FnContext, Function, OptionEntries};
use crate::sink::{Sink, SubPartSink};
use crate::text::write_escaped_literal;
use crate::unannotated;
use crate::value::Value;
pub trait PartSink {
fn part(&mut self, part: Part<'_>);
#[doc(hidden)]
fn part_catalog_text(&mut self, catalog: &Catalog, r: StrRef) -> bool {
match catalog.text(r) {
Some(s) => {
if !s.is_empty() {
self.part(Part::Text(s));
}
true
}
None => false,
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Part<'p> {
Text(&'p str),
BidiIsolation(Isolation),
Expression(ExpressionPart<'p>),
Markup(MarkupPart<'p>),
Fallback(FallbackSource<'p>),
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Isolation {
Lri,
Rli,
Fsi,
Pdi,
}
impl Isolation {
pub const fn as_str(self) -> &'static str {
match self {
Isolation::Lri => "\u{2066}",
Isolation::Rli => "\u{2067}",
Isolation::Fsi => "\u{2068}",
Isolation::Pdi => "\u{2069}",
}
}
}
pub struct ExpressionPart<'p> {
pub(crate) value: &'p Value<'p>,
pub(crate) handler: Option<&'static dyn Function>,
pub(crate) cx: FnContext<'p>,
pub(crate) dir: Dir,
pub(crate) id: Option<&'p str>,
}
impl<'p> ExpressionPart<'p> {
pub fn kind(&self) -> &'p str {
match self.handler {
Some(h) => h.part_kind(),
None => unannotated::kind(self.value),
}
}
pub fn locale(&self) -> &'p str {
self.cx.locale()
}
pub fn dir(&self) -> Dir {
self.dir
}
pub fn id(&self) -> Option<&'p str> {
self.id
}
pub fn value(&self) -> &'p Value<'p> {
self.value
}
pub fn write(&self, out: &mut dyn Sink) {
match self.handler {
Some(h) => h.format(&self.cx, self.value, out),
None => unannotated::format(self.value, self.cx.host(), out),
}
}
pub fn sub_parts(&self, out: &mut dyn SubPartSink) {
match self.handler {
Some(h) => h.format_parts(&self.cx, self.value, out),
None => unannotated::format_parts(self.value, self.cx.host(), out),
}
}
}
impl core::fmt::Debug for ExpressionPart<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ExpressionPart")
.field("value", self.value)
.field("dir", &self.dir)
.field("id", &self.id)
.finish_non_exhaustive()
}
}
pub struct MarkupPart<'p> {
pub(crate) kind: MarkupKind,
pub(crate) name: &'p str,
pub(crate) id: Option<&'p str>,
pub(crate) options: OptionEntries<'p, 'p>,
}
impl<'p> MarkupPart<'p> {
pub fn kind(&self) -> MarkupKind {
self.kind
}
pub fn name(&self) -> &'p str {
self.name
}
pub fn id(&self) -> Option<&'p str> {
self.id
}
pub fn options(&self) -> MarkupOptions<'p> {
MarkupOptions {
entries: self.options,
at: 0,
}
}
}
impl core::fmt::Debug for MarkupPart<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("MarkupPart")
.field("kind", &self.kind)
.field("name", &self.name)
.field("id", &self.id)
.field("options", &self.options.len())
.finish()
}
}
pub struct MarkupOptions<'p> {
entries: OptionEntries<'p, 'p>,
at: usize,
}
impl core::fmt::Debug for MarkupOptions<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("MarkupOptions")
.field("left", &self.entries.len().saturating_sub(self.at))
.finish()
}
}
impl<'p> Iterator for MarkupOptions<'p> {
type Item = (&'p str, &'p Value<'p>);
fn next(&mut self) -> Option<Self::Item> {
let e = self.entries.get(self.at)?;
self.at += 1;
Some((e.0, e.1.value))
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum FallbackSource<'p> {
Variable(&'p str),
Literal(&'p str),
Function(&'p str),
Unknown,
}
impl FallbackSource<'_> {
pub fn write(&self, out: &mut dyn Sink) {
match *self {
FallbackSource::Variable(n) => {
out.push_str("$");
out.push_str(n);
}
FallbackSource::Literal(v) => {
out.push_str("|");
write_escaped_literal(v, out);
out.push_str("|");
}
FallbackSource::Function(n) => {
out.push_str(":");
out.push_str(n);
}
FallbackSource::Unknown => out.push_str("\u{FFFD}"),
}
}
}