New json_encode command #124

This commit is contained in:
sagie gur ari 2020-07-30 06:53:27 +00:00
parent e000613de5
commit 4bc3045a7c
11 changed files with 281 additions and 10 deletions

View File

@ -2,6 +2,7 @@
### v0.6.4
* New json_encode command #124
* New json_parse command #124
### v0.6.3 (2020-07-24)

View File

@ -105,6 +105,7 @@
* [std::fs::TempFile (temp_file)](#std__fs__TempFile)
* [std::fs::WriteBytes (writebinfile, write_binary_file)](#std__fs__WriteBytes)
* [std::fs::WriteText (writefile, write_text_file)](#std__fs__WriteText)
* [std::json::Encode (json_encode)](#std__json__Encode)
* [std::json::Parse (json_parse)](#std__json__Parse)
* [std::lib::alias::Set (alias)](#std__lib__alias__Set)
* [std::lib::alias::Unset (unalias)](#std__lib__alias__Unset)
@ -3888,6 +3889,34 @@ result = writefile ./target/tests/writefile.txt "line 1\nline 2"
#### Aliases:
writefile, write_text_file
<a name="std__json__Encode"></a>
## std::json::Encode
```sh
string = json_encode var_name
```
This function will encode all variables, starting from the root variable as a JSON string.<br>
Since duckscript is untyped, all boolean and numeric values will be encoded as strings.
#### Parameters
The root variable name
#### Return Value
The JSON string
#### Examples
```sh
package = json_parse "{\"name\": \"my package\", \"version\": 1, \"publish\": false, \"keywords\": [\"test1\", \"test2\"], \"directories\": {\"test\": \"spec\"}}"
jsonstring = json_encode package
```
#### Aliases:
json_encode
<a name="std__json__Parse"></a>
## std::json::Parse
```sh
@ -3917,9 +3946,7 @@ The root value.
```sh
package = json_parse "{\"name\": \"my package\", \"version\": 1, \"publish\": false, \"keywords\": [\"test1\", \"test2\"], \"directories\": {\"test\": \"spec\"}}"
defined = is_defined package
assert_false ${defined}
assert_eq ${package} "[OBJECT]"
assert_eq ${package.name} "my package"
assert_eq ${package.version} 1
assert_eq ${package.publish} false

View File

@ -0,0 +1,21 @@
```sh
string = json_encode var_name
```
This function will encode all variables, starting from the root variable as a JSON string.<br>
Since duckscript is untyped, all boolean and numeric values will be encoded as strings.
#### Parameters
The root variable name
#### Return Value
The JSON string
#### Examples
```sh
package = json_parse "{\"name\": \"my package\", \"version\": 1, \"publish\": false, \"keywords\": [\"test1\", \"test2\"], \"directories\": {\"test\": \"spec\"}}"
jsonstring = json_encode package
```

View File

@ -0,0 +1,165 @@
use crate::sdk::std::json::OBJECT_VALUE;
use crate::utils::pckg;
use duckscript::types::command::{Command, CommandResult, Commands};
use duckscript::types::instruction::Instruction;
use duckscript::types::runtime::StateValue;
use serde_json::map::Map;
use serde_json::Value;
use std::collections::{HashMap, HashSet};
#[cfg(test)]
#[path = "./mod_test.rs"]
mod mod_test;
fn encode_array(name: &str, values: &HashMap<&str, &str>) -> Result<Value, String> {
match values.get(format!("{}.length", name).as_str()) {
Some(length_str) => match length_str.parse::<usize>() {
Ok(length) => {
let mut json_vec = vec![];
for index in 0..length {
let array_item_name = format!("{}[{}]", name, index);
match encode_any(&array_item_name, values) {
Ok(array_item) => json_vec.push(array_item),
Err(error) => return Err(error),
}
}
Ok(Value::Array(json_vec))
}
Err(error) => Err(error.to_string()),
},
None => Err(format!(
"{} is not a valid JSON array, missing length attribute.",
name
)),
}
}
fn encode_object(name: &str, values: &HashMap<&str, &str>) -> Result<Value, String> {
let child_prefix = format!("{}.", name);
let child_prefix_end = child_prefix.len() - 1;
let mut children: HashSet<&str> = HashSet::new();
for (key, _) in values {
if key.starts_with(&child_prefix) {
let last_index = key.rfind('.').unwrap();
if last_index == child_prefix_end {
let array_key_end = key.rfind("[").unwrap_or(0);
let next_key = if array_key_end > last_index && key.rfind("]").is_some() {
&key[0..array_key_end]
} else {
key
};
children.insert(next_key);
}
}
}
let mut object = Map::new();
let prefix_length = name.len() + 1;
for key in children {
match encode_any(key, values) {
Ok(json_value) => {
let child_key = &key[prefix_length..];
object.insert(child_key.to_string(), json_value);
()
}
Err(error) => return Err(error),
}
}
Ok(Value::Object(object))
}
fn encode_any(name: &str, values: &HashMap<&str, &str>) -> Result<Value, String> {
match values.get(name) {
Some(value) => {
if *value == OBJECT_VALUE {
encode_object(name, values)
} else {
Ok(Value::String(value.to_string()))
}
}
None => {
if values.contains_key(format!("{}.length", name).as_str()) {
encode_array(name, values)
} else {
Ok(Value::Null)
}
}
}
}
fn encode(name: &str, variables: &HashMap<String, String>) -> Result<String, String> {
let mut object_variables: HashMap<&str, &str> = HashMap::new();
for (key, value) in variables {
if key == name || key.starts_with(name) {
object_variables.insert(key, value);
}
}
if object_variables.is_empty() {
Ok("".to_string())
} else {
match encode_any(name, &object_variables) {
Ok(value) => Ok(value.to_string()),
Err(error) => Err(error),
}
}
}
#[derive(Clone)]
pub(crate) struct CommandImpl {
package: String,
}
impl Command for CommandImpl {
fn name(&self) -> String {
pckg::concat(&self.package, "Encode")
}
fn aliases(&self) -> Vec<String> {
vec!["json_encode".to_string()]
}
fn help(&self) -> String {
include_str!("help.md").to_string()
}
fn clone_and_box(&self) -> Box<dyn Command> {
Box::new((*self).clone())
}
fn requires_context(&self) -> bool {
true
}
fn run_with_context(
&self,
arguments: Vec<String>,
_state: &mut HashMap<String, StateValue>,
variables: &mut HashMap<String, String>,
_output_variable: Option<String>,
_instructions: &Vec<Instruction>,
_commands: &mut Commands,
_line: usize,
) -> CommandResult {
if arguments.is_empty() {
CommandResult::Error("No JSON root variable name provided.".to_string())
} else {
match encode(&arguments[0], variables) {
Ok(output) => CommandResult::Continue(Some(output)),
Err(error) => CommandResult::Error(error),
}
}
}
}
pub(crate) fn create(package: &str) -> Box<dyn Command> {
Box::new(CommandImpl {
package: package.to_string(),
})
}

View File

@ -0,0 +1,7 @@
use super::*;
use crate::test;
#[test]
fn common_functions() {
test::test_common_command_functions(create(""));
}

View File

@ -1,3 +1,4 @@
mod encode;
mod parse;
use crate::utils::pckg;
@ -6,9 +7,12 @@ use duckscript::types::error::ScriptError;
static PACKAGE: &str = "json";
pub(crate) static OBJECT_VALUE: &str = "[OBJECT]";
pub(crate) fn load(commands: &mut Commands, parent: &str) -> Result<(), ScriptError> {
let package = pckg::concat(parent, PACKAGE);
commands.set(encode::create(&package))?;
commands.set(parse::create(&package))?;
Ok(())

View File

@ -25,9 +25,7 @@ The root value.
```sh
package = json_parse "{\"name\": \"my package\", \"version\": 1, \"publish\": false, \"keywords\": [\"test1\", \"test2\"], \"directories\": {\"test\": \"spec\"}}"
defined = is_defined package
assert_false ${defined}
assert_eq ${package} "[OBJECT]"
assert_eq ${package.name} "my package"
assert_eq ${package.version} 1
assert_eq ${package.publish} false

View File

@ -1,3 +1,4 @@
use crate::sdk::std::json::OBJECT_VALUE;
use crate::utils::pckg;
use duckscript::types::command::{Command, CommandResult, Commands};
use duckscript::types::instruction::Instruction;
@ -33,6 +34,8 @@ fn create_variables(data: Value, name: &str, variables: &mut HashMap<String, Str
None
}
Value::Object(map) => {
variables.insert(name.to_string(), OBJECT_VALUE.to_string());
for (key, value) in map {
let child_name = format!("{}.{}", name, key);
create_variables(value, &child_name, variables);

View File

@ -24,7 +24,7 @@ out = json_parse "{\"name\": \"my package\", \"version\": 1, \"publish\": false,
let variables = context.variables;
assert!(!variables.contains_key("out"));
assert_eq!(variables.get("out").unwrap(), "[OBJECT]");
assert_eq!(variables.get("out.name").unwrap(), "my package");
assert_eq!(variables.get("out.version").unwrap(), "1");
assert_eq!(variables.get("out.publish").unwrap(), "false");

View File

@ -0,0 +1,47 @@
fn test_all_types
jsonstring = set "{\"name\": \"my package\", \"version\": 1, \"publish\": false, \"keywords\": [\"test1\", \"test2\"], \"directories\": {\"test\": \"spec\"}}"
package = json_parse ${jsonstring}
assert_eq ${package} "[OBJECT]"
assert_eq ${package.name} "my package"
assert_eq ${package.version} 1
assert_eq ${package.publish} false
assert_eq ${package.keywords.length} 2
assert_eq ${package.keywords[0]} test1
assert_eq ${package.keywords[1]} test2
assert_eq ${package.directories.test} spec
package.keywords[1] = json_parse ${jsonstring}
package.subpackage = json_parse ${jsonstring}
package.name = set "my package 2"
update_jsonstring = json_encode package
package2 = json_parse ${update_jsonstring}
assert_eq ${package2} "[OBJECT]"
assert_eq ${package2.name} "my package 2"
assert_eq ${package2.version} 1
assert_eq ${package2.publish} false
assert_eq ${package2.keywords.length} 2
assert_eq ${package2.keywords[0]} test1
assert_eq ${package2.keywords[1]} "[OBJECT]"
assert_eq ${package2.directories.test} spec
assert_eq ${package2.subpackage} "[OBJECT]"
assert_eq ${package2.subpackage.name} "my package"
assert_eq ${package2.subpackage.version} 1
assert_eq ${package2.subpackage.publish} false
assert_eq ${package2.subpackage.keywords.length} 2
assert_eq ${package2.subpackage.keywords[0]} test1
assert_eq ${package2.subpackage.keywords[1]} test2
assert_eq ${package2.subpackage.directories.test} spec
assert_eq ${package2.keywords[1].name} "my package"
assert_eq ${package2.keywords[1].version} 1
assert_eq ${package2.keywords[1].publish} false
assert_eq ${package2.keywords[1].keywords.length} 2
assert_eq ${package2.keywords[1].keywords[0]} test1
assert_eq ${package2.keywords[1].keywords[1]} test2
assert_eq ${package2.keywords[1].directories.test} spec
end

View File

@ -2,9 +2,7 @@
fn test_all_types
package = json_parse "{\"name\": \"my package\", \"version\": 1, \"publish\": false, \"keywords\": [\"test1\", \"test2\"], \"directories\": {\"test\": \"spec\"}}"
defined = is_defined package
assert_false ${defined}
assert_eq ${package} "[OBJECT]"
assert_eq ${package.name} "my package"
assert_eq ${package.version} 1
assert_eq ${package.publish} false