Skip to content

Commit

Permalink
Modernize the validation requirements
Browse files Browse the repository at this point in the history
 * Require <content_rating> for any desktop or console application
 * Make <release> 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: #263
  • Loading branch information
hughsie committed Mar 20, 2019
1 parent a859874 commit 2eb9b8c
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 48 deletions.
4 changes: 2 additions & 2 deletions data/tests/broken.appdata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
<summary>Observe power management.</summary>
<description>
<ul>
<li>One</li>
<li>On</li>
<li>two.</li>
</ul>
<p xml:lang="C">
This application is awesome
Awesome!
</p>
<p>
This application is awesome
Expand Down
13 changes: 13 additions & 0 deletions data/tests/intltool.appdata.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,17 @@
<translation type="gettext">gnome-power-manager</translation>
<url type="homepage">http://www.gnome.org/projects/gnome-power-manager/</url>
<updatecontact>richard_at_hughsie.com</updatecontact>
<content_rating type="oars-1.1" />
<releases>
<release date="2019-03-11" version="3.32.0">
<description>
<p>
This is the first stable release for GNOME 3.32.
</p>
<ul>
<li>Appstream parsing is completely rewritten and now uses the new libxmlb library, instead of appstream-glib</li>
</ul>
</description>
</release>
</releases>
</application>
13 changes: 13 additions & 0 deletions data/tests/success.appdata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,17 @@
<url type="homepage">http://www.gnome.org/projects/gnome-power-manager/</url>
<updatecontact>richard_at_hughsie.com</updatecontact>
<project_group>GNOME</project_group>
<content_rating type="oars-1.1" />
<releases>
<release date="2019-03-11" version="3.32.0">
<description>
<p>
This is the first stable release for GNOME 3.32.
</p>
<ul>
<li>Appstream parsing is completely rewritten and now uses the new libxmlb library, instead of appstream-glib</li>
</ul>
</description>
</release>
</releases>
</application>
155 changes: 127 additions & 28 deletions libappstream-glib/as-app-validate.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -180,7 +186,7 @@ as_app_validate_description_li (const gchar *text, AsAppValidateHelper *helper)
"<li> 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,
"<li> cannot end in '.' [%s]", text);
Expand All @@ -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 */
Expand All @@ -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 */
Expand Down Expand Up @@ -261,7 +271,8 @@ as_app_validate_description_para (const gchar *text, AsAppValidateHelper *helper
AS_PROBLEM_KIND_STYLE_INCORRECT,
"<p> 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,
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Expand All @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -907,14 +934,28 @@ 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);
if (as_format_get_kind (format) != AS_FORMAT_KIND_APPDATA &&
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,
"<release> 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))
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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 */
Expand Down Expand Up @@ -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,
"<category> 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 <category> types: %u",
nr_toplevel_cats);
}
}

/* translation */
if (require_translation &&
as_format_get_kind (format) == AS_FORMAT_KIND_APPDATA &&
Expand Down Expand Up @@ -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,
"<content_rating> required for game "
"<content_rating> required "
"[use https://odrs.gnome.org/oars]");
}
}
Expand Down Expand Up @@ -1544,7 +1635,8 @@ as_app_validate (AsApp *app, guint32 flags, GError **error)
"<summary> 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,
"<summary> cannot end in '.' [%s]",
Expand All @@ -1568,14 +1660,21 @@ as_app_validate (AsApp *app, guint32 flags, GError **error)
AS_PROBLEM_KIND_TAG_MISSING,
"<summary> 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,
"<summary> is shorter than <name>");
}
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,
"<description> required");
}
} else {
ret = as_app_validate_description (description,
helper,
number_para_min,
Expand Down
Loading

0 comments on commit 2eb9b8c

Please sign in to comment.