summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/skaldpress/main.rs92
-rw-r--r--src/skaldpress/mod.rs1
-rw-r--r--src/skaldpress/parseopts.rs150
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;
+}