Tholp's bespoke website generator
1mod macros;
2mod projectparse;
3mod stringtools;
4mod types;
5
6use macros::MACRO_LIST;
7use markdown::{to_html_with_options, CompileOptions, Constructs, Options, ParseOptions};
8use projectparse::{parse_project, FileIndexing, ProjectContext};
9use std::{
10 env,
11 fs::{self, File},
12 io::Write,
13 path::PathBuf,
14 process::{exit, Output},
15};
16use stringtools::{
17 collect_arguments, collect_block, split_keep_delimiters, split_to_tokens, strings_to_tokens,
18 trim_whitespace_tokens,
19};
20use types::{InputFile, Macro, Token};
21
22static DELIMITERS: &'static [char] = &[
23 ' ', '\n', '\t', '(', ')', '{', '}', '[', ']', '\\', '\'', '\"', ';',
24];
25
26fn main() {
27 let mut project_folder = PathBuf::from(env::current_dir().unwrap().as_path());
28
29 let mut project_path = project_folder.clone();
30 project_path.push("skidmark.toml");
31
32 while !project_path.exists() || project_path.is_dir() {
33 let ok = project_folder.pop();
34 if !ok {
35 println!("[ERROR] No skidmark.toml project file found in this folder or ancestors.");
36 exit(1);
37 }
38 project_path = project_folder.clone();
39 project_path.push("skidmark.toml");
40 }
41 println!("Operatting with {:?}", &project_path.as_os_str());
42 assert!(env::set_current_dir(&project_folder).is_ok());
43
44 let mut project = parse_project(&project_path);
45
46 let mut num = 0;
47
48 for group in &project.filegroups {
49 num = num + group.files.len();
50 }
51
52 println!("Proccesing {} files.", num);
53 for group in &mut project.filegroups {
54 for infile in &mut group.files {
55 process_file(infile, &mut project.context);
56 }
57 }
58}
59
60fn process_file(file: &mut InputFile, context: &mut ProjectContext) {
61 //}, context: &mut ProjectContext) {
62 let contents = fs::read_to_string(&file.file_input).expect("File unreadable or missing");
63 //println!("{}\n {}", f.filename_out, contents);
64
65 //file.tokens = strings_to_tokens(split_keep_delimiters(contents), file.filename_input.clone());
66 file.tokens = split_to_tokens(contents, context.index_of_file(&file.file_input));
67 //let mut escaped = false;
68
69 while file.working_index < file.tokens.len() {
70 //look for macros or blocks
71 //println!(">\"{}\"<", file.tokens[file.working_index].contents);
72
73 if file.tokens[file.working_index].contents.len() == 0 {
74 file.working_index += 1;
75 continue;
76 }
77
78 if file.tokens[file.working_index].contents == "\\" {
79 file.tokens[file.working_index].contents = "".into();
80 file.working_index += 2;
81 //println!("Hit backslash");
82 continue;
83 }
84
85 let mut matched_macro: bool = false;
86 if file.tokens[file.working_index]
87 .contents
88 .starts_with(['!', '&'])
89 {
90 let mut prefix_len = 1;
91 let mut symbol = file.tokens[file.working_index].contents.clone();
92 symbol = symbol.trim().to_string();
93
94 if symbol.len() > 2 {
95 let mut ephemeral = false;
96 let same_file = file.tokens[file.working_index].origin_file
97 != context.index_of_file(&file.file_input);
98
99 // Inversely Ephemeral
100 if symbol.starts_with("!&") {
101 prefix_len = 2;
102 ephemeral = !same_file;
103 }
104 // Ephemeral
105 else if symbol.starts_with("&") {
106 ephemeral = same_file;
107 }
108
109 // Check if its a macro
110 for m in MACRO_LIST {
111 if &symbol[prefix_len..] == m.symbol {
112 matched_macro = true;
113 //println!("Found a macro ({})", m.symbol);
114
115 let (args, args_tokcount) =
116 collect_arguments(&file.tokens[file.working_index..]);
117 let expansion: Vec<Token>;
118 let block_tokcount: usize;
119 if m.has_scope {
120 //println!("is scoped.");
121
122 let block_opt =
123 collect_block(&file.tokens[(file.working_index + args_tokcount)..]);
124 if block_opt.is_none() {
125 println!(
126 "[ERROR] {:?}:{} ;Malformed block",
127 file.tokens[file.working_index].origin_file,
128 file.tokens[file.working_index].line_number
129 );
130 exit(1);
131 }
132 let block: Vec<Token>;
133 (block, block_tokcount) = block_opt.unwrap();
134
135 if ephemeral {
136 expansion = Vec::new();
137 } else {
138 expansion = (m.expand)(
139 file,
140 file.tokens[file.working_index].origin_file,
141 file.tokens[file.working_index].line_number,
142 context,
143 &args,
144 &block,
145 );
146 }
147 } else {
148 block_tokcount = 0;
149
150 if ephemeral {
151 expansion = Vec::new();
152 } else {
153 expansion = (m.expand)(
154 file,
155 file.tokens[file.working_index].origin_file,
156 file.tokens[file.working_index].line_number,
157 context,
158 &args,
159 &Vec::new()[..],
160 );
161 }
162 }
163
164 let trimmed = trim_whitespace_tokens(&expansion);
165
166 file.tokens.remove(file.working_index);
167 file.tokens.splice(
168 file.working_index
169 ..(file.working_index + args_tokcount + block_tokcount - 1),
170 trimmed.iter().cloned(),
171 );
172 if expansion.len() == 0 && file.working_index > 0 {
173 file.working_index -= 1;
174 }
175 }
176 }
177
178 // check for templates
179 // todo maybe deduplicate this
180 for m in &mut file.templates {
181 if &symbol[prefix_len..] == m.symbol {
182 matched_macro = true;
183 //println!("Found a macro ({})", m.symbol);
184
185 let (args, args_tokcount) =
186 collect_arguments(&file.tokens[file.working_index..]);
187 let expansion: Vec<Token>;
188 let block_tokcount: usize;
189
190 if m.has_scope {
191 //println!("is scoped.");
192 let block: Vec<Token>;
193 let block_opt =
194 collect_block(&file.tokens[(file.working_index + args_tokcount)..]);
195 if block_opt.is_none() {
196 println!(
197 "[ERROR] {:?}:{} ;Malformed block",
198 file.tokens[file.working_index].origin_file,
199 file.tokens[file.working_index].line_number
200 );
201 exit(1);
202 }
203
204 (block, block_tokcount) = block_opt.unwrap();
205
206 if ephemeral {
207 expansion = Vec::new();
208 } else {
209 expansion = m.expand(
210 //file,
211 file.tokens[file.working_index].origin_file,
212 //file.tokens[file.working_index].line_number,
213 context,
214 &args,
215 &block,
216 );
217 }
218 } else {
219 block_tokcount = 0;
220
221 if ephemeral {
222 expansion = Vec::new();
223 } else {
224 expansion = m.expand(
225 //file,
226 file.tokens[file.working_index].origin_file,
227 //file.tokens[file.working_index].line_number,
228 context,
229 &args,
230 &Vec::new()[..],
231 );
232 }
233 }
234
235 let trimmed = trim_whitespace_tokens(&expansion);
236
237 file.tokens.remove(file.working_index);
238 file.tokens.splice(
239 file.working_index
240 ..(file.working_index + args_tokcount + block_tokcount - 1),
241 trimmed.iter().cloned(),
242 );
243 if expansion.len() == 0 && file.working_index > 0 {
244 file.working_index -= 1;
245 }
246 }
247 }
248 }
249 if !matched_macro {
250 println!(
251 "[WARN] {:?}:{}; Token written as a function but no such function exists \"{}\"",
252 file.file_input,
253 file.tokens[file.working_index].line_number,
254 file.tokens[file.working_index].contents.trim()
255 );
256 }
257 }
258 if !matched_macro {
259 file.working_index += 1;
260 }
261 }
262 //println!("{:?}", file.tokens);
263 let mut skid_output: String = "".to_string();
264 for t in &file.tokens {
265 skid_output += &t.contents;
266 }
267 fs::write(&file.file_skidout, &skid_output).expect("Couldn't write skid to file");
268
269 //let html_output = markdown::to_html(&skid_output);
270 let html_output = markdown::to_html_with_options(
271 &skid_output,
272 &Options {
273 compile: CompileOptions {
274 allow_dangerous_html: true,
275 allow_dangerous_protocol: true,
276 gfm_tagfilter: false,
277 // gfm_footnote_clobber_prefix:
278 gfm_task_list_item_checkable: true,
279 allow_any_img_src: true,
280 ..CompileOptions::gfm()
281 },
282 parse: ParseOptions {
283 constructs: Constructs {
284 code_indented: false,
285 ..Constructs::gfm()
286 },
287 ..ParseOptions::default()
288 },
289 },
290 )
291 .unwrap();
292 fs::write(&file.file_htmlout, &html_output).expect("Couldn't write html to file");
293 print!(
294 "[OK] {} written.\n\n",
295 file.file_htmlout
296 .to_str()
297 .unwrap_or("Couldnt Unwrap htmlout name")
298 );
299}