From fdd40b6a6821eaced26a08ca6d2ad731ad506071 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 4 Apr 2017 15:23:39 +0200 Subject: [PATCH] cli: implement new nmc_print() command to generically output cli data We already have - data sources (nm_cli, connections or settings) - meta data information how to access the data sources (NMMetaAbstractInfo, NmcMetaGenericInfo, NMMetaPropertyInfo) Add now a generic way to output cli data using nmc_print(). It gets a list of data-sources (@targets) and a list of available fields (meta data). It also gets cli configuration (NmcConfig) and field selector strings (@field_str). Based on that, it should output the desired data. This is intended to replaces the previous approach, where functions like show_nm_status() have full knowledge about how to access the data and create an intermediate output format (NmcOutputData, NmcOutputField) that was printed via print_data(). show_nm_status() contained both knowledge about the data itself (how to print a value) and intimate knoweledge about the output intermediate format. Also, the intermediate format is hard to understand. For example, sometimes we put the field prefix in NmcOutputField at index 0 and via the NmcOfFlags we control how to output the data. Clearly separate the responsibilities. - The meta data (NmcMetaGenericInfo) is only concerned with converting a data source to a string (or a color format). - the field selection (@field_str) only cares about parsing the list of NMMetaAbstractInfo. - _print_fill() populates a table with output values and header entries. - _print_do() prints the previously prepared table. The advantage is that if you want to change anything, you only need to touch a particular part. This is only a show-case for `nmcli general status`. Parts are still un-implemented and will follow. This changes behavior for --terse mode: the values are now no longer translated: $ LANG=de_DE.utf8 nmcli -t --mode multiline general --- clients/cli/connections.c | 4 +- clients/cli/general.c | 191 +++-- clients/cli/utils.c | 916 ++++++++++++++++++++---- clients/cli/utils.h | 33 +- clients/common/nm-meta-setting-access.c | 24 + clients/common/nm-meta-setting-access.h | 8 + clients/common/nm-meta-setting-desc.h | 27 + 7 files changed, 964 insertions(+), 239 deletions(-) diff --git a/clients/cli/connections.c b/clients/cli/connections.c index c981faecd5..d9ed01c719 100644 --- a/clients/cli/connections.c +++ b/clients/cli/connections.c @@ -1391,8 +1391,8 @@ split_required_fields_for_con_show (const char *input, else if (!strcasecmp (*iter, CON_SHOW_DETAIL_GROUP_ACTIVE)) group_active = TRUE; else { - char *allowed1 = nmc_get_allowed_fields ((const NMMetaAbstractInfo *const*) nm_meta_setting_infos_editor_p ()); - char *allowed2 = nmc_get_allowed_fields ((const NMMetaAbstractInfo *const*) nmc_fields_con_active_details_groups); + char *allowed1 = nmc_get_allowed_fields ((const NMMetaAbstractInfo *const*) nm_meta_setting_infos_editor_p (), NULL); + char *allowed2 = nmc_get_allowed_fields ((const NMMetaAbstractInfo *const*) nmc_fields_con_active_details_groups, NULL); g_set_error (error, NMCLI_ERROR, 0, _("invalid field '%s'; allowed fields: %s and %s, or %s,%s"), *iter, allowed1, allowed2, CON_SHOW_DETAIL_GROUP_PROFILE, CON_SHOW_DETAIL_GROUP_ACTIVE); g_free (allowed1); diff --git a/clients/cli/general.c b/clients/cli/general.c index fd4c326a51..82f878b692 100644 --- a/clients/cli/general.c +++ b/clients/cli/general.c @@ -108,20 +108,107 @@ connectivity_to_color (NMConnectivityState connectivity) /*****************************************************************************/ -static const NmcMetaGenericInfo *const nmc_fields_nm_status[] = { - NMC_META_GENERIC ("RUNNING"), /* 0 */ - NMC_META_GENERIC ("VERSION"), /* 1 */ - NMC_META_GENERIC ("STATE"), /* 2 */ - NMC_META_GENERIC ("STARTUP"), /* 3 */ - NMC_META_GENERIC ("CONNECTIVITY"), /* 4 */ - NMC_META_GENERIC ("NETWORKING"), /* 5 */ - NMC_META_GENERIC ("WIFI-HW"), /* 6 */ - NMC_META_GENERIC ("WIFI"), /* 7 */ - NMC_META_GENERIC ("WWAN-HW"), /* 8 */ - NMC_META_GENERIC ("WWAN"), /* 9 */ - NMC_META_GENERIC ("WIMAX-HW"), /* 10 */ - NMC_META_GENERIC ("WIMAX"), /* 11 */ - NULL, +static const NmcMetaGenericInfo *const metagen_general_status[]; + +static gconstpointer +_metagen_general_status_get_fcn (const NMMetaEnvironment *environment, + gpointer environment_user_data, + const NmcMetaGenericInfo *info, + gpointer target, + NMMetaAccessorGetType get_type, + NMMetaAccessorGetFlags get_flags, + gpointer *out_to_free) +{ + NmCli *nmc = target; + const char *value; + gboolean v_bool; + NMState state; + NMConnectivityState connectivity; + +#define HANDLE_TERMFORMAT(color) \ + G_STMT_START { \ + if (get_type == NM_META_ACCESSOR_GET_TYPE_TERMFORMAT) \ + return nm_meta_termformat_pack ((color), NM_META_TERM_FORMAT_NORMAL); \ + } G_STMT_END + + switch (info->info_type) { + case NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_RUNNING: + HANDLE_TERMFORMAT (NM_META_TERM_COLOR_NORMAL); + value = N_("running"); + goto translate_and_out; + case NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_VERSION: + HANDLE_TERMFORMAT (NM_META_TERM_COLOR_NORMAL); + value = nm_client_get_version (nmc->client); + goto clone_and_out; + case NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_STATE: + state = nm_client_get_state (nmc->client); + HANDLE_TERMFORMAT (state_to_color (state)); + value = nm_state_to_string_no_l10n (state); + goto translate_and_out; + case NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_STARTUP: + v_bool = nm_client_get_startup (nmc->client); + HANDLE_TERMFORMAT (v_bool ? NM_META_TERM_COLOR_YELLOW : NM_META_TERM_COLOR_GREEN); + value = v_bool ? N_("starting") : N_("started"); + goto translate_and_out; + case NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_CONNECTIVITY: + connectivity = nm_client_get_connectivity (nmc->client); + HANDLE_TERMFORMAT (connectivity_to_color (connectivity)); + value = nm_connectivity_to_string_no_l10n (connectivity); + goto translate_and_out; + case NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_NETWORKING: + v_bool = nm_client_networking_get_enabled (nmc->client); + goto enabled_out; + case NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WIFI_HW: + v_bool = nm_client_wireless_hardware_get_enabled (nmc->client); + goto enabled_out; + case NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WIFI: + v_bool = nm_client_wireless_get_enabled (nmc->client); + goto enabled_out; + case NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WWAN_HW: + v_bool = nm_client_wwan_hardware_get_enabled (nmc->client); + goto enabled_out; + case NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WWAN: + v_bool = nm_client_wwan_get_enabled (nmc->client); + goto enabled_out; + case NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WIMAX_HW: + case NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WIMAX: + /* deprected fields. Don't return anything. */ + return NULL; + default: + break; + } + + g_return_val_if_reached (NULL); + +enabled_out: + HANDLE_TERMFORMAT (v_bool ? NM_META_TERM_COLOR_GREEN : NM_META_TERM_COLOR_RED); + value = v_bool ? N_("enabled") : N_("disabled"); + goto translate_and_out; + +clone_and_out: + return (*out_to_free = g_strdup (value)); + +translate_and_out: + if (get_type == NM_META_ACCESSOR_GET_TYPE_PRETTY) + return _(value); + return value; +} + +static const NmcMetaGenericInfo *const metagen_general_status[_NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_NUM + 1] = { +#define _METAGEN_GENERAL_STATUS(type, name) \ + [type] = NMC_META_GENERIC(name, .info_type = type, .get_fcn = _metagen_general_status_get_fcn) + _METAGEN_GENERAL_STATUS (NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_RUNNING, "RUNNING"), + _METAGEN_GENERAL_STATUS (NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_VERSION, "VERSION"), + _METAGEN_GENERAL_STATUS (NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_STATE, "STATE"), + _METAGEN_GENERAL_STATUS (NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_STARTUP, "STARTUP"), + _METAGEN_GENERAL_STATUS (NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_CONNECTIVITY, "CONNECTIVITY"), + _METAGEN_GENERAL_STATUS (NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_NETWORKING, "NETWORKING"), + _METAGEN_GENERAL_STATUS (NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WIFI_HW, "WIFI-HW"), + _METAGEN_GENERAL_STATUS (NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WIFI, "WIFI"), + _METAGEN_GENERAL_STATUS (NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WWAN_HW, "WWAN-HW"), + _METAGEN_GENERAL_STATUS (NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WWAN, "WWAN"), + _METAGEN_GENERAL_STATUS (NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WIMAX_HW, "WIMAX-HW"), + _METAGEN_GENERAL_STATUS (NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WIMAX, "WIMAX"), }; #define NMC_FIELDS_NM_STATUS_ALL "RUNNING,VERSION,STATE,STARTUP,CONNECTIVITY,NETWORKING,WIFI-HW,WIFI,WWAN-HW,WWAN" #define NMC_FIELDS_NM_STATUS_SWITCH "NETWORKING,WIFI-HW,WIFI,WWAN-HW,WWAN" @@ -306,19 +393,10 @@ quit (void) static gboolean show_nm_status (NmCli *nmc, const char *pretty_header_name, const char *print_flds) { - gboolean startup = FALSE; - NMState state = NM_STATE_UNKNOWN; - NMConnectivityState connectivity = NM_CONNECTIVITY_UNKNOWN; - gboolean net_enabled; - gboolean wireless_hw_enabled, wireless_enabled; - gboolean wwan_hw_enabled, wwan_enabled; - GError *error = NULL; + gs_free_error GError *error = NULL; const char *fields_str; const char *fields_all = print_flds ? print_flds : NMC_FIELDS_NM_STATUS_ALL; const char *fields_common = print_flds ? print_flds : NMC_FIELDS_NM_STATUS_COMMON; - const NMMetaAbstractInfo *const*tmpl; - NmcOutputField *arr; - NMC_OUTPUT_DATA_DEFINE_SCOPED (out); if (!nmc->required_fields || strcasecmp (nmc->required_fields, "common") == 0) fields_str = fields_common; @@ -327,57 +405,16 @@ show_nm_status (NmCli *nmc, const char *pretty_header_name, const char *print_fl else fields_str = nmc->required_fields; - tmpl = (const NMMetaAbstractInfo *const*) nmc_fields_nm_status; - out_indices = parse_output_fields (fields_str, tmpl, FALSE, NULL, &error); - - if (error) { + if (!nmc_print (&nmc->nmc_config, + (gpointer[]) { nmc, NULL }, + pretty_header_name ?: N_("NetworkManager status"), + (const NMMetaAbstractInfo *const*) metagen_general_status, + fields_str, + &error)) { g_string_printf (nmc->return_text, _("Error: only these fields are allowed: %s"), fields_all); - g_error_free (error); nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; return FALSE; } - - state = nm_client_get_state (nmc->client); - startup = nm_client_get_startup (nmc->client); - connectivity = nm_client_get_connectivity (nmc->client); - net_enabled = nm_client_networking_get_enabled (nmc->client); - wireless_hw_enabled = nm_client_wireless_hardware_get_enabled (nmc->client); - wireless_enabled = nm_client_wireless_get_enabled (nmc->client); - wwan_hw_enabled = nm_client_wwan_hardware_get_enabled (nmc->client); - wwan_enabled = nm_client_wwan_get_enabled (nmc->client); - - arr = nmc_dup_fields_array (tmpl, NMC_OF_FLAG_MAIN_HEADER_ADD | NMC_OF_FLAG_FIELD_NAMES); - g_ptr_array_add (out.output_data, arr); - - arr = nmc_dup_fields_array (tmpl, 0); - set_val_strc (arr, 0, _("running")); - set_val_strc (arr, 1, nm_client_get_version (nmc->client)); - set_val_strc (arr, 2, nm_state_to_string (state)); - set_val_strc (arr, 3, startup ? _("starting") : _("started")); - set_val_strc (arr, 4, nm_connectivity_to_string (connectivity)); - set_val_strc (arr, 5, net_enabled ? _("enabled") : _("disabled")); - set_val_strc (arr, 6, wireless_hw_enabled ? _("enabled") : _("disabled")); - set_val_strc (arr, 7, wireless_enabled ? _("enabled") : _("disabled")); - set_val_strc (arr, 8, wwan_hw_enabled ? _("enabled") : _("disabled")); - set_val_strc (arr, 9, wwan_enabled ? _("enabled") : _("disabled")); - - /* Set colors */ - arr[2].color = state_to_color (state); - arr[3].color = startup ? NM_META_TERM_COLOR_YELLOW : NM_META_TERM_COLOR_GREEN; - arr[4].color = connectivity_to_color (connectivity); - arr[5].color = net_enabled ? NM_META_TERM_COLOR_GREEN : NM_META_TERM_COLOR_RED; - arr[6].color = wireless_hw_enabled ? NM_META_TERM_COLOR_GREEN : NM_META_TERM_COLOR_RED; - arr[7].color = wireless_enabled ? NM_META_TERM_COLOR_GREEN : NM_META_TERM_COLOR_RED; - arr[8].color = wwan_hw_enabled ? NM_META_TERM_COLOR_GREEN : NM_META_TERM_COLOR_RED; - arr[9].color = wwan_enabled ? NM_META_TERM_COLOR_GREEN : NM_META_TERM_COLOR_RED; - - g_ptr_array_add (out.output_data, arr); - - print_data_prepare_width (out.output_data); - print_data (&nmc->nmc_config, out_indices, - pretty_header_name ?: _("NetworkManager status"), - 0, &out); - return TRUE; } @@ -860,7 +897,7 @@ do_networking_connectivity (NmCli *nmc, int argc, char **argv) if (!argc) { /* no arguments -> get current state */ - nmc_switch_show (nmc, NMC_FIELDS_NM_CONNECTIVITY, _("Connectivity")); + nmc_switch_show (nmc, NMC_FIELDS_NM_CONNECTIVITY, N_("Connectivity")); } else if (matches (*argv, "check")) { gs_free_error GError *error = NULL; @@ -872,7 +909,7 @@ do_networking_connectivity (NmCli *nmc, int argc, char **argv) g_string_printf (nmc->return_text, _("Error: %s."), error->message); nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; } else - nmc_switch_show (nmc, NMC_FIELDS_NM_CONNECTIVITY, _("Connectivity")); + nmc_switch_show (nmc, NMC_FIELDS_NM_CONNECTIVITY, N_("Connectivity")); } else { usage_networking (); g_string_printf (nmc->return_text, _("Error: 'networking' command '%s' is not valid."), *argv); @@ -889,7 +926,7 @@ do_networking_show (NmCli *nmc, int argc, char **argv) if (nmc->complete) return nmc->return_value; - nmc_switch_show (nmc, NMC_FIELDS_NM_NETWORKING, _("Networking")); + nmc_switch_show (nmc, NMC_FIELDS_NM_NETWORKING, N_("Networking")); return nmc->return_value; } @@ -924,7 +961,7 @@ do_radio_all (NmCli *nmc, int argc, char **argv) return nmc->return_value; /* no argument, show all radio switches */ - show_nm_status (nmc, _("Radio switches"), NMC_FIELDS_NM_STATUS_RADIO); + show_nm_status (nmc, N_("Radio switches"), NMC_FIELDS_NM_STATUS_RADIO); } else { if (nmc->complete) { if (argc == 1) @@ -954,7 +991,7 @@ do_radio_wifi (NmCli *nmc, int argc, char **argv) return nmc->return_value; /* no argument, show current WiFi state */ - nmc_switch_show (nmc, NMC_FIELDS_NM_WIFI, _("Wi-Fi radio switch")); + nmc_switch_show (nmc, NMC_FIELDS_NM_WIFI, N_("Wi-Fi radio switch")); } else { if (nmc->complete) { if (argc == 1) @@ -981,7 +1018,7 @@ do_radio_wwan (NmCli *nmc, int argc, char **argv) return nmc->return_value; /* no argument, show current WWAN (mobile broadband) state */ - nmc_switch_show (nmc, NMC_FIELDS_NM_WWAN, _("WWAN radio switch")); + nmc_switch_show (nmc, NMC_FIELDS_NM_WWAN, N_("WWAN radio switch")); } else { if (nmc->complete) { if (argc == 1) diff --git a/clients/cli/utils.c b/clients/cli/utils.c index f24b74ccca..ddefb4c2a3 100644 --- a/clients/cli/utils.c +++ b/clients/cli/utils.c @@ -79,10 +79,19 @@ _meta_type_nmc_generic_info_get_fcn (const NMMetaEnvironment *environment, { const NmcMetaGenericInfo *info = (const NmcMetaGenericInfo *) abstract_info; - nm_assert (out_to_free && !*out_to_free); + nm_assert (!out_to_free || !*out_to_free); if (!info->get_fcn) g_return_val_if_reached (NULL); + if (!NM_IN_SET (get_type, + NM_META_ACCESSOR_GET_TYPE_PARSABLE, + NM_META_ACCESSOR_GET_TYPE_PRETTY, + NM_META_ACCESSOR_GET_TYPE_TERMFORMAT)) + g_return_val_if_reached (NULL); + + /* omitting the out_to_free value is only allowed for TERMFORMAT. */ + nm_assert (out_to_free || NM_IN_SET (get_type, NM_META_ACCESSOR_GET_TYPE_TERMFORMAT)); + return info->get_fcn (environment, environment_user_data, info, target, get_type, get_flags, @@ -733,26 +742,185 @@ nmc_free_output_field_values (NmcOutputField fields_array[]) } } +/*****************************************************************************/ + typedef struct { guint idx; - gsize offset_plus_1; + gsize self_offset_plus_1; + gsize sub_offset_plus_1; } OutputSelectionItem; -NmcOutputSelection * -nmc_output_selection_create (const NMMetaAbstractInfo *const* fields_array, - const char *fields_str, - GError **error) +static NmcOutputSelection * +_output_selection_pack (const NMMetaAbstractInfo *const* fields_array, + GArray *array, + GString *str) +{ + NmcOutputSelection *result; + guint i; + guint len; + + len = array ? array->len : 0; + + /* re-organize the collected output data in one buffer that can be freed using + * g_free(). This makes allocation more complicated, but saves us from special + * handling for free. */ + result = g_malloc0 (sizeof (NmcOutputSelection) + (len * sizeof (NmcOutputSelectionItem)) + (str ? str->len : 0)); + *((guint *) &result->num) = len; + if (len > 0) { + char *pdata = &((char *) result)[sizeof (NmcOutputSelection) + (len * sizeof (NmcOutputSelectionItem))]; + + if (str) + memcpy (pdata, str->str, str->len); + for (i = 0; i < len; i++) { + const OutputSelectionItem *a = &g_array_index (array, OutputSelectionItem, i); + NmcOutputSelectionItem *p = (NmcOutputSelectionItem *) &result->items[i]; + + p->info = fields_array[a->idx]; + p->idx = a->idx; + if (a->self_offset_plus_1 > 0) + p->self_selection = &pdata[a->self_offset_plus_1 - 1]; + if (a->sub_offset_plus_1 > 0) + p->sub_selection = &pdata[a->sub_offset_plus_1 - 1]; + } + } + + return result; +} + +static gboolean +_output_selection_select_one (const NMMetaAbstractInfo *const* fields_array, + const char *fields_prefix, + const char *fields_str, + gboolean validate_nested, + GArray **p_array, + GString **p_str, + GError **error) +{ + guint i, j; + const char *i_name; + const char *right; + gboolean found = FALSE; + const NMMetaAbstractInfo *fields_array_failure = NULL; + gs_free char *fields_str_clone = NULL; + + nm_assert (fields_str); + nm_assert (p_array); + nm_assert (p_str); + nm_assert (!error || !*error); + + right = strchr (fields_str, '.'); + if (right) { + fields_str_clone = g_strdup (fields_str); + fields_str_clone[right - fields_str] = '\0'; + i_name = fields_str_clone; + right = &fields_str_clone[right - fields_str + 1]; + } else + i_name = fields_str; + + if (!fields_array) + goto not_found; + + for (i = 0; fields_array[i]; i++) { + const NMMetaAbstractInfo *fi = fields_array[i]; + + if (g_ascii_strcasecmp (i_name, nm_meta_abstract_info_get_name (fi)) != 0) + continue; + + if (!right || !validate_nested) { + found = TRUE; + break; + } + + if (fi->meta_type == &nm_meta_type_setting_info_editor) { + const NMMetaSettingInfoEditor *fi_s = &fi->as.setting_info; + + for (j = 1; j < fi_s->properties_num; j++) { + if (g_ascii_strcasecmp (right, fi_s->properties[j].property_name) == 0) { + found = TRUE; + break; + } + } + } else if (fi->meta_type == &nmc_meta_type_generic_info) { + const NmcMetaGenericInfo *fi_g = (const NmcMetaGenericInfo *) fi; + + for (j = 0; fi_g->nested && fi_g->nested[j]; j++) { + if (g_ascii_strcasecmp (right, nm_meta_abstract_info_get_name ((const NMMetaAbstractInfo *) fi_g->nested[j])) == 0) { + found = TRUE; + break; + } + } + } + fields_array_failure = fields_array[i]; + break; + } + + if (!found) { +not_found: + if ( !right + && !fields_prefix + && ( !g_ascii_strcasecmp (i_name, "all") + || !g_ascii_strcasecmp (i_name, "common"))) + g_set_error (error, NMCLI_ERROR, 0, _("field '%s' has to be alone"), i_name); + else { + gs_free char *allowed_fields = NULL; + + if (fields_array_failure) { + gs_free char *p = NULL; + + if (fields_prefix) { + p = g_strdup_printf ("%s.%s", fields_prefix, + nm_meta_abstract_info_get_name (fields_array_failure)); + } + allowed_fields = nmc_get_allowed_fields_nested (fields_array_failure, p); + } else + allowed_fields = nmc_get_allowed_fields (fields_array, NULL); + + g_set_error (error, NMCLI_ERROR, 1, _("invalid field '%s%s%s%s%s'; %s%s%s"), + fields_prefix ?: "", fields_prefix ? "." : "", + i_name, right ? "." : "", right ?: "", + NM_PRINT_FMT_QUOTED (allowed_fields, "allowed fields: ", allowed_fields, "", "no fields")); + } + return FALSE; + } + + { + GString *str; + OutputSelectionItem s = { + .idx = i, + }; + + if (!*p_str) + *p_str = g_string_sized_new (64); + str = *p_str; + + s.self_offset_plus_1 = str->len + 1; + if (fields_prefix) { + g_string_append (str, fields_prefix); + g_string_append_c (str, '.'); + } + g_string_append_len (str, i_name, strlen (i_name) + 1); + + if (right) { + s.sub_offset_plus_1 = str->len + 1; + g_string_append_len (str, right, strlen (right) + 1); + } + + if (!*p_array) + *p_array = g_array_new (FALSE, FALSE, sizeof (OutputSelectionItem)); + g_array_append_val (*p_array, s); + } + + return TRUE; +} + +static NmcOutputSelection * +_output_selection_create_all (const NMMetaAbstractInfo *const* fields_array) { gs_unref_array GArray *array = NULL; - nm_auto_free_gstring GString *str = NULL; - guint i, j; - NmcOutputSelection *result; + guint i; - g_return_val_if_fail (!error || !*error, NULL); - - array = g_array_new (FALSE, FALSE, sizeof (OutputSelectionItem)); - - if (!fields_str) { + if (fields_array) { + array = g_array_new (FALSE, FALSE, sizeof (OutputSelectionItem)); for (i = 0; fields_array[i]; i++) { OutputSelectionItem s = { .idx = i, @@ -760,131 +928,220 @@ nmc_output_selection_create (const NMMetaAbstractInfo *const* fields_array, g_array_append_val (array, s); } - } else { - gs_free char *fields_str_clone = NULL; - char *fields_str_cur; - char *fields_str_next; - - fields_str_clone = g_strdup (fields_str); - for (fields_str_cur = fields_str_clone; fields_str_cur; fields_str_cur = fields_str_next) { - const char *i_name; - const char *right = NULL; - gboolean found = FALSE; - const NMMetaAbstractInfo *fields_array_failure = NULL; - - fields_str_cur = nm_str_skip_leading_spaces (fields_str_cur); - fields_str_next = strchr (fields_str_cur, ','); - if (fields_str_next) - *fields_str_next++ = '\0'; - - g_strchomp (fields_str_cur); - if (!fields_str_cur[0]) - continue; - - i_name = fields_str_cur; - fields_str_cur = strchr (fields_str_cur, '.'); - if (fields_str_cur) { - right = fields_str_cur + 1; - *fields_str_cur = '\0'; - } - - for (i = 0; fields_array[i]; i++) { - const NMMetaAbstractInfo *fi = fields_array[i]; - - if (g_ascii_strcasecmp (i_name, nm_meta_abstract_info_get_name (fi)) != 0) - continue; - - if (!right) - found = TRUE; - else { - found = FALSE; - if (fi->meta_type == &nm_meta_type_setting_info_editor) { - const NMMetaSettingInfoEditor *fi_s = &fi->as.setting_info; - - for (j = 1; j < fi_s->properties_num; j++) { - if (g_ascii_strcasecmp (right, fi_s->properties[j].property_name) == 0) { - found = TRUE; - break; - } - } - } else if (fi->meta_type == &nmc_meta_type_generic_info) { - const NmcMetaGenericInfo *fi_g = (const NmcMetaGenericInfo *) fi; - - for (j = 0; fi_g->nested && fi_g->nested[j]; j++) { - if (g_ascii_strcasecmp (right, nm_meta_abstract_info_get_name ((const NMMetaAbstractInfo *) fi_g->nested[j])) == 0) { - found = TRUE; - break; - } - } - } - } - - if (found) { - OutputSelectionItem s = { - .idx = i, - }; - - if (right) { - if (!str) - str = g_string_sized_new (32); - - s.offset_plus_1 = str->len + 1; - g_string_append_len (str, right, strlen (right) + 1); - } - - g_array_append_val (array, s); - } - - fields_array_failure = fields_array[i]; - break; - } - - if (!found) { - if ( !right - && ( !g_ascii_strcasecmp (i_name, "all") - || !g_ascii_strcasecmp (i_name, "common"))) - g_set_error (error, NMCLI_ERROR, 0, _("field '%s' has to be alone"), i_name); - else { - gs_free char *allowed_fields = NULL; - - if (fields_array_failure) - allowed_fields = nmc_get_allowed_fields_nested (fields_array_failure); - else - allowed_fields = nmc_get_allowed_fields (fields_array); - - g_set_error (error, NMCLI_ERROR, 1, _("invalid field '%s%s%s'; allowed fields: %s"), - i_name, right ? "." : "", right ?: "", allowed_fields); - } - - return NULL; - } - } } - /* re-organize the collected output data in one buffer that can be freed using - * g_free(). This makes allocation more complicated, but saves us from special - * handling for free. */ - result = g_malloc0 (sizeof (NmcOutputSelection) + (array->len * sizeof (NmcOutputSelectionItem)) + (str ? str->len : 0)); - *((guint *) &result->num) = array->len; - if (array->len > 0) { - char *pdata = &((char *) result)[sizeof (NmcOutputSelection) + (array->len * sizeof (NmcOutputSelectionItem))]; - - if (str) - memcpy (pdata, str->str, str->len); - for (i = 0; i < array->len; i++) { - const OutputSelectionItem *a = &g_array_index (array, OutputSelectionItem, i); - NmcOutputSelectionItem *p = (NmcOutputSelectionItem *) &result->items[i]; - - p->info = fields_array[a->idx]; - p->idx = a->idx; - if (a->offset_plus_1 > 0) - p->sub_selection = &pdata[a->offset_plus_1 - 1]; - } - } - - return result; + return _output_selection_pack (fields_array, array, NULL); } +static NmcOutputSelection * +_output_selection_create_one (const NMMetaAbstractInfo *const* fields_array, + const char *fields_prefix, + const char *fields_str, /* one field selector (contains not commas) and is alrady stripped of spaces. */ + gboolean validate_nested, + GError **error) +{ + gs_unref_array GArray *array = NULL; + nm_auto_free_gstring GString *str = NULL; + + g_return_val_if_fail (!error || !*error, NULL); + nm_assert (fields_str && !strchr (fields_str, ',')); + + if (!_output_selection_select_one (fields_array, + fields_prefix, + fields_str, + validate_nested, + &array, + &str, + error)) + return NULL; + return _output_selection_pack (fields_array, array, str); + +} + +#define PRINT_DATA_COL_PARENT_NIL (G_MAXUINT) + +typedef struct { + const NmcOutputSelectionItem *selection_item; + guint parent_idx; + guint self_idx; + bool is_leaf; +} PrintDataCol; + +static gboolean +_output_selection_append (GArray *cols, + const char *fields_prefix, + guint parent_idx, + const NmcOutputSelectionItem *selection_item, + GPtrArray *gfree_keeper, + GError **error) +{ + gs_free gpointer nested_to_free = NULL; + guint col_idx; + guint i; + const NMMetaAbstractInfo *const*nested; + NmcOutputSelection *selection; + const NmcOutputSelectionItem *si; + + col_idx = cols->len; + + { + PrintDataCol col = { + .selection_item = selection_item, + .parent_idx = parent_idx, + .self_idx = col_idx, + .is_leaf = TRUE, + }; + g_array_append_val (cols, col); + } + + nested = nm_meta_abstract_info_get_nested (selection_item->info, NULL, &nested_to_free); + + if (selection_item->sub_selection) { + if (!nested) { + gs_free char *allowed_fields = NULL; + + if (parent_idx != PRINT_DATA_COL_PARENT_NIL) { + si = g_array_index (cols, PrintDataCol, parent_idx).selection_item; + allowed_fields = nmc_get_allowed_fields_nested (si->info, si->self_selection); + } + if (!allowed_fields) { + g_set_error (error, NMCLI_ERROR, 1, _("invalid field '%s%s%s'; no such field"), + selection_item->self_selection ?: "", selection_item->self_selection ? "." : "", + selection_item->sub_selection); + } else { + g_set_error (error, NMCLI_ERROR, 1, _("invalid field '%s%s%s'; allowed fields: [%s]"), + selection_item->self_selection ?: "", selection_item->self_selection ? "." : "", + selection_item->sub_selection, + allowed_fields); + } + return FALSE; + } + + selection = _output_selection_create_one (nested, selection_item->self_selection, + selection_item->sub_selection, FALSE, error); + if (!selection) + return FALSE; + nm_assert (selection->num == 1); + } else if (nested) { + selection = _output_selection_create_all (nested); + nm_assert (selection && selection->num > 0); + } else + selection = NULL; + + if (selection) { + g_ptr_array_add (gfree_keeper, selection); + + for (i = 0; i < selection->num; i++) { + si = &selection->items[i]; + if (!_output_selection_append (cols, si->self_selection, col_idx, + si, gfree_keeper, error)) + return FALSE; + } + g_array_index (cols, PrintDataCol, col_idx).is_leaf = FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +NmcOutputSelection * +nmc_output_selection_create (const NMMetaAbstractInfo *const* fields_array, + const char *fields_prefix, + const char *fields_str, /* a comma separated list of selectors */ + gboolean validate_nested, + GError **error) +{ + gs_unref_array GArray *array = NULL; + nm_auto_free_gstring GString *str = NULL; + gs_free char *fields_str_clone = NULL; + char *fields_str_cur; + char *fields_str_next; + + g_return_val_if_fail (!error || !*error, NULL); + + if (!fields_str) + return _output_selection_create_all (fields_array); + + fields_str_clone = g_strdup (fields_str); + for (fields_str_cur = fields_str_clone; fields_str_cur; fields_str_cur = fields_str_next) { + fields_str_cur = nm_str_skip_leading_spaces (fields_str_cur); + fields_str_next = strchr (fields_str_cur, ','); + if (fields_str_next) + *fields_str_next++ = '\0'; + + g_strchomp (fields_str_cur); + if (!fields_str_cur[0]) + continue; + if (!_output_selection_select_one (fields_array, + fields_prefix, + fields_str_cur, + validate_nested, + &array, + &str, + error)) + return NULL; + } + + return _output_selection_pack (fields_array, array, str); +} + +/** + * _output_selection_parse: + * @fields: a %NULL terminated array of meta-data fields + * @fields_str: a comma separated selector for fields. Nested fields + * can be specified using '.' notation. + * @out_cols: (transfer full): the result, parsed as an GArray of PrintDataCol items. + * The order of the items is as specified by @fields_str. Meta data + * items that contain nested elements are unpacked (note the is_leaf + * and parent properties of PrintDataCol). + * @out_gfree_keeper: (transfer full): an output GPtrArray that owns + * strings to which @out_cols points to. The lifetime of @out_cols + * and @out_gfree_keeper should correspond. + * @error: + * + * Returns: %TRUE on success. + */ +static gboolean +_output_selection_parse (const NMMetaAbstractInfo *const*fields, + const char *fields_str, + GArray **out_cols, + GPtrArray **out_gfree_keeper, + GError **error) +{ + NmcOutputSelection *selection; + gs_unref_ptrarray GPtrArray *gfree_keeper = NULL; + gs_unref_array GArray *cols = NULL; + guint i; + + selection = nmc_output_selection_create (fields, NULL, fields_str, FALSE, error); + if (!selection) + return FALSE; + + if (!selection->num) { + g_set_error (error, NMCLI_ERROR, 1, _("failure to select field")); + return FALSE; + } + + gfree_keeper = g_ptr_array_new_with_free_func (g_free); + g_ptr_array_add (gfree_keeper, selection); + + cols = g_array_new (FALSE, TRUE, sizeof (PrintDataCol)); + + for (i = 0; i < selection->num; i++) { + const NmcOutputSelectionItem *si = &selection->items[i]; + + if (!_output_selection_append (cols, NULL, PRINT_DATA_COL_PARENT_NIL, + si, gfree_keeper, error)) + return FALSE; + } + + *out_cols = g_steal_pointer (&cols); + *out_gfree_keeper = g_steal_pointer (&gfree_keeper); + return TRUE; +} + +/*****************************************************************************/ /** * parse_output_fields: @@ -921,7 +1178,7 @@ parse_output_fields (const char *fields_str, g_return_val_if_fail (!error || !*error, NULL); g_return_val_if_fail (!out_group_fields || !*out_group_fields, NULL); - selection = nmc_output_selection_create (fields_array, fields_str, error); + selection = nmc_output_selection_create (fields_array, NULL, fields_str, TRUE, error); if (!selection) return NULL; @@ -943,40 +1200,46 @@ parse_output_fields (const char *fields_str, } char * -nmc_get_allowed_fields_nested (const NMMetaAbstractInfo *abstract_info) +nmc_get_allowed_fields_nested (const NMMetaAbstractInfo *abstract_info, const char *name_prefix) { - GString *allowed_fields = g_string_sized_new (256); - int i; - const char *name = nm_meta_abstract_info_get_name (abstract_info); gs_free gpointer nested_to_free = NULL; - const NMMetaAbstractInfo *const*nested = NULL; + guint i; + const NMMetaAbstractInfo *const*nested; + GString *allowed_fields; nested = nm_meta_abstract_info_get_nested (abstract_info, NULL, &nested_to_free); - if (nested) { - for (i = 0; nested && nested[i]; i++) { - g_string_append_printf (allowed_fields, "%s.%s,", - name, nm_meta_abstract_info_get_name (nested[i])); - } - } else - g_string_append_printf (allowed_fields, "%s,", name); + if (!nested) + return NULL; + allowed_fields = g_string_sized_new (256); + + if (!name_prefix) + name_prefix = nm_meta_abstract_info_get_name (abstract_info); + + for (i = 0; nested[i]; i++) { + g_string_append_printf (allowed_fields, "%s.%s,", + name_prefix, nm_meta_abstract_info_get_name (nested[i])); + } g_string_truncate (allowed_fields, allowed_fields->len - 1); - return g_string_free (allowed_fields, FALSE); } char * -nmc_get_allowed_fields (const NMMetaAbstractInfo *const*fields_array) +nmc_get_allowed_fields (const NMMetaAbstractInfo *const*fields_array, const char *name_prefix) { - GString *allowed_fields = g_string_sized_new (256); + GString *allowed_fields; guint i; - for (i = 0; fields_array[i]; i++) + if (!fields_array || !fields_array[0]) + return NULL; + + allowed_fields = g_string_sized_new (256); + for (i = 0; fields_array[i]; i++) { + if (name_prefix) + g_string_append_printf (allowed_fields, "%s.", name_prefix); g_string_append_printf (allowed_fields, "%s,", nm_meta_abstract_info_get_name (fields_array[i])); - - if (allowed_fields->len) - g_string_truncate (allowed_fields, allowed_fields->len - 1); - + } + g_string_truncate (allowed_fields, allowed_fields->len - 1); return g_string_free (allowed_fields, FALSE); } @@ -1012,6 +1275,343 @@ nmc_empty_output_fields (NmcOutputData *output_data) g_ptr_array_remove_range (output_data->output_data, 0, output_data->output_data->len); } +/*****************************************************************************/ + +typedef struct { + guint col_idx; + const PrintDataCol *col; + bool is_nested; + const char *title; + int width; +} PrintDataHeaderCell; + +typedef struct { + guint row_idx; + const PrintDataHeaderCell *header_cell; + NMMetaTermColor term_color; + NMMetaTermFormat term_format; + const char *text; + bool text_to_free:1; +} PrintDataCell; + +static void +_print_data_header_cell_clear (gpointer cell_p) +{ +} + +static void +_print_data_cell_clear_text (PrintDataCell *cell) +{ + if (cell->text_to_free) { + g_free ((char *) cell->text); + cell->text_to_free = FALSE; + } + cell->text = NULL; +} + +static void +_print_data_cell_clear (gpointer cell_p) +{ + PrintDataCell *cell = cell_p; + + _print_data_cell_clear_text (cell); +} + +static void +_print_fill (const NmcConfig *nmc_config, + gpointer const *targets, + const PrintDataCol *cols, + guint cols_len, + GArray **out_header_row, + GArray **out_cells) +{ + GArray *cells; + GArray *header_row; + guint i_row, i_col; + guint targets_len; + gboolean pretty; + NMMetaAccessorGetType text_get_type; + + pretty = (nmc_config->print_output != NMC_PRINT_TERSE); + + header_row = g_array_sized_new (FALSE, TRUE, sizeof (PrintDataHeaderCell), cols_len); + g_array_set_clear_func (header_row, _print_data_header_cell_clear); + + for (i_col = 0; i_col < cols_len; i_col++) { + const PrintDataCol *col; + PrintDataHeaderCell *header_cell; + guint col_idx; + + col = &cols[i_col]; + if (!col->is_leaf) + continue; + + col_idx = header_row->len; + g_array_set_size (header_row, col_idx + 1); + + header_cell = &g_array_index (header_row, PrintDataHeaderCell, col_idx); + + header_cell->col_idx = col_idx; + header_cell->col = col; + header_cell->is_nested = FALSE; + header_cell->title = nm_meta_abstract_info_get_name (col->selection_item->info); + if (pretty) + header_cell->title = _(header_cell->title); + } + + targets_len = NM_PTRARRAY_LEN (targets); + + cells = g_array_sized_new (FALSE, TRUE, sizeof (PrintDataCell), targets_len * header_row->len); + g_array_set_clear_func (cells, _print_data_cell_clear); + g_array_set_size (cells, targets_len * header_row->len); + + text_get_type = pretty + ? NM_META_ACCESSOR_GET_TYPE_PRETTY + : NM_META_ACCESSOR_GET_TYPE_PARSABLE; + + for (i_row = 0; i_row < targets_len; i_row++) { + gpointer target = targets[i_row]; + PrintDataCell *cells_line = &g_array_index (cells, PrintDataCell, i_row * header_row->len); + + for (i_col = 0; i_col < header_row->len; i_col++) { + char *to_free = NULL; + PrintDataCell *cell = &cells_line[i_col]; + const PrintDataHeaderCell *header_cell; + const NMMetaAbstractInfo *info; + + header_cell = &g_array_index (header_row, PrintDataHeaderCell, i_col); + info = header_cell->col->selection_item->info; + + cell->row_idx = i_row; + cell->header_cell = header_cell; + cell->text = nm_meta_abstract_info_get (info, + NULL, + NULL, + target, + text_get_type, + NM_META_ACCESSOR_GET_FLAGS_NONE, + (gpointer *) &to_free); + cell->text_to_free = !!to_free; + + nm_meta_termformat_unpack (nm_meta_abstract_info_get (info, + NULL, + NULL, + target, + NM_META_ACCESSOR_GET_TYPE_TERMFORMAT, + NM_META_ACCESSOR_GET_FLAGS_NONE, + NULL), + &cell->term_color, + &cell->term_format); + + if (pretty && (!cell->text || !cell->text[0])) { + _print_data_cell_clear_text (cell); + cell->text = "--"; + } else if (!cell->text) + cell->text = ""; + } + } + + for (i_col = 0; i_col < header_row->len; i_col++) { + PrintDataHeaderCell *header_cell = &g_array_index (header_row, PrintDataHeaderCell, i_col); + + header_cell->width = nmc_string_screen_width (header_cell->title, NULL); + + for (i_row = 0; i_row < targets_len; i_row++) { + const PrintDataCell *cell = &g_array_index (cells, PrintDataCell, i_row * cols_len + i_col); + + if (header_cell->is_nested) { + g_assert_not_reached (/*TODO*/); + } else { + header_cell->width = NM_MAX (header_cell->width, + nmc_string_screen_width (cell->text, NULL)); + } + } + + header_cell->width += 1; + } + + *out_header_row = header_row; + *out_cells = cells; +} + +static void +_print_do (const NmcConfig *nmc_config, + const char *header_name_no_l10n, + guint col_len, + guint row_len, + const PrintDataHeaderCell *header_row, + const PrintDataCell *cells) +{ + int width1, width2; + int table_width = 0; + gboolean pretty = (nmc_config->print_output == NMC_PRINT_PRETTY); + gboolean terse = (nmc_config->print_output == NMC_PRINT_TERSE); + gboolean multiline = nmc_config->multiline_output; + guint i_row, i_col; + nm_auto_free_gstring GString *str = NULL; + + g_assert (col_len && row_len); + + /* Main header */ + if (pretty) { + gs_free char *line = NULL; + int header_width; + const char *header_name = _(header_name_no_l10n); + + header_width = nmc_string_screen_width (header_name, NULL) + 4; + + if (multiline) { + table_width = NM_MAX (header_width, ML_HEADER_WIDTH); + line = g_strnfill (ML_HEADER_WIDTH, '='); + } else { /* tabular */ + table_width = NM_MAX (table_width, header_width); + line = g_strnfill (table_width, '='); + } + + width1 = strlen (header_name); + width2 = nmc_string_screen_width (header_name, NULL); + g_print ("%s\n", line); + g_print ("%*s\n", (table_width + width2)/2 + width1 - width2, header_name); + g_print ("%s\n", line); + } + + str = !multiline + ? g_string_sized_new (100) + : NULL; + + /* print the header for the tabular form */ + if (!multiline && !terse) { + for (i_col = 0; i_col < col_len; i_col++) { + const PrintDataHeaderCell *header_cell = &header_row[i_col]; + const char *title; + + title = header_cell->title; + + width1 = strlen (title); + width2 = nmc_string_screen_width (title, NULL); /* Width of the string (in screen colums) */ + g_string_append_printf (str, "%-*s", (int) (header_cell->width + width1 - width2), title); + g_string_append_c (str, ' '); /* Column separator */ + table_width += header_cell->width + width1 - width2 + 1; + } + + if (str->len) + g_string_truncate (str, str->len-1); /* Chop off last column separator */ + g_print ("%s\n", str->str); + g_string_truncate (str, 0); + + /* Print horizontal separator */ + if (pretty) { + gs_free char *line = NULL; + + g_print ("%s\n", (line = g_strnfill (table_width, '-'))); + } + } + + for (i_row = 0; i_row < row_len; i_row++) { + const PrintDataCell *current_line = &cells[i_row * col_len]; + + for (i_col = 0; i_col < col_len; i_col++) { + const PrintDataCell *cell = ¤t_line[i_col]; + gs_free char *text_to_free = NULL; + const char *text; + + if (cell->header_cell->is_nested) { + g_assert_not_reached (/*TODO*/); + } else { + text = colorize_string (nmc_config->use_colors, + cell->term_color, cell->term_format, + cell->text, &text_to_free); + } + + if (multiline) { + gs_free char *prefix = NULL; + + prefix = g_strdup_printf ("%s:", cell->header_cell->title); + width1 = strlen (prefix); + width2 = nmc_string_screen_width (prefix, NULL); + g_print ("%-*s%s\n", (int) (terse ? 0 : ML_VALUE_INDENT+width1-width2), prefix, text); + } else { + nm_assert (str); + if (terse) { + if (nmc_config->escape_values) { + const char *p = text; + while (*p) { + if (*p == ':' || *p == '\\') + g_string_append_c (str, '\\'); /* Escaping by '\' */ + g_string_append_c (str, *p); + p++; + } + } + else + g_string_append_printf (str, "%s", text); + g_string_append_c (str, ':'); /* Column separator */ + } else { + const PrintDataHeaderCell *header_cell = &header_row[i_col]; + + width1 = strlen (text); + width2 = nmc_string_screen_width (text, NULL); /* Width of the string (in screen colums) */ + g_string_append_printf (str, "%-*s", (int) (header_cell->width + width1 - width2), text); + g_string_append_c (str, ' '); /* Column separator */ + table_width += header_cell->width + width1 - width2 + 1; + } + } + } + + if (!multiline) { + if (str->len) + g_string_truncate (str, str->len-1); /* Chop off last column separator */ + g_print ("%s\n", str->str); + + g_string_truncate (str, 0); + } + + if ( pretty + && ( i_row < row_len - 1 + || multiline)) { + gs_free char *line = NULL; + + g_print ("%s\n", (line = g_strnfill (ML_HEADER_WIDTH, '-'))); + } + } +} + +gboolean +nmc_print (const NmcConfig *nmc_config, + gpointer const *targets, + const char *header_name_no_l10n, + const NMMetaAbstractInfo *const*fields, + const char *fields_str, + GError **error) +{ + gs_unref_ptrarray GPtrArray *gfree_keeper = NULL; + gs_unref_array GArray *cols = NULL; + gs_unref_array GArray *header_row = NULL; + gs_unref_array GArray *cells = NULL; + + if (!_output_selection_parse (fields, fields_str, + &cols, &gfree_keeper, + error)) + return FALSE; + + _print_fill (nmc_config, + targets, + &g_array_index (cols, PrintDataCol, 0), + cols->len, + &header_row, + &cells); + + _print_do (nmc_config, + header_name_no_l10n, + header_row->len, + cells->len / header_row->len, + &g_array_index (header_row, PrintDataHeaderCell, 0), + &g_array_index (cells, PrintDataCell, 0)); + + return TRUE; +} + +/*****************************************************************************/ + static const char * get_value_to_print (NmcColorOption color_option, const NmcOutputField *field, @@ -1139,7 +1739,7 @@ print_required_fields (const NmcConfig *nmc_config, j); width1 = strlen (tmp); width2 = nmc_string_screen_width (tmp, NULL); - g_print ("%-*s%s\n", terse ? 0 : ML_VALUE_INDENT+width1-width2, tmp, print_val); + g_print ("%-*s%s\n", (int) (terse ? 0 : ML_VALUE_INDENT+width1-width2), tmp, print_val); } } else { gs_free char *val_to_free = NULL; @@ -1159,7 +1759,7 @@ print_required_fields (const NmcConfig *nmc_config, _(nm_meta_abstract_info_get_name (field_values[idx].info))); width1 = strlen (tmp); width2 = nmc_string_screen_width (tmp, NULL); - g_print ("%-*s%s\n", terse ? 0 : ML_VALUE_INDENT+width1-width2, tmp, print_val); + g_print ("%-*s%s\n", (int) (terse ? 0 : ML_VALUE_INDENT+width1-width2), tmp, print_val); } } if (pretty) { diff --git a/clients/cli/utils.h b/clients/cli/utils.h index d212ecb31a..1a4c796539 100644 --- a/clients/cli/utils.h +++ b/clients/cli/utils.h @@ -63,6 +63,7 @@ void nmc_free_output_field_values (NmcOutputField fields_array[]); typedef struct { const NMMetaAbstractInfo *info; + const char *self_selection; const char *sub_selection; guint idx; } NmcOutputSelectionItem; @@ -73,7 +74,9 @@ typedef struct { } NmcOutputSelection; NmcOutputSelection *nmc_output_selection_create (const NMMetaAbstractInfo *const* fields_array, + const char *fields_prefix, const char *fields_str, + gboolean validate_nested, GError **error); GArray *parse_output_fields (const char *fields_str, @@ -81,8 +84,8 @@ GArray *parse_output_fields (const char *fields_str, gboolean parse_groups, GPtrArray **group_fields, GError **error); -char *nmc_get_allowed_fields_nested (const NMMetaAbstractInfo *abstract_info); -char *nmc_get_allowed_fields (const NMMetaAbstractInfo *const*fields_array); +char *nmc_get_allowed_fields_nested (const NMMetaAbstractInfo *abstract_info, const char *name_prefix); +char *nmc_get_allowed_fields (const NMMetaAbstractInfo *const*fields_array, const char *name_prefix); NmcOutputField *nmc_dup_fields_array (const NMMetaAbstractInfo *const*fields, NmcOfFlags flags); void nmc_empty_output_fields (NmcOutputData *output_data); void print_required_fields (const NmcConfig *nmc_config, @@ -100,8 +103,25 @@ void print_data (const NmcConfig *nmc_config, /*****************************************************************************/ +typedef enum { + NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_RUNNING = 0, + NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_VERSION, + NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_STATE, + NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_STARTUP, + NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_CONNECTIVITY, + NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_NETWORKING, + NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WIFI_HW, + NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WIFI, + NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WWAN_HW, + NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WWAN, + NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WIMAX_HW, + NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WIMAX, + _NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_NUM, +} NmcGenericInfoType; + struct _NmcMetaGenericInfo { const NMMetaType *meta_type; + NmcGenericInfoType info_type; const char *name; const NmcMetaGenericInfo *const*nested; gconstpointer (*get_fcn) (const NMMetaEnvironment *environment, @@ -125,4 +145,13 @@ struct _NmcMetaGenericInfo { /*****************************************************************************/ +gboolean nmc_print (const NmcConfig *nmc_config, + gpointer const *targets, + const char *header_name_no_l10n, + const NMMetaAbstractInfo *const*fields, + const char *fields_str, + GError **error); + +/*****************************************************************************/ + #endif /* NMC_UTILS_H */ diff --git a/clients/common/nm-meta-setting-access.c b/clients/common/nm-meta-setting-access.c index 6da71448fc..5af55b377b 100644 --- a/clients/common/nm-meta-setting-access.c +++ b/clients/common/nm-meta-setting-access.c @@ -223,3 +223,27 @@ nm_meta_abstract_info_get_nested (const NMMetaAbstractInfo *abstract_info, NM_SET_OUT (out_len, 0); return NULL; } + +gconstpointer +nm_meta_abstract_info_get (const NMMetaAbstractInfo *abstract_info, + const NMMetaEnvironment *environment, + gpointer environment_user_data, + gpointer target, + NMMetaAccessorGetType get_type, + NMMetaAccessorGetFlags get_flags, + gpointer *out_to_free) +{ + nm_assert (abstract_info); + nm_assert (abstract_info->meta_type); + nm_assert (!out_to_free || !*out_to_free); + + if (!abstract_info->meta_type->get_fcn) + g_return_val_if_reached (NULL); + + return abstract_info->meta_type->get_fcn (environment, environment_user_data, + abstract_info, + target, + get_type, + get_flags, + out_to_free); +} diff --git a/clients/common/nm-meta-setting-access.h b/clients/common/nm-meta-setting-access.h index 553f41136f..8ed69b29dd 100644 --- a/clients/common/nm-meta-setting-access.h +++ b/clients/common/nm-meta-setting-access.h @@ -50,6 +50,14 @@ const NMMetaAbstractInfo *const*nm_meta_abstract_info_get_nested (const NMMetaAb guint *out_len, gpointer *nested_to_free); +gconstpointer nm_meta_abstract_info_get (const NMMetaAbstractInfo *abstract_info, + const NMMetaEnvironment *environment, + gpointer environment_user_data, + gpointer target, + NMMetaAccessorGetType get_type, + NMMetaAccessorGetFlags get_flags, + gpointer *out_to_free); + /*****************************************************************************/ #endif /* _NM_META_SETTING_ACCESS_H__ */ diff --git a/clients/common/nm-meta-setting-desc.h b/clients/common/nm-meta-setting-desc.h index 37ed367812..73e773b4de 100644 --- a/clients/common/nm-meta-setting-desc.h +++ b/clients/common/nm-meta-setting-desc.h @@ -49,8 +49,35 @@ typedef enum { typedef enum { NM_META_ACCESSOR_GET_TYPE_PRETTY, NM_META_ACCESSOR_GET_TYPE_PARSABLE, + NM_META_ACCESSOR_GET_TYPE_TERMFORMAT, } NMMetaAccessorGetType; +static inline void +nm_meta_termformat_unpack (gconstpointer value, NMMetaTermColor *out_color, NMMetaTermFormat *out_format) +{ + /* get_fcn() with NM_META_ACCESSOR_GET_TYPE_TERMFORMAT returns a pointer + * that encodes NMMetaTermColor and NMMetaTermFormat. Unpack it. */ + if (!value) { + /* by default, objects that don't support NM_META_ACCESSOR_GET_TYPE_TERMFORMAT + * return NULL. This allows for an explicit fallback value here... */ + NM_SET_OUT (out_color, NM_META_TERM_COLOR_NORMAL); + NM_SET_OUT (out_format, NM_META_TERM_FORMAT_NORMAL); + } else { + NM_SET_OUT (out_color, GPOINTER_TO_UINT (value) & 0xFF); + NM_SET_OUT (out_format, (GPOINTER_TO_UINT (value) & 0xFF00) >> 8); + } +} + +static inline gconstpointer +nm_meta_termformat_pack (NMMetaTermColor color, NMMetaTermFormat format) +{ + /* get_fcn() with NM_META_ACCESSOR_GET_TYPE_TERMFORMAT returns a pointer + * that encodes NMMetaTermColor and NMMetaTermFormat. Pack it. */ + return GUINT_TO_POINTER (((guint) 0x10000) | (((guint) color) & 0xFFu) | ((((guint) format) & 0xFFu) << 8)); +} + +#define NM_META_TERMFORMAT_DEFAULT() (nm_meta_termformat_pack (NM_META_TERM_COLOR_NORMAL, NM_META_TERM_FORMAT_NORMAL)) + typedef enum { NM_META_ACCESSOR_GET_FLAGS_NONE = 0, NM_META_ACCESSOR_GET_FLAGS_SHOW_SECRETS = (1LL << 0),