diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7ecffd7..add7165 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
* Fixed runner to return an error if on_error requested crash or exit and not just end the script.
* Unalias can remove aliases not created from the alias command.
+* New properties read/write commands #61
### v0.1.6 (2020-01-12)
diff --git a/docs/sdk.md b/docs/sdk.md
index cfcb319..e724868 100644
--- a/docs/sdk.md
+++ b/docs/sdk.md
@@ -18,6 +18,8 @@
* [std::collections::ArrayIsEmpty (array_is_empty)](#std__collections__ArrayIsEmpty)
* [std::collections::ArrayLength (array_length, arrlen)](#std__collections__ArrayLength)
* [std::collections::Range (range)](#std__collections__Range)
+* [std::collections::ReadProperties (read_properties)](#std__collections__ReadProperties)
+* [std::collections::WriteProperties (write_properties)](#std__collections__WriteProperties)
* [std::env::GetVar (get_env)](#std__env__GetVar)
* [std::env::PrintCurrentDirectory (pwd)](#std__env__PrintCurrentDirectory)
* [std::env::SetCurrentDirectory (cd, set_current_dir)](#std__env__SetCurrentDirectory)
@@ -797,6 +799,72 @@ release ${handle}
#### Aliases:
range
+
+## std::collections::ReadProperties
+```sh
+count = read_properties text
+```
+
+Parses the properties (based on java properties format) text and sets them as variables.
+This command will also return the count of properties read.
+
+#### Parameters
+
+The text to parse.
+
+#### Return Value
+
+The properties count.
+
+#### Examples
+
+```sh
+count = read_properties "a=1\nb=2\na.b.c=3"
+assert_eq ${count} 3
+
+assert_eq ${a} 1
+assert_eq ${b} 2
+assert_eq ${a.b.c} 3
+```
+
+
+#### Aliases:
+read_properties
+
+
+## std::collections::WriteProperties
+```sh
+text = write_properties [names]
+```
+
+Creates a properties string from the provided list of variable names (not values).
+
+#### Parameters
+
+A list of variable names.
+
+#### Return Value
+
+The properties text value.
+
+#### Examples
+
+```sh
+a = set 1
+b = set 2
+a.b.c = set 3
+
+# text will be equal to:
+# a=1
+# b=2
+# a.b.c=3
+text = write_properties a b a.b.c
+```
+
+
+#### Aliases:
+write_properties
+
## std::env::GetVar
```sh
diff --git a/duckscript_sdk/Cargo.toml b/duckscript_sdk/Cargo.toml
index 8395cbd..bd65678 100644
--- a/duckscript_sdk/Cargo.toml
+++ b/duckscript_sdk/Cargo.toml
@@ -27,6 +27,7 @@ duckscript = { version = "^0.1.5", path = "../duckscript" }
fs_extra = "^1"
home = "^0.5"
hostname = "^0.3"
+java-properties = "^1"
meval = "^0.2"
rand = "^0.7"
walkdir = "^2"
diff --git a/duckscript_sdk/src/sdk/std/collections/mod.rs b/duckscript_sdk/src/sdk/std/collections/mod.rs
index 2df6f39..48e9971 100755
--- a/duckscript_sdk/src/sdk/std/collections/mod.rs
+++ b/duckscript_sdk/src/sdk/std/collections/mod.rs
@@ -2,6 +2,8 @@ pub(crate) mod array;
mod array_is_empty;
pub(crate) mod array_length;
mod range;
+mod read_properties;
+mod write_properties;
use crate::utils::pckg;
use duckscript::types::command::Commands;
@@ -16,6 +18,8 @@ pub(crate) fn load(commands: &mut Commands, parent: &str) -> Result<(), ScriptEr
commands.set(array_is_empty::create(&package))?;
commands.set(array_length::create(&package))?;
commands.set(range::create(&package))?;
+ commands.set(read_properties::create(&package))?;
+ commands.set(write_properties::create(&package))?;
Ok(())
}
diff --git a/duckscript_sdk/src/sdk/std/collections/read_properties/help.md b/duckscript_sdk/src/sdk/std/collections/read_properties/help.md
new file mode 100644
index 0000000..f1273a4
--- /dev/null
+++ b/duckscript_sdk/src/sdk/std/collections/read_properties/help.md
@@ -0,0 +1,25 @@
+```sh
+count = read_properties text
+```
+
+Parses the properties (based on java properties format) text and sets them as variables.
+This command will also return the count of properties read.
+
+#### Parameters
+
+The text to parse.
+
+#### Return Value
+
+The properties count.
+
+#### Examples
+
+```sh
+count = read_properties "a=1\nb=2\na.b.c=3"
+assert_eq ${count} 3
+
+assert_eq ${a} 1
+assert_eq ${b} 2
+assert_eq ${a.b.c} 3
+```
diff --git a/duckscript_sdk/src/sdk/std/collections/read_properties/mod.rs b/duckscript_sdk/src/sdk/std/collections/read_properties/mod.rs
new file mode 100755
index 0000000..4c407f5
--- /dev/null
+++ b/duckscript_sdk/src/sdk/std/collections/read_properties/mod.rs
@@ -0,0 +1,64 @@
+use crate::utils::pckg;
+use duckscript::types::command::{Command, CommandResult, Commands};
+use duckscript::types::instruction::Instruction;
+use duckscript::types::runtime::StateValue;
+use java_properties::read;
+use std::collections::HashMap;
+
+#[cfg(test)]
+#[path = "./mod_test.rs"]
+mod mod_test;
+
+struct CommandImpl {
+ package: String,
+}
+
+impl Command for CommandImpl {
+ fn name(&self) -> String {
+ pckg::concat(&self.package, "ReadProperties")
+ }
+
+ fn aliases(&self) -> Vec {
+ vec!["read_properties".to_string()]
+ }
+
+ fn help(&self) -> String {
+ include_str!("help.md").to_string()
+ }
+
+ fn requires_context(&self) -> bool {
+ true
+ }
+
+ fn run_with_context(
+ &self,
+ arguments: Vec,
+ _state: &mut HashMap,
+ variables: &mut HashMap,
+ _output_variable: Option,
+ _instructions: &Vec,
+ _commands: &mut Commands,
+ _line: usize,
+ ) -> CommandResult {
+ if arguments.len() < 1 {
+ CommandResult::Error("Missing properties text argument.".to_string())
+ } else {
+ match read(arguments[0].as_bytes()) {
+ Ok(data) => {
+ for (key, value) in &data {
+ variables.insert(key.to_string(), value.to_string());
+ }
+
+ CommandResult::Continue(Some(data.len().to_string()))
+ }
+ Err(error) => CommandResult::Error(error.to_string()),
+ }
+ }
+ }
+}
+
+pub(crate) fn create(package: &str) -> Box {
+ Box::new(CommandImpl {
+ package: package.to_string(),
+ })
+}
diff --git a/duckscript_sdk/src/sdk/std/collections/read_properties/mod_test.rs b/duckscript_sdk/src/sdk/std/collections/read_properties/mod_test.rs
new file mode 100644
index 0000000..b838fa3
--- /dev/null
+++ b/duckscript_sdk/src/sdk/std/collections/read_properties/mod_test.rs
@@ -0,0 +1,28 @@
+use super::*;
+use crate::test;
+use crate::test::{CommandValidation, SetCommand};
+
+#[test]
+fn common_functions() {
+ test::test_common_command_functions(create(""));
+}
+
+#[test]
+fn run_no_args() {
+ test::run_script_and_error(vec![create("")], "out = read_properties", "out");
+}
+
+#[test]
+fn run_valid() {
+ let context = test::run_script_and_validate(
+ vec![create(""), Box::new(SetCommand {})],
+ r#"
+ props = test_set "a=1\nb=2"
+ out = read_properties ${props}
+ "#,
+ CommandValidation::Match("out".to_string(), "2".to_string()),
+ );
+
+ assert_eq!(context.variables.get("a").unwrap(), "1");
+ assert_eq!(context.variables.get("b").unwrap(), "2");
+}
diff --git a/duckscript_sdk/src/sdk/std/collections/write_properties/help.md b/duckscript_sdk/src/sdk/std/collections/write_properties/help.md
new file mode 100644
index 0000000..a1fa1b5
--- /dev/null
+++ b/duckscript_sdk/src/sdk/std/collections/write_properties/help.md
@@ -0,0 +1,27 @@
+```sh
+text = write_properties [names]
+```
+
+Creates a properties string from the provided list of variable names (not values).
+
+#### Parameters
+
+A list of variable names.
+
+#### Return Value
+
+The properties text value.
+
+#### Examples
+
+```sh
+a = set 1
+b = set 2
+a.b.c = set 3
+
+# text will be equal to:
+# a=1
+# b=2
+# a.b.c=3
+text = write_properties a b a.b.c
+```
diff --git a/duckscript_sdk/src/sdk/std/collections/write_properties/mod.rs b/duckscript_sdk/src/sdk/std/collections/write_properties/mod.rs
new file mode 100755
index 0000000..79bcea0
--- /dev/null
+++ b/duckscript_sdk/src/sdk/std/collections/write_properties/mod.rs
@@ -0,0 +1,73 @@
+use crate::utils::pckg;
+use duckscript::types::command::{Command, CommandResult, Commands};
+use duckscript::types::instruction::Instruction;
+use duckscript::types::runtime::StateValue;
+use java_properties::write;
+use std::collections::HashMap;
+use std::str;
+
+#[cfg(test)]
+#[path = "./mod_test.rs"]
+mod mod_test;
+
+struct CommandImpl {
+ package: String,
+}
+
+impl Command for CommandImpl {
+ fn name(&self) -> String {
+ pckg::concat(&self.package, "WriteProperties")
+ }
+
+ fn aliases(&self) -> Vec {
+ vec!["write_properties".to_string()]
+ }
+
+ fn help(&self) -> String {
+ include_str!("help.md").to_string()
+ }
+
+ fn requires_context(&self) -> bool {
+ true
+ }
+
+ fn run_with_context(
+ &self,
+ arguments: Vec,
+ _state: &mut HashMap,
+ variables: &mut HashMap,
+ _output_variable: Option,
+ _instructions: &Vec,
+ _commands: &mut Commands,
+ _line: usize,
+ ) -> CommandResult {
+ if arguments.len() < 1 {
+ CommandResult::Error("Missing properties names.".to_string())
+ } else {
+ let mut data = HashMap::new();
+ for argument in &arguments {
+ match variables.get(argument) {
+ Some(value) => {
+ data.insert(argument.to_string(), value.to_string());
+ }
+ None => (),
+ }
+ }
+
+ let mut buffer: Vec = vec![];
+ match write(&mut buffer, &data) {
+ Ok(_) => match str::from_utf8(&buffer) {
+ Ok(text) => CommandResult::Continue(Some(text.trim().to_string())),
+ Err(error) => CommandResult::Error(error.to_string()),
+ },
+ Err(error) => CommandResult::Error(error.to_string()),
+ }
+ }
+ }
+}
+
+pub(crate) fn create(package: &str) -> Box {
+ Box::new(CommandImpl {
+ package: package.to_string(),
+ })
+}
diff --git a/duckscript_sdk/src/sdk/std/collections/write_properties/mod_test.rs b/duckscript_sdk/src/sdk/std/collections/write_properties/mod_test.rs
new file mode 100644
index 0000000..016ae14
--- /dev/null
+++ b/duckscript_sdk/src/sdk/std/collections/write_properties/mod_test.rs
@@ -0,0 +1,26 @@
+use super::*;
+use crate::test;
+use crate::test::{CommandValidation, SetCommand};
+
+#[test]
+fn common_functions() {
+ test::test_common_command_functions(create(""));
+}
+
+#[test]
+fn run_no_args() {
+ test::run_script_and_error(vec![create("")], "out = write_properties", "out");
+}
+
+#[test]
+fn run_valid() {
+ test::run_script_and_validate(
+ vec![create(""), Box::new(SetCommand {})],
+ r#"
+ a = test_set 1
+ b = test_set 2
+ out = write_properties a b
+ "#,
+ CommandValidation::Contains("out".to_string(), "b=2".to_string()),
+ );
+}
diff --git a/test/std/collections/read_properties_test.ds b/test/std/collections/read_properties_test.ds
new file mode 100644
index 0000000..51c5da7
--- /dev/null
+++ b/test/std/collections/read_properties_test.ds
@@ -0,0 +1,9 @@
+
+function test_read_properties
+ count = read_properties a=1\nb=2\na.b.c=3
+ assert_eq ${count} 3
+
+ assert_eq ${a} 1
+ assert_eq ${b} 2
+ assert_eq ${a.b.c} 3
+end
diff --git a/test/std/collections/write_properties_test.ds b/test/std/collections/write_properties_test.ds
new file mode 100644
index 0000000..a6a3f4d
--- /dev/null
+++ b/test/std/collections/write_properties_test.ds
@@ -0,0 +1,22 @@
+
+function test_write_properties
+ count = read_properties a=1\nb=2\na.b.c=3
+ assert_eq ${count} 3
+
+ assert_eq ${a} 1
+ assert_eq ${b} 2
+ assert_eq ${a.b.c} 3
+
+ text = write_properties a b a.b.c
+
+ a = set
+ b = set
+ a.b.c = set
+
+ count = read_properties ${text}
+ assert_eq ${count} 3
+
+ assert_eq ${a} 1
+ assert_eq ${b} 2
+ assert_eq ${a.b.c} 3
+end