From 5fba06d71f572e2d6f0285869e80a37ee88e3040 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Sun, 4 Feb 2018 16:08:30 -0600 Subject: [PATCH] Add json as an output type. * Uses serde_json * Doesn't handle macro values very well. * Handles maps and lists just fine. * doesn't pretty print the values though. --- Cargo.toml | 1 + TODO.md | 5 +-- src/convert/flags.rs | 8 +++-- src/convert/json.rs | 86 ++++++++++++++++++++++++++++++++++++++++++++ src/convert/mod.rs | 4 +++ src/lib.rs | 1 + src/main.rs | 2 +- 7 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 src/convert/json.rs diff --git a/Cargo.toml b/Cargo.toml index 65c9334..3b44fee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ version = "^3.2" [dependencies] nom_locate = "^0.1.1" clap = "~2.26.0" +serde_json = "~1.0.9" [lib] name = "ucglib" diff --git a/TODO.md b/TODO.md index fbfa3fc..0b1cb6f 100644 --- a/TODO.md +++ b/TODO.md @@ -5,11 +5,12 @@ You should be able to ask the compiler to tell you any value or set of values in the compiled configuration. -## Translation Language (Experiemental) +## Translation Language (Experimental) For some configuration file formats we need a way to specify a particular organiztion for a given configuration structure. Some options here could be - + +* Simple data export (json) * A Functional Transform similar to xslt or css transforms. * A Templating language * Annotations. diff --git a/src/convert/flags.rs b/src/convert/flags.rs index 638ac21..b13f354 100644 --- a/src/convert/flags.rs +++ b/src/convert/flags.rs @@ -25,9 +25,7 @@ impl FlagConverter { pub fn new() -> Self { FlagConverter {} } -} -impl FlagConverter { fn write(&self, v: &Val, w: &mut Write) -> Result<()> { match v { &Val::Float(ref f) => { @@ -45,6 +43,10 @@ impl FlagConverter { } &Val::Tuple(ref flds) => { for &(ref name, ref val) in flds.iter() { + if val.is_tuple() { + eprintln!("Skipping embedded tuple..."); + return Ok(()); + } try!(write!(w, "--{} ", name.val)); // TODO(jwall): What if the value is a tuple? try!(self.write(&val, w)); @@ -61,6 +63,6 @@ impl FlagConverter { impl Converter for FlagConverter { fn convert(&self, v: Rc, mut w: Box) -> Result<()> { - return self.write(&v, &mut w); + self.write(&v, &mut w) } } diff --git a/src/convert/json.rs b/src/convert/json.rs new file mode 100644 index 0000000..0816061 --- /dev/null +++ b/src/convert/json.rs @@ -0,0 +1,86 @@ +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::rc::Rc; +use std::io::Write; +use std::io::Result; + +use serde_json; + +use ast; +use build::Val; +use convert::traits::Converter; + +pub struct JsonConverter {} + +impl JsonConverter { + pub fn new() -> Self { + JsonConverter {} + } + + fn convert_list(&self, items: &Vec>) -> Result { + let mut v = Vec::new(); + for val in items.iter() { + v.push(try!(self.convert_value(val))); + } + Ok(serde_json::Value::Array(v)) + } + + fn convert_tuple(&self, items: &Vec<(ast::Positioned, Rc)>) -> Result { + let mut mp = serde_json::Map::new(); + for &(ref k, ref v) in items.iter() { + mp.entry(k.val.clone()).or_insert(try!(self.convert_value(v))); + } + Ok(serde_json::Value::Object(mp)) + } + + fn convert_value(&self, v: &Val) -> Result { + let jsn_val = match v { + &Val::Float(f) => { + let n = match serde_json::Number::from_f64(f) { + Some(n) => n, + // In theory this should never happen. But on the off chance that it does... + None => panic!("Float is too large or Not a Number {}", f), + }; + serde_json::Value::Number(n) + }, + &Val::Int(i) => { + let n = match serde_json::Number::from_f64(i as f64) { + Some(n) => n, + // In theory this should never happen. But on the off chance that it does... + None => panic!("Float is too large or Not a Number {}", i), + }; + serde_json::Value::Number(n) + }, + &Val::String(ref s) => serde_json::Value::String(s.clone()), + &Val::Macro(_) => { + // TODO(jwall): We probably want to actually skip this but for now + // we'll use null + eprintln!("Skipping macro encoding as null..."); + serde_json::Value::Null + }, + &Val::List(ref l) => try!(self.convert_list(l)), + &Val::Tuple(ref t) => try!(self.convert_tuple(t)), + }; + Ok(jsn_val) + } + + fn write(&self, v: &Val, w: &mut Write) -> Result<()> { + let jsn_val = try!(self.convert_value(v)); + try!(serde_json::to_writer(w, &jsn_val)); + Ok(()) + } +} + +impl Converter for JsonConverter { + fn convert(&self, v: Rc, mut w: Box) -> Result<()> { + self.write(&v, &mut w) + } +} \ No newline at end of file diff --git a/src/convert/mod.rs b/src/convert/mod.rs index c69955f..cbaf8b3 100644 --- a/src/convert/mod.rs +++ b/src/convert/mod.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. pub mod flags; +pub mod json; pub mod traits; use std::io; @@ -29,6 +30,9 @@ impl ConverterRunner { if typ == "flags" { return Ok(ConverterRunner { converter: Box::new(flags::FlagConverter::new()) }); } + if typ == "json" { + return Ok(ConverterRunner { converter: Box::new(json::JsonConverter::new()) }); + } return Err(format!("Unknown Target output type: {}", typ)); } diff --git a/src/lib.rs b/src/lib.rs index c7ba447..28814d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ extern crate nom; #[macro_use] extern crate nom_locate; +extern crate serde_json; #[macro_use] pub mod ast; diff --git a/src/main.rs b/src/main.rs index 174b2b5..4f2f649 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,7 +34,7 @@ fn do_flags<'a>() -> clap::ArgMatches<'a> { (@subcommand build => (about: "Compile a specific ucg file.") (@arg sym: --sym +takes_value "Specify a specific let binding in the ucg file to output.") - (@arg target: --target -t +required +takes_value "Target output type.") + (@arg target: --target -t +required +takes_value "Target output type. (flags, json)") (@arg out: --out -o +takes_value "Output file to write to.") (@arg INPUT: +required "Input ucg file to build.") )