Merge pull request #6 from Trivernis/feature/hierarchical-config

Feature/hierarchical config
pull/7/head
Trivernis 4 years ago committed by GitHub
commit 6b9a30c571
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

133
Cargo.lock generated

@ -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",
]

@ -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 }

@ -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>'''
# The scale at which the website is rendered into pdf.
page_scale = 1.0
# Top margin of the pdf. Should be between 0 and 1. (default: 1.0)
pdf-margin-top = 1
# margin of the pdf document
[pdf.margin]
# Bottom margin of the pdf. Should be between 0 and 1. (default: 1.0)
pdf-margin-bottom = 1
# 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: 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.

@ -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<String>,
pub(crate) placeholders: Vec<Arc<RwLock<Placeholder>>>,
pub config: Configuration,
pub config: Arc<Mutex<Settings>>,
pub bibliography: BibManager,
pub downloads: Arc<Mutex<DownloadManager>>,
pub images: Arc<Mutex<ImageConverter>>,
@ -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 {
if self.config.lock().features.embed_external {
downloads.lock().download_all();
}
} else {
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;
let mut image_width = 0;
let mut image_height = 0;
if let Some(Value::Integer(i)) = self
.config
.get_entry(IMAGE_MAX_WIDTH)
.map(|v| v.get().clone())
{
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<HashMap<String, Value>> for InlineMetadata {
fn into(self) -> HashMap<String, Value> {
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<Vec<u8>> {
let mut data = None;
@ -802,15 +768,11 @@ pub struct BibEntry {
pub struct BibReference {
pub(crate) key: String,
pub(crate) entry_anchor: Arc<Mutex<BibRefAnchor>>,
pub(crate) display: Option<ConfigRefEntry>,
pub(crate) display: Option<String>,
}
impl BibReference {
pub fn new(
key: String,
display: Option<ConfigRefEntry>,
anchor: Arc<Mutex<BibRefAnchor>>,
) -> Self {
pub fn new(key: String, display: Option<String>, anchor: Arc<Mutex<BibRefAnchor>>) -> 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());

@ -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<Vec<u8>> {
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<Mutex<Settings>>) -> 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,
}
}

@ -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("<!DOCTYPE html>".to_string())?;
writer.write("<html lang=\"".to_string())?;
@ -128,12 +124,7 @@ impl ToHtml for Document {
let mut stylesheet = stylesheet.lock();
let data = std::mem::replace(&mut stylesheet.data, None);
if let Some(data) = data {
if self
.config
.get_entry(INCLUDE_MATHJAX)
.and_then(|e| e.get().as_bool())
.unwrap_or(true)
{
if self.config.lock().features.include_mathjax {
writer.write(format!(
"<script id=\"MathJax-script\" type=\"text/javascript\" async src={}></script>",
MATHJAX_URL

@ -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;

@ -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);

@ -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<Block> {
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()),
}
}
}

@ -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<PlainText> {
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())
}
}
}

@ -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());
}
}
};

@ -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,22 +264,11 @@ 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>>();
let ignore = &self.options.document.config.lock().imports.ignored_imports;
if ignore.contains(&fname) {
return ImportType::None;
}
}
}
{
let mut paths = self.options.paths.lock().unwrap();
if paths.iter().find(|item| **item == path).is_some() {
@ -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,15 +339,11 @@ 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())},
"Manifest.toml".to_string(),
&maplit::hashmap! {"type".to_string() => "manifest".to_string()},
);
}
}
}
wg.wait();
if !self.options.is_child {
self.import_from_config();
@ -353,59 +360,27 @@ 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() {
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() {
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() {
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);
}
}
}
}
pub(crate) enum ImportType {

@ -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";

@ -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>>(),
)),
}
}
}

@ -1,5 +1,4 @@
pub mod bibliography;
pub mod configuration;
pub mod glossary;
pub mod placeholders;
pub mod templates;

@ -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())
}
}
}

@ -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,
}
}
}

@ -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(),
}
}
}

@ -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,
}
}
}

@ -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()],
}
}
}

@ -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(),
}
}
}

@ -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());
}
}

@ -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(),
}
}
}
Loading…
Cancel
Save