1mod read;
9#[cfg(feature = "parse")]
10mod write;
11
12#[cfg(feature = "parse")]
13use std::collections::HashMap;
14
15use self::read::read;
16#[cfg(feature = "parse")]
17use self::write::write;
18use crate::catalog::build_all;
19#[cfg(feature = "parse")]
20use crate::components::Components;
21#[cfg(feature = "parse")]
22use crate::parse::each_call;
23use crate::syntax::{Op, Written};
24use crate::{Template, TemplateError};
25
26const FORMAT: u16 = 2;
28const MAGIC: &[u8; 3] = b"sft";
29
30mod tag {
31 pub(super) const MARKUP: u8 = 0;
32 pub(super) const TEXT: u8 = 1;
33 pub(super) const URL: u8 = 2;
34 pub(super) const IDENTIFIED: u8 = 3;
35 pub(super) const COMPONENT: u8 = 4;
36 pub(super) const IF: u8 = 5;
37 pub(super) const EACH: u8 = 6;
38
39 pub(super) const PATH: u8 = 0;
40 pub(super) const QUOTED: u8 = 1;
41 pub(super) const INTEGER: u8 = 2;
42 pub(super) const AUTHORED: u8 = 3;
43 pub(super) const INTERPOLATED: u8 = 4;
44 pub(super) const TRANSLATE: u8 = 5;
45 pub(super) const FORMAT_CALL: u8 = 6;
46 pub(super) const LABEL: u8 = 7;
47 pub(super) const GATE: u8 = 8;
48
49 pub(super) const DATE_FORMAT: u8 = 2;
50}
51
52pub fn load_template(name: &str, compiled: &[u8]) -> Result<Template, TemplateError> {
54 let written = read(compiled).map_err(|message| TemplateError::unlocated(name, message))?;
55 build(name, written)
56}
57
58#[cfg(feature = "parse")]
59pub fn compile_template(name: &str, source: &str) -> Result<Vec<u8>, TemplateError> {
60 compile_template_with(name, source, &Components::default())
61}
62
63#[cfg(feature = "parse")]
65pub fn compile_template_with(
66 name: &str,
67 source: &str,
68 components: &Components,
69) -> Result<Vec<u8>, TemplateError> {
70 let written = parse_written(name, source, components)?;
71 let compiled = write(&written);
72 checked(name, written)?;
73 Ok(compiled)
74}
75
76#[cfg(all(feature = "parse", feature = "i18n"))]
79pub fn compile_template_with_texts(
80 name: &str,
81 source: &str,
82 components: &Components,
83 translations: &crate::Translations,
84) -> Result<Vec<u8>, TemplateError> {
85 let mut written = parse_written(name, source, components)?;
86 crate::translations::record(&mut written, translations.negotiate(None, None));
87 let compiled = write(&written);
88 checked(name, written)?;
89 Ok(compiled)
90}
91
92#[cfg(feature = "parse")]
93pub(crate) fn parse_template(
94 name: &str,
95 source: &str,
96 components: &Components,
97) -> Result<Template, TemplateError> {
98 checked(name, parse_written(name, source, components)?)
99}
100
101#[cfg(feature = "parse")]
105pub fn parse_template_with_recorded(
106 name: &str,
107 source: &str,
108 components: &Components,
109 compiled: &[u8],
110) -> Result<Template, TemplateError> {
111 let mut program = read(compiled).map_err(|message| TemplateError::unlocated(name, message))?;
112 let mut texts = HashMap::new();
113 each_call(&mut program, &mut |call| {
114 if let Some((key, recorded)) = call.recorded_mut()
115 && let Some(text) = recorded.take()
116 {
117 texts.insert(key.to_owned(), text);
118 }
119 });
120 let mut written = parse_written(name, source, components)?;
121 each_call(&mut written, &mut |call| {
122 if let Some((key, recorded)) = call.recorded_mut() {
123 *recorded = texts.get(key).cloned();
124 }
125 });
126 checked(name, written)
127}
128
129#[cfg(feature = "parse")]
130fn parse_written(
131 name: &str,
132 source: &str,
133 components: &Components,
134) -> Result<Vec<Op<Written>>, TemplateError> {
135 crate::parse::parse(source, components).map_err(|error| TemplateError::located(name, error))
136}
137
138#[cfg(feature = "parse")]
139fn checked(name: &str, written: Vec<Op<Written>>) -> Result<Template, TemplateError> {
140 let template = build(name, written)?;
141 #[cfg(feature = "i18n")]
142 crate::translations::key_uses(&template.ops)
143 .map_err(|error| TemplateError::located(name, error))?;
144 Ok(template)
145}
146
147pub(crate) fn build(name: &str, written: Vec<Op<Written>>) -> Result<Template, TemplateError> {
148 let ops = build_all(written).map_err(|error| TemplateError::located(name, error))?;
149 Ok(Template::new(name, ops))
150}
151
152#[cfg(all(test, feature = "parse", not(feature = "i18n")))]
153mod tests {
154 use serde_json::json;
155
156 use super::{load_template, parse_template_with_recorded, write};
157 use crate::Components;
158 use crate::language::{Argument, Call};
159 use crate::message::{Message, Recorded};
160 use crate::syntax::{Expression, Op, Path, Position, Root};
161
162 fn recorded_call(key: &str, recorded: Option<Recorded>) -> Op<crate::syntax::Written> {
163 let count = Argument {
164 name: "count".to_owned(),
165 value: Expression::Path(Path {
166 root: Root::Data,
167 fields: vec!["count".to_owned()],
168 }),
169 };
170 let position = Position { line: 1, column: 1 };
171 let call = Call::translate(key.to_owned(), vec![count], position, recorded);
172 Op::Text(Expression::Call(Box::new(call)))
173 }
174
175 #[test]
176 fn a_build_without_i18n_writes_the_recorded_text_with_its_arguments() {
177 let recorded = Recorded {
178 other: Message::parse("{count} <orders>").unwrap(),
179 one: Some(Message::parse("one order").unwrap()),
180 };
181 let compiled = write(&[recorded_call("orders.shown", Some(recorded))]);
182 let loaded = load_template("orders", &compiled).unwrap();
183
184 assert_eq!(
185 loaded.render(&json!({"count": 2})).unwrap(),
186 "2 <orders>"
187 );
188 assert_eq!(loaded.render(&json!({"count": 1})).unwrap(), "one order");
189 }
190
191 #[test]
192 fn a_call_compiled_without_its_text_writes_its_key() {
193 let compiled = write(&[recorded_call("orders.shown", None)]);
194 let loaded = load_template("orders", &compiled).unwrap();
195
196 assert_eq!(loaded.render(&json!({"count": 2})).unwrap(), "orders.shown");
197 }
198
199 #[test]
200 fn a_screen_parsed_again_without_i18n_writes_the_texts_its_compiled_program_recorded() {
201 let recorded = Recorded {
202 other: Message::parse("{count} orders").unwrap(),
203 one: None,
204 };
205 let compiled = write(&[recorded_call("orders.shown", Some(recorded))]);
206 let source = "<p>{t(\"orders.shown\", count = data.count)}, {t(\"orders.new\")}</p>";
207
208 let parsed =
209 parse_template_with_recorded("orders", source, &Components::default(), &compiled)
210 .unwrap();
211
212 assert_eq!(
213 parsed.render(&json!({"count": 2})).unwrap(),
214 "<p>2 orders, orders.new</p>"
215 );
216 }
217}