diff --git a/.travis.yml b/.travis.yml index 7e776c3..7382736 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,29 @@ language: c -dist: bionic + +os: + - linux before_script: - - mkdir -p build - - cd build - - sudo apt-get install -y libyaml-0-2 + - mkdir -p build + - cd build + - sudo apt-get install -y libyaml-0-2 script: - - cmake -DENABLE_COVERAGE=yes .. - - make && ctest -V - - make gcov + - cmake -DENABLE_COVERAGE=yes .. + - build-wrapper-linux-x86-64 --out-dir bw-output make clean all || true # Allowed to fail in builds from forks + - make && ctest -V + - make gcov + - sonar-scanner -Dproject.settings=../sonar-project.properties || true # Allowed to fail in builds from forks after_success: - - curl -s https://codecov.io/bash > cov.sh && bash cov.sh -x "$GCOV" + - curl -s https://codecov.io/bash > cov.sh && bash cov.sh -x "$GCOV" + +addons: + sonarcloud: + organization: "openscap" + token: + secure: "BoPCYdulv5+7sNQtShKE0tYVelHSL3sWNhjz5QFZCDefK7az2+Lze9Zp/sas02gmBLoc0vuuFjFgtKLSPt9mEL4YabwAysCF445jwE+wu8KJf/Bz36tMz1gE9383+Ic9LcR2bdPBY/0gBWZpmKK33mNs+P5xuI23XsK2Whkjev9tg/kjjXAd3Q79WqlDW2SKT5Ugg5SxE6RgFS/pBsxWsqp3Vfx38t3hIhloECf51/aVrBabpYmwYe8l8gq/+A2PO/gpw6SBdCu2Y9x+zlAhWkY7cri7C8LSUSMS0pUM5haij4oO/7UVUbRjWmTDpg3sSLtJka2BIIl32XINMdvBCzbhIpuNCTZlWz3KCyBWRvV8r95n+p2IahbV7ZU/Jc8QvBNC0IsjCjHORtuJzXOa3BCZ2PXggboX1uaHkLe+xECC/3gjLDXAcUvM6QJN2Ytbnzfd2jTlhCt1a3ttCPUvSqN9CIJWIKnbpHPWwAk7YuMH7GXbCle9mInDvPOe16KTQ39RMsWRE1HgyTHErfT5pyaCwv2lq4oThuCrUyAGmMYgC6OZalW9DciQwp/kzKECnrwfjCCy8uJu/BlXWGMeJWjRaLFpOpZLIPaVVvKlODb8zZUZeVV1sqyyQ6+I4Opfmi0ikVW4eOqyXRy1G1O9OxXeHC2SHFuK4E5cks05EA4=" + +cache: + directories: + - '$HOME/.sonar/cache' \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 796709d..9c14766 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,5 @@ cmake_minimum_required(VERSION 2.8) +project(yaml-path C) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") @@ -6,35 +7,35 @@ include(FindPkgConfig) pkg_check_modules(YAML yaml-0.1) find_package(codecov) -include_directories(${YAML_INCLUDE_DIRS}) +include_directories(${YAML_INCLUDE_DIRS} src) -add_library(yaml-path yaml-path.c) +add_library(yaml-path src/yaml-path.c) target_link_libraries(yaml-path ${YAML_LIBRARIES}) +add_coverage(yaml-path) -add_executable(ya yaml.c) -target_link_libraries(ya yaml-path) -add_coverage(ya) - -add_executable(yamlp yamlp.c) +add_executable(yamlp src/yamlp.c) target_link_libraries(yamlp yaml-path) add_coverage(yamlp) -add_coverage(yaml-path) - -install(TARGETS ya RUNTIME DESTINATION bin) install(TARGETS yamlp RUNTIME DESTINATION bin) -add_executable(test-path-segments test-path-segments.c yaml-path.c) -add_coverage(test-path-segments) +if(${CMAKE_C_COMPILER_ID} STREQUAL "GNU" OR ${CMAKE_C_COMPILER_ID} STREQUAL "Clang") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pipe -std=c99 -W -Wall -Wnonnull -Wshadow -Wformat -Wundef -Wno-unused-parameter -Wmissing-prototypes -Wno-unknown-pragmas -D_GNU_SOURCE -D_POSIX_C_SOURCE=200112L") +endif() +if(${CMAKE_SYSTEM_NAME} EQUAL "Solaris") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D__EXTENSIONS__") +endif() +if(WIN32) + # Expose new WinAPI function appearing on Windows 7 (e.g. inet_pton) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_WIN32_WINNT=0x0600") +endif() +if(APPLE) + # Full Single Unix Standard v3 (SUSv3) conformance (the Unix API) + add_definitions(-D_DARWIN_C_SOURCE) +endif() -add_executable(test-paths test-paths.c) -target_link_libraries(test-paths yaml-path) -add_coverage(test-paths) -list(APPEND LCOV_REMOVE_PATTERNS "'${CMAKE_SOURCE_DIR}/test-*'") +enable_testing() +add_subdirectory("tests") coverage_evaluate() -enable_testing() -add_test(NAME "test-path-segments" COMMAND ${CMAKE_BINARY_DIR}/test-path-segments) -add_test(NAME "test-paths" COMMAND ${CMAKE_BINARY_DIR}/test-paths) -add_test(NAME "test-yamlp" COMMAND ${CMAKE_SOURCE_DIR}/test-yamlp.sh) diff --git a/README.md b/README.md index 9f66685..26ecd44 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ -# YAML Path filter +[![Build Status](https://travis-ci.org/OpenSCAP/yaml-filter.svg?branch=master)](https://travis-ci.org/OpenSCAP/yaml-filter) [![Total alerts](https://img.shields.io/lgtm/alerts/g/OpenSCAP/yaml-filter.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/OpenSCAP/yaml-filter/alerts/) -YAML document filtering for libyaml +# YAML filter + +YAML documnets filtering library (based on [libyaml](https://github.com/yaml/libyaml)). + +[How to build and test it](docs/developer.md). + +[YAML Path definition (v1)](docs/yaml_path_v1.md). \ No newline at end of file diff --git a/docs/developer.md b/docs/developer.md new file mode 100644 index 0000000..1d396af --- /dev/null +++ b/docs/developer.md @@ -0,0 +1,86 @@ +# Developer's Guide + +## Building on Linux + +If you want to build the `libyaml-path` library and `yamlp` filtering utility follow the these instructions: + + +### 1. Get the source code + +```sh +$ git clone https://github.com/OpenSCAP/yaml-filter.git +$ cd yaml-filter +``` + + +### 2. *Get the build dependencies* + +To build the library you will also need to install the build dependencies. + +The project relies on CMake (`cmake`) build system and C99 compiler (for example `gcc`). + +The only mandatroy dependency of `libyaml-path` is the YAML document parser/emitter library `libyaml`. You can also use `lcov` for coverage reports, but it is optional. + +```sh +# Ubuntu +$ sudo apt-get install -y libyaml-0-2 +``` + +```sh +# Fedora +$ sudo dnf install -y libyaml lcov +``` + +When you have all the build dependencies installed you can build the library. + + +### 3. *Build the project* + +Run the following commands to build the library and filtering utility: + +```sh +$ mkdir -p build +$ cd build/ +$ cmake .. +$ make +``` + + +### 3. *Run the tests* + +Now you can execute the following command to run library self-checks: + +```sh +$ ctest +``` + + +### 4. *Install* + +Run the installation procedure by executing the following command: + +```sh +$ make install +``` + +You can also configure CMake to install everything into the $HOME/.local directory: + +```sh +$ cd build +$ rm -rf * +$ cmake -DCMAKE_INSTALL_PREFIX=$HOME/.local .. +$ make +$ make install +``` + + +### 5. *Generate code coverage report* + +You can use `lcov` for code coverage report generation. It is integrated into the project's build configuration: + +```sh +$ cd build +$ cmake -DENABLE_COVERAGE=yes .. +$ make && ctest -V +$ make gcov +``` \ No newline at end of file diff --git a/docs/yaml_path_v1.md b/docs/yaml_path_v1.md new file mode 100644 index 0000000..e84dbb3 --- /dev/null +++ b/docs/yaml_path_v1.md @@ -0,0 +1,119 @@ +### v1.0.0 2020-09-28 + +## Overview +This implementation is a subset of the intersection between [JSONPath](https://goessner.net/articles/JsonPath) and [YAML Path](https://pypi.org/project/yamlpath), and it focuses on addressing elements inside YAML files (node descriptors). There are no *query*-like capabilities. + +Example YAML structure: +```yaml +foo: + - bar: &bar True + first: First Bar + second: 2 + arr: [1, 2, 3] + - baz: False + other_bar: *bar + first: First Baz + some.el/here: Delimiters... + "bar's": 0 +``` + +Example JSON structure: +```json +{ + "foo": [ + { + "bar": true, + "first": "First Bar", + "second": 2, + "arr": [1, 2, 3] + }, + { + "baz": false, + "other_bar": true, + "first": "First Baz", + "some.el/here": "Delimiters...", + "bar's": 0, + } + ] +} +``` + +## Delimiters +Dot-notation (`.`) is the only supported notation to define *map key* path segments. Both *map key* and *sequence index* path segments could also be defined using square brackets (`[`, `]`). For *map key* and *map keys selection* segments both single (`'`) and double (`"`) quotes are supported, `['key']` is the same as `["key"]` and `['key',"other's key"]` is a valid path segment. + +For example: `$.foo[0].bar` or `.foo[0]['bar']` or `['foo'][0].bar` or `foo[1]["bar's"]`. + +## Special symbols +A path might be prefixed by a dollar sign and a dot (`$`, `.`), but this prefix is retained for compatibility with *JSONPath* and not mandatory. Implicit *document root* is assumed unless the path explicitly starts with an *anchor* (`&...`) segment (see below for details). + +If the first segment is a *map key* segment (and explicit *document root* is omitted) the initial dot (`.`) is also not mandatory. + +For example, these paths are equal: `$.el`, `.el`, `el`, `['el']`. And they all address the value stored in the "el" key of the top-most map of the document. + +An asterisk (`*`) as a key name has a special meaning, and treated as an all-inclusive *keys selection* section (see below). That's it, `$.*` expression would include all keys of the map in the document root. The `[*]` syntax is also valid. One should use the `[:]` notation to acheive same effect for sequences (include all indices). + +## Path Segment Types + +#### Document Root +`$` + +Optional explicit document root. Only allowed to appear at the beginning of the path. + +```python +$.foo[0].bar = .foo[0].bar = foo[0].bar +== true +``` + + +#### Map Key +`.map.key` or `.map['key']` + +```python +$.foo[0].second = ['foo'][0]['second'] +== 2 +``` + + +#### Map Keys Selection +`.map['key1','key2',...'keyN']` + +Special syntax for the all-inclusive key selection: `.*`. Also, there is the `[*]` variant of this syntax. + +```python +$.foo[0]['first','second'] = ['foo'][0]['first','second'] +== {"first": "First Bar", "second": 2} + +foo[0]['first','second','bar','arr'] = foo[0].* +== {"bar": true, "first": "First Bar", "second": 2, "arr": [1, 2, 3]} +``` + + +#### Sequence Index +`.array[]` + +```python +$.foo[0] +== {"bar": True, "first": "First Bar", "second": 2} +``` + + +#### Sequence Indices Set +`.array[,,...]` + +Special syntax for the all-inclusive indices set: `[:]`. + +```python +$.foo[0].arr[0,1,2] = foo[0].arr[:] +== [1, 2, 3] +``` + + +#### Anchor +`&anchor` + +Matches elements starting from the given anchor instead of the document root. This segment is only sensible in paths for YAML documents as there is no anchors/aliases concept in the JSON specification. + +```python +$.foo[0].bar = &bar +== True +``` \ No newline at end of file diff --git a/openshift-logging.yaml b/res/openshift-logging.yaml similarity index 100% rename from openshift-logging.yaml rename to res/openshift-logging.yaml diff --git a/res/openshift-upgradeable.yaml b/res/openshift-upgradeable.yaml new file mode 100644 index 0000000..e962fdd --- /dev/null +++ b/res/openshift-upgradeable.yaml @@ -0,0 +1,39 @@ +apiVersion: config.openshift.io/v1 +kind: ClusterOperator +metadata: + annotations: + exclude.release.openshift.io/internal-openshift-hosted: "true" + creationTimestamp: "2020-06-08T04:36:36Z" + generation: 1 + name: openshift-apiserver + resourceVersion: "53861" + selfLink: /apis/config.openshift.io/v1/clusteroperators/openshift-apiserver +spec: {} +status: + conditions: + - lastTransitionTime: "2020-06-08T04:54:58Z" + reason: AsExpected + status: "False" + type: Degraded + - lastTransitionTime: "2020-06-08T06:34:00Z" + reason: AsExpected + status: "False" + type: Progressing + - lastTransitionTime: "2020-06-08T04:51:08Z" + reason: AsExpected + status: "True" + type: Available + - lastTransitionTime: "2020-06-08T04:45:45Z" + reason: AsExpected + status: "True" + type: Upgradeable + extension: null + relatedObjects: + - group: operator.openshift.io + name: cluster + resource: openshiftapiservers + versions: + - name: operator + version: 4.5.0-0.nightly-2020-06-04-214605 + - name: openshift-apiserver + version: 4.5.0-0.nightly-2020-06-04-214605 \ No newline at end of file diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..8983591 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,8 @@ +sonar.organization=openscap + +sonar.projectKey=OpenSCAP_yaml-filter +sonar.projectName=yaml-filter + +sonar.sources=. + +sonar.cfamily.build-wrapper-output=bw-output diff --git a/src/yaml-path.c b/src/yaml-path.c new file mode 100644 index 0000000..004c61d --- /dev/null +++ b/src/yaml-path.c @@ -0,0 +1,829 @@ +#include +#include +#include +#include +#include + +#include + +#include "yaml-path.h" + + +#define YAML_PATH_MAX_SECTION_ITEMS 256 + +#define _STR(x) #x +#define STR(x) _STR(x) + + +typedef enum yaml_path_section_type { + YAML_PATH_SECTION_ROOT, + YAML_PATH_SECTION_ANCHOR, + YAML_PATH_SECTION_INDEX, + YAML_PATH_SECTION_SET, + YAML_PATH_SECTION_KEY, + YAML_PATH_SECTION_SELECTION, +} yaml_path_section_type_t; + +typedef struct yaml_path_selection_key_raw { + const char *start; + size_t len; +} yaml_path_selection_key_raw_t; + + +typedef struct yaml_path_key { + const char *key; + TAILQ_ENTRY(yaml_path_key) entries; +} yaml_path_key_t; + +typedef TAILQ_HEAD(path_key_list, yaml_path_key) path_key_list_t; + + +typedef struct yaml_path_section { + yaml_path_section_type_t type; + size_t level; + union { + const char *anchor; + size_t index; + size_t *set; + const char *key; + path_key_list_t selection; + } data; + TAILQ_ENTRY(yaml_path_section) entries; + + yaml_node_type_t node_type; + size_t counter; + bool valid; + bool next_valid; +} yaml_path_section_t; + +typedef TAILQ_HEAD(path_section_list, yaml_path_section) path_section_list_t; + + +struct yaml_path { + path_section_list_t sections_list; + size_t sections_count; + size_t current_level; + size_t start_level; + + yaml_path_error_t error; +}; + + +static size_t +yaml_path_set_snprint (const size_t *set, char *s, size_t max_len) +{ + assert(set != NULL); + if (s == NULL) + return -1; + size_t len = 0; + if (set[0] == 0) { + len += snprintf(s, max_len, "[:]"); + } else { + for (size_t i = 1; i <= set[0]; i++) + len += snprintf(s + (len < max_len ? len : max_len), max_len - (len < max_len ? len : max_len), "%s%zu", (len ? "," : "["), set[i]); + len += snprintf(s + (len < max_len ? len : max_len), max_len - (len < max_len ? len : max_len), "]"); + } + return len; +} + +static size_t +yaml_path_selection_snprint (const path_key_list_t *selection, char *s, size_t max_len) +{ + assert(selection != NULL); + if (s == NULL) + return -1; + size_t len = 0; + yaml_path_key_t *el; + if TAILQ_EMPTY(selection) { + len += snprintf(s, max_len, ".*"); + } else { + TAILQ_FOREACH(el, selection, entries) { + char quote = strchr(el->key, '\'') ? '"' : '\''; + len += snprintf(s + (len < max_len ? len : max_len), max_len - (len < max_len ? len : max_len), "%s%c%s%c", (len ? "," : "["), quote, el->key, quote); + } + len += snprintf(s + (len < max_len ? len : max_len), max_len - (len < max_len ? len : max_len), "]"); + } + return len; +} + +static bool +yaml_path_set_is_empty (const size_t *set) +{ + assert(set != NULL); + return set[0] == 0; +} + +static bool +yaml_path_set_has_index (const size_t *set, size_t idx) +{ + assert(set != NULL); + for (size_t i = 1; i <= set[0]; i++) + if (set[i] == idx) + return true; + return false; +} + +static bool +yaml_path_selection_is_empty (path_key_list_t *selection) +{ + assert(selection != NULL); + return TAILQ_EMPTY(selection) ? true : false; +} + +static const char* +yaml_path_selection_key_get (path_key_list_t *selection, const char *key) +{ + assert(selection != NULL); + yaml_path_key_t *el; + TAILQ_FOREACH(el, selection, entries) { + if (!strcmp(el->key, key)) + return el->key; + } + return NULL; +} + +static size_t +yaml_path_selection_keys_add (path_key_list_t *selection, yaml_path_selection_key_raw_t *raw_keys, size_t count) +{ + assert(selection != NULL); + assert(raw_keys != NULL); + for (size_t i = 0; i < count; i++) { + yaml_path_key_t *el = malloc(sizeof(*el)); + if (el == NULL) + return i; + TAILQ_INSERT_TAIL(selection, el, entries); + el->key = strndup(raw_keys[i].start, raw_keys[i].len); + if (el->key == NULL) + return i; + } + return count; +} + +static void +yaml_path_selection_keys_remove (path_key_list_t *selection) +{ + assert(selection != NULL); + while (!TAILQ_EMPTY(selection)) { + yaml_path_key_t *el = TAILQ_FIRST(selection); + TAILQ_REMOVE(selection, el, entries); + free((void *)el->key); + free(el); + } +} + +static void +yaml_path_sections_remove (yaml_path_t *path) +{ + assert(path != NULL); + while (!TAILQ_EMPTY(&path->sections_list)) { + yaml_path_section_t *el = TAILQ_FIRST(&path->sections_list); + TAILQ_REMOVE(&path->sections_list, el, entries); + path->sections_count--; + switch (el->type) { + case YAML_PATH_SECTION_KEY: + free((void *)el->data.key); + break; + case YAML_PATH_SECTION_ANCHOR: + free((void *)el->data.anchor); + break; + case YAML_PATH_SECTION_SET: + free((void *)el->data.set); + break; + case YAML_PATH_SECTION_SELECTION: + yaml_path_selection_keys_remove(&el->data.selection); + break; + default: + break; + } + free(el); + } +} + +static yaml_path_section_t* +yaml_path_section_create (yaml_path_t *path, yaml_path_section_type_t section_type) +{ + yaml_path_section_t *el = malloc(sizeof(*el)); + if (el != NULL) { + memset(el, 0, sizeof(*el)); + path->sections_count++; + el->level = path->sections_count; + el->type = section_type; + el->node_type = YAML_NO_NODE; + TAILQ_INSERT_TAIL(&path->sections_list, el, entries); + if (el->type == YAML_PATH_SECTION_SELECTION) { + TAILQ_INIT(&el->data.selection); + } + } + return el; +} + +static size_t +yaml_path_section_snprint (yaml_path_section_t *section, char *s, size_t max_len) +{ + assert(section != NULL); + if (s == NULL) + return -1; + size_t len; + switch (section->type) { + case YAML_PATH_SECTION_ROOT: + len = snprintf(s, max_len, "$"); + break; + case YAML_PATH_SECTION_KEY: { + char quote = '\0'; + if (strpbrk(section->data.key, "[]().$&*")) + quote = strchr(section->data.key, '\'') ? '"' : '\''; + if (quote) { + len = snprintf(s, max_len, "[%c%s%c]", quote, section->data.key, quote); + } else { + len = snprintf(s, max_len, ".%s", section->data.key); + } + } + break; + case YAML_PATH_SECTION_ANCHOR: + len = snprintf(s, max_len, "&%s", section->data.anchor); + break; + case YAML_PATH_SECTION_INDEX: + len = snprintf(s, max_len, "[%zu]", section->data.index); + break; + case YAML_PATH_SECTION_SET: + len = yaml_path_set_snprint(section->data.set, s, max_len); + break; + case YAML_PATH_SECTION_SELECTION: + len = yaml_path_selection_snprint(§ion->data.selection, s, max_len); + break; + default: + len = snprintf(s, max_len, ""); + break; + } + return len; +} + +static void +yaml_path_error_set (yaml_path_t *path, yaml_path_error_type_t error_type, const char *message, size_t pos) +{ + assert(path != NULL); + path->error.type = error_type; + path->error.message = message; + path->error.pos = pos; +} + +static void +yaml_path_error_clear (yaml_path_t *path) +{ + yaml_path_error_set(path, YAML_PATH_ERROR_NONE, NULL, 0); +} + +#define return_with_error(error_type, error_text, error_pos) \ +do { \ + yaml_path_error_set(path, error_type, (error_text), (error_pos)); \ + goto error; \ +} while (0) + +static void +yaml_path_parse_impl (yaml_path_t *path, char *s_path) { + char *sp = s_path; + char *spe = NULL; + + assert(path != NULL); + + if (s_path == NULL || !s_path[0]) { + yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Path string is NULL or empty", 0); + return; + } + + while (*sp != '\0') { + switch (*sp) { + case '.': + case '[': + if (path->sections_count == 0) { + yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_ROOT); + if (sec == NULL) + return_with_error(YAML_PATH_ERROR_NOMEM, "Unable to allocate memory (section)", sp - s_path); + } + if (*sp == '.') { + // Key or Selection + spe = sp + 1; + if (*spe == '*') { + // Empty key selection section means that all keys were selected + yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_SELECTION); + if (sec == NULL) + return_with_error(YAML_PATH_ERROR_NOMEM, "Unable to allocate memory (section)", sp - s_path); + while (*spe != '.' && *spe != '[' && *spe != '\0') + spe++; + } else { + while (*spe != '.' && *spe != '[' && *spe != '\0') + spe++; + if (spe == sp+1) + return_with_error(YAML_PATH_ERROR_PARSE, "Segment key is missing", sp - s_path); + yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_KEY); + if (sec == NULL) + return_with_error(YAML_PATH_ERROR_NOMEM, "Unable to allocate memory (section)", sp - s_path); + sec->data.key = strndup(sp + 1, spe-sp - 1); + if (sec->data.key == NULL) + return_with_error(YAML_PATH_ERROR_NOMEM, "Unable to allocate memory (key)", sp - s_path); + } + sp = spe-1; + } else if (*sp == '[') { + spe = sp + 1; + if (*spe == '*' && *(spe+1) == ']') { + // Empty key selection section means that all keys were selected + yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_SELECTION); + if (sec == NULL) + return_with_error(YAML_PATH_ERROR_NOMEM, "Unable to allocate memory (section)", sp - s_path); + sp = spe+1; + } else if (*spe == '\'' || *spe == '"') { + // Key(s) + size_t keys_count = 0; + yaml_path_selection_key_raw_t raw_keys[YAML_PATH_MAX_SECTION_ITEMS] = {{NULL, 0}}; + sp = spe; + while (*spe != ']' && *spe != '\0') { + if (keys_count >= YAML_PATH_MAX_SECTION_ITEMS) + return_with_error(YAML_PATH_ERROR_SECTION, "Segment keys selection has reached the limit of keys: "STR(YAML_PATH_MAX_SECTION_ITEMS), sp - s_path); + char quote = *spe; + spe++; + while (*spe != quote && *spe != '\0') + spe++; + if (*spe == '\0') + return_with_error(YAML_PATH_ERROR_PARSE, "Segment key is invalid (unexpected end of string, missing closing quotation mark)", sp - s_path); + if (spe == sp+1) + return_with_error(YAML_PATH_ERROR_PARSE, "Segment key is missing", sp - s_path); + spe++; + if (*spe == '\0') + return_with_error(YAML_PATH_ERROR_PARSE, "Segment key is invalid (unexpected end of string, missing ']')", sp - s_path); + if (*spe == ']') { + raw_keys[keys_count].start = sp + 1; + raw_keys[keys_count].len = spe-sp - 2; + keys_count++; + } else if (*spe == ',') { + spe++; + if (*spe != '\'' && *spe != '"') + return_with_error(YAML_PATH_ERROR_PARSE, "Segment keys selection is invalid (invalid character)", spe - s_path); + raw_keys[keys_count].start = sp + 1; + raw_keys[keys_count].len = spe-sp - 3; + keys_count++; + sp = spe; + } else { + return_with_error(YAML_PATH_ERROR_PARSE, "Segment key is invalid (invalid character)", spe - s_path); + } + } + if (keys_count == 1) { + yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_KEY); + if (sec == NULL) + return_with_error(YAML_PATH_ERROR_NOMEM, "Unable to allocate memory (section)", sp - s_path); + sec->data.key = strndup(raw_keys[0].start, raw_keys[0].len); + if (sec->data.key == NULL) + return_with_error(YAML_PATH_ERROR_NOMEM, "Unable to allocate memory (key)", sp - s_path); + } else if (keys_count > 1) { + yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_SELECTION); + if (sec == NULL) + return_with_error(YAML_PATH_ERROR_NOMEM, "Unable to allocate memory (section)", sp - s_path); + if (yaml_path_selection_keys_add(&sec->data.selection, raw_keys, keys_count) != keys_count) + return_with_error(YAML_PATH_ERROR_NOMEM, "Unable to allocate memory (keys selection)", sp - s_path); + } + sp = spe; + } else { + // Indices + if (*spe == ':' && *(spe+1) == ']') { + // Set (all-inclusive) + yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_SET); + if (sec == NULL) + return_with_error(YAML_PATH_ERROR_NOMEM, "Unable to allocate memory (section)", sp - s_path); + sec->data.set = malloc(sizeof(size_t) * 1); + if (sec->data.set == NULL) + return_with_error(YAML_PATH_ERROR_NOMEM, "Unable to allocate memory (set)", sp - s_path); + sec->data.set[0] = 0; + sp = spe+1; + } else { + while (*spe == ' ' || *spe == '\t') + spe++; + if (*spe == '-') + return_with_error(YAML_PATH_ERROR_PARSE, "Segment index is invalid (negative number)", spe - s_path); + size_t idx = strtoul(spe, &spe, 10); + if (*spe == ']') { + // Index + yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_INDEX); + if (sec == NULL) + return_with_error(YAML_PATH_ERROR_NOMEM, "Unable to allocate memory (section)", sp - s_path); + sec->data.index = idx; + sp = spe; + } else if (*spe == ',') { + // Set + size_t indices[YAML_PATH_MAX_SECTION_ITEMS+1] = {0}; + while (*spe == ',' && spe > sp+1) { + if (indices[0] >= YAML_PATH_MAX_SECTION_ITEMS) + return_with_error(YAML_PATH_ERROR_SECTION, "Segment indices set has reached the limit of indices: "STR(YAML_PATH_MAX_SECTION_ITEMS), sp - s_path); + sp = spe++; + indices[0]++; + indices[indices[0]] = idx; + while (*spe == ' ' || *spe == '\t') + spe++; + if (*spe == '-') + return_with_error(YAML_PATH_ERROR_PARSE, "Segment set index is invalid (negative number)", spe - s_path); + idx = strtoul(spe, &spe, 10); + } + if (*spe == ']' && spe > sp+1) { + indices[0]++; + indices[indices[0]] = idx; + yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_SET); + if (sec == NULL) + return_with_error(YAML_PATH_ERROR_NOMEM, "Unable to allocate memory (section)", sp - s_path); + sec->data.set = malloc(sizeof(*indices) * (indices[0] + 1)); + if (sec->data.set == NULL) + return_with_error(YAML_PATH_ERROR_NOMEM, "Unable to allocate memory (set)", sp - s_path); + memcpy(sec->data.set, indices, sizeof(*indices) * (indices[0] + 1)); + sp = spe; + } else { + return_with_error(YAML_PATH_ERROR_PARSE, "Segment set is invalid (invalid character)", spe - s_path); + } + } else if (*spe == '\0') { + return_with_error(YAML_PATH_ERROR_PARSE, "Segment index is invalid (unexpected end of string, missing ']')", spe - s_path); + } else { + return_with_error(YAML_PATH_ERROR_PARSE, "Segment index is invalid (invalid character)", spe - s_path); + } + } + } + } + break; + case '&': + if (path->sections_count == 0) { + spe = sp + 1; + while (*spe != '.' && *spe != '[' && *spe != '\0') + spe++; + if (spe - sp > 1) { + yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_ANCHOR); + if (sec == NULL) + return_with_error(YAML_PATH_ERROR_NOMEM, "Unable to allocate memory (section)", sp - s_path); + sec->data.anchor = strndup(sp+1, spe-sp-1); + if (sec->data.anchor == NULL) + return_with_error(YAML_PATH_ERROR_NOMEM, "Unable to allocate memory (anchor)", sp - s_path); + } else { + return_with_error(YAML_PATH_ERROR_PARSE, "Segment anchor is invalid (empty)", spe - s_path); + } + } else { + return_with_error(YAML_PATH_ERROR_SECTION, "Anchor segment is only allowed at the beginning of the path", sp - s_path); + } + sp = spe - 1; + break; + case '$': + if (path->sections_count == 0) { + yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_ROOT); + if (sec == NULL) + return_with_error(YAML_PATH_ERROR_NOMEM, "Unable to allocate memory (section)", sp - s_path); + } else { + return_with_error(YAML_PATH_ERROR_SECTION, "Root segment is only allowed at the beginning of the path", sp - s_path); + } + break; + default: + if (path->sections_count == 0) { + spe = sp + 1; + // Special beginning of the path (implicit key) + while (*spe != '.' && *spe != '[' && *spe != '\0') + spe++; + yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_ROOT); + if (sec == NULL) + return_with_error(YAML_PATH_ERROR_NOMEM, "Unable to allocate memory (section)", sp - s_path); + sec = yaml_path_section_create(path, YAML_PATH_SECTION_KEY); + if (sec == NULL) + return_with_error(YAML_PATH_ERROR_NOMEM, "Unable to allocate memory (section)", sp - s_path); + sec->data.key = strndup(sp, spe-sp); + if (sec->data.key == NULL) + return_with_error(YAML_PATH_ERROR_NOMEM, "Unable to allocate memory (key)", sp - s_path); + sp = spe-1; + } + break; + } + sp++; + } + + if (path->sections_count == 0) + return_with_error(YAML_PATH_ERROR_SECTION, "Invalid, empty or meaningless path", 0); + + return; // OK + +error: + yaml_path_sections_remove(path); + if (path->error.type == YAML_PATH_ERROR_NONE) + yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Unable to parse the path string", 0); +} + +static yaml_path_section_t* +yaml_path_section_get_at_level (yaml_path_t *path, size_t level) +{ + assert(path != NULL); + yaml_path_section_t *el; + TAILQ_FOREACH(el, &path->sections_list, entries) { + if (el->level == level) + return el; + } + return NULL; +} + +static yaml_path_section_t* +yaml_path_section_get_first (yaml_path_t *path) +{ + return yaml_path_section_get_at_level(path, 1); +} + +static yaml_path_section_t* +yaml_path_section_get_current (yaml_path_t *path) +{ + assert(path != NULL); + if (!path->start_level) + return NULL; + return yaml_path_section_get_at_level(path, path->current_level - path->start_level + 1); +} + +static bool +yaml_path_section_current_is_last (yaml_path_t *path) +{ + assert(path != NULL); + yaml_path_section_t *sec = yaml_path_section_get_current(path); + if (sec == NULL) + return false; + return sec->level == path->sections_count; +} + +static bool +yaml_path_sections_prev_are_valid (yaml_path_t *path) +{ + assert(path != NULL); + int valid = true; + yaml_path_section_t *el; + TAILQ_FOREACH(el, &path->sections_list, entries) { + if (el->level < path->current_level - path->start_level + 1) + valid = el->valid && valid; + } + return valid; +} + +static bool +yaml_path_section_is_mandatory_container (yaml_path_section_t *sec) +{ + assert(sec != NULL); + bool res = false; + if ((sec->type == YAML_PATH_SECTION_SELECTION && sec->node_type == YAML_MAPPING_NODE) + || + (sec->type == YAML_PATH_SECTION_SET && sec->node_type == YAML_SEQUENCE_NODE)) + res = true; + return res; +} + +static const char * +yaml_path_filter_event_get_anchor (const yaml_event_t *event) +{ + assert(event != NULL); + switch(event->type) { + case YAML_MAPPING_START_EVENT: + return (const char *)event->data.mapping_start.anchor; + case YAML_SEQUENCE_START_EVENT: + return (const char *)event->data.sequence_start.anchor; + case YAML_SCALAR_EVENT: + return (const char *)event->data.scalar.anchor; + default: + break; + } + return NULL; +} + +static bool +yaml_path_is_valid (yaml_path_t *path) +{ + assert(path != NULL); + bool valid = true; + yaml_path_section_t *el; + TAILQ_FOREACH(el, &path->sections_list, entries) { + valid = el->valid && valid; + } + return valid; +} + + +/* Public API -------------------------------------------------------------- */ + +yaml_path_t* +yaml_path_create (void) +{ + yaml_path_t *ypath = malloc(sizeof(*ypath)); + if (ypath != NULL) { + memset (ypath, 0, sizeof(*ypath)); + TAILQ_INIT(&ypath->sections_list); + } + return ypath; +} + +int +yaml_path_parse (yaml_path_t *path, char *s_path) +{ + if (path == NULL) + return -1; + + yaml_path_sections_remove(path); + yaml_path_error_clear(path); + + yaml_path_parse_impl(path, s_path); + if (path->error.type != YAML_PATH_ERROR_NONE) + return -2; + + return 0; +} + +void +yaml_path_destroy (yaml_path_t *path) +{ + if (path == NULL) + return; + yaml_path_sections_remove(path); + free(path); +} + +const yaml_path_error_t* +yaml_path_error_get (yaml_path_t *path) +{ + if (path == NULL) + return NULL; + return &path->error; +} + +size_t +yaml_path_snprint (yaml_path_t *path, char *s, size_t max_len) +{ + if (s == NULL) + return -1; + if (path == NULL) + return 0; + + size_t len = 0; + yaml_path_section_t *el; + TAILQ_FOREACH(el, &path->sections_list, entries) { + len += yaml_path_section_snprint(el, s + (len < max_len ? len : max_len), max_len - (len < max_len ? len : max_len)); + } + return len; +} + +yaml_path_filter_result_t +yaml_path_filter_event (yaml_path_t *path, yaml_parser_t *parser, yaml_event_t *event) +{ + if (path == NULL || parser == NULL || event == NULL || path->sections_count == 0) + return YAML_PATH_FILTER_RESULT_OUT; + + int res = YAML_PATH_FILTER_RESULT_OUT; + + const char *anchor = yaml_path_filter_event_get_anchor(event); + + if (!path->start_level) { + switch (yaml_path_section_get_first(path)->type) { + case YAML_PATH_SECTION_ROOT: + if (event->type == YAML_DOCUMENT_START_EVENT) { + path->start_level = 1; + yaml_path_section_get_first(path)->valid = true; + } + break; + case YAML_PATH_SECTION_ANCHOR: + if (anchor != NULL && !strcmp(yaml_path_section_get_first(path)->data.anchor, anchor)) { + path->start_level = path->current_level; + } + break; + default: + break; + } + } + + yaml_path_section_t *current_section = yaml_path_section_get_current(path); + if (current_section) { + switch (event->type) { + case YAML_DOCUMENT_START_EVENT: + case YAML_MAPPING_START_EVENT: + case YAML_SEQUENCE_START_EVENT: + case YAML_ALIAS_EVENT: + case YAML_SCALAR_EVENT: + switch (current_section->node_type) { + case YAML_NO_NODE: + if (current_section->type == YAML_PATH_SECTION_ANCHOR) { + current_section->valid = false; + if (anchor != NULL && !strcmp(current_section->data.anchor, anchor)) + current_section->valid = true; + } + break; + case YAML_MAPPING_NODE: + if (current_section->type == YAML_PATH_SECTION_KEY) { + if (current_section->counter % 2) { + current_section->valid = current_section->next_valid; + current_section->next_valid = false; + } else { + current_section->next_valid = !strcmp(current_section->data.key, (const char *)event->data.scalar.value); + current_section->valid = false; + } + } else if (current_section->type == YAML_PATH_SECTION_SELECTION) { + if (current_section->counter % 2) { + current_section->valid = current_section->next_valid; + current_section->next_valid = false; + } else { + current_section->next_valid = yaml_path_selection_is_empty(¤t_section->data.selection) + || yaml_path_selection_key_get(¤t_section->data.selection, (const char *)event->data.scalar.value) != NULL; + current_section->valid = current_section->next_valid; + } + } else { + current_section->valid = false; + } + break; + case YAML_SEQUENCE_NODE: + if (current_section->type == YAML_PATH_SECTION_INDEX) { + current_section->valid = current_section->data.index == current_section->counter; + } else if (current_section->type == YAML_PATH_SECTION_SET) { + current_section->valid = yaml_path_set_is_empty(current_section->data.set) + || yaml_path_set_has_index(current_section->data.set, current_section->counter); + } else { + current_section->valid = false; + } + break; + default: + break; + } + current_section->counter++; + default: + break; + } + } + + switch (event->type) { + case YAML_STREAM_START_EVENT: + case YAML_STREAM_END_EVENT: + case YAML_NO_EVENT: + res = YAML_PATH_FILTER_RESULT_IN; + break; + case YAML_DOCUMENT_START_EVENT: + if (path->start_level == 1) + path->current_level++; + res = YAML_PATH_FILTER_RESULT_IN; + break; + case YAML_DOCUMENT_END_EVENT: + if (path->start_level == 1) + path->current_level--; + res = YAML_PATH_FILTER_RESULT_IN; + break; + case YAML_MAPPING_START_EVENT: + case YAML_SEQUENCE_START_EVENT: + if (current_section) { + if (yaml_path_section_current_is_last(path)) + if (yaml_path_is_valid(path)) + res = YAML_PATH_FILTER_RESULT_IN; + } else { + if (path->current_level > path->start_level) { + if (yaml_path_is_valid(path)) + res = YAML_PATH_FILTER_RESULT_IN; + } + } + path->current_level++; + current_section = yaml_path_section_get_current(path); + if (current_section) { + current_section->node_type = event->type == YAML_MAPPING_START_EVENT ? YAML_MAPPING_NODE : YAML_SEQUENCE_NODE; + current_section->counter = 0; + } + if (current_section) { + if (yaml_path_section_is_mandatory_container(current_section) && yaml_path_sections_prev_are_valid(path)) + res = YAML_PATH_FILTER_RESULT_IN; + } + break; + case YAML_MAPPING_END_EVENT: + case YAML_SEQUENCE_END_EVENT: + if (current_section) { + if (yaml_path_section_is_mandatory_container(current_section) && yaml_path_sections_prev_are_valid(path)) + res = YAML_PATH_FILTER_RESULT_IN; + + } + path->current_level--; + current_section = yaml_path_section_get_current(path); + if (current_section) { + if (yaml_path_section_current_is_last(path)) + if (yaml_path_is_valid(path)) + res = YAML_PATH_FILTER_RESULT_IN; + } else { + if (path->current_level > path->start_level) { + if (yaml_path_is_valid(path)) + res = YAML_PATH_FILTER_RESULT_IN; + } + } + break; + case YAML_ALIAS_EVENT: + case YAML_SCALAR_EVENT: + if (!current_section) { + if (path->current_level >= path->start_level) + if (yaml_path_is_valid(path)) + res = YAML_PATH_FILTER_RESULT_IN; + } else { + if (yaml_path_section_current_is_last(path) && yaml_path_is_valid(path)) + res = YAML_PATH_FILTER_RESULT_IN; + if (current_section->valid + && current_section->node_type == YAML_MAPPING_NODE + && current_section->counter % 2) { + if (yaml_path_section_is_mandatory_container(current_section) && yaml_path_sections_prev_are_valid(path)) + res = YAML_PATH_FILTER_RESULT_IN_DANGLING_KEY; + } + } + break; + default: + break; + } + + return res; +} diff --git a/yaml-path.h b/src/yaml-path.h similarity index 72% rename from yaml-path.h rename to src/yaml-path.h index eb9e9dc..66e0298 100644 --- a/yaml-path.h +++ b/src/yaml-path.h @@ -8,20 +8,22 @@ typedef struct yaml_path yaml_path_t; typedef enum yaml_path_error_type { YAML_PATH_ERROR_NONE, + YAML_PATH_ERROR_NOMEM, YAML_PATH_ERROR_PARSE, + YAML_PATH_ERROR_SECTION, } yaml_path_error_type_t; typedef struct yaml_path_error { yaml_path_error_type_t type; const char *message; - const char *context; size_t pos; } yaml_path_error_t; -typedef enum yaml_path_filter_mode { - YAML_PATH_FILTER_RETURN_ALL, - YAML_PATH_FILTER_RETURN_SHALLOW, -} yaml_path_filter_mode_t; +typedef enum yaml_path_filter_result { + YAML_PATH_FILTER_RESULT_OUT, + YAML_PATH_FILTER_RESULT_IN, + YAML_PATH_FILTER_RESULT_IN_DANGLING_KEY, +} yaml_path_filter_result_t; yaml_path_t* @@ -36,8 +38,8 @@ yaml_path_destroy (yaml_path_t *path); const yaml_path_error_t* yaml_path_error_get (yaml_path_t *path); -int -yaml_path_filter_event (yaml_path_t *path, yaml_parser_t *parser, yaml_event_t *event, yaml_path_filter_mode_t mode); +yaml_path_filter_result_t +yaml_path_filter_event (yaml_path_t *path, yaml_parser_t *parser, yaml_event_t *event); size_t yaml_path_snprint (yaml_path_t *path, char *s, size_t max_len); diff --git a/yamlp.c b/src/yamlp.c similarity index 80% rename from yamlp.c rename to src/yamlp.c index 334c626..f4d44d2 100644 --- a/yamlp.c +++ b/src/yamlp.c @@ -9,13 +9,14 @@ #include "yaml-path.h" -int parse_and_emit (yaml_parser_t *parser, yaml_emitter_t *emitter, yaml_path_t *path, yaml_path_filter_mode_t mode, int use_flow_style) +static int +parse_and_emit (yaml_parser_t *parser, yaml_emitter_t *emitter, yaml_path_t *path, int use_flow_style) { yaml_event_t event; - yaml_event_type_t prev_event_type, event_type; + yaml_event_type_t event_type, prev_event_type = YAML_NO_EVENT; + yaml_path_filter_result_t result, prev_result = YAML_PATH_FILTER_RESULT_OUT; do { - if (!yaml_parser_parse(parser, &event)) { switch (parser->error) { case YAML_MEMORY_ERROR: @@ -53,7 +54,8 @@ int parse_and_emit (yaml_parser_t *parser, yaml_emitter_t *emitter, yaml_path_t return 1; } else { event_type = event.type; - if (!yaml_path_filter_event(path, parser, &event, mode)) { + result = yaml_path_filter_event(path, parser, &event); + if (result == YAML_PATH_FILTER_RESULT_OUT) { yaml_event_delete(&event); } else { if (use_flow_style) { @@ -68,11 +70,16 @@ int parse_and_emit (yaml_parser_t *parser, yaml_emitter_t *emitter, yaml_path_t break; } } - if (prev_event_type == YAML_DOCUMENT_START_EVENT && event_type == YAML_DOCUMENT_END_EVENT) { + if ((prev_event_type == YAML_DOCUMENT_START_EVENT && event_type == YAML_DOCUMENT_END_EVENT) + || (prev_result == YAML_PATH_FILTER_RESULT_IN_DANGLING_KEY + && (event_type == YAML_MAPPING_END_EVENT + || event_type == YAML_SEQUENCE_END_EVENT + || result == YAML_PATH_FILTER_RESULT_IN_DANGLING_KEY))) { yaml_event_t null_event= {0}; yaml_scalar_event_initialize(&null_event, NULL, (yaml_char_t *)"!!null", (yaml_char_t *)"null", 4, 1, 0, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(emitter, &null_event); } + prev_result = result; prev_event_type = event_type; if (!yaml_emitter_emit(emitter, &event)) { switch (emitter->error) @@ -100,11 +107,12 @@ int parse_and_emit (yaml_parser_t *parser, yaml_emitter_t *emitter, yaml_path_t } -void help (void) +static void +help (void) { printf("yamlp - filtering utility for YAML documents\n"); printf("\n"); - printf("Usage: yamlp [-F] [-S] [-W ] [-f ] \n"); + printf("Usage: yamlp [-F] [-W ] [-f ] \n"); printf(" yamlp -h\n"); printf("\n"); printf("The tool will take the input YAML document from or a (-f option),\n"); @@ -118,8 +126,6 @@ void help (void) printf("\n"); printf(" -h help;\n"); printf("\n"); - printf(" -S 'shallow' filter mode;\n"); - printf("\n"); printf(" -W line wrap width, no wrapping if omitted.\n"); printf("\n"); } @@ -129,8 +135,7 @@ int main(int argc, char *argv[]) int flow = 0; char *file_name = NULL; char *path_string = NULL; - int wrap = -1; - yaml_path_filter_mode_t mode = YAML_PATH_FILTER_RETURN_ALL; + long wrap = -1; int opt; while ((opt = getopt(argc, argv, ":f:W:vhSF")) != -1) { @@ -138,13 +143,9 @@ int main(int argc, char *argv[]) case 'h': help(); return 0; - break; case 'F': flow = 1; break; - case 'S': - mode = YAML_PATH_FILTER_RETURN_SHALLOW; - break; case 'W': wrap = strtol(optarg, NULL, 10); if (!wrap) { @@ -158,11 +159,12 @@ int main(int argc, char *argv[]) case ':': fprintf(stderr, "Option needs a value\n"); return 1; - break; case '?': fprintf(stderr, "Unknown option '%c'\n", optopt); return 1; - break; + default: + fprintf(stderr, "Unhandled option '%c'\n", opt); + return 1; } } @@ -186,9 +188,10 @@ int main(int argc, char *argv[]) yaml_path_t *path = yaml_path_create(); if (yaml_path_parse(path, path_string)) { - fprintf(stderr, "Invalid path '%s' (%s)\n", path_string, yaml_path_error_get(path)->message); + fprintf(stderr, "Invalid path: '%s'\n", path_string); + fprintf(stderr, " %*s^ %s [at position %zu]\n", (int)yaml_path_error_get(path)->pos, " ", yaml_path_error_get(path)->message, yaml_path_error_get(path)->pos); return 3; - }; + } yaml_parser_t parser; yaml_emitter_t emitter; @@ -198,9 +201,9 @@ int main(int argc, char *argv[]) yaml_emitter_initialize(&emitter); yaml_emitter_set_output_file(&emitter, stdout); - yaml_emitter_set_width(&emitter, wrap); + yaml_emitter_set_width(&emitter, (int) wrap); - if (parse_and_emit(&parser, &emitter, path, mode, flow)) { + if (parse_and_emit(&parser, &emitter, path, flow)) { return 4; } @@ -208,7 +211,8 @@ int main(int argc, char *argv[]) yaml_emitter_delete(&emitter); yaml_path_destroy(path); - fclose(file); + if (file != NULL) + fclose(file); return 0; } diff --git a/test-yamlp.sh b/test-yamlp.sh deleted file mode 100755 index 8c2b413..0000000 --- a/test-yamlp.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -yamlp_test() -{ - echo -n "$1 ($2) " - out=$(./yamlp -F -f "$1" "$2") || return 1 - echo -n "-> $out" - if [ "$out" != "$3" ]; then - echo ": FAILED, expected result: $3" - return 2 - else - echo ": OK" - fi -} - -yamlp_test "../openshift-logging.yaml" ".spec.pipelines[:].inputSource" "[logs.app, logs.infra, logs.audit]" -res=$((res+$?)) - -exit $res diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..bc01b4b --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,28 @@ +function(_add_test TEST_NAME TEST_COMMAND) + add_test(NAME ${TEST_NAME} COMMAND ${TEST_COMMAND}) + set_tests_properties(${TEST_NAME} PROPERTIES + SKIP_RETURN_CODE 255 + ENVIRONMENT "CURRENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR};SOURCE_DIR=${CMAKE_SOURCE_DIR};BINARY_DIR=${CMAKE_BINARY_DIR}" + ) +endfunction() + +function(add_test_executable EXECUTABLE_NAME SOURCE_FILE) + set(TEST_EXECUTABLE_LIBRARIES yaml-path) + add_executable(${EXECUTABLE_NAME} ${SOURCE_FILE} ${ARGN}) + target_link_libraries(${EXECUTABLE_NAME} ${TEST_EXECUTABLE_LIBRARIES}) + target_include_directories(${EXECUTABLE_NAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") + add_coverage(${EXECUTABLE_NAME}) + string(REPLACE "${CMAKE_SOURCE_DIR}/tests/" "" TEST_NAME "${CMAKE_CURRENT_SOURCE_DIR}/${EXECUTABLE_NAME}") + _add_test(${TEST_NAME} "${CMAKE_BINARY_DIR}/tests/${EXECUTABLE_NAME}") +endfunction() + +function(add_test_script TEST_SCRIPT) + string(REPLACE "${CMAKE_SOURCE_DIR}/tests/" "" TEST_NAME "${CMAKE_CURRENT_SOURCE_DIR}/${TEST_SCRIPT}") + _add_test(${TEST_NAME} "${CMAKE_CURRENT_SOURCE_DIR}/${TEST_SCRIPT}") +endfunction() + +add_test_executable(test-path-segments test-path-segments.c) +add_test_executable(test-paths test-paths.c) +add_test_script(test-yamlp.sh) + +list(APPEND LCOV_REMOVE_PATTERNS "'${CMAKE_SOURCE_DIR}/tests/*'") diff --git a/test-path-segments.c b/tests/test-path-segments.c similarity index 54% rename from test-path-segments.c rename to tests/test-path-segments.c index 2340937..0889004 100644 --- a/test-path-segments.c +++ b/tests/test-path-segments.c @@ -1,6 +1,7 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2020 Red Hat Inc., Durham, North Carolina. + #include -#include -#include #include "yaml-path.h" @@ -17,7 +18,7 @@ yp_s[PATH_STRING_LEN] = {0}; #define ASCII_ERR "\033[0;33m" #define ASCII_RST "\033[0;0m" -void +static void yp_test (char *p, int expected_failure) { yaml_path_t *yp = yaml_path_create(); @@ -35,7 +36,7 @@ yp_test (char *p, int expected_failure) printf(ASCII_ERR); test_result++; } - printf(" -X %s (at pos: %zu): %s\n", ype->message, ype->pos, !expected_failure ? ASCII_RST"FAILED" : "OK"); + printf(" -- %s (at pos: %zu): %s\n", ype->message, ype->pos, !expected_failure ? ASCII_RST"FAILED" : "OK"); } yaml_path_destroy(yp); } @@ -46,7 +47,9 @@ yp_test (char *p, int expected_failure) int main (int argc, char *argv[]) { - yp_test_good(".first"); + (void) argc; (void) argv; // Yep, we don't need them + + yp_test_good(".first"); yp_test_good(".first[0]"); yp_test_good(".first.second[0].third"); yp_test_good(".first.0"); @@ -58,31 +61,62 @@ int main (int argc, char *argv[]) yp_test_good("!"); yp_test_good("$"); - yp_test_good("[0:0]"); - yp_test_good("[0:0:1]"); - yp_test_good("[100:]"); - yp_test_good("[100::]"); - yp_test_good("[:100]"); - yp_test_good("[:100:]"); yp_test_good("[:]"); - yp_test_good("[::]"); - yp_test_good("[-03:-200:+500]"); + yp_test_good("[':']['*'][:]"); + yp_test_good(".:.*[:]"); + yp_test_good("[0,2,3,4,5,20,180]"); yp_test_good("&anc"); yp_test_good("&anc[0]"); yp_test_good("&anc[0].zzz"); yp_test_good("el['key']"); - yp_test_good("el['key'].other[0]['key']"); + yp_test_good("el[\"key\"]"); + yp_test_good("el[\"k[]ey\"]"); + yp_test_good("el[\"k'ey\"]"); + yp_test_good("el['k\"ey']"); + yp_test_good("el.k\"ey"); + yp_test_good("el.k$ey"); + yp_test_good("el.k'&'ey"); + yp_test_good("el['key'].other[0]['key'][0,2]"); + + yp_test_good("el['first','other']"); + yp_test_good("el[\"first\",\"other\"]"); + yp_test_good("el[\"first\",'other']"); + yp_test_good("el['key','valid']['now','allowed']"); + yp_test_good("el.*"); + yp_test_good("el[*]"); + yp_test_good("el['*']"); + + yp_test_invalid("$$"); + yp_test_invalid("$&"); + + yp_test_invalid("&"); yp_test_invalid("$."); yp_test_invalid(""); yp_test_invalid("."); yp_test_invalid("element["); + yp_test_invalid("[-5]"); + yp_test_invalid("[1,-5]"); + + yp_test_invalid("[0:0]"); + yp_test_invalid("[0:0:1]"); + yp_test_invalid("[100:]"); + yp_test_invalid("[100::]"); + yp_test_invalid("[:100]"); + yp_test_invalid("[:100:]"); + yp_test_invalid("[::]"); + yp_test_invalid("[-03:-200:+500]"); + yp_test_invalid("[0:0:0]"); yp_test_invalid("[::-1]"); yp_test_invalid("[0.key[0]"); + yp_test_invalid("[1,]"); + yp_test_invalid("[,]"); + yp_test_invalid("[1,:]"); + yp_test_invalid("[1,2:]"); yp_test_invalid("el[&]"); yp_test_invalid("el[&"); @@ -93,9 +127,18 @@ int main (int argc, char *argv[]) yp_test_invalid("el[&anchor][100]"); yp_test_invalid("el[']"); + yp_test_invalid("[*'"); yp_test_invalid("el['key].wrong"); yp_test_invalid("el['key.wrong"); yp_test_invalid("el['key'"); + yp_test_invalid("el['key\"]"); + yp_test_invalid("el[\"key']"); + yp_test_invalid("el['k'ey']"); + + yp_test_invalid("el['key';'wrong']"); + yp_test_invalid("el['key',]"); + yp_test_invalid("el['key',invalid]"); + yp_test_invalid("el['first',]"); return test_result; } diff --git a/test-paths.c b/tests/test-paths.c similarity index 65% rename from test-paths.c rename to tests/test-paths.c index 58cfdad..fa722e8 100644 --- a/test-paths.c +++ b/tests/test-paths.c @@ -1,6 +1,8 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2020 Red Hat Inc., Durham, North Carolina. + #include #include -#include #include #include "yaml-path.h" @@ -59,14 +61,11 @@ yaml_out[YAML_STRING_LEN] = {0}; static size_t yaml_out_len = 0; -static yaml_path_filter_mode_t -mode = YAML_PATH_FILTER_RETURN_ALL; - static int test_result = 0; -int +static int yp_run (char *path) { yaml_parser_t parser; @@ -74,11 +73,15 @@ yp_run (char *path) int res = 0; yaml_path_t *yp = yaml_path_create(); - yaml_path_parse(yp, path); + if (yaml_path_parse(yp, path)) { + printf("Path error: %s\n", yaml_path_error_get(yp)->message); + yaml_path_destroy(yp); + return 1; + } - char spath[YAML_STRING_LEN] = {0}; - yaml_path_snprint(yp, spath, YAML_STRING_LEN); - printf("(%s) ", spath); + //char spath[YAML_STRING_LEN] = {0}; + //yaml_path_snprint(yp, spath, YAML_STRING_LEN); + //printf("(%s) ", spath); yaml_emitter_initialize(&emitter); yaml_parser_initialize(&parser); @@ -89,7 +92,8 @@ yp_run (char *path) yaml_emitter_set_width(&emitter, -1); yaml_event_t event; - yaml_event_type_t prev_event_type, event_type; + yaml_event_type_t event_type, prev_event_type = YAML_NO_EVENT; + yaml_path_filter_result_t result, prev_result = 0; do { if (!yaml_parser_parse(&parser, &event)) { @@ -126,30 +130,35 @@ yp_run (char *path) goto error; } else { event_type = event.type; - if (!yaml_path_filter_event(yp, &parser, &event, mode)) { + result = yaml_path_filter_event(yp, &parser, &event); + if (result == YAML_PATH_FILTER_RESULT_OUT) { yaml_event_delete(&event); } else { - if (prev_event_type == YAML_DOCUMENT_START_EVENT && event_type == YAML_DOCUMENT_END_EVENT) { + if ((prev_event_type == YAML_DOCUMENT_START_EVENT && event_type == YAML_DOCUMENT_END_EVENT) + || (prev_result == YAML_PATH_FILTER_RESULT_IN_DANGLING_KEY + && (event_type == YAML_MAPPING_END_EVENT || event_type == YAML_SEQUENCE_END_EVENT || result == YAML_PATH_FILTER_RESULT_IN_DANGLING_KEY))) { yaml_event_t null_event= {0}; yaml_scalar_event_initialize(&null_event, NULL, (yaml_char_t *)"!!null", (yaml_char_t *)"null", 4, 1, 0, YAML_ANY_SCALAR_STYLE); yaml_emitter_emit(&emitter, &null_event); } + prev_result = result; prev_event_type = event_type; if (!yaml_emitter_emit(&emitter, &event)) { - printf("Error after '%s'\n", yp_event_name(event.type)); + yaml_emitter_flush(&emitter); + printf("%s --> Error after '%s': ", yaml_out, yp_event_name(event.type)); switch (emitter.error) { case YAML_MEMORY_ERROR: - printf("Memory error: Not enough memory for emitting\n"); + printf("Memory error (Not enough memory for emitting)"); break; case YAML_WRITER_ERROR: - printf("Writer error: %s\n", emitter.problem); + printf("Writer error (%s)", emitter.problem); break; case YAML_EMITTER_ERROR: - printf("Emitter error: %s\n", emitter.problem); + printf("Emitter error (%s)", emitter.problem); break; default: - printf("Internal error\n"); + printf("Internal error"); break; } res = 2; @@ -171,17 +180,19 @@ yp_run (char *path) #define ASCII_ERR "\033[0;33m" #define ASCII_RST "\033[0;0m" -void +static void yp_test (char *path, char *yaml_exp) { - printf("%s ", path); + printf("%s "ASCII_ERR, path); if (!yp_run(path)) { rstrip(yaml_out); if (!strcmp(yaml_exp, yaml_out)) { - printf("(%s): OK\n", yaml_exp); + printf(ASCII_RST"(%s): OK\n", yaml_exp); return; } - printf(ASCII_ERR"(%s != %s)"ASCII_RST": FAILED\n", yaml_exp, yaml_out); + printf("(%s != %s)"ASCII_RST": FAILED\n", yaml_exp, yaml_out); + } else { + printf(ASCII_RST": ERROR\n"); } test_result++; } @@ -189,6 +200,8 @@ yp_test (char *path, char *yaml_exp) int main (int argc, char *argv[]) { + (void) argc; (void) argv; // Yep, we don't need them + yaml = "{" "first: {" @@ -204,14 +217,18 @@ int main (int argc, char *argv[]) "]" "}," "second: [" - "{'abc': &anc [1, 2], 'abcdef': 2, 'z': *anc}," - "{'abc': [3, 4], 'abcdef': 4, 'z': 'zzz'}" + "{'abc': &anc [1, 2], 'def': [11, 22], 'abcdef': 2, 'z': *anc, 'q': 'Q'}," + "{'abc': [3, 4], 'def': {'z': '!'}, 'abcdef': 4, 'z': 'zzz'}" + "]," + "3rd: [" + "{'a': {'A': [0, 1], 'AA': [2, 3]}, 'b': {'A': [10, 11], 'BB': [9, 8]}}," + "{'z': {'A': [0, 1], 'BB': [22, 33]}}," + "&x {'q': [1, 2]}," "]" "}"; // Path Expected filtered YAML result - mode = YAML_PATH_FILTER_RETURN_ALL; yp_test("$.first.Map", "{1: '1'}"); yp_test(".first", "{'Map': {1: '1'}, 'Nop': 0, 'Yep': '1', 'Arr': [[11, 12], 2, ['31', '32'], [4, 5, 6, 7, 8, 9], {'k': 'val', 0: 0}]}"); yp_test(".first.Nop", "0"); @@ -219,24 +236,27 @@ int main (int argc, char *argv[]) yp_test(".first.Arr[0]", "[11, 12]"); yp_test(".first.Arr[1]", "2"); yp_test(".first.Arr[2][0]", "'31'"); - yp_test(".first.Arr[:2][0]", "[11]"); yp_test(".first.Arr[3][:]", "[4, 5, 6, 7, 8, 9]"); + yp_test(".first.Arr[:][:]", "[[11, 12], ['31', '32'], [4, 5, 6, 7, 8, 9]]"); yp_test(".first.Arr[4].k", "'val'"); yp_test(".first.Arr[:][0]", "[11, '31', 4]"); yp_test(".first.Arr[:].k", "['val']"); yp_test(".first.Arr[:][2]", "[6]"); - yp_test(".first.Arr[3][1::2]", "[5, 7, 9]"); - yp_test(".first.Arr[3][::2]", "[4, 6, 8]"); - yp_test(".first.Arr[3][:4:2]", "[4, 6]"); + yp_test(".first.Arr[:][0,1]", "[[11, 12], ['31', '32'], [4, 5]]"); + yp_test(".first.Arr[:][1]", "[12, '32', 5]"); yp_test(".second[2].abc", "null"); - yp_test(".second[0:2].abc", "[&anc [1, 2], [3, 4]]"); yp_test(".second[0].z", "*anc"); + yp_test("&anc", "&anc [1, 2]"); yp_test("&anc[0]", "1"); - - mode = YAML_PATH_FILTER_RETURN_SHALLOW; - yp_test(".first", "{}"); - yp_test(".first.Nop", "0"); - yp_test(".first.Map", "{}"); + yp_test(".first['Nop','Yep']", "{'Nop': 0, 'Yep': '1'}"); + yp_test(".second[0]['abc','def'][0]","{'abc': 1, 'def': 11}"); + yp_test(".second[:]['abc','def'][0]","[{'abc': 1, 'def': 11}, {'abc': 3, 'def': null}]"); + yp_test(".second[:]['abc','def'].z", "[{'abc': null, 'def': null}, {'abc': null, 'def': '!'}]"); + yp_test(".second[:][*].z", "[{'abc': null, 'def': null, 'abcdef': null, 'z': null, 'q': null}, {'abc': null, 'def': '!', 'abcdef': null, 'z': null}]"); + yp_test(".second[:]['abc','q']", "[{'abc': &anc [1, 2], 'q': 'Q'}, {'abc': [3, 4]}]"); + yp_test(".second[:]['abc','def'][:]","[{'abc': &anc [1, 2], 'def': [11, 22]}, {'abc': [3, 4], 'def': null}]"); + yp_test(".second[0]['abc','def']", "{'abc': &anc [1, 2], 'def': [11, 22]}"); + yp_test(".3rd[:].*.*[:]", "[{'a': {'A': [0, 1], 'AA': [2, 3]}, 'b': {'A': [10, 11], 'BB': [9, 8]}}, {'z': {'A': [0, 1], 'BB': [22, 33]}}, &x {'q': null}]"); return test_result; } diff --git a/tests/test-yamlp.sh b/tests/test-yamlp.sh new file mode 100755 index 0000000..d009659 --- /dev/null +++ b/tests/test-yamlp.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# SPDX-License-Identifier: MIT +# Copyright (c) 2020 Red Hat Inc., Durham, North Carolina. + +yamlp_test() +{ + echo "$1:" + echo -n " ($2) " + out=$("${BINARY_DIR:-../build}/yamlp" -F -f "$1" "$2") || return 1 + echo -n "-> $out" + if [ "$out" != "$3" ]; then + echo ": FAILED, expected result: $3" + return 2 + else + echo ": OK" + fi +} + +yamlp_test "${SOURCE_DIR:-..}/res/openshift-logging.yaml" ".spec.pipelines[:].inputSource" "[logs.app, logs.infra, logs.audit]" +res=$((res+$?)) + +yamlp_test "${SOURCE_DIR:-..}/res/openshift-upgradeable.yaml" ".status.conditions[:]['status','type']" \ + '[{status: "False", type: Degraded}, {status: "False", type: Progressing}, {status: "True", type: Available}, {status: "True", type: Upgradeable}]' +res=$((res+$?)) + +exit $res diff --git a/yaml-path.c b/yaml-path.c deleted file mode 100644 index 00611ca..0000000 --- a/yaml-path.c +++ /dev/null @@ -1,588 +0,0 @@ -#include -#include -#include -#include -#include - -#include - -#include "yaml-path.h" - - -#define YAML_PATH_MAX_SECTION_LEN 1024 -#define YAML_PATH_MAX_SECTIONS 255 -#define YAML_PATH_MAX_LEN YAML_PATH_MAX_SECTION_LEN * YAML_PATH_MAX_SECTIONS - - -typedef enum yaml_path_section_type { - YAML_PATH_SECTION_ROOT, - YAML_PATH_SECTION_ANCHOR, - YAML_PATH_SECTION_KEY, - YAML_PATH_SECTION_INDEX, - YAML_PATH_SECTION_SLICE, -} yaml_path_section_type_t; - -typedef struct yaml_path_section { - yaml_path_section_type_t type; - int level; - union { - const char *key; - const char *anchor; - int index; - struct {int start, end, stride;} slice; - } data; - TAILQ_ENTRY(yaml_path_section) entries; - - yaml_node_type_t node_type; - int counter; - bool valid; - bool next_valid; -} yaml_path_section_t; - -typedef TAILQ_HEAD(path_section_list, yaml_path_section) path_section_list_t; - -typedef struct yaml_path { - path_section_list_t sections_list; - size_t sections_count; - size_t sequence_level; - - size_t current_level; - size_t start_level; - - yaml_path_error_t error; -} yaml_path_t; - - -static void -yaml_path_error_set (yaml_path_t *path, yaml_path_error_type_t error_type, const char *message, size_t pos) -{ - assert(path != NULL); - path->error.type = error_type; - path->error.message = message; - path->error.pos = pos; -} - -static void -yaml_path_sections_remove (yaml_path_t *path) -{ - assert(path != NULL); - while (!TAILQ_EMPTY(&path->sections_list)) { - yaml_path_section_t *el = TAILQ_FIRST(&path->sections_list); - TAILQ_REMOVE(&path->sections_list, el, entries); - path->sections_count--; - switch (el->type) { - case YAML_PATH_SECTION_KEY: - free((void *)el->data.key); - break; - case YAML_PATH_SECTION_ANCHOR: - free((void *)el->data.anchor); - break; - case YAML_PATH_SECTION_SLICE: - if (path->sequence_level == el->level) - path->sequence_level = 0; - break; - default: - break; - } - free(el); - } -} - -static yaml_path_section_t* -yaml_path_section_create (yaml_path_t *path, yaml_path_section_type_t section_type) -{ - yaml_path_section_t *el = malloc(sizeof(*el)); - assert(el != NULL); - memset(el, 0, sizeof(*el)); - path->sections_count++; - el->level = path->sections_count; - el->type = section_type; - el->node_type = YAML_SCALAR_NODE; - TAILQ_INSERT_TAIL(&path->sections_list, el, entries); - if (el->type == YAML_PATH_SECTION_SLICE && !path->sequence_level) { - path->sequence_level = el->level; - } - return el; -} - -static size_t -yaml_path_section_snprint (yaml_path_section_t *section, char *s, size_t max_len) -{ - assert(section != NULL); - if (s == NULL) - return -1; - size_t len = 0; - switch (section->type) { - case YAML_PATH_SECTION_ROOT: - len = snprintf(s, max_len, "$"); - break; - case YAML_PATH_SECTION_KEY: - len = snprintf(s, max_len, ".%s", section->data.key); - break; - case YAML_PATH_SECTION_ANCHOR: - len = snprintf(s, max_len, "&%s", section->data.anchor); - break; - case YAML_PATH_SECTION_INDEX: - len = snprintf(s, max_len, "[%d]", section->data.index); - break; - case YAML_PATH_SECTION_SLICE: - len = snprintf(s, max_len, "[%d:%d:%d]", section->data.slice.start, section->data.slice.end, section->data.slice.stride); - break; - default: - len = snprintf(s, max_len, ""); - break; - } - return len; -} - -static void -_parse (yaml_path_t *path, char *s_path) { - char *sp = s_path; - char *spe = NULL; - - assert(path != NULL); - - if (s_path == NULL || !s_path[0]) { - yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Path is empty", 0); - return; - } - - while (*sp != '\0') { - switch (*sp) { - case '.': - case '[': - if (path->sections_count == 0) { - yaml_path_section_create(path, YAML_PATH_SECTION_ROOT); - } - if (*sp == '.') { - // Key - spe = sp + 1; - while (*spe != '.' && *spe != '[' && *spe != '\0') - spe++; - if (spe == sp+1) { - yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment key is missing", sp - s_path); - goto error; - } - yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_KEY); - sec->data.key = strndup(sp + 1, spe-sp - 1); - sp = spe-1; - } else if (*sp == '[') { - spe = sp+1; - if (*spe == '\'') { - // Key - sp = spe; - spe++; - while (*spe != '\'' && *spe != '\0') - spe++; - if (spe == sp+1) { - yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment key is missing", sp - s_path); - goto error; - } - if (*spe == '\0') { - yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment key is invalid (unxepected end of string, missing ''')", sp - s_path); - goto error; - } - spe++; - if (*spe == '\0') { - yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment key is invalid (unxepected end of string, missing ']')", sp - s_path); - goto error; - } - if (*spe != ']') { - yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment key is invalid (invalid character)", spe - s_path); - goto error; - } - yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_KEY); - sec->data.key = strndup(sp + 1, spe-sp - 2); - sp = spe; - } else { - // Index or Slice - int idx = strtol(spe, &spe, 10); - if (*spe == ']') { - // Index - yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_INDEX); - sec->data.index = idx; - sp = spe; - } else if (*spe == ':') { - // Slice - int idx_start = idx; - sp = spe++; - idx = strtol(spe, &spe, 10); - if (*spe == ':') { - int idx_end = (spe == sp+1 ? __INT_MAX__ : idx); - sp = spe++; - idx = strtol(spe, &spe, 10); - if (*spe == ']' && (idx > 0 || spe == sp+1)) { - yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_SLICE); - sec->data.slice.start = idx_start; - sec->data.slice.end = idx_end; - sec->data.slice.stride = idx > 0 ? idx : 1; - sp = spe; - } else if (*spe == ']' && idx <= 0) { - yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment slice stride can not be less than 1", spe - s_path - 1); - goto error; - } else { - yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment slice stride is invalid (invalid character)", spe - s_path); - goto error; - } - } else if (*spe == ']') { - yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_SLICE); - sec->data.slice.start = idx_start; - sec->data.slice.end = (spe == sp+1 ? __INT_MAX__ : idx); - sec->data.slice.stride = 1; - sp = spe; - } else { - yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment slice end index is invalid (invalid character)", spe - s_path); - goto error; - } - } else if (*spe == '\0') { - yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment index is invalid (unxepected end of string, missing ']')", spe - s_path); - goto error; - } else { - yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment index is invalid (invalid character)", spe - s_path); - goto error; - } - } - } - break; - default: - if (path->sections_count == 0) { - spe = sp + 1; - if (*sp == '$' && (*spe == '.' || *spe == '[' || *spe == '\0')) { - yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_ROOT); - } else if (*sp == '&') { - // Anchor - sp++; - while (*spe != '.' && *spe != '[' && *spe != '\0') - spe++; - yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_ANCHOR); - sec->data.anchor = strndup(sp, spe-sp); - } else { - // Key - while (*spe != '.' && *spe != '[' && *spe != '\0') - spe++; - yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_ROOT); - sec = yaml_path_section_create(path, YAML_PATH_SECTION_KEY); - sec->data.key = strndup(sp, spe-sp); - } - sp = spe-1; - } - break; - } - sp++; - } - - if (path->sections_count == 0) { - yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Invalid path segments", 0); - } - - return; - -error: - yaml_path_sections_remove(path); -} - -static yaml_path_section_t* -yaml_path_section_get_at_level (yaml_path_t *path, size_t level) -{ - assert(path != NULL); - yaml_path_section_t *el; - TAILQ_FOREACH(el, &path->sections_list, entries) { - if (el->level == level) - return el; - } - return NULL; -} - -static yaml_path_section_t* -yaml_path_section_get_first (yaml_path_t *path) -{ - assert(path != NULL); - return yaml_path_section_get_at_level(path, 1); -} - -static yaml_path_section_t* -yaml_path_section_get_current (yaml_path_t *path) -{ - assert(path != NULL); - if (!path->start_level) - return NULL; - return yaml_path_section_get_at_level(path, path->current_level - path->start_level + 1); -} - -static bool -yaml_path_sections_prev_are_valid (yaml_path_t *path) -{ - assert(path != NULL); - int valid = true; - yaml_path_section_t *el; - TAILQ_FOREACH(el, &path->sections_list, entries) { - if (el->level < path->current_level - path->start_level + 1) - valid = el->valid && valid; - } - return valid; -} - -static bool -yaml_path_section_current_is_last (yaml_path_t *path) -{ - assert(path != NULL); - yaml_path_section_t *sec = yaml_path_section_get_current(path); - if (sec == NULL) - return false; - return sec->level == path->sections_count; -} - -static bool -yaml_path_section_current_is_mandatory_sequence (yaml_path_t *path) -{ - assert(path != NULL); - yaml_path_section_t *sec = yaml_path_section_get_current(path); - if (sec == NULL) - return false; - return (sec->type == YAML_PATH_SECTION_SLICE && sec->level == path->sequence_level); -} - -static bool -yaml_path_is_valid (yaml_path_t *path) -{ - assert(path != NULL); - bool valid = true; - yaml_path_section_t *el; - TAILQ_FOREACH(el, &path->sections_list, entries) { - valid = el->valid && valid; - } - return valid; -} - - -/* Public */ - -yaml_path_t* -yaml_path_create (void) -{ - yaml_path_t *ypath = malloc(sizeof(*ypath)); - - assert(ypath != NULL); - memset (ypath, 0, sizeof(*ypath)); - TAILQ_INIT(&ypath->sections_list); - - return ypath; -} - -int -yaml_path_parse (yaml_path_t *path, char *s_path) -{ - if (path == NULL) - return -1; - - yaml_path_sections_remove(path); - memset(&path->error, 0, sizeof(path->error)); - - _parse(path, s_path); - - if (path->sections_count == 0) - return -2; - - return 0; -} - -void -yaml_path_destroy (yaml_path_t *path) -{ - if (path == NULL) - return; - yaml_path_sections_remove(path); - free(path); -} - -/* API */ - -const yaml_path_error_t* -yaml_path_error_get (yaml_path_t *path) -{ - if (path == NULL) - return NULL; - return &path->error; -} - -size_t -yaml_path_snprint (yaml_path_t *path, char *s, size_t max_len) -{ - if (s == NULL) - return -1; - if (path == NULL) - return 0; - - size_t len = 0; - yaml_path_section_t *el; - TAILQ_FOREACH(el, &path->sections_list, entries) { - len += yaml_path_section_snprint(el, s + (len < max_len ? len : max_len), max_len - (len < max_len ? len : max_len)); - } - return len; -} - -int -yaml_path_filter_event (yaml_path_t *path, yaml_parser_t *parser, yaml_event_t *event, yaml_path_filter_mode_t mode) -{ - int res = 0; - const char *anchor = NULL; - - if (path == NULL || parser == NULL || event == NULL) - goto exit; - - switch(event->type) { - case YAML_MAPPING_START_EVENT: - anchor = (const char *)event->data.mapping_start.anchor; - break; - case YAML_SEQUENCE_START_EVENT: - anchor = (const char *)event->data.sequence_start.anchor; - break; - case YAML_SCALAR_EVENT: - anchor = (const char *)event->data.scalar.anchor; - break; - default: - break; - } - - if (!path->start_level) { - switch (yaml_path_section_get_first(path)->type) { - case YAML_PATH_SECTION_ROOT: - if (event->type == YAML_DOCUMENT_START_EVENT) { - path->start_level = 1; - yaml_path_section_get_first(path)->valid = true; - } - break; - case YAML_PATH_SECTION_ANCHOR: - if (anchor != NULL) { - if (!strcmp(yaml_path_section_get_first(path)->data.anchor, anchor)) { - path->start_level = path->current_level; - if (yaml_path_section_get_current(path)) - yaml_path_section_get_current(path)->node_type = YAML_SCALAR_NODE; - } - } - break; - default: - //TODO: This path is invalid - break; - } - } else { - //TODO: ? - } - - yaml_path_section_t *current_section = yaml_path_section_get_current(path); - if (!current_section) { - } else { - switch (event->type) { - case YAML_DOCUMENT_START_EVENT: - case YAML_MAPPING_START_EVENT: - case YAML_SEQUENCE_START_EVENT: - case YAML_ALIAS_EVENT: - case YAML_SCALAR_EVENT: - switch (current_section->node_type) { - case YAML_SCALAR_NODE: - current_section->valid = true; - break; - case YAML_MAPPING_NODE: - if (current_section->type == YAML_PATH_SECTION_KEY) { - if (current_section->counter % 2) { - current_section->valid = current_section->next_valid; - current_section->next_valid = false; - } else { - current_section->next_valid = !strcmp(current_section->data.key, (const char *)event->data.scalar.value); - current_section->valid = false; - } - } else { - current_section->valid = false; - } - break; - case YAML_SEQUENCE_NODE: - if (current_section->type == YAML_PATH_SECTION_INDEX) { - current_section->valid = current_section->data.index == current_section->counter; - } else if (current_section->type == YAML_PATH_SECTION_SLICE) { - current_section->valid = current_section->data.slice.start <= current_section->counter && - current_section->data.slice.end > current_section->counter && - (current_section->data.slice.start + current_section->counter) % current_section->data.slice.stride == 0; - } else { - current_section->valid = false; - } - break; - default: - break; - } - current_section->counter++; - default: - break; - } - } - - switch (event->type) { - case YAML_STREAM_START_EVENT: - case YAML_STREAM_END_EVENT: - case YAML_NO_EVENT: - res = 1; - break; - case YAML_DOCUMENT_START_EVENT: - if (path->start_level == 1) - path->current_level++; - res = 1; - break; - case YAML_DOCUMENT_END_EVENT: - if (path->start_level == 1) - path->current_level--; - res = 1; - break; - case YAML_MAPPING_START_EVENT: - case YAML_SEQUENCE_START_EVENT: - if (current_section) { - if (yaml_path_section_current_is_last(path)) - res = yaml_path_is_valid(path); - } else { - if (path->current_level > path->start_level) { - if (mode == YAML_PATH_FILTER_RETURN_ALL) - res = yaml_path_is_valid(path); - } - } - path->current_level++; - current_section = yaml_path_section_get_current(path); - if (current_section && yaml_path_section_current_is_mandatory_sequence(path)) { - res = yaml_path_sections_prev_are_valid(path); - } - if (current_section) { - current_section->node_type = event->type == YAML_MAPPING_START_EVENT ? YAML_MAPPING_NODE : YAML_SEQUENCE_NODE; - current_section->counter = 0; - } - break; - case YAML_MAPPING_END_EVENT: - case YAML_SEQUENCE_END_EVENT: - if (current_section) { - if (yaml_path_section_current_is_mandatory_sequence(path)) - res = yaml_path_sections_prev_are_valid(path); - } - path->current_level--; - current_section = yaml_path_section_get_current(path); - if (current_section) { - if (yaml_path_section_current_is_last(path)) - res = yaml_path_is_valid(path); - } else { - if (path->current_level > path->start_level) { - if (mode == YAML_PATH_FILTER_RETURN_ALL) - res = yaml_path_is_valid(path); - } - } - break; - case YAML_ALIAS_EVENT: - case YAML_SCALAR_EVENT: - if (!current_section) { - if ((mode == YAML_PATH_FILTER_RETURN_ALL && path->current_level > path->start_level) || path->current_level == path->start_level) - res = yaml_path_is_valid(path); - } else { - res = yaml_path_is_valid(path) && yaml_path_section_current_is_last(path); - } - break; - default: - break; - } - -exit: - return res; -} diff --git a/yaml.c b/yaml.c deleted file mode 100644 index e2d9007..0000000 --- a/yaml.c +++ /dev/null @@ -1,172 +0,0 @@ -#include -#include -#include -#include - -#include "yaml-path.h" - -#define INDENT " " -#define STRVAL(x) ((x) ? (char*)(x) : "") - -void indent(int level) -{ - int i; - for (i = 0; i < level; i++) { - printf("%s", INDENT); - } -} - -void print_event(yaml_event_t *event) -{ - static int level = 0; - - switch (event->type) { - case YAML_NO_EVENT: - indent(level); - printf("no-event\n"); - break; - case YAML_STREAM_START_EVENT: - indent(level++); - printf("stream-start-event\n"); - break; - case YAML_STREAM_END_EVENT: - indent(--level); - printf("stream-end-event\n"); - break; - case YAML_DOCUMENT_START_EVENT: - indent(level++); - printf("document-start-event\n"); - break; - case YAML_DOCUMENT_END_EVENT: - indent(--level); - printf("document-end-event\n"); - break; - case YAML_ALIAS_EVENT: - indent(level); - printf("alias-event * (anc=\"%s\")\n", STRVAL(event->data.scalar.anchor)); - break; - case YAML_SCALAR_EVENT: - indent(level); - printf("= scalar-event (anc=\"%s\" val=\"%s\", l=%d, t=%s, pl_impl=%d, q_impl=%d, st=%d)\n", - STRVAL(event->data.scalar.anchor), - STRVAL(event->data.scalar.value), - (int)event->data.scalar.length, - event->data.scalar.tag, - event->data.scalar.plain_implicit, event->data.scalar.quoted_implicit, event->data.scalar.style); - break; - case YAML_SEQUENCE_START_EVENT: - indent(level++); - printf("[ sequence-start-event (anc=\"%s\", t=%s)\n", - STRVAL(event->data.sequence_start.anchor), - event->data.sequence_start.tag); - break; - case YAML_SEQUENCE_END_EVENT: - indent(--level); - printf("] sequence-end-event\n"); - break; - case YAML_MAPPING_START_EVENT: - indent(level++); - printf("{ mapping-start-event\n"); - break; - case YAML_MAPPING_END_EVENT: - indent(--level); - printf("} mapping-end-event\n"); - break; - } - if (level < 0) { - fprintf(stderr, "indentation underflow!\n"); - level = 0; - } -} - -int yaml_parser_parse_and_filter (yaml_parser_t *parser, yaml_event_t *event, yaml_path_t *path) -{ - int valid_event = 0; - int res; - do { - res = yaml_parser_parse(parser, event); - if (res) { - printf("=====> "); - print_event(event); - if (!yaml_path_filter_event(path, parser, event, YAML_PATH_FILTER_RETURN_ALL)) { - yaml_event_delete(event); - } else { - printf("+------------------------------------------------------------------------------------> "); - print_event(event); - valid_event = 1; - } - } else { - break; - } - } while (!valid_event && res); - - return res; -} - -int main(int argc, char *argv[]) -{ - yaml_parser_t parser; - yaml_event_t event; - yaml_event_type_t event_type; - - yaml_path_t *yp = yaml_path_create(); - //yaml_path_parse(yp, ".fruit.Oop[1]"); - //yaml_path_parse(yp, ".first.Arr[:2][0]"); //.Arr[2][0] - //yaml_path_parse(yp, ".first.Arr[3][:]"); - //yaml_path_parse(yp, ".first.Map"); - //yaml_path_parse(yp, ".first.Arr[:].k"); - //yaml_path_parse(yp, ".first.Arr[:][2]"); - //yaml_path_parse(yp, ".metadata.name"); - //yaml_path_parse(yp, ".spec.outputs[0:2].name"); - //yaml_path_parse(yp, ".second[0].abc"); - //yaml_path_parse(yp, "&anc[0]"); - yaml_path_parse(yp, ".first.Map"); - - //const char *yaml = "2"; - //const char *yaml = "{'el': {'Z': &anc [{'key': 0}, {'item': 1}]}, first: {'Map': &anc {1: '1'}, 'Nop': 'b', 'Yep': '2', 'Arr': [[11,12],2,[31,32],[4, 5, 6],{'k': 1, 0: 0}]}}"; - const char *yaml = - "{" - "first: {" - "'Map': {1: '1'}," - "'Nop': 0," - "'Yep': '1'," - "'Arr': [" - "[11, 12]," - "2," - "['31', '32']," - "[4, 5, 6, 7, 8, 9]," - "{'k': 'val', 0: 0}" - "]" - "}," - "second: [" - "{'abc': &anc [1, 2], 'abcdef': 2, 'z': *anc}," - "{'abc': [3, 4], 'abcdef': 4, 'z': 'zzz'}" - "]" - "}"; - - char ypath[255] = {0}; - yaml_path_snprint (yp, ypath, 255); - printf("%s\n", yaml); - printf("%s\n\n", ypath); - - yaml_parser_initialize(&parser); - //yaml_parser_set_input_file(&parser, fopen("../openshift-logging-1.yaml", "r")); - yaml_parser_set_input_string(&parser, (const unsigned char*)yaml, strlen(yaml)); - - do { - if (!yaml_parser_parse_and_filter(&parser, &event, yp)) - goto error; - event_type = event.type; - yaml_event_delete(&event); - } while (event_type != YAML_STREAM_END_EVENT); - - yaml_path_destroy(yp); - yaml_parser_delete(&parser); - return 0; - -error: - yaml_path_destroy(yp); - fprintf(stderr, "Failed to parse: %s\n", parser.problem); - yaml_parser_delete(&parser); - return 1; -}