This repository has no description
5.1 kB
188 lines
1use std::fmt;
2
3const SPACER: &str = "\u{200b}";
4
5pub trait Key: Copy + 'static {
6 const PLACEHOLDERS: &'static [(&'static str, Self)];
7}
8
9#[derive(Debug, Clone, Copy)]
10pub enum NoKeys {}
11
12impl Key for NoKeys {
13 const PLACEHOLDERS: &'static [(&'static str, Self)] = &[];
14}
15
16pub trait Shape {
17 type Repr<K>;
18}
19
20#[derive(Debug)]
21pub struct Lines;
22
23impl Shape for Lines {
24 type Repr<K> = Vec<Vec<Segment<K>>>;
25}
26
27#[derive(Debug)]
28pub struct Line;
29
30impl Shape for Line {
31 type Repr<K> = Vec<Segment<K>>;
32}
33
34#[derive(Debug, thiserror::Error, PartialEq, Eq)]
35pub enum TemplateError {
36 #[error("{field}: unknown placeholder {{{name}}}")]
37 UnknownPlaceholder { field: &'static str, name: String },
38 #[error("{field}: unclosed {{ in template")]
39 UnclosedBrace { field: &'static str },
40 #[error("{field}: stray }} in template")]
41 StrayBrace { field: &'static str },
42 #[error("{field}: template mustn't be empty")]
43 Empty { field: &'static str },
44 #[error("{field}: template must be a single line")]
45 Multiline { field: &'static str },
46}
47
48#[derive(Debug)]
49pub enum Segment<K> {
50 Literal(String),
51 Placeholder(K),
52}
53
54pub struct Template<K, S: Shape> {
55 repr: S::Repr<K>,
56}
57
58impl<K, S: Shape> fmt::Debug for Template<K, S>
59where
60 S::Repr<K>: fmt::Debug,
61{
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 f.debug_struct("Template")
64 .field("repr", &self.repr)
65 .finish()
66 }
67}
68
69impl<K: Key> Template<K, Lines> {
70 pub fn parse_lines(field: &'static str, texts: &[String]) -> Result<Self, TemplateError> {
71 Ok(Self {
72 repr: texts
73 .iter()
74 .map(|text| line_segments(field, text))
75 .collect::<Result<_, _>>()?,
76 })
77 }
78
79 pub fn lines(&self, resolve: impl Fn(K) -> String) -> Vec<String> {
80 self.repr
81 .iter()
82 .map(|segments| {
83 let rendered = render_segments(segments, &resolve);
84 match rendered.is_empty() {
85 true => SPACER.to_string(),
86 false => rendered,
87 }
88 })
89 .collect()
90 }
91}
92
93impl<K: Key> Template<K, Line> {
94 // A reject reason is a Line - `receive-pack` gives it as
95 // "ng <ref> <reason>\n" inside one `pkt-line`.
96 // `line_segments` won't accept newlines
97 // so the reason stays on that one line.
98 pub fn parse(field: &'static str, text: &str) -> Result<Self, TemplateError> {
99 match text.is_empty() {
100 true => Err(TemplateError::Empty { field }),
101 false => Ok(Self {
102 repr: line_segments(field, text)?,
103 }),
104 }
105 }
106
107 pub fn line(&self, resolve: impl Fn(K) -> String) -> String {
108 render_segments(&self.repr, &resolve)
109 }
110}
111
112impl Template<NoKeys, Lines> {
113 pub fn text_lines(&self) -> Vec<String> {
114 self.lines(|key| match key {})
115 }
116}
117
118impl Template<NoKeys, Line> {
119 pub fn text(&self) -> String {
120 self.line(|key| match key {})
121 }
122}
123
124fn line_segments<K: Key>(
125 field: &'static str,
126 text: &str,
127) -> Result<Vec<Segment<K>>, TemplateError> {
128 match text.contains('\n') {
129 true => Err(TemplateError::Multiline { field }),
130 false => segments(field, text),
131 }
132}
133
134fn render_segments<K: Key>(segments: &[Segment<K>], resolve: &dyn Fn(K) -> String) -> String {
135 segments
136 .iter()
137 .map(|segment| match segment {
138 Segment::Literal(text) => text.clone(),
139 Segment::Placeholder(key) => resolve(*key),
140 })
141 .collect()
142}
143
144fn segments<K: Key>(field: &'static str, text: &str) -> Result<Vec<Segment<K>>, TemplateError> {
145 let Some(at) = text.find(['{', '}']) else {
146 return Ok(literal(text));
147 };
148 let (before, rest) = text.split_at(at);
149 let (parsed, remainder) = brace(field, rest)?;
150 Ok(literal(before)
151 .into_iter()
152 .chain(parsed)
153 .chain(segments(field, remainder)?)
154 .collect())
155}
156
157fn literal<K>(text: &str) -> Vec<Segment<K>> {
158 match text.is_empty() {
159 true => Vec::new(),
160 false => vec![Segment::Literal(text.to_string())],
161 }
162}
163
164type Braced<'a, K> = (Option<Segment<K>>, &'a str);
165
166fn brace<'a, K: Key>(field: &'static str, rest: &'a str) -> Result<Braced<'a, K>, TemplateError> {
167 if let Some(after) = rest.strip_prefix("{{") {
168 return Ok((Some(Segment::Literal("{".to_string())), after));
169 }
170 if let Some(after) = rest.strip_prefix("}}") {
171 return Ok((Some(Segment::Literal("}".to_string())), after));
172 }
173 if rest.starts_with('}') {
174 return Err(TemplateError::StrayBrace { field });
175 }
176 let close = rest
177 .find('}')
178 .ok_or(TemplateError::UnclosedBrace { field })?;
179 let name = &rest[1..close];
180 K::PLACEHOLDERS
181 .iter()
182 .find(|(candidate, _)| *candidate == name)
183 .map(|(_, key)| (Some(Segment::Placeholder(*key)), &rest[close + 1..]))
184 .ok_or_else(|| TemplateError::UnknownPlaceholder {
185 field,
186 name: name.to_string(),
187 })
188}