From 5e6bacf5457a2fa111b1c78038b08041ece57eb9 Mon Sep 17 00:00:00 2001 From: Andrey Voronkov Date: Wed, 28 Feb 2024 21:16:31 +0300 Subject: [PATCH] Drinks - DRY Links Proof of Concept Uses links dict in drinks.txt for general links storage Replaces `{{#drink somedrink}}` placeholders with value from the dict --- src/book/init.rs | 14 ++++++++ src/book/mod.rs | 5 +-- src/preprocess/drinks.rs | 77 ++++++++++++++++++++++++++++++++++++++++ src/preprocess/mod.rs | 2 ++ 4 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 src/preprocess/drinks.rs diff --git a/src/book/init.rs b/src/book/init.rs index faca1d09..9e5837f3 100644 --- a/src/book/init.rs +++ b/src/book/init.rs @@ -83,6 +83,8 @@ impl BookBuilder { self.write_book_toml()?; + self.write_drinks_txt()?; + match MDBook::load(&self.root) { Ok(book) => Ok(book), Err(e) => { @@ -108,6 +110,18 @@ impl BookBuilder { Ok(()) } + fn write_drinks_txt(&self) -> Result<()> { + debug!("Writing drinks.txt"); + let drinks_txt = self.root.join("drinks.txt"); + let entry = "hello: https://world.org"; + + File::create(drinks_txt) + .with_context(|| "Couldn't create drinks.txt")? + .write_all(&entry.as_bytes()) + .with_context(|| "Unable to write config to drinks.txt")?; + Ok(()) + } + fn copy_across_theme(&self) -> Result<()> { debug!("Copying theme"); diff --git a/src/book/mod.rs b/src/book/mod.rs index c0ab8a54..de5d9eab 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -24,7 +24,7 @@ use topological_sort::TopologicalSort; use crate::errors::*; use crate::preprocess::{ - CmdPreprocessor, IndexPreprocessor, LinkPreprocessor, Preprocessor, PreprocessorContext, + CmdPreprocessor, DrinkPreprocessor, IndexPreprocessor, LinkPreprocessor, Preprocessor, PreprocessorContext, }; use crate::renderer::{CmdRenderer, HtmlHandlebars, MarkdownRenderer, RenderContext, Renderer}; use crate::utils; @@ -432,7 +432,7 @@ fn determine_renderers(config: &Config) -> Vec> { renderers } -const DEFAULT_PREPROCESSORS: &[&str] = &["links", "index"]; +const DEFAULT_PREPROCESSORS: &[&str] = &["drinks", "links", "index"]; fn is_default_preprocessor(pre: &dyn Preprocessor) -> bool { let name = pre.name(); @@ -533,6 +533,7 @@ fn determine_preprocessors(config: &Config) -> Result> names.sort(); for name in names { let preprocessor: Box = match name.as_str() { + "drinks" => Box::new(DrinkPreprocessor::new()), "links" => Box::new(LinkPreprocessor::new()), "index" => Box::new(IndexPreprocessor::new()), _ => { diff --git a/src/preprocess/drinks.rs b/src/preprocess/drinks.rs new file mode 100644 index 00000000..f2cd2c08 --- /dev/null +++ b/src/preprocess/drinks.rs @@ -0,0 +1,77 @@ +use crate::errors::*; + +use super::{Preprocessor, PreprocessorContext}; +use crate::book::{Book, BookItem, Chapter}; +use regex::{Captures, Regex}; +use once_cell::sync::Lazy; +use std::io::{BufRead, BufReader}; +use std::fs::File; +use std::collections::HashMap; + +const SPLITTER: char = ':'; + +type Dict=HashMap; + +/// DRY Links - A preprocessor for using centralized links collection: +/// +/// - `{{# drink}}` - Insert link from the collection +#[derive(Default)] +pub struct DrinkPreprocessor; + +impl DrinkPreprocessor { + pub(crate) const NAME: &'static str = "drinks"; + + /// Create a new `DrinkPreprocessor`. + pub fn new() -> Self { + DrinkPreprocessor + } + + fn replace_drinks(&self, chapter: &mut Chapter, dict: &Dict) -> Result { + static RE: Lazy = Lazy::new(|| { + Regex::new( + r"(?x) # insignificant whitespace mode + \{\{\s* # link opening parens and whitespace + \#(drink) # drink marker + \s+ # separating whitespace + (?[A-z0-9_-]+) # drink name + \}\} # link closing parens", + ).unwrap() + }); + + static NODRINK: Lazy = Lazy::new(|| { + "deadbeef".to_string() + }); + + let res = RE.replace_all(&chapter.content, |caps: &Captures<'_>| { + dict.get(&caps["drink"]).unwrap_or(&NODRINK) + }); + Ok(res.to_string()) + } +} + +impl Preprocessor for DrinkPreprocessor { + fn name(&self) -> &str { + Self::NAME + } + + fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result { + let path = ctx.root.join("drinks.txt"); + + let drinks: Dict = { + let reader = BufReader::new(File::open(path).expect("Cannot open drinks dictionary")); + reader.lines().filter_map(|l| { + l.expect("Cannot read line in drinks dictionary").split_once(SPLITTER).map(|(name, value)| (name.trim().to_owned(), value.trim().to_owned())) + }).collect::>() + }; + + book.for_each_mut(|section: &mut BookItem| { + if let BookItem::Chapter(ref mut ch) = *section { + ch.content = self + .replace_drinks(ch, &drinks) + .expect("Error converting drinks into links for chapter"); + } + }); + + Ok(book) + } +} diff --git a/src/preprocess/mod.rs b/src/preprocess/mod.rs index df01a3db..7a6d47a6 100644 --- a/src/preprocess/mod.rs +++ b/src/preprocess/mod.rs @@ -1,10 +1,12 @@ //! Book preprocessing. pub use self::cmd::CmdPreprocessor; +pub use self::drinks::DrinkPreprocessor; pub use self::index::IndexPreprocessor; pub use self::links::LinkPreprocessor; mod cmd; +mod drinks; mod index; mod links;