diff --git a/.github/workflows/ArduinoBuild.yml b/.github/workflows/ArduinoBuild.yml new file mode 100644 index 0000000..f55bb34 --- /dev/null +++ b/.github/workflows/ArduinoBuild.yml @@ -0,0 +1,159 @@ +name: ArduinoBuild + +env: + SKETCH_NAME: test.ino + +on: + push: # see: https://help.github.com/en/actions/reference/events-that-trigger-workflows#pull-request-event-pull_request + paths: + - '**.ino' + - '**.cpp' + - '**.hpp' + - '**.h' + - '**.c' + - '**ArduinoBuild.yml' + pull_request: + +jobs: + + set_env: + name: "Set matrix env vars" + runs-on: ubuntu-latest + env: + esp32_board_url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json + esp8266_board_url: https://arduino.esp8266.com/stable/package_esp8266com_index.json + Seeeduino_board_url: https://files.seeedstudio.com/arduino/package_seeeduino_boards_index.json + rp2040_board_url: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json + rp2040_cli_args: "--build-property compiler.cpp.extra_flags=-DSKIP_I2C_TEST" + d1_mini_fbqn_extra: ":eesz=4M3M,xtal=80" + + outputs: + env: ${{steps.set-env.outputs.env}} + steps: + # export at output variables, flatten as JSON + - id: set-env + run: | + content='${{ toJson(env) }}' # convert env vars to json + content="${content//'%'/'%25'}" # escape percent entities + content="${content//$'\n'/''}" # remove lf + content="${content//$'\r'/''}" # remove cr + echo "env=${content}" >> $GITHUB_OUTPUT + + build: + name: ${{matrix.board}}@${{matrix.platform-version}} + runs-on: ubuntu-latest + needs: set_env + env: ${{fromJSON(needs.set_env.outputs.env)}} + + strategy: + fail-fast: false + matrix: + + board: + # ESP32 devices for multidimensional matrix + - esp32 + - esp32s2 + - esp32s3 + - esp32c3 + + platform-version: + # ESP32 Core versions for multidimensional matrix + - 1.0.6 + - 2.0.0 + - 2.0.1 + - 2.0.2 + - 2.0.3 + - 2.0.4 + - latest + + include: + # multidimensional matrix doesn't apply to these profiles, so they are explicitely populated + + - { board: d1_mini, platform: esp8266, arch: esp8266, platform-version: 3.0.1, ... } + - { board: d1_mini, platform: esp8266, arch: esp8266, platform-version: latest, ... } + + - { board: seeed_wio_terminal, platform: Seeeduino, arch: samd, platform-version: 1.8.2, ... } + - { board: seeed_wio_terminal, platform: Seeeduino, arch: samd, platform-version: latest, ... } + + - { board: rpipico, platform: rp2040, arch: rp2040, platform-version: 2.3.3, ... } + - { board: rpipico, platform: rp2040, arch: rp2040, platform-version: latest, ... } + + # multidimensional matrix applies to these: + - { board: esp32, platform: esp32, arch: esp32, ... } + - { board: esp32s2, platform: esp32, arch: esp32, ... } + - { board: esp32s3, platform: esp32, arch: esp32, ... } + - { board: esp32c3, platform: esp32, arch: esp32, ... } + + exclude: + # multidimensional matrix excludes (no support or unstable): + + - { board: esp32s2, platform-version: 1.0.6 } + - { board: esp32s2, platform-version: 2.0.0 } + + - { board: esp32s3, platform-version: 1.0.6 } + - { board: esp32s3, platform-version: 2.0.0 } + - { board: esp32s3, platform-version: 2.0.1 } + - { board: esp32s3, platform-version: 2.0.2 } + - { board: esp32s3, platform-version: 2.0.4 } + + - { board: esp32c3, platform-version: 1.0.6 } + - { board: esp32c3, platform-version: 2.0.0 } + + + + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Prepare variables for arduino-test-compile + run: | + # prepare some associative arrays to receive matrix-specific env vars + declare -A board_urls + declare -A cli_args + declare -A fbqn_extras + + # assign package URLs (per platform) + board_urls[esp32]=${{env.esp32_board_url}} + board_urls[esp8266]=${{env.esp8266_board_url}} + board_urls[Seeeduino]=${{env.Seeeduino_board_url}} + board_urls[rp2040]=${{env.rp2040_board_url}} + + # assign optional cli args (per platform) + cli_args[rp2040]="${{env.rp2040_cli_args}}" + + # assign optional extra FQBN info (per board) + fbqn_extras[d1_mini]="${{env.d1_mini_fbqn_extra}}" + + # collect the index names that will be used to populate the associative array + board_name=${{matrix.board}} + platform_name=${{matrix.platform}} + + # populate values for the next step + arduino_platform_url=${board_urls[$platform_name]} + arduino_cli_args=${cli_args[$platform_name]} + arduino_board_fqbn=${{matrix.platform}}:${{matrix.arch}}:${{matrix.board}}${fbqn_extras[$board_name]} + arduino_platform=${{matrix.platform}}:${{matrix.arch}}@${{matrix.platform-version}} + + # export to env + echo "ARDUINO_PLATFORM=$arduino_platform" >> $GITHUB_ENV + echo "ARDUINO_PLATFORM_URL=$arduino_platform_url" >> $GITHUB_ENV + echo "ARDUINO_CLI_ARGS=$arduino_cli_args" >> $GITHUB_ENV + echo "ARDUINO_BOARD_FQBN=$arduino_board_fqbn" >> $GITHUB_ENV + + + - name: Compile example + uses: ArminJo/arduino-test-compile@master + with: + arduino-board-fqbn: ${{env.ARDUINO_BOARD_FQBN}} + arduino-platform: ${{env.ARDUINO_PLATFORM}} + platform-url: ${{env.ARDUINO_PLATFORM_URL}} + required-libraries: ArduinoJson + extra-arduino-cli-args: ${{env.ARDUINO_CLI_ARGS}} + #build-properties: ${{ toJson(matrix.build-properties) }} + sketch-names: ${{ env.SKETCH_NAME }} + #sketches-exclude: ${{ matrix.sketches-exclude }} + + diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4f06033..25c5471 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -22,7 +22,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: arduino/arduino-lint-action@v1 with: project-type: library diff --git a/ReadMe.md b/ReadMe.md index 1f039ab..62933b2 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -49,23 +49,23 @@ or YAML is a superset of JSON, so native conversion from/to JSON is possible without any additional JSON library. ```cpp - // JSON <=> YAML stream to stream conversion (both ways!), accepts valid JSON or YAML as the input - // Available values for output format: - // YAMLParser::OUTPUT_YAML - // YAMLParser::OUTPUT_JSON - // YAMLParser::OUTPUT_JSON_PRETTY - size_t serializeYml( Stream &source, Stream &destination, OutputFormat_t format ); +// JSON <=> YAML stream to stream conversion (both ways!). +// Accepts valid JSON or YAML as the input. +// Available values for output format: +// YAMLParser::OUTPUT_YAML +// YAMLParser::OUTPUT_JSON +// YAMLParser::OUTPUT_JSON_PRETTY +size_t serializeYml( Stream &source, Stream &destination, OutputFormat_t format ); ``` **Convert YAML to JSON** ```cpp +String yaml_str = "hello: world\nboolean: true\nfloat: 1.2345"; +StringStream yaml_stream( yaml_str ); - String yaml_str = "hello: world\nboolean: true\nfloat: 1.2345"; - StringStream yaml_stream( yaml_str ); - - serializeYml( yaml_stream, Serial, YAMLParser::OUTPUT_JSON_PRETTY ); +serializeYml( yaml_stream, Serial, YAMLParser::OUTPUT_JSON_PRETTY ); ``` @@ -73,11 +73,10 @@ YAML is a superset of JSON, so native conversion from/to JSON is possible withou **Convert JSON to YAML** ```cpp +String json_str = "{\"hello\": \"world\", \"boolean\": true, \"float\":1.2345}"; +StringStream json_stream( json_str ); - String json_str = "{\"hello\": \"world\", \"boolean\": true, \"float\":1.2345}"; - StringStream json_stream( json_str ); - - serializeYml( json_stream, Serial, YAMLParser::OUTPUT_YAML ); +serializeYml( json_stream, Serial, YAMLParser::OUTPUT_YAML ); ``` @@ -102,7 +101,7 @@ So all ArduinoJson functions will be disabled unless `#include ` On ESP8266/RP2040/SAMD platforms it is assumed that ArduinoJson is already available as a dependency. -In order to save flash space and/or memory, the defaults bindings can be disabled independently by setting one or all of the +In order to save flash space and/or memory, the default bindings can be disabled independently by setting one or all of the following macros before including ArduinoYaml: ```cpp @@ -120,29 +119,32 @@ Note to readers: should ArduinoJson and/or cJSON be implicitely loaded? ## ArduinoJson bindings -The support is implicitely enabled on most platforms. +See the [motivational post](https://github.com/bblanchon/ArduinoJson/issues/1808) for this implementation. + +ArduinoJson support is implicitely enabled on most platforms except for ESP32 where dependencies can be detected. ```cpp - #include // ESP32 plaforms must include this before ArduinoYaml or functions will be disabled - #include +// ESP32 plaforms must include ArduinoJson before ArduinoYaml or functions will be disabled +#include +#include ``` Enabling support will expose the following functions: ```cpp - // ArduinoJSON object to YAML string - size_t serializeYml( JsonVariant src_obj, String &dest_string ); - // ArduinoJSON object to YAML stream - size_t serializeYml( JsonVariant src_obj, Stream &dest_stream ); - // Deserialize YAML string to ArduinoJSON object - DeserializationError deserializeYml( JsonObject &dest_obj, const char* src_yaml_str ); - // Deserialize YAML stream to ArduinoJSON object - DeserializationError deserializeYml( JsonObject &dest_obj, Stream &src_stream ); - // Deserialize YAML string to ArduinoJSON document - DeserializationError deserializeYml( JsonDocument &dest_doc, Stream &src_stream ); - // Deserialize YAML string to ArduinoJSON document - DeserializationError deserializeYml( JsonDocument &dest_doc, const char *src_yaml_str) ; +// ArduinoJSON object to YAML string +size_t serializeYml( JsonVariant src_obj, String &dest_string ); +// ArduinoJSON object to YAML stream +size_t serializeYml( JsonVariant src_obj, Stream &dest_stream ); +// Deserialize YAML string to ArduinoJSON object +DeserializationError deserializeYml( JsonObject &dest_obj, const char* src_yaml_str ); +// Deserialize YAML stream to ArduinoJSON object +DeserializationError deserializeYml( JsonObject &dest_obj, Stream &src_stream ); +// Deserialize YAML string to ArduinoJSON document +DeserializationError deserializeYml( JsonDocument &dest_doc, Stream &src_stream ); +// Deserialize YAML string to ArduinoJSON document +DeserializationError deserializeYml( JsonDocument &dest_doc, const char *src_yaml_str) ; ``` @@ -150,11 +152,11 @@ Enabling support will expose the following functions: ## cJSON bindinds -The support is implicitely enabled on most platforms and will use the bundled cJSON version. -ESP32 will use the built-in version. +cJSON support is implicitely enabled on most platforms, and will use the bundled cJSON version unless ESP32 platform is detected. +ESP32 will use the built-in cJSON version from esp-idf instead of the YAMLDuino bundled version. -⚠️ both versions of cJSON have a memory leak with floats, the leak happens only once though, and +⚠️ Both versions of cJSON have a memory leak with floats, the leak happens only once though, and may be avoided by quoting the float, which won't affect yaml output. @@ -162,14 +164,14 @@ Enabling support will expose the following functions: ```cpp - // cJSON object to YAML string - size_t serializeYml( cJSON* src_obj, String &dest_string ); - // cJSON object to YAML stream - size_t serializeYml( cJSON* src_obj, Stream &dest_stream ); - // YAML string to cJSON object - int deserializeYml( cJSON* dest_obj, const char* src_yaml_str ); - // YAML stream to cJSON object - int deserializeYml( cJSON* dest_obj, Stream &src_stream ); +// cJSON object to YAML string +size_t serializeYml( cJSON* src_obj, String &dest_string ); +// cJSON object to YAML stream +size_t serializeYml( cJSON* src_obj, Stream &dest_stream ); +// YAML string to cJSON object +int deserializeYml( cJSON* dest_obj, const char* src_yaml_str ); +// YAML stream to cJSON object +int deserializeYml( cJSON* dest_obj, Stream &src_stream ); ``` @@ -183,12 +185,11 @@ The `StringStream` class is provided with this library as a helper. ```cpp - String my_json = "{\"blah\":true}"; +String my_json = "{\"blah\":true}"; +StringStream json_input_stream(my_json); - StringStream json_input_stream(my_json); - - String my_output; - StringStream output_stream(my_output); +String my_output; +StringStream output_stream(my_output); ``` @@ -224,8 +225,8 @@ JSON and YAML indentation levels can be customized: ```cpp - void YAML::setYAMLIndent( int spaces_per_indent=2 ); // min=2, max=16 - void YAML::setJSONIndent( const char* spaces_or_tabs=JSON_SCALAR_TAB, int folding_depth=JSON_FOLDING_DEPTH ); +void YAML::setYAMLIndent( int spaces_per_indent=2 ); // min=2, max=16 +void YAML::setJSONIndent( const char* spaces_or_tabs="\t", int folding_depth=4 ); ``` @@ -233,7 +234,7 @@ JSON and YAML indentation levels can be customized: Set custom JSON indentation and folding depth: ```cpp -// two spaces per indentation level, unfold up to 8 nesting levels +// this set two spaces per indentation level, unfolds up to 8 nesting levels YAMLParser::setJSONIndent(" ", 8 ); // lame fact: folds on objects, not on arrays ``` @@ -258,7 +259,7 @@ The debug level can be changed at runtime: ```cpp - void YAML::setLogLevel( LogLevel_t level ); +void YAML::setLogLevel( LogLevel_t level ); ``` Set library debug level: @@ -275,6 +276,23 @@ Set library debug level: YAMLParser::setLogLevel( YAML::LogLevelDebug ); ``` +---------------------------- + + +## Support the Project + +There are a few things you can do to support the project: + + - Star 🌟 this [repository](https://github.com/tobozo/YAMLDuino) and/or [follow me](https://github.com/tobozo/) on GitHub + - Share and upvote on sites like Twitter, Reddit, and Hacker News + - [Report](https://github.com/tobozo/YAMLDuino/issues) any bugs, glitches, or errors that you find + + +These things motivate me to to keep sharing what I build, and they provide +validation that my work is appreciated! They also help me improve the +project. Thanks in advance! + + ---------------------------- ## Credits and special thanks to: @@ -284,6 +302,8 @@ YAMLParser::setLogLevel( YAML::LogLevelDebug ); - [@bblanchon](https://github.com/bblanchon) - [@vikman90](https://github.com/vikman90/yaml2json) + + ## Additional resources: - ArduinoJson : https://github.com/bblanchon/ArduinoJson diff --git a/examples/deserializeYml/deserializeYml.ino b/examples/deserializeYml/deserializeYml.ino new file mode 100644 index 0000000..606af68 --- /dev/null +++ b/examples/deserializeYml/deserializeYml.ino @@ -0,0 +1,130 @@ +#pragma GCC diagnostic ignored "-Wunused-variable" + +#include +#include + +const char* yaml_sample_str = R"_YAML_STRING_( +first: true +blah: + nope: ["n","o","p","e"] +integer: 12345 +float: 12.3323 +last: true + +)_YAML_STRING_"; + +// exact JSON representation of yaml_sample_str +const char* json_sample_str = R"_JSON_STRING_( +{ + "first": true, + "blah": { "nope": [ "n", "o", "p", "e" ] }, + "integer": 12345, + "float": 12.3323, + "last": true +} + +)_JSON_STRING_"; + + +const size_t yaml_str_size = strlen(yaml_sample_str); +const size_t json_str_size = strlen(json_sample_str); +int test_number = 1; + + +// uncomment/comment as needed + +#define TEST_YAML_Stream_To_ArduinoJsonObject +#define TEST_YAML_String_To_ArduinoJsonObject +#define TEST_YAML_Stream_To_ArduinoJsonDocument +#define TEST_YAML_String_To_ArduinoJsonDocument + + + +void test_deserializeYml_JsonObject_YamlStream() +{ + #if defined TEST_YAML_Stream_To_ArduinoJsonObject + String yaml_str = String( yaml_sample_str ); + StringStream yaml_stream( yaml_str ); + StaticJsonDocument<128> json_doc; + JsonObject json_obj = json_doc.to(); + auto err = deserializeYml( json_obj, yaml_stream ); // deserialize yaml stream to JsonObject + if( err ) { + Serial.printf("Unable to deserialize demo YAML to JsonObject: %s", err.c_str() ); + return; + } + const size_t bytes_out = serializeJsonPretty( json_obj, Serial ); // print deserialized JsonObject + #endif +} + + +void test_deserializeYml_JsonObject_YamlString() +{ + #if defined TEST_YAML_String_To_ArduinoJsonObject + StaticJsonDocument<128> json_doc; + JsonObject json_obj = json_doc.to(); + auto err = deserializeYml( json_obj, yaml_sample_str ); // deserialize yaml string to JsonObject + if( err ) { + Serial.printf("Unable to deserialize demo YAML to JsonObject: %s", err.c_str() ); + return; + } + const size_t bytes_out = serializeJsonPretty( json_obj, Serial ); // print deserialized JsonObject + #endif +} + + +void test_deserializeYml_JsonDocument_YamlStream() +{ + #if defined TEST_YAML_Stream_To_ArduinoJsonDocument + StaticJsonDocument<128> json_doc; + String yaml_str = String( yaml_sample_str ); + StringStream yaml_stream( yaml_str ); + auto err = deserializeYml( json_doc, yaml_stream ); // deserialize yaml stream to JsonDocument + if( err ) { + Serial.printf("Unable to deserialize demo YAML to JsonObject: %s", err.c_str() ); + return; + } + const size_t bytes_out = serializeJsonPretty( json_doc, Serial ); // print deserialized JsonObject + #endif +} + + +void test_deserializeYml_JsonDocument_YamlString() +{ + #if defined TEST_YAML_String_To_ArduinoJsonDocument + String yaml_str( yaml_sample_str ); + StaticJsonDocument<128> json_doc; + auto err = deserializeYml( json_doc, yaml_str.c_str() ); // deserialize yaml string to JsonDocument + if( err ) { + Serial.printf("Unable to deserialize demo YAML to JsonObject: %s", err.c_str() ); + return; + } + JsonObject json_obj = json_doc.as(); + const size_t bytes_out = serializeJsonPretty( json_obj, Serial ); // print deserialized JsonObject + #endif +} + + + +void setup() +{ + Serial.begin(115200); + delay(5000); + Serial.printf("Welcome to the YAML Test sketch\nRam free: %d bytes", HEAP_AVAILABLE() ); + + // YAML::setLogLevel( YAML::LogLevelDebug ); // override sketch debug level (otherwise inherited) + // YAML::setJSONIndent(" ", 8 ); // JSON -> two spaces per indent level, unfold objets up to 8 nesting levels + // YAML::setYAMLIndent( 3 ); // annoy your friends with 3 spaces indentation + + test_deserializeYml_JsonDocument_YamlStream(); + test_deserializeYml_JsonDocument_YamlString(); + test_deserializeYml_JsonObject_YamlString(); + test_deserializeYml_JsonObject_YamlStream(); + +} + + +void loop() +{ + +} + diff --git a/examples/serializeYml/serializeYml.ino b/examples/serializeYml/serializeYml.ino new file mode 100644 index 0000000..d4bf697 --- /dev/null +++ b/examples/serializeYml/serializeYml.ino @@ -0,0 +1,133 @@ +#include +//#define YAML_DISABLE_ARDUINOJSON +#include + +const char* yaml_sample_str = R"_YAML_STRING_( +first: true +blah: + nope: ["n","o","p","e"] +integer: 12345 +float: 12.3323 +last: true + +)_YAML_STRING_"; + +// exact JSON representation of yaml_sample_str +const char* json_sample_str = R"_JSON_STRING_( +{ + "first": true, + "blah": { "nope": [ "n", "o", "p", "e" ] }, + "integer": 12345, + "float": 12.3323, + "last": true +} + +)_JSON_STRING_"; + + +const size_t yaml_str_size = strlen(yaml_sample_str); +const size_t json_str_size = strlen(json_sample_str); + + +int test_number = 1; + + +// uncomment/comment as needed + +#define TEST_YAML_TO_JSON +#define TEST_YAML_TO_JSON_PRETTY +#define TEST_JSON_TO_YAML +#define TEST_ArduinoJsonObject_TO_YAML_Stream +#define TEST_ArduinoJsonObject_TO_YAML_String + + +void test_Yaml2JsonPretty() +{ + #if defined TEST_YAML_TO_JSON + String yaml_str = String( yaml_sample_str ); + StringStream yaml_stream( yaml_str ); + serializeYml( yaml_stream, Serial, YAMLParser::OUTPUT_JSON_PRETTY ); + #endif +} + +void test_Yaml2Json() +{ + #if defined TEST_YAML_TO_JSON_PRETTY + String yaml_str = String( yaml_sample_str ); + StringStream yaml_stream( yaml_str ); + serializeYml( yaml_stream, Serial, YAMLParser::OUTPUT_JSON ); + #endif +} + + +void test_Json2Yaml() +{ + #if defined TEST_JSON_TO_YAML + String yaml_str = String( yaml_sample_str ); + StringStream yaml_stream( yaml_str ); + serializeYml( yaml_stream, Serial, YAMLParser::OUTPUT_YAML ); + #endif +} + + +void test_serializeYml_JsonObject_YamlStream() +{ + #if defined TEST_ArduinoJsonObject_TO_YAML_Stream + // Convert JsonObject to yaml + String json_str = String( json_sample_str ); + DynamicJsonDocument doc(128); // create and populate a JsonObject + auto err = deserializeJson( doc, json_str.c_str() ); + if( err ) { + Serial.printf("Unable to deserialize demo JSON to JsonObject: %s", err.c_str() ); + return; + } + JsonObject json_obj = doc.as(); + const size_t bytes_out = serializeYml( json_obj, Serial ); + #endif +} + + +void test_serializeYml_JsonObject_YamlString() +{ + #if defined TEST_ArduinoJsonObject_TO_YAML_String + // Convert JsonObject to yaml + String str_yaml_out = ""; // YAML output string + String json_str = String( json_sample_str ); + DynamicJsonDocument doc(128); // create and populate a JsonObject + auto err = deserializeJson( doc, json_str.c_str() ); + if( err ) { + Serial.printf("Unable to deserialize demo JSON to JsonObject: %s", err.c_str() ); + return; + } + JsonObject json_obj = doc.as(); + const size_t bytes_out = serializeYml( json_obj, str_yaml_out ); + Serial.println( str_yaml_out ); + #endif +} + + + + +void setup() +{ + Serial.begin(115200); + delay(5000); + Serial.printf("Welcome to the YAML Test sketch\nRam free: %d bytes", HEAP_AVAILABLE() ); + + // YAML::setLogLevel( YAML::LogLevelDebug ); // override sketch debug level (otherwise inherited) + // YAML::setJSONIndent(" ", 8 ); // JSON -> two spaces per indent level, unfold objets up to 8 nesting levels + // YAML::setYAMLIndent( 3 ); // annoy your friends with 3 spaces indentation + + test_Yaml2JsonPretty(); + test_Yaml2Json(); + test_Json2Yaml(); + test_serializeYml_JsonObject_YamlString(); + test_serializeYml_JsonObject_YamlStream(); +} + + +void loop() +{ + +} + diff --git a/examples/test/platformio.ini b/examples/test/platformio.ini index fbba8e6..c0327e7 100644 --- a/examples/test/platformio.ini +++ b/examples/test/platformio.ini @@ -8,7 +8,8 @@ src_dir = src framework = arduino lib_deps = bblanchon/ArduinoJson @ ^6 - tobozo/YAMLDuino @ ^1.2 +; tobozo/YAMLDuino @ ^1.2 + YAMLDuino lib_ldf_mode = deep @@ -18,3 +19,13 @@ platform = espressif32 ; or alternate platform/package: ;platform = https://github.com/tasmota/platform-espressif32 ;platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32/releases/download/2.0.5/esp32-2.0.5.zip + + +[env:rp2040_pico] +platform = https://github.com/maxgerhardt/platform-raspberrypi.git +platform_packages = framework-arduinopico @ https://github.com/earlephilhower/arduino-pico/releases/download/2.6.1/rp2040-2.6.1.zip +board = generic +framework = arduino +board_build.filesystem_size = 0.5m ; adjust if needed +board_upload.maximum_size = 16777216 +board_build.arduino.earlephilhower.boot2_source = boot2_w25q080_4_padded_checksum.S diff --git a/examples/test/src/test.cpp b/examples/test/src/test.cpp index 66cbbcf..d19f41a 100644 --- a/examples/test/src/test.cpp +++ b/examples/test/src/test.cpp @@ -227,7 +227,7 @@ void test_Yaml_Stream_Loader() String yaml_str = String( yaml_sample_str ); StringStream yaml_stream( yaml_str ); DynamicJsonDocument json_doc(2048); - JsonObject json_obj = json_doc.as(); + JsonObject json_obj = json_doc.to(); auto err = deserializeYml( json_obj, yaml_stream ); // deserialize yaml stream to JsonObject if( err ) { YAML_LOG_n("Unable to deserialize demo YAML to JsonObject: %s", err.c_str() ); @@ -244,7 +244,7 @@ void test_Yaml_Stream_Loader() { YAML_LOG_n( "[TEST #%d] YAML string to JsonObject -> deserializeYml(json_obj, yaml_sample_str):", test_number++ ); DynamicJsonDocument json_doc(2048); - JsonObject json_obj = json_doc.as(); + JsonObject json_obj = json_doc.to(); auto err = deserializeYml( json_obj, yaml_sample_str ); // deserialize yaml string to JsonObject if( err ) { YAML_LOG_n("Unable to deserialize demo YAML to JsonObject: %s", err.c_str() ); @@ -456,12 +456,16 @@ void setup() #pragma message "Enabling ArduinoJson tests" Serial.println("\n"); YAML_LOG_n("### YAML=>JSON and JSON=>YAML using ArduinoJson\n"); - test_deserializeYml_JsonObject_YamlString(); + #if !defined ARDUINO_ARCH_AVR + test_deserializeYml_JsonDocument_YamlStream(); + test_deserializeYml_JsonDocument_YamlString(); + test_deserializeYml_JsonObject_YamlString(); + test_serializeYml_JsonObject_YamlString(); + #endif test_deserializeYml_JsonObject_YamlStream(); - test_deserializeYml_JsonDocument_YamlStream(); - test_deserializeYml_JsonDocument_YamlString(); test_serializeYml_JsonObject_YamlStream(); - test_serializeYml_JsonObject_YamlString(); + + YAML_LOG_n("### ArduinoJson tests complete\n"); #endif diff --git a/library.properties b/library.properties index 477d725..0e84c61 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=YAMLDuino -version=1.2.8 +version=1.2.9 author=tobozo maintainer=tobozo sentence=A simple and efficient YAML library for embedded C++ diff --git a/src/ArduinoYaml.cpp b/src/ArduinoYaml.cpp index e4a63b7..859e620 100644 --- a/src/ArduinoYaml.cpp +++ b/src/ArduinoYaml.cpp @@ -348,20 +348,21 @@ size_t serialize_YamlDocument( yaml_traverser_t *it ) size_t bytes_out = 0; int node_count = 0; + int node_max = 0; + bool is_seq = false; + bool needs_folding = false; + bool needs_quotes = false; switch (node->type) { case YAML_SCALAR_NODE: - { - bool needs_quotes = scalar_needs_quote( node ); + needs_quotes = scalar_needs_quote( node ); if( needs_quotes ) bytes_out += stream->printf("\""); yaml_escape_quoted_string( stream, SCALAR_c(node), strlen(SCALAR_c(node)), &bytes_out ); if( needs_quotes ) bytes_out += stream->printf("\""); - } break; case YAML_SEQUENCE_NODE: - { bytes_out += stream->printf("["); - const int node_max = node->data.sequence.items.top - node->data.sequence.items.start; + node_max = node->data.sequence.items.top - node->data.sequence.items.start; for (auto item_i = node->data.sequence.items.start; item_i < node->data.sequence.items.top; ++item_i) { auto node_item = yaml_document_get_node(document, *item_i); int child_level = node_item->type == YAML_MAPPING_NODE ? depth+1 : depth-1; @@ -373,15 +374,13 @@ size_t serialize_YamlDocument( yaml_traverser_t *it ) } } bytes_out += stream->printf("]"); - } break; case YAML_MAPPING_NODE: - { - bool is_seq = ( depth>0 && nest_type == YAMLParser::SEQ_KEY ); - bool needs_folding = (depth>YAML::JSONFoldindDepth); + is_seq = ( depth>0 && nest_type == YAMLParser::SEQ_KEY ); + needs_folding = (depth>YAML::JSONFoldindDepth); bytes_out += stream->printf("{"); if( !needs_folding ) bytes_out += stream->printf("\n%s", indent(depth+1, YAML::JSON_INDENT) ); - const int node_max = node->data.mapping.pairs.top - node->data.mapping.pairs.start; + node_max = node->data.mapping.pairs.top - node->data.mapping.pairs.start; for (auto pair_i = node->data.mapping.pairs.start; pair_i < node->data.mapping.pairs.top; ++pair_i) { auto key = yaml_document_get_node(document, pair_i->key); auto value = yaml_document_get_node(document, pair_i->value); @@ -400,8 +399,6 @@ size_t serialize_YamlDocument( yaml_traverser_t *it ) } if( !needs_folding ) bytes_out += stream->printf("\n%s", indent( is_seq ? depth-1 : depth, YAML::JSON_INDENT) ); bytes_out += stream->printf("}"); - - } break; case YAML_NO_NODE: break; default: YAML_LOG_e("Unknown node type (line %lu).", node->start_mark.line); break; @@ -512,12 +509,12 @@ void YAMLParser::setLogLevel( YAML::LogLevel_t level ) } -void YAMLParser::parse( OutputFormat_t format ) +bool YAMLParser::parse( OutputFormat_t format ) { yaml_node_t* node; if (node = yaml_document_get_root_node(&document), !node) { YAML_LOG_e("No document defined."); - return; + return false; } yaml_traverser_t doc = { &document, node, _yaml_stream, YAMLParser::NONE, 0 }; @@ -525,14 +522,15 @@ void YAMLParser::parse( OutputFormat_t format ) size_t bytes_out = output_cb( &doc ); YAML_LOG_d("written %d bytes", bytes_out ); + return bytes_out>0; } -void YAMLParser::parse( Stream &yaml_or_json_stream, OutputFormat_t format ) +bool YAMLParser::parse( Stream &yaml_or_json_stream, OutputFormat_t format ) { if (!yaml_parser_initialize(&parser)) { YAML_LOG_e("Failed to initialize parser!\n", stderr); - return; + return false; } yaml_stream_handler_data_t shd = { &yaml_or_json_stream, &_bytes_read }; @@ -540,13 +538,13 @@ void YAMLParser::parse( Stream &yaml_or_json_stream, OutputFormat_t format ) if (!yaml_parser_load(&parser, &document)) { handle_parser_error(&parser); - return; + return false; } yaml_node_t* node; if (node = yaml_document_get_root_node(&document), !node) { YAML_LOG_e("No document defined."); - return; + return false; } yaml_traverser_t doc = { &document, node, _yaml_stream, YAMLParser::NONE, 0 }; @@ -554,30 +552,31 @@ void YAMLParser::parse( Stream &yaml_or_json_stream, OutputFormat_t format ) size_t bytes_out = output_cb( &doc ); YAML_LOG_d("written %d bytes", bytes_out ); + return bytes_out>0; } -void YAMLParser::parse( const char* yaml_or_json_str, OutputFormat_t format ) +bool YAMLParser::parse( const char* yaml_or_json_str, OutputFormat_t format ) { - assert( yaml_or_json_str ); + if( !yaml_or_json_str || strlen(yaml_or_json_str)<=0 ) return false; if (!yaml_parser_initialize(&parser)) { YAML_LOG_e("Failed to initialize parser!\n", stderr); - return; + return false; } yaml_parser_set_input_string(&parser, (const unsigned char*)yaml_or_json_str, strlen(yaml_or_json_str) ); if (!yaml_parser_load(&parser, &document)) { handle_parser_error(&parser); - return; + return false; } yaml_node_t* node; if (node = yaml_document_get_root_node(&document), !node) { YAML_LOG_e("No document defined."); - return; + return false; } yaml_traverser_t doc = { &document, node, _yaml_stream, YAMLParser::NONE, 0 }; @@ -585,10 +584,11 @@ void YAMLParser::parse( const char* yaml_or_json_str, OutputFormat_t format ) size_t bytes_out = output_cb( &doc ); YAML_LOG_d("written %d bytes", bytes_out ); + return bytes_out>0; } -void YAMLParser::load( Stream &yaml_or_json_stream ) +bool YAMLParser::load( Stream &yaml_or_json_stream ) { _yaml_string = ""; // reset internal output stream _bytes_read = 0; // length will be known when the stream is consumed @@ -597,19 +597,19 @@ void YAMLParser::load( Stream &yaml_or_json_stream ) if ( !yaml_parser_initialize(&parser) ) { handle_parser_error(&parser); YAML_LOG_e("[FATAL] could not initialize parser"); - return; + return false; } yaml_stream_handler_data_t shd = { &yaml_or_json_stream, &_bytes_read }; yaml_parser_set_input(&parser, &_yaml_stream_reader, &shd); - _loadDocument(); + return _loadDocument(); } -void YAMLParser::load( const char* yaml_or_json_str ) +bool YAMLParser::load( const char* yaml_or_json_str ) { - assert( yaml_or_json_str ); + if( !yaml_or_json_str || strlen(yaml_or_json_str)<=0 ) return false; _yaml_string = ""; // reset internal output stream _bytes_read = strlen(yaml_or_json_str); // length is already known @@ -618,21 +618,22 @@ void YAMLParser::load( const char* yaml_or_json_str ) if ( !yaml_parser_initialize(&parser) ) { handle_parser_error(&parser); YAML_LOG_e("[FATAL] could not initialize parser"); - return; + return false; } yaml_parser_set_input_string(&parser, (const unsigned char*)yaml_or_json_str, _bytes_read ); - _loadDocument(); + return _loadDocument(); } // private, called by load(const char*) and load(Stream&) -void YAMLParser::_loadDocument() +bool YAMLParser::_loadDocument() { + bool ret = false; yaml_document_t _tmpdoc; yaml_emitter_t emitter; - assert(yaml_emitter_initialize(&emitter)); + if(!yaml_emitter_initialize(&emitter)) return false; yaml_stream_handler_data_t shd = { nullptr, &_bytes_written }; yaml_emitter_set_canonical(&emitter, 1); yaml_emitter_set_unicode(&emitter, 1); @@ -643,13 +644,16 @@ void YAMLParser::_loadDocument() YAML_LOG_e("[FATAL] Failed to load YAML document at line %lu", parser.problem_mark.line); goto _emitter_delete; } - assert( yaml_copy_document(&_tmpdoc, &document) ); // copy into local document for later parsing - assert( yaml_emitter_dump(&emitter, &_tmpdoc) ); // dump to emitter for input length evaluation - yaml_document_delete(&_tmpdoc); + if(! yaml_copy_document(&_tmpdoc, &document) ) goto _emitter_delete; + if( yaml_emitter_dump(&emitter, &_tmpdoc) ) { // dump to emitter for input length evaluation + yaml_document_delete(&_tmpdoc); + ret = true; + } _emitter_delete: yaml_emitter_close(&emitter); yaml_emitter_delete(&emitter); + return ret; } @@ -696,8 +700,10 @@ void YAMLParser::handle_emitter_error(yaml_emitter_t *e) // yaml_node_t deconstructor => JsonObject - void deserializeYml_JsonObject( yaml_document_t* document, yaml_node_t* yamlNode, JsonObject &jsonNode, JNestingType_t nt, const char *nodename, int depth ) + DeserializationError deserializeYml_JsonObject( yaml_document_t* document, yaml_node_t* yamlNode, JsonObject &jsonNode, JNestingType_t nt, const char *nodename, int depth ) { + bool isRootNode = ( strlen(nodename)<=0 ); + switch (yamlNode->type) { case YAML_SCALAR_NODE: { @@ -754,7 +760,7 @@ void YAMLParser::handle_emitter_error(yaml_emitter_t *e) break; case YAML_MAPPING_NODE: { - JsonObject tmpNode = jsonNode.createNestedObject((char*)nodename); + JsonObject tmpNode = isRootNode ? jsonNode : jsonNode.createNestedObject((char*)nodename); yaml_node_pair_t* pair_i; yaml_node_t* key; yaml_node_t* value; @@ -770,9 +776,12 @@ void YAMLParser::handle_emitter_error(yaml_emitter_t *e) } } break; - case YAML_NO_NODE: YAML_LOG_e("YAML_NO_NODE"); break; + case YAML_NO_NODE: YAML_LOG_e("YAML_NO_NODE"); + + break; default: YAML_LOG_e("Unknown node type (line %lu).", yamlNode->start_mark.line); break; } + return DeserializationError::Ok; } @@ -821,29 +830,42 @@ void YAMLParser::handle_emitter_error(yaml_emitter_t *e) DeserializationError deserializeYml( JsonDocument &dest_doc, Stream &src ) { YAMLToArduinoJson *parser = new YAMLToArduinoJson(); - JsonObject tmpObj = parser->toJson( src ); // decode yaml stream/string - DeserializationError ret = DeserializationError::Ok; - if( !dest_doc.set( tmpObj[ROOT_NODE] ) ) { - ret = DeserializationError::NoMemory; - } + JsonObject dest_obj = dest_doc.to(); + DeserializationError ret = parser->toJsonObject( src, dest_obj ); delete parser; - return DeserializationError::Ok; + return ret; } - DeserializationError deserializeYml( JsonDocument &dest_doc, const char *src ) + DeserializationError deserializeYml( JsonDocument &dest_doc, const char* src ) { YAMLToArduinoJson *parser = new YAMLToArduinoJson(); - JsonObject tmpObj = parser->toJson( src ); // decode yaml stream/string - DeserializationError ret = DeserializationError::Ok; - if( !dest_doc.set( tmpObj[ROOT_NODE] ) ) { - ret = DeserializationError::NoMemory; - } + JsonObject dest_obj = dest_doc.to(); + DeserializationError ret = parser->toJsonObject( src, dest_obj ); delete parser; - return DeserializationError::Ok; + return ret; + } + + + DeserializationError deserializeYml( JsonObject &dest_obj, Stream &src) + { + YAMLToArduinoJson *parser = new YAMLToArduinoJson(); + DeserializationError ret = parser->toJsonObject( src, dest_obj ); + delete parser; + return ret; } + DeserializationError deserializeYml( JsonObject &dest_obj, const char* src) + { + YAMLToArduinoJson *parser = new YAMLToArduinoJson(); + DeserializationError ret = parser->toJsonObject( src, dest_obj ); + delete parser; + return ret; + } + + + #endif // HAS_ARDUINOJSON diff --git a/src/ArduinoYaml.hpp b/src/ArduinoYaml.hpp index de5bb08..6c1fa6b 100644 --- a/src/ArduinoYaml.hpp +++ b/src/ArduinoYaml.hpp @@ -36,17 +36,23 @@ extern "C" { #include "libyaml/yaml.h" // https://github.com/yaml/libyaml } -#if !defined YAML_DISABLE_CJSON // define this from sketch if cJSON isn't needed - #define HAS_CJSON // built-in (esp32) or bundled +//#define YAML_DISABLE_ARDUINOJSON + +#if defined ARDUINO_ARCH_SAMD || defined ARDUINO_ARCH_RP2040 || defined ESP8266 || defined ARDUINO_ARCH_AVR + #include + #include +#endif + +#if !defined YAML_DISABLE_CJSON && !defined ARDUINO_ARCH_AVR // define this from sketch if cJSON isn't needed + #define HAS_CJSON // built-in (esp32) or bundled, except for AVR #endif + #if !defined YAML_DISABLE_ARDUINOJSON - #if defined ARDUINO_ARCH_SAMD || defined ARDUINO_ARCH_RP2040 || defined ESP8266 + #if defined ARDUINO_ARCH_SAMD || defined ARDUINO_ARCH_RP2040 || defined ESP8266 || defined ARDUINO_ARCH_AVR // those platforms don't have built-in cJSON and __has_include() macro is limited to // the sketch folder, so assume ArduinoJson is in use - #include - #include #include #define HAS_ARDUINOJSON #endif @@ -58,6 +64,7 @@ extern "C" { #endif + #define YAML_SCALAR_SPACE " " // YAML is indented with spaces (2 or more), not tabs #define JSON_SCALAR_TAB "\t" // JSON is indented with one tab as a default, this can be changed later #define JSON_FOLDING_DEPTH 4 // lame fact: folds on objects, not on arrays @@ -116,19 +123,19 @@ class YAMLParser void setOutputStream( Stream* stream ) { _yaml_stream = stream; } // yaml/json loaders (populates this.document) - void load( const char* yaml_or_json_str ); - void load( Stream &yaml_or_json_stream ); + bool load( const char* yaml_or_json_str ); + bool load( Stream &yaml_or_json_stream ); // the swiss-army knife functions: // parse any of yaml/json input, and output as yaml, json ugly or json pretty - void parse( OutputFormat_t format=OUTPUT_YAML ); - void parse( const char* yaml_or_json_str, OutputFormat_t format=OUTPUT_YAML ); - void parse( Stream &yaml_or_json_stream, OutputFormat_t format=OUTPUT_YAML ); + bool parse( OutputFormat_t format=OUTPUT_YAML ); + bool parse( const char* yaml_or_json_str, OutputFormat_t format=OUTPUT_YAML ); + bool parse( Stream &yaml_or_json_stream, OutputFormat_t format=OUTPUT_YAML ); - // explicit JSON exporters - template - void toJson( T &yaml, bool pretty = true ) { load( yaml ); toJson( pretty ); } - void toJson( bool pretty = true ) { parse( pretty ? OUTPUT_JSON_PRETTY : OUTPUT_JSON ); } + // // explicit JSON exporters + // template + // void toJson( T &yaml, bool pretty = true ) { load( yaml ); toJson( pretty ); } + // void toJson( bool pretty = true ) { parse( pretty ? OUTPUT_JSON_PRETTY : OUTPUT_JSON ); } // various getters yaml_document_t* getDocument() { return &document; } @@ -147,7 +154,7 @@ class YAMLParser String _yaml_string; Stream *_yaml_stream = nullptr; StringStream *_yaml_string_stream_ptr = nullptr; - void _loadDocument(); + bool _loadDocument(); yaml_document_t document; yaml_parser_t parser; OutputFormat_t output_format; @@ -173,7 +180,7 @@ size_t serializeYml( Stream &json_src_stream, Stream &yml_dest_stream, OutputFor // default name for the topmost temporary JsonObject #define ROOT_NODE "_root_" // deconstructors - void deserializeYml_JsonObject( yaml_document_t* document, yaml_node_t* yamlNode, JsonObject &jsonNode, JNestingType_t nt=YAMLParser::NONE, const char *nodename=ROOT_NODE, int depth=0 ); + DeserializationError deserializeYml_JsonObject( yaml_document_t*, yaml_node_t* , JsonObject&, JNestingType_t nt=YAMLParser::NONE, const char *nodename="", int depth=0 ); size_t serializeYml_JsonVariant( JsonVariant root, Stream &out, int depth_level, JNestingType_t nt ); class YAMLToArduinoJson : public YAMLParser @@ -197,16 +204,19 @@ size_t serializeYml( Stream &json_src_stream, Stream &yml_dest_stream, OutputFor } deserializeYml_JsonObject(getDocument(), node, _root); } + template - JsonObject& toJson( T &yaml ) + DeserializationError toJsonObject( T &src, JsonObject& output ) { - load( yaml ); - if( bytesWritten() > 0 ) { - setJsonDocument( bytesWritten()*2 ); - toJson(); + if( !load( src ) ) return DeserializationError::NoMemory; + yaml_node_t * node; + if (node = yaml_document_get_root_node(getDocument()), !node) { + YAML_LOG_e("No document defined."); + return DeserializationError::NoMemory; } - return _root; + return deserializeYml_JsonObject(getDocument(), node, output); } + private: DynamicJsonDocument *_doc = nullptr; JsonObject _root; @@ -223,27 +233,41 @@ size_t serializeYml( Stream &json_src_stream, Stream &yml_dest_stream, OutputFor // Deserialize YAML stream to ArduinoJSON document DeserializationError deserializeYml( JsonDocument &dest_doc, const char *src); + + // [templated] Deserialize YAML string to ArduinoJSON Document + DeserializationError deserializeYml( JsonObject &dest_doc, const char* src_yaml_str ); + // [templated] Deserialize YAML stream to ArduinoJSON Document + DeserializationError deserializeYml( JsonObject &dest_doc, Stream &src_stream ); + + // [templated] Deserialize YAML string to ArduinoJSON object // DeserializationError deserializeYml( JsonObject &dest_obj, const char* src_yaml_str ); // [templated] Deserialize YAML stream to ArduinoJSON object // DeserializationError deserializeYml( JsonObject &dest_obj, Stream &src_stream ); - template - DeserializationError deserializeYml( JsonObject &dest_obj, T &src) - { - static_assert(std::is_same::value || std::is_same::value || std::is_same::value, "src must be const char* or Stream*"); - YAMLToArduinoJson *parser = new YAMLToArduinoJson(); - JsonObject _dest_obj = parser->toJson( src ); // decode yaml stream/string - dest_obj = _dest_obj[ROOT_NODE]; - size_t capacity = parser->bytesWritten()*2; - delete parser; - if( capacity == 0 ) { - return DeserializationError::InvalidInput; - } - if( dest_obj.isNull() ) { - return DeserializationError::NoMemory; - } - return DeserializationError::Ok; - } +// template +// DeserializationError deserializeYml( JsonObject &dest_obj, T &src) +// { +// YAMLToArduinoJson *parser = new YAMLToArduinoJson(); +// //JsonObject dest_obj = dest_doc.to(); +// DeserializationError ret = parser->toJsonObject( src, dest_obj ); +// delete parser; +// return ret; +// /* +// +// //static_assert(std::is_same::value || std::is_same::value || std::is_same::value, "src must be const char* or Stream*"); +// YAMLToArduinoJson *parser = new YAMLToArduinoJson(); +// JsonObject _dest_obj = parser->toJson( src ); // decode yaml stream/string +// dest_obj = _dest_obj[ROOT_NODE]; +// size_t capacity = parser->bytesWritten()*2; +// delete parser; +// if( capacity == 0 ) { +// return DeserializationError::InvalidInput; +// } +// if( dest_obj.isNull() ) { +// return DeserializationError::NoMemory; +// } +// return DeserializationError::Ok;*/ +// } #endif // HAS_ARDUINOJSON @@ -276,7 +300,7 @@ size_t serializeYml( Stream &json_src_stream, Stream &yml_dest_stream, OutputFor if (node = yaml_document_get_root_node(document), !node) { YAML_LOG_w("No document defined."); return NULL; } return deserializeYml_cJSONObject(document, node); }; - cJSON *toJson( const char* yaml_str ) { load( yaml_str ); return toJson( getDocument() ); }; + cJSON *toJson( const char* yaml_str ) { load( yaml_str ); return toJson( getDocument() ); } cJSON* toJson( Stream &yaml_stream ) { load( yaml_stream ); return toJson( getDocument() ); } }; diff --git a/src/logger.hpp b/src/logger.hpp index 2600749..25c3a7e 100644 --- a/src/logger.hpp +++ b/src/logger.hpp @@ -82,6 +82,9 @@ } #define HEAP_AVAILABLE() getFreeRam() #else + #include + #include + #define LOG_PRINTF Serial.printf static int getFreeRam() { // implement your own @@ -95,7 +98,7 @@ #define YAML_PATHNAME _pathToFileName static const char * _pathToFileName(const char * path) { - size_t i = 0, pos = 0; + int i = 0, pos = 0; char * p = (char *)path; while(*p){ i++; @@ -106,6 +109,8 @@ } return path+pos; } + #include + #include #endif #if !defined LOG_PRINTF