Tholp's bespoke website generator
at main 13 kB view raw
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}