#![doc(html_root_url = "http://alexcrichton.com/toml-rs")]
#![deny(missing_docs)]
#![cfg_attr(test, deny(warnings))]
#[cfg(feature = "rustc-serialize")] extern crate rustc_serialize;
#[cfg(feature = "serde")] extern crate serde;
use std::collections::BTreeMap;
use std::str::FromStr;
pub use parser::{Parser, ParserError};
#[cfg(any(feature = "rustc-serialize", feature = "serde"))]
pub use self::encoder::{Encoder, Error, EncoderState, encode, encode_str};
#[cfg(any(feature = "rustc-serialize", feature = "serde"))]
pub use self::decoder::{Decoder, DecodeError, DecodeErrorKind, decode, decode_str};
mod parser;
mod display;
#[cfg(any(feature = "rustc-serialize", feature = "serde"))]
mod encoder;
#[cfg(any(feature = "rustc-serialize", feature = "serde"))]
mod decoder;
#[derive(PartialEq, Clone, Debug)]
#[allow(missing_docs)]
pub enum Value {
String(String),
Integer(i64),
Float(f64),
Boolean(bool),
Datetime(String),
Array(Array),
Table(Table),
}
pub type Array = Vec<Value>;
pub type Table = BTreeMap<String, Value>;
impl Value {
pub fn same_type(&self, other: &Value) -> bool {
match (self, other) {
(&Value::String(..), &Value::String(..)) |
(&Value::Integer(..), &Value::Integer(..)) |
(&Value::Float(..), &Value::Float(..)) |
(&Value::Boolean(..), &Value::Boolean(..)) |
(&Value::Datetime(..), &Value::Datetime(..)) |
(&Value::Array(..), &Value::Array(..)) |
(&Value::Table(..), &Value::Table(..)) => true,
_ => false,
}
}
pub fn type_str(&self) -> &'static str {
match *self {
Value::String(..) => "string",
Value::Integer(..) => "integer",
Value::Float(..) => "float",
Value::Boolean(..) => "boolean",
Value::Datetime(..) => "datetime",
Value::Array(..) => "array",
Value::Table(..) => "table",
}
}
pub fn as_str(&self) -> Option<&str> {
match *self { Value::String(ref s) => Some(&**s), _ => None }
}
pub fn as_integer(&self) -> Option<i64> {
match *self { Value::Integer(i) => Some(i), _ => None }
}
pub fn as_float(&self) -> Option<f64> {
match *self { Value::Float(f) => Some(f), _ => None }
}
pub fn as_bool(&self) -> Option<bool> {
match *self { Value::Boolean(b) => Some(b), _ => None }
}
pub fn as_datetime(&self) -> Option<&str> {
match *self { Value::Datetime(ref s) => Some(&**s), _ => None }
}
pub fn as_slice(&self) -> Option<&[Value]> {
match *self { Value::Array(ref s) => Some(&**s), _ => None }
}
pub fn as_table(&self) -> Option<&Table> {
match *self { Value::Table(ref s) => Some(s), _ => None }
}
pub fn lookup(&self, path: &str) -> Option<&Value> {
let ref path = match Parser::new(path).lookup() {
Some(path) => path,
None => return None,
};
let mut cur_value = self;
if path.is_empty() {
return Some(cur_value)
}
for key in path {
match *cur_value {
Value::Table(ref hm) => {
match hm.get(key) {
Some(v) => cur_value = v,
None => return None
}
},
Value::Array(ref v) => {
match key.parse::<usize>().ok() {
Some(idx) if idx < v.len() => cur_value = &v[idx],
_ => return None
}
},
_ => return None
}
};
Some(cur_value)
}
pub fn lookup_mut(&mut self, path: &str) -> Option<&mut Value> {
let ref path = match Parser::new(path).lookup() {
Some(path) => path,
None => return None,
};
let mut cur = self;
if path.is_empty() {
return Some(cur)
}
for key in path {
let tmp = cur;
match *tmp {
Value::Table(ref mut hm) => {
match hm.get_mut(key) {
Some(v) => cur = v,
None => return None
}
}
Value::Array(ref mut v) => {
match key.parse::<usize>().ok() {
Some(idx) if idx < v.len() => cur = &mut v[idx],
_ => return None
}
}
_ => return None
}
}
Some(cur)
}
}
impl FromStr for Value {
type Err = Vec<ParserError>;
fn from_str(s: &str) -> Result<Value, Vec<ParserError>> {
let mut p = Parser::new(s);
match p.parse().map(Value::Table) {
Some(n) => Ok(n),
None => Err(p.errors),
}
}
}
#[cfg(test)]
mod tests {
use super::Value;
#[test]
fn lookup_mut_change() {
let toml = r#"
[test]
foo = "bar"
[[values]]
foo = "baz"
[[values]]
foo = "qux"
"#;
let mut value: Value = toml.parse().unwrap();
{
let foo = value.lookup_mut("values.0.foo").unwrap();
*foo = Value::String(String::from("bar"));
}
let foo = value.lookup("values.0.foo").unwrap();
assert_eq!(foo.as_str().unwrap(), "bar");
}
#[test]
fn lookup_mut_valid() {
let toml = r#"
[test]
foo = "bar"
[[values]]
foo = "baz"
[[values]]
foo = "qux"
"#;
let mut value: Value = toml.parse().unwrap();
{
let test_foo = value.lookup_mut("test.foo").unwrap();
assert_eq!(test_foo.as_str().unwrap(), "bar");
}
{
let foo1 = value.lookup_mut("values.1.foo").unwrap();
assert_eq!(foo1.as_str().unwrap(), "qux");
}
assert!(value.lookup_mut("test.bar").is_none());
assert!(value.lookup_mut("test.foo.bar").is_none());
}
#[test]
fn lookup_mut_invalid_index() {
let toml = r#"
[[values]]
foo = "baz"
"#;
let mut value: Value = toml.parse().unwrap();
{
let foo = value.lookup_mut("test.foo");
assert!(foo.is_none());
}
{
let foo = value.lookup_mut("values.100.foo");
assert!(foo.is_none());
}
{
let foo = value.lookup_mut("values.str.foo");
assert!(foo.is_none());
}
}
#[test]
fn lookup_mut_self() {
let mut value: Value = r#"foo = "bar""#.parse().unwrap();
{
let foo = value.lookup_mut("foo").unwrap();
assert_eq!(foo.as_str().unwrap(), "bar");
}
let foo = value.lookup_mut("").unwrap();
assert!(foo.as_table().is_some());
let baz = foo.lookup_mut("foo").unwrap();
assert_eq!(baz.as_str().unwrap(), "bar");
}
#[test]
fn lookup_valid() {
let toml = r#"
[test]
foo = "bar"
[[values]]
foo = "baz"
[[values]]
foo = "qux"
"#;
let value: Value = toml.parse().unwrap();
let test_foo = value.lookup("test.foo").unwrap();
assert_eq!(test_foo.as_str().unwrap(), "bar");
let foo1 = value.lookup("values.1.foo").unwrap();
assert_eq!(foo1.as_str().unwrap(), "qux");
assert!(value.lookup("test.bar").is_none());
assert!(value.lookup("test.foo.bar").is_none());
}
#[test]
fn lookup_invalid_index() {
let toml = r#"
[[values]]
foo = "baz"
"#;
let value: Value = toml.parse().unwrap();
let foo = value.lookup("test.foo");
assert!(foo.is_none());
let foo = value.lookup("values.100.foo");
assert!(foo.is_none());
let foo = value.lookup("values.str.foo");
assert!(foo.is_none());
}
#[test]
fn lookup_self() {
let value: Value = r#"foo = "bar""#.parse().unwrap();
let foo = value.lookup("foo").unwrap();
assert_eq!(foo.as_str().unwrap(), "bar");
let foo = value.lookup("").unwrap();
assert!(foo.as_table().is_some());
let baz = foo.lookup("foo").unwrap();
assert_eq!(baz.as_str().unwrap(), "bar");
}
#[test]
fn lookup_advanced() {
let value: Value = "[table]\n\"value\" = 0".parse().unwrap();
let looked = value.lookup("table.\"value\"").unwrap();
assert_eq!(*looked, Value::Integer(0));
}
#[test]
fn lookup_advanced_table() {
let value: Value = "[table.\"name.other\"]\nvalue = \"my value\"".parse().unwrap();
let looked = value.lookup(r#"table."name.other".value"#).unwrap();
assert_eq!(*looked, Value::String(String::from("my value")));
}
#[test]
fn lookup_mut_advanced() {
let mut value: Value = "[table]\n\"value\" = [0, 1, 2]".parse().unwrap();
let looked = value.lookup_mut("table.\"value\".1").unwrap();
assert_eq!(*looked, Value::Integer(1));
}
#[test]
fn single_dot() {
let value: Value = "[table]\n\"value\" = [0, 1, 2]".parse().unwrap();
assert_eq!(None, value.lookup("."));
}
#[test]
fn array_dot() {
let value: Value = "[table]\n\"value\" = [0, 1, 2]".parse().unwrap();
assert_eq!(None, value.lookup("0."));
}
#[test]
fn dot_inside() {
let value: Value = "[table]\n\"value\" = [0, 1, 2]".parse().unwrap();
assert_eq!(None, value.lookup("table.\"value.0\""));
}
#[test]
fn table_with_quotes() {
let value: Value = "[table.\"element\"]\n\"value\" = [0, 1, 2]".parse().unwrap();
assert_eq!(None, value.lookup("\"table.element\".\"value\".0"));
}
#[test]
fn table_with_quotes_2() {
let value: Value = "[table.\"element\"]\n\"value\" = [0, 1, 2]".parse().unwrap();
assert_eq!(Value::Integer(0), *value.lookup("table.\"element\".\"value\".0").unwrap());
}
}