ck3_history_extractor/
jinja_env.rs1use std::{fs, path::Path};
2
3use minijinja::{context, Environment, State, UndefinedBehavior, Value};
4use serde::{ser::SerializeStruct, Serialize};
5
6use super::{
7 display::ProceduralPath,
8 game_data::{GameData, Localize},
9 parser::GameRef,
10 structures::{FromGameObject, GameObjectDerived},
11 types::Wrapper,
12};
13
14#[cfg(feature = "internal")]
15mod internal_templates {
16 pub const INT_H_TEMPLATE: &str = include_str!("../templates/homeTemplate.html");
17 pub const INT_C_TEMPLATE: &str = include_str!("../templates/charTemplate.html");
18 pub const INT_CUL_TEMPLATE: &str = include_str!("../templates/cultureTemplate.html");
19 pub const INT_DYN_TEMPLATE: &str = include_str!("../templates/dynastyTemplate.html");
20 pub const INT_HOUSE_TEMPLATE: &str = include_str!("../templates/houseTemplate.html");
21 pub const INT_FAITH_TEMPLATE: &str = include_str!("../templates/faithTemplate.html");
22 pub const INT_TITLE_TEMPLATE: &str = include_str!("../templates/titleTemplate.html");
23 pub const INT_TIMELINE_TEMPLATE: &str = include_str!("../templates/timelineTemplate.html");
24 pub const INT_BASE_TEMPLATE: &str = include_str!("../templates/base.html");
25 pub const INT_REF_TEMPLATE: &str = include_str!("../templates/refTemplate.html");
26}
27
28pub const H_TEMPLATE_NAME: &str = "homeTemplate";
29pub const C_TEMPLATE_NAME: &str = "charTemplate";
30pub const CUL_TEMPLATE_NAME: &str = "cultureTemplate";
31pub const DYN_TEMPLATE_NAME: &str = "dynastyTemplate";
32pub const HOUSE_TEMPLATE_NAME: &str = "houseTemplate";
33pub const FAITH_TEMPLATE_NAME: &str = "faithTemplate";
34pub const TITLE_TEMPLATE_NAME: &str = "titleTemplate";
35pub const TIMELINE_TEMPLATE_NAME: &str = "timelineTemplate";
36pub const BASE_TEMPLATE_NAME: &str = "base";
37pub const REF_TEMPLATE_NAME: &str = "refTemplate";
38
39const TEMPLATE_NAMES: [&str; 10] = [
40 H_TEMPLATE_NAME,
41 C_TEMPLATE_NAME,
42 CUL_TEMPLATE_NAME,
43 DYN_TEMPLATE_NAME,
44 HOUSE_TEMPLATE_NAME,
45 FAITH_TEMPLATE_NAME,
46 TITLE_TEMPLATE_NAME,
47 TIMELINE_TEMPLATE_NAME,
48 BASE_TEMPLATE_NAME,
49 REF_TEMPLATE_NAME,
50];
51
52const LOCALIZATION_GLOBAL: &str = "localization";
53const LOCALIZATION_FUNC_NAME: &str = "localize";
54
55const DERIVED_REF_NAME_ATTR: &str = "name";
56const DERIVED_REF_SUBDIR_ATTR: &str = "subdir";
57const DERIVED_REF_ID_ATTR: &str = "id";
58
59impl<T: GameObjectDerived + FromGameObject + ProceduralPath> Serialize for GameRef<T> {
60 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
61 where
62 S: serde::Serializer,
63 {
64 let internal = self.get_internal();
65 if let Some(inner) = internal.inner() {
66 let mut state = serializer.serialize_struct("DerivedRef", 3)?;
67 state.serialize_field(DERIVED_REF_ID_ATTR, &internal.get_id())?;
68 state.serialize_field(DERIVED_REF_NAME_ATTR, &inner.get_name())?;
69 state.serialize_field(DERIVED_REF_SUBDIR_ATTR, T::get_subdir())?;
70 state.end()
71 } else {
72 serializer.serialize_none()
73 }
74 }
75}
76
77impl Localize<String> for Value {
84 fn lookup<K: AsRef<str>>(&self, key: K) -> Option<String> {
85 self.get_attr(key.as_ref())
86 .ok()
87 .map(|x| x.as_str().and_then(|x| Some(x.to_string())))
88 .flatten()
89 }
90
91 fn is_empty(&self) -> bool {
92 self.is_none()
93 }
94}
95
96pub fn create_env<'a>(
119 internal: bool,
120 map_present: bool,
121 no_vis: bool,
122 data: &GameData,
123) -> Environment<'a> {
124 let mut env = Environment::new();
125 env.set_lstrip_blocks(true);
126 env.set_trim_blocks(true);
127 env.add_filter("render_ref", render_ref);
128 env.add_filter(LOCALIZATION_FUNC_NAME, localize);
129 env.add_function(LOCALIZATION_FUNC_NAME, localize);
130 env.add_global("map_present", map_present);
131 env.add_global("no_vis", no_vis);
132 env.add_global(
133 LOCALIZATION_GLOBAL,
134 Value::from_serialize(data.get_localizer()),
135 );
136 env.set_undefined_behavior(UndefinedBehavior::Strict);
137 let template_path = Path::new("./templates");
138 if internal || !template_path.exists() {
139 #[cfg(feature = "internal")]
140 {
141 use internal_templates::*;
142 env.add_template(H_TEMPLATE_NAME, INT_H_TEMPLATE).unwrap();
143 env.add_template(C_TEMPLATE_NAME, INT_C_TEMPLATE).unwrap();
144 env.add_template(CUL_TEMPLATE_NAME, INT_CUL_TEMPLATE)
145 .unwrap();
146 env.add_template(DYN_TEMPLATE_NAME, INT_DYN_TEMPLATE)
147 .unwrap();
148 env.add_template(HOUSE_TEMPLATE_NAME, INT_HOUSE_TEMPLATE)
149 .unwrap();
150 env.add_template(FAITH_TEMPLATE_NAME, INT_FAITH_TEMPLATE)
151 .unwrap();
152 env.add_template(TITLE_TEMPLATE_NAME, INT_TITLE_TEMPLATE)
153 .unwrap();
154 env.add_template(TIMELINE_TEMPLATE_NAME, INT_TIMELINE_TEMPLATE)
155 .unwrap();
156 env.add_template(BASE_TEMPLATE_NAME, INT_BASE_TEMPLATE)
157 .unwrap();
158 env.add_template(REF_TEMPLATE_NAME, INT_REF_TEMPLATE)
159 .unwrap();
160 }
161 #[cfg(not(feature = "internal"))]
162 {
163 panic!("Internal templates requested but not compiled in");
164 }
165 } else {
166 let template_dir = fs::read_dir(template_path).unwrap();
167 for read_result in template_dir {
168 match read_result {
169 Ok(entry) => {
170 let path = entry.path();
172 if !path.is_file() {
173 continue;
174 }
175 let name = TEMPLATE_NAMES
176 .iter()
177 .find(|&x| x == &path.file_stem().unwrap());
178 if let Some(name) = name {
179 env.add_template_owned(*name, fs::read_to_string(path).unwrap())
180 .unwrap();
181 }
182 }
183 Err(e) => eprintln!("Error reading template directory: {}", e),
184 }
185 }
186 }
187 env
188}
189
190fn render_ref(state: &State, reference: Value, root: Option<bool>) -> String {
196 if let Some(name) = reference
197 .get_attr(DERIVED_REF_NAME_ATTR)
198 .expect("Reference doesn't have attributes")
199 .as_str()
200 {
201 let subdir = reference.get_attr(DERIVED_REF_SUBDIR_ATTR).unwrap();
202 let id = reference.get_attr(DERIVED_REF_ID_ATTR).unwrap();
203 if state
204 .lookup("depth_map")
205 .unwrap()
206 .get_item(&subdir)
207 .unwrap()
208 .get_item(&id)
209 .ok()
210 .and_then(|i| i.as_i64())
211 .unwrap_or(0)
212 <= 0
213 {
214 name.to_string()
215 } else {
216 state
217 .env()
218 .get_template(REF_TEMPLATE_NAME)
219 .unwrap()
220 .render(context! {root=>root, ..reference})
221 .unwrap()
222 }
223 } else {
224 "".to_owned()
225 }
226}
227
228fn localize(state: &State, key: &str, value: Option<&str>, provider: Option<&str>) -> String {
229 let localizer = state.lookup(LOCALIZATION_GLOBAL).unwrap();
230 if let Some(value) = value {
231 if let Some(provider) = provider {
232 localizer.localize_provider(key, provider, value).unwrap()
233 } else {
234 localizer
235 .localize_query(key, |_| Some(value.to_string()))
236 .unwrap()
237 }
238 } else {
239 localizer.localize(key).unwrap()
240 }
241}