1use std::fmt;
23use std::path::PathBuf;
24use std::convert::From;
25use std::collections::HashMap;
26
27use mdbook::{
28 BookItem,
29 book::{Book, Chapter},
30};
31use tinytemplate::TinyTemplate;
32use serde::{Serialize, Deserialize};
33
34use crate::cat_context::CatContext;
35use crate::error::CatError;
36use crate::models::*;
37
38#[derive(Debug, Clone)]
42pub enum RenderType {
43 Prepend(String),
45 Append(String),
47 Both(String, String),
49 EntirePage(String),
51}
52
53use RenderType::*;
54
55impl fmt::Display for RenderType {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 match self {
65 Prepend(_) => write!(f, "Prepend"),
66 Append(_) => write!(f, "Append"),
67 Both(_, _) => write!(f, "Both"),
68 EntirePage(_) => write!(f, "EntirePage"),
69 }
70 }
71}
72
73#[derive(Debug, Clone)]
75pub struct RenderSite {
76 pub site: PathBuf,
78 pub render: RenderType,
80}
81
82impl RenderSite {
83 pub fn new(site: PathBuf, render: RenderType) -> Self {
85 RenderSite { site, render }
86 }
87}
88
89pub trait Render {
92 fn render(&self, context: &CatContext) -> Result<RenderSite, CatError>;
97}
98
99pub static TEACHER_TEMPLATE: &'static str = r#"
101<h2 id="{card.username}">{card.jmeno}</h2>
102
103- email: <a href="mailto:{card.email}">{card.email}</a>
104- username: {card.username}
105
106### Bio
107{card.bio}
108
109### Předměty
110{{ for p in subjects }} - [{p.card.nazev}](/{p.path})
111{{ endfor }}
112
113### Materiály
114{{ for a in articles }} - [{a.card.nazev}](/{a.path})
115{{ endfor }}
116<hr>
117"#;
118
119impl Render for Teacher {
120 fn render(&self, _: &CatContext) -> Result<RenderSite, CatError> {
121 let render_site = PathBuf::from("teachers.md");
122 let mut tt = TinyTemplate::new();
123
124 tt.add_template("teacher", TEACHER_TEMPLATE)
125 .map_err(|e| CatError::TinyError { error: e.to_string() })?;
126 let res = tt
127 .render("teacher", &self)
128 .map_err(|e| CatError::TinyError { error: e.to_string() })?;
129
130 dbg!("{}", &res);
131
132 Ok(RenderSite::new(render_site, Append(res)))
133 }
134}
135
136pub static TEACHER_LIST_TEMPLATE: &'static str = r#"
138{{ for t in list }} [{t.jmeno}](#{t.username}) {{ endfor }}
139"#;
140
141#[derive(Debug, Serialize, Clone)]
143pub struct TeacherList {
144 pub list: Vec<TeacherCard>,
146}
147
148impl Render for TeacherList {
149 fn render(&self, _: &CatContext) -> Result<RenderSite, CatError> {
150 let render_site = PathBuf::from("teachers.md");
151 let mut tt = TinyTemplate::new();
152
153 tt.add_template("teacher", TEACHER_LIST_TEMPLATE)
154 .map_err(|e| CatError::TinyError { error: e.to_string() })?;
155 let res = tt
156 .render("teacher", &self)
157 .map_err(|e| CatError::TinyError { error: e.to_string() })?;
158
159 dbg!("{}", &res);
160
161 Ok(RenderSite::new(render_site, Append(res)))
162 }
163}
164
165pub static SUBJECT_PRE_TEMPLATE: &'static str = r#"
167| Název | { card.nazev } |
168| ----- | -------------- |
169{{ if resolved_author }}| Zodpovědná osoba | [{resolved_author.jmeno}](/teachers.md#{resolved_author.username}) | {{ else }}| Zodpovědná osoba | {card.zodpovedna_osoba} | {{ endif }}
170| Popis | { card.bio } |
171"#;
172
173pub static SUBJECT_POST_TEMPLATE: &'static str = r#"
175### Seznam materiálů
176{{ for a in articles }} - [{a.card.nazev}](/{a.path})
177{{ endfor }}
178"#;
179
180impl Render for Subject {
181 fn render(&self, _: &CatContext) -> Result<RenderSite, CatError> {
182 let render_site = self.path.clone();
183 let mut tt = TinyTemplate::new();
184
185 tt.add_template("subject_pre", SUBJECT_PRE_TEMPLATE)
186 .map_err(|e| CatError::TinyError { error: e.to_string() })?;
187 tt.add_template("subject_post", SUBJECT_POST_TEMPLATE)
188 .map_err(|e| CatError::TinyError { error: e.to_string() })?;
189
190 let pre = tt
191 .render("subject_pre", &self)
192 .map_err(|e| CatError::TinyError { error: e.to_string() })?;
193
194 let post = tt
195 .render("subject_post", &self)
196 .map_err(|e| CatError::TinyError { error: e.to_string() })?;
197
198 dbg!("{}\n{}", &pre, &post);
199
200 Ok(RenderSite::new(render_site, Both(pre, post)))
201 }
202}
203
204pub static ARTICLE_PRE_TEMPLATE: &'static str = r#"
206| Název | {card.nazev} |
207| ----- | ------------ |
208{{ if resolved_author }}| Autor | [{resolved_author.jmeno}](/teachers.md#{resolved_author.username}) | {{ else }}| Autor | {author} | {{ endif }}
209{{ if modified_resolved }}| Naposledy upravil | [{modified_resolved.jmeno}](/teachers.md#{modified_resolved.username}) | {{ else }}| Naposledy upravil | {modified_by} | {{ endif }}
210| Poslední změna | {last_modified} |
211| Předmět | [{subject_card.nazev}](/{subject_card._resolved_path}) |
212{{ if card.datum }}| Datum | {card.datum} |{{endif}}
213"#;
214
215pub static ARTICLE_POST_TEMPLATE: &'static str = r#"
220#### Tagy
221{{ for tag in card.tagy}} [{tag}](/tags.md#{tag}) {{ endfor }}
222
223<div id="disqus_thread"></div>
224<script>var disqus_config = function () \{ this.page.url = window.location.href; this.page.identifier = window.location.href; }; (function() \{ var d = document, s = d.createElement('script'); s.src = 'https://gjk-cat.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); })(); </script>
225<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
226"#;
227
228impl Render for Article {
229 fn render(&self, _: &CatContext) -> Result<RenderSite, CatError> {
230 let render_site = self.path.clone();
231 let mut tt = TinyTemplate::new();
232
233 tt.add_template("article_pre", ARTICLE_PRE_TEMPLATE)
234 .map_err(|e| CatError::TinyError { error: e.to_string() })?;
235 tt.add_template("article_post", ARTICLE_POST_TEMPLATE)
236 .map_err(|e| CatError::TinyError { error: e.to_string() })?;
237
238 let pre = tt
239 .render("article_pre", &self)
240 .map_err(|e| CatError::TinyError { error: e.to_string() })?;
241
242 let post = tt
243 .render("article_post", &self)
244 .map_err(|e| CatError::TinyError { error: e.to_string() })?;
245
246 dbg!("{}\n{}", &pre, &post);
247
248 Ok(RenderSite::new(render_site, Both(pre, post)))
249 }
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct Tag {
255 pub name: String,
257 pub articles: Vec<ArticleCard>,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
263pub struct TagContext {
264 pub tags: Vec<Tag>,
266}
267
268impl From<&HashMap<String, Vec<ArticleCard>>> for TagContext {
270 fn from(src: &HashMap<String, Vec<ArticleCard>>) -> Self {
271 let mut tags =
272 src.iter().map(|(k, v)| (k.clone(), v.clone())).collect::<Vec<_>>();
273 tags.sort_by(|a, b| a.0.cmp(&b.0));
274
275 Self {
276 tags: tags
277 .into_iter()
278 .map(|(k, v)| Tag { name: k, articles: v })
279 .collect::<Vec<_>>(),
280 }
281 }
282}
283
284pub static TAGS_TEMPLATE: &'static str = r#"
286# Tagy
287{{ for tag in tags }} [{tag.name}](#{tag.name}) {{ endfor }}
288
289{{ for tag in tags }}
290<h3 id="{tag.name}">{tag.name}</h3>
291{{ for a in tag.articles }}
292 - [{a.nazev}](/{a._resolved_path}){{ endfor }}
293{{ endfor }}
294"#;
295
296impl Render for TagContext {
297 fn render(&self, _: &CatContext) -> Result<RenderSite, CatError> {
298 let render_site = PathBuf::from("tags.md");
299 let mut tt = TinyTemplate::new();
300
301 tt.add_template("tags", TAGS_TEMPLATE)
302 .map_err(|e| CatError::TinyError { error: e.to_string() })?;
303 let res = tt
304 .render("tags", &self)
305 .map_err(|e| CatError::TinyError { error: e.to_string() })?;
306
307 dbg!("{}", &res);
308
309 Ok(RenderSite::new(render_site, EntirePage(res)))
310 }
311}
312
313pub fn create_renders(
318 context: &CatContext,
319 book: &mut Book,
320) -> Result<Vec<RenderSite>, CatError> {
321 let mut pending_renders: Vec<RenderSite> = vec![];
322 let mut errors: Vec<CatError> = vec![];
323
324 match (TeacherList { list: context.teacher_cards.clone() }).render(context) {
325 Ok(r) => pending_renders.push(r),
326 Err(e) => return Err(e),
327 }
328
329 context.teachers.iter().for_each(|t| match t.render(context) {
330 Ok(r) => pending_renders.push(r),
331 Err(e) => errors.push(e),
332 });
333
334 if !errors.is_empty() {
335 errors.iter().for_each(|x| eprintln!("[cat-prep] {}", x));
336
337 return Err(errors[0].clone());
338 }
339
340 context.subjects.iter().for_each(|t| match t.render(context) {
341 Ok(r) => pending_renders.push(r),
342 Err(e) => errors.push(e),
343 });
344
345 if !errors.is_empty() {
346 errors.iter().for_each(|x| eprintln!("[cat-prep] {}", x));
347
348 return Err(errors[0].clone());
349 }
350
351 context.articles.iter().for_each(|t| match t.render(context) {
352 Ok(r) => pending_renders.push(r),
353 Err(e) => errors.push(e),
354 });
355
356 if !errors.is_empty() {
357 errors.iter().for_each(|x| eprintln!("[cat-prep] {}", x));
358
359 return Err(errors[0].clone());
360 }
361
362 match TagContext::from(&context.tags).render(context) {
363 Ok(r) => pending_renders.push(r),
364 Err(e) => return Err(e),
365 }
366
367
368 if !context.teacher_cards.is_empty() {
369 book.push_item(BookItem::Chapter(Chapter::new(
370 "Vyučující",
371 "# Vyučující\n".to_string(),
372 "teachers.md".to_string(),
373 vec![],
374 )));
375 }
376
377 if !context.tags.is_empty() {
378 book.push_item(BookItem::Chapter(Chapter::new(
379 "Tagy",
380 "".to_string(),
381 "tags.md".to_string(),
382 vec![],
383 )));
384 }
385
386 dbg!("[cat prep] prerender: {:#?}", &book);
387
388 Ok(pending_renders)
389}
390
391pub fn execute_renders(
396 mut pending_renders: Vec<RenderSite>,
397 book: &mut Book,
398) -> Result<(), CatError> {
399 book.for_each_mut(|c| {
400 if let BookItem::Chapter(c) = c {
401 let path = c.path.clone();
402
403 pending_renders.iter().filter(|x| x.site == path.clone().unwrap()).for_each(|x| {
404 match &x.render {
405 Prepend(s) => c.content = format!("{}\n{}", c.content, s),
406 Both(pre, post) =>
407 c.content = format!("{}\n{}\n{}", pre, c.content, post),
408 Append(s) => c.content = format!("{}\n{}", c.content, s),
409 EntirePage(s) => c.content = s.clone(),
410 }
411 });
412
413 pending_renders.retain(|x| x.site != c.path.clone().unwrap());
414 }
415 });
416
417 if !pending_renders.is_empty() {
418 for RenderSite { site, render } in &pending_renders {
419 println!("[cat-prep] error: oprhan render: {} at {}", render, site.display());
420 }
421 return Err(CatError::OrphanRender {
422 site: pending_renders[0].site.display().to_string(),
423 render: pending_renders[0].render.clone(),
424 });
425 }
426
427 Ok(())
428}