233 lines
7.7 KiB
Rust
233 lines
7.7 KiB
Rust
use crate::parser::KVPair;
|
|
use crate::parser::{Filter, RValue};
|
|
use crate::renderer::context_element::CompareContextElement;
|
|
use crate::renderer::context_element::ContextElement;
|
|
use crate::renderer::walking::owned_walk_path;
|
|
use crate::renderer::Loopable;
|
|
use crate::renderer::RenderError;
|
|
use crate::renderer::Renderable;
|
|
use crate::renderer::WalkError;
|
|
use crate::renderer::Walkable;
|
|
use std::{cmp::Ordering, collections::HashMap};
|
|
|
|
/// Copy the data from an RValue to an Owned struct
|
|
///
|
|
/// In order to get comparisons to work for our `ContextElement` trait
|
|
/// objects, we need to be able to use `std::any::Any`. Unfortunately,
|
|
/// `Any` requires that the structs do not have a lifetime (so they
|
|
/// will have a `'static` lifetime. This means that we cannot have a
|
|
/// `<'a>` appended to the struct type, so the struct cannot contain
|
|
/// any borrows. Rather than impose the copy cost in the parser, we
|
|
/// are imposing the cost of copying the data in the renderer because
|
|
/// the parser has no reason to not be able to reference data from the
|
|
/// input string.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub enum OwnedRValue {
|
|
RVPath(OwnedPath),
|
|
RVString(String),
|
|
RVPositiveInteger(u64),
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct OwnedPath {
|
|
pub keys: Vec<String>,
|
|
}
|
|
|
|
impl From<&RValue<'_>> for OwnedRValue {
|
|
fn from(original: &RValue<'_>) -> Self {
|
|
match original {
|
|
RValue::RVString(text) => OwnedRValue::RVString(text.to_owned()),
|
|
RValue::RVPath(path) => OwnedRValue::RVPath(OwnedPath {
|
|
keys: path.keys.iter().map(|k| k.to_string()).collect(),
|
|
}),
|
|
RValue::RVPositiveInteger(num) => OwnedRValue::RVPositiveInteger(*num),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct ParametersContext {
|
|
params: HashMap<String, OwnedRValue>,
|
|
breadcrumbs: Vec<Box<dyn ContextElement>>,
|
|
}
|
|
|
|
impl ParametersContext {
|
|
pub fn new(breadcrumbs: &Vec<&dyn ContextElement>, params: &Vec<KVPair>) -> ParametersContext {
|
|
let owned_params: HashMap<String, OwnedRValue> = params
|
|
.iter()
|
|
.map(|kvpair| (kvpair.key.to_string(), OwnedRValue::from(&kvpair.value)))
|
|
.collect();
|
|
let owned_breadcrumbs: Vec<Box<dyn ContextElement>> =
|
|
breadcrumbs.iter().map(|ce| ce.clone_to_box()).collect();
|
|
|
|
ParametersContext {
|
|
params: owned_params,
|
|
breadcrumbs: owned_breadcrumbs,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ContextElement for ParametersContext {}
|
|
|
|
impl Renderable for ParametersContext {
|
|
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
|
// TODO: Would this even ever be called? Won't matter, but I'd
|
|
// like to know. Since it is injected 1 above the current
|
|
// context, we wouldn't be able to access it with `{.}`.
|
|
Ok("[object Object]".to_owned())
|
|
}
|
|
}
|
|
|
|
impl Loopable for ParametersContext {
|
|
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
|
// TODO: Would this even ever be called? Won't matter, but I'd
|
|
// like to know. Since it is injected 1 above the current
|
|
// context, we wouldn't be able to access it with `{.}`.
|
|
vec![self]
|
|
}
|
|
}
|
|
|
|
impl Walkable for ParametersContext {
|
|
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
|
|
let rval = self.params.get(segment).ok_or(WalkError::CantWalk)?;
|
|
match rval {
|
|
OwnedRValue::RVPath(path) => owned_walk_path(&self.breadcrumbs, &path.keys),
|
|
OwnedRValue::RVString(text) => Ok(text),
|
|
OwnedRValue::RVPositiveInteger(num) => Ok(num),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Clone for ParametersContext {
|
|
fn clone(&self) -> Self {
|
|
let new_params: HashMap<String, OwnedRValue> = self
|
|
.params
|
|
.iter()
|
|
.map(|(k, v)| (k.clone(), v.clone()))
|
|
.collect();
|
|
let new_breadcrumbs: Vec<Box<dyn ContextElement>> = self
|
|
.breadcrumbs
|
|
.iter()
|
|
.map(|bread| bread.clone_to_box())
|
|
.collect();
|
|
ParametersContext {
|
|
params: new_params,
|
|
breadcrumbs: new_breadcrumbs,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl CompareContextElement for ParametersContext {
|
|
fn equals(&self, other: &dyn ContextElement) -> bool {
|
|
// TODO: Does this ever happen? perhaps I should have a panic here.
|
|
false
|
|
}
|
|
|
|
fn partial_compare(&self, other: &dyn ContextElement) -> Option<Ordering> {
|
|
// TODO: Does this ever happen? perhaps I should have a panic here.
|
|
None
|
|
}
|
|
}
|
|
|
|
impl ContextElement for String {}
|
|
|
|
impl Renderable for String {
|
|
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
|
Ok(self.clone())
|
|
}
|
|
}
|
|
|
|
impl Loopable for String {
|
|
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
|
if self.is_empty() {
|
|
Vec::new()
|
|
} else {
|
|
vec![self]
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Walkable for String {
|
|
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
|
|
Err(WalkError::CantWalk)
|
|
}
|
|
}
|
|
|
|
impl CompareContextElement for String {
|
|
fn equals(&self, other: &dyn ContextElement) -> bool {
|
|
// If its a String then compare them directly, otherwise defer
|
|
// to the other type's implementation of CompareContextElement
|
|
// since the end user could add any type.
|
|
match other.to_any().downcast_ref::<Self>() {
|
|
None => other.equals(self),
|
|
Some(other_string) => self == other_string,
|
|
}
|
|
}
|
|
|
|
fn partial_compare(&self, other: &dyn ContextElement) -> Option<Ordering> {
|
|
// If its a string then compare them directly, otherwise defer
|
|
// to the other type's implementation of CompareContextElement
|
|
// since the end user could add any type.
|
|
match other.to_any().downcast_ref::<Self>() {
|
|
None => match other.partial_compare(self) {
|
|
None => None,
|
|
Some(ord) => match ord {
|
|
Ordering::Equal => Some(Ordering::Equal),
|
|
Ordering::Greater => Some(Ordering::Less),
|
|
Ordering::Less => Some(Ordering::Greater),
|
|
},
|
|
},
|
|
Some(other_string) => self.partial_cmp(other_string),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ContextElement for u64 {}
|
|
|
|
impl Renderable for u64 {
|
|
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
|
Ok(self.to_string())
|
|
}
|
|
}
|
|
|
|
impl Loopable for u64 {
|
|
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
|
vec![self]
|
|
}
|
|
}
|
|
|
|
impl Walkable for u64 {
|
|
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
|
|
Err(WalkError::CantWalk)
|
|
}
|
|
}
|
|
|
|
impl CompareContextElement for u64 {
|
|
fn equals(&self, other: &dyn ContextElement) -> bool {
|
|
// If its a u64 then compare them directly, otherwise defer
|
|
// to the other type's implementation of CompareContextElement
|
|
// since the end user could add any type.
|
|
match other.to_any().downcast_ref::<Self>() {
|
|
None => other.equals(self),
|
|
Some(other_num) => self == other_num,
|
|
}
|
|
}
|
|
|
|
fn partial_compare(&self, other: &dyn ContextElement) -> Option<Ordering> {
|
|
// If its a u64 then compare them directly, otherwise defer
|
|
// to the other type's implementation of CompareContextElement
|
|
// since the end user could add any type.
|
|
match other.to_any().downcast_ref::<Self>() {
|
|
None => match other.partial_compare(self) {
|
|
None => None,
|
|
Some(ord) => match ord {
|
|
Ordering::Equal => Some(Ordering::Equal),
|
|
Ordering::Greater => Some(Ordering::Less),
|
|
Ordering::Less => Some(Ordering::Greater),
|
|
},
|
|
},
|
|
Some(other_num) => self.partial_cmp(other_num),
|
|
}
|
|
}
|
|
}
|