diff -r -N -U3 a/yaml-filter/CMakeLists.txt b/yaml-filter/CMakeLists.txt --- a/yaml-filter/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 +++ b/yaml-filter/CMakeLists.txt 2020-02-25 13:34:48.946813539 +0100 @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 2.8) + +include(FindPkgConfig) +pkg_check_modules(YAML yaml-0.1) + +include_directories(${YAML_INCLUDE_DIRS}) + +add_library(yaml-path yaml-path.c) +target_link_libraries(yaml-path ${YAML_LIBRARIES}) + +add_executable(ya yaml.c) +target_link_libraries(ya yaml-path) + +add_executable(yamlp yamlp.c) +target_link_libraries(yamlp 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_executable(test-paths test-paths.c) +target_link_libraries(test-paths yaml-path) + +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 -r -N -U3 a/yaml-filter/.git b/yaml-filter/.git --- a/yaml-filter/.git 1970-01-01 01:00:00.000000000 +0100 +++ b/yaml-filter/.git 2020-02-25 13:34:48.468803146 +0100 @@ -0,0 +1 @@ +gitdir: ../.git/modules/yaml-filter diff -r -N -U3 a/yaml-filter/.gitignore b/yaml-filter/.gitignore --- a/yaml-filter/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ b/yaml-filter/.gitignore 2020-02-25 13:34:48.946813539 +0100 @@ -0,0 +1,54 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +build \ No newline at end of file diff -r -N -U3 a/yaml-filter/LICENSE b/yaml-filter/LICENSE --- a/yaml-filter/LICENSE 1970-01-01 01:00:00.000000000 +0100 +++ b/yaml-filter/LICENSE 2020-02-25 13:34:48.946813539 +0100 @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 OpenSCAP + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff -r -N -U3 a/yaml-filter/openshift-logging.yaml b/yaml-filter/openshift-logging.yaml --- a/yaml-filter/openshift-logging.yaml 1970-01-01 01:00:00.000000000 +0100 +++ b/yaml-filter/openshift-logging.yaml 2020-02-25 13:34:48.946813539 +0100 @@ -0,0 +1,36 @@ +apiVersion: "logging.openshift.io/v1alpha1" +kind: "LogForwarding" +metadata: + name: instance + namespace: openshift-logging +spec: + disableDefaultForwarding: true + outputs: + - type: "elasticsearch" + name: elasticsearch + endpoint: elasticsearch.openshift-logging.svc:9200 + secret: + name: fluentd + - type: "elasticsearch" + name: elasticsearch-insecure + endpoint: elasticsearch-insecure.svc.messaging.cluster.local + insecure: true + - type: "forward" + name: secureforward-offcluster + endpoint: https://secureforward.offcluster.com:9200 + secret: + name: secureforward + pipelines: + - name: container-logs + inputSource: logs.app + outputRefs: + - elasticsearch + - secureforward-offcluster + - name: infra-logs + inputSource: logs.infra + outputRefs: + - elasticsearch-insecure + - name: audit-logs + inputSource: logs.audit + outputRefs: + - secureforward-offcluster diff -r -N -U3 a/yaml-filter/README.md b/yaml-filter/README.md --- a/yaml-filter/README.md 1970-01-01 01:00:00.000000000 +0100 +++ b/yaml-filter/README.md 2020-02-25 13:34:48.946813539 +0100 @@ -0,0 +1,3 @@ +# YAML Path filter + +YAML document filtering for libyaml diff -r -N -U3 a/yaml-filter/test-paths.c b/yaml-filter/test-paths.c --- a/yaml-filter/test-paths.c 1970-01-01 01:00:00.000000000 +0100 +++ b/yaml-filter/test-paths.c 2020-02-25 13:34:48.947813560 +0100 @@ -0,0 +1,230 @@ +#include +#include +#include +#include + +#include "yaml-path.h" + +static void +rstrip (char *s) +{ + // This will strip whitespace and explicit document ending + // to cope with emitter behavior for libyaml < 0.2 + char *pos = s + strlen(s) - 1; + while (pos > s && (isspace((unsigned char)*pos) || *pos == '.')) { + *pos = 0; + pos--; + } +} + +static const char* +yp_event_name (yaml_event_type_t event_type) +{ + switch (event_type) { + case YAML_NO_EVENT: + return "no-event"; + case YAML_STREAM_START_EVENT: + return "stream-start-event"; + case YAML_STREAM_END_EVENT: + return "stream-end-event"; + case YAML_DOCUMENT_START_EVENT: + return "document-start-event"; + case YAML_DOCUMENT_END_EVENT: + return "document-end-event"; + case YAML_ALIAS_EVENT: + return "alias-event"; + case YAML_SCALAR_EVENT: + return "scalar-event"; + case YAML_SEQUENCE_START_EVENT: + return "sequence-start-event"; + case YAML_SEQUENCE_END_EVENT: + return "sequence-end-event"; + case YAML_MAPPING_START_EVENT: + return "mapping-start-event"; + case YAML_MAPPING_END_EVENT: + return "mapping-end-event"; + default: + return "unknown-event"; + } +} + + +static char* +yaml; + +#define YAML_STRING_LEN 2048 +static char +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 +yp_run (char *path) +{ + yaml_parser_t parser; + yaml_emitter_t emitter; + int res = 0; + + yaml_path_t *yp = yaml_path_create(); + yaml_path_parse(yp, path); + + yaml_emitter_initialize(&emitter); + yaml_parser_initialize(&parser); + + yaml_parser_set_input_string(&parser, (const unsigned char *)yaml, strlen(yaml)); + memset(yaml_out, 0, YAML_STRING_LEN); + yaml_emitter_set_output_string(&emitter, (unsigned char *)yaml_out, YAML_STRING_LEN, &yaml_out_len); + yaml_emitter_set_width(&emitter, -1); + + yaml_event_t event; + yaml_event_type_t event_type; + do { + if (!yaml_parser_parse(&parser, &event)) { + switch (parser.error) { + case YAML_MEMORY_ERROR: + printf("Memory error: Not enough memory for parsing\n"); + break; + case YAML_READER_ERROR: + if (parser.problem_value != -1) { + printf("Reader error: %s: #%X at %ld\n", parser.problem, parser.problem_value, (long)parser.problem_offset); + } else { + printf("Reader error: %s at %ld\n", parser.problem, (long)parser.problem_offset); + } + break; + case YAML_SCANNER_ERROR: + if (parser.context) { + printf("Scanner error: %s at line %d, column %d\n%s at line %d, column %d\n", parser.context, (int)parser.context_mark.line+1, (int)parser.context_mark.column+1, parser.problem, (int)parser.problem_mark.line+1, (int)parser.problem_mark.column+1); + } else { + printf("Scanner error: %s at line %d, column %d\n", parser.problem, (int)parser.problem_mark.line+1, (int)parser.problem_mark.column+1); + } + break; + case YAML_PARSER_ERROR: + if (parser.context) { + printf("Parser error: %s at line %d, column %d\n%s at line %d, column %d\n", parser.context, (int)parser.context_mark.line+1, (int)parser.context_mark.column+1, parser.problem, (int)parser.problem_mark.line+1, (int)parser.problem_mark.column+1); + } else { + printf("Parser error: %s at line %d, column %d\n", parser.problem, (int)parser.problem_mark.line+1, (int)parser.problem_mark.column+1); + } + break; + default: + printf("Internal error\n"); + break; + } + res = 1; + goto error; + } else { + event_type = event.type; + if (!yaml_path_filter_event(yp, &parser, &event, mode)) { + yaml_event_delete(&event); + } else { + if (!yaml_emitter_emit(&emitter, &event)) { + printf("Error after '%s'\n", yp_event_name(event.type)); + switch (emitter.error) + { + case YAML_MEMORY_ERROR: + printf("Memory error: Not enough memory for emitting\n"); + break; + case YAML_WRITER_ERROR: + printf("Writer error: %s\n", emitter.problem); + break; + case YAML_EMITTER_ERROR: + printf("Emitter error: %s\n", emitter.problem); + break; + default: + printf("Internal error\n"); + break; + } + res = 2; + goto error; + } + } + } + } while (event_type != YAML_STREAM_END_EVENT); + +error: + yaml_parser_delete(&parser); + yaml_emitter_delete(&emitter); + + yaml_path_destroy(yp); + + return res; +} + +#define ASCII_ERR "\033[0;33m" +#define ASCII_RST "\033[0;0m" + +void +yp_test (char *path, char *yaml_exp) +{ + printf("%s ", path); + if (!yp_run(path)) { + rstrip(yaml_out); + if (!strcmp(yaml_exp, yaml_out)) { + printf("(%s): OK\n", yaml_exp); + return; + } + printf(ASCII_ERR"(%s != %s)"ASCII_RST": FAILED\n", yaml_exp, yaml_out); + } + test_result++; +} + + +int main (int argc, char *argv[]) +{ + 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'}" + "]" + "}"; + + // Path Expected filtered YAML result + + mode = YAML_PATH_FILTER_RETURN_ALL; + 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.Map", "{1: '1'}"); + yp_test(".first.Nop", "0"); + yp_test(".first.Arr", "[[11, 12], 2, ['31', '32'], [4, 5, 6, 7, 8, 9], {'k': 'val', 0: 0}]"); + 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[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(".second[0].abc", "&anc [1, 2]"); + yp_test(".second[0:2].abc", "[&anc [1, 2], [3, 4]]"); + yp_test(".second[0].z", "*anc"); + + mode = YAML_PATH_FILTER_RETURN_SHALLOW; + yp_test(".first", "{}"); + yp_test(".first.Nop", "0"); + yp_test(".first.Map", "{}"); + + return test_result; +} diff -r -N -U3 a/yaml-filter/test-path-segments.c b/yaml-filter/test-path-segments.c --- a/yaml-filter/test-path-segments.c 1970-01-01 01:00:00.000000000 +0100 +++ b/yaml-filter/test-path-segments.c 2020-02-25 13:34:48.947813560 +0100 @@ -0,0 +1,97 @@ +#include +#include +#include + +#include "yaml-path.h" + + +#define PATH_STRING_LEN 1024 + +static int +test_result = 0; + +static char +yp_s[PATH_STRING_LEN] = {0}; + + +#define ASCII_ERR "\033[0;33m" +#define ASCII_RST "\033[0;0m" + +void +yp_test (char *p, int expected_failure) +{ + yaml_path_t *yp = yaml_path_create(); + printf("%s", p); + if (!yaml_path_parse(yp, p)) { + yaml_path_snprint(yp, yp_s, PATH_STRING_LEN); + if (expected_failure) { + printf(ASCII_ERR); + test_result++; + } + printf(" -> %s: %s\n", yp_s, expected_failure ? ASCII_RST"FAILED" : "OK"); + } else { + const yaml_path_error_t *ype = yaml_path_error_get(yp); + if (!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"); + } + yaml_path_destroy(yp); +} + +#define yp_test_good(p) yp_test(p, 0) +#define yp_test_invalid(p) yp_test(p, 1) + + +int main (int argc, char *argv[]) +{ + yp_test_good(".first"); + yp_test_good(".first[0]"); + yp_test_good(".first.second[0].third"); + yp_test_good(".first.0"); + yp_test_good("$.jsonpath.something"); + yp_test_good("unprefixed.key[0]"); + yp_test_good("$[0]"); + yp_test_good("[0]"); + yp_test_good("0"); + 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("el[&anchor]"); + yp_test_good("el[&anchor].key"); + yp_test_good("el[&anchor][100]"); + + yp_test_good("el['key']"); + yp_test_good("el['key'].other[0]['key']"); + + yp_test_invalid(""); + yp_test_invalid("."); + yp_test_invalid("$."); + yp_test_invalid("element["); + + yp_test_invalid("[0:0:0]"); + yp_test_invalid("[::-1]"); + yp_test_invalid("[0.key[0]"); + + yp_test_invalid("el[&]"); + yp_test_invalid("el[&"); + yp_test_invalid("el[&wrong."); + + yp_test_invalid("el[']"); + yp_test_invalid("el['key].wrong"); + yp_test_invalid("el['key.wrong"); + yp_test_invalid("el['key'"); + + return test_result; +} diff -r -N -U3 a/yaml-filter/test-yamlp.sh b/yaml-filter/test-yamlp.sh --- a/yaml-filter/test-yamlp.sh 1970-01-01 01:00:00.000000000 +0100 +++ b/yaml-filter/test-yamlp.sh 2020-02-25 13:34:48.947813560 +0100 @@ -0,0 +1,19 @@ +#!/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 -r -N -U3 a/yaml-filter/yaml.c b/yaml-filter/yaml.c --- a/yaml-filter/yaml.c 1970-01-01 01:00:00.000000000 +0100 +++ b/yaml-filter/yaml.c 2020-02-25 13:34:48.947813560 +0100 @@ -0,0 +1,142 @@ +#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 &\n"); + break; + case YAML_SCALAR_EVENT: + indent(level); + printf("= scalar-event (val=\"%s\", l=%d, t=%s, pl_impl=%d, q_impl=%d, st=%d)\n", + 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 (t=%s)\n", + 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"); + //yaml_path_parse(yp, ".first.Arr[:].k"); + yaml_path_parse(yp, ".first.Arr[:][2]"); + + //const char *yaml = "2"; + const char *yaml = "{first: {'Map': {1: '1'}, 'Nop': 'b', 'Yep': '2', 'Arr': [[11,12],2,[31,32],[4, 5, 6],{'k': 1, 0: 0}]}}"; + printf("%s\n\n", yaml); + + yaml_parser_initialize(&parser); + //yaml_parser_set_input_file(&parser, stdin); + 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; +} diff -r -N -U3 a/yaml-filter/yaml-path.c b/yaml-filter/yaml-path.c --- a/yaml-filter/yaml-path.c 1970-01-01 01:00:00.000000000 +0100 +++ b/yaml-filter/yaml-path.c 2020-02-25 13:34:48.947813560 +0100 @@ -0,0 +1,568 @@ +#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_KEY, + YAML_PATH_SECTION_INDEX, + YAML_PATH_SECTION_SLICE, + YAML_PATH_SECTION_ANCHOR, +} 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; + int valid; + int 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 current_level; + size_t sequence_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) +{ + if (path == NULL) + return; + path->error.type = error_type; + path->error.message = message; + path->error.pos = pos; +} + +static void +yaml_path_sections_remove (yaml_path_t *path) +{ + if (path == NULL) + return; + 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)); + path->sections_count++; + el->level = path->sections_count; + el->type = section_type; + 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) +{ + if (s == NULL) + return -1; + if (section == NULL) + return 0; + + size_t len = 0; + switch (section->type) { + 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; + + 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 (*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 == '&') { + // Anchor + sp = spe; + while (*spe != ']' && *spe != '\0') + spe++; + if (spe == sp+1) { + yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment anchor is missing", sp - s_path); + goto error; + } + if (*spe == '\0') { + yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment anchor is invalid (unxepected end of string, missing ']')", sp - s_path); + goto error; + } + yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_ANCHOR); + sec->data.anchor = strndup(sp + 1, spe-sp - 1); + sp = spe; + } else 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) { + // Key + spe = sp + 1; + if (*sp == '$' && (*spe == '.' || *spe == '[')) { + // Ignore leading '$' + // TODO: Should we do this? + } else { + while (*spe != '.' && *spe != '[' && *spe != '\0') + spe++; + yaml_path_section_t *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) +{ + if (path == NULL) + return 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_last (yaml_path_t *path) +{ + if (path == NULL) + return NULL; + return yaml_path_section_get_at_level(path, path->sections_count); +} + +static yaml_path_section_t* +yaml_path_section_get_current (yaml_path_t *path) +{ + if (path == NULL) + return NULL; + return yaml_path_section_get_at_level(path, path->current_level); +} + +static int +yaml_path_prev_section_is_valid (yaml_path_t *path) +{ + if (path == NULL) + return 0; + yaml_path_section_t *sec = yaml_path_section_get_at_level(path, path->current_level-1); + if (sec == NULL) + return -1; + return sec->valid; +} + +static int +yaml_path_prev_sections_are_valid (yaml_path_t *path) +{ + if (path == NULL) + return 0; + int valid = 1; + yaml_path_section_t *el; + TAILQ_FOREACH(el, &path->sections_list, entries) { + if (el->level < path->current_level) + valid = el->valid && valid; + } + return valid; +} + +static int +yaml_path_all_sections_are_valid (yaml_path_t *path) +{ + if (path == NULL) + return 0; + int valid = 1; + yaml_path_section_t *el; + TAILQ_FOREACH(el, &path->sections_list, entries) { + valid = el->valid && valid; + } + return valid; +} + +static int +yaml_path_section_current_is_last (yaml_path_t *path) +{ + if (path == NULL) + return 0; + return path->current_level == path->sections_count; +} + +static int +yaml_path_section_current_is_mandatory_sequence (yaml_path_t *path) +{ + if (path == NULL) + return 0; + yaml_path_section_t *sec = yaml_path_section_get_current(path); + if (sec == NULL) + return 0; + return (sec->type == YAML_PATH_SECTION_SLICE && path->current_level == path->sequence_level); +} + +/* Public */ + +yaml_path_t* +yaml_path_create (void) +{ + yaml_path_t *ypath = malloc(sizeof(*ypath)); + + if (ypath != NULL) { + TAILQ_INIT(&ypath->sections_list); + ypath->sections_count = 0; + ypath->sequence_level = 0; + ypath->current_level = 0; + } + + 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; + + if (path == NULL || parser == NULL || event == NULL) + goto exit; + + const char *anchor = NULL; + 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; + } + + yaml_path_section_t *current_section = yaml_path_section_get_current(path); + if (current_section) { + switch (event->type) { + 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_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 = 0; + } else { + current_section->next_valid = !strcmp(current_section->data.key, (const char *)event->data.scalar.value); + current_section->valid = 0; + } + } else if (current_section->type == YAML_PATH_SECTION_ANCHOR && anchor != NULL) { + current_section->valid = !strcmp(current_section->data.key, anchor); + } else { + current_section->valid = 0; + } + 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 if (current_section->type == YAML_PATH_SECTION_ANCHOR && anchor != NULL) { + current_section->valid = !strcmp(current_section->data.key, anchor); + } else { + current_section->valid = 0; + } + break; + default: + break; + } + current_section->counter++; + default: + break; + } + //TODO: DEBUG printf("iv: %d, t: %d, nt: %d, lev: %d\n", current_section->valid, current_section->type, current_section->node_type, current_section->level); + } + + switch (event->type) { + case YAML_STREAM_START_EVENT: + case YAML_STREAM_END_EVENT: + case YAML_DOCUMENT_START_EVENT: + case YAML_DOCUMENT_END_EVENT: + case YAML_NO_EVENT: + res = 1; + break; + case YAML_MAPPING_START_EVENT: + case YAML_SEQUENCE_START_EVENT: + current_section = yaml_path_section_get_current(path); + if (current_section && yaml_path_section_current_is_last(path)) { + res = current_section->valid && yaml_path_prev_section_is_valid(path); + } else { + if (path->current_level > path->sections_count) + if ((!current_section && mode == YAML_PATH_FILTER_RETURN_ALL) || path->current_level == path->sections_count) + res = yaml_path_all_sections_are_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_prev_section_is_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 && yaml_path_section_current_is_mandatory_sequence(path)) { + res = yaml_path_prev_section_is_valid(path); + } + path->current_level--; + if (path->current_level < path->sections_count && (path->current_level != path->sequence_level || !path->sequence_level)) + break; + current_section = yaml_path_section_get_current(path); + if (current_section && yaml_path_section_current_is_last(path)) { + res = current_section->valid && yaml_path_prev_section_is_valid(path); + } else { + if ((!current_section && mode == YAML_PATH_FILTER_RETURN_ALL) || path->current_level == path->sections_count) { + res = yaml_path_all_sections_are_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->sections_count) + res = yaml_path_all_sections_are_valid(path); + } else { + res = current_section->valid && yaml_path_prev_sections_are_valid(path) && yaml_path_section_current_is_last(path); + } + break; + default: + break; + } + +exit: + return res; +} diff -r -N -U3 a/yaml-filter/yaml-path.h b/yaml-filter/yaml-path.h --- a/yaml-filter/yaml-path.h 1970-01-01 01:00:00.000000000 +0100 +++ b/yaml-filter/yaml-path.h 2020-02-25 13:34:48.947813560 +0100 @@ -0,0 +1,46 @@ +#ifndef YAML_PATH_H +#define YAML_PATH_H + +#include + + +typedef struct yaml_path yaml_path_t; + +typedef enum yaml_path_error_type { + YAML_PATH_ERROR_NONE, + YAML_PATH_ERROR_PARSE, +} 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; + + +yaml_path_t* +yaml_path_create (void); + +int +yaml_path_parse (yaml_path_t *path, char *s_path); + +void +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); + +size_t +yaml_path_snprint (yaml_path_t *path, char *s, size_t max_len); + +#endif//YAML_PATH_H + diff -r -N -U3 a/yaml-filter/yamlp.c b/yaml-filter/yamlp.c --- a/yaml-filter/yamlp.c 1970-01-01 01:00:00.000000000 +0100 +++ b/yaml-filter/yamlp.c 2020-02-25 13:34:48.947813560 +0100 @@ -0,0 +1,207 @@ +#include +#include +#include +#include +#include + +#include + +#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) +{ + yaml_event_t event; + yaml_event_type_t event_type; + + do { + if (!yaml_parser_parse(parser, &event)) { + switch (parser->error) { + case YAML_MEMORY_ERROR: + fprintf(stderr, "Memory error: Not enough memory for parsing\n"); + break; + case YAML_READER_ERROR: + if (parser->problem_value != -1) { + fprintf(stderr, "Reader error: %s: #%X at %ld\n", parser->problem, parser->problem_value, (long)parser->problem_offset); + } else { + fprintf(stderr, "Reader error: %s at %ld\n", parser->problem, (long)parser->problem_offset); + } + break; + case YAML_SCANNER_ERROR: + if (parser->context) { + fprintf(stderr, "Scanner error: %s at line %d, column %d\n%s at line %d, column %d\n", parser->context, + (int)parser->context_mark.line+1,(int)parser->context_mark.column+1, parser->problem, + (int)parser->problem_mark.line+1, (int)parser->problem_mark.column+1); + } else { + fprintf(stderr, "Scanner error: %s at line %d, column %d\n", parser->problem, (int)parser->problem_mark.line+1, (int)parser->problem_mark.column+1); + } + break; + case YAML_PARSER_ERROR: + if (parser->context) { + fprintf(stderr, "Parser error: %s at line %d, column %d\n%s at line %d, column %d\n", parser->context, + (int)parser->context_mark.line+1, (int)parser->context_mark.column+1, parser->problem, + (int)parser->problem_mark.line+1, (int)parser->problem_mark.column+1); + } else { + fprintf(stderr, "Parser error: %s at line %d, column %d\n", parser->problem, (int)parser->problem_mark.line+1, (int)parser->problem_mark.column+1); + } + break; + default: + fprintf(stderr, "Internal error\n"); + break; + } + return 1; + } else { + event_type = event.type; + if (!yaml_path_filter_event(path, parser, &event, mode)) { + yaml_event_delete(&event); + } else { + if (use_flow_style) { + switch (event.type) { + case YAML_SEQUENCE_START_EVENT: + event.data.sequence_start.style = YAML_FLOW_SEQUENCE_STYLE; + break; + case YAML_MAPPING_START_EVENT: + event.data.mapping_start.style = YAML_FLOW_MAPPING_STYLE; + break; + default: + break; + } + } + if (!yaml_emitter_emit(emitter, &event)) { + switch (emitter->error) + { + case YAML_MEMORY_ERROR: + fprintf(stderr, "Memory error: Not enough memory for emitting\n"); + break; + case YAML_WRITER_ERROR: + fprintf(stderr, "Writer error: %s\n", emitter->problem); + break; + case YAML_EMITTER_ERROR: + fprintf(stderr, "Emitter error: %s\n", emitter->problem); + break; + default: + fprintf(stderr, "Internal error\n"); + break; + } + return 2; + } + } + } + } while (event_type != YAML_STREAM_END_EVENT); + + return 0; +} + + +void help (void) +{ + printf("yamlp - filtering utility for YAML documents\n"); + printf("\n"); + printf("Usage: yamlp [-F] [-S] [-W ] [-f ] \n"); + printf(" yamlp -h\n"); + printf("\n"); + printf("The tool will take the input YAML document from or a (-f option),\n"); + printf("and it will then return the portion of the document marked with the given .\n"); + printf("\n"); + printf("Options:\n"); + printf(" -f a filename to get the YAML document from,\n"); + printf(" will be used if omitted;\n"); + printf("\n"); + printf(" -F forced 'flow' style for the output YAML document;\n"); + 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"); +} + +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; + + int opt; + while ((opt = getopt(argc, argv, ":f:W:vhSF")) != -1) { + switch (opt) { + 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) { + fprintf(stderr, "Invalid value for wrap width '%s'\n", optarg); + return 1; + } + break; + case 'f': + file_name = optarg; + break; + case ':': + fprintf(stderr, "Option needs a value\n"); + return 1; + break; + case '?': + fprintf(stderr, "Unknown option '%c'\n", optopt); + return 1; + break; + } + } + + for (; optind < argc; optind++) { + path_string = argv[optind]; + } + + FILE *file = NULL; + if (file_name != NULL) { + file = fopen(file_name, "r"); + if (file == NULL) { + fprintf(stderr, "Unable to open file '%s' (%s)\n", file_name, strerror(errno)); + return 2; + } + } + + if (path_string == NULL || path_string[0] == 0) { + fprintf(stderr, "Empty path\n"); + return 3; + } + + 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); + return 3; + }; + + yaml_parser_t parser; + yaml_emitter_t emitter; + + yaml_parser_initialize(&parser); + yaml_parser_set_input_file(&parser, file != NULL ? file : stdin); + + yaml_emitter_initialize(&emitter); + yaml_emitter_set_output_file(&emitter, stdout); + yaml_emitter_set_width(&emitter, wrap); + + if (parse_and_emit(&parser, &emitter, path, mode, flow)) { + return 4; + } + + yaml_parser_delete(&parser); + yaml_emitter_delete(&emitter); + + yaml_path_destroy(path); + fclose(file); + + return 0; +}