From 2eb9b8caf9ca22ac914b63f34fea5dd6da6f4e76 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Wed, 20 Mar 2019 14:24:03 +0000 Subject: Modernize the validation requirements * Require for any desktop or console application * Make mandatory when validating * Further relax style requirements * Require description for desktop an console apps This changes a lot of things that are now best practice for all apps, and required to be included in Flathub. Use `appstream-util validate-relax` to ignore some new failures and use `appstream-util validate-strict` to bring them back. Fixes: https://github.com/hughsie/appstream-glib/issues/263 --- data/tests/broken.appdata.xml | 4 +- data/tests/intltool.appdata.xml.in | 13 +++ data/tests/success.appdata.xml | 13 +++ libappstream-glib/as-app-validate.c | 155 +++++++++++++++++++++++++++++------- libappstream-glib/as-self-test.c | 28 +++---- 5 files changed, 165 insertions(+), 48 deletions(-) diff --git a/data/tests/broken.appdata.xml b/data/tests/broken.appdata.xml index 126d419..f7a5386 100644 --- a/data/tests/broken.appdata.xml +++ b/data/tests/broken.appdata.xml @@ -9,11 +9,11 @@ Observe power management.
    -
  • One
  • +
  • On
  • two.

- This application is awesome + Awesome!

This application is awesome diff --git a/data/tests/intltool.appdata.xml.in b/data/tests/intltool.appdata.xml.in index 6033cc6..1c6cec0 100644 --- a/data/tests/intltool.appdata.xml.in +++ b/data/tests/intltool.appdata.xml.in @@ -30,4 +30,17 @@ gnome-power-manager http://www.gnome.org/projects/gnome-power-manager/ richard_at_hughsie.com + + + + +

+ This is the first stable release for GNOME 3.32. +

+
    +
  • Appstream parsing is completely rewritten and now uses the new libxmlb library, instead of appstream-glib
  • +
+
+
+ diff --git a/data/tests/success.appdata.xml b/data/tests/success.appdata.xml index e1ca97f..248b9e6 100644 --- a/data/tests/success.appdata.xml +++ b/data/tests/success.appdata.xml @@ -42,4 +42,17 @@ http://www.gnome.org/projects/gnome-power-manager/ richard_at_hughsie.com GNOME + + + + +

+ This is the first stable release for GNOME 3.32. +

+
    +
  • Appstream parsing is completely rewritten and now uses the new libxmlb library, instead of appstream-glib
  • +
