diff options
Diffstat (limited to 'tools/re')
-rw-r--r-- | tools/re/CMakeLists.txt | 20 | ||||
-rw-r--r-- | tools/re/main.c | 309 | ||||
-rw-r--r-- | tools/re/yangre.1 | 118 |
3 files changed, 447 insertions, 0 deletions
diff --git a/tools/re/CMakeLists.txt b/tools/re/CMakeLists.txt new file mode 100644 index 0000000..7b6de22 --- /dev/null +++ b/tools/re/CMakeLists.txt @@ -0,0 +1,20 @@ +# yangre + +set(resrc + main.c) + +set(format_sources + ${format_sources} + ${CMAKE_CURRENT_SOURCE_DIR}/*.c + PARENT_SCOPE) + +add_executable(yangre ${resrc} ${compatsrc}) +target_link_libraries(yangre yang) +install(TARGETS yangre DESTINATION ${CMAKE_INSTALL_BINDIR}) +install(FILES ${PROJECT_SOURCE_DIR}/tools/re/yangre.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) +target_include_directories(yangre BEFORE PRIVATE ${PROJECT_BINARY_DIR}) + +if(WIN32) + target_include_directories(yangre PRIVATE ${GETOPT_INCLUDE_DIR}) + target_link_libraries(yangre ${GETOPT_LIBRARY}) +endif() diff --git a/tools/re/main.c b/tools/re/main.c new file mode 100644 index 0000000..2292b2a --- /dev/null +++ b/tools/re/main.c @@ -0,0 +1,309 @@ +/** + * @file main.c + * @author Radek Krejci <rkrejci@cesnet.cz> + * @brief libyang's YANG Regular Expression tool + * + * Copyright (c) 2017 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE /* asprintf, strdup */ + +#include <errno.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include "libyang.h" + +#include "compat.h" +#include "tools/config.h" + +void +help(void) +{ + fprintf(stdout, "YANG Regular Expressions processor.\n"); + fprintf(stdout, "Usage:\n"); + fprintf(stdout, " yangre [-hv]\n"); + fprintf(stdout, " yangre [-V] -p <regexp1> [-i] [-p <regexp2> [-i] ...] <string>\n"); + fprintf(stdout, " yangre [-V] -f <file>\n"); + fprintf(stdout, "Returns 0 if string matches the pattern(s), 1 if not and -1 on error.\n\n"); + fprintf(stdout, "Options:\n" + " -h, --help Show this help message and exit.\n" + " -v, --version Show version number and exit.\n" + " -V, --verbose Print the processing information.\n" + " -i, --invert-match Invert-match modifier for the closest preceding\n" + " pattern.\n" + " -p, --pattern=\"REGEXP\" Regular expression including the quoting,\n" + " which is applied the same way as in a YANG module.\n" + " -f, --file=\"FILE\" List of patterns and the <string> (separated by an\n" + " empty line) are taken from <file>. Invert-match is\n" + " indicated by the single space character at the \n" + " beginning of the pattern line. YANG quotation around\n" + " patterns is still expected, but that avoids issues with\n" + " reading quotation by shell. Avoid newline at the end\n" + " of the string line to represent empty <string>."); + fprintf(stdout, "Examples:\n" + " pattern \"[0-9a-fA-F]*\"; -> yangre -p '\"[0-9a-fA-F]*\"' '1F'\n" + " pattern '[a-zA-Z0-9\\-_.]*'; -> yangre -p \"'[a-zA-Z0-9\\-_.]*'\" 'a-b'\n" + " pattern [xX][mM][lL].*; -> yangre -p '[xX][mM][lL].*' 'xml-encoding'\n\n"); + fprintf(stdout, "Note that to pass YANG quoting through your shell, you are supposed to use\n" + "the other quotation around. For not-quoted patterns, use single quotes.\n\n"); +} + +void +version(void) +{ + fprintf(stdout, "yangre %s\n", PROJECT_VERSION); +} + +void +pattern_error(LY_LOG_LEVEL level, const char *msg, const char *path) +{ + (void) path; /* unused */ + + if (level == LY_LLERR) { + fprintf(stderr, "yangre error: %s\n", msg); + } +} + +static const char *module_start = "module yangre {" + "yang-version 1.1;" + "namespace urn:cesnet:libyang:yangre;" + "prefix re;" + "leaf pattern {" + " type string {"; +static const char *module_invertmatch = " { modifier invert-match; }"; +static const char *module_match = ";"; +static const char *module_end = "}}}"; + +static int +add_pattern(char ***patterns, int **inverts, int *counter, char *pattern) +{ + void *reallocated1, *reallocated2; + + (*counter)++; + reallocated1 = realloc(*patterns, *counter * sizeof **patterns); + reallocated2 = realloc(*inverts, *counter * sizeof **inverts); + if (!reallocated1 || !reallocated2) { + fprintf(stderr, "yangre error: memory allocation error.\n"); + free(reallocated1); + free(reallocated2); + return EXIT_FAILURE; + } + (*patterns) = reallocated1; + (*patterns)[*counter - 1] = strdup(pattern); + (*inverts) = reallocated2; + (*inverts)[*counter - 1] = 0; + + return EXIT_SUCCESS; +} + +int +main(int argc, char *argv[]) +{ + LY_ERR match; + int i, opt_index = 0, ret = -1, verbose = 0, blankline = 0; + struct option options[] = { + {"help", no_argument, NULL, 'h'}, + {"file", required_argument, NULL, 'f'}, + {"invert-match", no_argument, NULL, 'i'}, + {"pattern", required_argument, NULL, 'p'}, + {"version", no_argument, NULL, 'v'}, + {"verbose", no_argument, NULL, 'V'}, + {NULL, 0, NULL, 0} + }; + char **patterns = NULL, *str = NULL, *modstr = NULL, *s; + int *invert_match = NULL; + int patterns_count = 0; + struct ly_ctx *ctx = NULL; + struct lys_module *mod; + FILE *infile = NULL; + size_t len = 0; + ssize_t l; + + opterr = 0; + while ((i = getopt_long(argc, argv, "hf:ivVp:", options, &opt_index)) != -1) { + switch (i) { + case 'h': + help(); + ret = -2; /* continue to allow printing version and help at once */ + break; + case 'f': + if (infile) { + help(); + fprintf(stderr, "yangre error: multiple input files are not supported.\n"); + goto cleanup; + } else if (patterns_count) { + help(); + fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n"); + goto cleanup; + } + infile = fopen(optarg, "rb"); + if (!infile) { + fprintf(stderr, "yangre error: unable to open input file %s (%s).\n", optarg, strerror(errno)); + goto cleanup; + } + + while ((l = getline(&str, &len, infile)) != -1) { + if (!blankline && (str[0] == '\n')) { + /* blank line */ + blankline = 1; + continue; + } + if ((str[0] != '\n') && (str[l - 1] == '\n')) { + /* remove ending newline */ + str[l - 1] = '\0'; + } + if (blankline) { + /* done - str is now the string to check */ + blankline = 0; + break; + /* else read the patterns */ + } else if (add_pattern(&patterns, &invert_match, &patterns_count, + (str[0] == ' ') ? &str[1] : str)) { + goto cleanup; + } + if (str[0] == ' ') { + /* set invert-match */ + invert_match[patterns_count - 1] = 1; + } + } + if (blankline) { + /* corner case, no input after blankline meaning the pattern to check is empty */ + if (str != NULL) { + free(str); + } + str = malloc(sizeof(char)); + str[0] = '\0'; + } + break; + case 'i': + if (!patterns_count || invert_match[patterns_count - 1]) { + help(); + fprintf(stderr, "yangre error: invert-match option must follow some pattern.\n"); + goto cleanup; + } + invert_match[patterns_count - 1] = 1; + break; + case 'p': + if (infile) { + help(); + fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n"); + goto cleanup; + } + if (add_pattern(&patterns, &invert_match, &patterns_count, optarg)) { + goto cleanup; + } + break; + case 'v': + version(); + ret = -2; /* continue to allow printing version and help at once */ + break; + case 'V': + verbose = 1; + break; + default: + help(); + if (optopt) { + fprintf(stderr, "yangre error: invalid option: -%c\n", optopt); + } else { + fprintf(stderr, "yangre error: invalid option: %s\n", argv[optind - 1]); + } + goto cleanup; + } + } + + if (ret == -2) { + goto cleanup; + } + + if (!str) { + /* check options compatibility */ + if (optind >= argc) { + help(); + fprintf(stderr, "yangre error: missing <string> parameter to process.\n"); + goto cleanup; + } else if (!patterns_count) { + help(); + fprintf(stderr, "yangre error: missing pattern parameter to use.\n"); + goto cleanup; + } + str = argv[optind]; + } + + for (modstr = (char *)module_start, i = 0; i < patterns_count; i++) { + if (asprintf(&s, "%s pattern %s%s", modstr, patterns[i], invert_match[i] ? module_invertmatch : module_match) == -1) { + fprintf(stderr, "yangre error: memory allocation failed.\n"); + goto cleanup; + } + if (modstr != module_start) { + free(modstr); + } + modstr = s; + } + if (asprintf(&s, "%s%s", modstr, module_end) == -1) { + fprintf(stderr, "yangre error: memory allocation failed.\n"); + goto cleanup; + } + if (modstr != module_start) { + free(modstr); + } + modstr = s; + + if (ly_ctx_new(NULL, 0, &ctx)) { + goto cleanup; + } + + ly_set_log_clb(pattern_error, 0); + if (lys_parse_mem(ctx, modstr, LYS_IN_YANG, &mod) || !mod->compiled || !mod->compiled->data) { + goto cleanup; + } + + /* check the value */ + match = lyd_value_validate(ctx, mod->compiled->data, str, strlen(str), NULL, NULL, NULL); + + if (verbose) { + for (i = 0; i < patterns_count; i++) { + fprintf(stdout, "pattern %d: %s\n", i + 1, patterns[i]); + fprintf(stdout, "matching %d: %s\n", i + 1, invert_match[i] ? "inverted" : "regular"); + } + fprintf(stdout, "string : %s\n", str); + if (match == LY_SUCCESS) { + fprintf(stdout, "result : matching\n"); + } else if (match == LY_EVALID) { + fprintf(stdout, "result : not matching\n"); + } else { + fprintf(stdout, "result : error (%s)\n", ly_errmsg(ctx)); + } + } + if (match == LY_SUCCESS) { + ret = 0; + } else if (match == LY_EVALID) { + ret = 1; + } else { + ret = -1; + } + +cleanup: + ly_ctx_destroy(ctx); + for (i = 0; i < patterns_count; i++) { + free(patterns[i]); + } + free(patterns); + free(invert_match); + free(modstr); + if (infile) { + fclose(infile); + free(str); + } + + return ret; +} diff --git a/tools/re/yangre.1 b/tools/re/yangre.1 new file mode 100644 index 0000000..e7b572b --- /dev/null +++ b/tools/re/yangre.1 @@ -0,0 +1,118 @@ +.\" Manpage for yanglint. +.\" Process this file with +.\" groff -man -Tascii yangre.1 +.\" + +.TH YANGRE 1 "2018-11-09" "libyang" +.SH NAME +yangre \- YANG regular expression processor +. +.SH SYNOPSIS +.B yangre +[\-V] \-p \fIREGEXP\fP [\-i] [\-p \fIREGEXP\fP [\-i]...] \fISTRING\fP +.br +.B yangre +[\-V] \-f \fIFILE\fP +. +.SH DESCRIPTION +\fByangre\fP is a command-line tool to test and evaluate regular expressions +for use in YANG schemas. Supported regular expressions are defined by the +W3C's XML-Schema standard. + +\fByangre\fP can be used either with regular expressions and a target string +on the command line or with input from a file. The latter is particularly +useful to avoid dealing with proper shell escaping of regular expression +patterns, which can be somewhat tricky. +. +.SH GENERAL OPTIONS +.TP +.BR "\-h\fR,\fP \-\^\-help" +.br +Outputs usage help and exits. +.TP +.BR "\-v\fR,\fP \-\^\-version" +.br +Outputs the version number and exits. +.TP +.BR "\-V\fR,\fP \-\^\-verbose" +Increases the verbosity level. If not specified, only errors are printed, with +each appearance it adds: warnings, verbose messages, debug messages (if compiled +with debug information). +.SH COMMAND LINE INPUT +.TP +.BR "\-p \fIREGEXP\fP\fR,\fP \-\^\-pattern=\fIREGEXP\fP" +.br +One or more regular expression patterns to be tested against the input +string. Supplied expressions are tested in the order they appear on the +command line. Testing is aborted when an expression does not match (or +does match, if the \fB-i\fP option is used.) +.TP +.BR "\-i\fR,\fP \-\^\-invert-match" +.br +Reverse match condition for the previous pattern. If the pattern matches, +an error is printed and evaluation is aborted. +.TP +.BR "\fISTRING\fP" +.br +Target text input to match the regular expression(s) against. The same +text is used for all regular expressions. Note that only the first +argument is used by \fByangre\fP, if it contains spaces or other shell +metacharacters they must be properly escaped. Additional arguments are +silently ignored. +.SH FILE INPUT +.TP +.BR "\-f \fIFILE\fP\fR,\fP \-\^\-file=\fIFILE\fP" +Read both patterns and target text from the specified input file. + +\fIFILE\fP must consist of one or more YANG regular expressions, each on +their own line, followed by a blank line and one line of target text. No +preprocessing is done on file input, there are no comment lines and +whitespace is not stripped. A single space character at the beginning of +a pattern line inverts the match condition for the pattern on that line. +Patterns must still be properly quoted as mandated by the YANG standard. +.SH RETURN VALUES +.TP +0 +.I Successful match +.br +The target text matched for all patterns. +.TP +1 +.I Pattern mismatch +.br +One or more patterns did not match the target text. An error message is +printed to stderr describing which pattern was the first not to match. +.TP +255 +.I Other error +.br +One or more patterns could not be processed or some other error occurred that +precluded processing. +.SH EXAMPLES +.IP \[bu] 2 +Test a single pattern: + yangre -p 'te.*xt' text_text +.IP \[bu] +Test multiple patterns: + yangre -p '.*pat1' -p 'pat2.*' -p 'notpat' -i pat2testpat1 +.IP \[bu] +Input from a file: + cat > /tmp/patterns <<EOF + .*pat1 + pat2.* + notpat + + pat2testpat1 + EOF + yangre -f /tmp/patterns + +.SH SEE ALSO +https://github.com/CESNET/libyang (libyang homepage and Git repository) +. +.SH AUTHORS +Radek Krejci <rkrejci@cesnet.cz>, Michal Vasko <mvasko@cesnet.cz> +.br +This man page was written by David Lamparter <equinox@diac24.net> +. +.SH COPYRIGHT +Copyright \(co 2015-2018 CESNET, a.l.e. |