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}