From 4f8b935567a5feb75b29e056839cbce8afe2e563 Mon Sep 17 00:00:00 2001 From: Adian Kozlica Date: Mon, 29 Sep 2025 17:31:29 +0200 Subject: [PATCH] feat: json support for table printer --- app/flatpak-builtins-document-list.c | 4 +- app/flatpak-builtins-history.c | 4 +- app/flatpak-builtins-list.c | 4 +- app/flatpak-builtins-permission-list.c | 5 +- app/flatpak-builtins-permission-show.c | 5 +- app/flatpak-builtins-ps.c | 4 +- app/flatpak-builtins-remote-list.c | 4 +- app/flatpak-builtins-remote-ls.c | 4 +- app/flatpak-builtins-repo.c | 6 +- app/flatpak-builtins-search.c | 4 +- app/flatpak-table-printer.c | 35 ++++++++++- app/flatpak-table-printer.h | 1 + tests/test-completion.sh | 1 + tests/test-history.sh | 82 ++++++++++++++++++++++++++ 14 files changed, 151 insertions(+), 12 deletions(-) diff --git a/app/flatpak-builtins-document-list.c b/app/flatpak-builtins-document-list.c index dd1b63da..ffed4764 100644 --- a/app/flatpak-builtins-document-list.c +++ b/app/flatpak-builtins-document-list.c @@ -37,9 +37,11 @@ #include "flatpak-table-printer.h" static const char **opt_cols; +static gboolean opt_json; static GOptionEntry options[] = { { "columns", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_cols, N_("What information to show"), N_("FIELD,…") }, + { "json", 'j', 0, G_OPTION_ARG_NONE, &opt_json, N_("Show output in JSON format"), NULL }, { NULL } }; @@ -160,7 +162,7 @@ print_documents (const char *app_id, return TRUE; } - flatpak_table_printer_print (printer); + opt_json ? flatpak_table_printer_print_json (printer) : flatpak_table_printer_print (printer); return TRUE; } diff --git a/app/flatpak-builtins-history.c b/app/flatpak-builtins-history.c index 1d45063e..34ea7db8 100644 --- a/app/flatpak-builtins-history.c +++ b/app/flatpak-builtins-history.c @@ -44,12 +44,14 @@ static char *opt_since; static char *opt_until; static gboolean opt_reverse; static const char **opt_cols; +static gboolean opt_json; static GOptionEntry options[] = { { "since", 0, 0, G_OPTION_ARG_STRING, &opt_since, N_("Only show changes after TIME"), N_("TIME") }, { "until", 0, 0, G_OPTION_ARG_STRING, &opt_until, N_("Only show changes before TIME"), N_("TIME") }, { "reverse", 0, 0, G_OPTION_ARG_NONE, &opt_reverse, N_("Show newest entries first"), NULL }, { "columns", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_cols, N_("What information to show"), N_("FIELD,…") }, + { "json", 'j', 0, G_OPTION_ARG_NONE, &opt_json, N_("Show output in JSON format"), NULL }, { NULL } }; @@ -362,7 +364,7 @@ print_history (GPtrArray *dirs, flatpak_table_printer_finish_row (printer); } - flatpak_table_printer_print (printer); + opt_json ? flatpak_table_printer_print_json (printer) : flatpak_table_printer_print (printer); sd_journal_close (j); diff --git a/app/flatpak-builtins-list.c b/app/flatpak-builtins-list.c index b2a89bf4..2285cbca 100644 --- a/app/flatpak-builtins-list.c +++ b/app/flatpak-builtins-list.c @@ -38,6 +38,7 @@ static gboolean opt_show_details; static gboolean opt_runtime; static gboolean opt_app; static gboolean opt_all; +static gboolean opt_json; static char *opt_arch; static char *opt_app_runtime; static const char **opt_cols; @@ -48,6 +49,7 @@ static GOptionEntry options[] = { { "app", 0, 0, G_OPTION_ARG_NONE, &opt_app, N_("List installed applications"), NULL }, { "arch", 0, 0, G_OPTION_ARG_STRING, &opt_arch, N_("Arch to show"), N_("ARCH") }, { "all", 'a', 0, G_OPTION_ARG_NONE, &opt_all, N_("List all refs (including locale/debug)"), NULL }, + { "json", 'j', 0, G_OPTION_ARG_NONE, &opt_json, N_("Show output in JSON format"), NULL }, { "app-runtime", 0, 0, G_OPTION_ARG_STRING, &opt_app_runtime, N_("List all applications using RUNTIME"), N_("RUNTIME") }, { "columns", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_cols, N_("What information to show"), N_("FIELD,…") }, { NULL } @@ -355,7 +357,7 @@ print_table_for_refs (gboolean print_apps, if (flatpak_table_printer_get_current_row (printer) > 0) { - flatpak_table_printer_print (printer); + opt_json ? flatpak_table_printer_print_json (printer) : flatpak_table_printer_print (printer); } return TRUE; diff --git a/app/flatpak-builtins-permission-list.c b/app/flatpak-builtins-permission-list.c index 04764ec0..86014d49 100644 --- a/app/flatpak-builtins-permission-list.c +++ b/app/flatpak-builtins-permission-list.c @@ -37,7 +37,10 @@ #include "flatpak-utils-private.h" #include "flatpak-run-private.h" +static gboolean opt_json; + static GOptionEntry options[] = { + { "json", 'j', 0, G_OPTION_ARG_NONE, &opt_json, N_("Show output in JSON format"), NULL }, { NULL } }; @@ -195,7 +198,7 @@ flatpak_builtin_permission_list (int argc, char **argv, } } - flatpak_table_printer_print (printer); + opt_json ? flatpak_table_printer_print_json (printer) : flatpak_table_printer_print (printer); return TRUE; } diff --git a/app/flatpak-builtins-permission-show.c b/app/flatpak-builtins-permission-show.c index 33395f41..63485ff9 100644 --- a/app/flatpak-builtins-permission-show.c +++ b/app/flatpak-builtins-permission-show.c @@ -37,7 +37,10 @@ #include "flatpak-utils-private.h" #include "flatpak-run-private.h" +static gboolean opt_json; + static GOptionEntry options[] = { + { "json", 'j', 0, G_OPTION_ARG_NONE, &opt_json, N_("Show output in JSON format"), NULL }, { NULL } }; @@ -149,7 +152,7 @@ flatpak_builtin_permission_show (int argc, char **argv, return FALSE; } - flatpak_table_printer_print (printer); + opt_json ? flatpak_table_printer_print_json (printer) : flatpak_table_printer_print (printer); return TRUE; } diff --git a/app/flatpak-builtins-ps.c b/app/flatpak-builtins-ps.c index 6f240495..a2936268 100644 --- a/app/flatpak-builtins-ps.c +++ b/app/flatpak-builtins-ps.c @@ -37,9 +37,11 @@ #include "flatpak-instance.h" static const char **opt_cols; +static gboolean opt_json; static GOptionEntry options[] = { { "columns", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_cols, N_("What information to show"), N_("FIELD,…") }, + { "json", 'j', 0, G_OPTION_ARG_NONE, &opt_json, N_("Show output in JSON format"), NULL }, { NULL } }; @@ -226,7 +228,7 @@ enumerate_instances (Column *columns, GError **error) flatpak_table_printer_finish_row (printer); } - flatpak_table_printer_print (printer); + opt_json ? flatpak_table_printer_print_json (printer) : flatpak_table_printer_print (printer); return TRUE; } diff --git a/app/flatpak-builtins-remote-list.c b/app/flatpak-builtins-remote-list.c index 2b8da762..44502eb2 100644 --- a/app/flatpak-builtins-remote-list.c +++ b/app/flatpak-builtins-remote-list.c @@ -37,11 +37,13 @@ static gboolean opt_show_details; static gboolean opt_show_disabled; static const char **opt_cols; +static gboolean opt_json; static GOptionEntry options[] = { { "show-details", 'd', 0, G_OPTION_ARG_NONE, &opt_show_details, N_("Show remote details"), NULL }, { "show-disabled", 0, 0, G_OPTION_ARG_NONE, &opt_show_disabled, N_("Show disabled remotes"), NULL }, { "columns", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_cols, N_("What information to show"), N_("FIELD,…") }, + { "json", 'j', 0, G_OPTION_ARG_NONE, &opt_json, N_("Show output in JSON format"), NULL }, { NULL } }; @@ -213,7 +215,7 @@ list_remotes (GPtrArray *dirs, Column *columns, GCancellable *cancellable, GErro } } - flatpak_table_printer_print (printer); + opt_json ? flatpak_table_printer_print_json (printer) : flatpak_table_printer_print (printer); return TRUE; } diff --git a/app/flatpak-builtins-remote-ls.c b/app/flatpak-builtins-remote-ls.c index c56e08f5..e6c8be72 100644 --- a/app/flatpak-builtins-remote-ls.c +++ b/app/flatpak-builtins-remote-ls.c @@ -46,6 +46,7 @@ static gboolean opt_sideloaded; static char *opt_arch; static char *opt_app_runtime; static const char **opt_cols; +static gboolean opt_json; static GOptionEntry options[] = { { "show-details", 'd', 0, G_OPTION_ARG_NONE, &opt_show_details, N_("Show arches and branches"), NULL }, @@ -59,6 +60,7 @@ static GOptionEntry options[] = { { "cached", 0, 0, G_OPTION_ARG_NONE, &opt_cached, N_("Use local caches even if they are stale"), NULL }, /* Translators: A sideload is when you install from a local USB drive rather than the Internet. */ { "sideloaded", 0, 0, G_OPTION_ARG_NONE, &opt_sideloaded, N_("Only list refs available as sideloads"), NULL }, + { "json", 'j', 0, G_OPTION_ARG_NONE, &opt_json, N_("Show output in JSON format"), NULL }, { NULL } }; @@ -368,7 +370,7 @@ ls_remote (GHashTable *refs_hash, const char **arches, const char *app_runtime, if (flatpak_table_printer_get_current_row (printer) > 0) { - flatpak_table_printer_print (printer); + opt_json ? flatpak_table_printer_print_json (printer) : flatpak_table_printer_print (printer); } return TRUE; diff --git a/app/flatpak-builtins-repo.c b/app/flatpak-builtins-repo.c index 274198ec..40d57358 100644 --- a/app/flatpak-builtins-repo.c +++ b/app/flatpak-builtins-repo.c @@ -41,6 +41,7 @@ static gboolean opt_subsets; static gchar *opt_metadata_branch; static gchar *opt_commits_branch; static gchar *opt_subset; +static gboolean opt_json; static gboolean ostree_repo_mode_to_string (OstreeRepoMode mode, @@ -377,7 +378,7 @@ print_branches (OstreeRepo *repo, flatpak_table_printer_sort (printer, (GCompareFunc) strcmp); - flatpak_table_printer_print (printer); + opt_json ? flatpak_table_printer_print_json (printer) : flatpak_table_printer_print (printer); } static void @@ -426,7 +427,7 @@ print_subsets (OstreeRepo *repo, } } - flatpak_table_printer_print (printer); + opt_json ? flatpak_table_printer_print_json (printer) : flatpak_table_printer_print (printer); } @@ -712,6 +713,7 @@ static GOptionEntry options[] = { { "commits", 0, 0, G_OPTION_ARG_STRING, &opt_commits_branch, N_("Show commits for a branch"), N_("BRANCH") }, { "subsets", 0, 0, G_OPTION_ARG_NONE, &opt_subsets, N_("Print information about the repo subsets"), NULL }, { "subset", 0, 0, G_OPTION_ARG_STRING, &opt_subset, N_("Limit information to subsets with this prefix"), NULL }, + { "json", 'j', 0, G_OPTION_ARG_NONE, &opt_json, N_("Show output in JSON format"), NULL }, { NULL } }; diff --git a/app/flatpak-builtins-search.c b/app/flatpak-builtins-search.c index 674007e9..58f4db2e 100644 --- a/app/flatpak-builtins-search.c +++ b/app/flatpak-builtins-search.c @@ -31,10 +31,12 @@ static char *opt_arch; static const char **opt_cols; +static gboolean opt_json; static GOptionEntry options[] = { { "arch", 0, 0, G_OPTION_ARG_STRING, &opt_arch, N_("Arch to search for"), N_("ARCH") }, { "columns", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_cols, N_("What information to show"), N_("FIELD,…") }, + { "json", 'j', 0, G_OPTION_ARG_NONE, &opt_json, N_("Show output in JSON format"), NULL }, { NULL} }; @@ -230,7 +232,7 @@ print_matches (Column *columns, GSList *matches) print_app (columns, res, printer); } - flatpak_table_printer_print (printer); + opt_json ? flatpak_table_printer_print_json (printer) : flatpak_table_printer_print (printer); } gboolean diff --git a/app/flatpak-table-printer.c b/app/flatpak-table-printer.c index e8bc75d2..d757e67f 100644 --- a/app/flatpak-table-printer.c +++ b/app/flatpak-table-printer.c @@ -21,7 +21,8 @@ #include "config.h" #include - +#include +#include #include "flatpak-table-printer.h" #include "flatpak-tty-utils-private.h" #include "flatpak-utils-private.h" @@ -724,6 +725,38 @@ flatpak_table_printer_print (FlatpakTablePrinter *printer) g_print ("\n"); } +void +flatpak_table_printer_print_json (FlatpakTablePrinter *printer) +{ + g_autoptr(JsonArray) json_array = json_array_new (); + g_autoptr(JsonNode) root_node = NULL; + g_autofree gchar *json_string = NULL; + + for (size_t i = 0; i < printer->rows->len; i++) + { + g_autoptr(JsonObject) json_object = json_object_new (); + Row *row = g_ptr_array_index (printer->rows, i); + + for (size_t j = 0; j < row->cells->len; j++) + { + Cell *cell = g_ptr_array_index (row->cells, j); + TableColumn *col = peek_table_column (printer, j); + const char *title = col && col->title ? col->title : ""; + g_autofree gchar *normalized_title = g_ascii_strdown (title, -1); + g_strdelimit (normalized_title, " ", '_'); + json_object_set_string_member (json_object, normalized_title, cell->text); + } + + json_array_add_object_element (json_array, g_steal_pointer (&json_object)); + } + + root_node = json_node_new (JSON_NODE_ARRAY); + json_node_take_array (root_node, g_steal_pointer (&json_array)); + + json_string = json_to_string (root_node, TRUE); + g_print ("%s\n", json_string); +} + int flatpak_table_printer_get_current_row (FlatpakTablePrinter *printer) { diff --git a/app/flatpak-table-printer.h b/app/flatpak-table-printer.h index c54fdcb3..32eb2a58 100644 --- a/app/flatpak-table-printer.h +++ b/app/flatpak-table-printer.h @@ -61,6 +61,7 @@ void flatpak_table_printer_sort (FlatpakTablePrinter *printer, GCompareFunc cmp); int flatpak_table_printer_lookup_row (FlatpakTablePrinter *printer, const char *key); void flatpak_table_printer_print (FlatpakTablePrinter *printer); +void flatpak_table_printer_print_json (FlatpakTablePrinter *printer); void flatpak_table_printer_print_full (FlatpakTablePrinter *printer, int skip, int columns, diff --git a/tests/test-completion.sh b/tests/test-completion.sh index 33649660..956d429f 100755 --- a/tests/test-completion.sh +++ b/tests/test-completion.sh @@ -89,6 +89,7 @@ ${FLATPAK} complete "flatpak list --" 15 "--" | sort > complete_out --columns= --help --installation= +--json --ostree-verbose --runtime --show-details diff --git a/tests/test-history.sh b/tests/test-history.sh index 712f69c6..556b7b21 100755 --- a/tests/test-history.sh +++ b/tests/test-history.sh @@ -71,6 +71,88 @@ uninstall org.test.Hello.Locale master system (history-installation) remove remote system (history-installation) test-repo EOF +if ! ${FLATPAK} --installation=history-installation history --since="${HISTORY_START_TIME}" \ + --columns=change,application,branch,installation,remote --json > history-log 2>&1; then + cat history-log >&2 + echo "Bail out! 'flatpak history' failed" + exit 1 +fi + +diff history-log - >&2 << EOF +[ + { + "change" : "add remote", + "application" : "", + "branch" : "", + "installation" : "system (history-installation)", + "remote" : "test-repo" + }, + { + "change" : "deploy install", + "application" : "org.test.Hello.Locale", + "branch" : "master", + "installation" : "system (history-installation)", + "remote" : "test-repo" + }, + { + "change" : "deploy install", + "application" : "org.test.Platform", + "branch" : "master", + "installation" : "system (history-installation)", + "remote" : "test-repo" + }, + { + "change" : "deploy install", + "application" : "org.test.Hello", + "branch" : "master", + "installation" : "system (history-installation)", + "remote" : "test-repo" + }, + { + "change" : "deploy update", + "application" : "org.test.Hello.Locale", + "branch" : "master", + "installation" : "system (history-installation)", + "remote" : "test-repo" + }, + { + "change" : "deploy update", + "application" : "org.test.Hello", + "branch" : "master", + "installation" : "system (history-installation)", + "remote" : "test-repo" + }, + { + "change" : "uninstall", + "application" : "org.test.Hello", + "branch" : "master", + "installation" : "system (history-installation)", + "remote" : "" + }, + { + "change" : "uninstall", + "application" : "org.test.Platform", + "branch" : "master", + "installation" : "system (history-installation)", + "remote" : "" + }, + { + "change" : "uninstall", + "application" : "org.test.Hello.Locale", + "branch" : "master", + "installation" : "system (history-installation)", + "remote" : "" + }, + { + "change" : "remove remote", + "application" : "", + "branch" : "", + "installation" : "system (history-installation)", + "remote" : "test-repo" + } +] +EOF + rm -f ${FLATPAK_CONFIG_DIR}/installations.d/history-inst.conf rm -rf ${TEST_DATA_DIR}/system-history-installation