Implement a more advanced algorithm for lookup

The new algorithm allows the explicit usage of "" and '' to denote key
names. This is useful for accessing tables or keys that are named in a
non-conventional manner.
This commit is contained in:
Bourgond Aries 2016-03-27 20:46:19 +02:00
parent b171205c57
commit 3517215eab
2 changed files with 133 additions and 9 deletions

View file

@ -181,12 +181,16 @@ impl Value {
/// assert_eq!(no_bar.is_none(), true); /// assert_eq!(no_bar.is_none(), true);
/// ``` /// ```
pub fn lookup<'a>(&'a self, path: &'a str) -> Option<&'a Value> { pub fn lookup<'a>(&'a self, path: &'a str) -> Option<&'a Value> {
let ref path = match Parser::new(path).lookup() {
Some(path) => path,
None => return None,
};
let mut cur_value = self; let mut cur_value = self;
if path.len() == 0 { if path.len() == 0 {
return Some(cur_value) return Some(cur_value)
} }
for key in path.split('.') { for key in path {
match *cur_value { match *cur_value {
Value::Table(ref hm) => { Value::Table(ref hm) => {
match hm.get(key) { match hm.get(key) {
@ -205,8 +209,8 @@ impl Value {
}; };
Some(cur_value) Some(cur_value)
}
}
/// Lookups for mutable value at specified path. /// Lookups for mutable value at specified path.
/// ///
/// Uses '.' as a path separator. /// Uses '.' as a path separator.
@ -278,6 +282,7 @@ impl FromStr for Value {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::Value; use super::Value;
use parser::Parser;
#[test] #[test]
fn lookup_mut_change() { fn lookup_mut_change() {
@ -428,4 +433,47 @@ mod tests {
let baz = foo.lookup("foo").unwrap(); let baz = foo.lookup("foo").unwrap();
assert_eq!(baz.as_str().unwrap(), "bar"); 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_internal() {
let mut parser = Parser::new(r#"hello."world\t".a.0.'escaped'.value"#);
let result = vec![
String::from("hello"),
String::from("world\t"),
String::from("a"),
String::from("0"),
String::from("escaped"),
String::from("value")
];
assert_eq!(parser.lookup().unwrap(), result);
}
#[test]
fn lookup_internal_void() {
let mut parser = Parser::new("");
assert_eq!(parser.lookup().unwrap(), Vec::<String>::new());
}
#[test]
fn lookup_internal_simple() {
let mut parser = Parser::new("value");
assert_eq!(parser.lookup().unwrap(), vec![String::from("value")]);
}
// This is due to key_name not parsing an empty "" correctly. Disabled for now.
#[test]
#[ignore]
fn lookup_internal_quoted_void() {
let mut parser = Parser::new("\"\"");
assert_eq!(parser.lookup().unwrap(), vec![String::from("")]);
}
} }

View file

@ -290,6 +290,46 @@ impl<'a> Parser<'a> {
} }
} }
// Parse an array index as a natural number
fn array_index(&mut self) -> Option<String> {
let mut index = String::new();
while let Some((_, ch)) = self.peek(0) {
match ch {
v @ '0' ... '9' => {
if !self.eat(v) {
return None
}
index.push(v);
}
_ => return Some(index),
}
}
if index.len() > 0 {
return Some(index);
}
None
}
/// Parse a path into a vector of paths
pub fn lookup(&mut self) -> Option<Vec<String>> {
if self.input.len() == 0 {
return Some(vec![]);
}
let mut keys = Vec::new();
loop {
self.ws();
if let Some(s) = self.key_name() {
keys.push(s);
} else if let Some(s) = self.array_index() {
keys.push(s);
} else {
return None
}
self.ws();
if !self.expect('.') { return Some(keys) }
}
}
// Parse a single key name starting at `start` // Parse a single key name starting at `start`
fn key_name(&mut self) -> Option<String> { fn key_name(&mut self) -> Option<String> {
let start = self.next_pos(); let start = self.next_pos();
@ -969,6 +1009,42 @@ mod tests {
}) })
} }
#[test]
fn lookup_internal() {
let mut parser = Parser::new(r#"hello."world\t".a.0.'escaped'.value"#);
let result = vec![
String::from("hello"),
String::from("world\t"),
String::from("a"),
String::from("0"),
String::from("escaped"),
String::from("value")
];
assert_eq!(parser.lookup().unwrap(), result);
}
#[test]
fn lookup_internal_void() {
let mut parser = Parser::new("");
assert_eq!(parser.lookup().unwrap(), Vec::<String>::new());
}
#[test]
fn lookup_internal_simple() {
let mut parser = Parser::new("value");
assert_eq!(parser.lookup().unwrap(), vec![String::from("value")]);
}
// This is due to key_name not parsing an empty "" correctly. Disabled for now.
#[test]
#[ignore]
fn lookup_internal_quoted_void() {
let mut parser = Parser::new("\"\"");
assert_eq!(parser.lookup().unwrap(), vec![String::from("")]);
}
#[test] #[test]
fn crlf() { fn crlf() {
let mut p = Parser::new("\ let mut p = Parser::new("\
@ -1246,10 +1322,10 @@ trimmed in raw strings.
assert!(table.lookup("foo_3").is_some()); assert!(table.lookup("foo_3").is_some());
assert!(table.lookup("foo_-2--3--r23f--4-f2-4").is_some()); assert!(table.lookup("foo_-2--3--r23f--4-f2-4").is_some());
assert!(table.lookup("a").is_some()); assert!(table.lookup("a").is_some());
assert!(table.lookup("!").is_some()); assert!(table.lookup("\"!\"").is_some());
assert!(table.lookup("\"").is_some()); assert!(table.lookup("\"\\\"\"").is_some());
assert!(table.lookup("character encoding").is_some()); assert!(table.lookup("\"character encoding\"").is_some());
assert!(table.lookup("ʎǝʞ").is_some()); assert!(table.lookup("'ʎǝʞ'").is_some());
} }
#[test] #[test]
@ -1293,9 +1369,9 @@ trimmed in raw strings.
"); ");
let table = Table(p.parse().unwrap()); let table = Table(p.parse().unwrap());
assert!(table.lookup("a.b").is_some()); assert!(table.lookup("a.b").is_some());
assert!(table.lookup("f f").is_some()); assert!(table.lookup("\"f f\"").is_some());
assert!(table.lookup("\"").is_some()); assert!(table.lookup("\"\\\"\"").is_some());
assert!(table.lookup("\"\"").is_some()); assert!(table.lookup("'\"\"'").is_some());
} }
#[test] #[test]