diff options
Diffstat (limited to 'tools/lint/completion.c')
-rw-r--r-- | tools/lint/completion.c | 508 |
1 files changed, 508 insertions, 0 deletions
diff --git a/tools/lint/completion.c b/tools/lint/completion.c new file mode 100644 index 0000000..59207ca --- /dev/null +++ b/tools/lint/completion.c @@ -0,0 +1,508 @@ +/** + * @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; + + p = realloc(*matches, (*match_count + 1) * sizeof **matches); + if (!p) { + YLMSG_E("Memory allocation failed (%s:%d, %s).", __FILE__, __LINE__, strerror(errno)); + return; + } + *matches = p; + (*matches)[*match_count] = strdup(match); + if (!((*matches)[*match_count])) { + YLMSG_E("Memory allocation failed (%s:%d, %s).", __FILE__, __LINE__, strerror(errno)); + return; + } + ++(*match_count); +} + +/** + * @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 arguments. + * + * @param[in] hint User input. + * @param[in] args Array of all possible arguments. The last element must be NULL. + * @param[out] matches Matches provided to the user as a completion hint. + * @param[out] match_count Number of matches. + */ +static void +get_arg_completion(const char *hint, const char **args, char ***matches, unsigned int *match_count) +{ + int i; + + *match_count = 0; + *matches = NULL; + + for (i = 0; args[i]; i++) { + if (!strncmp(hint, args[i], strlen(hint))) { + cmd_completion_add_match(args[i], matches, match_count); + } + } + if (*match_count == 0) { + for (i = 0; args[i]; i++) { + cmd_completion_add_match(args[i], 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] parent Node of which children will be added to the hint. + * @param[out] matches Matches provided to the user as a completion hint. + * @param[out] match_count Number of matches. + */ +static void +single_hint_add_children(const struct lysc_node *parent, char ***matches, unsigned int *match_count) +{ + const struct lysc_node *node = NULL; + char *match; + + if (!parent) { + return; + } + + while ((node = lys_getnext(node, parent, 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 all possible argument hints for option. + * + * @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_print_format_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"yang", "yin", "tree", "info", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_data_type_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"data", "config", "get", "getconfig", "edit", "rpc", "reply", "notif", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_data_in_format_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"xml", "json", "lyb", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_data_default_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"all", "all-tagged", "trim", "implicit-tagged", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_list_format_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"xml", "json", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_verb_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"error", "warning", "verbose", "debug", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_debug_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"dict", "xpath", "dep-sets", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @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 { + enum COMMAND_INDEX ci; /**< command index to global variable 'commands' */ + const char *opt; /**< optional option */ + 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[] = { + {CMD_ADD, NULL, linenoisePathCompletion, NULL}, + {CMD_PRINT, "-f", NULL, get_print_format_arg}, + {CMD_PRINT, "-P", NULL, get_schema_completion}, + {CMD_PRINT, "-o", linenoisePathCompletion, NULL}, + {CMD_PRINT, NULL, NULL, get_model_completion}, + {CMD_SEARCHPATH, NULL, linenoisePathCompletion, NULL}, + {CMD_EXTDATA, NULL, linenoisePathCompletion, NULL}, + {CMD_CLEAR, "-Y", linenoisePathCompletion, NULL}, + {CMD_DATA, "-t", NULL, get_data_type_arg}, + {CMD_DATA, "-O", linenoisePathCompletion, NULL}, + {CMD_DATA, "-R", linenoisePathCompletion, NULL}, + {CMD_DATA, "-f", NULL, get_data_in_format_arg}, + {CMD_DATA, "-F", NULL, get_data_in_format_arg}, + {CMD_DATA, "-d", NULL, get_data_default_arg}, + {CMD_DATA, "-o", linenoisePathCompletion, NULL}, + {CMD_DATA, NULL, linenoisePathCompletion, NULL}, + {CMD_LIST, NULL, NULL, get_list_format_arg}, + {CMD_FEATURE, NULL, NULL, get_model_completion}, + {CMD_VERB, NULL, NULL, get_verb_arg}, + {CMD_DEBUG, NULL, NULL, get_debug_arg}, + }; + size_t name_len; + const char *last, *name, *getoptstr; + char opt[3] = {'\0', ':', '\0'}; + 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) { + /* Find the right command. */ + name = commands[ac[i].ci].name; + name_len = strlen(name); + if (strncmp(buf, name, name_len) || (buf[name_len] != ' ')) { + /* not this command */ + continue; + } + + /* Select based on the right option. */ + last = get_last_str(buf, hint); + opt[0] = (last[0] == '-') && last[1] ? last[1] : '\0'; + getoptstr = commands[ac[i].ci].optstring; + if (!ac[i].opt && opt[0] && strstr(getoptstr, opt)) { + /* completion for the argument must be defined */ + continue; + } else if (ac[i].opt && opt[0] && strncmp(ac[i].opt, last, strlen(ac[i].opt))) { + /* completion for (another) option */ + continue; + } else if (ac[i].opt && !opt[0]) { + /* completion is defined for option */ + continue; + } + + /* 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); +} |