]>
Commit | Line | Data |
---|---|---|
29982470 RBR |
1 | use std::fs::File; |
2 | use std::path::PathBuf; | |
3 | use std::io::Read; | |
4 | ||
5 | const TXT_TEMPLATE: &'static str = include_str!("../templates/index.txt"); | |
6 | const HTML_TEMPLATE: &'static str = include_str!("../templates/index.html"); | |
7 | const GMI_TEMPLATE: &'static str = include_str!("../templates/index.gmi"); | |
8 | const RSS_TEMPLATE: &'static str = include_str!("../templates/feed.xml"); | |
9 | ||
10 | // Parse and Render | |
11 | ||
12 | pub enum Token { | |
13 | Text(String), | |
14 | DisplayDirective { content: String }, | |
15 | ConditionalDirective { condition: String, children: Vec<Token>}, | |
16 | IteratorDirective { collection: String, member_label: String, children: Vec<Token> } | |
17 | } | |
18 | ||
19 | impl std::fmt::Display for Token { | |
20 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { | |
21 | match self { | |
22 | Token::Text(label) => write!(f, "Text {}", label), | |
23 | Token::DisplayDirective{content} => write!(f, "DisplayDirective {}", content), | |
24 | Token::ConditionalDirective{condition, children} => { | |
25 | write!(f, "ConditionalDirective {} [[[\n", condition)?; | |
26 | for child in children { | |
27 | write!(f, "\t{}\n", child)?; | |
28 | } | |
29 | write!(f, "\n]]]") | |
30 | }, | |
31 | Token::IteratorDirective{collection, member_label, children} => { | |
32 | write!(f, "IteratorDirective {}: {} [[[\n", collection, member_label)?; | |
33 | for child in children { | |
34 | write!(f, "\t{}\n", child)?; | |
35 | } | |
36 | write!(f, "\n]]]") | |
37 | }, | |
38 | } | |
39 | } | |
40 | } | |
41 | ||
42 | pub struct ParsedTemplate { | |
43 | pub tokens: Vec<Token> | |
44 | } | |
45 | ||
46 | pub fn parse(template: &str) -> ParsedTemplate { | |
47 | let mut tokens = Vec::new(); | |
48 | tokenize(template, &mut tokens); | |
49 | ParsedTemplate { | |
50 | tokens | |
51 | } | |
52 | } | |
53 | ||
54 | fn tokenize(template: &str, tokens: &mut Vec<Token>) { | |
55 | let mut remaining_template = template; | |
56 | ||
57 | while !remaining_template.is_empty() && remaining_template.contains("{{") { | |
58 | let directive_start_index = remaining_template.find("{{") | |
59 | .expect("Was expecting at least one tag opener"); | |
60 | if directive_start_index > 0 { | |
61 | let text = remaining_template[..directive_start_index].to_string(); | |
62 | tokens.push(Token::Text(text.to_string())); | |
63 | } | |
64 | remaining_template = &remaining_template[directive_start_index..]; | |
65 | ||
66 | let directive_end_index = remaining_template.find("}}") | |
67 | .expect("Was expecting }} after {{") + 2; | |
68 | let directive = &remaining_template[..directive_end_index]; | |
69 | remaining_template = &remaining_template[directive_end_index..]; | |
70 | ||
71 | let directive_type = directive.chars().nth(2).unwrap(); | |
72 | match directive_type { | |
73 | // Simple Directives | |
74 | '=' => { | |
75 | let content = directive[3..directive.len() - 2].trim(); | |
76 | tokens.push(Token::DisplayDirective{ | |
77 | content: content.to_string() | |
78 | }); | |
79 | }, | |
80 | // Block Directives | |
81 | '?' | '~' => { | |
82 | let content = directive[3..directive.len() - 2].trim(); | |
83 | let mut children = Vec::new(); | |
84 | ||
85 | match directive_type { | |
86 | '?' => { | |
87 | let closing_block = remaining_template.find("{{?}}").unwrap(); | |
88 | let directive_block = &remaining_template[..closing_block]; | |
89 | remaining_template = &remaining_template[closing_block + 5..]; | |
90 | tokenize(directive_block, &mut children); | |
91 | tokens.push(Token::ConditionalDirective{ | |
92 | condition: content.to_string(), | |
93 | children | |
94 | }); | |
95 | }, | |
96 | '~' => { | |
97 | let parts: Vec<_> = content.splitn(2, ':').collect(); | |
98 | let closing_block = remaining_template.find("{{~}}").unwrap(); | |
99 | let directive_block = &remaining_template[..closing_block]; | |
100 | remaining_template = &remaining_template[closing_block + 5..]; | |
101 | tokenize(directive_block, &mut children); | |
102 | if parts.len() == 2 { | |
103 | tokens.push(Token::IteratorDirective { | |
104 | collection: parts[0].trim().to_string(), | |
105 | member_label: parts[1].trim().to_string(), | |
106 | children | |
107 | }); | |
108 | } | |
109 | }, | |
110 | _ => unreachable!() | |
111 | } | |
112 | }, | |
113 | _ => unreachable!() | |
114 | } | |
115 | } | |
116 | tokens.push(Token::Text(remaining_template.to_string())); | |
117 | } | |
118 | ||
119 | // File helpers. | |
120 | ||
121 | pub fn find(template_directory: &PathBuf, filename: &str) -> Option<String> { | |
122 | let template_path = template_directory.join(filename); | |
123 | if template_path.exists() { | |
124 | let mut contents = String::new(); | |
125 | if File::open(template_path).ok()?.read_to_string(&mut contents).is_ok() { | |
126 | return Some(contents); | |
127 | } | |
128 | } | |
129 | find_default(filename) | |
130 | } | |
131 | ||
132 | fn find_default(filename: &str) -> Option<String> { | |
133 | match filename { | |
134 | "index.txt" => Some(TXT_TEMPLATE.to_string()), | |
135 | "index.html" => Some(HTML_TEMPLATE.to_string()), | |
136 | "index.gmi" => Some(GMI_TEMPLATE.to_string()), | |
137 | "index.rss" => Some(RSS_TEMPLATE.to_string()), | |
138 | &_ => None | |
139 | } | |
140 | } |