diff --git a/Cargo.lock b/Cargo.lock index e2d0909..354c7de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -150,7 +150,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d" dependencies = [ "byteorder", - "serde", + "serde 1.0.118", ] [[package]] @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index 52283ca..e4f34aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,9 +36,8 @@ colored = "1.9.3" gh-emoji = "1.0.3" notify = "4.0.12" toml = "0.5.6" -serde ="1.0.111" -serde_derive = "1.0.111" -reqwest = {version = "0.10", features=["blocking"]} +serde = { version = "1.0.111", features = ["serde_derive"] } +reqwest = { version = "0.10", features = ["blocking"] } mime_guess = "2.0.3" mime = "0.3.16" base64 = "0.12.3" @@ -51,6 +50,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 } diff --git a/README.md b/README.md index 6206761..c1bd6db 100644 --- a/README.md +++ b/README.md @@ -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 = "
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("".to_string())?;
diff --git a/src/format/mod.rs b/src/format/mod.rs
index 168963b..6fc2726 100644
--- a/src/format/mod.rs
+++ b/src/format/mod.rs
@@ -4,6 +4,7 @@ use std::collections::HashMap;
#[cfg(feature = "pdf")]
pub mod chromium_pdf;
pub mod html;
+pub mod style;
pub struct PlaceholderTemplate {
value: String,
diff --git a/src/format/style.rs b/src/format/style.rs
new file mode 100644
index 0000000..c73eb9c
--- /dev/null
+++ b/src/format/style.rs
@@ -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()
+}
diff --git a/src/lib.rs b/src/lib.rs
index 81e8702..5f5aa8b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,6 +2,7 @@ pub mod elements;
pub mod format;
pub mod parser;
pub mod references;
+pub mod settings;
pub mod utils;
pub use parser::Parser;
diff --git a/src/main.rs b/src/main.rs
index 288e5a9..ddb7d5c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -6,6 +6,7 @@ use snekdown::elements::Document;
use snekdown::format::html::html_writer::HTMLWriter;
use snekdown::format::html::to_html::ToHtml;
use snekdown::parser::ParserOptions;
+use snekdown::settings::Settings;
use snekdown::utils::caching::CacheStorage;
use snekdown::Parser;
use std::fs::{File, OpenOptions};
@@ -31,6 +32,9 @@ enum SubCommand {
/// Parse and render the document.
Render(RenderOptions),
+ /// Initializes the project with default settings
+ Init,
+
/// Clears the cache directory
ClearCache,
}
@@ -94,6 +98,7 @@ fn main() {
let cache = CacheStorage::new();
cache.clear().expect("Failed to clear cache");
}
+ SubCommand::Init => init(),
};
}
@@ -107,6 +112,31 @@ fn get_level_style(level: Level) -> colored::Color {
}
}
+fn init() {
+ let settings = Settings::default();
+ let settings_string = toml::to_string_pretty(&settings).unwrap();
+ let manifest_path = PathBuf::from("Manifest.toml");
+ let bibliography_path = PathBuf::from("Bibliography.toml");
+ let glossary_path = PathBuf::from("Glossary.toml");
+
+ if !manifest_path.exists() {
+ let mut file = OpenOptions::new()
+ .create(true)
+ .write(true)
+ .truncate(true)
+ .open("Manifest.toml")
+ .unwrap();
+ file.write_all(settings_string.as_bytes()).unwrap();
+ file.flush().unwrap();
+ }
+ if !bibliography_path.exists() {
+ File::create("Bibliography.toml".to_string()).unwrap();
+ }
+ if !glossary_path.exists() {
+ File::create("Glossary.toml".to_string()).unwrap();
+ }
+}
+
/// Watches a file with all of its imports and renders on change
fn watch(opt: &WatchOptions) {
let parser = render(&opt.render_options);
@@ -177,7 +207,7 @@ fn render_format(opt: &RenderOptions, document: Document, writer: BufWriter) {
- 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();
}
diff --git a/src/parser/block.rs b/src/parser/block.rs
index bcb0c1a..c2c5a40 100644
--- a/src/parser/block.rs
+++ b/src/parser/block.rs
@@ -1,7 +1,7 @@
use super::ParseResult;
use crate::elements::tokens::*;
use crate::elements::{
- Block, CodeBlock, Import, List, ListItem, MathBlock, Paragraph, Quote, Section, Table,
+ Block, CodeBlock, Import, List, ListItem, MathBlock, Metadata, Paragraph, Quote, Section, Table,
};
use crate::parser::inline::ParseInline;
use crate::parser::line::ParseLine;
@@ -26,7 +26,7 @@ impl ParseBlock for Parser {
fn parse_block(&mut self) -> ParseResult {
if let Some(section) = self.section_return {
if section <= self.section_nesting && (self.section_nesting > 0) {
- return Err(self.ctm.assert_error(None));
+ return Err(self.ctm.assert_error(None).into());
} else {
self.section_return = None;
}
@@ -35,7 +35,7 @@ impl ParseBlock for Parser {
log::trace!("Block::Section");
Block::Section(section)
} else if let Some(_) = self.section_return {
- return Err(self.ctm.err());
+ return Err(self.ctm.err().into());
} else if let Ok(list) = self.parse_list() {
log::trace!("Block::List");
Block::List(list)
@@ -60,7 +60,7 @@ impl ParseBlock for Parser {
Block::Null
}
} else if let Some(_) = self.section_return {
- return Err(self.ctm.err());
+ return Err(self.ctm.err().into());
} else if let Ok(pholder) = self.parse_placeholder() {
log::trace!("Block::Placeholder");
Block::Placeholder(pholder)
@@ -68,7 +68,7 @@ impl ParseBlock for Parser {
log::trace!("Block::Paragraph");
Block::Paragraph(paragraph)
} else {
- return Err(self.ctm.err());
+ return Err(self.ctm.err().into());
};
Ok(token)
@@ -78,6 +78,7 @@ impl ParseBlock for Parser {
fn parse_section(&mut self) -> ParseResult {
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()),
}
}
}
diff --git a/src/parser/inline.rs b/src/parser/inline.rs
index 39bd2d6..2665307 100644
--- a/src/parser/inline.rs
+++ b/src/parser/inline.rs
@@ -3,7 +3,6 @@ use crate::elements::tokens::*;
use crate::elements::BibReference;
use crate::elements::*;
use crate::parser::block::ParseBlock;
-use crate::references::configuration::keys::{BIB_REF_DISPLAY, SMART_ARROWS};
use crate::references::glossary::GlossaryDisplay;
use crate::references::glossary::GlossaryReference;
use crate::references::templates::{GetTemplateVariables, Template, TemplateVariable};
@@ -48,11 +47,12 @@ impl ParseInline for Parser {
self.ctm.assert_char(surrounding, Some(start_index))?;
self.ctm.seek_one()?;
let mut inline = vec![self.parse_inline()?];
+
while !self.ctm.check_char(surrounding) {
if let Ok(result) = self.parse_inline() {
inline.push(result)
} else {
- return Err(self.ctm.rewind_with_error(start_index));
+ return Err(self.ctm.rewind_with_error(start_index).into());
}
}
if !self.ctm.check_eof() {
@@ -71,10 +71,10 @@ impl ParseInline for Parser {
}
}
if self.ctm.check_char(&PIPE) || self.ctm.check_char(&LB) {
- Err(self.ctm.err())
+ Err(self.ctm.err().into())
} else if self.ctm.check_eof() {
log::trace!("EOF");
- Err(self.ctm.err())
+ Err(self.ctm.err().into())
} else if let Ok(image) = self.parse_image() {
log::trace!("Inline::Image {:?}", image);
Ok(Inline::Image(image))
@@ -160,7 +160,7 @@ impl ParseInline for Parser {
image_data: pending_image,
})
} else {
- Err(self.ctm.rewind_with_error(start_index))
+ Err(self.ctm.rewind_with_error(start_index).into())
}
}
@@ -185,7 +185,7 @@ impl ParseInline for Parser {
self.inline_break_at.pop();
self.ctm.seek_one()?;
} else if !short_syntax {
- return Err(self.ctm.rewind_with_error(start_index));
+ return Err(self.ctm.rewind_with_error(start_index).into());
}
self.ctm.assert_char(&URL_OPEN, Some(start_index))?;
self.ctm.seek_one()?;
@@ -218,7 +218,7 @@ impl ParseInline for Parser {
} else if self.ctm.check_char(&SPACE) {
false
} else {
- return Err(self.ctm.rewind_with_error(start_index));
+ return Err(self.ctm.rewind_with_error(start_index).into());
};
self.ctm.seek_one()?;
self.ctm.assert_char(&CHECK_CLOSE, Some(start_index))?;
@@ -233,11 +233,12 @@ impl ParseInline for Parser {
self.ctm.assert_sequence(&BOLD, Some(start_index))?;
self.ctm.seek_one()?;
let mut inline = vec![self.parse_inline()?];
+
while !self.ctm.check_sequence(&BOLD) {
if let Ok(result) = self.parse_inline() {
inline.push(result);
} else {
- return Err(self.ctm.rewind_with_error(start_index));
+ return Err(self.ctm.rewind_with_error(start_index).into());
}
}
self.ctm.seek_one()?;
@@ -261,12 +262,12 @@ impl ParseInline for Parser {
if let Ok(result) = self.parse_inline() {
inline.push(result);
} else {
- return Err(self.ctm.rewind_with_error(start_index));
+ return Err(self.ctm.rewind_with_error(start_index).into());
}
}
self.ctm.rewind(self.ctm.get_index() - STRIKED.len());
if self.ctm.check_any(WHITESPACE) {
- return Err(self.ctm.rewind_with_error(start_index));
+ return Err(self.ctm.rewind_with_error(start_index).into());
}
for _ in 0..(STRIKED.len() + 1) {
self.ctm.seek_one()?;
@@ -330,7 +331,7 @@ impl ParseInline for Parser {
name,
})
} else {
- Err(self.ctm.rewind_with_error(start_index))
+ Err(self.ctm.rewind_with_error(start_index).into())
}
}
@@ -347,7 +348,7 @@ impl ParseInline for Parser {
)?;
self.ctm.seek_one()?;
if color.is_empty() {
- return Err(self.ctm.err());
+ return Err(self.ctm.err().into());
}
Ok(Colored {
value: Box::new(self.parse_inline()?),
@@ -367,7 +368,15 @@ impl ParseInline for Parser {
let bib_ref = BibRef::new(key.clone());
let ref_entry = Arc::new(RwLock::new(BibReference::new(
key,
- self.options.document.config.get_ref_entry(BIB_REF_DISPLAY),
+ Some(
+ self.options
+ .document
+ .config
+ .lock()
+ .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 {
if self.ctm.check_char(&LB) {
- return Err(self.ctm.err());
+ return Err(self.ctm.err().into());
}
let mut characters = String::new();
if !self.ctm.check_char(&SPECIAL_ESCAPE) {
@@ -466,7 +475,7 @@ impl ParseInline for Parser {
if characters.len() > 0 {
Ok(PlainText { value: characters })
} else {
- Err(self.ctm.err())
+ Err(self.ctm.err().into())
}
}
@@ -490,7 +499,7 @@ impl ParseInline for Parser {
if values.len() == 0 {
// if there was a linebreak (the metadata wasn't closed) or there is no inner data
// return an error
- return Err(self.ctm.rewind_with_error(start_index));
+ return Err(self.ctm.rewind_with_error(start_index).into());
}
Ok(InlineMetadata { data: values })
@@ -566,7 +575,7 @@ impl ParseInline for Parser {
{
name_str
} else {
- return Err(self.ctm.rewind_with_error(start_index));
+ return Err(self.ctm.rewind_with_error(start_index).into());
};
if !self.ctm.check_eof() {
self.ctm.seek_one()?;
@@ -590,7 +599,7 @@ impl ParseInline for Parser {
self.ctm.seek_one()?;
if self.ctm.check_char(&TEMPLATE) {
- return Err(self.ctm.rewind_with_error(start_index));
+ return Err(self.ctm.rewind_with_error(start_index).into());
}
let mut elements = Vec::new();
@@ -645,15 +654,8 @@ impl ParseInline for Parser {
/// Parses an arrow
fn parse_arrow(&mut self) -> ParseResult {
- if !self
- .options
- .document
- .config
- .get_entry(SMART_ARROWS)
- .and_then(|e| e.get().as_bool())
- .unwrap_or(true)
- {
- Err(self.ctm.err())
+ if !self.options.document.config.lock().features.smart_arrows {
+ Err(self.ctm.err().into())
} else if self.ctm.check_sequence(A_LEFT_RIGHT_ARROW) {
self.ctm.seek_one()?;
Ok(Arrow::LeftRightArrow)
@@ -673,7 +675,7 @@ impl ParseInline for Parser {
self.ctm.seek_one()?;
Ok(Arrow::BigLeftArrow)
} else {
- Err(self.ctm.err())
+ Err(self.ctm.err().into())
}
}
}
diff --git a/src/parser/line.rs b/src/parser/line.rs
index 0a50eb2..13cdeef 100644
--- a/src/parser/line.rs
+++ b/src/parser/line.rs
@@ -25,7 +25,7 @@ impl ParseLine for Parser {
fn parse_line(&mut self) -> ParseResult {
if self.ctm.check_eof() {
log::trace!("EOF");
- Err(self.ctm.err())
+ Err(self.ctm.err().into())
} else {
if let Ok(ruler) = self.parse_ruler() {
log::trace!("Line::Ruler");
@@ -40,7 +40,7 @@ impl ParseLine for Parser {
log::trace!("Line::Text");
Ok(Line::Text(text))
} else {
- Err(self.ctm.err())
+ Err(self.ctm.err().into())
}
}
}
@@ -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());
}
}
};
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 7085440..a05d390 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -5,31 +5,54 @@ pub(crate) mod line;
use self::block::ParseBlock;
use crate::elements::tokens::LB;
use crate::elements::{Document, ImportAnchor};
-use crate::references::configuration::keys::{
- IMP_BIBLIOGRAPHY, IMP_CONFIGS, IMP_GLOSSARY, IMP_IGNORE, IMP_STYLESHEETS,
-};
-use crate::references::configuration::Value;
-use charred::tapemachine::{CharTapeMachine, TapeError, TapeResult};
+use crate::settings::SettingsError;
+use charred::tapemachine::{CharTapeMachine, TapeError};
use crossbeam_utils::sync::WaitGroup;
use regex::Regex;
use std::collections::HashMap;
+use std::fmt;
use std::fs::{read_to_string, File};
-use std::io::BufReader;
+use std::io::{self, BufReader};
use std::path::PathBuf;
use std::sync::{Arc, Mutex, RwLock};
use std::thread;
-pub type ParseResult = TapeResult;
-pub type ParseError = TapeError;
+pub type ParseResult = Result;
-const DEFAULT_IMPORTS: &'static [(&str, &str)] = &[
- ("snekdown.toml", "manifest"),
- ("manifest.toml", "manifest"),
- ("bibliography.toml", "bibliography"),
- ("bibliography2.bib.toml", "bibliography"),
- ("glossary.toml", "glossary"),
- ("style.css", "stylesheet"),
-];
+#[derive(Debug)]
+pub enum ParseError {
+ TapeError(TapeError),
+ SettingsError(SettingsError),
+ IoError(io::Error),
+}
+
+impl fmt::Display for ParseError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ ParseError::TapeError(e) => write!(f, "{}", e),
+ ParseError::SettingsError(e) => write!(f, "{}", e),
+ ParseError::IoError(e) => write!(f, "IO Error: {}", e),
+ }
+ }
+}
+
+impl From for ParseError {
+ fn from(e: TapeError) -> Self {
+ Self::TapeError(e)
+ }
+}
+
+impl From for ParseError {
+ fn from(e: SettingsError) -> Self {
+ Self::SettingsError(e)
+ }
+}
+
+impl From for ParseError {
+ fn from(e: io::Error) -> Self {
+ Self::IoError(e)
+ }
+}
#[derive(Clone, Debug)]
pub struct ParserOptions {
@@ -65,6 +88,7 @@ pub struct Parser {
pub(crate) ctm: CharTapeMachine,
section_nesting: u8,
sections: Vec,
+ section_anchors: Vec,
section_return: Option,
wg: WaitGroup,
pub(crate) block_break_at: Vec,
@@ -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 {
- 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::()
- .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) -> ImportType {
+ fn import(&mut self, path: String, args: &HashMap) -> 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::>();
- 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);
}
}
}
diff --git a/src/references/configuration/keys.rs b/src/references/configuration/keys.rs
deleted file mode 100644
index cce5760..0000000
--- a/src/references/configuration/keys.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-#![allow(unused)]
-
-pub const BIB_REF_DISPLAY: &str = "bib-ref-display";
-pub const META_LANG: &str = "language";
-
-// import and include options
-pub const IMP_IGNORE: &str = "ignored-imports";
-pub const IMP_STYLESHEETS: &str = "included-stylesheets";
-pub const IMP_CONFIGS: &str = "included-configs";
-pub const IMP_BIBLIOGRAPHY: &str = "included-bibliography";
-pub const IMP_GLOSSARY: &str = "included-glossary";
-pub const EMBED_EXTERNAL: &str = "embed-external";
-pub const SMART_ARROWS: &str = "smart-arrows";
-pub const INCLUDE_MATHJAX: &str = "include-math-jax";
-
-// PDF options
-pub const PDF_DISPLAY_HEADER_FOOTER: &str = "pfd-display-header-footer";
-pub const PDF_HEADER_TEMPLATE: &str = "pdf-header-template";
-pub const PDF_FOOTER_TEMPLATE: &str = "pdf-footer-template";
-pub const PDF_MARGIN_TOP: &str = "pdf-margin-top";
-pub const PDF_MARGIN_BOTTOM: &str = "pdf-margin-bottom";
-pub const PDF_MARGIN_LEFT: &str = "pdf-margin-left";
-pub const PDF_MARGIN_RIGHT: &str = "pdf-margin-right";
-pub const PDF_PAGE_HEIGHT: &str = "pdf-page-height";
-pub const PDF_PAGE_WIDTH: &str = "pdf-page-width";
-pub const PDF_PAGE_SCALE: &str = "pdf-page-scale";
-
-// Image Options
-pub const IMAGE_FORMAT: &str = "image-format";
-pub const IMAGE_MAX_WIDTH: &str = "image-max-width";
-pub const IMAGE_MAX_HEIGHT: &str = "image-max-height";
diff --git a/src/references/configuration/mod.rs b/src/references/configuration/mod.rs
deleted file mode 100644
index 335f5cc..0000000
--- a/src/references/configuration/mod.rs
+++ /dev/null
@@ -1,188 +0,0 @@
-use crate::elements::MetadataValue;
-use crate::references::configuration::keys::{
- BIB_REF_DISPLAY, META_LANG, PDF_DISPLAY_HEADER_FOOTER, PDF_FOOTER_TEMPLATE,
- PDF_HEADER_TEMPLATE, PDF_MARGIN_BOTTOM, PDF_MARGIN_TOP,
-};
-use crate::references::templates::Template;
-use serde::export::TryFrom;
-use std::collections::HashMap;
-use std::sync::{Arc, RwLock};
-
-pub(crate) mod keys;
-
-#[derive(Clone, Debug)]
-pub enum Value {
- String(String),
- Bool(bool),
- Float(f64),
- Integer(i64),
- Template(Template),
- Array(Vec),
-}
-
-#[derive(Clone, Debug)]
-pub struct ConfigEntry {
- inner: Value,
-}
-
-pub type ConfigRefEntry = Arc>;
-
-#[derive(Clone, Debug)]
-pub struct Configuration {
- config: Arc>>,
-}
-
-impl Value {
- pub fn as_string(&self) -> String {
- match self {
- Value::String(string) => string.clone(),
- Value::Integer(int) => format!("{}", int),
- Value::Float(f) => format!("{:02}", f),
- Value::Bool(b) => format!("{}", b),
- Value::Array(a) => a.iter().fold("".to_string(), |a, b| {
- format!("{} \"{}\"", a, b.as_string())
- }),
- _ => "".to_string(),
- }
- }
-
- /// Returns the bool value if the value is a boolean
- pub fn as_bool(&self) -> Option {
- match self {
- Value::Bool(b) => Some(*b),
- _ => None,
- }
- }
-
- pub fn as_float(&self) -> Option {
- match self {
- Value::Float(v) => Some(*v),
- _ => None,
- }
- }
-}
-
-impl ConfigEntry {
- pub fn new(value: Value) -> Self {
- Self { inner: value }
- }
-
- pub fn set(&mut self, value: Value) {
- self.inner = value;
- }
-
- pub fn get(&self) -> &Value {
- &self.inner
- }
-}
-
-impl Default for Configuration {
- fn default() -> Self {
- let mut self_config = Self::new();
- self_config.set(BIB_REF_DISPLAY, Value::String("{{number}}".to_string()));
- self_config.set(META_LANG, Value::String("en".to_string()));
- self_config.set(PDF_MARGIN_BOTTOM, Value::Float(0.5));
- self_config.set(PDF_MARGIN_TOP, Value::Float(0.5));
- self_config.set(PDF_DISPLAY_HEADER_FOOTER, Value::Bool(true));
- self_config.set(
- PDF_HEADER_TEMPLATE,
- Value::String("".to_string()),
- );
- self_config.set(
- PDF_FOOTER_TEMPLATE,
- Value::String(
- include_str!("../../format/chromium_pdf/assets/default-footer-template.html")
- .to_string(),
- ),
- );
-
- self_config
- }
-}
-
-impl Configuration {
- pub fn new() -> Self {
- Self {
- config: Arc::new(RwLock::new(HashMap::new())),
- }
- }
-
- /// returns the value of a config entry
- pub fn get_entry(&self, key: &str) -> Option {
- let config = self.config.read().unwrap();
- if let Some(entry) = config.get(key) {
- let value = entry.read().unwrap();
- Some(value.clone())
- } else {
- None
- }
- }
-
- /// returns a config entry that is a reference to a value
- pub fn get_ref_entry(&self, key: &str) -> Option {
- let config = self.config.read().unwrap();
- if let Some(entry) = config.get(&key.to_string()) {
- Some(Arc::clone(entry))
- } else {
- None
- }
- }
-
- /// Sets a config parameter
- pub fn set(&mut self, key: &str, value: Value) {
- let mut config = self.config.write().unwrap();
- if let Some(entry) = config.get(&key.to_string()) {
- entry.write().unwrap().set(value)
- } else {
- config.insert(
- key.to_string(),
- Arc::new(RwLock::new(ConfigEntry::new(value))),
- );
- }
- }
-
- /// Sets a config value based on a metadata value
- pub fn set_from_meta(&mut self, key: &str, value: MetadataValue) {
- match value {
- MetadataValue::String(string) => self.set(key, Value::String(string)),
- MetadataValue::Bool(bool) => self.set(key, Value::Bool(bool)),
- MetadataValue::Float(f) => self.set(key, Value::Float(f)),
- MetadataValue::Integer(i) => self.set(key, Value::Integer(i)),
- MetadataValue::Template(t) => self.set(key, Value::Template(t)),
- _ => {}
- }
- }
-
- pub fn set_from_toml(&mut self, value: &toml::Value) -> Option<()> {
- let table = value.as_table().cloned()?;
- table.iter().for_each(|(k, v)| {
- match v {
- toml::Value::Table(_) => self.set_from_toml(v).unwrap_or(()),
- _ => self.set(k, Value::try_from(v.clone()).unwrap()),
- };
- });
-
- Some(())
- }
-}
-
-impl TryFrom for Value {
- type Error = ();
-
- fn try_from(value: toml::Value) -> Result {
- match value {
- toml::Value::Table(_) => Err(()),
- toml::Value::Float(f) => Ok(Value::Float(f)),
- toml::Value::Integer(i) => Ok(Value::Integer(i)),
- toml::Value::String(s) => Ok(Value::String(s)),
- toml::Value::Boolean(b) => Ok(Value::Bool(b)),
- toml::Value::Datetime(dt) => Ok(Value::String(dt.to_string())),
- toml::Value::Array(a) => Ok(Value::Array(
- a.iter()
- .cloned()
- .filter_map(|e| Value::try_from(e).ok())
- .collect::>(),
- )),
- }
- }
-}
diff --git a/src/references/mod.rs b/src/references/mod.rs
index 2ebfb66..33b22fe 100644
--- a/src/references/mod.rs
+++ b/src/references/mod.rs
@@ -1,5 +1,4 @@
pub mod bibliography;
-pub mod configuration;
pub mod glossary;
pub mod placeholders;
pub mod templates;
diff --git a/src/references/placeholders.rs b/src/references/placeholders.rs
index f9c0be6..14f8950 100644
--- a/src/references/placeholders.rs
+++ b/src/references/placeholders.rs
@@ -35,6 +35,8 @@ const P_GLS: &str = "gls";
const P_DATE: &str = "date";
const P_TIME: &str = "time";
const P_DATETIME: &str = "datetime";
+const P_AUTHOR: &str = "author";
+const P_TITLE: &str = "title";
impl ProcessPlaceholders for Document {
/// parses all placeholders and assigns values to them
@@ -65,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())
}
}
}
diff --git a/src/settings/feature_settings.rs b/src/settings/feature_settings.rs
new file mode 100644
index 0000000..aa38da7
--- /dev/null
+++ b/src/settings/feature_settings.rs
@@ -0,0 +1,18 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct FeatureSettings {
+ pub embed_external: bool,
+ pub smart_arrows: bool,
+ pub include_mathjax: bool,
+}
+
+impl Default for FeatureSettings {
+ fn default() -> Self {
+ Self {
+ embed_external: true,
+ smart_arrows: true,
+ include_mathjax: true,
+ }
+ }
+}
diff --git a/src/settings/image_settings.rs b/src/settings/image_settings.rs
new file mode 100644
index 0000000..4e836d8
--- /dev/null
+++ b/src/settings/image_settings.rs
@@ -0,0 +1,18 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct ImageSettings {
+ pub format: Option,
+ pub max_width: Option,
+ pub max_height: Option,
+}
+
+impl Default for ImageSettings {
+ fn default() -> Self {
+ Self {
+ format: None,
+ max_height: None,
+ max_width: None,
+ }
+ }
+}
diff --git a/src/settings/import_settings.rs b/src/settings/import_settings.rs
new file mode 100644
index 0000000..32e24c8
--- /dev/null
+++ b/src/settings/import_settings.rs
@@ -0,0 +1,20 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct ImportSettings {
+ pub ignored_imports: Vec,
+ pub included_stylesheets: Vec,
+ pub included_bibliography: Vec,
+ pub included_glossaries: Vec,
+}
+
+impl Default for ImportSettings {
+ fn default() -> Self {
+ Self {
+ ignored_imports: Vec::with_capacity(0),
+ included_stylesheets: vec!["style.css".to_string()],
+ included_bibliography: vec!["Bibliography.toml".to_string()],
+ included_glossaries: vec!["Glossary.toml".to_string()],
+ }
+ }
+}
diff --git a/src/settings/metadata_settings.rs b/src/settings/metadata_settings.rs
new file mode 100644
index 0000000..386abae
--- /dev/null
+++ b/src/settings/metadata_settings.rs
@@ -0,0 +1,18 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct MetadataSettings {
+ pub title: Option,
+ pub author: Option,
+ pub language: String,
+}
+
+impl Default for MetadataSettings {
+ fn default() -> Self {
+ Self {
+ title: None,
+ author: None,
+ language: "en".to_string(),
+ }
+ }
+}
diff --git a/src/settings/mod.rs b/src/settings/mod.rs
new file mode 100644
index 0000000..a76cec0
--- /dev/null
+++ b/src/settings/mod.rs
@@ -0,0 +1,125 @@
+use crate::elements::{Metadata, MetadataValue};
+use crate::settings::feature_settings::FeatureSettings;
+use crate::settings::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 = Result;
+
+#[derive(Debug)]
+pub enum SettingsError {
+ IoError(io::Error),
+ ConfigError(ConfigError),
+ TomlError(toml::ser::Error),
+}
+
+impl Display for SettingsError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::IoError(e) => write!(f, "IO Error: {}", e),
+ Self::ConfigError(e) => write!(f, "Config Error: {}", e),
+ Self::TomlError(e) => write!(f, "Toml Error: {}", e),
+ }
+ }
+}
+
+impl Error for SettingsError {}
+
+impl From for SettingsError {
+ fn from(e: io::Error) -> Self {
+ Self::IoError(e)
+ }
+}
+
+impl From for SettingsError {
+ fn from(e: ConfigError) -> Self {
+ Self::ConfigError(e)
+ }
+}
+
+impl From for SettingsError {
+ fn from(e: toml::ser::Error) -> Self {
+ Self::TomlError(e)
+ }
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, Default)]
+pub struct Settings {
+ pub metadata: MetadataSettings,
+ pub features: FeatureSettings,
+ pub imports: ImportSettings,
+ pub pdf: PDFSettings,
+ pub images: ImageSettings,
+ pub style: StyleSettings,
+ pub custom_attributes: HashMap,
+}
+
+impl Source for Settings {
+ fn clone_into_box(&self) -> Box {
+ Box::new(self.clone())
+ }
+
+ fn collect(&self) -> Result, config::ConfigError> {
+ let source_str =
+ toml::to_string(&self).map_err(|e| config::ConfigError::Foreign(Box::new(e)))?;
+ let result = toml::de::from_str(&source_str)
+ .map_err(|e| config::ConfigError::Foreign(Box::new(e)))?;
+
+ Ok(result)
+ }
+}
+
+impl Settings {
+ /// Loads the settings from the specified path
+ pub fn load(path: PathBuf) -> SettingsResult {
+ let mut settings = config::Config::default();
+ settings
+ .merge(Self::default())?
+ .merge(config::File::from(path))?;
+ let settings: Self = settings.try_into()?;
+
+ Ok(settings)
+ }
+
+ /// Merges the current settings with the settings from the given path
+ /// returning updated settings
+ pub fn merge(&mut self, path: PathBuf) -> SettingsResult<()> {
+ let mut settings = config::Config::default();
+ settings
+ .merge(self.clone())?
+ .merge(config::File::from(path))?;
+ let mut settings: Self = settings.try_into()?;
+ mem::swap(self, &mut settings); // replace the old settings with the new ones
+
+ Ok(())
+ }
+
+ pub fn append_metadata(&mut self, metadata: M) {
+ let entries = metadata.get_string_map();
+ for (key, value) in entries {
+ self.custom_attributes.insert(key, value);
+ }
+ }
+
+ pub fn set_from_meta(&mut self, key: &str, value: MetadataValue) {
+ self.custom_attributes
+ .insert(key.to_string(), value.to_string());
+ }
+}
diff --git a/src/settings/pdf_settings.rs b/src/settings/pdf_settings.rs
new file mode 100644
index 0000000..de7fe9b
--- /dev/null
+++ b/src/settings/pdf_settings.rs
@@ -0,0 +1,48 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct PDFSettings {
+ pub display_header_footer: bool,
+ pub header_template: Option,
+ pub footer_template: Option,
+ pub page_height: Option,
+ pub page_width: Option,
+ pub page_scale: f32,
+ pub margin: PDFMarginSettings,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct PDFMarginSettings {
+ pub top: Option,
+ pub bottom: Option,
+ pub left: Option,
+ pub right: Option,
+}
+
+impl Default for PDFMarginSettings {
+ fn default() -> Self {
+ Self {
+ top: Some(0.5),
+ bottom: Some(0.5),
+ left: None,
+ right: None,
+ }
+ }
+}
+
+impl Default for PDFSettings {
+ fn default() -> Self {
+ Self {
+ display_header_footer: true,
+ header_template: Some("".to_string()),
+ footer_template: Some(
+ include_str!("../format/chromium_pdf/assets/default-footer-template.html")
+ .to_string(),
+ ),
+ page_height: None,
+ page_width: None,
+ page_scale: 1.0,
+ margin: Default::default(),
+ }
+ }
+}
diff --git a/src/settings/style_settings.rs b/src/settings/style_settings.rs
new file mode 100644
index 0000000..c5b43d9
--- /dev/null
+++ b/src/settings/style_settings.rs
@@ -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,
+}