citum_engine/render/
org.rs1use super::format::{OutputFormat, QuoteMarks};
9use citum_schema::template::WrapPunctuation;
10
11#[derive(Default, Clone)]
21pub struct OrgOutputFormat;
22
23impl OutputFormat for OrgOutputFormat {
24 type Output = String;
25
26 fn text(&self, s: &str) -> Self::Output {
27 s.to_string()
28 }
29
30 fn join(&self, items: Vec<Self::Output>, delimiter: &str) -> Self::Output {
31 items.join(delimiter)
32 }
33
34 fn finish(&self, output: Self::Output) -> String {
35 output
36 }
37
38 fn heading(&self, level: u8, content: Self::Output) -> Self::Output {
40 let marks = "*".repeat(level.max(1) as usize);
41 format!("{marks} {content}\n\n")
42 }
43
44 fn emph(&self, content: Self::Output) -> Self::Output {
46 if content.is_empty() {
47 return content;
48 }
49 format!("/{content}/")
50 }
51
52 fn strong(&self, content: Self::Output) -> Self::Output {
54 if content.is_empty() {
55 return content;
56 }
57 format!("*{content}*")
58 }
59
60 fn small_caps(&self, content: Self::Output) -> Self::Output {
62 if content.is_empty() {
63 return content;
64 }
65 format!("~{content}~")
66 }
67
68 fn superscript(&self, content: Self::Output) -> Self::Output {
69 if content.is_empty() {
70 return content;
71 }
72 format!("^{content}^")
73 }
74
75 fn quote(&self, content: Self::Output, marks: &QuoteMarks) -> Self::Output {
76 if content.is_empty() {
77 return content;
78 }
79 let (open, close) = marks.for_depth(0);
81 format!("{open}{content}{close}")
82 }
83
84 fn affix(&self, prefix: &str, content: Self::Output, suffix: &str) -> Self::Output {
85 format!("{prefix}{content}{suffix}")
86 }
87
88 fn inner_affix(&self, prefix: &str, content: Self::Output, suffix: &str) -> Self::Output {
89 format!("{prefix}{content}{suffix}")
90 }
91
92 fn wrap_punctuation(
93 &self,
94 wrap: &WrapPunctuation,
95 content: Self::Output,
96 marks: &QuoteMarks,
97 ) -> Self::Output {
98 match wrap {
99 WrapPunctuation::Parentheses => format!("({content})"),
100 WrapPunctuation::Brackets => format!("[{content}]"),
101 WrapPunctuation::Quotes => self.quote(content, marks),
102 }
103 }
104
105 fn semantic(&self, _class: &str, content: Self::Output) -> Self::Output {
106 content
108 }
109
110 fn annotation(&self, content: Self::Output) -> Self::Output {
111 if content.is_empty() {
112 return content;
113 }
114 format!(
115 "\n\n#+begin_citum_annotation\n{}\n#+end_citum_annotation",
116 content
117 )
118 }
119
120 fn link(&self, url: &str, content: Self::Output) -> Self::Output {
122 format!("[[{url}][{content}]]")
123 }
124
125 fn entry(
126 &self,
127 _id: &str,
128 content: Self::Output,
129 _url: Option<&str>,
130 _metadata: &super::format::ProcEntryMetadata,
131 ) -> Self::Output {
132 content
133 }
134}
135
136#[cfg(test)]
137#[allow(
138 clippy::unwrap_used,
139 clippy::expect_used,
140 clippy::panic,
141 clippy::indexing_slicing,
142 clippy::todo,
143 clippy::unimplemented,
144 clippy::unreachable,
145 clippy::get_unwrap,
146 reason = "Panicking is acceptable and often desired in tests."
147)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_org_emph() {
153 let fmt = OrgOutputFormat;
154 let result = fmt.emph(fmt.text("italic text"));
155 assert_eq!(result, "/italic text/");
156 }
157
158 #[test]
159 fn test_org_strong() {
160 let fmt = OrgOutputFormat;
161 let result = fmt.strong(fmt.text("bold text"));
162 assert_eq!(result, "*bold text*");
163 }
164
165 #[test]
166 fn test_org_small_caps() {
167 let fmt = OrgOutputFormat;
168 let result = fmt.small_caps(fmt.text("small caps"));
169 assert_eq!(result, "~small caps~");
170 }
171
172 #[test]
173 fn test_org_link() {
174 let fmt = OrgOutputFormat;
175 let result = fmt.link("https://example.com", fmt.text("Example"));
176 assert_eq!(result, "[[https://example.com][Example]]");
177 }
178
179 #[test]
180 fn test_org_empty_content() {
181 let fmt = OrgOutputFormat;
182 let result = fmt.emph(fmt.text(""));
183 assert_eq!(result, "");
184 }
185}