citum_engine/render/
plain.rs1use super::format::{OutputFormat, QuoteMarks, realize_wrap};
9use crate::values::ScriptClass;
10use citum_schema::template::WrapPunctuation;
11
12#[derive(Default, Clone)]
13pub struct PlainText;
15
16impl OutputFormat for PlainText {
17 type Output = String;
18
19 fn text(&self, s: &str) -> Self::Output {
20 s.to_string()
21 }
22
23 fn join(&self, items: Vec<Self::Output>, delimiter: &str) -> Self::Output {
24 items.join(delimiter)
25 }
26
27 fn finish(&self, output: Self::Output) -> String {
28 output
29 }
30
31 fn heading(&self, level: u8, content: Self::Output) -> Self::Output {
32 let marks = "#".repeat(level.max(1) as usize);
33 format!("{marks} {content}\n\n")
34 }
35
36 fn emph(&self, content: Self::Output) -> Self::Output {
37 if content.is_empty() {
38 return content;
39 }
40 format!("_{content}_")
41 }
42
43 fn strong(&self, content: Self::Output) -> Self::Output {
44 if content.is_empty() {
45 return content;
46 }
47 format!("**{content}**")
48 }
49
50 fn small_caps(&self, content: Self::Output) -> Self::Output {
51 content.to_uppercase()
52 }
53
54 fn superscript(&self, content: Self::Output) -> Self::Output {
55 if content.is_empty() {
56 return content;
57 }
58 format!("^{content}^")
59 }
60
61 fn quote(&self, content: Self::Output, marks: &QuoteMarks) -> Self::Output {
62 if content.is_empty() {
63 return content;
64 }
65 let (open, close) = marks.for_depth(0);
66 format!("{open}{content}{close}")
67 }
68
69 fn affix(&self, prefix: &str, content: Self::Output, suffix: &str) -> Self::Output {
70 format!("{prefix}{content}{suffix}")
71 }
72
73 fn inner_affix(&self, prefix: &str, content: Self::Output, suffix: &str) -> Self::Output {
74 format!("{prefix}{content}{suffix}")
75 }
76
77 fn wrap_punctuation(
78 &self,
79 wrap: &WrapPunctuation,
80 content: Self::Output,
81 marks: &QuoteMarks,
82 script: ScriptClass,
83 realization: Option<&citum_schema::options::PunctuationRealization>,
84 ) -> Self::Output {
85 match realize_wrap(wrap, script, realization) {
86 Some((open, close)) => {
87 format!("{}{}{}", self.text(&open), content, self.text(&close))
88 }
89 None => self.quote(content, marks),
90 }
91 }
92
93 fn semantic(&self, _class: &str, content: Self::Output) -> Self::Output {
94 content
96 }
97
98 fn annotation(&self, content: Self::Output) -> Self::Output {
99 if content.is_empty() {
100 return content;
101 }
102
103 format!("\n\n{content}")
104 }
105
106 fn link(&self, _url: &str, content: Self::Output) -> Self::Output {
107 content
109 }
110
111 fn entry(
112 &self,
113 _id: &str,
114 content: Self::Output,
115 _url: Option<&str>,
116 _metadata: &super::format::ProcEntryMetadata,
117 ) -> Self::Output {
118 content
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn small_caps_preserves_empty_text() {
128 let fmt = PlainText;
129
130 assert_eq!(fmt.small_caps(String::new()), "");
131 }
132
133 #[test]
134 fn small_caps_uppercases_plain_text() {
135 let fmt = PlainText;
136
137 assert_eq!(
138 fmt.small_caps("Smith and Lumière".to_string()),
139 "SMITH AND LUMIÈRE"
140 );
141 }
142}