Tholp's bespoke website generator
1mod args;
2mod console;
3mod macros;
4mod project;
5mod reservednames;
6mod stringtools;
7mod types;
8
9use crate::{
10 reservednames::RESERVED_NAMES_MISC,
11 stringtools::TokenTools,
12 types::{IsScoped, MacroExpand, SkidContext},
13};
14
15use console::*;
16use macros::MACRO_LIST;
17use markdown::{CompileOptions, Constructs, Options, ParseOptions};
18use project::{parse_project, Indexing, Project};
19use reservednames::RESERVED_NAMES_HTML;
20use std::{
21 env,
22 fs::{self},
23 path::PathBuf,
24 time::Instant,
25};
26use stringtools::{collect_arguments, collect_block, split_to_tokens};
27use types::Token;
28
29// really need to change this whole thing to work with characters rather than
30// strings split on kind of abitrary chars..
31static DELIMITERS: &'static [char] = &[
32 ' ', '\n', '\t', '(', ')', '{', '}', '[', ']', '<', '>', '\\', '\'', '\"', ';', '?', '^', '-',
33 '`',
34];
35
36#[derive(PartialEq)]
37enum EphemeralType {
38 Normal,
39 Ephemeral,
40 InverseEphemeral,
41}
42
43fn main() {
44 // let args = ProgramArgs::parse();
45
46 let mut project_folder = PathBuf::from(env::current_dir().unwrap().as_path());
47
48 let mut project_path = project_folder.clone();
49 project_path.push("skidmark.toml");
50
51 while !project_path.exists() || project_path.is_dir() {
52 let ok = project_folder.pop();
53 if !ok {
54 error_generic(
55 &"No skidmark.toml project file found in this folder or ancestors.".into(),
56 );
57 }
58 project_path = project_folder.clone();
59 project_path.push("skidmark.toml");
60 }
61 info_generic(&format!("Operatting on {:?}", &project_path.as_os_str()));
62 assert!(env::set_current_dir(&project_folder).is_ok());
63
64 let mut project = parse_project(&project_path);
65
66 let mut num = 0;
67
68 for group in &project.filegroups {
69 num = num + group.files.len();
70 }
71
72 info_generic(&format!("Proccesing {} files.", num));
73 // for group in &mut project.filegroups {
74 // for infile in &mut group.files {
75 // process_skid(infile, group.convert_html, &mut project.context);
76 // }
77 // }
78 let project_start = Instant::now();
79 for i in 0..project.filegroups.len() {
80 if !project.filegroups[i].process {
81 continue;
82 }
83 let convert_html = project.filegroups[i].convert_html;
84 for k in 0..project.filegroups[i].files.len() {
85 let file_start = Instant::now();
86 let file_input = project.filegroups[i].files[k].file_input.clone();
87 let contents = fs::read_to_string(&project.filegroups[i].files[k].file_input)
88 .expect("File unreadable or missing");
89 let tokens = split_to_tokens(contents, project.index_of_file(&file_input));
90
91 let mut skid_context = SkidContext::new(project.index_of_file(&file_input));
92 write_file(
93 &project.filegroups[i].files[k].file_skidout.clone(),
94 &project.filegroups[i].files[k].file_out.clone(),
95 convert_html,
96 &process_skid(&tokens, &mut project, &mut skid_context),
97 file_start,
98 );
99 }
100 }
101 info_generic(&format!(
102 "Finished in {:.3}ms",
103 project_start.elapsed().as_secs_f64() * 1000.0
104 ));
105}
106
107fn find_and_run_macro(
108 tokens_in: &[Token],
109 proj_context: &mut Project,
110 skid_context: &mut SkidContext,
111) -> Option<(Vec<Token>, usize)> {
112 // (Output, to be consumed size)
113
114 // At this point we think its a macro (starts with ! or &) so check which, we have the rest of the file
115 let ephemeral_type: EphemeralType;
116 if tokens_in.len() < 2 {
117 return None;
118 }
119
120 if tokens_in[0] == '!' && tokens_in[1] == '&' {
121 ephemeral_type = EphemeralType::InverseEphemeral;
122 } else if tokens_in[0] == '!' {
123 ephemeral_type = EphemeralType::Normal;
124 } else if tokens_in[0] == '&' {
125 ephemeral_type = EphemeralType::Ephemeral;
126 } else {
127 return None;
128 }
129
130 let mut chars_consumed = if ephemeral_type == EphemeralType::InverseEphemeral {
131 2
132 } else {
133 1
134 };
135 // Look for name
136 let mut symbol: String = "".into();
137 for tok in &tokens_in[chars_consumed..] {
138 if tok.contents.is_whitespace() || DELIMITERS.contains(&tok.contents) {
139 break;
140 }
141 symbol.push(tok.contents);
142 chars_consumed += 1;
143 }
144
145 if symbol.is_empty() {
146 return None;
147 }
148
149 let args;
150 let block;
151
152 {
153 let mut expander: &dyn IsScoped = &MACRO_LIST[0]; // assinging because it complains about possibly being empty later even if not the case
154 let mut found = false;
155 // Check if its a macro
156 for m in MACRO_LIST {
157 if m.symbol == symbol {
158 found = true;
159 expander = m;
160 break;
161 }
162 }
163
164 // Not a macro check templates
165 if !found {
166 for t in &skid_context.templates {
167 if t.symbol == symbol {
168 found = true;
169 expander = t;
170 break;
171 }
172 }
173 }
174
175 // Not a template either, see if its reserved or not to see if we should say something
176 if !found {
177 let name = symbol.to_lowercase();
178 let mut dont_error = false;
179
180 for reserved in RESERVED_NAMES_HTML {
181 if name.starts_with(reserved) {
182 dont_error = true;
183 break;
184 }
185 }
186
187 if !dont_error {
188 for reserved in RESERVED_NAMES_MISC {
189 if name.starts_with(reserved) {
190 dont_error = true;
191 break;
192 }
193 }
194 }
195
196 if !dont_error {
197 warn_skid(
198 &proj_context,
199 tokens_in[0].origin_index,
200 tokens_in[0].origin_line,
201 &format!("No such macro or defined template \"{symbol}\""),
202 );
203 }
204 return None;
205 }
206
207 let args_result = collect_arguments(&tokens_in[chars_consumed..]);
208 if args_result.is_none() {
209 error_skid(
210 proj_context,
211 tokens_in[0].origin_index,
212 tokens_in[0].origin_line,
213 &format!("Didnt find any arguments for macro \"{symbol}\"."),
214 );
215 return None;
216 }
217
218 let consumed_by_args;
219 (args, consumed_by_args) = args_result.unwrap();
220 chars_consumed += consumed_by_args;
221
222 if expander.is_scoped() {
223 let block_result = collect_block(&tokens_in[chars_consumed..]);
224 if block_result.is_none() {
225 error_skid(
226 proj_context,
227 tokens_in[0].origin_index,
228 tokens_in[0].origin_line,
229 &format!("Didnt find a block for macro \"{symbol}\"."),
230 );
231 return None;
232 }
233 let consumed_by_block;
234 (block, consumed_by_block) = block_result.unwrap();
235 chars_consumed += consumed_by_block;
236 } else {
237 block = Vec::new();
238 }
239 }
240
241 let return_empty: bool;
242
243 match ephemeral_type {
244 EphemeralType::Normal => return_empty = false,
245 EphemeralType::Ephemeral => {
246 return_empty = skid_context.file_index != tokens_in[0].origin_index
247 }
248 EphemeralType::InverseEphemeral => {
249 return_empty = skid_context.file_index == tokens_in[0].origin_index
250 }
251 }
252
253 if return_empty {
254 return Some((Vec::new(), chars_consumed));
255 } else {
256 // we have to find it again because of borrower
257 for m in MACRO_LIST {
258 if m.symbol == symbol {
259 return Some((
260 m.expand(
261 tokens_in[0].origin_index,
262 tokens_in[0].origin_line,
263 proj_context,
264 skid_context,
265 &args,
266 &block,
267 )
268 .trim_whitespace()
269 .to_vec(),
270 chars_consumed,
271 ));
272 }
273 }
274 let mut i = 0;
275 while i < skid_context.templates.len() {
276 if skid_context.templates[i].symbol == symbol {
277 return Some((
278 skid_context.templates[i]
279 .expand(
280 tokens_in[0].origin_index,
281 tokens_in[0].origin_line,
282 proj_context,
283 &args,
284 &block,
285 )
286 .trim_whitespace()
287 .to_vec(),
288 chars_consumed,
289 ));
290 }
291 i += 1;
292 }
293 }
294 None
295}
296
297pub fn process_skid(
298 tokens_in: &[Token],
299 proj_context: &mut Project,
300 skid_context: &mut SkidContext,
301) -> Vec<Token> {
302 //}, context: &mut ProjectContext) {
303 //println!("{}\n {}", f.filename_out, contents);
304
305 //file.tokens = strings_to_tokens(split_keep_delimiters(contents), file.filename_input.clone());
306
307 //let mut escaped = false;
308 let mut tokens = tokens_in.to_vec();
309 let starting_template_count = skid_context.templates.len();
310
311 let mut escaped = false;
312 let mut working_index = 0;
313
314 while working_index < tokens.len() {
315 if tokens[working_index].pre_proccessed {
316 working_index += 1;
317 continue;
318 }
319
320 if tokens[working_index] == '\\' && !escaped {
321 tokens[working_index].contents = '\0'; // skip over this later when outputting to avoid shifting memory rn
322 escaped = true;
323 working_index += 1;
324
325 // bit of a hack for reverse ephemeral escaping behavior to be the same as previously
326 if tokens.len() > working_index + 1
327 && tokens[working_index] == '!'
328 && tokens[working_index + 1] == '&'
329 {
330 working_index += 1;
331 }
332 continue;
333 }
334
335 if (tokens[working_index] == '!' || tokens[working_index] == '&') && !escaped {
336 let expansion =
337 find_and_run_macro(&tokens[working_index..], proj_context, skid_context);
338 if expansion.is_some() {
339 tokens.splice(
340 working_index..working_index + expansion.as_ref().unwrap().1,
341 expansion.unwrap().0,
342 );
343 continue;
344 }
345 }
346
347 // Not a macro or template, look through our closures
348 // for c in CLOSURE_LIST
349 // {
350 // if tokens[working_index].contents.starts_with(c.opener)
351 // {
352
353 // }
354 // }
355
356 working_index += 1;
357
358 escaped = false;
359 }
360 skid_context.templates.truncate(starting_template_count);
361
362 tokens.retain(|t| t.contents != '\0');
363
364 return tokens;
365}
366
367fn write_file(
368 file_skidout: &PathBuf,
369 file_out: &PathBuf,
370 convert_html: bool,
371 tokens: &[Token],
372 file_start: Instant,
373) {
374 //println!("{:?}", tokens);
375 let mut skid_output: String = if convert_html {
376 "<!-- Generated by Skidmark, Do Not Edit! -->\n\n".into()
377 } else {
378 "".into()
379 };
380 for t in tokens {
381 skid_output.push(t.contents);
382 }
383
384 let mut folder = file_skidout.clone();
385 folder.pop();
386 if fs::create_dir_all(&folder).is_err() {
387 error_generic(&format!("Could not make the folder {:?}", &folder));
388 }
389
390 if convert_html {
391 fs::write(&file_skidout, &skid_output).expect("Couldn't write skid to file");
392
393 //let html_output = markdown::to_html(&skid_output);
394 let html_output = markdown::to_html_with_options(
395 &skid_output,
396 &Options {
397 compile: CompileOptions {
398 allow_dangerous_html: true,
399 allow_dangerous_protocol: true,
400 gfm_tagfilter: false,
401 // gfm_footnote_clobber_prefix:
402 gfm_task_list_item_checkable: true,
403 allow_any_img_src: true,
404 ..CompileOptions::gfm()
405 },
406 parse: ParseOptions {
407 constructs: Constructs {
408 code_indented: false,
409
410 //html_flow: false,
411 ..Constructs::gfm()
412 },
413 ..ParseOptions::default()
414 },
415 },
416 )
417 .unwrap();
418 fs::write(&file_out, &html_output).expect("Couldn't write output to file");
419 } else {
420 fs::write(&file_out, &skid_output).expect("Couldn't write output to file");
421 }
422 ok_generic(&format!(
423 "{} written in {:.3}ms",
424 file_out.to_str().unwrap_or("Couldnt Unwrap file_out name"),
425 file_start.elapsed().as_secs_f64() * 1000.0
426 ));
427}