use std::collections::HashMap;
use damascene_core::El;
use crate::{MarkdownOptions, md_with_options};
const DEFAULT_CAPACITY: usize = 256;
pub struct MdCache {
options: MarkdownOptions,
capacity: usize,
entries: HashMap<Box<str>, Entry>,
tick: u64,
parses: u64,
}
struct Entry {
rendered: El,
last_used: u64,
}
impl MdCache {
pub fn new(options: MarkdownOptions) -> Self {
Self::with_capacity(options, DEFAULT_CAPACITY)
}
pub fn with_capacity(options: MarkdownOptions, capacity: usize) -> Self {
Self {
options,
capacity: capacity.max(1),
entries: HashMap::new(),
tick: 0,
parses: 0,
}
}
pub fn get(&mut self, text: &str) -> El {
self.tick += 1;
let tick = self.tick;
if let Some(entry) = self.entries.get_mut(text) {
entry.last_used = tick;
return entry.rendered.clone();
}
self.parses += 1;
let rendered = md_with_options(text, self.options);
if self.entries.len() >= self.capacity {
self.evict_lru();
}
self.entries.insert(
Box::from(text),
Entry {
rendered: rendered.clone(),
last_used: tick,
},
);
rendered
}
pub fn parses(&self) -> u64 {
self.parses
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn clear(&mut self) {
self.entries.clear();
}
fn evict_lru(&mut self) {
if let Some(oldest) = self
.entries
.iter()
.min_by_key(|(_, e)| e.last_used)
.map(|(k, _)| k.clone())
{
self.entries.remove(&oldest);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hits_skip_the_parse() {
let mut cache = MdCache::new(MarkdownOptions::default());
let _ = cache.get("# stable message");
let _ = cache.get("# stable message");
let _ = cache.get("# stable message");
assert_eq!(cache.parses(), 1);
assert_eq!(cache.len(), 1);
let _ = cache.get("partial");
let _ = cache.get("partial text");
assert_eq!(cache.parses(), 3);
}
#[test]
fn lru_eviction_keeps_recent_entries() {
let mut cache = MdCache::with_capacity(MarkdownOptions::default(), 2);
let _ = cache.get("one");
let _ = cache.get("two");
let _ = cache.get("one"); let _ = cache.get("three"); assert_eq!(cache.len(), 2);
let parses = cache.parses();
let _ = cache.get("one"); assert_eq!(cache.parses(), parses);
let _ = cache.get("two"); assert_eq!(cache.parses(), parses + 1);
}
}