diff --git a/Cargo.toml b/Cargo.toml index f1a1385..4979f62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ authors = ["eV "] name = "molysite" version = "0.1.0" +edition = "2018" -[dependencies.nom] -features = ["nightly"] -version = "^3.2" +[dependencies] +nom = "^3.2" diff --git a/src/common.rs b/src/common.rs index 51c435f..cbcb5d2 100644 --- a/src/common.rs +++ b/src/common.rs @@ -11,11 +11,14 @@ named!(pub boolean, ) ); -named!(unsigned_float, recognize!(alt_complete!( - delimited!(digit, tag!("."), opt!(complete!(digit))) | - delimited!(opt!(digit), tag!("."), digit) | - digit -))); +named!( + unsigned_float, + recognize!(alt_complete!( + delimited!(digit, tag!("."), opt!(complete!(digit))) + | delimited!(opt!(digit), tag!("."), digit) + | digit + )) +); named!(pub float, map_res!( map_res!( @@ -32,8 +35,8 @@ named!(pub float, map_res!( FromStr::from_str )); -fn to_i(i: &str) -> Result { - u32::from_str_radix(i, 16) +fn to_i(i: &str) -> Result { + u32::from_str_radix(i, 16) } named!(pub int, map_res!( @@ -43,7 +46,7 @@ named!(pub int, map_res!( ), to_i )); - + // TODO: add support for octal named!(pub number, alt_complete!( map!(int, |i| { i as f32 }) | diff --git a/src/hcl.rs b/src/hcl.rs index c460598..ef286cd 100644 --- a/src/hcl.rs +++ b/src/hcl.rs @@ -1,18 +1,17 @@ -use std::str::{self, FromStr}; -use std::string::String; use std::collections::HashMap; +use std::str; +use std::string::String; -use nom::{alphanumeric, eol, multispace, not_line_ending}; use nom::IResult::Done; +use nom::{alphanumeric, eol, multispace, not_line_ending}; -use common::{boolean, number}; -use types::{JsonValue, ParseError}; - +use crate::common::{boolean, number}; +use crate::types::{JsonValue, ParseError}; pub fn parse_hcl(config: &str) -> Result { match hcl(&config.as_bytes()[..]) { Done(_, c) => Ok(c), - _ => Err(0) + _ => Err(0), } } @@ -20,122 +19,158 @@ named!(hcl, map!(hcl_top, |h| JsonValue::Object(h))); named!(end_of_line, alt!(eof!() | eol)); -fn to_s(i:Vec) -> String { String::from_utf8_lossy(&i).into_owned() } -fn slen(i: String) -> usize { i.len() } -fn ulen(i: &[u8]) -> usize { i.len() } -fn take_limited(min: usize, max: usize) -> usize { if max < min { return max } return min } +fn to_s(i: Vec) -> String { + String::from_utf8_lossy(&i).into_owned() +} +fn slen(i: String) -> usize { + i.len() +} +fn ulen(i: &[u8]) -> usize { + i.len() +} +fn take_limited(min: usize, max: usize) -> usize { + if max < min { + return max; + } + return min; +} -named!(hcl_escaped_string, map!( - escaped_transform!(is_not!("\\\"\n"), '\\', alt!( - tag!("\\") => { |_| &b"\\"[..] } | - tag!("\"") => { |_| &b"\""[..] } | - tag!("n") => { |_| &b"\n"[..] } - )), to_s -)); +named!( + hcl_escaped_string, + map!( + escaped_transform!( + is_not!("\\\"\n"), + '\\', + alt!( + tag!("\\") => { |_| &b"\\"[..] } | + tag!("\"") => { |_| &b"\""[..] } | + tag!("n") => { |_| &b"\n"[..] } + ) + ), + to_s + ) +); -named!(hcl_template_string, map!( - do_parse!( - tag!("${") >> - s: take_until_and_consume!("}") >> - (s) - ), - |s| { format!("${{{}}}", String::from_utf8_lossy(s)) } -)); - -named!(hcl_quoted_escaped_string, delimited!( - tag!("\""), +named!( + hcl_template_string, map!( - fold_many0!( - alt_complete!( - hcl_template_string | - flat_map!(do_parse!( - max: map!(peek!(hcl_escaped_string), slen) >> - min: map!(peek!(take_until!("${")), ulen) >> - buf: take!(take_limited(min, max)) >> - (buf) - ), hcl_escaped_string) | - hcl_escaped_string + do_parse!(tag!("${") >> s: take_until_and_consume!("}") >> (s)), + |s| format!("${{{}}}", String::from_utf8_lossy(s)) + ) +); + +named!( + hcl_quoted_escaped_string, + delimited!( + tag!("\""), + map!( + fold_many0!( + alt_complete!( + hcl_template_string + | flat_map!( + do_parse!( + max: map!(peek!(hcl_escaped_string), slen) + >> min: map!(peek!(take_until!("${")), ulen) + >> buf: take!(take_limited(min, max)) + >> (buf) + ), + hcl_escaped_string + ) + | hcl_escaped_string + ), + Vec::new(), + |mut acc: Vec<_>, item| { + acc.push(item); + acc + } ), - Vec::new(), - |mut acc: Vec<_>, item| { - acc.push(item); - acc - } + |s| s.join("") ), - |s| { s.join("") } - ), - tag!("\"") -)); + tag!("\"") + ) +); -named!(hcl_multiline_string, map!( - do_parse!( - delimiter: tag!("<<") >> - indent: opt!(tag!("-")) >> - delimiter: terminated!(alphanumeric, eol) >> - delimiter_str: expr_res!(str::from_utf8(delimiter)) >> - s: take_until!(delimiter_str) >> - tag!(delimiter_str) >> - end_of_line >> - (indent, s) - ), - |(indent, s)| { - let body = String::from_utf8_lossy(s); - let lines: Vec<&str> = body.split("\n").collect(); - let mut out: Vec<&str> = Vec::new(); - let count = lines.len(); - - let mut min_indent = 80; - if let Some(_) = indent { - for (i, line) in lines.clone().into_iter().enumerate() { - let indent_num = line.len() - line.trim_left().len(); - if indent_num < min_indent { - min_indent = indent_num; - } - if i != count - 1 { - if min_indent < indent_num { - // NOTE this behavior is odd, and will change based on the hcl2 specs - min_indent = 0; +named!( + hcl_multiline_string, + map!( + do_parse!( + delimiter: tag!("<<") + >> indent: opt!(tag!("-")) + >> delimiter: terminated!(alphanumeric, eol) + >> delimiter_str: expr_res!(str::from_utf8(delimiter)) + >> s: take_until!(delimiter_str) + >> tag!(delimiter_str) + >> end_of_line + >> (indent, s) + ), + |(indent, s)| { + let body = String::from_utf8_lossy(s); + let lines: Vec<&str> = body.split("\n").collect(); + let mut out: Vec<&str> = Vec::new(); + let count = lines.len(); + + let mut min_indent = 80; + if let Some(_) = indent { + for (i, line) in lines.clone().into_iter().enumerate() { + let indent_num = line.len() - line.trim_start().len(); + if indent_num < min_indent { + min_indent = indent_num; + } + if i != count - 1 { + if min_indent < indent_num { + // NOTE this behavior is odd, and will change based on the hcl2 specs + min_indent = 0; + } } } } - } - for (i, line) in lines.into_iter().enumerate() { - if i != count - 1 { - if let Some(_) = indent { - out.push(&line[min_indent..]) - } else { - out.push(line) + for (i, line) in lines.into_iter().enumerate() { + if i != count - 1 { + if let Some(_) = indent { + out.push(&line[min_indent..]) + } else { + out.push(line) + } } } + out.join("\n") + "\n" } - out.join("\n") + "\n" - } -)); + ) +); // close enough... -named!(identifier_char, alt!(tag!("_") | tag!("-") | tag!(".") | alphanumeric)); +named!( + identifier_char, + alt!(tag!("_") | tag!("-") | tag!(".") | alphanumeric) +); -named!(hcl_unquoted_key, map!( - fold_many0!( - identifier_char, - Vec::new(), - |mut acc: Vec<_>, item| { +named!( + hcl_unquoted_key, + map!( + fold_many0!(identifier_char, Vec::new(), |mut acc: Vec<_>, item| { acc.extend(item); acc - } - ), - to_s -)); + }), + to_s + ) +); -named!(hcl_quoted_escaped_key, - map!(do_parse!(tag!("\"") >> out: opt!(hcl_escaped_string) >> tag!("\"") >> (out)), - |out| { if let Some(val) = out { val } else { "".to_string() } } -)); +named!( + hcl_quoted_escaped_key, + map!( + do_parse!(tag!("\"") >> out: opt!(hcl_escaped_string) >> tag!("\"") >> (out)), + |out| if let Some(val) = out { + val + } else { + "".to_string() + } + ) +); -named!(hcl_key, alt!( - hcl_quoted_escaped_key | - hcl_unquoted_key -)); +named!( + hcl_key, + alt!(hcl_quoted_escaped_key | hcl_unquoted_key) +); named!(space, eat_separator!(&b" \t"[..])); @@ -147,42 +182,48 @@ macro_rules! sp ( ) ); -named!(hcl_key_value<(String, JsonValue)>, sp!(alt_complete!( - separated_pair!(hcl_key, tag!("="), hcl_value_nested_hash) | - separated_pair!(hcl_key, tag!("="), hcl_value) | - pair!(hcl_key, hcl_value_nested_hash) -))); +named!( + hcl_key_value<(String, JsonValue)>, + sp!(alt_complete!( + separated_pair!(hcl_key, tag!("="), hcl_value_nested_hash) + | separated_pair!(hcl_key, tag!("="), hcl_value) + | pair!(hcl_key, hcl_value_nested_hash) + )) +); -named!(comment_one_line, +named!( + comment_one_line, do_parse!(alt!(tag!("//") | tag!("#")) >> opt!(not_line_ending) >> end_of_line >> (&b""[..])) ); -named!(comment_block, +named!( + comment_block, do_parse!(tag!("/*") >> take_until_and_consume!("*/") >> (&b""[..])) ); -named!(blanks, - do_parse!(many0!(alt!(tag!(",") | multispace | comment_one_line | comment_block)) >> (&b""[..])) +named!( + blanks, + do_parse!( + many0!(alt!( + tag!(",") | multispace | comment_one_line | comment_block + )) >> (&b""[..]) + ) ); -named!(hcl_key_values>, +named!( + hcl_key_values>, many0!(complete!(do_parse!( opt!(blanks) >> out: hcl_key_value >> opt!(blanks) >> (out) ))) ); -named!(hcl_hash>, - do_parse!( - opt!(blanks) >> - tag!("{") >> - out: hcl_top >> - tag!("}") >> - opt!(blanks) >> - (out) - ) +named!( + hcl_hash>, + do_parse!(opt!(blanks) >> tag!("{") >> out: hcl_top >> tag!("}") >> opt!(blanks) >> (out)) ); -named!(hcl_top>, +named!( + hcl_top>, map!(hcl_key_values, |tuple_vec| { let mut top: HashMap = HashMap::new(); for (k, v) in tuple_vec.into_iter().rev() { @@ -205,58 +246,75 @@ named!(hcl_top>, ); // a bit odd if you ask me -named!(hcl_value_nested_hash, map!( - // NOTE hcl allows arbitrarily deep nesting - pair!(many0!(sp!(hcl_quoted_escaped_key)), hcl_value_hash), - |(tuple_vec, value)| { - let mut cur = value; - for parent in tuple_vec.into_iter().rev() { - let mut inner: Vec = Vec::new(); - inner.push(cur); - let mut h: HashMap = HashMap::new(); - h.insert(parent.to_string(), JsonValue::Array(inner)); - cur = JsonValue::Object(h); +named!( + hcl_value_nested_hash, + map!( + // NOTE hcl allows arbitrarily deep nesting + pair!(many0!(sp!(hcl_quoted_escaped_key)), hcl_value_hash), + |(tuple_vec, value)| { + let mut cur = value; + for parent in tuple_vec.into_iter().rev() { + let mut inner: Vec = Vec::new(); + inner.push(cur); + let mut h: HashMap = HashMap::new(); + h.insert(parent.to_string(), JsonValue::Array(inner)); + cur = JsonValue::Object(h); + } + let mut outer: Vec = Vec::new(); + outer.push(cur); + JsonValue::Array(outer) } - let mut outer: Vec = Vec::new(); - outer.push(cur); - JsonValue::Array(outer) - } -)); + ) +); -named!(hcl_value_hash, map!(hcl_hash, |h| JsonValue::Object(h))); +named!( + hcl_value_hash, + map!(hcl_hash, |h| JsonValue::Object(h)) +); -named!(hcl_array>, delimited!( - tag!("["), - do_parse!( - init: fold_many0!( - do_parse!(opt!(blanks) >> out: hcl_value >> opt!(blanks) >> tag!(",") >> opt!(blanks) >> (out)), - Vec::new(), - |mut acc: Vec<_>, item| { - acc.push(item); - acc - } - ) >> - ret: fold_many0!( - do_parse!(opt!(blanks) >> out: hcl_value >> opt!(blanks) >> (out)), - init, - |mut acc: Vec<_>, item| { - acc.push(item); - acc - } - ) >> - (ret) - ), - tag!("]") -)); - -named!(hcl_value, alt!( - hcl_hash => { |h| JsonValue::Object(h) } | - hcl_array => { |v| JsonValue::Array(v) } | - hcl_quoted_escaped_string => { |s| JsonValue::Str(s) } | - hcl_multiline_string => { |s| JsonValue::Str(s) } | - number => { |num| JsonValue::Num(num) } | - boolean => { |b| JsonValue::Boolean(b) } -)); +named!( + hcl_array>, + delimited!( + tag!("["), + do_parse!( + init: fold_many0!( + do_parse!( + opt!(blanks) + >> out: hcl_value + >> opt!(blanks) + >> tag!(",") + >> opt!(blanks) + >> (out) + ), + Vec::new(), + |mut acc: Vec<_>, item| { + acc.push(item); + acc + } + ) >> ret: fold_many0!( + do_parse!(opt!(blanks) >> out: hcl_value >> opt!(blanks) >> (out)), + init, + |mut acc: Vec<_>, item| { + acc.push(item); + acc + } + ) >> (ret) + ), + tag!("]") + ) +); + +named!( + hcl_value, + alt!( + hcl_hash => { |h| JsonValue::Object(h) } | + hcl_array => { |v| JsonValue::Array(v) } | + hcl_quoted_escaped_string => { |s| JsonValue::Str(s) } | + hcl_multiline_string => { |s| JsonValue::Str(s) } | + number => { |num| JsonValue::Num(num) } | + boolean => { |b| JsonValue::Boolean(b) } + ) +); #[test] fn hcl_hex_num() { @@ -269,7 +327,6 @@ fn hcl_hex_num() { panic!("object did not parse"); } - #[test] fn hcl_string_empty() { let test = "foo = \"\""; @@ -443,12 +500,16 @@ service \"bar\" { if let Some(&JsonValue::Object(_)) = array.get(0) { pass = true; } - if !pass { panic!("missing nested object") } + if !pass { + panic!("missing nested object") + } pass = false; if let Some(&JsonValue::Object(_)) = array.get(1) { pass = true; } - if !pass { panic!("missing nested object") } + if !pass { + panic!("missing nested object") + } } } } diff --git a/src/json.rs b/src/json.rs index 4f1958c..b3a236f 100644 --- a/src/json.rs +++ b/src/json.rs @@ -6,8 +6,8 @@ use std::str; use nom::IResult::Done; -use common::{boolean, float}; -use types::{JsonValue, ParseError}; +use crate::common::{boolean, float}; +use crate::types::{JsonValue, ParseError}; // NOTE this json parser is only included for internal verification purposes // the standard hcl parser by hashicorp includes a nonstandrd json parser @@ -16,68 +16,89 @@ use types::{JsonValue, ParseError}; pub fn parse_json(config: &str) -> Result { match json(&config.as_bytes()[..]) { Done(_, c) => Ok(c), - _ => Err(0) + _ => Err(0), } } named!(json, map!(json_hash, |h| JsonValue::Object(h))); -fn to_s(i:Vec) -> String { String::from_utf8_lossy(&i).into_owned() } - -named!(json_escaped_string, map!( - escaped_transform!(is_not!("\\\"\n"), '\\', alt!( - tag!("\\") => { |_| &b"\\"[..] } | - tag!("\"") => { |_| &b"\""[..] } | - tag!("n") => { |_| &b"\n"[..] } - )), to_s -)); +fn to_s(i: Vec) -> String { + String::from_utf8_lossy(&i).into_owned() +} -named!(json_string, delimited!( - tag!("\""), +named!( + json_escaped_string, map!( - fold_many0!( - json_escaped_string, - Vec::new(), - |mut acc: Vec<_>, item| { - acc.push(item); - acc - } + escaped_transform!( + is_not!("\\\"\n"), + '\\', + alt!( + tag!("\\") => { |_| &b"\\"[..] } | + tag!("\"") => { |_| &b"\""[..] } | + tag!("n") => { |_| &b"\n"[..] } + ) ), - |s| { s.join("") } - ), - tag!("\"") -)); - -named!(json_array>, ws!(delimited!( - tag!("["), - separated_list!(tag!(","), json_value), - tag!("]") -))); + to_s + ) +); -named!(json_key_value<(String, JsonValue)>, ws!(separated_pair!(json_string, tag!(":"), json_value))); - -named!(json_hash>, ws!(map!( +named!( + json_string, delimited!( - tag!("{"), - separated_list!(tag!(","), json_key_value), - tag!("}") - ), - |tuple_vec| { - let mut h: HashMap = HashMap::new(); - for (k, v) in tuple_vec { - h.insert(String::from(k), v); + tag!("\""), + map!( + fold_many0!(json_escaped_string, Vec::new(), |mut acc: Vec<_>, item| { + acc.push(item); + acc + }), + |s| s.join("") + ), + tag!("\"") + ) +); + +named!( + json_array>, + ws!(delimited!( + tag!("["), + separated_list!(tag!(","), json_value), + tag!("]") + )) +); + +named!( + json_key_value<(String, JsonValue)>, + ws!(separated_pair!(json_string, tag!(":"), json_value)) +); + +named!( + json_hash>, + ws!(map!( + delimited!( + tag!("{"), + separated_list!(tag!(","), json_key_value), + tag!("}") + ), + |tuple_vec| { + let mut h: HashMap = HashMap::new(); + for (k, v) in tuple_vec { + h.insert(String::from(k), v); + } + h } - h - } -))); - -named!(json_value, ws!(alt!( - json_hash => { |h| JsonValue::Object(h) } | - json_array => { |v| JsonValue::Array(v) } | - json_string => { |s| JsonValue::Str(String::from(s)) } | - float => { |num| JsonValue::Num(num) } | - boolean => { |b| JsonValue::Boolean(b) } -))); + )) +); + +named!( + json_value, + ws!(alt!( + json_hash => { |h| JsonValue::Object(h) } | + json_array => { |v| JsonValue::Array(v) } | + json_string => { |s| JsonValue::Str(String::from(s)) } | + float => { |num| JsonValue::Num(num) } | + boolean => { |b| JsonValue::Boolean(b) } + )) +); #[test] fn json_bool_test() { @@ -92,12 +113,11 @@ fn json_bool_test() { if let Some(&JsonValue::Boolean(ref resp)) = dict.get("b") { assert_eq!(false, *resp); } - return + return; } panic!("object did not parse"); } - #[test] fn json_hash_test() { let test = " { \"a\"\t: 42, @@ -111,7 +131,7 @@ fn json_hash_test() { if let Some(&JsonValue::Str(ref resp)) = dict.get("b") { assert_eq!("x", *resp); } - return + return; } panic!("object did not parse"); } @@ -144,7 +164,7 @@ fn json_parse_example_test() { assert_eq!("world", *resp); } } - return + return; } panic!("object did not parse"); } diff --git a/src/lib.rs b/src/lib.rs index fd4cb9b..8d22dc8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -#![feature(str_escape)] - #[macro_use] extern crate nom; @@ -7,5 +5,5 @@ pub mod types; #[macro_use] mod common; -pub mod json; pub mod hcl; +pub mod json; diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 474b39b..170213e 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,5 +1,3 @@ -extern crate molysite; - use std::fs::File; use std::io::prelude::*; use std::path::Path; @@ -70,7 +68,7 @@ fn test_fixture(case: &str, expect_pass: bool) { let mut file = File::open(&path).unwrap(); file.read_to_string(&mut hcl).unwrap(); - if (expect_pass) { + if expect_pass { let path = Path::new(&json_path); let mut file = File::open(&path).unwrap(); file.read_to_string(&mut json).unwrap(); @@ -78,14 +76,14 @@ fn test_fixture(case: &str, expect_pass: bool) { let parsed_hcl = parse_hcl(&hcl); if let Ok(parsed_hcl) = parsed_hcl { - if (expect_pass) { + if expect_pass { let parsed_json = parse_json(&json).unwrap(); assert_eq!(parsed_hcl, parsed_json); } else { panic!("Expected failure") } } else { - if (expect_pass) { + if expect_pass { panic!("Expected success") } }