Blob Blame History Raw
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 <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <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 &\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 <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/queue.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_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 <yaml.h>
+
+
+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 <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <yaml.h>
+
+#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 <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");
+	printf("and it will then return the portion of the document marked with the given <path>.\n");
+	printf("\n");
+	printf("Options:\n");
+	printf("  -f	a filename to get the YAML document from,\n");
+	printf("    	<stdin> 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;
+}