blob: 13b00de3a0bc34ab7f4ac9d043f9f1333fa664ad [file] [log] [blame]
Radek Krejcied5acc52019-04-25 15:57:04 +02001/**
2 * @file completion.c
3 * @author Michal Vasko <mvasko@cesnet.cz>
4 * @brief libyang's yanglint tool auto completion
5 *
6 * Copyright (c) 2015 CESNET, z.s.p.o.
7 *
8 * This source code is licensed under BSD 3-Clause License (the "License").
9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * https://opensource.org/licenses/BSD-3-Clause
13 */
14
Radek Krejci535ea9f2020-05-29 16:01:05 +020015#define _GNU_SOURCE
Radek Krejcif8dc59a2020-11-25 13:47:44 +010016#define _POSIX_C_SOURCE 200809L /* strdup */
Radek Krejcied5acc52019-04-25 15:57:04 +020017
Radek Krejcied5acc52019-04-25 15:57:04 +020018#include <errno.h>
Radek Krejci535ea9f2020-05-29 16:01:05 +020019#include <stdint.h>
20#include <stdio.h>
21#include <stdlib.h>
Radek Krejcied5acc52019-04-25 15:57:04 +020022#include <string.h>
23
Radek Krejcied5acc52019-04-25 15:57:04 +020024#include "libyang.h"
25
Radek Krejcie9f13b12020-11-09 17:42:04 +010026#include "cmd.h"
27#include "common.h"
romanc1607ce2022-09-12 10:12:39 +020028#include "compat.h"
Radek Krejci535ea9f2020-05-29 16:01:05 +020029#include "linenoise/linenoise.h"
30
Radek Krejcie9f13b12020-11-09 17:42:04 +010031/* from the main.c */
Radek Krejcied5acc52019-04-25 15:57:04 +020032extern struct ly_ctx *ctx;
33
romanc1607ce2022-09-12 10:12:39 +020034/**
35 * @brief Add a match to the completions array.
36 *
37 * @param[in] match Match to be added.
38 * @param[in,out] matches Matches provided to the user as a completion hint.
39 * @param[in,out] match_count Number of matches.
40 */
41static void
42cmd_completion_add_match(const char *match, char ***matches, unsigned int *match_count)
43{
44 void *p;
45
46 ++(*match_count);
47 p = realloc(*matches, *match_count * sizeof **matches);
48 if (!p) {
49 YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
50 return;
51 }
52 *matches = p;
53 (*matches)[*match_count - 1] = strdup(match);
54}
55
56/**
57 * @brief Provides completion for command names.
58 *
59 * @param[in] hint User input.
60 * @param[out] matches Matches provided to the user as a completion hint.
61 * @param[out] match_count Number of matches.
62 */
Radek Krejcied5acc52019-04-25 15:57:04 +020063static void
64get_cmd_completion(const char *hint, char ***matches, unsigned int *match_count)
65{
66 int i;
Radek Krejcied5acc52019-04-25 15:57:04 +020067
68 *match_count = 0;
69 *matches = NULL;
70
71 for (i = 0; commands[i].name; i++) {
72 if (!strncmp(hint, commands[i].name, strlen(hint))) {
romanc1607ce2022-09-12 10:12:39 +020073 cmd_completion_add_match(commands[i].name, matches, match_count);
Radek Krejcied5acc52019-04-25 15:57:04 +020074 }
75 }
76}
77
romanc1607ce2022-09-12 10:12:39 +020078/**
79 * @brief Check if the last command line argument is an option.
80 *
81 * @param[in] hint User input.
82 * @return 1 if it is an option, 0 otherwise.
83 */
Radek Krejcied5acc52019-04-25 15:57:04 +020084static int
85last_is_opt(const char *hint)
86{
87 const char *ptr;
88
89 /* last is option */
90 if (hint[0] == '-') {
91 return 1;
92 }
93
94 do {
95 --hint;
96 } while (hint[0] == ' ');
97
98 /* last is option argument */
99 ptr = strrchr(hint, ' ');
100 if (ptr) {
101 ++ptr;
102 if (ptr[0] == '-') {
103 return 1;
104 }
105 }
106
107 return 0;
108}
109
romanc1607ce2022-09-12 10:12:39 +0200110/**
111 * @brief Provides completion for module names.
112 *
113 * @param[in] hint User input.
114 * @param[out] matches Matches provided to the user as a completion hint.
115 * @param[out] match_count Number of matches.
116 */
Radek Krejcied5acc52019-04-25 15:57:04 +0200117static void
118get_model_completion(const char *hint, char ***matches, unsigned int *match_count)
119{
Michal Vaskofd69e1d2020-07-03 11:57:17 +0200120 LY_ARRAY_COUNT_TYPE u;
Radek Krejcied5acc52019-04-25 15:57:04 +0200121 uint32_t idx = 0;
122 const struct lys_module *module;
Radek Krejcied5acc52019-04-25 15:57:04 +0200123
124 *match_count = 0;
125 *matches = NULL;
126
127 while ((module = ly_ctx_get_module_iter(ctx, &idx))) {
128 if (!strncmp(hint, module->name, strlen(hint))) {
romanc1607ce2022-09-12 10:12:39 +0200129 cmd_completion_add_match(module->name, matches, match_count);
Radek Krejcied5acc52019-04-25 15:57:04 +0200130 }
131
132 LY_ARRAY_FOR(module->parsed->includes, u) {
133 if (!strncmp(hint, module->parsed->includes[u].submodule->name, strlen(hint))) {
romanc1607ce2022-09-12 10:12:39 +0200134 cmd_completion_add_match(module->parsed->includes[u].submodule->name, matches, match_count);
Radek Krejcied5acc52019-04-25 15:57:04 +0200135 }
136 }
137 }
138}
139
romanc1607ce2022-09-12 10:12:39 +0200140/**
141 * @brief Add all child nodes of a single node to the completion hint.
142 *
143 * @param[in] last_node Node of which children will be added to the hint.
144 * @param matches[out] Matches provided to the user as a completion hint.
145 * @param match_count[out] Number of matches.
146 */
147static void
148single_hint_add_children(const struct lysc_node *last_node, char ***matches, unsigned int *match_count)
149{
150 const struct lysc_node *node = NULL;
151 char *match;
152
153 if (!last_node) {
154 return;
155 }
156
157 while ((node = lys_getnext(node, last_node, NULL, 0))) {
158 match = lysc_path(node, LYSC_PATH_DATA, NULL, 0);
159 cmd_completion_add_match(match, matches, match_count);
160 free(match);
161 }
162}
163
164/**
165 * @brief Add module and/or node's children names to the hint.
166 *
167 * @param[in] module Compiled schema module.
168 * @param[in] parent Parent node of which children are potential matches.
169 * @param[in] hint_node_name Node name contained within the hint specified by user.
170 * @param[in,out] matches Matches provided to the user as a completion hint.
171 * @param[in,out] match_count Number of matches.
172 * @param[out] last_node Last processed node.
173 */
174static void
175add_all_children_nodes(const struct lysc_module *module, const struct lysc_node *parent,
176 const char *hint_node_name, char ***matches, unsigned int *match_count, const struct lysc_node **last_node)
177{
178 const struct lysc_node *node;
179 char *match, *node_name = NULL;
180
181 *last_node = NULL;
182
183 if (!parent && !module) {
184 return;
185 }
186
187 node = NULL;
188 while ((node = lys_getnext(node, parent, module, 0))) {
189 if (parent && (node->module != parent->module)) {
190 /* augmented node */
191 if (asprintf(&node_name, "%s:%s", node->module->name, node->name) == -1) {
192 YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
193 break;
194 }
195 } else {
196 node_name = strdup(node->name);
197 if (!node_name) {
198 YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
199 break;
200 }
201 }
202 if (!hint_node_name || !strncmp(hint_node_name, node_name, strlen(hint_node_name))) {
203 /* adding just module names + their top level node(s) to the hint */
204 *last_node = node;
205 match = lysc_path(node, LYSC_PATH_DATA, NULL, 0);
206 cmd_completion_add_match(match, matches, match_count);
207 free(match);
208 }
209 free(node_name);
210 }
211}
212
213/**
214 * @brief Provides completion for schemas.
215 *
216 * @param[in] hint User input.
217 * @param[out] matches Matches provided to the user as a completion hint.
218 * @param[out] match_count Number of matches.
219 */
220static void
221get_schema_completion(const char *hint, char ***matches, unsigned int *match_count)
222{
223 const struct lys_module *module;
224 uint32_t idx, prev_lo;
225 const char *start;
226 char *end, *module_name = NULL, *path = NULL;
227 const struct lysc_node *parent, *last_node;
228 int rc = 0;
229 size_t len;
230
231 *match_count = 0;
232 *matches = NULL;
233
234 if (strlen(hint)) {
235 if (hint[0] != '/') {
236 return;
237 }
238 start = hint + 1;
239 } else {
240 start = hint;
241 }
242
243 end = strchr(start, ':');
244 if (!end) {
245 /* no module name */
246 len = strlen(start);
247
248 /* go through all the modules */
249 idx = 0;
250 while ((module = ly_ctx_get_module_iter(ctx, &idx))) {
251 if (!module->implemented) {
252 continue;
253 }
254
255 if (!len || !strncmp(start, module->name, len)) {
256 /* add all their (matching) top level nodes */
257 add_all_children_nodes(module->compiled, NULL, NULL, matches, match_count, &last_node);
258 }
259 }
260 } else {
261 /* module name known */
262 module_name = strndup(start, end - start);
263 if (!module_name) {
264 YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
265 rc = 1;
266 goto cleanup;
267 }
268
269 module = ly_ctx_get_module_implemented(ctx, module_name);
270 if (!module) {
271 goto cleanup;
272 }
273
274 /* find the last '/', if it is at the beginning of the hint, only path up to the top level node is known,
275 * else the name of the last node starts after the found '/' */
276 start = strrchr(hint, '/');
277 if (!start) {
278 goto cleanup;
279 }
280
281 if (start == hint) {
282 /* only the (incomplete) top level node path, add all (matching) top level nodes */
283 add_all_children_nodes(module->compiled, NULL, end + 1, matches, match_count, &last_node);
284 goto cleanup;
285 }
286
287 /* get rid of stuff after the last '/' to obtain the parent node */
288 path = strndup(hint, start - hint);
289 if (!path) {
290 YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
291 rc = 1;
292 goto cleanup;
293 }
294
295 /* silently get the last parent in the hint (it may not exist) */
296 prev_lo = ly_log_options(0);
297 parent = lys_find_path(ctx, NULL, path, 0);
298 ly_log_options(prev_lo);
299
300 /* add all (matching) child nodes of the parent */
301 add_all_children_nodes(NULL, parent, start + 1, matches, match_count, &last_node);
302 }
303
304cleanup:
305 if (!rc && (*match_count == 1)) {
306 /* to avoid a single hint (space at the end), add all children as hints */
307 single_hint_add_children(last_node, matches, match_count);
308 }
309 free(path);
310 free(module_name);
311}
312
313/* callback */
Radek Krejcied5acc52019-04-25 15:57:04 +0200314void
315complete_cmd(const char *buf, const char *hint, linenoiseCompletions *lc)
316{
317 char **matches = NULL;
318 unsigned int match_count = 0, i;
319
320 if (!strncmp(buf, "add ", 4)) {
321 linenoisePathCompletion(buf, hint, lc);
Radek Krejcie9f13b12020-11-09 17:42:04 +0100322 } else if ((!strncmp(buf, "searchpath ", 11) || !strncmp(buf, "data ", 5) ||
323 !strncmp(buf, "config ", 7) || !strncmp(buf, "filter ", 7) ||
324 !strncmp(buf, "xpath ", 6) || !strncmp(buf, "clear ", 6)) && !last_is_opt(hint)) {
Radek Krejcied5acc52019-04-25 15:57:04 +0200325 linenoisePathCompletion(buf, hint, lc);
326 } else if ((!strncmp(buf, "print ", 6) || !strncmp(buf, "feature ", 8)) && !last_is_opt(hint)) {
romanc1607ce2022-09-12 10:12:39 +0200327 if (!strncmp(buf, "print -f info -P ", 17)) {
328 get_schema_completion(hint, &matches, &match_count);
329 } else {
330 get_model_completion(hint, &matches, &match_count);
331 }
Radek Krejcied5acc52019-04-25 15:57:04 +0200332 } else if (!strchr(buf, ' ') && hint[0]) {
333 get_cmd_completion(hint, &matches, &match_count);
334 }
335
336 for (i = 0; i < match_count; ++i) {
337 linenoiseAddCompletion(lc, matches[i]);
338 free(matches[i]);
339 }
340 free(matches);
341}