Skip to content

Commit

Permalink
Merge pull request #91 from epage/serde
Browse files Browse the repository at this point in the history
feat(value): Add serde support
  • Loading branch information
epage authored May 2, 2017
2 parents e0d4f7a + 8ae7f5a commit 79f4d81
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 38 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ lazy_static = "0.2"
chrono = "0.3"
unicode-segmentation = "1.1"
itertools = "0.6.0"
serde = "1.0"
serde_derive = "1.0"

[build-dependencies]
skeptic = "0.9"

[dev-dependencies]
difference = "1.0"
skeptic = "0.9"
serde_yaml = "0.7"

[features]
default = ["extra-filters"]
Expand Down
20 changes: 9 additions & 11 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use error::{Result, Error};
use filters::Filter;
use std::collections::HashMap;
use token::Token::{self, Identifier, StringLiteral, NumberLiteral, BooleanLiteral};
use value::Value;
use value::{Value, Object};


#[derive(Clone)]
Expand All @@ -11,12 +11,10 @@ pub enum Interrupt {
Break,
}

type ValueMap = HashMap<String, Value>;

#[derive(Default)]
pub struct Context {
stack: Vec<ValueMap>,
globals: ValueMap,
stack: Vec<Object>,
globals: Object,

/// The current interrupt state. The interrupt state is used by
/// the `break` and `continue` tags to halt template rendering
Expand All @@ -43,22 +41,22 @@ impl Context {
/// assert_eq!(ctx.get_val("test"), None);
/// ```
pub fn new() -> Context {
Context::with_values_and_filters(HashMap::new(), HashMap::new())
Context::with_values_and_filters(Object::new(), HashMap::new())
}

pub fn with_values(values: HashMap<String, Value>) -> Context {
pub fn with_values(values: Object) -> Context {
Context::with_values_and_filters(values, HashMap::new())
}

pub fn with_filters(filters: HashMap<String, Box<Filter>>) -> Context {
Context::with_values_and_filters(HashMap::new(), filters)
Context::with_values_and_filters(Object::new(), filters)
}

pub fn with_values_and_filters(values: HashMap<String, Value>,
pub fn with_values_and_filters(values: Object,
filters: HashMap<String, Box<Filter>>)
-> Context {
Context {
stack: vec![HashMap::new()],
stack: vec![Object::new()],
interrupt: None,
cycles: HashMap::new(),
globals: values,
Expand Down Expand Up @@ -116,7 +114,7 @@ impl Context {

/// Creates a new variable scope chained to a parent scope.
fn push_scope(&mut self) {
self.stack.push(HashMap::new());
self.stack.push(Object::new());
}

/// Removes the topmost stack frame from the local variable stack.
Expand Down
11 changes: 9 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,18 @@
#![allow(unknown_lints)]
#![allow(zero_ptr)]

#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate chrono;
extern crate unicode_segmentation;
extern crate itertools;

#[macro_use]
extern crate lazy_static;

extern crate serde;
#[macro_use]
extern crate serde_derive;

use std::collections::HashMap;
use lexer::Element;
use tags::{assign_tag, cycle_tag, include_tag, break_tag, continue_tag, comment_block, raw_block,
Expand All @@ -49,6 +54,8 @@ use std::path::{PathBuf, Path};
use error::Result;

pub use value::Value;
pub use value::Object;
pub use value::Array;
pub use context::Context;
pub use template::Template;
pub use error::Error;
Expand Down
52 changes: 27 additions & 25 deletions src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ use token::Token::*;

/// An enum to represent different value types
#[derive(Clone, Debug)]
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
pub enum Value {
Str(String),
Num(f32),
Bool(bool),
Str(String),
Array(Array),
Object(Object),
}
Expand Down Expand Up @@ -40,19 +42,6 @@ impl<'a> Value {
}
}

/// Extracts the str value if it is a str.
pub fn as_str(&'a self) -> Option<&'a str> {
match *self {
Value::Str(ref v) => Some(v),
_ => None,
}
}

/// Tests whether this value is a str
pub fn is_str(&self) -> bool {
self.as_str().is_some()
}

/// Extracts the float value if it is a float.
pub fn as_float(&self) -> Option<f32> {
match *self {
Expand All @@ -79,6 +68,19 @@ impl<'a> Value {
self.as_bool().is_some()
}

/// Extracts the str value if it is a str.
pub fn as_str(&'a self) -> Option<&'a str> {
match *self {
Value::Str(ref v) => Some(v),
_ => None,
}
}

/// Tests whether this value is a str
pub fn is_str(&self) -> bool {
self.as_str().is_some()
}

/// Extracts the array value if it is an array.
pub fn as_array(&self) -> Option<&Vec<Value>> {
match *self {
Expand Down Expand Up @@ -125,9 +127,9 @@ impl<'a> Value {
impl PartialEq<Value> for Value {
fn eq(&self, other: &Value) -> bool {
match (self, other) {
(&Value::Str(ref x), &Value::Str(ref y)) => x == y,
(&Value::Num(x), &Value::Num(y)) => x == y,
(&Value::Bool(x), &Value::Bool(y)) => x == y,
(&Value::Str(ref x), &Value::Str(ref y)) => x == y,
(&Value::Array(ref x), &Value::Array(ref y)) => x == y,
(&Value::Object(ref x), &Value::Object(ref y)) => x == y,

Expand All @@ -146,9 +148,9 @@ impl Eq for Value {}
impl PartialOrd<Value> for Value {
fn partial_cmp(&self, other: &Value) -> Option<Ordering> {
match (self, other) {
(&Value::Str(ref x), &Value::Str(ref y)) => x.partial_cmp(y),
(&Value::Num(x), &Value::Num(y)) => x.partial_cmp(&y),
(&Value::Bool(x), &Value::Bool(y)) => x.partial_cmp(&y),
(&Value::Str(ref x), &Value::Str(ref y)) => x.partial_cmp(y),
(&Value::Array(ref x), &Value::Array(ref y)) => x.iter().partial_cmp(y.iter()),
(&Value::Object(ref x), &Value::Object(ref y)) => x.iter().partial_cmp(y.iter()),
_ => None,
Expand All @@ -159,9 +161,9 @@ impl PartialOrd<Value> for Value {
impl ToString for Value {
fn to_string(&self) -> String {
match *self {
Value::Str(ref x) => x.to_owned(),
Value::Num(ref x) => x.to_string(),
Value::Bool(ref x) => x.to_string(),
Value::Str(ref x) => x.to_owned(),
Value::Array(ref x) => {
let arr: Vec<String> = x.iter().map(|v| v.to_string()).collect();
arr.join(", ")
Expand Down Expand Up @@ -190,21 +192,21 @@ mod test {
static FALSE: Value = Value::Bool(false);

#[test]
fn test_as_str() {
fn test_num_to_string() {
let val = Value::Num(42f32);
assert_eq!(val.as_str(), None);
assert_eq!(&val.to_string(), "42");

let val = Value::str("test");
assert_eq!(val.as_str(), Some("test"));
let val = Value::Num(42.34);
assert_eq!(&val.to_string(), "42.34");
}

#[test]
fn test_num_to_string() {
fn test_as_str() {
let val = Value::Num(42f32);
assert_eq!(&val.to_string(), "42");
assert_eq!(val.as_str(), None);

let val = Value::Num(42.34);
assert_eq!(&val.to_string(), "42.34");
let val = Value::str("test");
assert_eq!(val.as_str(), Some("test"));
}

#[test]
Expand Down
131 changes: 131 additions & 0 deletions tests/value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
extern crate liquid;
extern crate serde_yaml;

#[macro_use]
extern crate difference;

use std::f32;

#[test]
pub fn serialize_num() {
let actual = liquid::Value::Num(1f32);
let actual = serde_yaml::to_string(&actual).unwrap();
assert_diff!(&actual, "---\n1", "", 0);

let actual = liquid::Value::Num(-100f32);
let actual = serde_yaml::to_string(&actual).unwrap();
assert_diff!(&actual, "---\n-100", "", 0);

let actual = liquid::Value::Num(3.14e10f32);
let actual = serde_yaml::to_string(&actual).unwrap();
assert_diff!(&actual, "---\n31399999488", "", 0);

let actual = liquid::Value::Num(f32::NAN);
let actual = serde_yaml::to_string(&actual).unwrap();
assert_diff!(&actual, "---\nNaN", "", 0);

let actual = liquid::Value::Num(f32::INFINITY);
let actual = serde_yaml::to_string(&actual).unwrap();
assert_diff!(&actual, "---\ninf", "", 0);
}

#[test]
pub fn deserialize_num() {
let actual: liquid::Value = serde_yaml::from_str("---\n1").unwrap();
assert_eq!(actual, liquid::Value::Num(1f32));

let actual: liquid::Value = serde_yaml::from_str("---\n-100").unwrap();
assert_eq!(actual, liquid::Value::Num(-100f32));

let actual: liquid::Value = serde_yaml::from_str("---\n31399999488").unwrap();
assert_eq!(actual, liquid::Value::Num(3.14e10f32));

// Skipping NaN since equality fails

let actual: liquid::Value = serde_yaml::from_str("---\ninf").unwrap();
assert_eq!(actual, liquid::Value::Num(f32::INFINITY));
}

#[test]
pub fn serialize_bool() {
let actual = liquid::Value::Bool(true);
let actual = serde_yaml::to_string(&actual).unwrap();
assert_diff!(&actual, "---\ntrue", "", 0);

let actual = liquid::Value::Bool(false);
let actual = serde_yaml::to_string(&actual).unwrap();
assert_diff!(&actual, "---\nfalse", "", 0);
}

#[test]
pub fn deserialize_bool() {
let actual: liquid::Value = serde_yaml::from_str("---\ntrue").unwrap();
assert_eq!(actual, liquid::Value::Bool(true));

let actual: liquid::Value = serde_yaml::from_str("---\nfalse").unwrap();
assert_eq!(actual, liquid::Value::Bool(false));
}

#[test]
pub fn serialize_str() {
let actual = liquid::Value::str("Hello");
let actual = serde_yaml::to_string(&actual).unwrap();
assert_diff!(&actual, "---\nHello", "", 0);

let actual = liquid::Value::str("10");
let actual = serde_yaml::to_string(&actual).unwrap();
assert_diff!(&actual, "---\n\"10\"", "", 0);

let actual = liquid::Value::str("false");
let actual = serde_yaml::to_string(&actual).unwrap();
assert_diff!(&actual, "---\n\"false\"", "", 0);
}

#[test]
pub fn deserialize_str() {
let actual: liquid::Value = serde_yaml::from_str("---\nHello").unwrap();
assert_eq!(actual, liquid::Value::str("Hello"));

let actual: liquid::Value = serde_yaml::from_str("\"10\"\n").unwrap();
assert_eq!(actual, liquid::Value::str("10"));

let actual: liquid::Value = serde_yaml::from_str("---\n\"false\"").unwrap();
assert_eq!(actual, liquid::Value::str("false"));
}

#[test]
pub fn serialize_array() {
let actual =
vec![liquid::Value::Num(1f32), liquid::Value::Bool(true), liquid::Value::str("true")];
let actual = liquid::Value::Array(actual);
let actual = serde_yaml::to_string(&actual).unwrap();
assert_diff!(&actual, "---\n- 1\n- true\n- \"true\"", "", 0);
}

#[test]
pub fn deserialize_array() {
let actual: liquid::Value = serde_yaml::from_str("---\n- 1\n- true\n- \"true\"").unwrap();
let expected =
vec![liquid::Value::Num(1f32), liquid::Value::Bool(true), liquid::Value::str("true")];
let expected = liquid::Value::Array(expected);
assert_eq!(actual, expected);
}

#[test]
pub fn serialize_object() {
// Skipping due to HashMap ordering issues
}

#[test]
pub fn deserialize_object() {
let actual: liquid::Value = serde_yaml::from_str("---\nNum: 1\nBool: true\nStr: \"true\"")
.unwrap();
let expected: liquid::Object = [("Num".to_owned(), liquid::Value::Num(1f32)),
("Bool".to_owned(), liquid::Value::Bool(true)),
("Str".to_owned(), liquid::Value::str("true"))]
.iter()
.cloned()
.collect();
let expected = liquid::Value::Object(expected);
assert_eq!(actual, expected);
}

0 comments on commit 79f4d81

Please sign in to comment.