mirror of
https://github.com/sagiegurari/duckscript
synced 2024-10-04 15:09:08 +00:00
New json_encode command #124
This commit is contained in:
parent
e000613de5
commit
4bc3045a7c
|
@ -2,6 +2,7 @@
|
|||
|
||||
### v0.6.4
|
||||
|
||||
* New json_encode command #124
|
||||
* New json_parse command #124
|
||||
|
||||
### v0.6.3 (2020-07-24)
|
||||
|
|
33
docs/sdk.md
33
docs/sdk.md
|
@ -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
|
||||
|
|
21
duckscript_sdk/src/sdk/std/json/encode/help.md
Normal file
21
duckscript_sdk/src/sdk/std/json/encode/help.md
Normal 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
|
||||
```
|
165
duckscript_sdk/src/sdk/std/json/encode/mod.rs
Executable file
165
duckscript_sdk/src/sdk/std/json/encode/mod.rs
Executable 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(),
|
||||
})
|
||||
}
|
7
duckscript_sdk/src/sdk/std/json/encode/mod_test.rs
Normal file
7
duckscript_sdk/src/sdk/std/json/encode/mod_test.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
use super::*;
|
||||
use crate::test;
|
||||
|
||||
#[test]
|
||||
fn common_functions() {
|
||||
test::test_common_command_functions(create(""));
|
||||
}
|
|
@ -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(())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
47
test/std/json/json_encode_test.ds
Normal file
47
test/std/json/json_encode_test.ds
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue