use std::{cell::RefCell, collections::HashSet};
use crate::{impl_cache, ConfigError};
pub type ConfigKey<'a> = CacheKey<'a>;
#[derive(Debug)]
pub(crate) struct CacheString {
current: String,
mark: usize,
}
thread_local! {
static BUG: RefCell<CacheString> = RefCell::new(CacheString::new());
}
impl CacheString {
pub(crate) fn new() -> CacheString {
Self {
current: String::with_capacity(10),
mark: 0,
}
}
#[inline]
#[allow(single_use_lifetimes)]
fn push<'a, I: IntoIterator<Item = PartialKey<'a>>>(&mut self, iter: I) -> usize {
let mark = self.mark;
self.mark = self.current.len();
for i in iter {
i.update_string(&mut self.current);
}
mark
}
#[inline]
fn pop(&mut self, mark: usize) {
self.current.truncate(self.mark);
self.mark = mark;
}
#[inline]
fn clear(&mut self) {
self.current.clear();
self.mark = 0;
}
#[inline]
pub(crate) fn new_key(&mut self) -> CacheKey<'_> {
CacheKey { cache: self }
}
#[inline]
pub(crate) fn with_key_place<T, F: FnMut(&mut Self) -> Result<T, ConfigError>>(
f: F,
) -> Result<T, ConfigError> {
BUG.with(move |buf| Self::with_key_buf(buf, f))
}
}
impl_cache!(CacheString);
#[derive(Debug)]
pub struct CacheKey<'a> {
cache: &'a mut CacheString,
}
impl Drop for CacheKey<'_> {
fn drop(&mut self) {
self.cache.clear();
}
}
impl<'a> CacheKey<'a> {
pub(crate) fn push<I: Into<PartialKeyIter<'a>>>(&mut self, iter: I) -> usize {
self.cache.push(iter.into())
}
pub(crate) fn pop(&mut self, mark: usize) {
self.cache.pop(mark);
}
pub(crate) fn as_str(&self) -> &str {
&self.cache.current
}
}
impl std::fmt::Display for CacheKey<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[allow(single_use_lifetimes)]
#[derive(Debug, PartialEq, Eq)]
pub enum PartialKey<'a> {
Str(&'a str),
Int(usize),
}
impl PartialKey<'_> {
#[inline]
pub(crate) fn update_string(&self, key_long: &mut String) {
match self {
PartialKey::Int(i) => {
key_long.push('[');
key_long.push_str(&i.to_string());
key_long.push(']');
}
PartialKey::Str(v) => {
if !key_long.is_empty() {
key_long.push('.');
}
key_long.push_str(v);
}
}
}
}
#[derive(Debug)]
#[allow(variant_size_differences)]
pub enum PartialKeyIter<'a> {
Str(std::str::Split<'a, &'a [char]>),
Int(Option<usize>),
}
impl<'a> Iterator for PartialKeyIter<'a> {
type Item = PartialKey<'a>;
fn next(&mut self) -> Option<Self::Item> {
match self {
PartialKeyIter::Str(s) => {
for v in s {
if v.is_empty() {
continue;
}
return Some(if let Ok(i) = v.parse() {
PartialKey::Int(i)
} else {
PartialKey::Str(v)
});
}
None
}
PartialKeyIter::Int(x) => x.take().map(|x| x.into()),
}
}
}
#[derive(Debug)]
pub struct PartialKeyCollector<'a> {
pub(crate) str_key: HashSet<&'a str>,
pub(crate) int_key: Option<usize>,
}
#[allow(single_use_lifetimes)]
impl<'a> PartialKeyCollector<'a> {
pub(crate) fn new() -> Self {
Self {
str_key: HashSet::new(),
int_key: None,
}
}
pub(crate) fn insert_int(&mut self, key: usize) {
if let Some(u) = self.int_key {
if u > key {
return;
}
}
self.int_key = Some(key + 1);
}
}
impl<'a> From<&'a str> for PartialKeyIter<'a> {
fn from(s: &'a str) -> Self {
PartialKeyIter::Str(s.split(&['.', '[', ']'][..]))
}
}
impl From<usize> for PartialKey<'_> {
fn from(s: usize) -> Self {
PartialKey::Int(s)
}
}
impl From<usize> for PartialKeyIter<'_> {
fn from(s: usize) -> Self {
PartialKeyIter::Int(Some(s))
}
}
impl<'a> From<&'a String> for PartialKeyIter<'a> {
fn from(s: &'a String) -> Self {
s.as_str().into()
}
}
#[cfg(test)]
mod test {
use super::*;
macro_rules! should_eq {
($origin:expr => $norm:expr) => {
let mut che = CacheString::new();
let mut key = che.new_key();
key.push($origin);
assert_eq!(&key.to_string(), $norm);
let mut chd = CacheString::new();
let mut kez = chd.new_key();
kez.push($norm);
assert_eq!(true, key.as_str() == kez.as_str());
};
}
#[test]
fn key_test() {
should_eq!("" => "");
should_eq!("." => "");
should_eq!(".." => "");
should_eq!("[" => "");
should_eq!("[1]" => "[1]");
should_eq!("1" => "[1]");
should_eq!("1[1]" => "[1][1]");
should_eq!("prefix.prop" => "prefix.prop");
should_eq!(".prefix.prop"=> "prefix.prop");
should_eq!("[]prefix.prop"=> "prefix.prop");
should_eq!("[0]prefix.prop"=> "[0].prefix.prop");
should_eq!("prefix[0].prop"=> "prefix[0].prop");
should_eq!("prefix.0.prop"=> "prefix[0].prop");
should_eq!("hello" => "hello");
}
macro_rules! should_ls {
($($origin:literal => $norm:literal,)+) => {
let mut che = CacheString::new();
let mut key = che.new_key();
let mut vec = vec![];
let mut xxx = vec![];
$(
xxx.push(key.push($origin));
assert_eq!($norm, key.as_str());
vec.push(key.to_string());
)+
while let Some(v) = vec.pop() {
assert_eq!(&v, key.as_str());
key.pop(xxx.pop().unwrap());
}
};
}
#[test]
fn key_push_test() {
should_ls!(
"a" => "a",
"" => "a",
"b" => "a.b",
"1" => "a.b[1]",
"1" => "a.b[1][1]",
"a.1" => "a.b[1][1].a[1]",
);
}
}