From fbaaf51f52b690c6d8b4512854994731cbda8a6f Mon Sep 17 00:00:00 2001 From: Francesco Giudici Date: Fri, 10 Jun 2016 14:48:43 +0200 Subject: [PATCH 1/6] nmcli autocompletion cleanups * no need to check HELP_ONLY_AS_FIRST var as when --help option is passed _nmcli_compl_OPTIONS will return 0, falling in the general case that will trigger end of autocompletion * clanup local var declaration in _nmcli func: - remove dupliated OPTIONS_MANDATORY declaration - init HELP_ONLY_AS_FIRST on declaration - order vars for common prefix --- clients/cli/nmcli-completion | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/clients/cli/nmcli-completion b/clients/cli/nmcli-completion index ffab7d8b99..2e9a149f5b 100644 --- a/clients/cli/nmcli-completion +++ b/clients/cli/nmcli-completion @@ -231,14 +231,14 @@ _nmcli_compl_OPTIONS() mode) if [[ "${#words[@]}" -eq 2 ]]; then _nmcli_list "tabular multiline" - return 0 + return 0 fi _nmcli_array_delete_at words 0 1 ;; colors) if [[ "${#words[@]}" -eq 2 ]]; then _nmcli_list "yes no auto" - return 0 + return 0 fi _nmcli_array_delete_at words 0 1 ;; @@ -249,21 +249,21 @@ _nmcli_compl_OPTIONS() connection 802-3-ethernet 802-1x 802-11-wireless 802-11-wireless-security ipv4 ipv6 serial ppp pppoe gsm cdma bluetooth 802-11-olpc-mesh vpn wimax infiniband bond vlan adsl bridge bridge-port team team-port dcb tun ip-tunnel macvlan vxlan GENERAL IP4 DHCP4 IP6 DHCP6 VPN profile active" - return 0 + return 0 fi _nmcli_array_delete_at words 0 1 ;; escape) if [[ "${#words[@]}" -eq 2 ]]; then _nmcli_list "no yes" - return 0 + return 0 fi _nmcli_array_delete_at words 0 1 ;; wait) if [[ "${#words[@]}" -eq 2 ]]; then _nmcli_list "" - return 0 + return 0 fi _nmcli_array_delete_at words 0 1 ;; @@ -802,22 +802,18 @@ _nmcli() cur='' fi - local OPTIONS_UNKNOWN_OPTION OPTIONS_TYPE OPTIONS_TYPED OPTIONS OPTIONS_MANDATORY COMMAND_ARGS_WAIT_OPTIONS OPTIONS_IP OPTIONS_MANDATORY OPTIONS_NEXT_GROUP OPTIONS_SEP OPTIONS_REPEATABLE - local COMMAND_CONNECTION_TYPE COMMAND_CONNECTION_ID OPTIONS_MANDATORY_IFNAME HELP_ONLY_AS_FIRST - local COMMAND_CONNECTION_ACTIVE="" + local OPTIONS OPTIONS_UNKNOWN_OPTION OPTIONS_TYPE OPTIONS_TYPED OPTIONS_IP OPTIONS_NEXT_GROUP \ + OPTIONS_SEP OPTIONS_REPEATABLE OPTIONS_MANDATORY OPTIONS_MANDATORY_IFNAME \ + COMMAND_ARGS_WAIT_OPTIONS COMMAND_CONNECTION_TYPE COMMAND_CONNECTION_ID \ + COMMAND_CONNECTION_ACTIVE="" HELP_ONLY_AS_FIRST="" \ + LONG_OPTIONS=(terse pretty mode fields colors escape ask show-secrets wait version help) - HELP_ONLY_AS_FIRST= - local LONG_OPTIONS=(terse pretty mode fields colors escape ask show-secrets wait version help) _nmcli_compl_OPTIONS i=$? - if [[ "$HELP_ONLY_AS_FIRST" == '0' ]]; then - # got a --help. No more completion. - return 0 - fi - case $i in 0) + # We have just completed an option or got an --help: terminate. return 0 ;; 1) From cd4395a261d24f4aaca0e39783d2d25f642c27ac Mon Sep 17 00:00:00 2001 From: Francesco Giudici Date: Mon, 13 Jun 2016 18:59:31 +0200 Subject: [PATCH 2/6] nmcli: enable connection autocompletion for 802.1x properties nmcli bash autocompletion leveraged on "nmcli connection edit", "print" to retrieve the specific properties of a connection. Anyway, the interactive editor is smart and just prints the used components, so in a connection where 802.1x is not enabled we had no autocompletion. Solved adding an "hidden" command "nmcli --complete connection modify" as suggested in bgo #724860 in order to retrieve ALL the available properties for use in autocompletion. Here patch from L.Rintel has been merged to make che --complete option global to nmcli (first version was local to "connection modify"). https://bugzilla.gnome.org/show_bug.cgi?id=724860 https://bugzilla.redhat.com/show_bug.cgi?id=1301226 --- clients/cli/connections.c | 56 ++++++++++++++++++++++++++++++++++++ clients/cli/nmcli-completion | 11 +++++-- clients/cli/nmcli.c | 5 ++++ clients/cli/nmcli.h | 1 + 4 files changed, 70 insertions(+), 3 deletions(-) diff --git a/clients/cli/connections.c b/clients/cli/connections.c index 506bfe0fd0..97be9450be 100644 --- a/clients/cli/connections.c +++ b/clients/cli/connections.c @@ -2900,6 +2900,38 @@ get_valid_settings_array (const char *con_type) return NULL; } +static char * +get_valid_autocompletion_string (const NameItem *array, const NameItem *array_slv) +{ + const NameItem *iter = array; + const NmcOutputField *field_iter; + GString *str; + int i; + + str = g_string_sized_new (1024); + + for (i = 0; i < 2; i++, iter = array_slv) { + while (iter && iter->name) { + int j = 0; + + while ((nmc_fields_settings_names[j].name) && + g_strcmp0 (iter->name, nmc_fields_settings_names[j].name)) + j++; + + field_iter = nmc_fields_settings_names[j].group; + j = 0; + while (field_iter[j].name) { + g_string_append_printf (str, "%s.%s ", iter->name, field_iter[j].name); + if (iter->alias) + g_string_append_printf (str, "%s.%s ", iter->alias, field_iter[j].name); + j++; + } + iter++; + } + } + return g_string_free (str, FALSE); +} + /* * Check if 'val' is valid string in either array->name or array->alias for * both array parameters (array & array_slv). @@ -10169,6 +10201,30 @@ do_connection_modify (NmCli *nmc, goto finish; } + /* Query comes from shell autocomplete function */ + if (nmc->complete) { + NMSettingConnection *s_con; + const NameItem *valid_settings_main = NULL; + const NameItem *valid_settings_slave = NULL; + const char *connection_type = NULL; + const char *slave_type = NULL; + gs_free char *slv_type = NULL; + gs_free char *word_list = NULL; + + connection_type = nm_connection_get_connection_type (connection); + s_con = nm_connection_get_setting_connection (connection); + if (s_con) + slave_type = nm_setting_connection_get_slave_type (s_con); + slv_type = g_strdup_printf ("%s-slave", slave_type ? slave_type : "no"); + valid_settings_main = get_valid_settings_array (connection_type); + valid_settings_slave = get_valid_settings_array (slv_type); + + word_list = get_valid_autocompletion_string (valid_settings_main, valid_settings_slave); + if (word_list) + g_print ("%s", word_list); + goto finish; + } + if (next_arg (&argc, &argv) != 0) { g_string_printf (nmc->return_text, _("Error: . argument is missing.")); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; diff --git a/clients/cli/nmcli-completion b/clients/cli/nmcli-completion index 2e9a149f5b..7b653da414 100644 --- a/clients/cli/nmcli-completion +++ b/clients/cli/nmcli-completion @@ -763,19 +763,24 @@ _nmcli_compl_PROPERTIES() { while [[ "${#words[@]}" -gt 0 ]]; do if [[ ${#words[@]} -le 1 ]]; then - local PREFIX="" + local PREFIX="" item list="" if [[ "${words[0]:0:1}" == [+-] ]]; then PREFIX="${words[0]:0:1}" fi - _nmcli_list_nl "$(echo -e 'print\nquit\nyes' |nmcli c edit "$@" 2>/dev/null |awk -F: '/\..*:/ {print "'$PREFIX'"$1}')" + item="$(nmcli c modify --complete "$@" 2>/dev/null)" + ### add prefix to each element + for item in $item; do + list="${list}${PREFIX}${item} " + done + _nmcli_list "$list" return 0 elif [[ ${#words[@]} -le 2 ]]; then return 0 fi _nmcli_array_delete_at words 0 1 done - _nmcli_list_nl "$(echo -e 'print\nquit\nyes' |nmcli c edit "$@" 2>/dev/null |awk -F: '/\..*:/ {print $1}')" + _nmcli_list "$(nmcli c modify --complete "$@" 2>/dev/null)" } _nmcli() diff --git a/clients/cli/nmcli.c b/clients/cli/nmcli.c index 7e788654d5..730f6a1d49 100644 --- a/clients/cli/nmcli.c +++ b/clients/cli/nmcli.c @@ -256,6 +256,8 @@ parse_command_line (NmCli *nmc, int argc, char **argv) /* ignore for backward compatibility */ } else if (matches (opt, "-ask") == 0) { nmc->ask = TRUE; + } else if (matches (opt, "-complete") == 0) { + nmc->complete = TRUE; } else if (matches (opt, "-show-secrets") == 0) { nmc->show_secrets = TRUE; } else if (matches (opt, "-wait") == 0) { @@ -628,6 +630,9 @@ main (int argc, char *argv[]) loop = g_main_loop_new (NULL, FALSE); /* create main loop */ g_main_loop_run (loop); /* run main loop */ + if (nm_cli.complete) + nm_cli.return_value = NMC_RESULT_SUCCESS; + /* Print result descripting text */ if (nm_cli.return_value != NMC_RESULT_SUCCESS) { g_printerr ("%s\n", nm_cli.return_text->str); diff --git a/clients/cli/nmcli.h b/clients/cli/nmcli.h index 34100f0026..c867ad5f9f 100644 --- a/clients/cli/nmcli.h +++ b/clients/cli/nmcli.h @@ -153,6 +153,7 @@ typedef struct _NmCli { GPtrArray *output_data; /* GPtrArray of arrays of NmcOutputField structs - accumulates data for output */ NmcPrintFields print_fields; /* Structure with field indices to print */ gboolean ask; /* Ask for missing parameters: option '--ask' */ + gboolean complete; /* Autocomplete the command line */ gboolean show_secrets; /* Whether to display secrets (both input and output): option '--show-secrets' */ gboolean in_editor; /* Whether running the editor - nmcli con edit' */ gboolean editor_status_line; /* Whether to display status line in connection editor */ From 7046e806d1514f6632c43dc2b786e6df1ea0d51a Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Wed, 15 Jun 2016 13:09:45 +0200 Subject: [PATCH 3/6] cli: streamline complete_connection_by_type() arguments Will be useful to pass around the complete flag. --- clients/cli/connections.c | 144 ++++++++++++++++++-------------------- 1 file changed, 70 insertions(+), 74 deletions(-) diff --git a/clients/cli/connections.c b/clients/cli/connections.c index 97be9450be..fa22798465 100644 --- a/clients/cli/connections.c +++ b/clients/cli/connections.c @@ -4833,11 +4833,9 @@ complete_slave (NMSettingConnection *s_con, } static gboolean -complete_connection_by_type (NMConnection *connection, +complete_connection_by_type (NmCli *nmc, + NMConnection *connection, const char *con_type, - const GPtrArray *all_connections, - gboolean ask, - gboolean show_secrets, int argc, char **argv, GError **error) @@ -4893,7 +4891,7 @@ complete_connection_by_type (NMConnection *connection, mtu = g_strdup (mtu_c); mac = g_strdup (mac_c); cloned_mac = g_strdup (cloned_mac_c); - if (ask) + if (nmc->ask) do_questionnaire_ethernet (TRUE, &mtu, &mac, &cloned_mac); if (!check_and_convert_mtu (mtu, &mtu_int, error)) @@ -4953,7 +4951,7 @@ cleanup_wired: mode = g_strdup (mode_c); parent = g_strdup (parent_c); p_key = g_strdup (p_key_c); - if (ask) + if (nmc->ask) do_questionnaire_infiniband (&mtu, &mac, &mode, &parent, &p_key); if (!check_and_convert_mtu (mtu, &mtu_int, error)) @@ -5013,7 +5011,7 @@ cleanup_ib: char *cloned_mac = NULL; const char *mode_c = NULL; char *mode = NULL; - nmc_arg_t exp_args[] = { {"ssid", TRUE, &ssid, !ask}, + nmc_arg_t exp_args[] = { {"ssid", TRUE, &ssid, !nmc->ask}, {"mtu", TRUE, &mtu_c, FALSE}, {"mac", TRUE, &mac_c, FALSE}, {"cloned-mac", TRUE, &cloned_mac_c, FALSE}, @@ -5023,7 +5021,7 @@ cleanup_ib: if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) return FALSE; - if (!ssid && ask) + if (!ssid && nmc->ask) ssid = ssid_ask = nmc_readline (_("SSID: ")); if (!ssid) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, @@ -5036,7 +5034,7 @@ cleanup_ib: mac = g_strdup (mac_c); cloned_mac = g_strdup (cloned_mac_c); mode = g_strdup (mode_c); - if (ask) + if (nmc->ask) do_questionnaire_wifi (&mtu, &mac, &cloned_mac, &mode); if (!check_and_convert_mtu (mtu, &mtu_int, error)) @@ -5083,14 +5081,14 @@ cleanup_wifi: char *nsp_name_ask = NULL; const char *mac_c = NULL; char *mac = NULL; - nmc_arg_t exp_args[] = { {"nsp", TRUE, &nsp_name, !ask}, + nmc_arg_t exp_args[] = { {"nsp", TRUE, &nsp_name, !nmc->ask}, {"mac", TRUE, &mac_c, FALSE}, {NULL} }; if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) return FALSE; - if (!nsp_name && ask) + if (!nsp_name && nmc->ask) nsp_name = nsp_name_ask = nmc_readline (_("WiMAX NSP name: ")); if (!nsp_name) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, @@ -5100,7 +5098,7 @@ cleanup_wifi: /* Also ask for all optional arguments if '--ask' is specified. */ mac = g_strdup (mac_c); - if (ask) + if (nmc->ask) do_questionnaire_wimax (&mac); if (!check_mac (mac, ARPHRD_ETHER, "mac", error)) @@ -5135,7 +5133,7 @@ cleanup_wimax: guint32 mtu_int = 0; const char *mac_c = NULL; char *mac = NULL; - nmc_arg_t exp_args[] = { {"username", TRUE, &username, !ask}, + nmc_arg_t exp_args[] = { {"username", TRUE, &username, !nmc->ask}, {"password", TRUE, &password_c, FALSE}, {"service", TRUE, &service_c, FALSE}, {"mtu", TRUE, &mtu_c, FALSE}, @@ -5145,7 +5143,7 @@ cleanup_wimax: if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) return FALSE; - if (!username && ask) + if (!username && nmc->ask) username = username_ask = nmc_readline (_("PPPoE username: ")); if (!username) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, @@ -5158,8 +5156,8 @@ cleanup_wimax: service = g_strdup (service_c); mtu = g_strdup (mtu_c); mac = g_strdup (mac_c); - if (ask) - do_questionnaire_pppoe (show_secrets, &password, &service, &mtu, &mac); + if (nmc->ask) + do_questionnaire_pppoe (nmc->show_secrets, &password, &service, &mtu, &mac); if (!check_and_convert_mtu (mtu, &mtu_int, error)) goto cleanup_pppoe; @@ -5209,7 +5207,7 @@ cleanup_pppoe: is_gsm = !strcmp (con_type, NM_SETTING_GSM_SETTING_NAME); if (is_gsm) - gsm_args[i++] = (nmc_arg_t) {"apn", TRUE, &apn, !ask}; + gsm_args[i++] = (nmc_arg_t) {"apn", TRUE, &apn, !nmc->ask}; gsm_args[i++] = (nmc_arg_t) {"user", TRUE, &user_c, FALSE}; gsm_args[i++] = (nmc_arg_t) {"password", TRUE, &password_c, FALSE}; gsm_args[i++] = (nmc_arg_t) {NULL}; @@ -5217,7 +5215,7 @@ cleanup_pppoe: if (!nmc_parse_args (gsm_args, FALSE, &argc, &argv, error)) return FALSE; - if (!apn && ask && is_gsm) + if (!apn && nmc->ask && is_gsm) apn = apn_ask = nmc_readline (_("APN: ")); if (!apn && is_gsm) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, @@ -5228,8 +5226,8 @@ cleanup_pppoe: /* Also ask for all optional arguments if '--ask' is specified. */ user = g_strdup (user_c); password = g_strdup (password_c); - if (ask) - do_questionnaire_mobile (show_secrets, &user, &password); + if (nmc->ask) + do_questionnaire_mobile (nmc->show_secrets, &user, &password); if (is_gsm) { g_object_set (s_con, NM_SETTING_CONNECTION_TYPE, NM_SETTING_GSM_SETTING_NAME, NULL); @@ -5271,14 +5269,14 @@ cleanup_mobile: char *addr_ask = NULL; const char *bt_type_c = NULL; char *bt_type = NULL; - nmc_arg_t exp_args[] = { {"addr", TRUE, &addr, !ask}, + nmc_arg_t exp_args[] = { {"addr", TRUE, &addr, !nmc->ask}, {"bt-type", TRUE, &bt_type_c, FALSE}, {NULL} }; if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) return FALSE; - if (!addr && ask) + if (!addr && nmc->ask) addr = addr_ask = nmc_readline (_("Bluetooth device address: ")); if (!addr) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, @@ -5290,7 +5288,7 @@ cleanup_mobile: /* Also ask for all optional arguments if '--ask' is specified. */ bt_type = g_strdup (bt_type_c); - if (ask) + if (nmc->ask) do_questionnaire_bluetooth (&bt_type); /* Default to 'panu' if bt-type is not provided. */ @@ -5355,8 +5353,8 @@ cleanup_bt: char *mtu = NULL; guint32 mtu_int; gboolean valid_mac = FALSE; - nmc_arg_t exp_args[] = { {"dev", TRUE, &parent, !ask}, - {"id", TRUE, &vlan_id, !ask}, + nmc_arg_t exp_args[] = { {"dev", TRUE, &parent, !nmc->ask}, + {"id", TRUE, &vlan_id, !nmc->ask}, {"flags", TRUE, &flags_c, FALSE}, {"ingress", TRUE, &ingress_c, FALSE}, {"egress", TRUE, &egress_c, FALSE}, @@ -5366,14 +5364,14 @@ cleanup_bt: if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) return FALSE; - if (!parent && ask) + if (!parent && nmc->ask) parent = parent_ask = nmc_readline (_("VLAN parent device or connection UUID: ")); if (!parent) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, _("Error: 'dev' is required.")); return FALSE; } - if (!vlan_id && ask) + if (!vlan_id && nmc->ask) vlan_id = vlan_id_ask = nmc_readline (_("VLAN ID <0-4094>: ")); if (!vlan_id) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, @@ -5403,7 +5401,7 @@ cleanup_bt: flags = g_strdup (flags_c); ingress = g_strdup (ingress_c); egress = g_strdup (egress_c); - if (ask) + if (nmc->ask) do_questionnaire_vlan (&mtu, &flags, &ingress, &egress); if (!check_and_convert_mtu (mtu, &mtu_int, error)) @@ -5498,7 +5496,7 @@ cleanup_vlan: bond_arpinterval = g_strdup (bond_arpinterval_c); bond_arpiptarget = g_strdup (bond_arpiptarget_c); bond_lacp_rate = g_strdup (bond_lacp_rate_c); - if (ask) + if (nmc->ask) do_questionnaire_bond (&bond_mode, &bond_primary, &bond_miimon, &bond_downdelay, &bond_updelay, &bond_arpinterval, &bond_arpiptarget, @@ -5507,7 +5505,7 @@ cleanup_vlan: /* Generate ifname if connection doesn't have one */ ifname = nm_setting_connection_get_interface_name (s_con); if (!ifname) { - char *bond_ifname = unique_master_iface_ifname (all_connections, "nm-bond"); + char *bond_ifname = unique_master_iface_ifname (nmc->connections, "nm-bond"); g_object_set (s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, bond_ifname, @@ -5580,7 +5578,7 @@ cleanup_bond: if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) return FALSE; - if (!complete_slave (s_con, all_connections, NM_SETTING_BOND_SETTING_NAME, master, type, ask, error)) + if (!complete_slave (s_con, nmc->connections, NM_SETTING_BOND_SETTING_NAME, master, type, nmc->ask, error)) return FALSE; /* Change properties in 'connection' setting */ @@ -5605,13 +5603,13 @@ cleanup_bond: /* Also ask for all optional arguments if '--ask' is specified. */ config = g_strdup (config_c); - if (ask) + if (nmc->ask) do_questionnaire_team (&config); /* Generate ifname if conneciton doesn't have one */ ifname = nm_setting_connection_get_interface_name (s_con); if (!ifname) { - char *team_ifname = unique_master_iface_ifname (all_connections, "nm-team"); + char *team_ifname = unique_master_iface_ifname (nmc->connections, "nm-team"); g_object_set (s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, team_ifname, @@ -5658,12 +5656,12 @@ cleanup_team: if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) return FALSE; - if (!complete_slave (s_con, all_connections, NM_SETTING_TEAM_SETTING_NAME, master, type, ask, error)) + if (!complete_slave (s_con, nmc->connections, NM_SETTING_TEAM_SETTING_NAME, master, type, nmc->ask, error)) return FALSE; /* Also ask for all optional arguments if '--ask' is specified. */ config = g_strdup (config_c); - if (ask) + if (nmc->ask) do_questionnaire_team_slave (&config); /* Add 'team-port' setting */ @@ -5738,14 +5736,14 @@ cleanup_team_slave: ageing_time = g_strdup (ageing_time_c); mcast_snoop = g_strdup (mcast_snoop_c); mac = g_strdup (mac_c); - if (ask) + if (nmc->ask) do_questionnaire_bridge (&stp, &priority, &fwd_delay, &hello_time, &max_age, &ageing_time, &mcast_snoop, &mac); /* Generate ifname if conneciton doesn't have one */ ifname = nm_setting_connection_get_interface_name (s_con); if (!ifname) { - char *bridge_ifname = unique_master_iface_ifname (all_connections, "nm-bridge"); + char *bridge_ifname = unique_master_iface_ifname (nmc->connections, "nm-bridge"); g_object_set (s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, bridge_ifname, @@ -5858,7 +5856,7 @@ cleanup_bridge: if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) return FALSE; - if (!complete_slave (s_con, all_connections, NM_SETTING_BRIDGE_SETTING_NAME, master, type, ask, error)) + if (!complete_slave (s_con, nmc->connections, NM_SETTING_BRIDGE_SETTING_NAME, master, type, nmc->ask, error)) return FALSE; /* Add 'bridge-port' setting */ @@ -5870,7 +5868,7 @@ cleanup_bridge: priority = g_strdup (priority_c); path_cost = g_strdup (path_cost_c); hairpin = g_strdup (hairpin_c); - if (ask) + if (nmc->ask) do_questionnaire_bridge_slave (&priority, &path_cost, &hairpin); if (priority) @@ -5922,14 +5920,14 @@ cleanup_bridge_slave: const char *user_c = NULL; char *user = NULL; gs_free char *service_type_free = NULL; - nmc_arg_t exp_args[] = { {"vpn-type", TRUE, &vpn_type, !ask}, + nmc_arg_t exp_args[] = { {"vpn-type", TRUE, &vpn_type, !nmc->ask}, {"user", TRUE, &user_c, FALSE}, {NULL} }; if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) return FALSE; - if (!vpn_type && ask) + if (!vpn_type && nmc->ask) vpn_type = vpn_type_ask = nmc_readline (PROMPT_VPN_TYPE); if (!vpn_type) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, @@ -5947,7 +5945,7 @@ cleanup_bridge_slave: /* Also ask for all optional arguments if '--ask' is specified. */ user = g_strdup (user_c); - if (ask) + if (nmc->ask) do_questionnaire_vpn (&user); /* Add 'vpn' setting */ @@ -5974,7 +5972,7 @@ cleanup_vpn: unsigned long chan; const char *dhcp_anycast_c = NULL; char *dhcp_anycast = NULL; - nmc_arg_t exp_args[] = { {"ssid", TRUE, &ssid, !ask}, + nmc_arg_t exp_args[] = { {"ssid", TRUE, &ssid, !nmc->ask}, {"channel", TRUE, &channel_c, FALSE}, {"dhcp-anycast", TRUE, &dhcp_anycast_c, FALSE}, {NULL} }; @@ -5982,7 +5980,7 @@ cleanup_vpn: if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) return FALSE; - if (!ssid && ask) + if (!ssid && nmc->ask) ssid = ssid_ask = nmc_readline (_("SSID: ")); if (!ssid) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, @@ -5993,7 +5991,7 @@ cleanup_vpn: /* Also ask for all optional arguments if '--ask' is specified. */ channel = g_strdup (channel_c); dhcp_anycast = g_strdup (dhcp_anycast_c); - if (ask) + if (nmc->ask) do_questionnaire_olpc (&channel, &dhcp_anycast); if (channel) { @@ -6040,8 +6038,8 @@ cleanup_olpc: char *password = NULL; const char *encapsulation_c = NULL; char *encapsulation = NULL; - nmc_arg_t exp_args[] = { {"username", TRUE, &username, !ask}, - {"protocol", TRUE, &protocol_c, !ask}, + nmc_arg_t exp_args[] = { {"username", TRUE, &username, !nmc->ask}, + {"protocol", TRUE, &protocol_c, !nmc->ask}, {"password", TRUE, &password_c, FALSE}, {"encapsulation", TRUE, &encapsulation_c, FALSE}, {NULL} }; @@ -6049,7 +6047,7 @@ cleanup_olpc: if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) return FALSE; - if (!username && ask) + if (!username && nmc->ask) username = username_ask = nmc_readline (_("Username: ")); if (!username) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, @@ -6058,7 +6056,7 @@ cleanup_olpc: } #define PROMPT_ADSL_PROTO "(" NM_SETTING_ADSL_PROTOCOL_PPPOA "/" NM_SETTING_ADSL_PROTOCOL_PPPOE "/" NM_SETTING_ADSL_PROTOCOL_IPOATM "): " - if (!protocol_c && ask) + if (!protocol_c && nmc->ask) protocol_c = protocol_ask = nmc_readline (_("Protocol %s"), PROMPT_ADSL_PROTO); if (!protocol_c) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, @@ -6072,8 +6070,8 @@ cleanup_olpc: /* Also ask for all optional arguments if '--ask' is specified. */ password = g_strdup (password_c); encapsulation = g_strdup (encapsulation_c); - if (ask) - do_questionnaire_adsl (show_secrets, &password, &encapsulation); + if (nmc->ask) + do_questionnaire_adsl (nmc->show_secrets, &password, &encapsulation); if (!check_adsl_encapsulation (&encapsulation, error)) goto cleanup_adsl; @@ -6112,15 +6110,15 @@ cleanup_adsl: NMSettingMacvlanMode mode_enum; gboolean valid_mac = FALSE; gboolean tap_bool = FALSE; - nmc_arg_t exp_args[] = { {"dev", TRUE, &parent, !ask}, - {"mode", TRUE, &mode, !ask}, + nmc_arg_t exp_args[] = { {"dev", TRUE, &parent, !nmc->ask}, + {"mode", TRUE, &mode, !nmc->ask}, {"tap", TRUE, &tap_c, FALSE}, {NULL} }; if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) return FALSE; - if (!parent && ask) + if (!parent && nmc->ask) parent = parent_ask = nmc_readline (_("MACVLAN parent device or connection UUID: ")); if (!parent) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, @@ -6137,7 +6135,7 @@ cleanup_adsl: goto cleanup_macvlan; } - if (!mode && ask) + if (!mode && nmc->ask) mode = mode_ask = nmc_readline (PROMPT_MACVLAN_MODE); if (!mode) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, @@ -6153,7 +6151,7 @@ cleanup_adsl: /* Also ask for all optional arguments if '--ask' is specified. */ tap = g_strdup (tap_c); - if (ask) + if (nmc->ask) do_questionnaire_macvlan (&tap); if (tap) { @@ -6203,7 +6201,7 @@ cleanup_macvlan: const char *pi_c = NULL, *vnet_hdr_c = NULL, *multi_queue_c = NULL; char *pi = NULL, *vnet_hdr = NULL, *multi_queue = NULL; gboolean pi_bool, vnet_hdr_bool, multi_queue_bool; - nmc_arg_t exp_args[] = { {"mode", TRUE, &mode_c, !ask}, + nmc_arg_t exp_args[] = { {"mode", TRUE, &mode_c, !nmc->ask}, {"owner", TRUE, &owner_c, FALSE}, {"group", TRUE, &group_c, FALSE}, {"pi", TRUE, &pi_c, FALSE}, @@ -6214,7 +6212,7 @@ cleanup_macvlan: if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) return FALSE; - if (!mode_c && ask) { + if (!mode_c && nmc->ask) { mode_ask = nmc_readline (_("Mode %s"), PROMPT_TUN_MODE); mode_ask = mode_ask ? mode_ask : g_strdup ("tun"); mode_c = mode_ask; @@ -6238,7 +6236,7 @@ cleanup_macvlan: pi = g_strdup (pi_c); vnet_hdr = g_strdup (vnet_hdr_c); multi_queue = g_strdup (multi_queue_c); - if (ask) + if (nmc->ask) do_questionnaire_tun (&owner, &group, &pi, &vnet_hdr, &multi_queue); if (pi) { @@ -6310,16 +6308,16 @@ cleanup_tun: char *parent = NULL; gboolean success = FALSE; NMIPTunnelMode mode_enum; - nmc_arg_t exp_args[] = { {"mode", TRUE, &mode_c, !ask}, + nmc_arg_t exp_args[] = { {"mode", TRUE, &mode_c, !nmc->ask}, {"local", TRUE, &local_c, FALSE}, - {"remote", TRUE, &remote_c, !ask}, + {"remote", TRUE, &remote_c, !nmc->ask}, {"dev", TRUE, &parent_c, FALSE}, {NULL} }; if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) return FALSE; - if (!mode_c && ask) + if (!mode_c && nmc->ask) mode_c = mode_ask = nmc_readline (PROMPT_IP_TUNNEL_MODE); if (!mode_c) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, @@ -6342,7 +6340,7 @@ cleanup_tun: goto cleanup_tunnel; } - if (!remote_c && ask) + if (!remote_c && nmc->ask) remote_c = remote_ask = nmc_readline (_("Remote endpoint: ")); if (!remote_c) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, @@ -6360,7 +6358,7 @@ cleanup_tun: local = g_strdup (local_c); parent = g_strdup (parent_c); - if (ask) + if (nmc->ask) do_questionnaire_ip_tunnel (&local, &parent); if ( local @@ -6424,8 +6422,8 @@ cleanup_tunnel: unsigned long int vni; unsigned long sport_min = G_MAXULONG, sport_max = G_MAXULONG; unsigned long dport = G_MAXULONG; - nmc_arg_t exp_args[] = { {"id", TRUE, &id, !ask}, - {"remote", TRUE, &remote, !ask}, + nmc_arg_t exp_args[] = { {"id", TRUE, &id, !nmc->ask}, + {"remote", TRUE, &remote, !nmc->ask}, {"dev", TRUE, &parent_c, FALSE}, {"local", TRUE, &local_c, FALSE}, {"source-port-min", TRUE, &src_port_min_c, FALSE}, @@ -6436,7 +6434,7 @@ cleanup_tunnel: if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) return FALSE; - if (!id && ask) + if (!id && nmc->ask) id = id_ask = nmc_readline (_("VXLAN ID: ")); if (!id) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, @@ -6444,7 +6442,7 @@ cleanup_tunnel: goto cleanup_vxlan; } - if (!remote && ask) + if (!remote && nmc->ask) remote = remote_ask = nmc_readline (_("Remote: ")); if (!remote) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, @@ -6464,7 +6462,7 @@ cleanup_tunnel: src_port_max = g_strdup (src_port_max_c); dst_port = g_strdup (dst_port_c); - if (ask) + if (nmc->ask) do_questionnaire_vxlan (&parent, &local, &src_port_min, &src_port_max, &dst_port); if (parent) { @@ -6649,7 +6647,7 @@ cleanup_vxlan: } /* Ask for addresses if '--ask' is specified. */ - if (ask) + if (nmc->ask) do_questionnaire_ip (connection); } @@ -7073,11 +7071,9 @@ do_connection_add (NmCli *nmc, int argc, char **argv) g_free (default_name); nm_connection_add_setting (connection, NM_SETTING (s_con)); - if (!complete_connection_by_type (connection, + if (!complete_connection_by_type (nmc, + connection, setting_name, - nmc->connections, - nmc->ask, - nmc->show_secrets, argc, argv, &error)) { From 5aec1a3928315ec45628563c00450bfce9285beb Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Wed, 15 Jun 2016 13:10:26 +0200 Subject: [PATCH 4/6] nmcli: improve connection autocompletion (1/2) Make property autocompletion take a prefix and modifier flags. This will make it easier to complete an unfinished property name (possibly accompanied by a modifier) without shell trickery. --- clients/cli/connections.c | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/clients/cli/connections.c b/clients/cli/connections.c index fa22798465..34e998c09e 100644 --- a/clients/cli/connections.c +++ b/clients/cli/connections.c @@ -2901,7 +2901,7 @@ get_valid_settings_array (const char *con_type) } static char * -get_valid_autocompletion_string (const NameItem *array, const NameItem *array_slv) +get_valid_autocompletion_string (const NameItem *array, const NameItem *array_slv, char modifier, const char *prefix) { const NameItem *iter = array; const NmcOutputField *field_iter; @@ -2921,9 +2921,26 @@ get_valid_autocompletion_string (const NameItem *array, const NameItem *array_sl field_iter = nmc_fields_settings_names[j].group; j = 0; while (field_iter[j].name) { - g_string_append_printf (str, "%s.%s ", iter->name, field_iter[j].name); - if (iter->alias) - g_string_append_printf (str, "%s.%s ", iter->alias, field_iter[j].name); + gchar *new; + + new = g_strdup_printf ("%s.%s ", iter->name, field_iter[j].name); + if (g_str_has_prefix (new, prefix)) { + if (modifier) + g_string_append_c (str, modifier); + g_string_append (str, new); + } + g_free (new); + + if (iter->alias) { + new = g_strdup_printf ("%s.%s ", iter->alias, field_iter[j].name); + if (g_str_has_prefix (new, prefix)) { + if (modifier) + g_string_append_c (str, modifier); + g_string_append (str, new); + } + g_free (new); + } + j++; } iter++; @@ -10215,7 +10232,7 @@ do_connection_modify (NmCli *nmc, valid_settings_main = get_valid_settings_array (connection_type); valid_settings_slave = get_valid_settings_array (slv_type); - word_list = get_valid_autocompletion_string (valid_settings_main, valid_settings_slave); + word_list = get_valid_autocompletion_string (valid_settings_main, valid_settings_slave, '\0', ""); if (word_list) g_print ("%s", word_list); goto finish; From 3aff650e2219d45a9c353dfebe590a638a470727 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Wed, 15 Jun 2016 13:12:39 +0200 Subject: [PATCH 5/6] nmcli: improve connection autocompletion (2/2) Complete the property as we parse the list of properties. This makes it possible to actually complete an unfinished property. E.g: $ nmcli --complete c modify enp0s25 +ipv6.addr +ipv6.addresses +ipv6.addr-gen-mode --- clients/cli/connections.c | 210 +++++++++++++++++++++++------------ clients/cli/nmcli-completion | 20 +--- clients/cli/nmcli.c | 19 +++- 3 files changed, 158 insertions(+), 91 deletions(-) diff --git a/clients/cli/connections.c b/clients/cli/connections.c index 34e998c09e..d612a80937 100644 --- a/clients/cli/connections.c +++ b/clients/cli/connections.c @@ -1590,6 +1590,10 @@ do_connections_show (NmCli *nmc, gboolean active_only, gboolean show_secrets, char *profile_flds = NULL, *active_flds = NULL; GPtrArray *invisibles, *sorted_cons; + /* Not (yet?) supported */ + if (nmc->complete) + goto finish; + if (argc == 0) { char *fields_str; char *fields_all = NMC_FIELDS_CON_SHOW_ALL; @@ -2358,6 +2362,9 @@ do_connection_up (NmCli *nmc, int argc, char **argv) const char *name = NULL; char *line = NULL; + /* Not (yet?) supported */ + if (nmc->complete) + return nmc->return_value; /* * Set default timeout for connection activation. * Activation can take quite a long time, use 90 seconds. @@ -2547,6 +2554,10 @@ do_connection_down (NmCli *nmc, int argc, char **argv) int arg_num = argc; int idx = 0; + /* Not (yet?) supported */ + if (nmc->complete) + return nmc->return_value; + if (nmc->timeout == -1) nmc->timeout = 10; @@ -2901,7 +2912,7 @@ get_valid_settings_array (const char *con_type) } static char * -get_valid_autocompletion_string (const NameItem *array, const NameItem *array_slv, char modifier, const char *prefix) +get_valid_properties_string (const NameItem *array, const NameItem *array_slv, char modifier, const char *prefix) { const NameItem *iter = array; const NmcOutputField *field_iter; @@ -2923,7 +2934,7 @@ get_valid_autocompletion_string (const NameItem *array, const NameItem *array_sl while (field_iter[j].name) { gchar *new; - new = g_strdup_printf ("%s.%s ", iter->name, field_iter[j].name); + new = g_strdup_printf ("%s.%s\n", iter->name, field_iter[j].name); if (g_str_has_prefix (new, prefix)) { if (modifier) g_string_append_c (str, modifier); @@ -2932,7 +2943,7 @@ get_valid_autocompletion_string (const NameItem *array, const NameItem *array_sl g_free (new); if (iter->alias) { - new = g_strdup_printf ("%s.%s ", iter->alias, field_iter[j].name); + new = g_strdup_printf ("%s.%s\n", iter->alias, field_iter[j].name); if (g_str_has_prefix (new, prefix)) { if (modifier) g_string_append_c (str, modifier); @@ -4650,8 +4661,33 @@ do_questionnaire_ip_tunnel (char **local, char **parent) } } +static void +complete_property_name (NmCli *nmc, NMConnection *connection, char modifier, const gchar *prefix) +{ + NMSettingConnection *s_con; + const NameItem *valid_settings_main = NULL; + const NameItem *valid_settings_slave = NULL; + const char *connection_type = NULL; + const char *slave_type = NULL; + gs_free char *slv_type = NULL; + gs_free char *word_list = NULL; + + connection_type = nm_connection_get_connection_type (connection); + s_con = nm_connection_get_setting_connection (connection); + if (s_con) + slave_type = nm_setting_connection_get_slave_type (s_con); + slv_type = g_strdup_printf ("%s-slave", slave_type ? slave_type : "no"); + valid_settings_main = get_valid_settings_array (connection_type); + valid_settings_slave = get_valid_settings_array (slv_type); + + word_list = get_valid_properties_string (valid_settings_main, valid_settings_slave, modifier, prefix); + if (word_list) + g_print ("%s", word_list); +} + static gboolean -read_connection_properties (NMConnection *connection, +read_connection_properties (NmCli *nmc, + NMConnection *connection, int argc, char **argv, GError **error) @@ -4659,13 +4695,12 @@ read_connection_properties (NMConnection *connection, NMSetting *setting; NMSettingConnection *s_con; const char *con_type; - const char *s_dot_p; + const char *s_dot_p = NULL; const char *value; char **strv = NULL; char *slv_type = NULL; const char *setting_name; - gboolean append = FALSE; - gboolean remove = FALSE; + char modifier = '\0'; gboolean success = FALSE; GError *local = NULL; @@ -4693,31 +4728,21 @@ read_connection_properties (NMConnection *connection, next_arg (&argc, &argv); if (!s_dot_p) { + /* XXX: can not happen? */ g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, _("Error: . argument is missing.")); goto finish; } - if (!value) { - g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, - _("Error: value for '%s' is missing."), s_dot_p); - goto finish; - } - /* Empty string will reset the value to default */ - if (value[0] == '\0') - value = NULL; - - if (s_dot_p[0] == '+') { - s_dot_p++; - append = TRUE; - } else if (s_dot_p[0] == '-') { - s_dot_p++; - remove = TRUE; - } + if (s_dot_p[0] == '+' || s_dot_p[0] == '-') + modifier = *s_dot_p++; strv = g_strsplit (s_dot_p, ".", 2); if (g_strv_length (strv) != 2) { - g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, - _("Error: invalid . '%s'."), s_dot_p); + if (nmc->complete) + complete_property_name (nmc, connection, modifier, s_dot_p); + else + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: invalid . '%s'."), s_dot_p); goto finish; } @@ -4752,9 +4777,18 @@ read_connection_properties (NMConnection *connection, goto finish; } - if (!remove) { + if (!value) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: value for '%s' is missing."), s_dot_p); + goto finish; + } + /* Empty string will reset the value to default */ + if (value[0] == '\0') + value = NULL; + + if (modifier != '-') { /* Set/add value */ - if (!append) + if (modifier != '+') nmc_setting_reset_property (setting, property_name, NULL); if (!nmc_setting_set_property (setting, property_name, value, &local)) { g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, @@ -4792,6 +4826,10 @@ read_connection_properties (NMConnection *connection, success = TRUE; finish: + if (!success && nmc->complete) { + complete_property_name (nmc, connection, modifier, s_dot_p); + success = TRUE; + } if (strv) g_strfreev (strv); g_free (slv_type); @@ -6676,7 +6714,7 @@ cleanup_vxlan: if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) return FALSE; - if (!read_connection_properties (connection, argc, argv, error)) + if (!read_connection_properties (nmc, connection, argc, argv, error)) return FALSE; } @@ -6969,7 +7007,10 @@ do_connection_add (NmCli *nmc, int argc, char **argv) rl_attempted_completion_function = (rl_completion_func_t *) nmcli_con_add_tab_completion; - nmc->return_value = NMC_RESULT_SUCCESS; + if (nmc->complete) { + /* Add support to properties and arg completion */ + return nmc->return_value; + } if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, &error)) { g_string_assign (nmc->return_text, error->message); @@ -10174,9 +10215,13 @@ do_connection_modify (NmCli *nmc, const char *selector = NULL; GError *error = NULL; - nmc->return_value = NMC_RESULT_SUCCESS; - if (argc == 0) { + /* + * TODO(?) complete "uuid", "path", "id" or connection name. + * (if we ever will move this here from shell completion script) + if (nmc->complete) { + quit (); + */ g_string_printf (nmc->return_text, _("Error: No arguments provided.")); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; goto finish; @@ -10187,6 +10232,12 @@ do_connection_modify (NmCli *nmc, selector = *argv; if (next_arg (&argc, &argv) != 0) { + /* + * TODO(?): complete uuid, path or id + if (nmc->complete) { + quit (); + } + */ g_string_printf (nmc->return_text, _("Error: %s argument is missing."), selector); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; @@ -10195,11 +10246,7 @@ do_connection_modify (NmCli *nmc, name = *argv; } name = *argv; - if (!name) { - g_string_printf (nmc->return_text, _("Error: connection ID is missing.")); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto finish; - } + connection = nmc_find_connection (nmc->connections, selector, name, NULL); if (!connection) { g_string_printf (nmc->return_text, _("Error: Unknown connection '%s'."), name); @@ -10214,47 +10261,32 @@ do_connection_modify (NmCli *nmc, goto finish; } - /* Query comes from shell autocomplete function */ - if (nmc->complete) { - NMSettingConnection *s_con; - const NameItem *valid_settings_main = NULL; - const NameItem *valid_settings_slave = NULL; - const char *connection_type = NULL; - const char *slave_type = NULL; - gs_free char *slv_type = NULL; - gs_free char *word_list = NULL; - - connection_type = nm_connection_get_connection_type (connection); - s_con = nm_connection_get_setting_connection (connection); - if (s_con) - slave_type = nm_setting_connection_get_slave_type (s_con); - slv_type = g_strdup_printf ("%s-slave", slave_type ? slave_type : "no"); - valid_settings_main = get_valid_settings_array (connection_type); - valid_settings_slave = get_valid_settings_array (slv_type); - - word_list = get_valid_autocompletion_string (valid_settings_main, valid_settings_slave, '\0', ""); - if (word_list) - g_print ("%s", word_list); - goto finish; - } - if (next_arg (&argc, &argv) != 0) { + if (nmc->complete) { + complete_property_name (nmc, NM_CONNECTION (rc), '\0', ""); + goto finish; + } g_string_printf (nmc->return_text, _("Error: . argument is missing.")); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; goto finish; } - if (!read_connection_properties (NM_CONNECTION (rc), argc, argv, &error)) { + if (!read_connection_properties (nmc, NM_CONNECTION (rc), argc, argv, &error)) { g_string_assign (nmc->return_text, error->message); nmc->return_value = error->code; g_clear_error (&error); goto finish; } - update_connection (!temporary, rc, modify_connection_cb, nmc); + if (!nmc->complete) { + update_connection (!temporary, rc, modify_connection_cb, nmc); + nmc->should_wait++; + } - nmc->should_wait++; finish: + /* shell completion - be sure to exit with success without printing errors */ + if (nmc->complete) + nmc->return_value = NMC_RESULT_SUCCESS; return nmc->return_value; } @@ -10312,6 +10344,10 @@ do_connection_clone (NmCli *nmc, gboolean temporary, int argc, char **argv) const char *selector = NULL; char *uuid; + /* Not (yet?) supported */ + if (nmc->complete) + return nmc->return_value; + if (argc == 0) { if (nmc->ask) { name = name_ask = nmc_readline (PROMPT_CONNECTION); @@ -10435,6 +10471,10 @@ do_connection_delete (NmCli *nmc, int argc, char **argv) GString *invalid_cons = NULL; int pos = 0; + /* Not (yet?) supported */ + if (nmc->complete) + return nmc->return_value; + if (nmc->timeout == -1) nmc->timeout = 10; @@ -10567,6 +10607,11 @@ connection_removed (NMClient *client, NMRemoteConnection *con, NmCli *nmc) static NMCResultCode do_connection_monitor (NmCli *nmc, int argc, char **argv) { + + /* Not (yet?) supported */ + if (nmc->complete) + return nmc->return_value; + if (argc == 0) { /* No connections specified. Monitor all. */ int i; @@ -10623,7 +10668,9 @@ do_connection_reload (NmCli *nmc, int argc, char **argv) { GError *error = NULL; - nmc->return_value = NMC_RESULT_SUCCESS; + /* Not (yet?) supported */ + if (nmc->complete) + return nmc->return_value; if (!nm_client_reload_connections (nmc->client, NULL, &error)) { g_string_printf (nmc->return_text, _("Error: failed to reload connections: %s."), @@ -10642,7 +10689,9 @@ do_connection_load (NmCli *nmc, int argc, char **argv) char **filenames, **failures = NULL; int i; - nmc->return_value = NMC_RESULT_SUCCESS; + /* Not (yet?) supported */ + if (nmc->complete) + return nmc->return_value; if (argc == 0) { g_string_printf (nmc->return_text, _("Error: No connection specified.")); @@ -10688,6 +10737,10 @@ do_connection_import (NmCli *nmc, gboolean temporary, int argc, char **argv) NMVpnEditorPlugin *plugin; gs_free char *service_type = NULL; + /* Not (yet?) supported */ + if (nmc->complete) + return nmc->return_value; + if (argc == 0) { if (nmc->ask) { type_ask = nmc_readline (PROMPT_IMPORT_TYPE); @@ -10804,6 +10857,10 @@ do_connection_export (NmCli *nmc, int argc, char **argv) GError *error = NULL; char tmpfile[] = "/tmp/nmcli-export-temp-XXXXXX"; + /* Not (yet?) supported */ + if (nmc->complete) + return nmc->return_value; + if (argc == 0) { if (nmc->ask) { name_ask = nmc_readline (PROMPT_VPN_CONNECTION); @@ -11113,8 +11170,10 @@ do_connections (NmCli *nmc, int argc, char **argv) /* Check whether NetworkManager is running */ if (!nm_client_get_nm_running (nmc->client)) { - g_string_printf (nmc->return_text, _("Error: NetworkManager is not running.")); - nmc->return_value = NMC_RESULT_ERROR_NM_NOT_RUNNING; + if (!nmc->complete) { + g_string_printf (nmc->return_text, _("Error: NetworkManager is not running.")); + nmc->return_value = NMC_RESULT_ERROR_NM_NOT_RUNNING; + } return nmc->return_value; } @@ -11169,6 +11228,9 @@ do_connections (NmCli *nmc, int argc, char **argv) } else if (matches(*argv, "add") == 0) { nmc->return_value = do_connection_add (nmc, argc-1, argv+1); } else if (matches(*argv, "edit") == 0) { + /* edit with --complete? Should not happen */ + if (nmc->complete) + goto opt_error; nmc->should_wait++; editor_thread_data.nmc = nmc; editor_thread_data.argc = argc - 1; @@ -11186,6 +11248,8 @@ do_connections (NmCli *nmc, int argc, char **argv) next_arg (&argc, &argv); if (nmc_arg_is_option (*argv, "temporary")) { + if (nmc->complete) + goto opt_error; temporary = TRUE; next_arg (&argc, &argv); } @@ -11195,6 +11259,8 @@ do_connections (NmCli *nmc, int argc, char **argv) next_arg (&argc, &argv); if (nmc_arg_is_option (*argv, "temporary")) { + if (nmc->complete) + goto opt_error; temporary = TRUE; next_arg (&argc, &argv); } @@ -11204,6 +11270,8 @@ do_connections (NmCli *nmc, int argc, char **argv) next_arg (&argc, &argv); if (nmc_arg_is_option (*argv, "temporary")) { + if (nmc->complete) + goto opt_error; temporary = TRUE; next_arg (&argc, &argv); } @@ -11213,6 +11281,8 @@ do_connections (NmCli *nmc, int argc, char **argv) } else if (matches(*argv, "monitor") == 0) { nmc->return_value = do_connection_monitor (nmc, argc-1, argv+1); } else { + if (nmc->complete) + goto opt_error; usage (); g_string_printf (nmc->return_text, _("Error: '%s' is not valid 'connection' command."), *argv); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; @@ -11222,8 +11292,10 @@ do_connections (NmCli *nmc, int argc, char **argv) return nmc->return_value; opt_error: - g_string_printf (nmc->return_text, _("Error: %s."), error->message); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + if (!nmc->complete) { + g_string_printf (nmc->return_text, _("Error: %s."), error->message); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + } g_error_free (error); return nmc->return_value; } diff --git a/clients/cli/nmcli-completion b/clients/cli/nmcli-completion index 7b653da414..d5bd80de5c 100644 --- a/clients/cli/nmcli-completion +++ b/clients/cli/nmcli-completion @@ -761,26 +761,14 @@ _nmcli_compl_COMMAND_nl() { _nmcli_compl_PROPERTIES() { - while [[ "${#words[@]}" -gt 0 ]]; do - if [[ ${#words[@]} -le 1 ]]; then - local PREFIX="" item list="" - - if [[ "${words[0]:0:1}" == [+-] ]]; then - PREFIX="${words[0]:0:1}" - fi - item="$(nmcli c modify --complete "$@" 2>/dev/null)" - ### add prefix to each element - for item in $item; do - list="${list}${PREFIX}${item} " - done - _nmcli_list "$list" - return 0 - elif [[ ${#words[@]} -le 2 ]]; then + while [[ ${#words[@]} -gt 0 ]]; do + if [[ ${#words[@]} -eq 1 ]]; then + _nmcli_list_nl "$(nmcli --complete-args connection modify "$@" "${words[@]}" 2>/dev/null)" return 0 fi _nmcli_array_delete_at words 0 1 done - _nmcli_list "$(nmcli c modify --complete "$@" 2>/dev/null)" + return 0 } _nmcli() diff --git a/clients/cli/nmcli.c b/clients/cli/nmcli.c index 730f6a1d49..84bb4a23df 100644 --- a/clients/cli/nmcli.c +++ b/clients/cli/nmcli.c @@ -154,7 +154,18 @@ parse_command_line (NmCli *nmc, int argc, char **argv) base = argv[0]; else base++; - + if (argc > 1 && nm_streq (argv[1], "--complete-args")) { + /* We (currently?) support --complete-args for "connection" command only: + * ignore any other command when this option is enabled as means we are in + * autocompletion mode (so we should just quit and don't print anything). + * This would help us to ensure shell autocompletion after NM package downgrade + * if we ever will enable --complete-args for other commands */ + if ((argc == 2) || !nm_streq0 (argv[2], "connection")) + return nmc->return_value; + nmc->complete = TRUE; + argv[1] = argv[0]; + argc--; argv++; + } /* parse options */ while (argc > 1) { char *opt = argv[1]; @@ -256,8 +267,6 @@ parse_command_line (NmCli *nmc, int argc, char **argv) /* ignore for backward compatibility */ } else if (matches (opt, "-ask") == 0) { nmc->ask = TRUE; - } else if (matches (opt, "-complete") == 0) { - nmc->complete = TRUE; } else if (matches (opt, "-show-secrets") == 0) { nmc->show_secrets = TRUE; } else if (matches (opt, "-wait") == 0) { @@ -547,6 +556,7 @@ nmc_init (NmCli *nmc) nmc->output_data = g_ptr_array_new_full (20, g_free); memset (&nmc->print_fields, '\0', sizeof (NmcPrintFields)); nmc->ask = FALSE; + nmc->complete = FALSE; nmc->show_secrets = FALSE; nmc->use_colors = NMC_USE_COLOR_AUTO; nmc->in_editor = FALSE; @@ -630,9 +640,6 @@ main (int argc, char *argv[]) loop = g_main_loop_new (NULL, FALSE); /* create main loop */ g_main_loop_run (loop); /* run main loop */ - if (nm_cli.complete) - nm_cli.return_value = NMC_RESULT_SUCCESS; - /* Print result descripting text */ if (nm_cli.return_value != NMC_RESULT_SUCCESS) { g_printerr ("%s\n", nm_cli.return_text->str); From 00b362bcf2052671788ce03d6cafb253904e991b Mon Sep 17 00:00:00 2001 From: Francesco Giudici Date: Sat, 18 Jun 2016 21:17:57 +0200 Subject: [PATCH 6/6] nmcli: add support to shortnames for connection properties autocompletion --- clients/cli/connections.c | 101 ++++++++++++++++++++++++++++---------- 1 file changed, 74 insertions(+), 27 deletions(-) diff --git a/clients/cli/connections.c b/clients/cli/connections.c index d612a80937..c10db7b446 100644 --- a/clients/cli/connections.c +++ b/clients/cli/connections.c @@ -2911,47 +2911,90 @@ get_valid_settings_array (const char *con_type) return NULL; } +/* get_valid_properties_string: + * @array: base properties for the current connection type + * @array_slv: slave properties (or ipv4/ipv6 ones) for the current connection type + * @modifier: to prepend to each element of the returned list + * @prefix: only properties matching the prefix will be returned + * @postfix: required prefix on the property args; if a empty string is passed, is + * assumed that the @prefix is a shortcut, so it should not be completed + * but left as is (and an additional check for shortcut ambiguity is performed) + * + * Returns a list of properties compatible with the current connection type + * for the shell autocompletion functionality. + * + * Returns: list of property.arg elements + */ static char * -get_valid_properties_string (const NameItem *array, const NameItem *array_slv, char modifier, const char *prefix) +get_valid_properties_string (const NameItem *array, + const NameItem *array_slv, + char modifier, + const char *prefix, + const char *postfix) { const NameItem *iter = array; const NmcOutputField *field_iter; + const char *prop_name = NULL; GString *str; - int i; + int i, j; + + g_return_val_if_fail (prefix, NULL); str = g_string_sized_new (1024); for (i = 0; i < 2; i++, iter = array_slv) { while (iter && iter->name) { - int j = 0; + if ( !(g_str_has_prefix (iter->name, prefix)) + && (!(iter->alias) || !g_str_has_prefix (iter->alias, prefix))) { + iter++; + continue; + } + /* If postix (so prefix is terminated by a dot), check + * that prefix is not ambiguous */ + if (postfix) { + if (prop_name) + return g_string_free (str, TRUE); + prop_name = prefix; + } else { + prop_name = iter->name; + } - while ((nmc_fields_settings_names[j].name) && - g_strcmp0 (iter->name, nmc_fields_settings_names[j].name)) + /* Search the array with the arguments of the current property */ + j = 0; + while (!nm_streq0 (iter->name, nmc_fields_settings_names[j].name)) { + g_assert (nmc_fields_settings_names[j].name); j++; - + } field_iter = nmc_fields_settings_names[j].group; + j = 0; while (field_iter[j].name) { gchar *new; + const char *arg_name = field_iter[j].name; - new = g_strdup_printf ("%s.%s\n", iter->name, field_iter[j].name); - if (g_str_has_prefix (new, prefix)) { + /* If required, expand the alias too */ + if (!postfix && iter->alias) { if (modifier) g_string_append_c (str, modifier); + new = g_strdup_printf ("%s.%s\n", + iter->alias, + arg_name); g_string_append (str, new); - } - g_free (new); - - if (iter->alias) { - new = g_strdup_printf ("%s.%s\n", iter->alias, field_iter[j].name); - if (g_str_has_prefix (new, prefix)) { - if (modifier) - g_string_append_c (str, modifier); - g_string_append (str, new); - } g_free (new); } + if (postfix && !g_str_has_prefix (arg_name, postfix)) { + j++; + continue; + } + + if (modifier) + g_string_append_c (str, modifier); + new = g_strdup_printf ("%s.%s\n", + prop_name, + arg_name); + g_string_append (str, new); + g_free (new); j++; } iter++; @@ -4662,7 +4705,10 @@ do_questionnaire_ip_tunnel (char **local, char **parent) } static void -complete_property_name (NmCli *nmc, NMConnection *connection, char modifier, const gchar *prefix) +complete_property_name (NmCli *nmc, NMConnection *connection, + char modifier, + const gchar *prefix, + const gchar *postfix) { NMSettingConnection *s_con; const NameItem *valid_settings_main = NULL; @@ -4680,7 +4726,7 @@ complete_property_name (NmCli *nmc, NMConnection *connection, char modifier, con valid_settings_main = get_valid_settings_array (connection_type); valid_settings_slave = get_valid_settings_array (slv_type); - word_list = get_valid_properties_string (valid_settings_main, valid_settings_slave, modifier, prefix); + word_list = get_valid_properties_string (valid_settings_main, valid_settings_slave, modifier, prefix, postfix); if (word_list) g_print ("%s", word_list); } @@ -4738,11 +4784,12 @@ read_connection_properties (NmCli *nmc, strv = g_strsplit (s_dot_p, ".", 2); if (g_strv_length (strv) != 2) { - if (nmc->complete) - complete_property_name (nmc, connection, modifier, s_dot_p); - else - g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, - _("Error: invalid . '%s'."), s_dot_p); + if (nmc->complete) { + complete_property_name (nmc, connection, modifier, s_dot_p, NULL); + break; + } + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: invalid . '%s'."), s_dot_p); goto finish; } @@ -4827,7 +4874,7 @@ read_connection_properties (NmCli *nmc, success = TRUE; finish: if (!success && nmc->complete) { - complete_property_name (nmc, connection, modifier, s_dot_p); + complete_property_name (nmc, connection, modifier, strv[0], strv[1]); success = TRUE; } if (strv) @@ -10263,7 +10310,7 @@ do_connection_modify (NmCli *nmc, if (next_arg (&argc, &argv) != 0) { if (nmc->complete) { - complete_property_name (nmc, NM_CONNECTION (rc), '\0', ""); + complete_property_name (nmc, NM_CONNECTION (rc), '\0', "", NULL); goto finish; } g_string_printf (nmc->return_text, _("Error: . argument is missing."));