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 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(&self, depth: usize) -> (&'static str, &'static str) {
unicode_quote_marks(depth)
}
fn quote_with_depth(&self, content: Self::Output, depth: usize) -> Self::Output {
let (open, close) = self.quote_marks(depth);
self.affix(open, content, close)
}
fn quote(&self, content: Self::Output) -> Self::Output {
self.quote_with_depth(content, 0)
}
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) -> 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 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) -> 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"
);
}
}