summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Hughes <richard@hughsie.com>2015-01-26 16:56:27 +0000
committerRichard Hughes <richard@hughsie.com>2015-01-26 16:56:27 +0000
commite176509a3e6d0ced80cabf1a6378abd115f91afd (patch)
tree543e6ad7af17e4e54eed1d7f44427cc1952fa08f
parenta40f09fcc625c27436718a6f13b16be011862b67 (diff)
downloadappstream-glib-e176509a3e6d0ced80cabf1a6378abd115f91afd.tar.gz
Add 'news-to-appdata' command to appstream-util
-rw-r--r--client/as-util.c365
1 files changed, 365 insertions, 0 deletions
diff --git a/client/as-util.c b/client/as-util.c
index 078c525..6278643 100644
--- a/client/as-util.c
+++ b/client/as-util.c
@@ -29,6 +29,7 @@
#include <archive_entry.h>
#include <archive.h>
#include <locale.h>
+#include <stdlib.h>
#define __APPSTREAM_GLIB_PRIVATE_H
#include <as-app-private.h>
@@ -690,6 +691,364 @@ as_util_appdata_to_news (AsUtilPrivate *priv, gchar **values, GError **error)
}
/**
+ * as_string_replace:
+ **/
+static guint
+as_string_replace (GString *string, const gchar *search, const gchar *replace)
+{
+ gchar *tmp;
+ guint count = 0;
+ guint replace_len;
+ guint search_len;
+
+ search_len = strlen (search);
+ replace_len = strlen (replace);
+
+ do {
+ tmp = g_strstr_len (string->str, -1, search);
+ if (tmp == NULL)
+ goto out;
+
+ /* reallocate the string if required */
+ if (search_len > replace_len) {
+ g_string_erase (string,
+ tmp - string->str,
+ search_len - replace_len);
+ }
+ if (search_len < replace_len) {
+ g_string_insert_len (string,
+ tmp - string->str,
+ search,
+ replace_len - search_len);
+ }
+
+ /* just memcmp in the new string */
+ memcpy (tmp, replace, replace_len);
+ count++;
+ } while (TRUE);
+out:
+ return count;
+}
+
+typedef enum {
+ AS_UTIL_SECTION_KIND_UNKNOWN,
+ AS_UTIL_SECTION_KIND_HEADER,
+ AS_UTIL_SECTION_KIND_BUGFIX,
+ AS_UTIL_SECTION_KIND_FEATURES,
+ AS_UTIL_SECTION_KIND_NOTES,
+ AS_UTIL_SECTION_KIND_TRANSLATION,
+ AS_UTIL_SECTION_KIND_LAST
+} AsUtilSectionKind;
+
+/**
+ * as_util_news_to_appdata_guess_section:
+ **/
+static AsUtilSectionKind
+as_util_news_to_appdata_guess_section (const gchar *lines)
+{
+ if (g_strstr_len (lines, -1, "~~~") != NULL)
+ return AS_UTIL_SECTION_KIND_HEADER;
+ if (g_strstr_len (lines, -1, "Bugfix:\n") != NULL)
+ return AS_UTIL_SECTION_KIND_BUGFIX;
+ if (g_strstr_len (lines, -1, "Bugfixes:\n") != NULL)
+ return AS_UTIL_SECTION_KIND_BUGFIX;
+ if (g_strstr_len (lines, -1, "Bug fixes:\n") != NULL)
+ return AS_UTIL_SECTION_KIND_BUGFIX;
+ if (g_strstr_len (lines, -1, "Features:\n") != NULL)
+ return AS_UTIL_SECTION_KIND_FEATURES;
+ if (g_strstr_len (lines, -1, "Notes:\n") != NULL)
+ return AS_UTIL_SECTION_KIND_NOTES;
+ if (g_strstr_len (lines, -1, "Note:\n") != NULL)
+ return AS_UTIL_SECTION_KIND_NOTES;
+ if (g_strstr_len (lines, -1, "Translations:\n") != NULL)
+ return AS_UTIL_SECTION_KIND_TRANSLATION;
+ return AS_UTIL_SECTION_KIND_UNKNOWN;
+}
+
+/**
+ * as_util_news_add_indent:
+ **/
+static void
+as_util_news_add_indent (GString *str, guint indent_level)
+{
+ guint i;
+ for (i = 0; i < indent_level; i++)
+ g_string_append (str, " ");
+}
+
+/**
+ * as_util_news_add_markup:
+ **/
+static void
+as_util_news_add_markup (GString *desc, const gchar *tag, const gchar *line)
+{
+ guint i;
+ guint indent = 0;
+ _cleanup_free_ gchar *escaped = NULL;
+
+ /* empty line means do nothing */
+ if (line != NULL && line[0] == '\0')
+ return;
+
+ /* work out indent */
+ if (g_strcmp0 (tag, "p") == 0)
+ indent = 4;
+ else if (g_strcmp0 (tag, "ul") == 0)
+ indent = 4;
+ else if (g_strcmp0 (tag, "/ul") == 0)
+ indent = 4;
+ else if (g_strcmp0 (tag, "release") == 0)
+ indent = 3;
+ else if (g_strcmp0 (tag, "/release") == 0)
+ indent = 3;
+ else if (g_strcmp0 (tag, "li") == 0)
+ indent = 5;
+
+ /* write XML with the correct maximum width */
+ if (line == NULL) {
+ as_util_news_add_indent (desc, indent);
+ g_string_append_printf (desc, "<%s>\n", tag);
+ } else {
+ gchar *tmp;
+ _cleanup_strv_free_ gchar **lines = NULL;
+ escaped = g_markup_escape_text (line, -1);
+ tmp = g_strrstr (escaped, " (");
+ if (tmp != NULL)
+ *tmp = '\0';
+ lines = as_markup_strsplit_words (escaped, 80 - indent * 2);
+ if (lines == NULL) {
+ g_warning ("line cannot be split `%s`", escaped);
+ return;
+ }
+ /* all fits on one line? */
+ if (g_strv_length (lines) == 1) {
+ as_util_news_add_indent (desc, indent);
+ g_string_append_printf (desc, "<%s>%s</%s>\n",
+ tag, escaped, tag);
+ } else {
+ as_util_news_add_indent (desc, indent);
+ g_string_append_printf (desc, "<%s>\n", tag);
+ for (i = 0; lines[i] != NULL; i++) {
+ as_util_news_add_indent (desc, indent + 1);
+ g_string_append (desc, lines[i]);
+ }
+ as_util_news_add_indent (desc, indent);
+ g_string_append_printf (desc, "</%s>\n", tag);
+ }
+ }
+}
+
+/**
+ * as_util_news_to_appdata_hdr:
+ **/
+static gboolean
+as_util_news_to_appdata_hdr (GString *desc, const gchar *txt, GError **error)
+{
+ guint i;
+ const gchar *version = NULL;
+ const gchar *release = NULL;
+ _cleanup_strv_free_ gchar **release_split = NULL;
+ _cleanup_date_time_unref_ GDateTime *dt = NULL;
+ _cleanup_strv_free_ gchar **lines = NULL;
+
+ /* get info */
+ lines = g_strsplit (txt, "\n", -1);
+ for (i = 0; lines[i] != NULL; i++) {
+ if (g_str_has_prefix (lines[i], "Version ")) {
+ version = lines[i] + 8;
+ continue;
+ }
+ if (g_str_has_prefix (lines[i], "Released: ")) {
+ release = lines[i] + 10;
+ continue;
+ }
+ }
+
+ /* check these exist */
+ if (version == NULL) {
+ g_set_error (error,
+ AS_ERROR,
+ AS_ERROR_FAILED,
+ "Unable to find version in: %s", txt);
+ return FALSE;
+ }
+ if (release == NULL) {
+ g_set_error (error,
+ AS_ERROR,
+ AS_ERROR_FAILED,
+ "Unable to find release in: %s", txt);
+ return FALSE;
+ }
+
+ /* parse date */
+ release_split = g_strsplit (release, "-", -1);
+ if (g_strv_length (release_split) != 3) {
+ g_set_error (error,
+ AS_ERROR,
+ AS_ERROR_FAILED,
+ "Unable to parse release: %s", release);
+ return FALSE;
+ }
+ dt = g_date_time_new_local (atoi (release_split[0]),
+ atoi (release_split[1]),
+ atoi (release_split[2]),
+ 0, 0, 0);
+ if (dt == NULL) {
+ g_set_error (error,
+ AS_ERROR,
+ AS_ERROR_FAILED,
+ "Unable to create release: %s", release);
+ return FALSE;
+ }
+
+ /* add markup */
+ as_util_news_add_indent (desc, 3);
+ g_string_append_printf (desc, "<release version=\"%s\" "
+ "release=\"%" G_GINT64_FORMAT "\">\n",
+ version,
+ g_date_time_to_unix (dt));
+
+ return TRUE;
+}
+
+/**
+ * as_util_news_to_appdata_list:
+ **/
+static gboolean
+as_util_news_to_appdata_list (GString *desc, gchar **lines, GError **error)
+{
+ guint i;
+
+ as_util_news_add_markup (desc, "ul", NULL);
+ for (i = 1; lines[i] != NULL; i++) {
+ guint prefix = 0;
+ if (g_str_has_prefix (lines[i], " - "))
+ prefix = 3;
+ as_util_news_add_markup (desc, "li", lines[i] + prefix);
+ }
+ as_util_news_add_markup (desc, "/ul", NULL);
+ return TRUE;
+}
+
+/**
+ * as_util_news_to_appdata_para:
+ **/
+static gboolean
+as_util_news_to_appdata_para (GString *desc, const gchar *txt, GError **error)
+{
+ guint i;
+ _cleanup_strv_free_ gchar **lines = NULL;
+
+ lines = g_strsplit (txt, "\n", -1);
+ for (i = 1; lines[i] != NULL; i++) {
+ guint prefix = 0;
+ if (g_str_has_prefix (lines[i], " - "))
+ prefix = 3;
+ as_util_news_add_markup (desc, "p", lines[i] + prefix);
+ }
+ return TRUE;
+}
+
+/**
+ * as_util_news_to_appdata:
+ **/
+static gboolean
+as_util_news_to_appdata (AsUtilPrivate *priv, gchar **values, GError **error)
+{
+ guint i;
+ _cleanup_free_ gchar *data = NULL;
+ _cleanup_string_free_ GString *data_str = NULL;
+ _cleanup_string_free_ GString *desc = NULL;
+ _cleanup_strv_free_ gchar **split = NULL;
+
+ /* check args */
+ if (g_strv_length (values) != 1) {
+ g_set_error_literal (error,
+ AS_ERROR,
+ AS_ERROR_INVALID_ARGUMENTS,
+ "Not enough arguments, "
+ "expected .appdata.xml");
+ return FALSE;
+ }
+
+ /* get data */
+ if (!g_file_get_contents (values[0], &data, NULL, error))
+ return FALSE;
+
+ /* try to unsplit lines */
+ data_str = g_string_new (data);
+ as_string_replace (data_str, "\n ", " ");
+
+ /* break up into sections */
+ desc = g_string_new ("");
+ split = g_strsplit (data_str->str, "\n\n", -1);
+ for (i = 0; split[i] != NULL; i++) {
+ _cleanup_strv_free_ gchar **lines = NULL;
+
+ switch (as_util_news_to_appdata_guess_section (split[i])) {
+ case AS_UTIL_SECTION_KIND_HEADER:
+ {
+ /* flush old release content */
+ if (desc->len > 0) {
+ as_util_news_add_markup (desc, "/release", NULL);
+ g_print ("%s", desc->str);
+ g_string_truncate (desc, 0);
+ }
+
+ /* parse header */
+ if (!as_util_news_to_appdata_hdr (desc, split[i], error))
+ return FALSE;
+ break;
+ }
+ case AS_UTIL_SECTION_KIND_BUGFIX:
+ lines = g_strsplit (split[i], "\n", -1);
+ if (g_strv_length (lines) == 2) {
+ as_util_news_add_markup (desc, "p",
+ "This release fixes the following bug:");
+ } else {
+ as_util_news_add_markup (desc, "p",
+ "This release fixes the following bugs:");
+ }
+ if (!as_util_news_to_appdata_list (desc, lines + 1, error))
+ return FALSE;
+ break;
+ case AS_UTIL_SECTION_KIND_NOTES:
+ if (!as_util_news_to_appdata_para (desc, split[i], error))
+ return FALSE;
+ break;
+ case AS_UTIL_SECTION_KIND_FEATURES:
+ lines = g_strsplit (split[i], "\n", -1);
+ if (g_strv_length (lines) == 2) {
+ as_util_news_add_markup (desc, "p",
+ "This release adds the following feature:");
+ } else {
+ as_util_news_add_markup (desc, "p",
+ "This release adds the following features:");
+ }
+ if (!as_util_news_to_appdata_list (desc, lines + 1, error))
+ return FALSE;
+ break;
+ case AS_UTIL_SECTION_KIND_TRANSLATION:
+ break;
+ default:
+ g_set_error (error,
+ AS_ERROR,
+ AS_ERROR_FAILED,
+ "failed to detect section %s", split[i]);
+ return FALSE;
+ }
+ }
+
+ /* flush old release content */
+ if (desc->len > 0) {
+ as_util_news_add_markup (desc, "/release", NULL);
+ g_print ("%s", desc->str);
+ }
+
+ return TRUE;
+}
+
+/**
* as_util_appdata_from_desktop:
**/
static gboolean
@@ -2700,6 +3059,12 @@ main (int argc, char *argv[])
_("Convert an AppData file to NEWS format"),
as_util_appdata_to_news);
as_util_add (priv->cmd_array,
+ "news-to-appdata",
+ NULL,
+ /* TRANSLATORS: command description */
+ _("Convert an NEWS file to AppData format"),
+ as_util_news_to_appdata);
+ as_util_add (priv->cmd_array,
"check-root",
NULL,
/* TRANSLATORS: command description */