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[<zero or positive number>]`
+
+```python
+$.foo[0]
+== {"bar": True, "first": "First Bar", "second": 2}
+```
+
+
+#### Sequence Indices Set
+`.array[<zero or positive number>,<zero or positive number>,...<zero or positive number>]`
+
+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 <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <assert.h>
+
+#include <yaml.h>
+
+#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 <width>] [-f <file>] <path>\n");
+ printf("Usage: yamlp [-F] [-W <width>] [-f <file>] <path>\n");
printf(" yamlp -h\n");
printf("\n");
printf("The tool will take the input YAML document from <stdin> or a <file> (-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 <stdio.h>
-#include <stdlib.h>
-#include <string.h>
#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 <ctype.h>
#include <stdio.h>
-#include <stdlib.h>
#include <string.h>
#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 <stdlib.h>
-#include <stdbool.h>
-#include <string.h>
-#include <sys/queue.h>
-#include <assert.h>
-
-#include <yaml.h>
-
-#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 <yaml.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#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;
-}