From 5c747691166d1db7f508cd3eaeb38ffc41be1d7f Mon Sep 17 00:00:00 2001 From: Matthias Klumpp Date: Tue, 23 Jan 2024 01:03:33 +0100 Subject: [PATCH] docs: Generate validation issue tag documentation from code --- docs/doc-build-helper.py | 35 ++++-- docs/gen-valtag-sections.c | 171 ++++++++++++++++++++++++++ docs/meson.build | 29 ++++- docs/validator-compose-hints.xml.tmpl | 33 +++++ docs/validator-issues.xml.tmpl | 41 ++++++ docs/xml/AppStream.xml | 1 + docs/xml/Validation.xml | 15 +++ tools/appstream-compose.c | 167 ++++++++++++++----------- 8 files changed, 412 insertions(+), 80 deletions(-) create mode 100644 docs/gen-valtag-sections.c create mode 100644 docs/validator-compose-hints.xml.tmpl create mode 100644 docs/validator-issues.xml.tmpl create mode 100644 docs/xml/Validation.xml diff --git a/docs/doc-build-helper.py b/docs/doc-build-helper.py index b79cbcb42..8d8c9d9c6 100755 --- a/docs/doc-build-helper.py +++ b/docs/doc-build-helper.py @@ -33,10 +33,16 @@ EXTRA_CSS = [['/usr/share/javascript/highlight.js/styles/routeros.css', 'highlight.css']] -def daps_build(src_dir, project_name, daps_exe): +def daps_build(src_dir, project_name, daps_exe, valsec_gen): print('Creating HTML with DAPS...') sys.stdout.flush() + if valsec_gen: + ret = subprocess.call([valsec_gen, src_dir], cwd=src_dir) + if ret != 0: + print('Failed to generated some documentation sections.') + sys.exit(6) + build_dir = os.path.join(src_dir, '_docbuild') cmd = [daps_exe, 'html', '--clean'] if project_name: @@ -77,15 +83,29 @@ def copy_result(build_dir, project_name, dest_dir): ) -def cleanup_build_dir(build_dir): +def cleanup_workspace(src_dir, build_dir): print('Cleaning up.') + assert src_dir != build_dir + + try: + os.remove(os.path.join(src_dir, 'xml', 'validator-compose-hints.xml')) + os.remove(os.path.join(src_dir, 'xml', 'validator-issues.xml')) + except OSError: + pass + if os.path.exists(build_dir): shutil.rmtree(build_dir) -def daps_validate(src_dir, daps_exe): +def daps_validate(src_dir, daps_exe, valsec_gen): print('Validating documentation with DAPS...') + if valsec_gen: + ret = subprocess.call([valsec_gen, src_dir], cwd=src_dir) + if ret != 0: + print('Failed to generated some documentation sections.') + sys.exit(6) + build_dir = os.path.join(src_dir, '_docbuild') if os.path.exists(build_dir): shutil.rmtree(build_dir) @@ -94,7 +114,7 @@ def daps_validate(src_dir, daps_exe): ret = subprocess.call([daps_exe, 'validate'], cwd=src_dir) if ret != 0: print('Validation failed!') - cleanup_build_dir(build_dir) + cleanup_workspace(src_dir, build_dir) return ret == 0 @@ -103,6 +123,7 @@ def main(args): parser.add_argument('--build', action='store_true') parser.add_argument('--validate', action='store_true') + parser.add_argument('--valsec-gen', action='store') parser.add_argument('--src', action='store') parser.add_argument('--builddir', action='store') parser.add_argument('--daps', action='store', default='daps') @@ -122,13 +143,13 @@ def main(args): if options.build: # build the HTML files - build_dir = daps_build(options.src, options.project, options.daps) + build_dir = daps_build(options.src, options.project, options.daps, options.valsec_gen) # copy to output HTML folder, overriding all previous contents copy_result(build_dir, options.project, os.path.join(options.src, 'html')) # remove temporary directory - cleanup_build_dir(build_dir) + cleanup_workspace(options.src, build_dir) # make a dummy file so Meson can rebuild documentation on demand if options.builddir: @@ -138,7 +159,7 @@ def main(args): elif options.validate: # validate the XML - ret = daps_validate(options.src, options.daps) + ret = daps_validate(options.src, options.daps, options.valsec_gen) if not ret: sys.exit(6) diff --git a/docs/gen-valtag-sections.c b/docs/gen-valtag-sections.c new file mode 100644 index 000000000..8f30be966 --- /dev/null +++ b/docs/gen-valtag-sections.c @@ -0,0 +1,171 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2019-2024 Matthias Klumpp + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the license, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include "as-validator-issue-tag.h" +#include "asc-hint-tags.h" + +static GString * +load_doc_template (const gchar *dir_path, const gchar *tmpl_name) +{ + g_autofree gchar *path = NULL; + g_autofree gchar *contents = NULL; + g_autoptr(GError) error = NULL; + + path = g_build_filename (dir_path, tmpl_name, NULL); + + if (!g_file_get_contents (path, &contents, NULL, &error)) { + g_error ("Failed to load template file '%s': %s", tmpl_name, error->message); + return NULL; + } + + return g_string_new (contents); +} + +static gchar * +make_valtag_entry (const gchar *ns_prefix, + const gchar *tag, + AsIssueSeverity severity, + const gchar *explanation) +{ + GString *entry = NULL; + g_autofree gchar *explanation_xml = NULL; + + const gchar *entry_tmpl = + " \n" + " {{tag}}\n" + " \n" + " Severity: {{severity}}\n" + " \n" + " {{explanation}}\n" + " \n" + " \n" + " "; + + entry = g_string_new (entry_tmpl); + + as_gstring_replace (entry, "{{prefix}}", ns_prefix, -1); + as_gstring_replace (entry, "{{tag}}", tag, -1); + as_gstring_replace (entry, "{{severity}}", as_issue_severity_to_string (severity), -1); + explanation_xml = g_markup_escape_text (explanation, -1); + as_gstring_replace (entry, "{{explanation}}", explanation_xml, -1); + + return g_string_free (entry, FALSE); +} + +static gboolean +process_validator_tag_lists (const gchar *work_dir) +{ + g_autoptr(GString) val_contents = NULL; + g_autoptr(GString) coval_contents = NULL; + g_autoptr(GString) valtags = NULL; + g_autofree gchar *val_fname = NULL; + g_autofree gchar *coval_fname = NULL; + g_autoptr(GError) error = NULL; + + val_contents = load_doc_template (work_dir, "validator-issues.xml.tmpl"); + coval_contents = load_doc_template (work_dir, "validator-compose-hints.xml.tmpl"); + + /* process validator hint tags */ + valtags = g_string_new (""); + for (guint i = 0; as_validator_issue_tag_list[i].tag != NULL; i++) { + g_autofree gchar *entry_str = NULL; + + entry_str = make_valtag_entry ("asv", + as_validator_issue_tag_list[i].tag, + as_validator_issue_tag_list[i].severity, + as_validator_issue_tag_list[i].explanation); + g_string_append_printf (valtags, "\n%s\n", entry_str); + } + as_gstring_replace (val_contents, "{{issue_list}}", valtags->str, -1); + g_string_truncate (valtags, 0); + + /* process compose hint tags */ + for (guint i = 0; asc_hint_tag_list[i].tag != NULL; i++) { + g_autofree gchar *entry_str = NULL; + + entry_str = make_valtag_entry ("asc", + asc_hint_tag_list[i].tag, + asc_hint_tag_list[i].severity, + asc_hint_tag_list[i].explanation); + g_string_append_printf (valtags, "\n%s\n", entry_str); + } + as_gstring_replace (coval_contents, "{{hints_list}}", valtags->str, -1); + + /* save result */ + val_fname = g_build_filename (work_dir, "xml", "validator-issues.xml", NULL); + coval_fname = g_build_filename (work_dir, "xml", "validator-compose-hints.xml", NULL); + + if (!g_file_set_contents (val_fname, val_contents->str, -1, &error)) { + g_error ("Failed to save generated documentation: %s", error->message); + return FALSE; + } + + if (!g_file_set_contents (coval_fname, coval_contents->str, -1, &error)) { + g_error ("Failed to save generated documentation: %s", error->message); + return FALSE; + } + + return TRUE; +} + +int +main (int argc, char **argv) +{ + g_autoptr(GOptionContext) option_context = NULL; + gboolean ret; + gboolean verbose = FALSE; + g_autoptr(GError) error = NULL; + + const GOptionEntry options[] = { + { "verbose", + 'v', 0, + G_OPTION_ARG_NONE, &verbose, + "Show extra debugging information", NULL }, + { NULL } + }; + + option_context = g_option_context_new (" - DOCDIR"); + + g_option_context_add_main_entries (option_context, options, NULL); + ret = g_option_context_parse (option_context, &argc, &argv, &error); + if (!ret) { + /* TRANSLATORS: Error message of appstream-compose */ + g_print ("%s: %s\n", "Failed to parse arguments", error->message); + return EXIT_FAILURE; + } + + if (verbose) + g_setenv ("G_MESSAGES_DEBUG", "all", TRUE); + + if (argc <= 1) { + g_autofree gchar *tmp = NULL; + tmp = g_option_context_get_help (option_context, TRUE, NULL); + g_print ("%s", tmp); + return EXIT_FAILURE; + } + + process_validator_tag_lists (argv[1]); + + return EXIT_SUCCESS; +} diff --git a/docs/meson.build b/docs/meson.build index a1bc78dff..5b6ee6715 100644 --- a/docs/meson.build +++ b/docs/meson.build @@ -61,6 +61,23 @@ endif # Documentation # +gen_doc_valsec_src = [ + 'gen-valtag-sections.c', + '../src/as-validator-issue-tag.h', + '../compose/asc-hint-tags.h', + '../compose/asc-hint-tags.c', +] + +gen_doc_valsec_exe = executable('gen-validator-tag-sections', + [gen_doc_valsec_src], + dependencies: [appstream_dep, + gio_dep], + include_directories: [root_inc_dir, + include_directories ('../compose')], + c_args: ['-DASC_COMPILATION'], + install: false, +) + as_doc_src = [ 'xml/APIDoc.xml', 'xml/AppStream.xml', @@ -100,6 +117,7 @@ as_doc_src = [ 'xml/quickstart-desktopapps.xml', 'xml/quickstart-packaging.xml', 'xml/quickstart-translation.xml', + 'xml/Validation.xml', ] hljs_installed_file = '/usr/share/javascript/highlight.js/highlight.min.js' @@ -113,6 +131,7 @@ if get_option('docs') '--build', '--src', meson.current_source_dir(), '--builddir', meson.current_build_dir(), + '--valsec-gen', gen_doc_valsec_exe.path(), 'AppStream' ] @@ -120,12 +139,15 @@ if get_option('docs') input: ['DC-AppStream', as_doc_src], output: ['docs_built.stamp'], + depends: [gen_doc_valsec_exe], build_by_default: true, command: build_docs_cmd ) # helper if you only and always want to rebuild the docs - run_target('documentation', command: build_docs_cmd) + run_target('documentation', + command: build_docs_cmd, + depends: [gen_doc_valsec_exe]) if get_option('install-docs') install_subdir('html', install_dir: as_doc_target_dir) @@ -139,11 +161,12 @@ if get_option('docs') endif # add an extra testcase for documentation validation - test ('as-validate_docs', + test('as-validate_docs', python_exe, args: [join_paths(meson.current_source_dir(), 'doc-build-helper.py'), '--validate', - '--src', meson.current_source_dir()], + '--src', meson.current_source_dir(), + '--valsec-gen', gen_doc_valsec_exe.path()], timeout: 60 ) elif get_option('install-docs') diff --git a/docs/validator-compose-hints.xml.tmpl b/docs/validator-compose-hints.xml.tmpl new file mode 100644 index 000000000..a1df2b02d --- /dev/null +++ b/docs/validator-compose-hints.xml.tmpl @@ -0,0 +1,33 @@ + + +%BOOK_ENTITIES; +]> + +
+ Compose Hints + +
+ Introduction + + The AppStream Compose utility appstreamcli compose can be used to construct + AppStream catalog metadata from a set of directories and other sources containing MetaInfo files. + + + While compositing the final data, a variety of hints can be emitted. + +
+ +
+ Compose Hints List + + + This is a list of all possible validation issues that appstreamcli can detect. + + + + {{hints_list}} + + +
+
diff --git a/docs/validator-issues.xml.tmpl b/docs/validator-issues.xml.tmpl new file mode 100644 index 000000000..435b07e17 --- /dev/null +++ b/docs/validator-issues.xml.tmpl @@ -0,0 +1,41 @@ + + +%BOOK_ENTITIES; +]> + +
+ Validator Issues + +
+ Introduction + + The AppStream utility appstreamcli validate can validate all kinds of AppSteeam XML metadata. + It will emit a variety of hints with different priority: + + + Error hints render the data unusable + or severely limit its usefulness and make it fail validation. Warnings warn about likely unintended + behavior, typing errors or issues which might cause data to not be displayed or improperly read. They fail validation, + but may be ignored in some circumstances. Info hints are optional recommendations to make the + data better, while pedantic hints are nice-to-have. The latter are not displayed by default + unless the tool is in pedantic mode. + + + This document contains a list of all issues checked for by appstreamcli validate. + +
+ +
+ Validator Issue Tags + + + This is a list of all possible validation issues that appstreamcli can detect. + + + + {{issue_list}} + + +
+
diff --git a/docs/xml/AppStream.xml b/docs/xml/AppStream.xml index 100d3deff..175602671 100644 --- a/docs/xml/AppStream.xml +++ b/docs/xml/AppStream.xml @@ -14,6 +14,7 @@ + diff --git a/docs/xml/Validation.xml b/docs/xml/Validation.xml new file mode 100644 index 000000000..9c54b8e9a --- /dev/null +++ b/docs/xml/Validation.xml @@ -0,0 +1,15 @@ + + +%BOOK_ENTITIES; +]> + + Data Validation + + These pages contain various information on AppStream metadata validation. + + + + + + diff --git a/tools/appstream-compose.c b/tools/appstream-compose.c index 1efde5e34..98042930b 100644 --- a/tools/appstream-compose.c +++ b/tools/appstream-compose.c @@ -187,89 +187,116 @@ main (int argc, char **argv) AscComposeFlags compose_flags; GPtrArray *results; + /* clang-format off */ const GOptionEntry options[] = { { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, /* TRANSLATORS: ascompose flag description for: --verbose */ - _("Show extra debugging information"), NULL }, - { "no-color", - '\0', 0, - G_OPTION_ARG_NONE, &no_color, - /* TRANSLATORS: ascompose flag description for: --no-color */ - _("Don\'t show colored output."), NULL }, - { "version", - '\0', 0, - G_OPTION_ARG_NONE, &show_version, - /* TRANSLATORS: ascompose flag description for: --version */ - _("Show the program version."), NULL }, - { "no-net", - '\0', 0, - G_OPTION_ARG_NONE, &no_net, - /* TRANSLATORS: ascompose flag description for: --no-net */ - _("Do not use the network at all, not even for URL validity checks."), NULL }, - { "print-report", + _("Show extra debugging information"), + NULL }, + + { "no-color", + '\0', 0, + G_OPTION_ARG_NONE, &no_color, + /* TRANSLATORS: ascompose flag description for: --no-color */ + _("Don\'t show colored output."), + NULL }, + + { "version", + '\0', 0, + G_OPTION_ARG_NONE, &show_version, + /* TRANSLATORS: ascompose flag description for: --version */ + _("Show the program version."), + NULL }, + + { "no-net", + '\0', 0, + G_OPTION_ARG_NONE, &no_net, + /* TRANSLATORS: ascompose flag description for: --no-net */ + _("Do not use the network at all, not even for URL validity checks."), + NULL }, + + { "print-report", '\0', 0, G_OPTION_ARG_STRING, &report_mode_str, /* TRANSLATORS: ascompose flag description for: --print-report */ - _("Set mode of the issue report that is printed to the console"), "MODE" }, - { "prefix", - '\0', 0, - G_OPTION_ARG_FILENAME, &prefix, - /* TRANSLATORS: ascompose flag description for: --prefix */ - _("Override the default prefix (`/usr` by default)"), "DIR" }, - { "result-root", - '\0', 0, - G_OPTION_ARG_FILENAME, &res_root_dir, - /* TRANSLATORS: ascompose flag description for: --result-root */ - _("Set the result output directory"), "DIR" }, - { "data-dir", - '\0', 0, - G_OPTION_ARG_FILENAME, &mdata_dir, - /* TRANSLATORS: ascompose flag description for: --data-dir, `catalog metadata` is an AppStream term */ - _("Override the catalog metadata output directory"), "DIR" }, - { "icons-dir", + _("Set mode of the issue report that is printed to the console"), + "MODE" }, + + { "prefix", + '\0', 0, + G_OPTION_ARG_FILENAME, &prefix, + /* TRANSLATORS: ascompose flag description for: --prefix */ + _("Override the default prefix (`/usr` by default)"), + "DIR" }, + + { "result-root", + '\0', 0, + G_OPTION_ARG_FILENAME, &res_root_dir, + /* TRANSLATORS: ascompose flag description for: --result-root */ + _("Set the result output directory"), + "DIR" }, + + { "data-dir", + '\0', 0, + G_OPTION_ARG_FILENAME, &mdata_dir, + /* TRANSLATORS: ascompose flag description for: --data-dir, `catalog metadata` is an AppStream term */ + _("Override the catalog metadata output directory"), + "DIR" }, + + { "icons-dir", '\0', 0, G_OPTION_ARG_FILENAME, &icons_dir, /* TRANSLATORS: ascompose flag description for: --icons-dir */ - _("Override the icon output directory"), "DIR" }, - { "media-dir", - '\0', 0, - G_OPTION_ARG_FILENAME, &media_dir, - /* TRANSLATORS: ascompose flag description for: --media-dir */ - _("Set the media output directory (for media data to be served by a webserver)"), - "DIR" }, - { "hints-dir", - '\0', 0, - G_OPTION_ARG_FILENAME, &hints_dir, - /* TRANSLATORS: ascompose flag description for: --hints-dir */ - _("Set a directory where HTML and text issue reports will be stored"), - "DIR" }, - { "origin", - '\0', 0, - G_OPTION_ARG_STRING, &origin, - /* TRANSLATORS: ascompose flag description for: --origin */ - _("Set the origin name"), "NAME" }, - { "media-baseurl", + _("Override the icon output directory"), + "DIR" }, + + { "media-dir", + '\0', 0, + G_OPTION_ARG_FILENAME, &media_dir, + /* TRANSLATORS: ascompose flag description for: --media-dir */ + _("Set the media output directory (for media data to be served by a webserver)"), + "DIR" }, + + { "hints-dir", + '\0', 0, + G_OPTION_ARG_FILENAME, &hints_dir, + /* TRANSLATORS: ascompose flag description for: --hints-dir */ + _("Set a directory where HTML and text issue reports will be stored"), + "DIR" }, + + { "origin", + '\0', 0, + G_OPTION_ARG_STRING, &origin, + /* TRANSLATORS: ascompose flag description for: --origin */ + _("Set the origin name"), + "NAME" }, + + { "media-baseurl", '\0', 0, G_OPTION_ARG_STRING, &media_baseurl, /* TRANSLATORS: ascompose flag description for: --media-baseurl */ - _("Set the URL where the exported media content will be hosted"), - "NAME" }, - { "no-partial-urls", - '\0', 0, - G_OPTION_ARG_NONE, &no_partial_urls, - /* TRANSLATORS: ascompose flag description for: --no-partial-urls */ - _("Makes all URLs in output data complete URLs and avoids the use of a shared URL prefix for all metadata."), - NULL }, - { "components", - '\0', 0, - G_OPTION_ARG_STRING, &components_str, - /* TRANSLATORS: ascompose flag description for: --components */ - _("A comma-separated list of component-IDs to accept"), - "COMPONENT-IDs" }, - { NULL } - }; + _("Set the URL where the exported media content will be hosted"), + "NAME" }, + + { "no-partial-urls", + '\0', 0, + G_OPTION_ARG_NONE, &no_partial_urls, + /* TRANSLATORS: ascompose flag description for: --no-partial-urls */ + _("Makes all URLs in output data complete URLs and avoids the use of a shared URL prefix for all metadata."), + NULL }, + + { "components", + '\0', 0, + G_OPTION_ARG_STRING, &components_str, + /* TRANSLATORS: ascompose flag description for: --components */ + _("A comma-separated list of component-IDs to accept"), + "COMPONENT-IDs" }, + + { NULL } + }; + /* clang-format on */ setlocale (LC_ALL, ""); bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); @@ -389,7 +416,7 @@ main (int argc, char **argv) asc_compose_set_media_baseurl (compose, media_baseurl); /* we need at least one unit to process */ - if (argc == 1) { + if (argc <= 1) { g_autofree gchar *tmp = NULL; tmp = g_option_context_get_help (option_context, TRUE, NULL); g_print ("%s", tmp);