Merge pull request #8 from Trivernis/develop

New Config and Themes
pull/10/head
Trivernis 3 years ago committed by GitHub
commit d1e71e0204
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

178
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]]
@ -185,6 +185,12 @@ version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
[[package]]
name = "bytecount"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72feb31ffc86498dacdbd0fcebb56138e7177a8cc5cea4516031d15ae85a742e"
[[package]]
name = "bytemuck"
version = "1.4.1"
@ -235,7 +241,7 @@ checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
"num-traits",
"num-traits 0.2.14",
"time",
"winapi 0.3.9",
]
@ -292,6 +298,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 +842,7 @@ dependencies = [
"log 0.4.11",
"rand 0.7.3",
"regex",
"serde",
"serde 1.0.118",
"serde_derive",
"serde_json",
"tempfile",
@ -992,7 +1014,7 @@ dependencies = [
"jpeg-decoder",
"num-iter",
"num-rational",
"num-traits",
"num-traits 0.2.14",
"png",
"scoped_threadpool",
"tiff",
@ -1117,6 +1139,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 +1167,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 +1359,28 @@ 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 = "nom_locate"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a67484adf5711f94f2f28b653bf231bff8e438be33bf5b0f35935a0db4f618a2"
dependencies = [
"bytecount",
"memchr",
"nom",
]
[[package]]
name = "notify"
version = "4.0.15"
@ -1332,6 +1399,17 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "num-bigint"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e9a41747ae4633fce5adffb4d2e81ffc5e89593cb19917f8fb2cc5ff76507bf"
dependencies = [
"autocfg 1.0.1",
"num-integer",
"num-traits 0.2.14",
]
[[package]]
name = "num-integer"
version = "0.1.44"
@ -1339,7 +1417,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 +1428,7 @@ checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
dependencies = [
"autocfg 1.0.1",
"num-integer",
"num-traits",
"num-traits 0.2.14",
]
[[package]]
@ -1361,7 +1439,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 +1687,7 @@ dependencies = [
"chrono",
"indexmap",
"line-wrap",
"serde",
"serde 1.0.118",
"xml-rs",
]
@ -1937,7 +2024,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",
@ -1949,6 +2036,22 @@ dependencies = [
"winreg 0.7.0",
]
[[package]]
name = "rsass"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c1144f0d9fd5df1f8fbac387e91b73a2d315e013ee6beb48353a71d8ec2c5ab"
dependencies = [
"lazy_static",
"nom",
"nom_locate",
"num-bigint",
"num-integer",
"num-rational",
"num-traits 0.2.14",
"rand 0.7.3",
]
[[package]]
name = "rust-argon2"
version = "0.8.3"
@ -1961,6 +2064,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 +2154,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 +2169,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 +2201,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 +2222,7 @@ dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
"serde 1.0.118",
]
[[package]]
@ -2135,6 +2272,7 @@ dependencies = [
"charred",
"chrono",
"colored",
"config",
"crossbeam-utils 0.7.2",
"env_logger 0.7.1",
"failure",
@ -2155,8 +2293,8 @@ dependencies = [
"rayon",
"regex",
"reqwest",
"serde",
"serde_derive",
"rsass",
"serde 1.0.118",
"sha2",
"structopt",
"syntect",
@ -2175,6 +2313,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 +2404,7 @@ dependencies = [
"onig",
"plist",
"regex-syntax",
"serde",
"serde 1.0.118",
"serde_derive",
"serde_json",
"walkdir",
@ -2403,7 +2547,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 +2758,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 +2968,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,8 @@ platform-dirs = "0.2.0"
image = "0.23.12"
parking_lot = "0.11.1"
sha2 = "0.9.2"
config = "0.10.1"
rsass = "0.16.0"
headless_chrome = {version = "0.9.0", optional = true}
failure = {version = "0.1.8", optional = true}
headless_chrome = { version = "0.9.0", optional = true }
failure = { version = "0.1.8", optional = true }

@ -197,90 +197,111 @@ Placeholder
```
Metadata can also be defined in a separate toml file with simple key-value pairs.
The file `Manifest.toml` will always be included by default.
Example:
```toml
# bibliography.bib.toml
author = "Snek"
published = "2020"
test-key = ["test value", "test value 2"]
# those files won't get imported
ignored-imports = ["style.css"]
# stylesheets that should be included
included-stylesheets = ["style2.css"]
# document metadata
[metadata]
# language setting of the document
language = 'en'
# other metadata files that should be included
included-configs = []
# author of the document
author = 'author'
# bibliography that should be included
included-bibliography = ["mybib.toml"]
# glossary that sould be included
included-glossary = ["myglossary.toml"]
# features used in the document
[features]
# if external sources (images, stylesheets, MathJax)
# should be embedded into the document (default: true)
embed-external = true
embed_external = true
# If SmartArrows should be used (default: true)
smart-arrows = true
# Includes a MathJax script tag in the document to render MathML in chromium.
# (default: true)
include-math-jax = true
smart_arrows = true
include_mathjax = true
### Image processing options ###
[imports]
# those files won't get imported
ignored_imports = []
# Force convert images to the specified format.
# Supported formats are png, jpeg, gif, bmp, (ico needs size <= 256), avif, pnm
# (default: keep original)
image-format = "jpg"
# stylesheets that should be included
included_stylesheets = ['style.css']
# the max width for the images.
# if an image is larger than that it get's resized.
# (default: none)
image-max-width = 700
# bibliography that should be included
included_bibliography = ['Bibliography.toml']
# the max width for the images.
# if an image is larger than that it get's resized.
# (default: none)
image-max-height = 800
# glossary that sould be included
included_glossaries = ['Glossary.toml']
### PDF Options - needs the pdf feature enabled ###
# settings related to pdf rendering
[pdf]
# If the header and footer of the pdf should be displayed (default: true)
pdf-display-header-footer = true
display_header_footer = true
# PDF header template of each page (default: empty)
pdf-header-template = "<div><span class='title'></span></div>"
# PDF header template of each page (default: '<div></div>')
header_template = '<div></div>'
# PDF footer template of each page (default: see chromium_pdf assets)
pdf-footer-template = "<div><span class='pageNumber'></span></div>"
footer_template = '''
<div style="font-size: 10px; text-align: center; width: 100%;">
<span class="pageNumber"></span>/<span class="totalPages"></span>
</div>'''
# Top margin of the pdf. Should be between 0 and 1. (default: 1.0)
pdf-margin-top = 1
# The scale at which the website is rendered into pdf.
page_scale = 1.0
# margin of the pdf document
[pdf.margin]
# Top margin of the pdf. Should be between 0 and 1. (default: 0.5)
top = 0.5
# Bottom margin of the pdf. Should be between 0 and 1. (default: 1.0)
pdf-margin-bottom = 1
# Bottom margin of the pdf. Should be between 0 and 1. (default: 0.5)
bottom = 0.5
# Left margin of the pdf. Should be between 0 and 1.
pdf-margin-left = 0
left = 0
# Right margin of the pdf. Should be between 0 and 1.
pdf-margin-right = 0
right = 0
# Page height of the pdf
pdf-page-height = 100
# Page width of the pdf
pdf-page-width = 80
# image settings
[images]
# The scale at which the website is rendered into pdf.
pdf-page-scale = 1.0
# Force convert images to the specified format.
# Supported formats are png, jpeg, gif, bmp, (ico needs size <= 256), avif, pnm
# (default: keep original)
format = "png"
# the max width for the images.
# if an image is larger than that it get's resized.
# (default: none)
max_width = 700
# the max width for the images.
# if an image is larger than that it get's resized.
# (default: none)
max_height = 500
# Visual adjustments
[style]
# how bibliography references should be displayed
bib_ref_display = '{{number}}'
# the chosen theme for the document
# one of: GithubLight, SolarizedLight, OceanLight, SolarizedDark, OceanDark, MagicDark
theme = 'GithubLight'
# 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>>,
@ -256,7 +252,7 @@ pub struct Placeholder {
#[derive(Clone, Debug)]
pub struct RefLink {
pub(crate) description: Box<Line>,
pub(crate) description: TextLine,
pub(crate) reference: String,
}
@ -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())),
@ -356,7 +352,7 @@ impl Document {
list.ordered = ordered;
self.elements.iter().for_each(|e| match e {
Block::Section(sec) => {
if !sec.get_hide_in_toc() {
if !sec.is_hidden_in_toc() {
let mut item =
ListItem::new(Line::RefLink(sec.header.get_anchor()), 1, ordered);
item.children.append(&mut sec.get_toc_list(ordered).items);
@ -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;
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;
}
}
@ -507,9 +486,10 @@ impl Section {
pub fn get_toc_list(&self, ordered: bool) -> List {
let mut list = List::new();
self.elements.iter().for_each(|e| {
if let Block::Section(sec) = e {
if !sec.get_hide_in_toc() {
if !sec.is_hidden_in_toc() {
let mut item =
ListItem::new(Line::RefLink(sec.header.get_anchor()), 1, ordered);
item.children.append(&mut sec.get_toc_list(ordered).items);
@ -521,7 +501,7 @@ impl Section {
list
}
pub(crate) fn get_hide_in_toc(&self) -> bool {
pub(crate) fn is_hidden_in_toc(&self) -> bool {
if let Some(meta) = &self.metadata {
meta.get_bool("toc-hidden")
} else {
@ -577,7 +557,7 @@ impl Header {
pub fn get_anchor(&self) -> RefLink {
RefLink {
description: Box::new(self.line.clone()),
description: self.line.as_raw_text().as_plain_line(),
reference: self.anchor.clone(),
}
}
@ -633,6 +613,16 @@ impl TextLine {
pub fn add_subtext(&mut self, subtext: Inline) {
self.subtext.push(subtext)
}
pub fn as_plain_line(&self) -> TextLine {
TextLine {
subtext: self
.subtext
.iter()
.map(|s| Inline::Plain(s.as_plain_text()))
.collect(),
}
}
}
impl Table {
@ -766,19 +756,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 +779,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 +796,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());
@ -853,3 +825,71 @@ impl MetadataValue {
}
}
}
impl Line {
pub fn as_raw_text(&self) -> TextLine {
match self {
Line::Text(t) => t.clone(),
Line::Ruler(_) => TextLine::new(),
Line::RefLink(r) => r.description.clone(),
Line::Anchor(a) => a.inner.as_raw_text().as_plain_line(),
Line::Centered(c) => c.line.clone(),
Line::BibEntry(_) => TextLine::new(),
}
}
}
impl Inline {
pub fn as_plain_text(&self) -> PlainText {
match self {
Inline::Plain(p) => p.clone(),
Inline::Bold(b) => b.value.iter().fold(
PlainText {
value: String::new(),
},
|a, b| PlainText {
value: format!("{} {}", a.value, b.as_plain_text().value),
},
),
Inline::Italic(i) => i.value.iter().fold(
PlainText {
value: String::new(),
},
|a, b| PlainText {
value: format!("{} {}", a.value, b.as_plain_text().value),
},
),
Inline::Underlined(u) => u.value.iter().fold(
PlainText {
value: String::new(),
},
|a, b| PlainText {
value: format!("{} {}", a.value, b.as_plain_text().value),
},
),
Inline::Striked(s) => s.value.iter().fold(
PlainText {
value: String::new(),
},
|a, b| PlainText {
value: format!("{} {}", a.value, b.as_plain_text().value),
},
),
Inline::Monospace(m) => PlainText {
value: m.value.clone(),
},
Inline::Superscript(s) => s.value.iter().fold(
PlainText {
value: String::new(),
},
|a, b| PlainText {
value: format!("{} {}", a.value, b.as_plain_text().value),
},
),
Inline::Colored(c) => c.value.as_plain_text(),
_ => PlainText {
value: String::new(),
},
}
}
}

@ -0,0 +1,159 @@
body {
background-color: $body-background;
overflow-x: hidden;
color: $primary-color;
word-break: break-word;
}
.content {
font-family: "Fira Sans", "Noto Sans", SansSerif, sans-serif;
width: 100vh;
max-width: calc(100% - 4rem);
padding: 2rem;
margin: auto;
background-color: $background-color;
}
h1 {
font-size: 2.2rem;
}
h2 {
font-size: 1.8rem;
}
h3 {
font-size: 1.4rem;
}
h4 {
font-size: 1rem;
}
h5 {
font-size: 0.8rem;
}
h6 {
font-size: 0.4rem;
}
img {
max-width: 100%;
max-height: 100vh;
height: auto;
}
code {
color: $primary-color;
pre {
font-family: "Fira Code", "Mono", monospace;
padding: 0.8em 0.2em;
background-color: $code-background !important;
border-radius: 0.25em;
overflow: auto;
}
&.inlineCode {
font-family: "Fira Code", monospace;
border-radius: 0.1em;
background-color: $code-background;
padding: 0 0.1em;
}
}
.tableWrapper {
overflow-x: auto;
width: 100%;
& > table {
margin: auto;
}
}
table {
border-collapse: collapse;
tr {
&:nth-child(odd) {
background-color: $table-background-alt;
}
&:nth-child(1) {
background-color: $table-background-alt;
font-weight: bold;
border-bottom: 1px solid invert($background-color)
}
}
}
table td, table th {
border-left: 1px solid invert($background-color);
padding: 0.2em 0.5em;
}
table tr td:first-child, table tr th:first-child {
border-left: none;
}
blockquote {
margin-left: 0;
background-color: rgba(0, 0, 0, 0);
}
a {
color: $secondary-color;
}
.quote {
border-left: 0.3em solid $quote-background-alt;
border-radius: 0.2em;
padding-left: 1em;
margin-left: 0;
background-color: $quote-background;
.metadata {
font-style: italic;
padding-left: 0.5em;
color: $primary-variant-1;
}
}
.figure {
width: 100%;
display: block;
text-align: center;
.imageDescription {
display: block;
color: $primary-variant-1;
font-style: italic;
}
}
.centered {
text-align: center;
}
.glossaryReference {
text-decoration: none;
color: inherit;
border-bottom: 1px dotted $primary-color;
}
.arrow {
font-family: "Fira Code", "Mono", monospace;
}
@media print {
.content > section > section, .content > section > section {
page-break-inside: avoid;
}
body {
background-color: $background-color !important;
}
}

@ -0,0 +1,14 @@
$background-color: #1e1d2c;
$background-color-variant-1: lighten($background-color, 7%);
$background-color-variant-2: lighten($background-color, 14%);
$background-color-variant-3: lighten($background-color, 21%);
$primary-color: #EEE;
$primary-variant-1: darken($primary-color, 14%);
$secondary-color: #3aa7df;
$body-background: darken($background-color, 5%);
$code-background: lighten($background-color, 5%);
$table-background-alt: $background-color-variant-2;
$quote-background: lighten($background-color-variant-1, 3%);
$quote-background-alt: $background-color-variant-3;

@ -0,0 +1,14 @@
$background-color: darken(#2b303b, 8%);
$background-color-variant-1: lighten($background-color, 7%);
$background-color-variant-2: lighten($background-color, 14%);
$background-color-variant-3: lighten($background-color, 21%);
$primary-color: #EEE;
$primary-variant-1: darken($primary-color, 14%);
$secondary-color: #3aa7df;
$body-background: darken($background-color, 5%);
$code-background: $background-color-variant-1;
$table-background-alt: $background-color-variant-2;
$quote-background: lighten($background-color-variant-1, 3%);
$quote-background-alt: $background-color-variant-3;

@ -0,0 +1,13 @@
$background-color: darken(#002b36, 5%);
$background-color-variant-1: lighten($background-color, 7%);
$background-color-variant-2: lighten($background-color, 14%);
$background-color-variant-3: lighten($background-color, 21%);
$primary-color: #EEE;
$primary-variant-1: darken($primary-color, 14%);
$secondary-color: #0096c9;
$body-background: darken($background-color, 3%);
$code-background: $background-color-variant-1;
$table-background-alt: lighten($background-color, 10%);
$quote-background: lighten($background-color-variant-1, 3%);
$quote-background-alt: $background-color-variant-3;

@ -0,0 +1,13 @@
$background-color: #FFF;
$background-color-variant-1: darken($background-color, 7%);
$background-color-variant-2: darken($background-color, 14%);
$background-color-variant-3: darken($background-color, 21%);
$primary-color: #000;
$primary-variant-1: lighten($primary-color, 14%);
$secondary-color: #00286a;
$body-background: $background-color-variant-1;
$code-background: $background-color-variant-1;
$table-background-alt: $background-color-variant-2;
$quote-background: $background-color-variant-2;
$quote-background-alt: $background-color-variant-3;

@ -0,0 +1,13 @@
$background-color: #FFF;
$background-color-variant-1: darken($background-color, 7%);
$background-color-variant-2: darken($background-color, 14%);
$background-color-variant-3: darken($background-color, 21%);
$primary-color: #112;
$primary-variant-1: lighten($primary-color, 14%);
$secondary-color: #00348e;
$body-background: $background-color-variant-1;
$code-background: $background-color-variant-1;
$table-background-alt: $background-color-variant-2;
$quote-background: $background-color-variant-2;
$quote-background-alt: $background-color-variant-3;

@ -0,0 +1,13 @@
$background-color: #fff8f0;
$background-color-variant-1: darken($background-color, 4%);
$background-color-variant-2: darken($background-color, 8%);
$background-color-variant-3: darken($background-color, 12%);
$primary-color: #112;
$primary-variant-1: lighten($primary-color, 14%);
$secondary-color: #2b61be;
$body-background: $background-color-variant-1;
$code-background: $background-color-variant-1;
$table-background-alt: $background-color-variant-2;
$quote-background: $background-color-variant-2;
$quote-background-alt: $background-color-variant-3;

@ -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,149 +0,0 @@
body {
background-color: #DDD;
overflow-x: hidden;
color: #000;
word-break: break-word;
}
.content {
font-family: "Fira Sans", "Noto Sans", SansSerif, sans-serif;
width: 100vh;
max-width: calc(100% - 4rem);
padding: 2rem;
margin: auto;
background-color: #FFF;
}
h1 {
font-size: 2.2rem;
}
h2 {
font-size: 1.8rem;
}
h3 {
font-size: 1.4rem;
}
h4 {
font-size: 1rem;
}
h5 {
font-size: 0.8rem;
}
h6 {
font-size: 0.4rem;
}
img {
max-width: 100%;
max-height: 100vh;
height: auto;
}
code {
color: #000;
}
code pre {
font-family: "Fira Code", "Mono", monospace;
padding: 0.8em 0.2em;
background-color: #EEE !important;
border-radius: 0.25em;
}
code.inlineCode {
font-family: "Fira Code", monospace;
border-radius: 0.1em;
background-color: #EEE;
padding: 0 0.1em
}
.tableWrapper {
overflow-x: auto;
width: 100%;
}
.tableWrapper > table {
margin: auto;
}
table {
border-collapse: collapse;
}
table tr:nth-child(odd) {
background-color: #DDD;
}
table tr:nth-child(1) {
background-color: white;
font-weight: bold;
border-bottom: 1px solid black;
}
table td, table th {
border-left: 1px solid black;
padding: 0.2em 0.5em
}
table tr td:first-child, table tr th:first-child {
border-left: none;
}
blockquote {
margin-left: 0;
background-color: rgba(0, 0, 0, 0);
}
.quote {
border-left: 0.3em solid gray;
border-radius: 0.2em;
padding-left: 1em;
margin-left: 0;
background-color: #EEE;
}
.quote .metadata {
font-style: italic;
padding-left: 0.5em;
color: #444
}
.figure {
width: 100%;
display: block;
text-align: center;
}
.figure .imageDescription {
display: block;
color: #444;
font-style: italic;
}
.centered {
text-align: center;
}
.glossaryReference {
text-decoration: none;
color: inherit;
border-bottom: 1px dotted #000;
}
.arrow {
font-family: "Fira Code", "Mono", monospace;
}
@media print {
.content > section > section, .content > section > section {
page-break-inside: avoid;
}
body {
background-color: white !important;
}
}

@ -1,14 +1,16 @@
use crate::settings::style_settings::Theme;
use std::io;
use std::io::Write;
pub struct HTMLWriter {
inner: Box<dyn Write>,
theme: Theme,
}
impl HTMLWriter {
/// Creates a new writer
pub fn new(inner: Box<dyn Write>) -> Self {
Self { inner }
pub fn new(inner: Box<dyn Write>, theme: Theme) -> Self {
Self { inner, theme }
}
/// Writes a raw string
@ -30,4 +32,9 @@ impl HTMLWriter {
pub fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
/// Return the theme of the html writer
pub fn get_theme(&mut self) -> Theme {
self.theme.clone()
}
}

@ -1,16 +1,14 @@
use crate::elements::*;
use crate::format::html::html_writer::HTMLWriter;
use crate::format::style::{get_code_theme_for_theme, get_css_for_theme};
use crate::format::PlaceholderTemplate;
use crate::references::configuration::keys::{INCLUDE_MATHJAX, META_LANG};
use crate::references::glossary::{GlossaryDisplay, GlossaryReference};
use crate::references::templates::{Template, TemplateVariable};
use asciimath_rs::format::mathml::ToMathML;
use htmlescape::encode_attribute;
use minify::html::minify;
use std::io;
use syntect::highlighting::ThemeSet;
use syntect::html::highlighted_html_for_string;
use syntect::parsing::SyntaxSet;
const MATHJAX_URL: &str = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js";
@ -107,12 +105,9 @@ 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 style = minify(std::include_str!("assets/style.css"));
let language = self.config.lock().metadata.language.clone();
let style = minify(get_css_for_theme(writer.get_theme()).as_str());
writer.write("<!DOCTYPE html>".to_string())?;
writer.write("<html lang=\"".to_string())?;
writer.write_attribute(language)?;
@ -128,12 +123,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
@ -321,19 +311,19 @@ impl ToHtml for Cell {
impl ToHtml for CodeBlock {
fn to_html(&self, writer: &mut HTMLWriter) -> io::Result<()> {
writer.write("<div><code".to_string())?;
if self.language.len() > 0 {
writer.write(" lang=\"".to_string())?;
writer.write_attribute(self.language.clone())?;
writer.write("\">".to_string())?;
lazy_static::lazy_static! { static ref PS: SyntaxSet = SyntaxSet::load_defaults_nonewlines(); }
lazy_static::lazy_static! { static ref TS: ThemeSet = ThemeSet::load_defaults(); }
let (theme, syntax_set) = get_code_theme_for_theme(writer.get_theme());
if let Some(syntax) = PS.find_syntax_by_token(self.language.as_str()) {
if let Some(syntax) = syntax_set.find_syntax_by_token(self.language.as_str()) {
writer.write(highlighted_html_for_string(
self.code.as_str(),
&PS,
&syntax_set,
syntax,
&TS.themes["InspiredGitHub"],
&theme,
))?;
} else {
writer.write("<pre>".to_string())?;

@ -4,6 +4,7 @@ use std::collections::HashMap;
#[cfg(feature = "pdf")]
pub mod chromium_pdf;
pub mod html;
pub mod style;
pub struct PlaceholderTemplate {
value: String,

@ -0,0 +1,55 @@
use crate::settings::style_settings::Theme;
use std::time::Instant;
use syntect::highlighting::ThemeSet;
use syntect::parsing::SyntaxSet;
/// Returns the css of a theme compiled from sass
pub fn get_css_for_theme(theme: Theme) -> String {
let start = Instant::now();
let vars = match theme {
Theme::GitHub => include_str!("assets/light-github.scss"),
Theme::SolarizedDark => include_str!("assets/dark-solarized.scss"),
Theme::SolarizedLight => include_str!("assets/light-solarized.scss"),
Theme::OceanDark => include_str!("assets/dark-ocean.scss"),
Theme::OceanLight => include_str!("assets/light-ocean.scss"),
Theme::MagicDark => include_str!("assets/dark-magic.scss"),
};
let style = format!("{}\n{}", vars, include_str!("assets/base.scss"));
let css = compile_sass(&*style);
log::debug!("Compiled style in {} ms", start.elapsed().as_millis());
css
}
/// Returns the syntax theme for a given theme
pub fn get_code_theme_for_theme(theme: Theme) -> (syntect::highlighting::Theme, SyntaxSet) {
lazy_static::lazy_static! { static ref PS: SyntaxSet = SyntaxSet::load_defaults_nonewlines(); }
lazy_static::lazy_static! { static ref TS: ThemeSet = ThemeSet::load_defaults(); }
let theme = match theme {
Theme::GitHub => "InspiredGitHub",
Theme::SolarizedDark => "Solarized (dark)",
Theme::SolarizedLight => "Solarized (light)",
Theme::OceanDark => "base16-ocean.dark",
Theme::OceanLight => "base16-ocean.light",
Theme::MagicDark => "base16-ocean.dark",
};
return (TS.themes[theme].clone(), PS.clone());
}
fn compile_sass(sass: &str) -> String {
String::from_utf8(
rsass::compile_scss(
sass.as_bytes(),
rsass::output::Format {
style: rsass::output::Style::Compressed,
precision: 5,
},
)
.unwrap(),
)
.unwrap()
}

@ -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);
@ -177,7 +207,7 @@ fn render_format(opt: &RenderOptions, document: Document, writer: BufWriter<File
}
fn render_html(document: Document, writer: BufWriter<File>) {
let mut writer = HTMLWriter::new(Box::new(writer));
let mut writer = HTMLWriter::new(Box::new(writer), document.config.lock().style.theme.clone());
document.to_html(&mut writer).unwrap();
writer.flush().unwrap();
}

@ -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)
@ -78,6 +78,7 @@ impl ParseBlock for Parser {
fn parse_section(&mut self) -> ParseResult<Section> {
let start_index = self.ctm.get_index();
self.ctm.seek_whitespace();
if self.ctm.check_char(&HASH) {
let mut size = 1;
while let Some(_) = self.ctm.next_char() {
@ -94,13 +95,15 @@ 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()?;
header.size = size;
self.section_nesting = size;
self.sections.push(size);
self.section_anchors.push(header.anchor.clone());
let mut section = Section::new(header);
section.metadata = metadata;
self.ctm.seek_whitespace();
@ -110,6 +113,7 @@ impl ParseBlock for Parser {
}
self.sections.pop();
self.section_anchors.pop();
if let Some(sec) = self.sections.last() {
self.section_nesting = *sec
} else {
@ -117,7 +121,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 +171,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 +190,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 +217,7 @@ impl ParseBlock for Parser {
if paragraph.elements.len() > 0 {
Ok(paragraph)
} else {
Err(self.ctm.err())
Err(self.ctm.err().into())
}
}
@ -273,7 +277,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 +323,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,22 +332,20 @@ 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();
match self.import(path.clone(), &metadata) {
ImportType::Document(Ok(anchor)) => Ok(Some(Import { path, anchor })),
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()
.style
.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())
}
}
}
@ -53,6 +53,9 @@ impl ParseLine for Parser {
self.ctm.get_text()[start_index..self.ctm.get_index()]
.iter()
.for_each(|e| anchor.push(*e));
if let Some(last) = self.section_anchors.last() {
anchor = format!("{}-{}", last, anchor);
}
anchor.retain(|c| !c.is_whitespace());
log::trace!("Line::Header");
Ok(Header::new(line, anchor))
@ -76,11 +79,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 +99,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 +137,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 +184,7 @@ impl ParseLine for Parser {
if text.subtext.len() > 0 {
Ok(text)
} else {
Err(self.ctm.err())
Err(self.ctm.err().into())
}
}
@ -211,7 +214,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 +235,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 {
@ -65,6 +88,7 @@ pub struct Parser {
pub(crate) ctm: CharTapeMachine,
section_nesting: u8,
sections: Vec<u8>,
section_anchors: Vec<String>,
section_return: Option<u8>,
wg: WaitGroup,
pub(crate) block_break_at: Vec<char>,
@ -88,6 +112,7 @@ impl Parser {
Self {
options,
sections: Vec::new(),
section_anchors: Vec::new(),
section_nesting: 0,
section_return: None,
wg: WaitGroup::new(),
@ -117,7 +142,7 @@ impl Parser {
text_unil.reverse();
let mut inline_pos = 0;
while text_unil[inline_pos] != LB {
while inline_pos < text_unil.len() && text_unil[inline_pos] != LB {
inline_pos += 1;
}
if let Some(path) = &self.options.path {
@ -150,7 +175,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 +206,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 +222,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 +247,7 @@ impl Parser {
}
/// Imports a path
fn import(&mut self, path: String, args: &HashMap<String, Value>) -> ImportType {
fn import(&mut self, path: String, args: &HashMap<String, String>) -> ImportType {
log::debug!(
"Importing file {}\n\t--> {}\n",
path,
@ -242,20 +266,9 @@ impl Parser {
.file_name()
.and_then(|f| Some(f.to_str().unwrap().to_string()))
{
if let Some(Value::Array(ignore)) = self
.options
.document
.config
.get_entry(IMP_IGNORE)
.and_then(|e| Some(e.get().clone()))
{
let ignore = ignore
.iter()
.map(|v| v.as_string())
.collect::<Vec<String>>();
if ignore.contains(&fname) {
return ImportType::None;
}
let ignore = &self.options.document.config.lock().imports.ignored_imports;
if ignore.contains(&fname) {
return ImportType::None;
}
}
{
@ -270,7 +283,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 +341,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 +362,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);
}
}
}

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

@ -35,6 +35,8 @@ const P_GLS: &str = "gls";
const P_DATE: &str = "date";
const P_TIME: &str = "time";
const P_DATETIME: &str = "datetime";
const P_AUTHOR: &str = "author";
const P_TITLE: &str = "title";
impl ProcessPlaceholders for Document {
/// parses all placeholders and assigns values to them
@ -65,10 +67,24 @@ impl ProcessPlaceholders for Document {
P_DATETIME => pholder.set_value(inline!(Inline::Plain(PlainText {
value: format!("{} {}", get_date_string(), get_time_string())
}))),
P_AUTHOR => {
if let Some(value) = self.config.lock().metadata.author.clone() {
pholder.set_value(inline!(Inline::Plain(PlainText { value })))
}
}
P_TITLE => {
if let Some(value) = self.config.lock().metadata.title.clone() {
pholder.set_value(inline!(Inline::Plain(PlainText { value })))
}
}
_ => {
if let Some(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 +110,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,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::image_settings::ImageSettings;
use crate::settings::import_settings::ImportSettings;
use crate::settings::metadata_settings::MetadataSettings;
use crate::settings::pdf_settings::PDFSettings;
use crate::settings::style_settings::StyleSettings;
use config::{ConfigError, Source};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
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 image_settings;
pub mod import_settings;
pub mod metadata_settings;
pub mod pdf_settings;
pub mod style_settings;
pub type SettingsResult<T> = Result<T, SettingsError>;
#[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 style: StyleSettings,
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(),
}
}
}

@ -0,0 +1,26 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct StyleSettings {
pub bib_ref_display: String,
pub theme: Theme,
}
impl Default for StyleSettings {
fn default() -> Self {
Self {
bib_ref_display: "{{number}}".to_string(),
theme: Theme::GitHub,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum Theme {
GitHub,
SolarizedDark,
SolarizedLight,
OceanDark,
OceanLight,
MagicDark,
}
Loading…
Cancel
Save