diff --git a/Cargo.lock b/Cargo.lock index e2d0909..5787b37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -150,7 +150,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d" dependencies = [ "byteorder", - "serde", + "serde 1.0.118", ] [[package]] @@ -235,7 +235,7 @@ checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" dependencies = [ "libc", "num-integer", - "num-traits", + "num-traits 0.2.14", "time", "winapi 0.3.9", ] @@ -292,6 +292,22 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "config" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3" +dependencies = [ + "lazy_static", + "nom", + "rust-ini", + "serde 1.0.118", + "serde-hjson", + "serde_json", + "toml", + "yaml-rust", +] + [[package]] name = "console" version = "0.13.0" @@ -820,7 +836,7 @@ dependencies = [ "log 0.4.11", "rand 0.7.3", "regex", - "serde", + "serde 1.0.118", "serde_derive", "serde_json", "tempfile", @@ -992,7 +1008,7 @@ dependencies = [ "jpeg-decoder", "num-iter", "num-rational", - "num-traits", + "num-traits 0.2.14", "png", "scoped_threadpool", "tiff", @@ -1117,6 +1133,19 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "lexical-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if 0.1.10", + "ryu", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.81" @@ -1132,6 +1161,16 @@ dependencies = [ "safemem", ] +[[package]] +name = "linked-hash-map" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" +dependencies = [ + "serde 0.8.23", + "serde_test", +] + [[package]] name = "linked-hash-map" version = "0.5.3" @@ -1314,6 +1353,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "lexical-core", + "memchr", + "version_check 0.9.2", +] + [[package]] name = "notify" version = "4.0.15" @@ -1339,7 +1389,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg 1.0.1", - "num-traits", + "num-traits 0.2.14", ] [[package]] @@ -1350,7 +1400,7 @@ checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" dependencies = [ "autocfg 1.0.1", "num-integer", - "num-traits", + "num-traits 0.2.14", ] [[package]] @@ -1361,7 +1411,16 @@ checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" dependencies = [ "autocfg 1.0.1", "num-integer", - "num-traits", + "num-traits 0.2.14", +] + +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.14", ] [[package]] @@ -1600,7 +1659,7 @@ dependencies = [ "chrono", "indexmap", "line-wrap", - "serde", + "serde 1.0.118", "xml-rs", ] @@ -1937,7 +1996,7 @@ dependencies = [ "native-tls", "percent-encoding 2.1.0", "pin-project-lite 0.2.0", - "serde", + "serde 1.0.118", "serde_urlencoded", "tokio", "tokio-tls", @@ -1961,6 +2020,12 @@ dependencies = [ "crossbeam-utils 0.8.1", ] +[[package]] +name = "rust-ini" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" + [[package]] name = "rustc-demangle" version = "0.1.18" @@ -2045,6 +2110,12 @@ dependencies = [ "libc", ] +[[package]] +name = "serde" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" + [[package]] name = "serde" version = "1.0.118" @@ -2054,6 +2125,19 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-hjson" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" +dependencies = [ + "lazy_static", + "linked-hash-map 0.3.0", + "num-traits 0.1.43", + "regex", + "serde 0.8.23", +] + [[package]] name = "serde_derive" version = "1.0.118" @@ -2073,7 +2157,16 @@ checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779" dependencies = [ "itoa", "ryu", - "serde", + "serde 1.0.118", +] + +[[package]] +name = "serde_test" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5" +dependencies = [ + "serde 0.8.23", ] [[package]] @@ -2085,7 +2178,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde", + "serde 1.0.118", ] [[package]] @@ -2135,6 +2228,7 @@ dependencies = [ "charred", "chrono", "colored", + "config", "crossbeam-utils 0.7.2", "env_logger 0.7.1", "failure", @@ -2155,8 +2249,7 @@ dependencies = [ "rayon", "regex", "reqwest", - "serde", - "serde_derive", + "serde 1.0.118", "sha2", "structopt", "syntect", @@ -2175,6 +2268,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.7.0" @@ -2260,7 +2359,7 @@ dependencies = [ "onig", "plist", "regex-syntax", - "serde", + "serde 1.0.118", "serde_derive", "serde_json", "walkdir", @@ -2403,7 +2502,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" dependencies = [ - "serde", + "serde 1.0.118", ] [[package]] @@ -2614,7 +2713,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e" dependencies = [ "cfg-if 1.0.0", - "serde", + "serde 1.0.118", "serde_json", "wasm-bindgen-macro", ] @@ -2824,5 +2923,5 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" dependencies = [ - "linked-hash-map", + "linked-hash-map 0.5.3", ] diff --git a/Cargo.toml b/Cargo.toml index 52283ca..d057dff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,9 +36,8 @@ colored = "1.9.3" gh-emoji = "1.0.3" notify = "4.0.12" toml = "0.5.6" -serde ="1.0.111" -serde_derive = "1.0.111" -reqwest = {version = "0.10", features=["blocking"]} +serde = { version = "1.0.111", features = ["serde_derive"] } +reqwest = { version = "0.10", features = ["blocking"] } mime_guess = "2.0.3" mime = "0.3.16" base64 = "0.12.3" @@ -51,6 +50,7 @@ platform-dirs = "0.2.0" image = "0.23.12" parking_lot = "0.11.1" sha2 = "0.9.2" +config = "0.10.1" -headless_chrome = {version = "0.9.0", optional = true} -failure = {version = "0.1.8", optional = true} +headless_chrome = { version = "0.9.0", optional = true } +failure = { version = "0.1.8", optional = true } diff --git a/README.md b/README.md index 6206761..f17c09f 100644 --- a/README.md +++ b/README.md @@ -197,90 +197,107 @@ Placeholder ``` Metadata can also be defined in a separate toml file with simple key-value pairs. +The file `Manifest.toml` will always be included by default. Example: ```toml -# bibliography.bib.toml -author = "Snek" -published = "2020" -test-key = ["test value", "test value 2"] - -# those files won't get imported -ignored-imports = ["style.css"] - -# stylesheets that should be included -included-stylesheets = ["style2.css"] +# document metadata +[metadata] +# language setting of the document +language = 'en' -# other metadata files that should be included -included-configs = [] +# author of the document +author = 'author' -# bibliography that should be included -included-bibliography = ["mybib.toml"] -# glossary that sould be included -included-glossary = ["myglossary.toml"] +# features used in the document +[features] # if external sources (images, stylesheets, MathJax) # should be embedded into the document (default: true) -embed-external = true +embed_external = true # If SmartArrows should be used (default: true) -smart-arrows = true - -# Includes a MathJax script tag in the document to render MathML in chromium. -# (default: true) -include-math-jax = true +smart_arrows = true +include_mathjax = true -### Image processing options ### +[imports] +# those files won't get imported +ignored_imports = [] -# Force convert images to the specified format. -# Supported formats are png, jpeg, gif, bmp, (ico needs size <= 256), avif, pnm -# (default: keep original) -image-format = "jpg" +# stylesheets that should be included +included_stylesheets = ['style.css'] -# the max width for the images. -# if an image is larger than that it get's resized. -# (default: none) -image-max-width = 700 +# bibliography that should be included +included_bibliography = ['Bibliography.toml'] -# the max width for the images. -# if an image is larger than that it get's resized. -# (default: none) -image-max-height = 800 +# glossary that sould be included +included_glossaries = ['Glossary.toml'] -### PDF Options - needs the pdf feature enabled ### +# settings related to pdf rendering +[pdf] # If the header and footer of the pdf should be displayed (default: true) -pdf-display-header-footer = true +display_header_footer = true -# PDF header template of each page (default: empty) -pdf-header-template = "
" +# PDF header template of each page (default: '
') +header_template = '
' # PDF footer template of each page (default: see chromium_pdf assets) -pdf-footer-template = "
" +footer_template = ''' +
+ / +
''' -# Top margin of the pdf. Should be between 0 and 1. (default: 1.0) -pdf-margin-top = 1 +# The scale at which the website is rendered into pdf. +page_scale = 1.0 + +# margin of the pdf document +[pdf.margin] + +# Top margin of the pdf. Should be between 0 and 1. (default: 0.5) +top = 0.5 -# Bottom margin of the pdf. Should be between 0 and 1. (default: 1.0) -pdf-margin-bottom = 1 +# Bottom margin of the pdf. Should be between 0 and 1. (default: 0.5) +bottom = 0.5 # Left margin of the pdf. Should be between 0 and 1. -pdf-margin-left = 0 +left = 0 # Right margin of the pdf. Should be between 0 and 1. -pdf-margin-right = 0 +right = 0 -# Page height of the pdf -pdf-page-height = 100 -# Page width of the pdf -pdf-page-width = 80 +# image settings +[images] -# The scale at which the website is rendered into pdf. -pdf-page-scale = 1.0 +# Force convert images to the specified format. +# Supported formats are png, jpeg, gif, bmp, (ico needs size <= 256), avif, pnm +# (default: keep original) +format = "png" + +# the max width for the images. +# if an image is larger than that it get's resized. +# (default: none) +max_width = 700 + +# the max width for the images. +# if an image is larger than that it get's resized. +# (default: none) +max_height = 500 + + +[formatting] +# how bibliography references should be displayed +bib_ref_display = '{{number}}' + + +# custom metadata +# String -> String Mappings +[custom_attributes] +custom_key1 = "Custom Value" ``` The `[Section]` keys are not relevant as the structure gets flattened before the values are read. diff --git a/src/elements/mod.rs b/src/elements/mod.rs index da3363a..d64c704 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -1,13 +1,10 @@ pub mod tokens; use crate::format::PlaceholderTemplate; -use crate::references::configuration::keys::{ - EMBED_EXTERNAL, IMAGE_FORMAT, IMAGE_MAX_HEIGHT, IMAGE_MAX_WIDTH, -}; -use crate::references::configuration::{ConfigRefEntry, Configuration, Value}; use crate::references::glossary::{GlossaryManager, GlossaryReference}; use crate::references::placeholders::ProcessPlaceholders; use crate::references::templates::{Template, TemplateVariable}; +use crate::settings::Settings; use crate::utils::downloads::{DownloadManager, PendingDownload}; use crate::utils::image_converting::{ImageConverter, PendingImage}; use asciimath_rs::elements::special::Expression; @@ -18,7 +15,6 @@ use image::ImageFormat; use mime::Mime; use parking_lot::Mutex; use std::collections::HashMap; -use std::iter::FromIterator; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; @@ -77,7 +73,7 @@ pub struct Document { pub(crate) is_root: bool, pub(crate) path: Option, pub(crate) placeholders: Vec>>, - pub config: Configuration, + pub config: Arc>, pub bibliography: BibManager, pub downloads: Arc>, pub images: Arc>, @@ -318,7 +314,7 @@ impl Document { is_root: true, path: None, placeholders: Vec::new(), - config: Configuration::default(), + config: Arc::new(Mutex::new(Settings::default())), bibliography: BibManager::new(), stylesheets: Vec::new(), downloads: Arc::new(Mutex::new(DownloadManager::new())), @@ -445,41 +441,24 @@ impl Document { fn process_media(&self) { let downloads = Arc::clone(&self.downloads); - if let Some(Value::Bool(embed)) = self - .config - .get_entry(EMBED_EXTERNAL) - .map(|e| e.get().clone()) - { - if embed { - downloads.lock().download_all(); - } - } else { + if self.config.lock().features.embed_external { downloads.lock().download_all(); } - if let Some(Value::String(s)) = self.config.get_entry(IMAGE_FORMAT).map(|e| e.get().clone()) - { + if let Some(s) = &self.config.lock().images.format { if let Some(format) = ImageFormat::from_extension(s) { self.images.lock().set_target_format(format); } } - let mut image_width = -1; - let mut image_height = -1; - - if let Some(Value::Integer(i)) = self - .config - .get_entry(IMAGE_MAX_WIDTH) - .map(|v| v.get().clone()) - { + let mut image_width = 0; + let mut image_height = 0; + + if let Some(i) = self.config.lock().images.max_width { image_width = i; image_height = i; } - if let Some(Value::Integer(i)) = self - .config - .get_entry(IMAGE_MAX_HEIGHT) - .map(|v| v.get().clone()) - { + if let Some(i) = self.config.lock().images.max_height { image_height = i; - if image_width < 0 { + if image_width <= 0 { image_width = i; } } @@ -766,19 +745,6 @@ impl Metadata for InlineMetadata { } } -impl Into> for InlineMetadata { - fn into(self) -> HashMap { - HashMap::from_iter(self.data.iter().filter_map(|(k, v)| match v { - MetadataValue::String(s) => Some((k.clone(), Value::String(s.clone()))), - MetadataValue::Bool(b) => Some((k.clone(), Value::Bool(*b))), - MetadataValue::Integer(i) => Some((k.clone(), Value::Integer(*i))), - MetadataValue::Float(f) => Some((k.clone(), Value::Float(*f))), - MetadataValue::Template(t) => Some((k.clone(), Value::Template(t.clone()))), - _ => None, - })) - } -} - impl Image { pub fn get_content(&self) -> Option> { let mut data = None; @@ -802,15 +768,11 @@ pub struct BibEntry { pub struct BibReference { pub(crate) key: String, pub(crate) entry_anchor: Arc>, - pub(crate) display: Option, + pub(crate) display: Option, } impl BibReference { - pub fn new( - key: String, - display: Option, - anchor: Arc>, - ) -> Self { + pub fn new(key: String, display: Option, anchor: Arc>) -> Self { Self { key: key.to_string(), display, @@ -823,8 +785,7 @@ impl BibReference { let entry = entry.lock(); if let Some(display) = &self.display { - let display = display.read().unwrap(); - let mut template = PlaceholderTemplate::new(display.get().as_string()); + let mut template = PlaceholderTemplate::new(display.clone()); let mut value_map = HashMap::new(); value_map.insert("key".to_string(), entry.key()); diff --git a/src/format/chromium_pdf/mod.rs b/src/format/chromium_pdf/mod.rs index 748d8a1..440311b 100644 --- a/src/format/chromium_pdf/mod.rs +++ b/src/format/chromium_pdf/mod.rs @@ -2,19 +2,16 @@ use crate::elements::Document; use crate::format::chromium_pdf::result::{PdfRenderingError, PdfRenderingResult}; use crate::format::html::html_writer::HTMLWriter; use crate::format::html::to_html::ToHtml; -use crate::references::configuration::keys::{ - INCLUDE_MATHJAX, PDF_DISPLAY_HEADER_FOOTER, PDF_FOOTER_TEMPLATE, PDF_HEADER_TEMPLATE, - PDF_MARGIN_BOTTOM, PDF_MARGIN_LEFT, PDF_MARGIN_RIGHT, PDF_MARGIN_TOP, PDF_PAGE_HEIGHT, - PDF_PAGE_SCALE, PDF_PAGE_WIDTH, -}; -use crate::references::configuration::Configuration; +use crate::settings::Settings; use crate::utils::caching::CacheStorage; +use bibliographix::Mutex; use headless_chrome::protocol::page::PrintToPdfOptions; use headless_chrome::{Browser, LaunchOptionsBuilder, Tab}; use std::fs; use std::fs::OpenOptions; use std::io::BufWriter; use std::path::PathBuf; +use std::sync::Arc; use std::thread; use std::time::{Duration, Instant}; @@ -25,14 +22,8 @@ pub fn render_to_pdf(document: Document) -> PdfRenderingResult> { let cache = CacheStorage::new(); let mut file_path = PathBuf::from(format!("tmp-document.html")); file_path = cache.get_file_path(&file_path); - let mut mathjax = false; - - if let Some(entry) = document.config.get_entry(INCLUDE_MATHJAX) { - if entry.get().as_bool() == Some(true) { - mathjax = true; - } - } let config = document.config.clone(); + let mathjax = config.lock().features.include_mathjax; let handle = thread::spawn({ let file_path = file_path.clone(); @@ -107,49 +98,23 @@ fn wait_for_mathjax(tab: &Tab, timeout: Duration) -> PdfRenderingResult<()> { Ok(()) } -fn get_pdf_options(config: Configuration) -> PrintToPdfOptions { +fn get_pdf_options(config: Arc>) -> PrintToPdfOptions { + let config = config.lock().pdf.clone(); PrintToPdfOptions { landscape: None, - display_header_footer: config - .get_entry(PDF_DISPLAY_HEADER_FOOTER) - .and_then(|value| value.get().as_bool()), + display_header_footer: Some(config.display_header_footer), print_background: Some(true), - scale: config - .get_entry(PDF_PAGE_SCALE) - .and_then(|value| value.get().as_float()) - .map(|value| value as f32), - paper_width: config - .get_entry(PDF_PAGE_WIDTH) - .and_then(|value| value.get().as_float()) - .map(|value| value as f32), - paper_height: config - .get_entry(PDF_PAGE_HEIGHT) - .and_then(|value| value.get().as_float()) - .map(|value| value as f32), - margin_top: config - .get_entry(PDF_MARGIN_TOP) - .and_then(|value| value.get().as_float()) - .map(|f| f as f32), - margin_bottom: config - .get_entry(PDF_MARGIN_BOTTOM) - .and_then(|value| value.get().as_float()) - .map(|f| f as f32), - margin_left: config - .get_entry(PDF_MARGIN_LEFT) - .and_then(|value| value.get().as_float()) - .map(|f| f as f32), - margin_right: config - .get_entry(PDF_MARGIN_RIGHT) - .and_then(|value| value.get().as_float()) - .map(|f| f as f32), + scale: Some(config.page_scale), + paper_width: config.page_width, + paper_height: config.page_height, + margin_top: config.margin.top, + margin_bottom: config.margin.bottom, + margin_left: config.margin.left, + margin_right: config.margin.right, page_ranges: None, ignore_invalid_page_ranges: None, - header_template: config - .get_entry(PDF_HEADER_TEMPLATE) - .map(|value| value.get().as_string()), - footer_template: config - .get_entry(PDF_FOOTER_TEMPLATE) - .map(|value| value.get().as_string()), + header_template: config.header_template, + footer_template: config.footer_template, prefer_css_page_size: None, } } diff --git a/src/format/html/to_html.rs b/src/format/html/to_html.rs index 28a6f95..22d8e15 100644 --- a/src/format/html/to_html.rs +++ b/src/format/html/to_html.rs @@ -1,7 +1,6 @@ use crate::elements::*; use crate::format::html::html_writer::HTMLWriter; use crate::format::PlaceholderTemplate; -use crate::references::configuration::keys::{INCLUDE_MATHJAX, META_LANG}; use crate::references::glossary::{GlossaryDisplay, GlossaryReference}; use crate::references::templates::{Template, TemplateVariable}; use asciimath_rs::format::mathml::ToMathML; @@ -107,11 +106,8 @@ impl ToHtml for Document { }; if self.is_root { - let language = self - .config - .get_entry(META_LANG) - .map(|e| e.get().as_string()) - .unwrap_or("en".to_string()); + let language = self.config.lock().metadata.language.clone(); + let style = minify(std::include_str!("assets/style.css")); writer.write("".to_string())?; writer.write("", MATHJAX_URL diff --git a/src/lib.rs b/src/lib.rs index 81e8702..5f5aa8b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ pub mod elements; pub mod format; pub mod parser; pub mod references; +pub mod settings; pub mod utils; pub use parser::Parser; diff --git a/src/main.rs b/src/main.rs index 288e5a9..d50159d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ use snekdown::elements::Document; use snekdown::format::html::html_writer::HTMLWriter; use snekdown::format::html::to_html::ToHtml; use snekdown::parser::ParserOptions; +use snekdown::settings::Settings; use snekdown::utils::caching::CacheStorage; use snekdown::Parser; use std::fs::{File, OpenOptions}; @@ -31,6 +32,9 @@ enum SubCommand { /// Parse and render the document. Render(RenderOptions), + /// Initializes the project with default settings + Init, + /// Clears the cache directory ClearCache, } @@ -94,6 +98,7 @@ fn main() { let cache = CacheStorage::new(); cache.clear().expect("Failed to clear cache"); } + SubCommand::Init => init(), }; } @@ -107,6 +112,31 @@ fn get_level_style(level: Level) -> colored::Color { } } +fn init() { + let settings = Settings::default(); + let settings_string = toml::to_string_pretty(&settings).unwrap(); + let manifest_path = PathBuf::from("Manifest.toml"); + let bibliography_path = PathBuf::from("Bibliography.toml"); + let glossary_path = PathBuf::from("Glossary.toml"); + + if !manifest_path.exists() { + let mut file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open("Manifest.toml") + .unwrap(); + file.write_all(settings_string.as_bytes()).unwrap(); + file.flush().unwrap(); + } + if !bibliography_path.exists() { + File::create("Bibliography.toml".to_string()).unwrap(); + } + if !glossary_path.exists() { + File::create("Glossary.toml".to_string()).unwrap(); + } +} + /// Watches a file with all of its imports and renders on change fn watch(opt: &WatchOptions) { let parser = render(&opt.render_options); diff --git a/src/parser/block.rs b/src/parser/block.rs index bcb0c1a..c85683a 100644 --- a/src/parser/block.rs +++ b/src/parser/block.rs @@ -1,7 +1,7 @@ use super::ParseResult; use crate::elements::tokens::*; use crate::elements::{ - Block, CodeBlock, Import, List, ListItem, MathBlock, Paragraph, Quote, Section, Table, + Block, CodeBlock, Import, List, ListItem, MathBlock, Metadata, Paragraph, Quote, Section, Table, }; use crate::parser::inline::ParseInline; use crate::parser::line::ParseLine; @@ -26,7 +26,7 @@ impl ParseBlock for Parser { fn parse_block(&mut self) -> ParseResult { if let Some(section) = self.section_return { if section <= self.section_nesting && (self.section_nesting > 0) { - return Err(self.ctm.assert_error(None)); + return Err(self.ctm.assert_error(None).into()); } else { self.section_return = None; } @@ -35,7 +35,7 @@ impl ParseBlock for Parser { log::trace!("Block::Section"); Block::Section(section) } else if let Some(_) = self.section_return { - return Err(self.ctm.err()); + return Err(self.ctm.err().into()); } else if let Ok(list) = self.parse_list() { log::trace!("Block::List"); Block::List(list) @@ -60,7 +60,7 @@ impl ParseBlock for Parser { Block::Null } } else if let Some(_) = self.section_return { - return Err(self.ctm.err()); + return Err(self.ctm.err().into()); } else if let Ok(pholder) = self.parse_placeholder() { log::trace!("Block::Placeholder"); Block::Placeholder(pholder) @@ -68,7 +68,7 @@ impl ParseBlock for Parser { log::trace!("Block::Paragraph"); Block::Paragraph(paragraph) } else { - return Err(self.ctm.err()); + return Err(self.ctm.err().into()); }; Ok(token) @@ -94,7 +94,7 @@ impl ParseBlock for Parser { if size <= self.section_nesting { self.section_return = Some(size); } - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } self.ctm.seek_any(&INLINE_WHITESPACE)?; let mut header = self.parse_header()?; @@ -117,7 +117,7 @@ impl ParseBlock for Parser { } Ok(section) } else { - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } } @@ -167,7 +167,7 @@ impl ParseBlock for Parser { }; if self.ctm.check_char(&META_CLOSE) { if self.ctm.next_char() == None { - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } } let mut quote = Quote::new(metadata); @@ -186,7 +186,7 @@ impl ParseBlock for Parser { } } if quote.text.len() == 0 { - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } Ok(quote) @@ -213,7 +213,7 @@ impl ParseBlock for Parser { if paragraph.elements.len() > 0 { Ok(paragraph) } else { - Err(self.ctm.err()) + Err(self.ctm.err().into()) } } @@ -273,7 +273,7 @@ impl ParseBlock for Parser { if list.items.len() > 0 { Ok(list) } else { - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } } @@ -319,7 +319,7 @@ impl ParseBlock for Parser { path.push(character); } if self.ctm.check_char(&LB) || path.is_empty() { - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } if self.ctm.check_char(&IMPORT_CLOSE) { self.ctm.seek_one()?; @@ -328,12 +328,12 @@ impl ParseBlock for Parser { if self.section_nesting > 0 { self.section_return = Some(0); - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } let metadata = self .parse_inline_metadata() .ok() - .map(|m| m.into()) + .map(|m| m.get_string_map()) .unwrap_or(HashMap::new()); self.ctm.seek_whitespace(); @@ -343,7 +343,7 @@ impl ParseBlock for Parser { ImportType::Stylesheet(_) => Ok(None), ImportType::Bibliography(_) => Ok(None), ImportType::Manifest(_) => Ok(None), - _ => Err(self.ctm.err()), + _ => Err(self.ctm.err().into()), } } } diff --git a/src/parser/inline.rs b/src/parser/inline.rs index 39bd2d6..5be3fa6 100644 --- a/src/parser/inline.rs +++ b/src/parser/inline.rs @@ -3,7 +3,6 @@ use crate::elements::tokens::*; use crate::elements::BibReference; use crate::elements::*; use crate::parser::block::ParseBlock; -use crate::references::configuration::keys::{BIB_REF_DISPLAY, SMART_ARROWS}; use crate::references::glossary::GlossaryDisplay; use crate::references::glossary::GlossaryReference; use crate::references::templates::{GetTemplateVariables, Template, TemplateVariable}; @@ -48,11 +47,12 @@ impl ParseInline for Parser { self.ctm.assert_char(surrounding, Some(start_index))?; self.ctm.seek_one()?; let mut inline = vec![self.parse_inline()?]; + while !self.ctm.check_char(surrounding) { if let Ok(result) = self.parse_inline() { inline.push(result) } else { - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } } if !self.ctm.check_eof() { @@ -71,10 +71,10 @@ impl ParseInline for Parser { } } if self.ctm.check_char(&PIPE) || self.ctm.check_char(&LB) { - Err(self.ctm.err()) + Err(self.ctm.err().into()) } else if self.ctm.check_eof() { log::trace!("EOF"); - Err(self.ctm.err()) + Err(self.ctm.err().into()) } else if let Ok(image) = self.parse_image() { log::trace!("Inline::Image {:?}", image); Ok(Inline::Image(image)) @@ -160,7 +160,7 @@ impl ParseInline for Parser { image_data: pending_image, }) } else { - Err(self.ctm.rewind_with_error(start_index)) + Err(self.ctm.rewind_with_error(start_index).into()) } } @@ -185,7 +185,7 @@ impl ParseInline for Parser { self.inline_break_at.pop(); self.ctm.seek_one()?; } else if !short_syntax { - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } self.ctm.assert_char(&URL_OPEN, Some(start_index))?; self.ctm.seek_one()?; @@ -218,7 +218,7 @@ impl ParseInline for Parser { } else if self.ctm.check_char(&SPACE) { false } else { - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); }; self.ctm.seek_one()?; self.ctm.assert_char(&CHECK_CLOSE, Some(start_index))?; @@ -233,11 +233,12 @@ impl ParseInline for Parser { self.ctm.assert_sequence(&BOLD, Some(start_index))?; self.ctm.seek_one()?; let mut inline = vec![self.parse_inline()?]; + while !self.ctm.check_sequence(&BOLD) { if let Ok(result) = self.parse_inline() { inline.push(result); } else { - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } } self.ctm.seek_one()?; @@ -261,12 +262,12 @@ impl ParseInline for Parser { if let Ok(result) = self.parse_inline() { inline.push(result); } else { - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } } self.ctm.rewind(self.ctm.get_index() - STRIKED.len()); if self.ctm.check_any(WHITESPACE) { - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } for _ in 0..(STRIKED.len() + 1) { self.ctm.seek_one()?; @@ -330,7 +331,7 @@ impl ParseInline for Parser { name, }) } else { - Err(self.ctm.rewind_with_error(start_index)) + Err(self.ctm.rewind_with_error(start_index).into()) } } @@ -347,7 +348,7 @@ impl ParseInline for Parser { )?; self.ctm.seek_one()?; if color.is_empty() { - return Err(self.ctm.err()); + return Err(self.ctm.err().into()); } Ok(Colored { value: Box::new(self.parse_inline()?), @@ -367,7 +368,15 @@ impl ParseInline for Parser { let bib_ref = BibRef::new(key.clone()); let ref_entry = Arc::new(RwLock::new(BibReference::new( key, - self.options.document.config.get_ref_entry(BIB_REF_DISPLAY), + Some( + self.options + .document + .config + .lock() + .formatting + .bib_ref_display + .clone(), + ), bib_ref.anchor(), ))); self.options @@ -422,7 +431,7 @@ impl ParseInline for Parser { self.ctm .get_string_until_any_or_rewind(&WHITESPACE, &[TILDE], start_index)?; if key.is_empty() { - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } while !key.is_empty() && !key.chars().last().unwrap().is_alphabetic() { self.ctm.rewind(self.ctm.get_index() - 1); @@ -441,7 +450,7 @@ impl ParseInline for Parser { /// parses plain text as a string until it encounters an unescaped special inline char fn parse_plain(&mut self) -> ParseResult { if self.ctm.check_char(&LB) { - return Err(self.ctm.err()); + return Err(self.ctm.err().into()); } let mut characters = String::new(); if !self.ctm.check_char(&SPECIAL_ESCAPE) { @@ -466,7 +475,7 @@ impl ParseInline for Parser { if characters.len() > 0 { Ok(PlainText { value: characters }) } else { - Err(self.ctm.err()) + Err(self.ctm.err().into()) } } @@ -490,7 +499,7 @@ impl ParseInline for Parser { if values.len() == 0 { // if there was a linebreak (the metadata wasn't closed) or there is no inner data // return an error - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } Ok(InlineMetadata { data: values }) @@ -566,7 +575,7 @@ impl ParseInline for Parser { { name_str } else { - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); }; if !self.ctm.check_eof() { self.ctm.seek_one()?; @@ -590,7 +599,7 @@ impl ParseInline for Parser { self.ctm.seek_one()?; if self.ctm.check_char(&TEMPLATE) { - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } let mut elements = Vec::new(); @@ -645,15 +654,8 @@ impl ParseInline for Parser { /// Parses an arrow fn parse_arrow(&mut self) -> ParseResult<Arrow> { - if !self - .options - .document - .config - .get_entry(SMART_ARROWS) - .and_then(|e| e.get().as_bool()) - .unwrap_or(true) - { - Err(self.ctm.err()) + if !self.options.document.config.lock().features.smart_arrows { + Err(self.ctm.err().into()) } else if self.ctm.check_sequence(A_LEFT_RIGHT_ARROW) { self.ctm.seek_one()?; Ok(Arrow::LeftRightArrow) @@ -673,7 +675,7 @@ impl ParseInline for Parser { self.ctm.seek_one()?; Ok(Arrow::BigLeftArrow) } else { - Err(self.ctm.err()) + Err(self.ctm.err().into()) } } } diff --git a/src/parser/line.rs b/src/parser/line.rs index 0a50eb2..151224e 100644 --- a/src/parser/line.rs +++ b/src/parser/line.rs @@ -25,7 +25,7 @@ impl ParseLine for Parser { fn parse_line(&mut self) -> ParseResult<Line> { if self.ctm.check_eof() { log::trace!("EOF"); - Err(self.ctm.err()) + Err(self.ctm.err().into()) } else { if let Ok(ruler) = self.parse_ruler() { log::trace!("Line::Ruler"); @@ -40,7 +40,7 @@ impl ParseLine for Parser { log::trace!("Line::Text"); Ok(Line::Text(text)) } else { - Err(self.ctm.err()) + Err(self.ctm.err().into()) } } } @@ -76,11 +76,11 @@ impl ParseLine for Parser { } if !self.ctm.check_any(&INLINE_WHITESPACE) { - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } self.ctm.seek_any(&INLINE_WHITESPACE)?; if self.ctm.check_char(&MINUS) { - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } let item = ListItem::new(self.parse_line()?, level as u16, ordered); @@ -96,7 +96,7 @@ impl ParseLine for Parser { self.ctm.assert_char(&PIPE, Some(start_index))?; self.ctm.seek_one()?; if self.ctm.check_char(&PIPE) { - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } self.inline_break_at.push(PIPE); @@ -134,7 +134,7 @@ impl ParseLine for Parser { log::trace!("Line::TableRow"); Ok(row) } else { - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } } @@ -181,7 +181,7 @@ impl ParseLine for Parser { if text.subtext.len() > 0 { Ok(text) } else { - Err(self.ctm.err()) + Err(self.ctm.err().into()) } } @@ -211,7 +211,7 @@ impl ParseLine for Parser { msg, self.get_position_string() ); - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } } } else { @@ -232,7 +232,7 @@ impl ParseLine for Parser { msg, self.get_position_string() ); - return Err(self.ctm.rewind_with_error(start_index)); + return Err(self.ctm.rewind_with_error(start_index).into()); } } }; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 7085440..f082081 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5,31 +5,54 @@ pub(crate) mod line; use self::block::ParseBlock; use crate::elements::tokens::LB; use crate::elements::{Document, ImportAnchor}; -use crate::references::configuration::keys::{ - IMP_BIBLIOGRAPHY, IMP_CONFIGS, IMP_GLOSSARY, IMP_IGNORE, IMP_STYLESHEETS, -}; -use crate::references::configuration::Value; -use charred::tapemachine::{CharTapeMachine, TapeError, TapeResult}; +use crate::settings::SettingsError; +use charred::tapemachine::{CharTapeMachine, TapeError}; use crossbeam_utils::sync::WaitGroup; use regex::Regex; use std::collections::HashMap; +use std::fmt; use std::fs::{read_to_string, File}; -use std::io::BufReader; +use std::io::{self, BufReader}; use std::path::PathBuf; use std::sync::{Arc, Mutex, RwLock}; use std::thread; -pub type ParseResult<T> = TapeResult<T>; -pub type ParseError = TapeError; +pub type ParseResult<T> = Result<T, ParseError>; -const DEFAULT_IMPORTS: &'static [(&str, &str)] = &[ - ("snekdown.toml", "manifest"), - ("manifest.toml", "manifest"), - ("bibliography.toml", "bibliography"), - ("bibliography2.bib.toml", "bibliography"), - ("glossary.toml", "glossary"), - ("style.css", "stylesheet"), -]; +#[derive(Debug)] +pub enum ParseError { + TapeError(TapeError), + SettingsError(SettingsError), + IoError(io::Error), +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ParseError::TapeError(e) => write!(f, "{}", e), + ParseError::SettingsError(e) => write!(f, "{}", e), + ParseError::IoError(e) => write!(f, "IO Error: {}", e), + } + } +} + +impl From<TapeError> for ParseError { + fn from(e: TapeError) -> Self { + Self::TapeError(e) + } +} + +impl From<SettingsError> for ParseError { + fn from(e: SettingsError) -> Self { + Self::SettingsError(e) + } +} + +impl From<io::Error> for ParseError { + fn from(e: io::Error) -> Self { + Self::IoError(e) + } +} #[derive(Clone, Debug)] pub struct ParserOptions { @@ -150,7 +173,7 @@ impl Parser { path.to_str().unwrap(), self.get_position_string(), ); - return Err(self.ctm.assert_error(None)); + return Err(self.ctm.assert_error(None).into()); } let anchor = Arc::new(RwLock::new(ImportAnchor::new())); let anchor_clone = Arc::clone(&anchor); @@ -181,7 +204,7 @@ impl Parser { /// Returns the text of an imported text file fn import_text_file(&self, path: PathBuf) -> ParseResult<String> { - read_to_string(path).map_err(|_| self.ctm.err()) + read_to_string(path).map_err(ParseError::from) } fn import_stylesheet(&mut self, path: PathBuf) -> ParseResult<()> { @@ -197,13 +220,12 @@ impl Parser { } fn import_manifest(&mut self, path: PathBuf) -> ParseResult<()> { - let contents = self.import_text_file(path)?; - let value = contents - .parse::<toml::Value>() - .map_err(|_| self.ctm.err())?; - self.options.document.config.set_from_toml(&value); - - Ok(()) + self.options + .document + .config + .lock() + .merge(path) + .map_err(ParseError::from) } /// Imports a glossary @@ -223,7 +245,7 @@ impl Parser { } /// Imports a path - fn import(&mut self, path: String, args: &HashMap<String, Value>) -> ImportType { + fn import(&mut self, path: String, args: &HashMap<String, String>) -> ImportType { log::debug!( "Importing file {}\n\t--> {}\n", path, @@ -242,20 +264,9 @@ impl Parser { .file_name() .and_then(|f| Some(f.to_str().unwrap().to_string())) { - if let Some(Value::Array(ignore)) = self - .options - .document - .config - .get_entry(IMP_IGNORE) - .and_then(|e| Some(e.get().clone())) - { - let ignore = ignore - .iter() - .map(|v| v.as_string()) - .collect::<Vec<String>>(); - if ignore.contains(&fname) { - return ImportType::None; - } + let ignore = &self.options.document.config.lock().imports.ignored_imports; + if ignore.contains(&fname) { + return ImportType::None; } } { @@ -270,7 +281,7 @@ impl Parser { } paths.push(path.clone()); } - match args.get("type").map(|e| e.as_string().to_lowercase()) { + match args.get("type").cloned() { Some(s) if s == "stylesheet".to_string() => { ImportType::Stylesheet(self.import_stylesheet(path)) } @@ -328,14 +339,10 @@ impl Parser { let wg = self.wg.clone(); self.wg = WaitGroup::new(); if !self.options.is_child { - for (path, file_type) in DEFAULT_IMPORTS { - if self.transform_path(path.to_string()).exists() { - self.import( - path.to_string(), - &maplit::hashmap! {"type".to_string() => Value::String(file_type.to_string())}, - ); - } - } + self.import( + "Manifest.toml".to_string(), + &maplit::hashmap! {"type".to_string() => "manifest".to_string()}, + ); } wg.wait(); if !self.options.is_child { @@ -353,57 +360,25 @@ impl Parser { /// Imports files from the configs import values fn import_from_config(&mut self) { - if let Some(Value::Array(mut imp)) = self - .options - .document - .config - .get_entry(IMP_STYLESHEETS) - .and_then(|e| Some(e.get().clone())) - { - let args = - maplit::hashmap! {"type".to_string() => Value::String("stylesheet".to_string())}; - while let Some(Value::String(s)) = imp.pop() { - self.import(s, &args); - } - } - if let Some(Value::Array(mut imp)) = self - .options - .document - .config - .get_entry(IMP_CONFIGS) - .and_then(|e| Some(e.get().clone())) - { - let args = maplit::hashmap! {"type".to_string() => Value::String("config".to_string())}; - while let Some(Value::String(s)) = imp.pop() { - self.import(s, &args); - } + let config = Arc::clone(&self.options.document.config); + + let mut stylesheets = config.lock().imports.included_stylesheets.clone(); + let args = maplit::hashmap! {"type".to_string() => "stylesheet".to_string()}; + while let Some(s) = stylesheets.pop() { + self.import(s, &args); } - if let Some(Value::Array(mut imp)) = self - .options - .document - .config - .get_entry(IMP_BIBLIOGRAPHY) - .and_then(|e| Some(e.get().clone())) - { - let args = - maplit::hashmap! {"type".to_string() => Value::String("bibliography".to_string())}; - while let Some(Value::String(s)) = imp.pop() { - self.import(s, &args); - } + + let mut bibliography = config.lock().imports.included_bibliography.clone(); + let args = maplit::hashmap! {"type".to_string() => "bibliography".to_string()}; + while let Some(s) = bibliography.pop() { + self.import(s, &args); } - if let Some(Value::Array(mut imp)) = self - .options - .document - .config - .get_entry(IMP_GLOSSARY) - .and_then(|e| Some(e.get().clone())) - { - let args = - maplit::hashmap! {"type".to_string() => Value::String("glossary".to_string())}; - while let Some(Value::String(s)) = imp.pop() { - self.import(s, &args); - } + let mut glossaries = config.lock().imports.included_glossaries.clone(); + + let args = maplit::hashmap! {"type".to_string() =>"glossary".to_string()}; + while let Some(s) = glossaries.pop() { + self.import(s, &args); } } } diff --git a/src/references/configuration/keys.rs b/src/references/configuration/keys.rs deleted file mode 100644 index cce5760..0000000 --- a/src/references/configuration/keys.rs +++ /dev/null @@ -1,31 +0,0 @@ -#![allow(unused)] - -pub const BIB_REF_DISPLAY: &str = "bib-ref-display"; -pub const META_LANG: &str = "language"; - -// import and include options -pub const IMP_IGNORE: &str = "ignored-imports"; -pub const IMP_STYLESHEETS: &str = "included-stylesheets"; -pub const IMP_CONFIGS: &str = "included-configs"; -pub const IMP_BIBLIOGRAPHY: &str = "included-bibliography"; -pub const IMP_GLOSSARY: &str = "included-glossary"; -pub const EMBED_EXTERNAL: &str = "embed-external"; -pub const SMART_ARROWS: &str = "smart-arrows"; -pub const INCLUDE_MATHJAX: &str = "include-math-jax"; - -// PDF options -pub const PDF_DISPLAY_HEADER_FOOTER: &str = "pfd-display-header-footer"; -pub const PDF_HEADER_TEMPLATE: &str = "pdf-header-template"; -pub const PDF_FOOTER_TEMPLATE: &str = "pdf-footer-template"; -pub const PDF_MARGIN_TOP: &str = "pdf-margin-top"; -pub const PDF_MARGIN_BOTTOM: &str = "pdf-margin-bottom"; -pub const PDF_MARGIN_LEFT: &str = "pdf-margin-left"; -pub const PDF_MARGIN_RIGHT: &str = "pdf-margin-right"; -pub const PDF_PAGE_HEIGHT: &str = "pdf-page-height"; -pub const PDF_PAGE_WIDTH: &str = "pdf-page-width"; -pub const PDF_PAGE_SCALE: &str = "pdf-page-scale"; - -// Image Options -pub const IMAGE_FORMAT: &str = "image-format"; -pub const IMAGE_MAX_WIDTH: &str = "image-max-width"; -pub const IMAGE_MAX_HEIGHT: &str = "image-max-height"; diff --git a/src/references/configuration/mod.rs b/src/references/configuration/mod.rs deleted file mode 100644 index 335f5cc..0000000 --- a/src/references/configuration/mod.rs +++ /dev/null @@ -1,188 +0,0 @@ -use crate::elements::MetadataValue; -use crate::references::configuration::keys::{ - BIB_REF_DISPLAY, META_LANG, PDF_DISPLAY_HEADER_FOOTER, PDF_FOOTER_TEMPLATE, - PDF_HEADER_TEMPLATE, PDF_MARGIN_BOTTOM, PDF_MARGIN_TOP, -}; -use crate::references::templates::Template; -use serde::export::TryFrom; -use std::collections::HashMap; -use std::sync::{Arc, RwLock}; - -pub(crate) mod keys; - -#[derive(Clone, Debug)] -pub enum Value { - String(String), - Bool(bool), - Float(f64), - Integer(i64), - Template(Template), - Array(Vec<Value>), -} - -#[derive(Clone, Debug)] -pub struct ConfigEntry { - inner: Value, -} - -pub type ConfigRefEntry = Arc<RwLock<ConfigEntry>>; - -#[derive(Clone, Debug)] -pub struct Configuration { - config: Arc<RwLock<HashMap<String, ConfigRefEntry>>>, -} - -impl Value { - pub fn as_string(&self) -> String { - match self { - Value::String(string) => string.clone(), - Value::Integer(int) => format!("{}", int), - Value::Float(f) => format!("{:02}", f), - Value::Bool(b) => format!("{}", b), - Value::Array(a) => a.iter().fold("".to_string(), |a, b| { - format!("{} \"{}\"", a, b.as_string()) - }), - _ => "".to_string(), - } - } - - /// Returns the bool value if the value is a boolean - pub fn as_bool(&self) -> Option<bool> { - match self { - Value::Bool(b) => Some(*b), - _ => None, - } - } - - pub fn as_float(&self) -> Option<f64> { - match self { - Value::Float(v) => Some(*v), - _ => None, - } - } -} - -impl ConfigEntry { - pub fn new(value: Value) -> Self { - Self { inner: value } - } - - pub fn set(&mut self, value: Value) { - self.inner = value; - } - - pub fn get(&self) -> &Value { - &self.inner - } -} - -impl Default for Configuration { - fn default() -> Self { - let mut self_config = Self::new(); - self_config.set(BIB_REF_DISPLAY, Value::String("{{number}}".to_string())); - self_config.set(META_LANG, Value::String("en".to_string())); - self_config.set(PDF_MARGIN_BOTTOM, Value::Float(0.5)); - self_config.set(PDF_MARGIN_TOP, Value::Float(0.5)); - self_config.set(PDF_DISPLAY_HEADER_FOOTER, Value::Bool(true)); - self_config.set( - PDF_HEADER_TEMPLATE, - Value::String("<div></div>".to_string()), - ); - self_config.set( - PDF_FOOTER_TEMPLATE, - Value::String( - include_str!("../../format/chromium_pdf/assets/default-footer-template.html") - .to_string(), - ), - ); - - self_config - } -} - -impl Configuration { - pub fn new() -> Self { - Self { - config: Arc::new(RwLock::new(HashMap::new())), - } - } - - /// returns the value of a config entry - pub fn get_entry(&self, key: &str) -> Option<ConfigEntry> { - let config = self.config.read().unwrap(); - if let Some(entry) = config.get(key) { - let value = entry.read().unwrap(); - Some(value.clone()) - } else { - None - } - } - - /// returns a config entry that is a reference to a value - pub fn get_ref_entry(&self, key: &str) -> Option<ConfigRefEntry> { - let config = self.config.read().unwrap(); - if let Some(entry) = config.get(&key.to_string()) { - Some(Arc::clone(entry)) - } else { - None - } - } - - /// Sets a config parameter - pub fn set(&mut self, key: &str, value: Value) { - let mut config = self.config.write().unwrap(); - if let Some(entry) = config.get(&key.to_string()) { - entry.write().unwrap().set(value) - } else { - config.insert( - key.to_string(), - Arc::new(RwLock::new(ConfigEntry::new(value))), - ); - } - } - - /// Sets a config value based on a metadata value - pub fn set_from_meta(&mut self, key: &str, value: MetadataValue) { - match value { - MetadataValue::String(string) => self.set(key, Value::String(string)), - MetadataValue::Bool(bool) => self.set(key, Value::Bool(bool)), - MetadataValue::Float(f) => self.set(key, Value::Float(f)), - MetadataValue::Integer(i) => self.set(key, Value::Integer(i)), - MetadataValue::Template(t) => self.set(key, Value::Template(t)), - _ => {} - } - } - - pub fn set_from_toml(&mut self, value: &toml::Value) -> Option<()> { - let table = value.as_table().cloned()?; - table.iter().for_each(|(k, v)| { - match v { - toml::Value::Table(_) => self.set_from_toml(v).unwrap_or(()), - _ => self.set(k, Value::try_from(v.clone()).unwrap()), - }; - }); - - Some(()) - } -} - -impl TryFrom<toml::Value> for Value { - type Error = (); - - fn try_from(value: toml::Value) -> Result<Self, Self::Error> { - match value { - toml::Value::Table(_) => Err(()), - toml::Value::Float(f) => Ok(Value::Float(f)), - toml::Value::Integer(i) => Ok(Value::Integer(i)), - toml::Value::String(s) => Ok(Value::String(s)), - toml::Value::Boolean(b) => Ok(Value::Bool(b)), - toml::Value::Datetime(dt) => Ok(Value::String(dt.to_string())), - toml::Value::Array(a) => Ok(Value::Array( - a.iter() - .cloned() - .filter_map(|e| Value::try_from(e).ok()) - .collect::<Vec<Value>>(), - )), - } - } -} diff --git a/src/references/mod.rs b/src/references/mod.rs index 2ebfb66..33b22fe 100644 --- a/src/references/mod.rs +++ b/src/references/mod.rs @@ -1,5 +1,4 @@ pub mod bibliography; -pub mod configuration; pub mod glossary; pub mod placeholders; pub mod templates; diff --git a/src/references/placeholders.rs b/src/references/placeholders.rs index f9c0be6..9376fc7 100644 --- a/src/references/placeholders.rs +++ b/src/references/placeholders.rs @@ -66,9 +66,13 @@ impl ProcessPlaceholders for Document { value: format!("{} {}", get_date_string(), get_time_string()) }))), _ => { - if let Some(entry) = self.config.get_entry(pholder.name.to_lowercase().as_str()) + if let Some(value) = self + .config + .lock() + .custom_attributes + .get(pholder.name.to_lowercase().as_str()) + .cloned() { - let value = entry.get().as_string(); pholder.set_value(inline!(Inline::Plain(PlainText { value }))) } } @@ -94,7 +98,7 @@ impl ProcessPlaceholders for Document { }))); if let Some(meta) = &pholder.metadata { if let Some(value) = meta.data.get(S_VALUE) { - self.config.set_from_meta(key, value.clone()) + self.config.lock().set_from_meta(key, value.clone()) } } } diff --git a/src/settings/feature_settings.rs b/src/settings/feature_settings.rs new file mode 100644 index 0000000..aa38da7 --- /dev/null +++ b/src/settings/feature_settings.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct FeatureSettings { + pub embed_external: bool, + pub smart_arrows: bool, + pub include_mathjax: bool, +} + +impl Default for FeatureSettings { + fn default() -> Self { + Self { + embed_external: true, + smart_arrows: true, + include_mathjax: true, + } + } +} diff --git a/src/settings/format_settings.rs b/src/settings/format_settings.rs new file mode 100644 index 0000000..873bcf1 --- /dev/null +++ b/src/settings/format_settings.rs @@ -0,0 +1,14 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct FormatSettings { + pub bib_ref_display: String, +} + +impl Default for FormatSettings { + fn default() -> Self { + Self { + bib_ref_display: "{{number}}".to_string(), + } + } +} diff --git a/src/settings/image_settings.rs b/src/settings/image_settings.rs new file mode 100644 index 0000000..4e836d8 --- /dev/null +++ b/src/settings/image_settings.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ImageSettings { + pub format: Option<String>, + pub max_width: Option<u32>, + pub max_height: Option<u32>, +} + +impl Default for ImageSettings { + fn default() -> Self { + Self { + format: None, + max_height: None, + max_width: None, + } + } +} diff --git a/src/settings/import_settings.rs b/src/settings/import_settings.rs new file mode 100644 index 0000000..32e24c8 --- /dev/null +++ b/src/settings/import_settings.rs @@ -0,0 +1,20 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ImportSettings { + pub ignored_imports: Vec<String>, + pub included_stylesheets: Vec<String>, + pub included_bibliography: Vec<String>, + pub included_glossaries: Vec<String>, +} + +impl Default for ImportSettings { + fn default() -> Self { + Self { + ignored_imports: Vec::with_capacity(0), + included_stylesheets: vec!["style.css".to_string()], + included_bibliography: vec!["Bibliography.toml".to_string()], + included_glossaries: vec!["Glossary.toml".to_string()], + } + } +} diff --git a/src/settings/metadata_settings.rs b/src/settings/metadata_settings.rs new file mode 100644 index 0000000..386abae --- /dev/null +++ b/src/settings/metadata_settings.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct MetadataSettings { + pub title: Option<String>, + pub author: Option<String>, + pub language: String, +} + +impl Default for MetadataSettings { + fn default() -> Self { + Self { + title: None, + author: None, + language: "en".to_string(), + } + } +} diff --git a/src/settings/mod.rs b/src/settings/mod.rs new file mode 100644 index 0000000..d742c03 --- /dev/null +++ b/src/settings/mod.rs @@ -0,0 +1,125 @@ +use crate::elements::{Metadata, MetadataValue}; +use crate::settings::feature_settings::FeatureSettings; +use crate::settings::format_settings::FormatSettings; +use crate::settings::image_settings::ImageSettings; +use crate::settings::import_settings::ImportSettings; +use crate::settings::metadata_settings::MetadataSettings; +use crate::settings::pdf_settings::PDFSettings; +use config::{ConfigError, Source}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::error::Error; +use std::fmt::{self, Display}; +use std::io; +use std::mem; +use std::path::PathBuf; + +pub mod feature_settings; +pub mod format_settings; +pub mod image_settings; +pub mod import_settings; +pub mod metadata_settings; +pub mod pdf_settings; + +pub type SettingsResult<T> = Result<T, SettingsError>; + +#[derive(Debug)] +pub enum SettingsError { + IoError(io::Error), + ConfigError(ConfigError), + TomlError(toml::ser::Error), +} + +impl Display for SettingsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::IoError(e) => write!(f, "IO Error: {}", e), + Self::ConfigError(e) => write!(f, "Config Error: {}", e), + Self::TomlError(e) => write!(f, "Toml Error: {}", e), + } + } +} + +impl Error for SettingsError {} + +impl From<io::Error> for SettingsError { + fn from(e: io::Error) -> Self { + Self::IoError(e) + } +} + +impl From<ConfigError> for SettingsError { + fn from(e: ConfigError) -> Self { + Self::ConfigError(e) + } +} + +impl From<toml::ser::Error> for SettingsError { + fn from(e: toml::ser::Error) -> Self { + Self::TomlError(e) + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct Settings { + pub metadata: MetadataSettings, + pub features: FeatureSettings, + pub imports: ImportSettings, + pub pdf: PDFSettings, + pub images: ImageSettings, + pub formatting: FormatSettings, + pub custom_attributes: HashMap<String, String>, +} + +impl Source for Settings { + fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> { + Box::new(self.clone()) + } + + fn collect(&self) -> Result<HashMap<String, config::Value>, config::ConfigError> { + let source_str = + toml::to_string(&self).map_err(|e| config::ConfigError::Foreign(Box::new(e)))?; + let result = toml::de::from_str(&source_str) + .map_err(|e| config::ConfigError::Foreign(Box::new(e)))?; + + Ok(result) + } +} + +impl Settings { + /// Loads the settings from the specified path + pub fn load(path: PathBuf) -> SettingsResult<Self> { + let mut settings = config::Config::default(); + settings + .merge(Self::default())? + .merge(config::File::from(path))?; + let settings: Self = settings.try_into()?; + + Ok(settings) + } + + /// Merges the current settings with the settings from the given path + /// returning updated settings + pub fn merge(&mut self, path: PathBuf) -> SettingsResult<()> { + let mut settings = config::Config::default(); + settings + .merge(self.clone())? + .merge(config::File::from(path))?; + let mut settings: Self = settings.try_into()?; + mem::swap(self, &mut settings); // replace the old settings with the new ones + + Ok(()) + } + + pub fn append_metadata<M: Metadata>(&mut self, metadata: M) { + let entries = metadata.get_string_map(); + for (key, value) in entries { + self.custom_attributes.insert(key, value); + } + } + + pub fn set_from_meta(&mut self, key: &str, value: MetadataValue) { + self.custom_attributes + .insert(key.to_string(), value.to_string()); + } +} diff --git a/src/settings/pdf_settings.rs b/src/settings/pdf_settings.rs new file mode 100644 index 0000000..de7fe9b --- /dev/null +++ b/src/settings/pdf_settings.rs @@ -0,0 +1,48 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct PDFSettings { + pub display_header_footer: bool, + pub header_template: Option<String>, + pub footer_template: Option<String>, + pub page_height: Option<f32>, + pub page_width: Option<f32>, + pub page_scale: f32, + pub margin: PDFMarginSettings, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct PDFMarginSettings { + pub top: Option<f32>, + pub bottom: Option<f32>, + pub left: Option<f32>, + pub right: Option<f32>, +} + +impl Default for PDFMarginSettings { + fn default() -> Self { + Self { + top: Some(0.5), + bottom: Some(0.5), + left: None, + right: None, + } + } +} + +impl Default for PDFSettings { + fn default() -> Self { + Self { + display_header_footer: true, + header_template: Some("<div></div>".to_string()), + footer_template: Some( + include_str!("../format/chromium_pdf/assets/default-footer-template.html") + .to_string(), + ), + page_height: None, + page_width: None, + page_scale: 1.0, + margin: Default::default(), + } + } +}