summaryrefslogtreecommitdiffstats
path: root/tools/lint/completion.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/lint/completion.c')
-rw-r--r--tools/lint/completion.c379
1 files changed, 379 insertions, 0 deletions
diff --git a/tools/lint/completion.c b/tools/lint/completion.c
new file mode 100644
index 0000000..9843816
--- /dev/null
+++ b/tools/lint/completion.c
@@ -0,0 +1,379 @@
+/**
+ * @file completion.c
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @brief libyang's yanglint tool auto completion
+ *
+ * Copyright (c) 2015 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
+#define _POSIX_C_SOURCE 200809L /* strdup */
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libyang.h"
+
+#include "cmd.h"
+#include "common.h"
+#include "compat.h"
+#include "linenoise/linenoise.h"
+
+/* from the main.c */
+extern struct ly_ctx *ctx;
+
+/**
+ * @brief Add a match to the completions array.
+ *
+ * @param[in] match Match to be added.
+ * @param[in,out] matches Matches provided to the user as a completion hint.
+ * @param[in,out] match_count Number of matches.
+ */
+static void
+cmd_completion_add_match(const char *match, char ***matches, unsigned int *match_count)
+{
+ void *p;
+
+ ++(*match_count);
+ p = realloc(*matches, *match_count * sizeof **matches);
+ if (!p) {
+ YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
+ return;
+ }
+ *matches = p;
+ (*matches)[*match_count - 1] = strdup(match);
+}
+
+/**
+ * @brief Provides completion for command names.
+ *
+ * @param[in] hint User input.
+ * @param[out] matches Matches provided to the user as a completion hint.
+ * @param[out] match_count Number of matches.
+ */
+static void
+get_cmd_completion(const char *hint, char ***matches, unsigned int *match_count)
+{
+ int i;
+
+ *match_count = 0;
+ *matches = NULL;
+
+ for (i = 0; commands[i].name; i++) {
+ if (!strncmp(hint, commands[i].name, strlen(hint))) {
+ cmd_completion_add_match(commands[i].name, matches, match_count);
+ }
+ }
+}
+
+/**
+ * @brief Provides completion for module names.
+ *
+ * @param[in] hint User input.
+ * @param[out] matches Matches provided to the user as a completion hint.
+ * @param[out] match_count Number of matches.
+ */
+static void
+get_model_completion(const char *hint, char ***matches, unsigned int *match_count)
+{
+ LY_ARRAY_COUNT_TYPE u;
+ uint32_t idx = 0;
+ const struct lys_module *module;
+
+ *match_count = 0;
+ *matches = NULL;
+
+ while ((module = ly_ctx_get_module_iter(ctx, &idx))) {
+ if (!strncmp(hint, module->name, strlen(hint))) {
+ cmd_completion_add_match(module->name, matches, match_count);
+ }
+
+ LY_ARRAY_FOR(module->parsed->includes, u) {
+ if (!strncmp(hint, module->parsed->includes[u].submodule->name, strlen(hint))) {
+ cmd_completion_add_match(module->parsed->includes[u].submodule->name, matches, match_count);
+ }
+ }
+ }
+}
+
+/**
+ * @brief Add all child nodes of a single node to the completion hint.
+ *
+ * @param[in] last_node Node of which children will be added to the hint.
+ * @param matches[out] Matches provided to the user as a completion hint.
+ * @param match_count[out] Number of matches.
+ */
+static void
+single_hint_add_children(const struct lysc_node *last_node, char ***matches, unsigned int *match_count)
+{
+ const struct lysc_node *node = NULL;
+ char *match;
+
+ if (!last_node) {
+ return;
+ }
+
+ while ((node = lys_getnext(node, last_node, NULL, LYS_GETNEXT_WITHCASE | LYS_GETNEXT_WITHCHOICE))) {
+ match = lysc_path(node, LYSC_PATH_LOG, NULL, 0);
+ cmd_completion_add_match(match, matches, match_count);
+ free(match);
+ }
+}
+
+/**
+ * @brief Add module and/or node's children names to the hint.
+ *
+ * @param[in] module Compiled schema module.
+ * @param[in] parent Parent node of which children are potential matches.
+ * @param[in] hint_node_name Node name contained within the hint specified by user.
+ * @param[in,out] matches Matches provided to the user as a completion hint.
+ * @param[in,out] match_count Number of matches.
+ * @param[out] last_node Last processed node.
+ */
+static void
+add_all_children_nodes(const struct lysc_module *module, const struct lysc_node *parent,
+ const char *hint_node_name, char ***matches, unsigned int *match_count, const struct lysc_node **last_node)
+{
+ const struct lysc_node *node;
+ char *match, *node_name = NULL;
+
+ *last_node = NULL;
+
+ if (!parent && !module) {
+ return;
+ }
+
+ node = NULL;
+ while ((node = lys_getnext(node, parent, module, LYS_GETNEXT_WITHCASE | LYS_GETNEXT_WITHCHOICE))) {
+ if (parent && (node->module != parent->module)) {
+ /* augmented node */
+ if (asprintf(&node_name, "%s:%s", node->module->name, node->name) == -1) {
+ YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
+ break;
+ }
+ } else {
+ node_name = strdup(node->name);
+ if (!node_name) {
+ YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
+ break;
+ }
+ }
+ if (!hint_node_name || !strncmp(hint_node_name, node_name, strlen(hint_node_name))) {
+ /* adding just module names + their top level node(s) to the hint */
+ *last_node = node;
+ match = lysc_path(node, LYSC_PATH_LOG, NULL, 0);
+ cmd_completion_add_match(match, matches, match_count);
+ free(match);
+ }
+ free(node_name);
+ }
+}
+
+/**
+ * @brief Provides completion for schemas.
+ *
+ * @param[in] hint User input.
+ * @param[out] matches Matches provided to the user as a completion hint.
+ * @param[out] match_count Number of matches.
+ */
+static void
+get_schema_completion(const char *hint, char ***matches, unsigned int *match_count)
+{
+ const struct lys_module *module;
+ uint32_t idx;
+ const char *start;
+ char *end, *module_name = NULL, *path = NULL;
+ const struct lysc_node *parent, *last_node;
+ int rc = 0;
+ size_t len;
+
+ *match_count = 0;
+ *matches = NULL;
+
+ if (strlen(hint)) {
+ if (hint[0] != '/') {
+ return;
+ }
+ start = hint + 1;
+ } else {
+ start = hint;
+ }
+
+ end = strchr(start, ':');
+ if (!end) {
+ /* no module name */
+ len = strlen(start);
+
+ /* go through all the modules */
+ idx = 0;
+ while ((module = ly_ctx_get_module_iter(ctx, &idx))) {
+ if (!module->implemented) {
+ continue;
+ }
+
+ if (!len || !strncmp(start, module->name, len)) {
+ /* add all their (matching) top level nodes */
+ add_all_children_nodes(module->compiled, NULL, NULL, matches, match_count, &last_node);
+ }
+ }
+ } else {
+ /* module name known */
+ module_name = strndup(start, end - start);
+ if (!module_name) {
+ YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
+ rc = 1;
+ goto cleanup;
+ }
+
+ module = ly_ctx_get_module_implemented(ctx, module_name);
+ if (!module) {
+ goto cleanup;
+ }
+
+ /* find the last '/', if it is at the beginning of the hint, only path up to the top level node is known,
+ * else the name of the last node starts after the found '/' */
+ start = strrchr(hint, '/');
+ if (!start) {
+ goto cleanup;
+ }
+
+ if (start == hint) {
+ /* only the (incomplete) top level node path, add all (matching) top level nodes */
+ add_all_children_nodes(module->compiled, NULL, end + 1, matches, match_count, &last_node);
+ goto cleanup;
+ }
+
+ /* get rid of stuff after the last '/' to obtain the parent node */
+ path = strndup(hint, start - hint);
+ if (!path) {
+ YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
+ rc = 1;
+ goto cleanup;
+ }
+
+ /* get the last parent in the hint (it may not exist) */
+ parent = find_schema_path(ctx, path);
+
+ /* add all (matching) child nodes of the parent */
+ add_all_children_nodes(NULL, parent, start + 1, matches, match_count, &last_node);
+ }
+
+cleanup:
+ if (!rc && (*match_count == 1)) {
+ /* to avoid a single hint (space at the end), add all children as hints */
+ single_hint_add_children(last_node, matches, match_count);
+ }
+ free(path);
+ free(module_name);
+}
+
+/**
+ * @brief Get the string before the hint, which autocompletion is for.
+ *
+ * @param[in] buf Complete user input.
+ * @param[in] hint Hint part of the user input.
+ * @return Pointer to the last string.
+ */
+static const char *
+get_last_str(const char *buf, const char *hint)
+{
+ const char *ptr;
+
+ if (buf == hint) {
+ return buf;
+ }
+
+ ptr = hint - 1;
+ while (ptr[0] == ' ') {
+ --ptr;
+ if (buf == ptr) {
+ return buf;
+ }
+ }
+
+ while (ptr[-1] != ' ') {
+ --ptr;
+ if (buf == ptr) {
+ return buf;
+ }
+ }
+
+ return ptr;
+}
+
+/* callback */
+void
+complete_cmd(const char *buf, const char *hint, linenoiseCompletions *lc)
+{
+ struct autocomplete {
+ const char *cmd; /**< command */
+ const char *opt; /**< optional option */
+ int last_opt; /**< whether to autocomplete even if an option is last in the hint */
+
+ void (*ln_cb)(const char *, const char *, linenoiseCompletions *); /**< linenoise callback to call */
+ void (*yl_cb)(const char *, char ***, unsigned int *); /**< yanglint callback to call */
+ } ac[] = {
+ {"add", NULL, 1, linenoisePathCompletion, NULL},
+ {"searchpath", NULL, 0, linenoisePathCompletion, NULL},
+ {"data", NULL, 0, linenoisePathCompletion, NULL},
+ {"print", NULL, 0, NULL, get_model_completion},
+ {"feature", NULL, 0, NULL, get_model_completion},
+ {"print", "-P", 1, NULL, get_schema_completion},
+ };
+ size_t cmd_len;
+ const char *last;
+ char **matches = NULL;
+ unsigned int match_count = 0, i;
+
+ if (buf == hint) {
+ /* command autocomplete */
+ get_cmd_completion(hint, &matches, &match_count);
+
+ } else {
+ for (i = 0; i < (sizeof ac / sizeof *ac); ++i) {
+ cmd_len = strlen(ac[i].cmd);
+ if (strncmp(buf, ac[i].cmd, cmd_len) || (buf[cmd_len] != ' ')) {
+ /* not this command */
+ continue;
+ }
+
+ last = get_last_str(buf, hint);
+ if (ac[i].opt && strncmp(ac[i].opt, last, strlen(ac[i].opt))) {
+ /* autocompletion for (another) option */
+ continue;
+ }
+ if (!ac[i].last_opt && (last[0] == '-')) {
+ /* autocompletion for the command, not an option */
+ continue;
+ }
+ if ((last != buf) && (last[0] != '-')) {
+ /* autocompleted */
+ return;
+ }
+
+ /* callback */
+ if (ac[i].ln_cb) {
+ ac[i].ln_cb(buf, hint, lc);
+ } else {
+ ac[i].yl_cb(hint, &matches, &match_count);
+ }
+ break;
+ }
+ }
+
+ /* transform matches into autocompletion, if needed */
+ for (i = 0; i < match_count; ++i) {
+ linenoiseAddCompletion(lc, matches[i]);
+ free(matches[i]);
+ }
+ free(matches);
+}