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