diff options
author | Qrius <[email protected]> | 2024-09-26 00:11:05 +0200 |
---|---|---|
committer | Qrius <[email protected]> | 2024-09-26 00:11:05 +0200 |
commit | f8df856d45acb619ebc7766b17bded03bac8d851 (patch) | |
tree | 074d39608615ec0a6e1e08f1cda5104cd39cf53d /src | |
parent | fee06958744756e280cdf83c3ffeade8057fcaff (diff) | |
download | skaldpress-f8df856d45acb619ebc7766b17bded03bac8d851.tar.gz skaldpress-f8df856d45acb619ebc7766b17bded03bac8d851.zip |
Add argument parser, prepare for utilizing already compiled documents to
expand some special macros
Diffstat (limited to 'src')
-rw-r--r-- | src/skaldpress/main.rs | 92 | ||||
-rw-r--r-- | src/skaldpress/mod.rs | 1 | ||||
-rw-r--r-- | src/skaldpress/parseopts.rs | 150 |
3 files changed, 224 insertions, 19 deletions
diff --git a/src/skaldpress/main.rs b/src/skaldpress/main.rs index 81fef9f..6f00963 100644 --- a/src/skaldpress/main.rs +++ b/src/skaldpress/main.rs @@ -1,3 +1,4 @@ +use skaldpress::skaldpress::parseopts::{parseopts, Opts}; use std::collections::HashMap; use std::fs; use std::path::Path; @@ -12,18 +13,25 @@ use skaldpress::skaldpress::error::{ use skaldpress::skaldpress::metadata_parser::extract_parse_yaml_metadata; use skaldpress::skaldpress::metadata_parser::YamlValue; -const TEMPLATES_DIR: &str = "templates/"; -const CONTENT_DIR: &str = "content/"; -const BUILD_DIR: &str = "build/"; +/// Cache of all compiled docs +static mut COMPILED_DOCS: Vec<CompiledFile> = Vec::new(); +/// Keeps all of the compiled docs sorted by the tag name, variable will be ASSUMED initialized!!! +static mut COMPILED_DOCS_BY_TAG: Option<HashMap<String, Vec<usize>>> = None; +/// Information about a file that has been compiled, this is likely going to be registered in the +/// global space. struct CompiledFile { + /// The compiled content of the file content: String, + /// All data from the metadata block of the file metadata: HashMap<String, YamlValue>, + /// The compiled files destination-extension, this will be the templates extension, + /// or the file itself if it didn't have a extension extension: String, } /// Will attempt to compile a specific file, potentially storing some state about the file -fn compile_file(file_path: &Path) -> Result<CompiledFile, SkaldpressError> { +fn compile_file(file_path: &Path, opts: &Opts) -> Result<CompiledFile, SkaldpressError> { let extension = file_path.extension().unwrap_or(std::ffi::OsStr::new("")); let file_content = fs::read_to_string(file_path).map_err(|e| { @@ -60,7 +68,7 @@ fn compile_file(file_path: &Path) -> Result<CompiledFile, SkaldpressError> { }); }; - let template_file = format!("{}{}", TEMPLATES_DIR, template); + let template_file = format!("{}{}", opts.template_dir, template); let template = fs::read_to_string(&template_file).map_err(|e| { SkaldpressError::TemplateReadError( SP_COMPILE_FILE_TEMPLATE_READ_ERROR, @@ -83,10 +91,38 @@ fn compile_file(file_path: &Path) -> Result<CompiledFile, SkaldpressError> { }) } -fn compile_file_and_write(source_file_path: &Path) -> Result<(), Box<dyn std::error::Error>> { - let cfile = compile_file(&source_file_path)?; +fn compile_file_and_write( + source_file_path: &Path, + opts: &Opts, +) -> Result<(), Box<dyn std::error::Error>> { + let compiled_file = compile_file(&source_file_path, opts)?; + + let cfile: &CompiledFile; + let cfile_i: usize; + // If this program ever becomes threaded, make sure to add a mutex for this + unsafe { + COMPILED_DOCS.push(compiled_file); + cfile_i = COMPILED_DOCS.len() - 1; + cfile = &COMPILED_DOCS[cfile_i]; + } + + if let Some(tags) = &cfile.metadata.get("tags") { + if let YamlValue::List(tags) = tags { + let compiled_docs_by_tag: &mut HashMap<String, Vec<usize>>; + unsafe { + compiled_docs_by_tag = COMPILED_DOCS_BY_TAG.as_mut().unwrap(); + } + for tag in tags { + if !compiled_docs_by_tag.contains_key(tag) { + (*compiled_docs_by_tag).insert(tag.clone(), Vec::new()); + } + // Assuming the creation of the tag was sucessfull, so just unwrapping + (*compiled_docs_by_tag).get_mut(tag).unwrap().push(cfile_i); + } + } + } - if let Some(skip_build) = cfile.metadata.get("skip_build") { + if let Some(skip_build) = &cfile.metadata.get("skip_build") { if let YamlValue::Scalar(skip_build) = skip_build { if skip_build.to_lowercase() == "true" { return Ok(()); @@ -94,11 +130,18 @@ fn compile_file_and_write(source_file_path: &Path) -> Result<(), Box<dyn std::er } } - let dest_file_path = Path::new(BUILD_DIR) - .join(source_file_path.strip_prefix(CONTENT_DIR).map_err(|e| { - SkaldpressError::PathOperationError(SP_GEN_DEST_STRIP_PREFIX_ERROR, Some(Box::new(e))) - })?) - .with_extension(cfile.extension); + let dest_file_path = Path::new(&opts.build_dir) + .join( + source_file_path + .strip_prefix(&opts.content_dir) + .map_err(|e| { + SkaldpressError::PathOperationError( + SP_GEN_DEST_STRIP_PREFIX_ERROR, + Some(Box::new(e)), + ) + })?, + ) + .with_extension(&cfile.extension); let dest_dir = &dest_file_path .parent() @@ -112,11 +155,11 @@ fn compile_file_and_write(source_file_path: &Path) -> Result<(), Box<dyn std::er })?; println!("> Writing {:#?} to {:#?}", source_file_path, dest_file_path); - fs::write(&dest_file_path, cfile.content)?; + fs::write(&dest_file_path, &cfile.content)?; Ok(()) } -fn compile_files_in_directory(directory: &Path) -> Result<(), SkaldpressError> { +fn compile_files_in_directory(directory: &Path, opts: &Opts) -> Result<(), SkaldpressError> { for entry in fs::read_dir(directory).map_err(|e| { SkaldpressError::DirectoryReadError( 8, @@ -142,7 +185,7 @@ fn compile_files_in_directory(directory: &Path) -> Result<(), SkaldpressError> { if metadata.is_file() { println!("< Compiling {:#?}", path.as_path()); - if let Err(e) = compile_file_and_write(path.as_path()) { + if let Err(e) = compile_file_and_write(path.as_path(), opts) { println!( "\x1b[31mError compiling {:#?}: {}\x1b[0m", path.as_path(), @@ -150,7 +193,7 @@ fn compile_files_in_directory(directory: &Path) -> Result<(), SkaldpressError> { ); }; } else if metadata.is_dir() { - if let Err(e) = compile_files_in_directory(path.as_path()) { + if let Err(e) = compile_files_in_directory(path.as_path(), opts) { println!( "\x1b[31mError processing directory {:#?}: {}\x1b[0m", path.as_path(), @@ -163,10 +206,21 @@ fn compile_files_in_directory(directory: &Path) -> Result<(), SkaldpressError> { } fn main() { - let _ = std::fs::remove_dir_all(Path::new(BUILD_DIR)); + unsafe { + COMPILED_DOCS_BY_TAG = Some(HashMap::new()); + } + + let opts = parseopts().build(); + + println!("Removing {:#?}", opts.build_dir); + let _ = std::fs::remove_dir_all(Path::new(&opts.build_dir)); // Should run twice, or at least the files which uses macros which has to be generated during // runtime - let _ = compile_files_in_directory(Path::new(CONTENT_DIR)); + let _ = compile_files_in_directory(Path::new(&opts.content_dir), &opts); + + unsafe { + println!("{:#?}", COMPILED_DOCS_BY_TAG); + } // Just for testing //compile_file_and_write(Path::new("content/test.html")); diff --git a/src/skaldpress/mod.rs b/src/skaldpress/mod.rs index 770035f..5245cd8 100644 --- a/src/skaldpress/mod.rs +++ b/src/skaldpress/mod.rs @@ -1,2 +1,3 @@ pub mod error; pub mod metadata_parser; +pub mod parseopts; diff --git a/src/skaldpress/parseopts.rs b/src/skaldpress/parseopts.rs new file mode 100644 index 0000000..bca6a73 --- /dev/null +++ b/src/skaldpress/parseopts.rs @@ -0,0 +1,150 @@ +/// Struct containing command line options +pub struct Opts { + /// Directory where templates are, will be relative to CWD + pub template_dir: String, + /// Directory where the content which will be recursively iterated is, will be relative to CWD + pub content_dir: String, + /// Directory where to put result, THIS WILL BE DELETED FIRST + pub build_dir: String, +} + +/// Struct containing command line options +#[derive(Clone)] +pub struct OptsBuilder { + /// Directory where templates are, will be relative to CWD + pub template_dir: String, + /// Directory where the content which will be recursively iterated is, will be relative to CWD + pub content_dir: String, + /// Directory where to put result, THIS WILL BE DELETED FIRST + pub build_dir: String, +} + +impl OptsBuilder { + pub fn build(self) -> Opts { + Opts { + template_dir: self.template_dir, + content_dir: self.content_dir, + build_dir: self.build_dir, + } + } +} + +/// Simple convenience macro for printing usage of the program and exiting without a stacktrace. +/// For some reason, having this as a function didn't always make the compiler recognize that +/// the program exited. +macro_rules! parseopts_panic { + ($progname:expr) => { + println!("Usage: {} [OPTIONS]\n", $progname); + println!("OPTIONS:"); + println!(" -h, --help Show this help text"); + println!(" See also 'man skaldpress'"); + println!(" -s, --source DIR Location of content to compile"); + println!(" -t, --templates DIR Location of templates"); + println!(" -o, --out DIR Location to write compiled data, THIS WILL BE DELETED AND RECREATED!"); + std::process::exit(1); + }; +} + +/// Parse a single named option/argument, and update the Opts struct accordingly +/// +/// # Arguments +/// +/// * `opts` - The opts struct to modify +/// * `arg` - The name of the option/argument to read (without the -) +/// * `value` - Optionally the value of the option/argument. This function will panic if not +/// provided when it is required. +/// * `progname` - The first argument of the program, this is used for error messages. +pub fn parseopt(opts: &mut OptsBuilder, arg: &str, value: Option<String>, progname: &str) { + match arg { + "o" | "out" => { + let Some(out) = value else { + println!("Missing value for {}\n", arg); + parseopts_panic!(progname); + }; + opts.build_dir = out; + } + "s" | "source" => { + let Some(source) = value else { + println!("Missing value for {}\n", arg); + parseopts_panic!(progname); + }; + opts.content_dir = source; + } + "t" | "templates" => { + let Some(templates) = value else { + println!("Missing value for {}\n", arg); + parseopts_panic!(progname); + }; + opts.template_dir = templates; + } + "help" => { + parseopts_panic!(progname); + } + opt => { + println!("Unknown option \"{}\"\n", opt); + parseopts_panic!(progname); + } + } +} + +/// Parse command line options passed to binary +/// Very rudimentary argument parser, which allows for the most part the standard convention +/// of unix style command line arguments. +/// This function is specialised for the SkaldPress program, +/// but is easily adaptable for other programs as well. +pub fn parseopts() -> OptsBuilder { + let mut opts = OptsBuilder { + build_dir: String::from("build/"), + template_dir: String::from("templates/"), + content_dir: String::from("content/"), + }; + + let mut it = std::env::args(); + let progname = it.next().expect("SP12"); + + let pos_arg = 0; + + while let Some(mut arg) = it.next() { + if arg.starts_with("--") { + arg.remove(0); + arg.remove(0); + + let arg_name; + let mut arg_value = None; + if arg.contains('=') { + let mut ita = arg.splitn(2, '='); + arg_name = ita.next().expect("SP12").to_string(); + arg_value = Some(ita.next().expect("SP13").to_string()); + } else { + arg_name = arg.clone(); + match arg_name.as_str() { + "out" | "source" | "templates" => { + arg_value = it.next(); + } + _ => (), + } + } + parseopt(&mut opts, &arg_name, arg_value, &progname); + } else if arg.starts_with("-") { + arg.remove(0); + for arg_name in arg.chars() { + match arg_name { + 'o' | 's' | 't' => { + parseopt(&mut opts, &arg_name.to_string(), it.next(), &progname); + } + _ => { + parseopt(&mut opts, &arg_name.to_string(), None, &progname); + } + } + } + } else { + println!( + "No positional argument expected at position {} (\"{}\")\n", + pos_arg, arg + ); + parseopts_panic!(progname); + } + } + + return opts; +} |