From 00c52abb69bc268842bf48d84c844c05a3716f87 Mon Sep 17 00:00:00 2001 From: Patrick Griffis Date: Sun, 7 Jan 2024 21:18:01 -0600 Subject: [PATCH] Add --show-changelog flag to flatpak-info This shows the latest changelog entry from the appstream data for a given app. --- app/flatpak-builtins-info.c | 36 +++++++++- app/flatpak-tty-utils-private.h | 2 + app/flatpak-tty-utils.c | 116 ++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 1 deletion(-) diff --git a/app/flatpak-builtins-info.c b/app/flatpak-builtins-info.c index ed905836..0c36d3c8 100644 --- a/app/flatpak-builtins-info.c +++ b/app/flatpak-builtins-info.c @@ -47,6 +47,7 @@ static gboolean opt_show_sdk; static gboolean opt_show_permissions; static gboolean opt_show_extensions; static gboolean opt_show_location; +static gboolean opt_show_changelog; static char *opt_arch; static char **opt_installations; static char *opt_file_access; @@ -67,6 +68,7 @@ static GOptionEntry options[] = { { "file-access", 0, 0, G_OPTION_ARG_FILENAME, &opt_file_access, N_("Query file access"), N_("PATH") }, { "show-extensions", 'e', 0, G_OPTION_ARG_NONE, &opt_show_extensions, N_("Show extensions"), NULL }, { "show-location", 'l', 0, G_OPTION_ARG_NONE, &opt_show_location, N_("Show location"), NULL }, + { "show-changelog", 0, 0, G_OPTION_ARG_NONE, &opt_show_changelog, N_("Show changelog"), NULL }, { NULL } }; @@ -166,7 +168,7 @@ flatpak_builtin_info (int argc, char **argv, GCancellable *cancellable, GError * metakey = flatpak_deploy_get_metadata (deploy); if (opt_show_ref || opt_show_origin || opt_show_commit || opt_show_size || opt_show_metadata || opt_show_permissions || - opt_file_access || opt_show_location || opt_show_runtime || opt_show_sdk) + opt_file_access || opt_show_location || opt_show_runtime || opt_show_sdk || opt_show_changelog) friendly = FALSE; if (friendly) @@ -443,6 +445,38 @@ flatpak_builtin_info (int argc, char **argv, GCancellable *cancellable, GError * g_print ("read-write\n"); } } + + if (opt_show_changelog) + { +#if AS_CHECK_VERSION(1, 0, 0) + g_autoptr(AsMetadata) mdata = as_metadata_new (); + g_autofree char *remote = g_strdup ("flathub"); // FIXME: Returns NULL: flatpak_decomposed_dup_remote(ref); + g_autofree char *arch = flatpak_decomposed_dup_arch (ref); + g_autoptr(GError) appstream_error = NULL; + g_autoptr(GPtrArray) dirs = g_ptr_array_new (); + AsComponent *component; + + g_ptr_array_add(dirs, dir); + + update_appstream (dirs, remote, arch, FLATPAK_APPSTREAM_TTL, TRUE, cancellable, NULL); + flatpak_dir_load_appstream_data (dir, remote, arch, mdata, cancellable, &appstream_error); + + component = metadata_find_component (mdata, flatpak_decomposed_get_ref(ref)); + if (component) + { + AsReleaseList *releases = as_component_get_releases_plain(component); + AsRelease *release = as_release_list_index_safe (releases, 0); + if (releases) + flatpak_print_appstream_release_description_markup (as_release_get_description (release)); + else + g_print ("No changelog\n"); + } + else + g_print ("Failed to find app, missing appstream data?\n"); +#else + g_warning ("Listing changelogs is not supported with this version of libappstream"); +#endif + } } if (opt_show_extensions) diff --git a/app/flatpak-tty-utils-private.h b/app/flatpak-tty-utils-private.h index a7af168b..d1fcbedc 100644 --- a/app/flatpak-tty-utils-private.h +++ b/app/flatpak-tty-utils-private.h @@ -83,3 +83,5 @@ void flatpak_format_choices (const char **choices, void flatpak_print_escaped_string (const char *s, FlatpakEscapeFlags flags); + +void flatpak_print_appstream_release_description_markup (const char *s); diff --git a/app/flatpak-tty-utils.c b/app/flatpak-tty-utils.c index de163fcf..8332a29d 100644 --- a/app/flatpak-tty-utils.c +++ b/app/flatpak-tty-utils.c @@ -521,3 +521,119 @@ flatpak_print_escaped_string (const char *s, g_autofree char *escaped = flatpak_escape_string (s, flags); g_print ("%s", escaped); } + +/* Markup format documented here: https://www.freedesktop.org/software/appstream/docs/chap-Metadata.html#tag-description */ +typedef struct { + GString *formatted; + guint current_list_index; + gboolean in_list; + gboolean in_paragraph; +} DescriptionParserData; + +static void +start_element (GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + gpointer user_data, + GError **error) +{ + DescriptionParserData *data = user_data; + + if (g_str_equal (element_name, "p")) + { + if (data->formatted->len != 0) + g_string_append_c (data->formatted, '\n'); + data->in_paragraph = TRUE; + } + else if (!data->in_list && g_str_equal (element_name, "ol")) + { + data->in_list = TRUE; + data->current_list_index = 1; + } + else if (!data->in_list && g_str_equal (element_name, "ul")) + { + data->in_list = TRUE; + } + else if (data->in_list && g_str_equal (element_name, "li")) + { + if (data->formatted->len != 0) + g_string_append_c (data->formatted, '\n'); + + if (data->current_list_index) + g_string_append_printf(data->formatted, " %u. ", data->current_list_index++); + else + g_string_append(data->formatted, " - "); + } + else if ((data->in_list || data->in_paragraph) && g_str_equal (element_name, "em")) + { + g_string_append (data->formatted, "\033[3m"); + } +} + +static void +end_element (GMarkupParseContext *context, + const char *element_name, + gpointer user_data, + GError **error) +{ + DescriptionParserData *data = user_data; + + if (g_str_equal (element_name, "p")) + { + data->in_paragraph = FALSE; + } + else if (data->in_list && g_str_equal (element_name, "ol")) + { + data->in_list = FALSE; + data->current_list_index = 0; + } + else if (data->in_list && g_str_equal (element_name, "ul")) + { + data->in_list = FALSE; + } + else if ((data->in_list || data->in_paragraph) && g_str_equal (element_name, "em")) + { + g_string_append (data->formatted, "\e[0m"); + } +} + +static void +text (GMarkupParseContext *context, + const char *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + DescriptionParserData *data = user_data; + + g_string_append_len (data->formatted, text, text_len); +} + +void +flatpak_print_appstream_release_description_markup (const char *s) +{ + g_autoptr(GString) formatted = g_string_new_len(NULL, strlen (s)); + g_autoptr(GError) error = NULL; + g_autoptr(GMarkupParseContext) context = NULL; + GMarkupParser parser = { + start_element, + end_element, + text, + NULL, + NULL + }; + + g_autofree DescriptionParserData *data = g_new0 (DescriptionParserData, 1); + data->formatted = formatted; + + context = g_markup_parse_context_new (&parser, G_MARKUP_TREAT_CDATA_AS_TEXT, data, NULL); + if (!g_markup_parse_context_parse (context, s, -1, &error)) + { + g_warning ("Failed to release description: %s", error->message); + return; + } + + g_string_append_c (formatted, '\n'); + g_print ("%s", formatted->str); +}