yanglint FEATURE hints for option arguments
... and also completion if prefix is also specified.
diff --git a/tools/lint/cmd.c b/tools/lint/cmd.c
index 10e7446..72bc680 100644
--- a/tools/lint/cmd.c
+++ b/tools/lint/cmd.c
@@ -54,7 +54,7 @@
goto cleanup;
}
- while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) {
+ while ((opt = getopt_long(argc, argv, commands[CMD_DEBUG].optstring, options, &opt_index)) != -1) {
switch (opt) {
case 'h':
cmd_debug_help();
@@ -110,7 +110,7 @@
goto cleanup;
}
- while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) {
+ while ((opt = getopt_long(argc, argv, commands[CMD_VERB].optstring, options, &opt_index)) != -1) {
switch (opt) {
case 'h':
cmd_verb_help();
@@ -188,7 +188,7 @@
goto cleanup;
}
- while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) {
+ while ((opt = getopt_long(argc, argv, commands[CMD_HELP].optstring, options, &opt_index)) != -1) {
switch (opt) {
case 'h':
cmd_help_help();
@@ -237,23 +237,24 @@
free_cmdline(argv);
}
+/* Also keep enum COMMAND_INDEX updated. */
COMMAND commands[] = {
- {"help", cmd_help, cmd_help_help, "Display commands description"},
- {"add", cmd_add, cmd_add_help, "Add a new module from a specific file"},
- {"load", cmd_load, cmd_load_help, "Load a new schema from the searchdirs"},
- {"print", cmd_print, cmd_print_help, "Print a module"},
- {"data", cmd_data, cmd_data_help, "Load, validate and optionally print instance data"},
- {"list", cmd_list, cmd_list_help, "List all the loaded modules"},
- {"feature", cmd_feature, cmd_feature_help, "Print all features of module(s) with their state"},
- {"searchpath", cmd_searchpath, cmd_searchpath_help, "Print/set the search path(s) for schemas"},
- {"clear", cmd_clear, cmd_clear_help, "Clear the context - remove all the loaded modules"},
- {"verb", cmd_verb, cmd_verb_help, "Change verbosity"},
+ {"help", cmd_help, cmd_help_help, "Display commands description", "h"},
+ {"add", cmd_add, cmd_add_help, "Add a new module from a specific file", "DF:hi"},
+ {"load", cmd_load, cmd_load_help, "Load a new schema from the searchdirs", "F:hi"},
+ {"print", cmd_print, cmd_print_help, "Print a module", "f:hL:o:P:q"},
+ {"data", cmd_data, cmd_data_help, "Load, validate and optionally print instance data", "d:ef:F:hmo:O:r:nt:x:"},
+ {"list", cmd_list, cmd_list_help, "List all the loaded modules", "f:h"},
+ {"feature", cmd_feature, cmd_feature_help, "Print all features of module(s) with their state", "haf"},
+ {"searchpath", cmd_searchpath, cmd_searchpath_help, "Print/set the search path(s) for schemas", "ch"},
+ {"clear", cmd_clear, cmd_clear_help, "Clear the context - remove all the loaded modules", "iyh"},
+ {"verb", cmd_verb, cmd_verb_help, "Change verbosity", "h"},
#ifndef NDEBUG
- {"debug", cmd_debug, cmd_debug_help, "Display specific debug message groups"},
+ {"debug", cmd_debug, cmd_debug_help, "Display specific debug message groups", "h"},
#endif
- {"quit", cmd_quit, NULL, "Quit the program"},
+ {"quit", cmd_quit, NULL, "Quit the program", "h"},
/* synonyms for previous commands */
- {"?", cmd_help, NULL, "Display commands description"},
- {"exit", cmd_quit, NULL, "Quit the program"},
- {NULL, NULL, NULL, NULL}
+ {"?", cmd_help, NULL, "Display commands description", "h"},
+ {"exit", cmd_quit, NULL, "Quit the program", "h"},
+ {NULL, NULL, NULL, NULL, NULL}
};
diff --git a/tools/lint/cmd.h b/tools/lint/cmd.h
index 9f6f88d..071348f 100644
--- a/tools/lint/cmd.h
+++ b/tools/lint/cmd.h
@@ -27,6 +27,7 @@
void (*func)(struct ly_ctx **ctx, const char *); /* Function to call to do the command. */
void (*help_func)(void); /* Display command help. */
char *helpstring; /* Documentation for this function. */
+ char *optstring; /* Option characters used in function getopt_long. */
} COMMAND;
/**
@@ -34,6 +35,25 @@
*/
extern COMMAND commands[];
+/**
+ * @brief Index for global variable ::commands.
+ */
+enum COMMAND_INDEX {
+ CMD_HELP = 0,
+ CMD_ADD,
+ CMD_LOAD,
+ CMD_PRINT,
+ CMD_DATA,
+ CMD_LIST,
+ CMD_FEATURE,
+ CMD_SEARCHPATH,
+ CMD_CLEAR,
+ CMD_VERB,
+#ifndef NDEBUG
+ CMD_DEBUG,
+#endif
+};
+
/* cmd_add.c */
void cmd_add(struct ly_ctx **ctx, const char *cmdline);
void cmd_add_help(void);
diff --git a/tools/lint/cmd_add.c b/tools/lint/cmd_add.c
index 38663c2..aa78156 100644
--- a/tools/lint/cmd_add.c
+++ b/tools/lint/cmd_add.c
@@ -67,7 +67,7 @@
goto cleanup;
}
- while ((opt = getopt_long(argc, argv, "DF:hi", options, &opt_index)) != -1) {
+ while ((opt = getopt_long(argc, argv, commands[CMD_ADD].optstring, options, &opt_index)) != -1) {
switch (opt) {
case 'D': /* --disable--search */
if (options_ctx & LY_CTX_DISABLE_SEARCHDIRS) {
diff --git a/tools/lint/cmd_clear.c b/tools/lint/cmd_clear.c
index 7420704..cbc5e10 100644
--- a/tools/lint/cmd_clear.c
+++ b/tools/lint/cmd_clear.c
@@ -64,7 +64,7 @@
goto cleanup;
}
- while ((opt = getopt_long(argc, argv, "iyh", options, &opt_index)) != -1) {
+ while ((opt = getopt_long(argc, argv, commands[CMD_CLEAR].optstring, options, &opt_index)) != -1) {
switch (opt) {
case 'i':
if (options_ctx & LY_CTX_REF_IMPLEMENTED) {
diff --git a/tools/lint/cmd_data.c b/tools/lint/cmd_data.c
index 945bc1d..f816d9a 100644
--- a/tools/lint/cmd_data.c
+++ b/tools/lint/cmd_data.c
@@ -138,7 +138,7 @@
goto cleanup;
}
- while ((opt = getopt_long(argc, argv, "d:ef:F:hmo:O:r:nt:x:", options, &opt_index)) != -1) {
+ while ((opt = getopt_long(argc, argv, commands[CMD_DATA].optstring, options, &opt_index)) != -1) {
switch (opt) {
case 'd': /* --default */
if (!strcasecmp(optarg, "all")) {
diff --git a/tools/lint/cmd_feature.c b/tools/lint/cmd_feature.c
index 6b332ab..383cf93 100644
--- a/tools/lint/cmd_feature.c
+++ b/tools/lint/cmd_feature.c
@@ -59,7 +59,7 @@
goto cleanup;
}
- while ((opt = getopt_long(argc, argv, "haf", options, &opt_index)) != -1) {
+ while ((opt = getopt_long(argc, argv, commands[CMD_FEATURE].optstring, options, &opt_index)) != -1) {
switch (opt) {
case 'h':
cmd_feature_help();
diff --git a/tools/lint/cmd_list.c b/tools/lint/cmd_list.c
index ec7a021..77b3ec4 100644
--- a/tools/lint/cmd_list.c
+++ b/tools/lint/cmd_list.c
@@ -55,7 +55,7 @@
goto cleanup;
}
- while ((opt = getopt_long(argc, argv, "f:h", options, &opt_index)) != -1) {
+ while ((opt = getopt_long(argc, argv, commands[CMD_LIST].optstring, options, &opt_index)) != -1) {
switch (opt) {
case 'f': /* --format */
if (!strcasecmp(optarg, "xml")) {
diff --git a/tools/lint/cmd_load.c b/tools/lint/cmd_load.c
index f5883e9..af204ca 100644
--- a/tools/lint/cmd_load.c
+++ b/tools/lint/cmd_load.c
@@ -62,7 +62,7 @@
goto cleanup;
}
- while ((opt = getopt_long(argc, argv, "F:hi", options, &opt_index)) != -1) {
+ while ((opt = getopt_long(argc, argv, commands[CMD_LOAD].optstring, options, &opt_index)) != -1) {
switch (opt) {
case 'F': /* --features */
if (parse_features(optarg, &fset)) {
diff --git a/tools/lint/cmd_print.c b/tools/lint/cmd_print.c
index 0017044..b187a71 100644
--- a/tools/lint/cmd_print.c
+++ b/tools/lint/cmd_print.c
@@ -164,7 +164,7 @@
goto cleanup;
}
- while ((opt = getopt_long(argc, argv, "f:hL:o:P:q", options, &opt_index)) != -1) {
+ while ((opt = getopt_long(argc, argv, commands[CMD_PRINT].optstring, options, &opt_index)) != -1) {
switch (opt) {
case 'o': /* --output */
if (out) {
diff --git a/tools/lint/cmd_searchpath.c b/tools/lint/cmd_searchpath.c
index 3a6e184..b3a83ad 100644
--- a/tools/lint/cmd_searchpath.c
+++ b/tools/lint/cmd_searchpath.c
@@ -53,7 +53,7 @@
goto cleanup;
}
- while ((opt = getopt_long(argc, argv, "ch", options, &opt_index)) != -1) {
+ while ((opt = getopt_long(argc, argv, commands[CMD_SEARCHPATH].optstring, options, &opt_index)) != -1) {
switch (opt) {
case 'c':
ly_ctx_unset_searchdir(*ctx, NULL);
diff --git a/tools/lint/completion.c b/tools/lint/completion.c
index 9843816..b174302 100644
--- a/tools/lint/completion.c
+++ b/tools/lint/completion.c
@@ -76,6 +76,34 @@
}
/**
+ * @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.
@@ -277,6 +305,65 @@
}
/**
+ * @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);
+}
+
+/**
* @brief Get the string before the hint, which autocompletion is for.
*
* @param[in] buf Complete user input.
@@ -315,22 +402,30 @@
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 */
-
+ 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[] = {
- {"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},
+ {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_DATA, "-t", NULL, get_data_type_arg},
+ {CMD_DATA, "-O", 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},
};
- size_t cmd_len;
- const char *last;
+ size_t name_len;
+ const char *last, *name, *getoptstr;
+ char opt[3] = {'\0', ':', '\0'};
char **matches = NULL;
unsigned int match_count = 0, i;
@@ -340,24 +435,27 @@
} 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] != ' ')) {
+ /* 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);
- if (ac[i].opt && strncmp(ac[i].opt, last, strlen(ac[i].opt))) {
- /* autocompletion for (another) option */
+ 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;
- }
- if (!ac[i].last_opt && (last[0] == '-')) {
- /* autocompletion for the command, not an option */
+ } else if (ac[i].opt && opt[0] && strncmp(ac[i].opt, last, strlen(ac[i].opt))) {
+ /* completion for (another) option */
continue;
- }
- if ((last != buf) && (last[0] != '-')) {
- /* autocompleted */
- return;
+ } else if (ac[i].opt && !opt[0]) {
+ /* completion is defined for option */
+ continue;
}
/* callback */
diff --git a/tools/lint/tests/interactive/completion.test b/tools/lint/tests/interactive/completion.test
index 633fabb..86ded1f 100644
--- a/tools/lint/tests/interactive/completion.test
+++ b/tools/lint/tests/interactive/completion.test
@@ -1,5 +1,7 @@
source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+set mdir "$::env(YANG_MODULES_DIR)"
+
variable ly_cleanup {
ly_ignore
ly_exit
@@ -7,7 +9,7 @@
test completion_hints_ietf_ip {Completion and hints for ietf-ip.yang} {
-setup $ly_setup -cleanup $ly_cleanup -body {
- ly_cmd "add $::env(YANG_MODULES_DIR)/ietf-ip.yang"
+ ly_cmd "add $mdir/ietf-ip.yang"
# completion and hint
ly_completion "print -f info -P " "print -f info -P /ietf-"
@@ -41,4 +43,27 @@
ly_completion "/e" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv4/enabled "
}}
+# Note that somehow a command is automatically sent again (\t\t replaced by \r) after the hints.
+# But that doesn't affect the test because the tests only focus on the word in the hint.
+
+test hint_data_file {Show file hints for command data} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_hint "data $mdir\t\t" "data $mdir" "modleaf.yang.*"
+}}
+
+test hint_data_format {Show print hints for command data --format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_hint "data -f \t\t" "data -f " "xml.*"
+}}
+
+test hint_data_file_after_opt {Show file hints after option with argument} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_hint "data -f xml $mdir\t\t" "data -f xml $mdir" "modleaf.yang.*"
+}}
+
+test hint_data_file_after_opt2 {Show file hints after option without argument} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+ ly_hint "data -m $mdir\t\t" "data -m $mdir" "modleaf.yang.*"
+}}
+
cleanupTests
diff --git a/tools/lint/tests/interactive/ly.tcl b/tools/lint/tests/interactive/ly.tcl
index 8386848..3fa2c82 100644
--- a/tools/lint/tests/interactive/ly.tcl
+++ b/tools/lint/tests/interactive/ly.tcl
@@ -165,7 +165,7 @@
send -- "${input}\t"
# expecting the hints, previous input from which the hints were generated
# and some number of terminal control characters
- expect -re "^\r\n${output}\r> ${prev_input}.*\r.*$"
+ expect -re "${output}\r> ${prev_input}.*\r.*$"
}
# Send 'exit' and wait for eof.