From a5fe59bb7c338575ca6f1f8af3f20f218e6245b9 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Mon, 25 Mar 2019 20:23:50 -0400 Subject: [PATCH] Add help text functionality for converters. --- src/convert/env.rs | 28 +++++++++++++++++++++++----- src/convert/exec.rs | 35 +++++++++++++++++++++++++++++++++++ src/convert/flags.rs | 36 ++++++++++++++++++++++++++++++++++-- src/convert/json.rs | 19 +++++++++++++++++++ src/convert/mod.rs | 4 ---- src/convert/toml.rs | 23 +++++++++++++++++++++++ src/convert/traits.rs | 1 + src/convert/xml.rs | 40 ++++++++++++++++++++++++++++++++++++++++ src/convert/yaml.rs | 19 +++++++++++++++++++ src/main.rs | 38 ++++++++++++++++++++++++++++---------- 10 files changed, 222 insertions(+), 21 deletions(-) diff --git a/src/convert/env.rs b/src/convert/env.rs index bf5218d..7ff4325 100644 --- a/src/convert/env.rs +++ b/src/convert/env.rs @@ -13,7 +13,8 @@ // limitations under the License. //! Contains code for converting a UCG Val into the environment variable output target. -use std::io::Write; +use std::fmt::Write as FmtWrite; +use std::io::Write as IOWrite; use std::rc::Rc; use crate::build::Val; @@ -28,7 +29,7 @@ impl EnvConverter { EnvConverter {} } - fn convert_tuple(&self, flds: &Vec<(String, Rc)>, w: &mut Write) -> ConvertResult { + fn convert_tuple(&self, flds: &Vec<(String, Rc)>, w: &mut IOWrite) -> ConvertResult { for &(ref name, ref val) in flds.iter() { if val.is_tuple() { eprintln!("Skipping embedded tuple..."); @@ -44,12 +45,12 @@ impl EnvConverter { Ok(()) } - fn convert_list(&self, _items: &Vec>, _w: &mut Write) -> ConvertResult { + fn convert_list(&self, _items: &Vec>, _w: &mut IOWrite) -> ConvertResult { eprintln!("Skipping List..."); Ok(()) } - fn write(&self, v: &Val, w: &mut Write) -> ConvertResult { + fn write(&self, v: &Val, w: &mut IOWrite) -> ConvertResult { match v { &Val::Empty => { // Empty is a noop. @@ -91,7 +92,7 @@ impl EnvConverter { } impl Converter for EnvConverter { - fn convert(&self, v: Rc, mut w: &mut Write) -> ConvertResult { + fn convert(&self, v: Rc, mut w: &mut IOWrite) -> ConvertResult { self.write(&v, &mut w) } @@ -102,4 +103,21 @@ impl Converter for EnvConverter { fn description(&self) -> String { "Convert ucg Vals into environment variables.".to_string() } + + #[allow(unused_must_use)] + fn help(&self) -> String { + let mut h = String::new(); + writeln!( + h, + "Env conversions expect a tuple. With keys represent the variable name." + ); + writeln!(h, ""); + writeln!(h, "Allowed values can be:"); + writeln!(h, "- Bool converts to true or false"); + writeln!(h, "- Int"); + writeln!(h, "- Float"); + writeln!(h, "- String converted to a quoted string"); + writeln!(h, "- Functions and Modules are ignored."); + h + } } diff --git a/src/convert/exec.rs b/src/convert/exec.rs index c042cf7..f9fbfa2 100644 --- a/src/convert/exec.rs +++ b/src/convert/exec.rs @@ -14,6 +14,7 @@ //! Contains code for converting a UCG Val into an executable script output target. use std; +use std::fmt::Write as FmtWrite; use std::io::{Cursor, Write}; use std::rc::Rc; @@ -185,6 +186,40 @@ impl Converter for ExecConverter { fn description(&self) -> String { "Convert ucg Vals into an bash script with \nenvironment variables set and command line arguments sent..".to_string() } + + #[allow(unused_must_use)] + fn help(&self) -> String { + let mut h = String::new(); + writeln!( + h, + "Exec conversions expect a tuple with an expected set of keys." + ); + writeln!(h, ""); + writeln!(h, "The expected keys are:"); + writeln!(h, ""); + writeln!(h, "- command (string, required)"); + writeln!(h, "\t The command to run in the script."); + writeln!(h, ""); + writeln!(h, "- env (tuple, optional)"); + writeln!( + h, + "\t Any environment variables that should be set in the script." + ); + writeln!( + h, + "\t The env tuple is converted using the same rules as the env converter." + ); + writeln!(h, ""); + writeln!(h, "- args (tuple, optional"); + writeln!(h, "\t Any command line arguments for the command line."); + writeln!( + h, + "\t The arguments are converted using the same rules as the flags converter." + ); + writeln!(h, ""); + writeln!(h, "- Functions and Modules are ignored."); + h + } } #[cfg(test)] diff --git a/src/convert/flags.rs b/src/convert/flags.rs index fc87884..d61c3f3 100644 --- a/src/convert/flags.rs +++ b/src/convert/flags.rs @@ -13,6 +13,7 @@ // limitations under the License. //! Contains code for converting a UCG Val into the command line flag output target. +use std::fmt::Write as FmtWrite; use std::io::Write; use std::rc::Rc; @@ -131,6 +132,37 @@ impl Converter for FlagConverter { fn description(&self) -> String { "Convert ucg Vals into command line flags.".to_string() } -} -// We need some unit tests for this now :D + #[allow(unused_must_use)] + fn help(&self) -> String { + let mut h = String::new(); + writeln!( + h, + "Flags converts a tuple into a set of command line arguments for command line application." + ); + writeln!(h, ""); + writeln!(h, "The flags are converted using the following rules:"); + writeln!(h, ""); + writeln!(h, "- keys in a tuple are converted into the argument name."); + writeln!( + h, + "- values in a tuple are converted into the argument value." + ); + writeln!(h, "- NULL values are not emitted"); + writeln!( + h, + "- lists expand out into an argument for each item in the list." + ); + writeln!(h, "\te.g. {{foo = [1, 2]}} becomes --foo=1 --foo=2"); + writeln!( + h, + "- tuples expand out into an argument with the key as a prefix separated by a `.`." + ); + writeln!( + h, + "\te.g. {{foo = {{bar = 1, baz = 2}}}} becomes --foo.bar=1 --foo.baz=2" + ); + writeln!(h, "- Functions and Modules are ignored."); + h + } +} diff --git a/src/convert/json.rs b/src/convert/json.rs index 04d8d92..a3b24d5 100644 --- a/src/convert/json.rs +++ b/src/convert/json.rs @@ -10,6 +10,7 @@ //! Flags contains code for converting a UCG Val into the json output target. use std; use std::error::Error; +use std::fmt::Write as FmtWrite; use std::io::Write; use std::rc::Rc; @@ -135,6 +136,24 @@ impl Converter for JsonConverter { fn description(&self) -> String { "Convert ucg Vals into valid json.".to_string() } + + #[allow(unused_must_use)] + fn help(&self) -> String { + let mut h = String::new(); + writeln!(h, "JSON conversions expect any ucg value."); + writeln!(h, ""); + writeln!( + h, + "They are transformed into json using the following rules:" + ); + writeln!(h, "- NULL becomes null"); + writeln!(h, "- tuples become objects {{}}"); + writeln!(h, "- lists become lists []"); + writeln!(h, "- Int and Float become numbers"); + writeln!(h, "- Strings become strings."); + writeln!(h, "- Functions and Modules are ignored."); + h + } } impl Importer for JsonConverter { diff --git a/src/convert/mod.rs b/src/convert/mod.rs index e267a66..bec447c 100644 --- a/src/convert/mod.rs +++ b/src/convert/mod.rs @@ -66,8 +66,6 @@ impl ConverterRegistry { pub fn get_converter_list(&self) -> Vec<(&String, &Box)> { self.converters.iter().collect() } - - // TODO(jwall): Support converter help descriptions. } pub struct ImporterRegistry { @@ -109,6 +107,4 @@ impl ImporterRegistry { pub fn get_importer_list(&self) -> Vec<(&String, &Box)> { self.importers.iter().collect() } - - // TODO(jwall): Support converter help descriptions. } diff --git a/src/convert/toml.rs b/src/convert/toml.rs index 2e1d3d9..7c35deb 100644 --- a/src/convert/toml.rs +++ b/src/convert/toml.rs @@ -15,6 +15,7 @@ use std; use std::error; use std::error::Error; +use std::fmt::Write as FmtWrite; use std::io::Write; use std::rc::Rc; @@ -127,6 +128,28 @@ impl Converter for TomlConverter { fn description(&self) -> String { "Convert ucg Vals into valid ucg.".to_string() } + + #[allow(unused_must_use)] + fn help(&self) -> String { + let mut h = String::new(); + writeln!(h, "TOML conversions expect any ucg value."); + writeln!(h, ""); + writeln!( + h, + "They are transformed into toml using the following rules:" + ); + writeln!(h, "- tuples become maps {{}}"); + writeln!(h, "- lists become lists []"); + writeln!(h, "- Int becomes an Int"); + writeln!(h, "- Float becomes a Float"); + writeln!(h, "- Strings become Strings."); + writeln!( + h, + "- NULL is not allowed in toml documents and will generate a compile error" + ); + writeln!(h, "- Functions and Modules are ignored."); + h + } } impl Importer for TomlConverter { diff --git a/src/convert/traits.rs b/src/convert/traits.rs index badfe17..10dba11 100644 --- a/src/convert/traits.rs +++ b/src/convert/traits.rs @@ -30,6 +30,7 @@ pub trait Converter { fn convert(&self, vs: Rc, w: &mut Write) -> ConvertResult; fn file_ext(&self) -> String; fn description(&self) -> String; + fn help(&self) -> String; } pub trait Importer { diff --git a/src/convert/xml.rs b/src/convert/xml.rs index 1b427b1..e5d8c95 100644 --- a/src/convert/xml.rs +++ b/src/convert/xml.rs @@ -14,6 +14,7 @@ use std; use std::error::Error; +use std::fmt::Write as FmtWrite; use std::io::Write; use std::rc::Rc; @@ -234,4 +235,43 @@ impl Converter for XmlConverter { fn description(&self) -> String { String::from("Convert a ucg DSL into xml.") } + + #[allow(unused_must_use)] + fn help(&self) -> String { + let mut h = String::new(); + writeln!(h, "XML converts ucg tuples into xml documents."); + writeln!(h, ""); + writeln!(h, "The tuple converts into xml using a declarative DSL."); + writeln!(h, "The top tuple describes the xml document:"); + writeln!( + h, + "{{ + version = \"1.1\" // Optional, Defaults to 1.1 + encoding = \"utf-8\" // Optional, Defaults to UTF-8 + standalone = true // Optional Defaults to false + root = {{ // Required defines the root element of the document. + name = \"top\", + }} +}};" + ); + writeln!(h, "XML nodes are constructed like :"); + writeln!( + h, + "{{ + name = \"ns:element-name\", + ns = {{ + prefix = \"myns\", + uri = \"http://example.org\", + }}, + attrs = {{ + id = \"foo\", + }}, + children = [ + // child elements go here. + ], +}};" + ); + writeln!(h, "Text nodes are just strings."); + h + } } diff --git a/src/convert/yaml.rs b/src/convert/yaml.rs index 809bb98..03d7a6f 100644 --- a/src/convert/yaml.rs +++ b/src/convert/yaml.rs @@ -1,5 +1,6 @@ use std; use std::error::Error; +use std::fmt::Write as FmtWrite; use std::io::Write; use std::rc::Rc; use std::result::Result; @@ -132,6 +133,24 @@ impl Converter for YamlConverter { fn description(&self) -> String { "Convert ucg Vals into valid yaml.".to_string() } + + #[allow(unused_must_use)] + fn help(&self) -> String { + let mut h = String::new(); + writeln!(h, "YAML conversions expect any ucg value."); + writeln!(h, ""); + writeln!( + h, + "They are transformed into toml using the following rules:" + ); + writeln!(h, "- tuples become maps {{}}"); + writeln!(h, "- lists become lists []"); + writeln!(h, "- Int becomes an Int"); + writeln!(h, "- Float becomes a Float"); + writeln!(h, "- Strings become Strings."); + writeln!(h, "- Functions and Modules are ignored."); + h + } } impl Importer for YamlConverter { diff --git a/src/main.rs b/src/main.rs index 4b884d0..0e220d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,6 +55,7 @@ fn do_flags<'a, 'b>() -> clap::App<'a, 'b> { ) (@subcommand converters => (about: "list the available converters") + (@arg converter: "Converter name to get help for.") ) (@subcommand importers => (about: "list the available importers for includes") @@ -252,7 +253,6 @@ fn inspect_command( builder.set_strict(strict); match registry.get_converter(target) { Some(converter) => { - // TODO(jwall): We should warn if this is a test file. let result = builder.build(file); if !result.is_ok() { eprintln!("{:?}", result.err().unwrap()); @@ -393,14 +393,32 @@ fn test_command( process::exit(0); } -fn converters_command(registry: &ConverterRegistry) { - println!("Available converters:"); - println!(""); - for (name, c) in registry.get_converter_list().iter() { - println!("- {}", name); - println!(" Description: {}", c.description()); - println!(" Output Extension: `.{}`", c.file_ext()); +fn converters_command(matches: &clap::ArgMatches, registry: &ConverterRegistry) { + if let Some(ref cname) = matches.value_of("converter") { + let mut found = false; + for (name, c) in registry.get_converter_list().iter() { + if cname == name { + println!("* {}", name); + println!("Description: {}", c.description()); + println!("Output Extension: `.{}`", c.file_ext()); + println!(""); + println!("{}", c.help()); + found = true; + } + } + if !found { + println!("No such converter {}", cname); + process::exit(1); + } + } else { + println!("Available converters:"); println!(""); + for (name, c) in registry.get_converter_list().iter() { + println!("* {}", name); + println!("Description: {}", c.description()); + println!("Output Extension: `.{}`", c.file_ext()); + println!(""); + } } } @@ -457,8 +475,8 @@ fn main() { build_command(matches, &import_paths, cache, ®istry, strict); } else if let Some(matches) = app_matches.subcommand_matches("test") { test_command(matches, &import_paths, cache, ®istry, strict); - } else if let Some(_) = app_matches.subcommand_matches("converters") { - converters_command(®istry) + } else if let Some(matches) = app_matches.subcommand_matches("converters") { + converters_command(matches, ®istry) } else if let Some(_) = app_matches.subcommand_matches("importers") { let registry = ImporterRegistry::make_registry(); importers_command(®istry)