use std::collections::BTreeMap;
use std::fmt::Write as _;
use super::StoreError;
use super::glob::{compile_glob, matches_tokens};
pub trait Store: Send {
fn write(&mut self, path: &str, contents: &str) -> Result<(), StoreError>;
fn append(&mut self, path: &str, contents: &str) -> Result<(), StoreError>;
fn read_lines(&self, path: &str) -> Result<String, StoreError>;
fn read(&self, path: &str) -> Result<String, StoreError>;
fn str_replace(&mut self, path: &str, old: &str, new: &str) -> Result<(), StoreError>;
fn delete(&mut self, path: &str) -> Result<(), StoreError>;
fn glob(&self, pattern: &str) -> Result<Vec<String>, StoreError>;
fn exists(&self, path: &str) -> Result<bool, StoreError>;
}
#[derive(Debug, Default, Clone)]
#[non_exhaustive]
pub struct MemStore {
files: BTreeMap<String, String>,
}
impl MemStore {
#[must_use]
pub fn new() -> MemStore {
MemStore::default()
}
}
impl Store for MemStore {
fn write(&mut self, path: &str, contents: &str) -> Result<(), StoreError> {
self.files.insert(path.to_string(), contents.to_string());
Ok(())
}
fn append(&mut self, path: &str, contents: &str) -> Result<(), StoreError> {
self.files
.entry(path.to_string())
.or_default()
.push_str(contents);
Ok(())
}
fn read_lines(&self, path: &str) -> Result<String, StoreError> {
let contents = self.files.get(path).ok_or_else(|| StoreError::NotFound {
path: path.to_string(),
})?;
Ok(number_lines(contents))
}
fn read(&self, path: &str) -> Result<String, StoreError> {
self.files
.get(path)
.cloned()
.ok_or_else(|| StoreError::NotFound {
path: path.to_string(),
})
}
fn str_replace(&mut self, path: &str, old: &str, new: &str) -> Result<(), StoreError> {
let contents = self.files.get(path).ok_or_else(|| StoreError::NotFound {
path: path.to_string(),
})?;
let count = contents.matches(old).count();
match count {
0 => Err(StoreError::AnchorNotFound {
path: path.to_string(),
anchor: old.to_string(),
}),
1 => {
let replaced = contents.replacen(old, new, 1);
self.files.insert(path.to_string(), replaced);
Ok(())
}
count => Err(StoreError::AnchorAmbiguous {
path: path.to_string(),
anchor: old.to_string(),
count,
}),
}
}
fn delete(&mut self, path: &str) -> Result<(), StoreError> {
if self.files.remove(path).is_some() {
Ok(())
} else {
Err(StoreError::NotFound {
path: path.to_string(),
})
}
}
fn glob(&self, pattern: &str) -> Result<Vec<String>, StoreError> {
let tokens = compile_glob(pattern.as_bytes());
Ok(self
.files
.keys()
.filter(|key| matches_tokens(&tokens, key.as_bytes()))
.cloned()
.collect())
}
fn exists(&self, path: &str) -> Result<bool, StoreError> {
Ok(self.files.contains_key(path))
}
}
fn number_lines(content: &str) -> String {
let total = content.lines().count();
if total == 0 {
return String::new();
}
let width = total.to_string().len();
let mut out = String::new();
for (index, line) in content.lines().enumerate() {
if index > 0 {
out.push('\n');
}
let number = index + 1;
let _ = write!(out, "{number:>width$}| {line}");
}
out
}