From d84b0d86dd3217d3ed7802352eb525aaf370bd70 Mon Sep 17 00:00:00 2001 From: trivernis Date: Thu, 17 Dec 2020 13:11:44 +0100 Subject: [PATCH 1/7] Change settings format Settings are now stored in a specific format defined in the settings module. The Manifest.toml file will be loaded by default. Further config files can be imported with the default file import syntax. Signed-off-by: trivernis --- Cargo.lock | 133 ++++++++++++++++--- Cargo.toml | 10 +- src/elements/mod.rs | 67 ++-------- src/format/chromium_pdf/mod.rs | 67 +++------- src/format/html/to_html.rs | 15 +-- src/lib.rs | 1 + src/main.rs | 30 +++++ src/parser/block.rs | 30 ++--- src/parser/inline.rs | 60 ++++----- src/parser/line.rs | 18 +-- src/parser/mod.rs | 169 ++++++++++-------------- src/references/configuration/keys.rs | 31 ----- src/references/configuration/mod.rs | 188 --------------------------- src/references/mod.rs | 1 - src/references/placeholders.rs | 10 +- src/settings/feature_settings.rs | 18 +++ src/settings/format_settings.rs | 14 ++ src/settings/image_settings.rs | 18 +++ src/settings/import_settings.rs | 20 +++ src/settings/metadata_settings.rs | 18 +++ src/settings/mod.rs | 125 ++++++++++++++++++ src/settings/pdf_settings.rs | 48 +++++++ 22 files changed, 580 insertions(+), 511 deletions(-) delete mode 100644 src/references/configuration/keys.rs delete mode 100644 src/references/configuration/mod.rs create mode 100644 src/settings/feature_settings.rs create mode 100644 src/settings/format_settings.rs create mode 100644 src/settings/image_settings.rs create mode 100644 src/settings/import_settings.rs create mode 100644 src/settings/metadata_settings.rs create mode 100644 src/settings/mod.rs create mode 100644 src/settings/pdf_settings.rs 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/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(), + } + } +} From ce311853ccda65de166e7f46de28636941e229ae Mon Sep 17 00:00:00 2001 From: trivernis <trivernis@protonmail.com> Date: Thu, 17 Dec 2020 13:15:06 +0100 Subject: [PATCH 2/7] Update README Signed-off-by: trivernis <trivernis@protonmail.com> --- README.md | 121 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 69 insertions(+), 52 deletions(-) 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 = "<div><span class='title'></span></div>" +# PDF header template of each page (default: '<div></div>') +header_template = '<div></div>' # PDF footer template of each page (default: see chromium_pdf assets) -pdf-footer-template = "<div><span class='pageNumber'></span></div>" +footer_template = ''' +<div style="font-size: 10px; text-align: center; width: 100%;"> + <span class="pageNumber"></span>/<span class="totalPages"></span> +</div>''' -# 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. From 097eae5f4e8fdb1d07e7bc0d4a8d6f02e967d837 Mon Sep 17 00:00:00 2001 From: trivernis <trivernis@protonmail.com> Date: Thu, 17 Dec 2020 16:12:47 +0100 Subject: [PATCH 3/7] Add theme config option Signed-off-by: trivernis <trivernis@protonmail.com> --- Cargo.lock | 45 +++++++ Cargo.toml | 1 + src/format/assets/base.scss | 159 +++++++++++++++++++++++++ src/format/assets/dark-ocean.scss | 7 ++ src/format/assets/dark-solarized.scss | 7 ++ src/format/assets/light-github.scss | 7 ++ src/format/assets/light-ocean.scss | 7 ++ src/format/assets/light-solarized.scss | 7 ++ src/format/html/assets/style.css | 149 ----------------------- src/format/html/html_writer.rs | 11 +- src/format/html/to_html.rs | 15 ++- src/format/mod.rs | 1 + src/format/style.rs | 53 +++++++++ src/main.rs | 5 +- src/settings/format_settings.rs | 11 ++ 15 files changed, 325 insertions(+), 160 deletions(-) create mode 100644 src/format/assets/base.scss create mode 100644 src/format/assets/dark-ocean.scss create mode 100644 src/format/assets/dark-solarized.scss create mode 100644 src/format/assets/light-github.scss create mode 100644 src/format/assets/light-ocean.scss create mode 100644 src/format/assets/light-solarized.scss delete mode 100644 src/format/html/assets/style.css create mode 100644 src/format/style.rs diff --git a/Cargo.lock b/Cargo.lock index 5787b37..354c7de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -185,6 +185,12 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" +[[package]] +name = "bytecount" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72feb31ffc86498dacdbd0fcebb56138e7177a8cc5cea4516031d15ae85a742e" + [[package]] name = "bytemuck" version = "1.4.1" @@ -1364,6 +1370,17 @@ dependencies = [ "version_check 0.9.2", ] +[[package]] +name = "nom_locate" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a67484adf5711f94f2f28b653bf231bff8e438be33bf5b0f35935a0db4f618a2" +dependencies = [ + "bytecount", + "memchr", + "nom", +] + [[package]] name = "notify" version = "4.0.15" @@ -1382,6 +1399,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "num-bigint" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9a41747ae4633fce5adffb4d2e81ffc5e89593cb19917f8fb2cc5ff76507bf" +dependencies = [ + "autocfg 1.0.1", + "num-integer", + "num-traits 0.2.14", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -2008,6 +2036,22 @@ dependencies = [ "winreg 0.7.0", ] +[[package]] +name = "rsass" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1144f0d9fd5df1f8fbac387e91b73a2d315e013ee6beb48353a71d8ec2c5ab" +dependencies = [ + "lazy_static", + "nom", + "nom_locate", + "num-bigint", + "num-integer", + "num-rational", + "num-traits 0.2.14", + "rand 0.7.3", +] + [[package]] name = "rust-argon2" version = "0.8.3" @@ -2249,6 +2293,7 @@ dependencies = [ "rayon", "regex", "reqwest", + "rsass", "serde 1.0.118", "sha2", "structopt", diff --git a/Cargo.toml b/Cargo.toml index d057dff..e4f34aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ image = "0.23.12" parking_lot = "0.11.1" sha2 = "0.9.2" config = "0.10.1" +rsass = "0.16.0" headless_chrome = { version = "0.9.0", optional = true } failure = { version = "0.1.8", optional = true } diff --git a/src/format/assets/base.scss b/src/format/assets/base.scss new file mode 100644 index 0000000..d058082 --- /dev/null +++ b/src/format/assets/base.scss @@ -0,0 +1,159 @@ +body { + background-color: $background-color-variant-2; + overflow-x: hidden; + color: $primary-color; + word-break: break-word; +} + +.content { + font-family: "Fira Sans", "Noto Sans", SansSerif, sans-serif; + width: 100vh; + max-width: calc(100% - 4rem); + padding: 2rem; + margin: auto; + background-color: $background-color; +} + +h1 { + font-size: 2.2rem; +} + +h2 { + font-size: 1.8rem; +} + +h3 { + font-size: 1.4rem; +} + +h4 { + font-size: 1rem; +} + +h5 { + font-size: 0.8rem; +} + +h6 { + font-size: 0.4rem; +} + +img { + max-width: 100%; + max-height: 100vh; + height: auto; +} + +code { + color: $primary-color; + + pre { + font-family: "Fira Code", "Mono", monospace; + padding: 0.8em 0.2em; + background-color: $background-color-variant-1 !important; + border-radius: 0.25em; + overflow: auto; + } + + &.inlineCode { + font-family: "Fira Code", monospace; + border-radius: 0.1em; + background-color: $background-color-variant-1; + padding: 0 0.1em; + } +} + +.tableWrapper { + overflow-x: auto; + width: 100%; + + & > table { + margin: auto; + } +} + +table { + border-collapse: collapse; + + tr { + &:nth-child(odd) { + background-color: $background-color-variant-2; + } + + &:nth-child(1) { + background-color: $background-color-variant-2; + font-weight: bold; + border-bottom: 1px solid invert($background-color) + } + } +} + +table td, table th { + border-left: 1px solid invert($background-color); + padding: 0.2em 0.5em; +} + +table tr td:first-child, table tr th:first-child { + border-left: none; +} + +blockquote { + margin-left: 0; + background-color: rgba(0, 0, 0, 0); +} + +a { + color: $secondary-color; +} + +.quote { + border-left: 0.3em solid $background-color-variant-3; + border-radius: 0.2em; + padding-left: 1em; + margin-left: 0; + background-color: $background-color-variant-1; + + .metadata { + font-style: italic; + padding-left: 0.5em; + color: $primary-variant-1; + } +} + + +.figure { + width: 100%; + display: block; + text-align: center; + + .imageDescription { + display: block; + color: $primary-variant-1; + font-style: italic; + } +} + +.centered { + text-align: center; +} + +.glossaryReference { + text-decoration: none; + color: inherit; + border-bottom: 1px dotted $primary-color; +} + +.arrow { + font-family: "Fira Code", "Mono", monospace; +} + +@media print { + + .content > section > section, .content > section > section { + page-break-inside: avoid; + } + + body { + background-color: $background-color !important; + } +} \ No newline at end of file diff --git a/src/format/assets/dark-ocean.scss b/src/format/assets/dark-ocean.scss new file mode 100644 index 0000000..0411d68 --- /dev/null +++ b/src/format/assets/dark-ocean.scss @@ -0,0 +1,7 @@ +$background-color: darken(#2b303b, 8%); +$background-color-variant-1: lighten($background-color, 7%); +$background-color-variant-2: lighten($background-color, 14%); +$background-color-variant-3: lighten($background-color, 21%); +$primary-color: #EEE; +$primary-variant-1: darken($primary-color, 14%); +$secondary-color: #3aa7df; \ No newline at end of file diff --git a/src/format/assets/dark-solarized.scss b/src/format/assets/dark-solarized.scss new file mode 100644 index 0000000..8e0784f --- /dev/null +++ b/src/format/assets/dark-solarized.scss @@ -0,0 +1,7 @@ +$background-color: darken(#002b36, 5%); +$background-color-variant-1: lighten($background-color, 7%); +$background-color-variant-2: lighten($background-color, 14%); +$background-color-variant-3: lighten($background-color, 21%); +$primary-color: #EEE; +$primary-variant-1: darken($primary-color, 14%); +$secondary-color: #0096c9; diff --git a/src/format/assets/light-github.scss b/src/format/assets/light-github.scss new file mode 100644 index 0000000..2d53d91 --- /dev/null +++ b/src/format/assets/light-github.scss @@ -0,0 +1,7 @@ +$background-color: #FFF; +$background-color-variant-1: darken($background-color, 7%); +$background-color-variant-2: darken($background-color, 14%); +$background-color-variant-3: darken($background-color, 21%); +$primary-color: #000; +$primary-variant-1: lighten($primary-color, 14%); +$secondary-color: #00286a; \ No newline at end of file diff --git a/src/format/assets/light-ocean.scss b/src/format/assets/light-ocean.scss new file mode 100644 index 0000000..c1bf8b9 --- /dev/null +++ b/src/format/assets/light-ocean.scss @@ -0,0 +1,7 @@ +$background-color: #FFF; +$background-color-variant-1: darken($background-color, 7%); +$background-color-variant-2: darken($background-color, 14%); +$background-color-variant-3: darken($background-color, 21%); +$primary-color: #112; +$primary-variant-1: lighten($primary-color, 14%); +$secondary-color: #00348e; diff --git a/src/format/assets/light-solarized.scss b/src/format/assets/light-solarized.scss new file mode 100644 index 0000000..eee3eb6 --- /dev/null +++ b/src/format/assets/light-solarized.scss @@ -0,0 +1,7 @@ +$background-color: #fff8f0; +$background-color-variant-1: darken($background-color, 4%); +$background-color-variant-2: darken($background-color, 8%); +$background-color-variant-3: darken($background-color, 12%); +$primary-color: #112; +$primary-variant-1: lighten($primary-color, 14%); +$secondary-color: #2b61be; diff --git a/src/format/html/assets/style.css b/src/format/html/assets/style.css deleted file mode 100644 index 5df7317..0000000 --- a/src/format/html/assets/style.css +++ /dev/null @@ -1,149 +0,0 @@ -body { - background-color: #DDD; - overflow-x: hidden; - color: #000; - word-break: break-word; -} - -.content { - font-family: "Fira Sans", "Noto Sans", SansSerif, sans-serif; - width: 100vh; - max-width: calc(100% - 4rem); - padding: 2rem; - margin: auto; - background-color: #FFF; -} - -h1 { - font-size: 2.2rem; -} - -h2 { - font-size: 1.8rem; -} - -h3 { - font-size: 1.4rem; -} - -h4 { - font-size: 1rem; -} - -h5 { - font-size: 0.8rem; -} - -h6 { - font-size: 0.4rem; -} - -img { - max-width: 100%; - max-height: 100vh; - height: auto; -} - -code { - color: #000; -} - -code pre { - font-family: "Fira Code", "Mono", monospace; - padding: 0.8em 0.2em; - background-color: #EEE !important; - border-radius: 0.25em; -} - -code.inlineCode { - font-family: "Fira Code", monospace; - border-radius: 0.1em; - background-color: #EEE; - padding: 0 0.1em -} - -.tableWrapper { - overflow-x: auto; - width: 100%; -} - -.tableWrapper > table { - margin: auto; -} - -table { - border-collapse: collapse; -} - -table tr:nth-child(odd) { - background-color: #DDD; -} - -table tr:nth-child(1) { - background-color: white; - font-weight: bold; - border-bottom: 1px solid black; -} - -table td, table th { - border-left: 1px solid black; - padding: 0.2em 0.5em -} - -table tr td:first-child, table tr th:first-child { - border-left: none; -} - -blockquote { - margin-left: 0; - background-color: rgba(0, 0, 0, 0); -} - -.quote { - border-left: 0.3em solid gray; - border-radius: 0.2em; - padding-left: 1em; - margin-left: 0; - background-color: #EEE; -} - -.quote .metadata { - font-style: italic; - padding-left: 0.5em; - color: #444 -} - -.figure { - width: 100%; - display: block; - text-align: center; -} - -.figure .imageDescription { - display: block; - color: #444; - font-style: italic; -} - -.centered { - text-align: center; -} - -.glossaryReference { - text-decoration: none; - color: inherit; - border-bottom: 1px dotted #000; -} - -.arrow { - font-family: "Fira Code", "Mono", monospace; -} - -@media print { - .content > section > section, .content > section > section { - page-break-inside: avoid; - } - body { - background-color: white !important; - } -} \ No newline at end of file diff --git a/src/format/html/html_writer.rs b/src/format/html/html_writer.rs index e5ba6e6..50c28ef 100644 --- a/src/format/html/html_writer.rs +++ b/src/format/html/html_writer.rs @@ -1,14 +1,16 @@ +use crate::settings::format_settings::Theme; use std::io; use std::io::Write; pub struct HTMLWriter { inner: Box<dyn Write>, + theme: Theme, } impl HTMLWriter { /// Creates a new writer - pub fn new(inner: Box<dyn Write>) -> Self { - Self { inner } + pub fn new(inner: Box<dyn Write>, theme: Theme) -> Self { + Self { inner, theme } } /// Writes a raw string @@ -30,4 +32,9 @@ impl HTMLWriter { pub fn flush(&mut self) -> io::Result<()> { self.inner.flush() } + + /// Return the theme of the html writer + pub fn get_theme(&mut self) -> Theme { + self.theme.clone() + } } diff --git a/src/format/html/to_html.rs b/src/format/html/to_html.rs index 22d8e15..464a590 100644 --- a/src/format/html/to_html.rs +++ b/src/format/html/to_html.rs @@ -1,5 +1,6 @@ use crate::elements::*; use crate::format::html::html_writer::HTMLWriter; +use crate::format::style::{get_code_theme_for_theme, get_css_for_theme}; use crate::format::PlaceholderTemplate; use crate::references::glossary::{GlossaryDisplay, GlossaryReference}; use crate::references::templates::{Template, TemplateVariable}; @@ -7,9 +8,7 @@ use asciimath_rs::format::mathml::ToMathML; use htmlescape::encode_attribute; use minify::html::minify; use std::io; -use syntect::highlighting::ThemeSet; use syntect::html::highlighted_html_for_string; -use syntect::parsing::SyntaxSet; const MATHJAX_URL: &str = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"; @@ -108,7 +107,7 @@ impl ToHtml for Document { if self.is_root { let language = self.config.lock().metadata.language.clone(); - let style = minify(std::include_str!("assets/style.css")); + let style = minify(get_css_for_theme(writer.get_theme()).as_str()); writer.write("<!DOCTYPE html>".to_string())?; writer.write("<html lang=\"".to_string())?; writer.write_attribute(language)?; @@ -312,19 +311,19 @@ impl ToHtml for Cell { impl ToHtml for CodeBlock { fn to_html(&self, writer: &mut HTMLWriter) -> io::Result<()> { writer.write("<div><code".to_string())?; + if self.language.len() > 0 { writer.write(" lang=\"".to_string())?; writer.write_attribute(self.language.clone())?; writer.write("\">".to_string())?; - lazy_static::lazy_static! { static ref PS: SyntaxSet = SyntaxSet::load_defaults_nonewlines(); } - lazy_static::lazy_static! { static ref TS: ThemeSet = ThemeSet::load_defaults(); } + let (theme, syntax_set) = get_code_theme_for_theme(writer.get_theme()); - if let Some(syntax) = PS.find_syntax_by_token(self.language.as_str()) { + if let Some(syntax) = syntax_set.find_syntax_by_token(self.language.as_str()) { writer.write(highlighted_html_for_string( self.code.as_str(), - &PS, + &syntax_set, syntax, - &TS.themes["InspiredGitHub"], + &theme, ))?; } else { writer.write("<pre>".to_string())?; diff --git a/src/format/mod.rs b/src/format/mod.rs index 168963b..6fc2726 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; #[cfg(feature = "pdf")] pub mod chromium_pdf; pub mod html; +pub mod style; pub struct PlaceholderTemplate { value: String, diff --git a/src/format/style.rs b/src/format/style.rs new file mode 100644 index 0000000..ef6f03d --- /dev/null +++ b/src/format/style.rs @@ -0,0 +1,53 @@ +use crate::settings::format_settings::Theme; +use std::time::Instant; +use syntect::highlighting::ThemeSet; +use syntect::parsing::SyntaxSet; + +/// Returns the css of a theme compiled from sass +pub fn get_css_for_theme(theme: Theme) -> String { + let start = Instant::now(); + let vars = match theme { + Theme::GitHub => include_str!("assets/light-github.scss"), + Theme::SolarizedDark => include_str!("assets/dark-solarized.scss"), + Theme::SolarizedLight => include_str!("assets/light-solarized.scss"), + Theme::OceanDark => include_str!("assets/dark-ocean.scss"), + Theme::OceanLight => include_str!("assets/light-ocean.scss"), + }; + let style = format!("{}\n{}", vars, include_str!("assets/base.scss")); + + let css = compile_sass(&*style); + + log::debug!("Compiled style in {} ms", start.elapsed().as_millis()); + + css +} + +/// Returns the syntax theme for a given theme +pub fn get_code_theme_for_theme(theme: Theme) -> (syntect::highlighting::Theme, SyntaxSet) { + lazy_static::lazy_static! { static ref PS: SyntaxSet = SyntaxSet::load_defaults_nonewlines(); } + lazy_static::lazy_static! { static ref TS: ThemeSet = ThemeSet::load_defaults(); } + + let theme = match theme { + Theme::GitHub => "InspiredGitHub", + Theme::SolarizedDark => "Solarized (dark)", + Theme::SolarizedLight => "Solarized (light)", + Theme::OceanDark => "base16-ocean.dark", + Theme::OceanLight => "base16-ocean.light", + }; + + return (TS.themes[theme].clone(), PS.clone()); +} + +fn compile_sass(sass: &str) -> String { + String::from_utf8( + rsass::compile_scss( + sass.as_bytes(), + rsass::output::Format { + style: rsass::output::Style::Compressed, + precision: 5, + }, + ) + .unwrap(), + ) + .unwrap() +} diff --git a/src/main.rs b/src/main.rs index d50159d..3bd88b6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -207,7 +207,10 @@ fn render_format(opt: &RenderOptions, document: Document, writer: BufWriter<File } fn render_html(document: Document, writer: BufWriter<File>) { - let mut writer = HTMLWriter::new(Box::new(writer)); + let mut writer = HTMLWriter::new( + Box::new(writer), + document.config.lock().formatting.theme.clone(), + ); document.to_html(&mut writer).unwrap(); writer.flush().unwrap(); } diff --git a/src/settings/format_settings.rs b/src/settings/format_settings.rs index 873bcf1..07b7748 100644 --- a/src/settings/format_settings.rs +++ b/src/settings/format_settings.rs @@ -3,12 +3,23 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct FormatSettings { pub bib_ref_display: String, + pub theme: Theme, } impl Default for FormatSettings { fn default() -> Self { Self { bib_ref_display: "{{number}}".to_string(), + theme: Theme::GitHub, } } } + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub enum Theme { + GitHub, + SolarizedDark, + SolarizedLight, + OceanDark, + OceanLight, +} From faa8e57ffadb2a5b214a3985ca0faa08de370c7c Mon Sep 17 00:00:00 2001 From: trivernis <trivernis@protonmail.com> Date: Thu, 17 Dec 2020 16:13:55 +0100 Subject: [PATCH 4/7] Update Theme Signed-off-by: trivernis <trivernis@protonmail.com> --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index f17c09f..514259a 100644 --- a/README.md +++ b/README.md @@ -289,10 +289,14 @@ max_width = 700 max_height = 500 +# Visual adjustments [formatting] # how bibliography references should be displayed bib_ref_display = '{{number}}' +# the chosen theme for the document +# one of: GithubLight, SolarizedLight, OceanLight, SolarizedDark, OceanDark +theme = 'GithubLight' # custom metadata # String -> String Mappings From d82c43e5a3b1c9cb76a16b0d3dc6ab42eb87955b Mon Sep 17 00:00:00 2001 From: trivernis <trivernis@protonmail.com> Date: Thu, 17 Dec 2020 16:29:27 +0100 Subject: [PATCH 5/7] Add magic dark theme and rename formatting config to style Signed-off-by: trivernis <trivernis@protonmail.com> --- README.md | 4 ++-- src/format/assets/base.scss | 14 +++++++------- src/format/assets/dark-magic.scss | 14 ++++++++++++++ src/format/assets/dark-ocean.scss | 9 ++++++++- src/format/assets/dark-solarized.scss | 6 ++++++ src/format/assets/light-github.scss | 8 +++++++- src/format/assets/light-ocean.scss | 6 ++++++ src/format/assets/light-solarized.scss | 6 ++++++ src/format/html/html_writer.rs | 2 +- src/format/style.rs | 4 +++- src/main.rs | 5 +---- src/parser/inline.rs | 2 +- src/settings/mod.rs | 6 +++--- .../{format_settings.rs => style_settings.rs} | 5 +++-- 14 files changed, 68 insertions(+), 23 deletions(-) create mode 100644 src/format/assets/dark-magic.scss rename src/settings/{format_settings.rs => style_settings.rs} (85%) diff --git a/README.md b/README.md index 514259a..c1bd6db 100644 --- a/README.md +++ b/README.md @@ -290,12 +290,12 @@ max_height = 500 # Visual adjustments -[formatting] +[style] # how bibliography references should be displayed bib_ref_display = '{{number}}' # the chosen theme for the document -# one of: GithubLight, SolarizedLight, OceanLight, SolarizedDark, OceanDark +# one of: GithubLight, SolarizedLight, OceanLight, SolarizedDark, OceanDark, MagicDark theme = 'GithubLight' # custom metadata diff --git a/src/format/assets/base.scss b/src/format/assets/base.scss index d058082..adf221c 100644 --- a/src/format/assets/base.scss +++ b/src/format/assets/base.scss @@ -1,5 +1,5 @@ body { - background-color: $background-color-variant-2; + background-color: $body-background; overflow-x: hidden; color: $primary-color; word-break: break-word; @@ -50,7 +50,7 @@ code { pre { font-family: "Fira Code", "Mono", monospace; padding: 0.8em 0.2em; - background-color: $background-color-variant-1 !important; + background-color: $code-background !important; border-radius: 0.25em; overflow: auto; } @@ -58,7 +58,7 @@ code { &.inlineCode { font-family: "Fira Code", monospace; border-radius: 0.1em; - background-color: $background-color-variant-1; + background-color: $code-background; padding: 0 0.1em; } } @@ -77,11 +77,11 @@ table { tr { &:nth-child(odd) { - background-color: $background-color-variant-2; + background-color: $table-background-alt; } &:nth-child(1) { - background-color: $background-color-variant-2; + background-color: $table-background-alt; font-weight: bold; border-bottom: 1px solid invert($background-color) } @@ -107,11 +107,11 @@ a { } .quote { - border-left: 0.3em solid $background-color-variant-3; + border-left: 0.3em solid $quote-background-alt; border-radius: 0.2em; padding-left: 1em; margin-left: 0; - background-color: $background-color-variant-1; + background-color: $quote-background; .metadata { font-style: italic; diff --git a/src/format/assets/dark-magic.scss b/src/format/assets/dark-magic.scss new file mode 100644 index 0000000..f1cac35 --- /dev/null +++ b/src/format/assets/dark-magic.scss @@ -0,0 +1,14 @@ +$background-color: #1e1d2c; +$background-color-variant-1: lighten($background-color, 7%); +$background-color-variant-2: lighten($background-color, 14%); +$background-color-variant-3: lighten($background-color, 21%); + +$primary-color: #EEE; +$primary-variant-1: darken($primary-color, 14%); +$secondary-color: #3aa7df; + +$body-background: darken($background-color, 5%); +$code-background: lighten($background-color, 5%); +$table-background-alt: $background-color-variant-2; +$quote-background: lighten($background-color-variant-1, 3%); +$quote-background-alt: $background-color-variant-3; \ No newline at end of file diff --git a/src/format/assets/dark-ocean.scss b/src/format/assets/dark-ocean.scss index 0411d68..510f274 100644 --- a/src/format/assets/dark-ocean.scss +++ b/src/format/assets/dark-ocean.scss @@ -2,6 +2,13 @@ $background-color: darken(#2b303b, 8%); $background-color-variant-1: lighten($background-color, 7%); $background-color-variant-2: lighten($background-color, 14%); $background-color-variant-3: lighten($background-color, 21%); + $primary-color: #EEE; $primary-variant-1: darken($primary-color, 14%); -$secondary-color: #3aa7df; \ No newline at end of file +$secondary-color: #3aa7df; + +$body-background: darken($background-color, 5%); +$code-background: $background-color-variant-1; +$table-background-alt: $background-color-variant-2; +$quote-background: lighten($background-color-variant-1, 3%); +$quote-background-alt: $background-color-variant-3; \ No newline at end of file diff --git a/src/format/assets/dark-solarized.scss b/src/format/assets/dark-solarized.scss index 8e0784f..a067a27 100644 --- a/src/format/assets/dark-solarized.scss +++ b/src/format/assets/dark-solarized.scss @@ -5,3 +5,9 @@ $background-color-variant-3: lighten($background-color, 21%); $primary-color: #EEE; $primary-variant-1: darken($primary-color, 14%); $secondary-color: #0096c9; + +$body-background: darken($background-color, 3%); +$code-background: $background-color-variant-1; +$table-background-alt: lighten($background-color, 10%); +$quote-background: lighten($background-color-variant-1, 3%); +$quote-background-alt: $background-color-variant-3; \ No newline at end of file diff --git a/src/format/assets/light-github.scss b/src/format/assets/light-github.scss index 2d53d91..05d74cb 100644 --- a/src/format/assets/light-github.scss +++ b/src/format/assets/light-github.scss @@ -4,4 +4,10 @@ $background-color-variant-2: darken($background-color, 14%); $background-color-variant-3: darken($background-color, 21%); $primary-color: #000; $primary-variant-1: lighten($primary-color, 14%); -$secondary-color: #00286a; \ No newline at end of file +$secondary-color: #00286a; + +$body-background: $background-color-variant-1; +$code-background: $background-color-variant-1; +$table-background-alt: $background-color-variant-2; +$quote-background: $background-color-variant-2; +$quote-background-alt: $background-color-variant-3; \ No newline at end of file diff --git a/src/format/assets/light-ocean.scss b/src/format/assets/light-ocean.scss index c1bf8b9..af7aef2 100644 --- a/src/format/assets/light-ocean.scss +++ b/src/format/assets/light-ocean.scss @@ -5,3 +5,9 @@ $background-color-variant-3: darken($background-color, 21%); $primary-color: #112; $primary-variant-1: lighten($primary-color, 14%); $secondary-color: #00348e; + +$body-background: $background-color-variant-1; +$code-background: $background-color-variant-1; +$table-background-alt: $background-color-variant-2; +$quote-background: $background-color-variant-2; +$quote-background-alt: $background-color-variant-3; \ No newline at end of file diff --git a/src/format/assets/light-solarized.scss b/src/format/assets/light-solarized.scss index eee3eb6..dcfbb87 100644 --- a/src/format/assets/light-solarized.scss +++ b/src/format/assets/light-solarized.scss @@ -5,3 +5,9 @@ $background-color-variant-3: darken($background-color, 12%); $primary-color: #112; $primary-variant-1: lighten($primary-color, 14%); $secondary-color: #2b61be; + +$body-background: $background-color-variant-1; +$code-background: $background-color-variant-1; +$table-background-alt: $background-color-variant-2; +$quote-background: $background-color-variant-2; +$quote-background-alt: $background-color-variant-3; \ No newline at end of file diff --git a/src/format/html/html_writer.rs b/src/format/html/html_writer.rs index 50c28ef..1a518b3 100644 --- a/src/format/html/html_writer.rs +++ b/src/format/html/html_writer.rs @@ -1,4 +1,4 @@ -use crate::settings::format_settings::Theme; +use crate::settings::style_settings::Theme; use std::io; use std::io::Write; diff --git a/src/format/style.rs b/src/format/style.rs index ef6f03d..c73eb9c 100644 --- a/src/format/style.rs +++ b/src/format/style.rs @@ -1,4 +1,4 @@ -use crate::settings::format_settings::Theme; +use crate::settings::style_settings::Theme; use std::time::Instant; use syntect::highlighting::ThemeSet; use syntect::parsing::SyntaxSet; @@ -12,6 +12,7 @@ pub fn get_css_for_theme(theme: Theme) -> String { Theme::SolarizedLight => include_str!("assets/light-solarized.scss"), Theme::OceanDark => include_str!("assets/dark-ocean.scss"), Theme::OceanLight => include_str!("assets/light-ocean.scss"), + Theme::MagicDark => include_str!("assets/dark-magic.scss"), }; let style = format!("{}\n{}", vars, include_str!("assets/base.scss")); @@ -33,6 +34,7 @@ pub fn get_code_theme_for_theme(theme: Theme) -> (syntect::highlighting::Theme, Theme::SolarizedLight => "Solarized (light)", Theme::OceanDark => "base16-ocean.dark", Theme::OceanLight => "base16-ocean.light", + Theme::MagicDark => "base16-ocean.dark", }; return (TS.themes[theme].clone(), PS.clone()); diff --git a/src/main.rs b/src/main.rs index 3bd88b6..ddb7d5c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -207,10 +207,7 @@ fn render_format(opt: &RenderOptions, document: Document, writer: BufWriter<File } fn render_html(document: Document, writer: BufWriter<File>) { - let mut writer = HTMLWriter::new( - Box::new(writer), - document.config.lock().formatting.theme.clone(), - ); + let mut writer = HTMLWriter::new(Box::new(writer), document.config.lock().style.theme.clone()); document.to_html(&mut writer).unwrap(); writer.flush().unwrap(); } diff --git a/src/parser/inline.rs b/src/parser/inline.rs index 5be3fa6..2665307 100644 --- a/src/parser/inline.rs +++ b/src/parser/inline.rs @@ -373,7 +373,7 @@ impl ParseInline for Parser { .document .config .lock() - .formatting + .style .bib_ref_display .clone(), ), diff --git a/src/settings/mod.rs b/src/settings/mod.rs index d742c03..a76cec0 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -1,10 +1,10 @@ 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 crate::settings::style_settings::StyleSettings; use config::{ConfigError, Source}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -15,11 +15,11 @@ 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 mod style_settings; pub type SettingsResult<T> = Result<T, SettingsError>; @@ -67,7 +67,7 @@ pub struct Settings { pub imports: ImportSettings, pub pdf: PDFSettings, pub images: ImageSettings, - pub formatting: FormatSettings, + pub style: StyleSettings, pub custom_attributes: HashMap<String, String>, } diff --git a/src/settings/format_settings.rs b/src/settings/style_settings.rs similarity index 85% rename from src/settings/format_settings.rs rename to src/settings/style_settings.rs index 07b7748..c5b43d9 100644 --- a/src/settings/format_settings.rs +++ b/src/settings/style_settings.rs @@ -1,12 +1,12 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone, Debug)] -pub struct FormatSettings { +pub struct StyleSettings { pub bib_ref_display: String, pub theme: Theme, } -impl Default for FormatSettings { +impl Default for StyleSettings { fn default() -> Self { Self { bib_ref_display: "{{number}}".to_string(), @@ -22,4 +22,5 @@ pub enum Theme { SolarizedLight, OceanDark, OceanLight, + MagicDark, } From d0186cc90e8e73bdf1ea95f98da200d4aa0784a7 Mon Sep 17 00:00:00 2001 From: trivernis <trivernis@protonmail.com> Date: Thu, 17 Dec 2020 16:52:45 +0100 Subject: [PATCH 6/7] Fix toc not using plain text Signed-off-by: trivernis <trivernis@protonmail.com> --- src/elements/mod.rs | 89 ++++++++++++++++++++++++++++++++++++++++++--- src/parser/block.rs | 1 + 2 files changed, 85 insertions(+), 5 deletions(-) diff --git a/src/elements/mod.rs b/src/elements/mod.rs index d64c704..631de81 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -252,7 +252,7 @@ pub struct Placeholder { #[derive(Clone, Debug)] pub struct RefLink { - pub(crate) description: Box<Line>, + pub(crate) description: TextLine, pub(crate) reference: String, } @@ -352,7 +352,7 @@ impl Document { list.ordered = ordered; self.elements.iter().for_each(|e| match e { Block::Section(sec) => { - if !sec.get_hide_in_toc() { + if !sec.is_hidden_in_toc() { let mut item = ListItem::new(Line::RefLink(sec.header.get_anchor()), 1, ordered); item.children.append(&mut sec.get_toc_list(ordered).items); @@ -486,9 +486,10 @@ impl Section { pub fn get_toc_list(&self, ordered: bool) -> List { let mut list = List::new(); + self.elements.iter().for_each(|e| { if let Block::Section(sec) = e { - if !sec.get_hide_in_toc() { + if !sec.is_hidden_in_toc() { let mut item = ListItem::new(Line::RefLink(sec.header.get_anchor()), 1, ordered); item.children.append(&mut sec.get_toc_list(ordered).items); @@ -500,7 +501,7 @@ impl Section { list } - pub(crate) fn get_hide_in_toc(&self) -> bool { + pub(crate) fn is_hidden_in_toc(&self) -> bool { if let Some(meta) = &self.metadata { meta.get_bool("toc-hidden") } else { @@ -556,7 +557,7 @@ impl Header { pub fn get_anchor(&self) -> RefLink { RefLink { - description: Box::new(self.line.clone()), + description: self.line.as_raw_text().as_plain_line(), reference: self.anchor.clone(), } } @@ -612,6 +613,16 @@ impl TextLine { pub fn add_subtext(&mut self, subtext: Inline) { self.subtext.push(subtext) } + + pub fn as_plain_line(&self) -> TextLine { + TextLine { + subtext: self + .subtext + .iter() + .map(|s| Inline::Plain(s.as_plain_text())) + .collect(), + } + } } impl Table { @@ -814,3 +825,71 @@ impl MetadataValue { } } } + +impl Line { + pub fn as_raw_text(&self) -> TextLine { + match self { + Line::Text(t) => t.clone(), + Line::Ruler(_) => TextLine::new(), + Line::RefLink(r) => r.description.clone(), + Line::Anchor(a) => a.inner.as_raw_text().as_plain_line(), + Line::Centered(c) => c.line.clone(), + Line::BibEntry(_) => TextLine::new(), + } + } +} + +impl Inline { + pub fn as_plain_text(&self) -> PlainText { + match self { + Inline::Plain(p) => p.clone(), + Inline::Bold(b) => b.value.iter().fold( + PlainText { + value: String::new(), + }, + |a, b| PlainText { + value: format!("{} {}", a.value, b.as_plain_text().value), + }, + ), + Inline::Italic(i) => i.value.iter().fold( + PlainText { + value: String::new(), + }, + |a, b| PlainText { + value: format!("{} {}", a.value, b.as_plain_text().value), + }, + ), + Inline::Underlined(u) => u.value.iter().fold( + PlainText { + value: String::new(), + }, + |a, b| PlainText { + value: format!("{} {}", a.value, b.as_plain_text().value), + }, + ), + Inline::Striked(s) => s.value.iter().fold( + PlainText { + value: String::new(), + }, + |a, b| PlainText { + value: format!("{} {}", a.value, b.as_plain_text().value), + }, + ), + Inline::Monospace(m) => PlainText { + value: m.value.clone(), + }, + Inline::Superscript(s) => s.value.iter().fold( + PlainText { + value: String::new(), + }, + |a, b| PlainText { + value: format!("{} {}", a.value, b.as_plain_text().value), + }, + ), + Inline::Colored(c) => c.value.as_plain_text(), + _ => PlainText { + value: String::new(), + }, + } + } +} diff --git a/src/parser/block.rs b/src/parser/block.rs index c85683a..e25bc61 100644 --- a/src/parser/block.rs +++ b/src/parser/block.rs @@ -78,6 +78,7 @@ impl ParseBlock for Parser { fn parse_section(&mut self) -> ParseResult<Section> { let start_index = self.ctm.get_index(); self.ctm.seek_whitespace(); + if self.ctm.check_char(&HASH) { let mut size = 1; while let Some(_) = self.ctm.next_char() { From 245c90841003c2382150e583104ea5d85594cd31 Mon Sep 17 00:00:00 2001 From: trivernis <trivernis@protonmail.com> Date: Thu, 17 Dec 2020 17:15:58 +0100 Subject: [PATCH 7/7] Fix nested anchors and placeholders Signed-off-by: trivernis <trivernis@protonmail.com> --- src/parser/block.rs | 5 +++-- src/parser/line.rs | 3 +++ src/parser/mod.rs | 4 +++- src/references/placeholders.rs | 12 ++++++++++++ 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/parser/block.rs b/src/parser/block.rs index e25bc61..c2c5a40 100644 --- a/src/parser/block.rs +++ b/src/parser/block.rs @@ -102,6 +102,8 @@ impl ParseBlock for Parser { header.size = size; self.section_nesting = size; self.sections.push(size); + self.section_anchors.push(header.anchor.clone()); + let mut section = Section::new(header); section.metadata = metadata; self.ctm.seek_whitespace(); @@ -111,6 +113,7 @@ impl ParseBlock for Parser { } self.sections.pop(); + self.section_anchors.pop(); if let Some(sec) = self.sections.last() { self.section_nesting = *sec } else { @@ -337,8 +340,6 @@ impl ParseBlock for Parser { .map(|m| m.get_string_map()) .unwrap_or(HashMap::new()); - self.ctm.seek_whitespace(); - match self.import(path.clone(), &metadata) { ImportType::Document(Ok(anchor)) => Ok(Some(Import { path, anchor })), ImportType::Stylesheet(_) => Ok(None), diff --git a/src/parser/line.rs b/src/parser/line.rs index 151224e..13cdeef 100644 --- a/src/parser/line.rs +++ b/src/parser/line.rs @@ -53,6 +53,9 @@ impl ParseLine for Parser { self.ctm.get_text()[start_index..self.ctm.get_index()] .iter() .for_each(|e| anchor.push(*e)); + if let Some(last) = self.section_anchors.last() { + anchor = format!("{}-{}", last, anchor); + } anchor.retain(|c| !c.is_whitespace()); log::trace!("Line::Header"); Ok(Header::new(line, anchor)) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f082081..a05d390 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -88,6 +88,7 @@ pub struct Parser { pub(crate) ctm: CharTapeMachine, section_nesting: u8, sections: Vec<u8>, + section_anchors: Vec<String>, section_return: Option<u8>, wg: WaitGroup, pub(crate) block_break_at: Vec<char>, @@ -111,6 +112,7 @@ impl Parser { Self { options, sections: Vec::new(), + section_anchors: Vec::new(), section_nesting: 0, section_return: None, wg: WaitGroup::new(), @@ -140,7 +142,7 @@ impl Parser { text_unil.reverse(); let mut inline_pos = 0; - while text_unil[inline_pos] != LB { + while inline_pos < text_unil.len() && text_unil[inline_pos] != LB { inline_pos += 1; } if let Some(path) = &self.options.path { diff --git a/src/references/placeholders.rs b/src/references/placeholders.rs index 9376fc7..14f8950 100644 --- a/src/references/placeholders.rs +++ b/src/references/placeholders.rs @@ -35,6 +35,8 @@ const P_GLS: &str = "gls"; const P_DATE: &str = "date"; const P_TIME: &str = "time"; const P_DATETIME: &str = "datetime"; +const P_AUTHOR: &str = "author"; +const P_TITLE: &str = "title"; impl ProcessPlaceholders for Document { /// parses all placeholders and assigns values to them @@ -65,6 +67,16 @@ impl ProcessPlaceholders for Document { P_DATETIME => pholder.set_value(inline!(Inline::Plain(PlainText { value: format!("{} {}", get_date_string(), get_time_string()) }))), + P_AUTHOR => { + if let Some(value) = self.config.lock().metadata.author.clone() { + pholder.set_value(inline!(Inline::Plain(PlainText { value }))) + } + } + P_TITLE => { + if let Some(value) = self.config.lock().metadata.title.clone() { + pholder.set_value(inline!(Inline::Plain(PlainText { value }))) + } + } _ => { if let Some(value) = self .config