Skip to content

Commit c1607ce

Browse files
romanmichalvasko
roman
authored andcommitted
yanglint FEATURE print info schema completion
Yanglint interactive modules' schemas completion added and some refactoring.
1 parent 9cd677d commit c1607ce

File tree

2 files changed

+228
-27
lines changed

2 files changed

+228
-27
lines changed

tools/lint/completion.c

+225-27
Original file line numberDiff line numberDiff line change
@@ -25,34 +25,62 @@
2525

2626
#include "cmd.h"
2727
#include "common.h"
28+
#include "compat.h"
2829
#include "linenoise/linenoise.h"
2930

3031
/* from the main.c */
3132
extern struct ly_ctx *ctx;
3233

34+
/**
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+
*/
41+
static void
42+
cmd_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+
*/
3363
static void
3464
get_cmd_completion(const char *hint, char ***matches, unsigned int *match_count)
3565
{
3666
int i;
37-
void *p;
3867

3968
*match_count = 0;
4069
*matches = NULL;
4170

4271
for (i = 0; commands[i].name; i++) {
4372
if (!strncmp(hint, commands[i].name, strlen(hint))) {
44-
++(*match_count);
45-
p = realloc(*matches, *match_count * sizeof **matches);
46-
if (!p) {
47-
YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
48-
return;
49-
}
50-
*matches = p;
51-
(*matches)[*match_count - 1] = strdup(commands[i].name);
73+
cmd_completion_add_match(commands[i].name, matches, match_count);
5274
}
5375
}
5476
}
5577

78+
/**
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+
*/
5684
static int
5785
last_is_opt(const char *hint)
5886
{
@@ -79,44 +107,210 @@ last_is_opt(const char *hint)
79107
return 0;
80108
}
81109

110+
/**
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+
*/
82117
static void
83118
get_model_completion(const char *hint, char ***matches, unsigned int *match_count)
84119
{
85120
LY_ARRAY_COUNT_TYPE u;
86121
uint32_t idx = 0;
87122
const struct lys_module *module;
88-
void *p;
89123

90124
*match_count = 0;
91125
*matches = NULL;
92126

93127
while ((module = ly_ctx_get_module_iter(ctx, &idx))) {
94128
if (!strncmp(hint, module->name, strlen(hint))) {
95-
++(*match_count);
96-
p = realloc(*matches, *match_count * sizeof **matches);
97-
if (!p) {
98-
YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
99-
return;
100-
}
101-
*matches = p;
102-
(*matches)[*match_count - 1] = strdup(module->name);
129+
cmd_completion_add_match(module->name, matches, match_count);
103130
}
104131

105132
LY_ARRAY_FOR(module->parsed->includes, u) {
106133
if (!strncmp(hint, module->parsed->includes[u].submodule->name, strlen(hint))) {
107-
++(*match_count);
108-
p = realloc(*matches, *match_count * sizeof **matches);
109-
if (!p) {
110-
YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
111-
return;
112-
}
113-
*matches = p;
114-
(*matches)[*match_count - 1] = strdup(module->parsed->includes[u].submodule->name);
134+
cmd_completion_add_match(module->parsed->includes[u].submodule->name, matches, match_count);
115135
}
116136
}
117137
}
118138
}
119139

140+
/**
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+
*/
147+
static void
148+
single_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+
*/
174+
static void
175+
add_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+
*/
220+
static void
221+
get_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+
304+
cleanup:
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 */
120314
void
121315
complete_cmd(const char *buf, const char *hint, linenoiseCompletions *lc)
122316
{
@@ -130,7 +324,11 @@ complete_cmd(const char *buf, const char *hint, linenoiseCompletions *lc)
130324
!strncmp(buf, "xpath ", 6) || !strncmp(buf, "clear ", 6)) && !last_is_opt(hint)) {
131325
linenoisePathCompletion(buf, hint, lc);
132326
} else if ((!strncmp(buf, "print ", 6) || !strncmp(buf, "feature ", 8)) && !last_is_opt(hint)) {
133-
get_model_completion(hint, &matches, &match_count);
327+
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+
}
134332
} else if (!strchr(buf, ' ') && hint[0]) {
135333
get_cmd_completion(hint, &matches, &match_count);
136334
}

tools/lint/completion.h

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
#include "./linenoise/linenoise.h"
1919

20+
/**
21+
* @brief Command line completion callback.
22+
*/
2023
void complete_cmd(const char *buf, const char *hint, linenoiseCompletions *lc);
2124

2225
#endif /* COMPLETION_H_ */

0 commit comments

Comments
 (0)