ck3_history_extractor/
jinja_env.rs1use std::{fs, path::Path, string::String};
2
3use minijinja::{context, Environment, State, UndefinedBehavior, Value};
4
5use ck3_history_extractor_lib::{
6 derived_ref::{DERIVED_REF_ID_ATTR, DERIVED_REF_NAME_ATTR, DERIVED_REF_SUBDIR_ATTR},
7 display::{Renderable, Timeline},
8 game_data::{GameData, Localize},
9 structures::{Character, Culture, Dynasty, Faith, GameObjectEntity, House, Player, Title},
10};
11
12#[cfg(feature = "internal")]
13mod internal_templates {
14 pub const INT_H_TEMPLATE: &str = include_str!("../templates/homeTemplate.html");
15 pub const INT_C_TEMPLATE: &str = include_str!("../templates/charTemplate.html");
16 pub const INT_CUL_TEMPLATE: &str = include_str!("../templates/cultureTemplate.html");
17 pub const INT_DYN_TEMPLATE: &str = include_str!("../templates/dynastyTemplate.html");
18 pub const INT_HOUSE_TEMPLATE: &str = include_str!("../templates/houseTemplate.html");
19 pub const INT_FAITH_TEMPLATE: &str = include_str!("../templates/faithTemplate.html");
20 pub const INT_TITLE_TEMPLATE: &str = include_str!("../templates/titleTemplate.html");
21 pub const INT_TIMELINE_TEMPLATE: &str = include_str!("../templates/timelineTemplate.html");
22 pub const INT_BASE_TEMPLATE: &str = include_str!("../templates/base.html");
23 pub const INT_REF_TEMPLATE: &str = include_str!("../templates/refTemplate.html");
24}
25
26pub const BASE_TEMPLATE_NAME: &str = "base";
27pub const REF_TEMPLATE_NAME: &str = "refTemplate";
28
29const TEMPLATE_NAMES: [&str; 10] = [
30 Player::TEMPLATE_NAME,
31 GameObjectEntity::<Character>::TEMPLATE_NAME,
32 GameObjectEntity::<Culture>::TEMPLATE_NAME,
33 GameObjectEntity::<Dynasty>::TEMPLATE_NAME,
34 GameObjectEntity::<House>::TEMPLATE_NAME,
35 GameObjectEntity::<Faith>::TEMPLATE_NAME,
36 GameObjectEntity::<Title>::TEMPLATE_NAME,
37 Timeline::TEMPLATE_NAME,
38 BASE_TEMPLATE_NAME,
39 REF_TEMPLATE_NAME,
40];
41
42const LOCALIZATION_GLOBAL: &str = "localization";
43const LOCALIZATION_FUNC_NAME: &str = "localize";
44
45pub fn create_env<'a>(
74 internal: bool,
75 map_present: bool,
76 no_vis: bool,
77 data: &GameData,
78) -> Environment<'a> {
79 let mut env = Environment::new();
80 env.set_lstrip_blocks(true);
81 env.set_trim_blocks(true);
82 env.add_filter("render_ref", render_ref);
83 env.add_filter(LOCALIZATION_FUNC_NAME, localize);
84 env.add_function(LOCALIZATION_FUNC_NAME, localize);
85 env.add_global("map_present", map_present);
86 env.add_global("no_vis", no_vis);
87 env.add_global(
88 LOCALIZATION_GLOBAL,
89 Value::from_serialize(data.get_localizer()),
90 );
91 env.set_undefined_behavior(UndefinedBehavior::Strict);
92 let template_path = Path::new("./templates");
93 if internal || !template_path.exists() {
94 #[cfg(feature = "internal")]
95 {
96 use internal_templates::*;
97 env.add_template(Player::TEMPLATE_NAME, INT_H_TEMPLATE)
98 .unwrap();
99 env.add_template(GameObjectEntity::<Character>::TEMPLATE_NAME, INT_C_TEMPLATE)
100 .unwrap();
101 env.add_template(GameObjectEntity::<Culture>::TEMPLATE_NAME, INT_CUL_TEMPLATE)
102 .unwrap();
103 env.add_template(GameObjectEntity::<Dynasty>::TEMPLATE_NAME, INT_DYN_TEMPLATE)
104 .unwrap();
105 env.add_template(GameObjectEntity::<House>::TEMPLATE_NAME, INT_HOUSE_TEMPLATE)
106 .unwrap();
107 env.add_template(GameObjectEntity::<Faith>::TEMPLATE_NAME, INT_FAITH_TEMPLATE)
108 .unwrap();
109 env.add_template(GameObjectEntity::<Title>::TEMPLATE_NAME, INT_TITLE_TEMPLATE)
110 .unwrap();
111 env.add_template(Timeline::TEMPLATE_NAME, INT_TIMELINE_TEMPLATE)
112 .unwrap();
113 env.add_template(BASE_TEMPLATE_NAME, INT_BASE_TEMPLATE)
114 .unwrap();
115 env.add_template(REF_TEMPLATE_NAME, INT_REF_TEMPLATE)
116 .unwrap();
117 }
118 #[cfg(not(feature = "internal"))]
119 {
120 panic!("Internal templates requested but not compiled in");
121 }
122 } else {
123 let template_dir = fs::read_dir(template_path).unwrap();
124 for read_result in template_dir {
125 match read_result {
126 Ok(entry) => {
127 let path = entry.path();
129 if !path.is_file() {
130 continue;
131 }
132 let name = TEMPLATE_NAMES
133 .iter()
134 .find(|&x| x == &path.file_stem().unwrap());
135 if let Some(name) = name {
136 env.add_template_owned(*name, fs::read_to_string(path).unwrap())
137 .unwrap();
138 }
139 }
140 Err(e) => eprintln!("Error reading template directory: {}", e),
141 }
142 }
143 }
144 env
145}
146
147fn render_ref(state: &State, reference: Value, root: Option<bool>) -> String {
153 if let Some(name) = reference
154 .get_attr(DERIVED_REF_NAME_ATTR)
155 .expect("Reference doesn't have attributes")
156 .as_str()
157 {
158 let subdir = reference.get_attr(DERIVED_REF_SUBDIR_ATTR).unwrap();
159 let id = reference.get_attr(DERIVED_REF_ID_ATTR).unwrap();
160 if state
161 .lookup("depth_map")
162 .unwrap()
163 .get_item(&subdir)
164 .unwrap()
165 .get_item(&id)
166 .ok()
167 .and_then(|i| i.as_i64())
168 .unwrap_or(0)
169 <= 0
170 {
171 name.to_string()
172 } else {
173 state
174 .env()
175 .get_template(REF_TEMPLATE_NAME)
176 .unwrap()
177 .render(context! {root=>root, ..reference})
178 .unwrap()
179 }
180 } else {
181 "".to_owned()
182 }
183}
184
185fn localize(state: &State, key: &str, value: Option<&str>, provider: Option<&str>) -> String {
186 let localizer = state.lookup(LOCALIZATION_GLOBAL).unwrap();
187 if let Some(value) = value {
188 if let Some(provider) = provider {
189 localizer.localize_provider(key, provider, value).unwrap()
190 } else {
191 localizer
192 .localize_query(key, |_| Some(value.to_string()))
193 .unwrap()
194 }
195 } else {
196 localizer.localize(key).unwrap()
197 }
198}