+
+
+
diff --git a/libappstream-glib/as-app-validate.c b/libappstream-glib/as-app-validate.c index 742b968..3ed6d40 100644 --- a/libappstream-glib/as-app-validate.c +++ b/libappstream-glib/as-app-validate.c @@ -147,10 +147,16 @@ as_app_validate_has_first_word_capital (AsAppValidateHelper *helper, const gchar static void as_app_validate_description_li (const gchar *text, AsAppValidateHelper *helper) { - gboolean require_sentence_case = TRUE; + gboolean require_sentence_case = FALSE; guint str_len; - guint length_li_max = 100; - guint length_li_min = 20; + guint length_li_max = 500; + guint length_li_min = 3; + + /* make the requirements more strict */ + if ((helper->flags & AS_APP_VALIDATE_FLAG_STRICT) > 0) { + require_sentence_case = TRUE; + length_li_max = 250; + } /* relax the requirements a bit */ if ((helper->flags & AS_APP_VALIDATE_FLAG_RELAX) > 0) { @@ -180,7 +186,7 @@ as_app_validate_description_li (const gchar *text, AsAppValidateHelper *helper) "
  • is too long [%s] maximum is %u chars", text, length_li_max); } - if (ai_app_validate_fullstop_ending (text)) { + if (require_sentence_case && ai_app_validate_fullstop_ending (text)) { ai_app_validate_add (helper, AS_PROBLEM_KIND_STYLE_INCORRECT, "
  • cannot end in '.' [%s]", text); @@ -202,9 +208,9 @@ as_app_validate_description_li (const gchar *text, AsAppValidateHelper *helper) static void as_app_validate_description_para (const gchar *text, AsAppValidateHelper *helper) { - gboolean require_sentence_case = TRUE; - guint length_para_max = 600; - guint length_para_min = 50; + gboolean require_sentence_case = FALSE; + guint length_para_max = 1000; + guint length_para_min = 10; guint str_len; /* empty */ @@ -215,11 +221,15 @@ as_app_validate_description_para (const gchar *text, AsAppValidateHelper *helper return; } + /* make the requirements more strict */ + if ((helper->flags & AS_APP_VALIDATE_FLAG_STRICT) > 0) { + require_sentence_case = TRUE; + } + /* relax the requirements a bit */ if ((helper->flags & AS_APP_VALIDATE_FLAG_RELAX) > 0) { - length_para_max = 1000; - length_para_min = 10; - require_sentence_case = FALSE; + length_para_max = 2000; + length_para_min = 5; } /* previous was short */ @@ -261,7 +271,8 @@ as_app_validate_description_para (const gchar *text, AsAppValidateHelper *helper AS_PROBLEM_KIND_STYLE_INCORRECT, "

    requires sentence case [%s]", text); } - if (text[str_len - 1] != '.' && + if (require_sentence_case && + text[str_len - 1] != '.' && text[str_len - 1] != '!' && text[str_len - 1] != ':') { ai_app_validate_add (helper, @@ -277,7 +288,7 @@ as_app_validate_description_list (const gchar *text, gboolean allow_short_para, AsAppValidateHelper *helper) { - guint length_para_before_list = 300; + guint length_para_before_list = 20; /* relax the requirements a bit */ if ((helper->flags & AS_APP_VALIDATE_FLAG_RELAX) > 0) { @@ -689,8 +700,17 @@ as_app_validate_icons (AsApp *app, AsAppValidateHelper *helper) /* just check the default icon */ icon = as_app_get_icon_default (app); - if (icon == NULL) + if (icon == NULL) { + AsFormat *fmt = as_app_get_format_default (app); + if (fmt != NULL && + as_format_get_kind (fmt) == AS_FORMAT_KIND_APPSTREAM && + as_app_get_kind (app) == AS_APP_KIND_DESKTOP) { + ai_app_validate_add (helper, + AS_PROBLEM_KIND_TAG_MISSING, + "desktop application has no icon"); + } return; + } /* check the content is correct */ icon_kind = as_icon_get_kind (icon); @@ -746,7 +766,7 @@ as_app_validate_screenshots (AsApp *app, AsAppValidateHelper *helper) AsScreenshot *ss; GPtrArray *screenshots; gboolean screenshot_has_default = FALSE; - guint number_screenshots_max = 5; + guint number_screenshots_max = 25; guint number_screenshots_min = 1; guint i; @@ -759,6 +779,8 @@ as_app_validate_screenshots (AsApp *app, AsAppValidateHelper *helper) /* firmware does not need screenshots */ if (as_app_get_kind (app) == AS_APP_KIND_FIRMWARE || as_app_get_kind (app) == AS_APP_KIND_DRIVER || + as_app_get_kind (app) == AS_APP_KIND_RUNTIME || + as_app_get_kind (app) == AS_APP_KIND_ADDON || as_app_get_kind (app) == AS_APP_KIND_LOCALIZATION) number_screenshots_min = 0; @@ -812,16 +834,21 @@ as_app_validate_release (AsApp *app, { const gchar *tmp; guint64 timestamp; - guint number_para_max = 3; + guint number_para_max = 10; guint number_para_min = 1; gboolean required_timestamp = TRUE; /* relax the requirements a bit */ if ((helper->flags & AS_APP_VALIDATE_FLAG_RELAX) > 0) { - number_para_max = 4; + number_para_max = 20; required_timestamp = FALSE; } + /* make the requirements more strict */ + if ((helper->flags & AS_APP_VALIDATE_FLAG_STRICT) > 0) { + number_para_max = 4; + } + /* check version */ tmp = as_release_get_version (release); if (tmp == NULL) { @@ -907,6 +934,7 @@ as_app_validate_releases (AsApp *app, AsAppValidateHelper *helper, GError **erro { GPtrArray *releases; AsFormat *format; + gboolean require_release = FALSE; /* only for AppData */ format = as_app_get_format_default (app); @@ -914,7 +942,20 @@ as_app_validate_releases (AsApp *app, AsAppValidateHelper *helper, GError **erro as_format_get_kind (format) != AS_FORMAT_KIND_METAINFO) return TRUE; + /* only for desktop and console apps */ + if (as_app_get_kind (app) == AS_APP_KIND_DESKTOP || + as_app_get_kind (app) == AS_APP_KIND_CONSOLE) { + require_release = TRUE; + } + + /* require releases */ releases = as_app_get_releases (app); + if (require_release && releases->len == 0) { + ai_app_validate_add (helper, + AS_PROBLEM_KIND_TAG_MISSING, + " required"); + return TRUE; + } for (guint i = 0; i < releases->len; i++) { AsRelease *release = g_ptr_array_index (releases, i); if (!as_app_validate_release (app, release, helper, error)) @@ -1149,22 +1190,24 @@ as_app_validate (AsApp *app, guint32 flags, GError **error) gboolean require_appstream_spec_only = FALSE; gboolean require_contactdetails = TRUE; gboolean require_copyright = FALSE; + gboolean require_description = FALSE; gboolean require_project_license = FALSE; - gboolean require_sentence_case = TRUE; + gboolean require_sentence_case = FALSE; gboolean require_translations = FALSE; gboolean require_url = TRUE; gboolean require_content_license = TRUE; gboolean require_name = TRUE; gboolean require_translation = TRUE; - gboolean require_content_rating = TRUE; + gboolean require_content_rating = FALSE; + gboolean require_name_shorter_than_summary = FALSE; gboolean validate_license = TRUE; gboolean ret; - guint length_name_max = 30; + guint length_name_max = 60; guint length_name_min = 3; - guint length_summary_max = 100; + guint length_summary_max = 200; guint length_summary_min = 8; - guint number_para_max = 4; - guint number_para_min = 2; + guint number_para_max = 10; + guint number_para_min = 1; guint str_len; g_autoptr(GList) keys = NULL; g_autoptr(AsAppValidateHelper) helper = g_new0 (AsAppValidateHelper, 1); @@ -1179,6 +1222,13 @@ as_app_validate (AsApp *app, guint32 flags, GError **error) return NULL; } + /* only for desktop and console apps */ + if (as_app_get_kind (app) == AS_APP_KIND_DESKTOP || + as_app_get_kind (app) == AS_APP_KIND_CONSOLE) { + require_content_rating = TRUE; + require_description = TRUE; + } + /* relax the requirements a bit */ if ((flags & AS_APP_VALIDATE_FLAG_RELAX) > 0) { length_name_max = 100; @@ -1187,7 +1237,7 @@ as_app_validate (AsApp *app, guint32 flags, GError **error) require_content_license = FALSE; validate_license = FALSE; require_url = FALSE; - number_para_max = 10; + number_para_max = 20; number_para_min = 1; require_sentence_case = FALSE; require_translation = FALSE; @@ -1210,6 +1260,10 @@ as_app_validate (AsApp *app, guint32 flags, GError **error) require_project_license = TRUE; require_content_license = TRUE; require_appstream_spec_only = TRUE; + require_sentence_case = TRUE; + require_name_shorter_than_summary = TRUE; + number_para_min = 2; + number_para_max = 4; } /* addons don't need such a long description */ @@ -1299,6 +1353,43 @@ as_app_validate (AsApp *app, guint32 flags, GError **error) } } + /* categories */ + if (as_format_get_kind (format) == AS_FORMAT_KIND_APPSTREAM && + as_app_get_kind (app) == AS_APP_KIND_DESKTOP) { + GPtrArray *categories = as_app_get_categories (app); + guint nr_toplevel_cats = 0; + const gchar *cats[] = { "AudioVideo", + "Development", + "Education", + "Game", + "Graphics", + "Network", + "Office", + "Science", + "Settings", + "System", + "Utility", + NULL }; + for (guint i = 0; i < categories->len; i++) { + const gchar *cat = g_ptr_array_index (categories, i); + for (guint j = 0; cats[j] != NULL; j++) { + if (g_strcmp0 (cats[j], cat) == 0) + nr_toplevel_cats++; + } + } + if (nr_toplevel_cats == 0) { + ai_app_validate_add (helper, + AS_PROBLEM_KIND_TAG_MISSING, + " must include main categories " + "from the desktop entry spec"); + } else if (nr_toplevel_cats > 3) { + ai_app_validate_add (helper, + AS_PROBLEM_KIND_TAG_MISSING, + "too many main types: %u", + nr_toplevel_cats); + } + } + /* translation */ if (require_translation && as_format_get_kind (format) == AS_FORMAT_KIND_APPDATA && @@ -1441,12 +1532,12 @@ as_app_validate (AsApp *app, guint32 flags, GError **error) } /* games require a content rating */ - if (require_content_rating && as_app_has_category (app, "Game")) { + if (require_content_rating) { GPtrArray *ratings = as_app_get_content_ratings (app); if (ratings->len == 0) { ai_app_validate_add (helper, AS_PROBLEM_KIND_TAG_MISSING, - " required for game " + " required " "[use https://odrs.gnome.org/oars]"); } } @@ -1544,7 +1635,8 @@ as_app_validate (AsApp *app, guint32 flags, GError **error) "

    is too long [%s] maximum is %u chars", summary, length_summary_max); } - if (ai_app_validate_fullstop_ending (summary)) { + if (require_sentence_case && + ai_app_validate_fullstop_ending (summary)) { ai_app_validate_add (helper, AS_PROBLEM_KIND_STYLE_INCORRECT, " cannot end in '.' [%s]", @@ -1568,14 +1660,21 @@ as_app_validate (AsApp *app, guint32 flags, GError **error) AS_PROBLEM_KIND_TAG_MISSING, " is not present"); } - if (summary != NULL && name != NULL && + if (require_name_shorter_than_summary && + summary != NULL && name != NULL && strlen (summary) < strlen (name)) { ai_app_validate_add (helper, AS_PROBLEM_KIND_STYLE_INCORRECT, " is shorter than "); } description = as_app_get_description (app, "C"); - if (description != NULL) { + if (description == NULL) { + if (require_description) { + ai_app_validate_add (helper, + AS_PROBLEM_KIND_TAG_MISSING, + " required"); + } + } else { ret = as_app_validate_description (description, helper, number_para_min, diff --git a/libappstream-glib/as-self-test.c b/libappstream-glib/as-self-test.c index 1758bb8..b65d523 100644 --- a/libappstream-glib/as-self-test.c +++ b/libappstream-glib/as-self-test.c @@ -2250,22 +2250,16 @@ as_test_app_validate_file_bad_func (void) " header not found"); as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT, " cannot end in '.'"); - as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT, - " cannot end in '.'"); as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT, "Not enough tags"); as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT, "
  • is too short"); - as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT, - "
  • cannot end in '.'"); as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT, "
      cannot start a description"); as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT, "
        cannot start a description"); as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT, "

        should not start with 'This application'"); - as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT, - "

        does not end in '.|:|!'"); as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT, "

        is too short"); as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT, @@ -2279,10 +2273,6 @@ as_test_app_validate_file_bad_func (void) " has no version"); as_test_app_validate_check (probs, AS_PROBLEM_KIND_ATTRIBUTE_MISSING, " has no timestamp"); - as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT, - "

        requires sentence case"); - as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT, - "

      • requires sentence case"); as_test_app_validate_check (probs, AS_PROBLEM_KIND_TAG_MISSING, " not specified"); as_test_app_validate_check (probs, AS_PROBLEM_KIND_TAG_INVALID, @@ -2291,11 +2281,9 @@ as_test_app_validate_file_bad_func (void) " version was duplicated"); as_test_app_validate_check (probs, AS_PROBLEM_KIND_ATTRIBUTE_INVALID, " timestamp is in the future"); - as_test_app_validate_check (probs, AS_PROBLEM_KIND_TAG_MISSING, - " required for game"); as_test_app_validate_check (probs, AS_PROBLEM_KIND_MARKUP_INVALID, " has invalid character"); - g_assert_cmpint (probs->len, ==, 34); + g_assert_cmpint (probs->len, ==, 23); /* again, harder */ probs2 = as_app_validate (app, AS_APP_VALIDATE_FLAG_STRICT, &error); @@ -2303,7 +2291,7 @@ as_test_app_validate_file_bad_func (void) g_assert (probs2 != NULL); as_test_app_validate_check (probs2, AS_PROBLEM_KIND_TAG_INVALID, "XML data contains unknown tag"); - g_assert_cmpint (probs2->len, ==, 40); + g_assert_cmpint (probs2->len, ==, 35); } static void @@ -2448,7 +2436,7 @@ as_test_app_validate_style_func (void) _as_app_add_format_kind (app, AS_FORMAT_KIND_APPDATA); as_app_set_metadata_license (app, "BSD"); as_app_set_project_license (app, "GPL-2.0+"); - as_app_set_name (app, "C", "Test app name that is very log indeed."); + as_app_set_name (app, "C", "Test app name that is very long indeed although Rob forced me to relax the requirements."); as_app_set_comment (app, "C", "Awesome"); as_app_set_update_contact (app, "someone_who_cares@upstream_project.org"); @@ -2475,13 +2463,17 @@ as_test_app_validate_style_func (void) " is too short"); as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT, "Not enough tags"); - as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT, - " is shorter than "); as_test_app_validate_check (probs, AS_PROBLEM_KIND_TAG_MISSING, " is not present"); as_test_app_validate_check (probs, AS_PROBLEM_KIND_TAG_MISSING, " not specified"); - g_assert_cmpint (probs->len, ==, 11); + as_test_app_validate_check (probs, AS_PROBLEM_KIND_TAG_MISSING, + " required"); + as_test_app_validate_check (probs, AS_PROBLEM_KIND_TAG_MISSING, + " required"); + as_test_app_validate_check (probs, AS_PROBLEM_KIND_TAG_MISSING, + " required"); + g_assert_cmpint (probs->len, ==, 13); } static void -- cgit v1.2.1