use citum_schema::locale::GrammarOptions;
use citum_schema::template::WrapPunctuation;
#[must_use]
pub fn unicode_quote_marks(depth: usize) -> (&'static str, &'static str) {
if depth.is_multiple_of(2) {
("\u{201C}", "\u{201D}")
} else {
("\u{2018}", "\u{2019}")
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QuoteMarks {
pub open: String,
pub close: String,
pub open_inner: String,
pub close_inner: String,
}
impl QuoteMarks {
#[must_use]
pub fn for_depth(&self, depth: usize) -> (&str, &str) {
if depth.is_multiple_of(2) {
(&self.open, &self.close)
} else {
(&self.open_inner, &self.close_inner)
}
}
}
impl Default for QuoteMarks {
fn default() -> Self {
let (open, close) = unicode_quote_marks(0);
let (open_inner, close_inner) = unicode_quote_marks(1);
Self {
open: open.to_string(),
close: close.to_string(),
open_inner: open_inner.to_string(),
close_inner: close_inner.to_string(),
}
}
}
impl From<&GrammarOptions> for QuoteMarks {
fn from(options: &GrammarOptions) -> Self {
Self {
open: options.open_quote.clone(),
close: options.close_quote.clone(),
open_inner: options.open_inner_quote.clone(),
close_inner: options.close_inner_quote.clone(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SemanticAttribute {
pub name: &'static str,
pub value: String,
}
pub trait OutputFormat: Default + Clone {
type Output;
fn text(&self, s: &str) -> Self::Output;
fn join(&self, items: Vec<Self::Output>, delimiter: &str) -> Self::Output;
fn finish(&self, output: Self::Output) -> String;
fn emph(&self, content: Self::Output) -> Self::Output;
fn strong(&self, content: Self::Output) -> Self::Output;
fn small_caps(&self, content: Self::Output) -> Self::Output;
fn superscript(&self, content: Self::Output) -> Self::Output;
fn quote_marks<'a>(&self, depth: usize, marks: &'a QuoteMarks) -> (&'a str, &'a str) {
marks.for_depth(depth)
}
fn quote_with_depth(
&self,
content: Self::Output,
depth: usize,
marks: &QuoteMarks,
) -> Self::Output {
let (open, close) = self.quote_marks(depth, marks);
self.affix(open, content, close)
}
fn quote(&self, content: Self::Output, marks: &QuoteMarks) -> Self::Output {
self.quote_with_depth(content, 0, marks)
}
fn affix(&self, prefix: &str, content: Self::Output, suffix: &str) -> Self::Output;
fn inner_affix(&self, prefix: &str, content: Self::Output, suffix: &str) -> Self::Output;
fn wrap_punctuation(
&self,
wrap: &WrapPunctuation,
content: Self::Output,
marks: &QuoteMarks,
) -> Self::Output;
fn semantic(&self, class: &str, content: Self::Output) -> Self::Output;
fn annotation(&self, content: Self::Output) -> Self::Output;
fn paragraph(&self, content: Self::Output) -> Self::Output {
content
}
fn block_quote(&self, content: Self::Output) -> Self::Output {
content
}
fn bullet_list(&self, items: Vec<Self::Output>) -> Self::Output {
self.join(items, "\n")
}
fn ordered_list(&self, items: Vec<Self::Output>) -> Self::Output {
self.join(items, "\n")
}
fn list_item(&self, content: Self::Output) -> Self::Output {
content
}
fn heading(&self, _level: u8, content: Self::Output) -> Self::Output {
content
}
fn unnumbered_heading(&self, level: u8, content: Self::Output) -> Self::Output {
self.heading(level, content)
}
fn code_block(&self, _lang: Option<&str>, content: Self::Output) -> Self::Output {
content
}
fn inline_code(&self, content: Self::Output) -> Self::Output {
content
}
fn strikeout(&self, content: Self::Output) -> Self::Output {
content
}
fn hard_break(&self) -> Self::Output {
self.text(" ")
}
fn semantic_with_attributes(
&self,
class: &str,
content: Self::Output,
_attributes: &[SemanticAttribute],
) -> Self::Output {
self.semantic(class, content)
}
fn citation(&self, _ids: Vec<String>, content: Self::Output) -> Self::Output {
content
}
fn link(&self, url: &str, content: Self::Output) -> Self::Output;
fn format_id(&self, id: &str) -> String {
id.to_string()
}
fn bibliography(&self, entries: Vec<Self::Output>) -> Self::Output {
self.join(entries, "\n\n")
}
fn entry(
&self,
_id: &str,
content: Self::Output,
_url: Option<&str>,
_metadata: &ProcEntryMetadata,
) -> Self::Output {
content
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ProcEntryMetadata {
pub author: Option<String>,
pub year: Option<String>,
pub title: Option<String>,
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing,
clippy::todo,
clippy::unimplemented,
clippy::unreachable,
clippy::get_unwrap,
reason = "Panicking is acceptable and often desired in tests."
)]
mod tests {
use super::*;
#[derive(Default, Clone)]
struct DummyFormat;
impl OutputFormat for DummyFormat {
type Output = String;
fn text(&self, s: &str) -> Self::Output {
s.to_string()
}
fn join(&self, items: Vec<Self::Output>, delimiter: &str) -> Self::Output {
items.join(delimiter)
}
fn finish(&self, output: Self::Output) -> String {
output
}
fn emph(&self, content: Self::Output) -> Self::Output {
format!("emph({content})")
}
fn strong(&self, content: Self::Output) -> Self::Output {
format!("strong({content})")
}
fn small_caps(&self, content: Self::Output) -> Self::Output {
format!("sc({content})")
}
fn superscript(&self, content: Self::Output) -> Self::Output {
format!("sup({content})")
}
fn affix(&self, prefix: &str, content: Self::Output, suffix: &str) -> Self::Output {
format!("{prefix}{content}{suffix}")
}
fn inner_affix(&self, prefix: &str, content: Self::Output, suffix: &str) -> Self::Output {
format!("{prefix}{content}{suffix}")
}
fn wrap_punctuation(
&self,
_wrap: &WrapPunctuation,
content: Self::Output,
_marks: &QuoteMarks,
) -> Self::Output {
content
}
fn semantic(&self, class: &str, content: Self::Output) -> Self::Output {
format!("sem[{class}]({content})")
}
fn annotation(&self, content: Self::Output) -> Self::Output {
format!("annot({content})")
}
fn link(&self, url: &str, content: Self::Output) -> Self::Output {
format!("link[{url}]({content})")
}
}
#[test]
fn test_default_methods() {
let fmt = DummyFormat;
assert_eq!(
fmt.semantic_with_attributes("test", "content".to_string(), &[]),
"sem[test](content)"
);
assert_eq!(
fmt.citation(vec!["id1".to_string()], "content".to_string()),
"content"
);
assert_eq!(fmt.format_id("id1"), "id1");
assert_eq!(
fmt.bibliography(vec!["entry1".to_string(), "entry2".to_string()]),
"entry1\n\nentry2"
);
assert_eq!(
fmt.entry(
"id1",
"content".to_string(),
None,
&ProcEntryMetadata::default()
),
"content"
);
}
}