From 8f9a80a9ac8f353ce2cdafa23f0e5163d30d5800 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Wed, 1 May 2019 22:32:11 +0300 Subject: Add support for description-type package manifest value --- brep/handler/ci/ci.bash.in | 3 + brep/handler/submit/submit.bash.in | 2 + libbrep/package.cxx | 2 + libbrep/package.hxx | 21 +++- libbrep/package.xml | 6 ++ load/load.cli | 5 + load/load.cxx | 49 ++++++--- manifest | 2 + migrate/migrate.cxx | 8 +- mod/buildfile | 4 +- mod/mod-package-details.cxx | 16 ++- mod/mod-package-version-details.cxx | 10 +- mod/mod-repository-root.cxx | 6 ++ mod/page.cxx | 179 ++++++++++++++++++++++++++++++-- mod/page.hxx | 124 ++++++++++++++++------ repositories.manifest | 4 + tests/load/1/math/libfoo-1.2.4+1.tar.gz | Bin 941 -> 964 bytes tests/load/1/math/packages.manifest | 7 +- tests/load/1/misc/packages.manifest | 1 + tests/load/1/stable/packages.manifest | 1 + tests/load/1/stable/signature.manifest | 20 ++-- tests/load/driver.cxx | 6 +- web/xhtml-fragment.cxx | 49 +++++++-- web/xhtml-fragment.hxx | 19 ++-- www/package-details-body.css | 44 +++++++- www/package-version-details-body.css | 45 +++++++- 26 files changed, 524 insertions(+), 109 deletions(-) diff --git a/brep/handler/ci/ci.bash.in b/brep/handler/ci/ci.bash.in index b55749e..a269e6b 100644 --- a/brep/handler/ci/ci.bash.in +++ b/brep/handler/ci/ci.bash.in @@ -50,6 +50,9 @@ function dump_repository_manifests () # local dir="$2" local tmo="$3" + # Note that due to the --manifest option only known manifest values are + # allowed. + # if ! run_silent bpkg rep-info --fetch-timeout "$tmo" --manifest \ --repositories --repositories-file "$dir/repositories.manifest" \ --packages --packages-file "$dir/packages.manifest" "$url"; then diff --git a/brep/handler/submit/submit.bash.in b/brep/handler/submit/submit.bash.in index cb83384..d1d0634 100644 --- a/brep/handler/submit/submit.bash.in +++ b/brep/handler/submit/submit.bash.in @@ -50,6 +50,8 @@ function extract_package_manifest () # # Pass the --deep option to make sure that the *-file manifest values are # resolvable, so rep-create will not fail due to this package down the road. + # Note that we also make sure that all the manifest values are known (see + # bpkg-pkg-verify for details). # if ! run_silent bpkg pkg-verify --deep --manifest "$arc" >"$man"; then diff --git a/libbrep/package.cxx b/libbrep/package.cxx index ecd6592..a01ed14 100644 --- a/libbrep/package.cxx +++ b/libbrep/package.cxx @@ -58,6 +58,7 @@ namespace brep license_alternatives_type la, strings tg, optional ds, + optional dt, string ch, optional ur, optional du, @@ -86,6 +87,7 @@ namespace brep license_alternatives (move (la)), tags (move (tg)), description (move (ds)), + description_type (move (dt)), changes (move (ch)), url (move (ur)), doc_url (move (du)), diff --git a/libbrep/package.hxx b/libbrep/package.hxx index 2f293c1..2e31ff4 100644 --- a/libbrep/package.hxx +++ b/libbrep/package.hxx @@ -21,7 +21,7 @@ // #define LIBBREP_PACKAGE_SCHEMA_VERSION_BASE 11 -#pragma db model version(LIBBREP_PACKAGE_SCHEMA_VERSION_BASE, 11, closed) +#pragma db model version(LIBBREP_PACKAGE_SCHEMA_VERSION_BASE, 12, closed) namespace brep { @@ -45,6 +45,21 @@ namespace brep #pragma db value(priority) definition #pragma db member(priority::value) column("") + // text_type + // + using bpkg::text_type; + using bpkg::to_text_type; + + #pragma db map type(text_type) as(string) \ + to(to_string (?)) \ + from(brep::to_text_type (?)) + + using optional_text_type = optional; + + #pragma db map type(optional_text_type) as(brep::optional_string) \ + to((?) ? to_string (*(?)) : brep::optional_string ()) \ + from((?) ? brep::to_text_type (*(?)) : brep::optional_text_type ()) + // url // using bpkg::url; @@ -348,6 +363,7 @@ namespace brep license_alternatives_type, strings tags, optional description, + optional description_type, string changes, optional, optional doc_url, @@ -396,7 +412,8 @@ namespace brep string summary; license_alternatives_type license_alternatives; strings tags; - optional description; + optional description; // Absent if type is unknown. + optional description_type; // Present if description is present. string changes; optional url; optional doc_url; diff --git a/libbrep/package.xml b/libbrep/package.xml index 8ba96ec..fdb4f04 100644 --- a/libbrep/package.xml +++ b/libbrep/package.xml @@ -1,4 +1,10 @@ + + + + + + diff --git a/load/load.cli b/load/load.cli index 17aba5b..74c91f2 100644 --- a/load/load.cli +++ b/load/load.cli @@ -40,6 +40,11 @@ class options { "\h|OPTIONS|" + bool --ignore-unknown + { + "Ignore unknown manifest entries." + } + bool --force { "Reload package information regardless of the repository manifest file diff --git a/load/load.cxx b/load/load.cxx index d00a1af..c5fa3cd 100644 --- a/load/load.cxx +++ b/load/load.cxx @@ -89,7 +89,7 @@ using internal_repositories = vector; // Specifically, the packages.manifest is not a pkg package manifest list. It // contains a raw list of package manifests that may contain values forbidden // for the pkg package manifest list (description-file, changes-file) and may -// omit the required ones (sha256sum). +// omit the required ones (sha256sum, description-type). // // @@ Latter, we may also want to support loading bpkg repositories using // manifest files produced by bpkg-rep-info command. This, in particular, @@ -343,6 +343,7 @@ repository_info (const options& lo, const string& rl, const cstrings& options) static void load_packages (const shared_ptr& rp, database& db, + bool ignore_unknown, const manifest_name_values& overrides) { // packages_timestamp other than timestamp_nonexistent signals the @@ -376,18 +377,17 @@ load_packages (const shared_ptr& rp, // We put no restrictions on the manifest values presence since it's not // critical for displaying and building if the packages omit some // manifest values (see libbpkg/manifest.hxx for details). Note, though, - // that we expect package dependency constraints to be complete. + // that we expect dependency constraints to be complete. // for (manifest_name_value nv (mp.next ()); !nv.empty (); nv = mp.next ()) - pms.emplace_back ( - mp, - move (nv), - false /* ignore_unknown */, - false /* complete_depends */, - package_manifest_flags::forbid_incomplete_depends); + pms.emplace_back (mp, + move (nv), + ignore_unknown, + false /* complete_depends */, + package_manifest_flags::forbid_incomplete_depends); } else - pms = pkg_package_manifests (mp); + pms = pkg_package_manifests (mp, ignore_unknown); } catch (const io_error& e) { @@ -423,6 +423,8 @@ load_packages (const shared_ptr& rp, // Create internal package object. // optional dsc; + optional dst; + if (pm.description) { // The description value should not be of the file type if the @@ -431,7 +433,18 @@ load_packages (const shared_ptr& rp, assert (!pm.description->file || cl.type () != repository_type::pkg); if (!pm.description->file) - dsc = move (pm.description->text); + { + dst = pm.effective_description_type (ignore_unknown); + + // If the description type is unknown (which may be the case for + // some "transitional" period and only if --ignore-unknown is + // specified) we just silently drop the description. + // + assert (dst || ignore_unknown); + + if (dst) + dsc = move (pm.description->text); + } } string chn; @@ -495,6 +508,7 @@ load_packages (const shared_ptr& rp, move (pm.license_alternatives), move (pm.tags), move (dsc), + move (dst), move (chn), move (pm.url), move (pm.doc_url), @@ -563,6 +577,7 @@ load_packages (const shared_ptr& rp, static void load_repositories (const shared_ptr& rp, database& db, + bool ignore_unknown, bool shallow) { // repositories_timestamp other than timestamp_nonexistent signals that @@ -592,7 +607,7 @@ load_repositories (const shared_ptr& rp, rp->repositories_timestamp = file_mtime (p); manifest_parser mp (ifs, p.string ()); - rpm = pkg_repository_manifests (mp); + rpm = pkg_repository_manifests (mp, ignore_unknown); } catch (const io_error& e) { @@ -744,8 +759,12 @@ load_repositories (const shared_ptr& rp, // We don't apply overrides to the external packages. // - load_packages (pr, db, manifest_name_values () /* overrides */); - load_repositories (pr, db, false /* shallow */); + load_packages (pr, + db, + ignore_unknown, + manifest_name_values () /* overrides */); + + load_repositories (pr, db, ignore_unknown, false /* shallow */); } db.update (rp); @@ -1248,7 +1267,7 @@ try move (cert), priority++)); - load_packages (r, db, overrides); + load_packages (r, db, ops.ignore_unknown (), overrides); } // On the second pass over the internal repositories we load their @@ -1261,7 +1280,7 @@ try db.load ( repository_id (tnt, ir.location.canonical_name ()))); - load_repositories (r, db, ops.shallow ()); + load_repositories (r, db, ops.ignore_unknown (), ops.shallow ()); } // Resolve internal packages dependencies unless this is a shallow load. diff --git a/manifest b/manifest index 51807f8..8f83c10 100644 --- a/manifest +++ b/manifest @@ -23,6 +23,8 @@ depends: * bpkg >= 0.10.0 requires: ? cli ; Only required if changing .cli files. depends: libapr1 depends: libapreq2 +depends: libcmark-gfm [0.29.0-a.0.1 0.29.0-a.1] +depends: libcmark-gfm-extensions [0.29.0-a.0.1 0.29.0-a.1] depends: libstudxml ^1.1.0-b.6 depends: libodb [2.5.0-b.14.1 2.5.0-b.15) depends: libodb-pgsql [2.5.0-b.14.1 2.5.0-b.15) diff --git a/migrate/migrate.cxx b/migrate/migrate.cxx index feec1ea..6bb0d48 100644 --- a/migrate/migrate.cxx +++ b/migrate/migrate.cxx @@ -207,7 +207,6 @@ create (database& db, bool extra_only) const // Register the data migration functions for the package database schema. // -#if 0 template using package_migration_entry_base = data_migration_entry; @@ -220,10 +219,13 @@ struct package_migration_entry: package_migration_entry_base }; static const package_migration_entry<12> -package_migrate_v12 ([] (database&) +package_migrate_v12 ([] (database& db) { + // Set the text_type::plain type for the present package descriptions. + // + db.execute ("UPDATE package SET description_type = 'text/plain' " + "WHERE description IS NOT NULL"); }); -#endif // main() function // diff --git a/mod/buildfile b/mod/buildfile index bd68bd0..ffa9031 100644 --- a/mod/buildfile +++ b/mod/buildfile @@ -11,7 +11,9 @@ mod{*}: install = libexec/ } -import libs = libodb%lib{odb} +import libs = libcmark-gfm%lib{cmark-gfm} +import libs += libcmark-gfm-extensions%lib{cmark-gfm-extensions} +import libs += libodb%lib{odb} import libs += libodb-pgsql%lib{odb-pgsql} import libs += libbutl%lib{butl} import libs += libbpkg%lib{bpkg} diff --git a/mod/mod-package-details.cxx b/mod/mod-package-details.cxx index 28370c2..fa073b5 100644 --- a/mod/mod-package-details.cxx +++ b/mod/mod-package-details.cxx @@ -186,13 +186,17 @@ handle (request& rq, response& rs) if (const optional& d = pkg->description) { const string id ("description"); + const string what (name.string () + " description"); s << (full - ? PRE_TEXT (*d, id) - : PRE_TEXT (*d, + ? DIV_TEXT (*d, *pkg->description_type, id, what, error) + : DIV_TEXT (*d, + *pkg->description_type, options_->package_description (), url (!full, squery, page, id), - id)); + id, + what, + error)); } s << TABLE(CLASS="proplist", ID="package") @@ -221,7 +225,11 @@ handle (request& rq, response& rs) package_db_->query_value ( search_params (squery, tenant, name))); - s << FORM_SEARCH (squery, "q") + // Let's disable autofocus in the full page mode since clicking the full or + // more link the user most likely intends to read rather than search, while + // autofocus scrolls the page to the search field. + // + s << FORM_SEARCH (squery, "q", !full) << DIV_COUNTER (pkg_count, "Version", "Versions"); // Enclose the subsequent tables to be able to use nth-child CSS selector. diff --git a/mod/mod-package-version-details.cxx b/mod/mod-package-version-details.cxx index 4e09c80..82221b4 100644 --- a/mod/mod-package-version-details.cxx +++ b/mod/mod-package-version-details.cxx @@ -184,13 +184,17 @@ handle (request& rq, response& rs) if (const optional& d = pkg->description) { const string id ("description"); + const string what (title + " description"); s << (full - ? PRE_TEXT (*d, id) - : PRE_TEXT (*d, + ? DIV_TEXT (*d, *pkg->description_type, id, what, error) + : DIV_TEXT (*d, + *pkg->description_type, options_->package_description (), url (!full, id), - id)); + id, + what, + error)); } const repository_location& rl (pkg->internal_repository.load ()->location); diff --git a/mod/mod-repository-root.cxx b/mod/mod-repository-root.cxx index fc13aca..ed170c9 100644 --- a/mod/mod-repository-root.cxx +++ b/mod/mod-repository-root.cxx @@ -6,6 +6,8 @@ #include // tzset() +#include + #include #include // find() @@ -285,6 +287,10 @@ namespace brep // sub-handlers, while handling requests). // tzset (); + + // To recognize cmark-gfm extensions while parsing Markdown later on. + // + cmark_gfm_core_extensions_ensure_registered (); } bool repository_root:: diff --git a/mod/page.cxx b/mod/page.cxx index f780b95..63bb495 100644 --- a/mod/page.cxx +++ b/mod/page.cxx @@ -4,6 +4,9 @@ #include +#include +#include + #include #include // hex, uppercase, right #include @@ -13,6 +16,7 @@ #include #include +#include #include #include @@ -98,8 +102,12 @@ namespace brep << TBODY << TR << TD(ID="search-txt") - << *INPUT(TYPE="search", NAME=name_, VALUE=query_, - AUTOFOCUS="") + << INPUT(TYPE="search", NAME=name_, VALUE=query_); + + if (autofocus_) + s << AUTOFOCUS(""); + + s << ~INPUT << ~TD << TD(ID="search-btn") << *INPUT(TYPE="submit", VALUE="Search") @@ -805,34 +813,183 @@ namespace brep // PRE_TEXT // - void PRE_TEXT:: - operator() (serializer& s) const + static void + serialize_pre_text (serializer& s, + const string& text, + size_t length, + const string* url, + const string& id) { - if (text_.empty ()) + if (text.empty ()) return; - size_t n (text_.find_first_of (" \t\n", length_)); + size_t n (text.find_first_of (" \t\n", length)); bool full (n == string::npos); // Text length is below the limit. // Truncate the text if length exceeds the limit. // - const string& t (full ? text_ : string (text_, 0, n)); + const string& t (full ? text : string (text, 0, n)); s << PRE; - if (!id_.empty ()) - s << ID(id_); + if (!id.empty ()) + s << ID(id); s << t; if (!full) { - assert (url_ != nullptr); - s << "... " << A(HREF=*url_) << "More" << ~A; + assert (url != nullptr); + s << "... " << A(HREF=*url) << "More" << ~A; } s << ~PRE; } + void PRE_TEXT:: + operator() (serializer& s) const + { + serialize_pre_text (s, text_, length_, url_, id_); + } + + // DIV_TEXT + // + void DIV_TEXT:: + operator() (serializer& s) const + { + switch (type_) + { + case text_type::plain: + { + // To keep things regular we wrap the preformatted text into
. + // + s << DIV(ID=id_, CLASS="plain"); + serialize_pre_text (s, text_, length_, url_, "" /* id */); + s << ~DIV; + break; + } + case text_type::common_mark: + case text_type::github_mark: + { + // Convert Markdown into XHTML wrapping it into the
element. + // + auto print_error = [&s, this] (const string& e) + { + s << DIV(ID=id_, CLASS="markdown") + << SPAN(CLASS="error") << e << ~SPAN + << ~DIV; + }; + + // Note that the only possible reason for the following cmark API + // calls to fail is the inability to allocate memory. Unfortunately, + // instead of reporting the failure to the caller, the API issues + // diagnostics to stderr and aborts the process. Let's decrease the + // probability of such an event by limiting the text size to 64K. + // + if (text_.size () > 64 * 1024) + { + print_error (what_ + " is too long"); + return; + } + + string html; + { + char* r; + { + // Parse Markdown into the AST. + // + unique_ptr parser ( + cmark_parser_new (CMARK_OPT_DEFAULT | CMARK_OPT_VALIDATE_UTF8), + [] (cmark_parser* p) {cmark_parser_free (p);}); + + // Enable GitHub extensions in the parser, if requested. + // + if (type_ == text_type::github_mark) + { + auto add = [&parser] (const char* ext) + { + cmark_syntax_extension* e ( + cmark_find_syntax_extension (ext)); + + // Built-in extension is only expected. + // + assert (e != nullptr); + + cmark_parser_attach_syntax_extension (parser.get (), e); + }; + + add ("table"); + add ("strikethrough"); + add ("autolink"); + + // Somehow feels unsafe (there are some nasty warnings when + // upstream's tasklist.c is compiled), so let's disable for now. + // + // add ("tasklist"); + } + + cmark_parser_feed (parser.get (), text_.c_str (), text_.size ()); + + unique_ptr doc ( + cmark_parser_finish (parser.get ()), + [] (cmark_node* n) {cmark_node_free (n);}); + + // Render the AST into an XHTML fragment. + // + // Note that unlike GitHub we follow the default API behavior and + // don't allow the raw HTML in Markdown (omitting the + // CMARK_OPT_UNSAFE flag). This way we can assume the rendered + // HTML is a well-formed XHTML fragment, which we rely upon for + // truncation (see below). Note that by default the renderer + // suppresses any HTML-alike markup and unsafe URLs (javascript:, + // etc). + // + r = cmark_render_html (doc.get (), + CMARK_OPT_DEFAULT, + nullptr /* extensions */); + } + + unique_ptr deleter ( + r, + [] (char* s) {cmark_get_default_mem_allocator ()->free (s);}); + + html = r; + } + + // From the CommonMark Spec it follows that the resulting HTML can be + // assumed a well-formed XHTML fragment with all the elements having + // closing tags. But let's not assume this being the case (due to some + // library bug or similar) and handle the xml::parsing exception. + // + try + { + fragment f (html, "gfm-html", url_ == nullptr ? 0 : length_); + + s << DIV(ID=id_, CLASS="markdown"); + + // Disable indentation not to introduce unwanted spaces. + // + s.suspend_indentation (); + s << f; + s.resume_indentation (); + + if (f.truncated) + s << DIV(CLASS="more") << A(HREF=*url_) << "More" << ~A << ~DIV; + + s << ~DIV; + } + catch (const xml::parsing& e) + { + string error ("unable to parse " + what_ + " XHTML fragment: " + + e.what ()); + diag_ << error; + print_error (error); + } + + break; + } + } + } + // DIV_PAGER // DIV_PAGER:: diff --git a/mod/page.hxx b/mod/page.hxx index b5c62c1..1138a80 100644 --- a/mod/page.hxx +++ b/mod/page.hxx @@ -17,6 +17,7 @@ #include #include +#include #include // page_menu namespace brep @@ -24,7 +25,7 @@ namespace brep // Page common building blocks. // - // Generates CSS link elements. + // Generate CSS link elements. // class CSS_LINKS { @@ -39,7 +40,7 @@ namespace brep const dir_path& root_; }; - // Generates page header element. + // Generate page header element. // class DIV_HEADER { @@ -60,13 +61,16 @@ namespace brep const string& tenant_; }; - // Generates package search form element with the specified query input + // Generate package search form element with the specified query input // element name. // class FORM_SEARCH { public: - FORM_SEARCH (const string& q, const string& n): query_ (q), name_ (n) {} + FORM_SEARCH (const string& q, const string& n, bool a = true) + : query_ (q), name_ (n), autofocus_ (a) + { + } void operator() (xml::serializer&) const; @@ -74,9 +78,10 @@ namespace brep private: const string& query_; const string& name_; + bool autofocus_; }; - // Generates counter element. + // Generate counter element. // // It could be redunant to distinguish between singular and plural word forms // if it wouldn't be so cheap in English, and phrase '1 Packages' wouldn't @@ -97,7 +102,7 @@ namespace brep const char* plural_; }; - // Generates table row element, that has the 'label: value' layout. + // Generate table row element, that has the 'label: value' layout. // class TR_VALUE { @@ -113,7 +118,7 @@ namespace brep const string& value_; }; - // Generates table row element, that has the 'label: ' + // Generate table row element, that has the 'label: ' // layout. // class TR_INPUT @@ -139,7 +144,7 @@ namespace brep bool autofocus_; }; - // Generates table row element, that has the 'label: ' + // Generate table row element, that has the 'label: ' // layout. Option elements are represented as a list of value/inner-text // pairs. // @@ -162,7 +167,7 @@ namespace brep const vector>& options_; }; - // Generates tenant id element. + // Generate tenant id element. // // Displays a link to the service page for the specified tenant. // @@ -185,7 +190,7 @@ namespace brep const string& tenant_; }; - // Generates package name element with an optional search criteria. The + // Generate package name element with an optional search criteria. The // search string should be url-encoded, if specified. // class TR_NAME @@ -207,7 +212,7 @@ namespace brep const string& tenant_; }; - // Generates package version element. + // Generate package version element. // class TR_VERSION { @@ -248,7 +253,7 @@ namespace brep const string* tenant_; }; - // Generates package project name element. + // Generate package project name element. // // Displays a link to the package search page with the project name // specified as a keyword. @@ -268,7 +273,7 @@ namespace brep const string& tenant_; }; - // Generates package summary element. + // Generate package summary element. // class TR_SUMMARY { @@ -282,7 +287,7 @@ namespace brep const string& summary_; }; - // Generates package license alternatives element. + // Generate package license alternatives element. // class TR_LICENSE { @@ -296,7 +301,7 @@ namespace brep const license_alternatives& licenses_; }; - // Generates package license alternatives elements. Differs from TR_LICENSE + // Generate package license alternatives elements. Differs from TR_LICENSE // by producing multiple rows instead of a single one. // class TR_LICENSES @@ -311,7 +316,7 @@ namespace brep const license_alternatives& licenses_; }; - // Generates package tags element. + // Generate package tags element. // class TR_TAGS { @@ -340,7 +345,7 @@ namespace brep const string& tenant_; }; - // Generates package dependencies element. + // Generate package dependencies element. // class TR_DEPENDS { @@ -357,7 +362,7 @@ namespace brep const string& tenant_; }; - // Generates package requirements element. + // Generate package requirements element. // class TR_REQUIRES { @@ -371,7 +376,7 @@ namespace brep const requirements& requirements_; }; - // Generates url element. + // Generate url element. // class TR_URL { @@ -386,7 +391,7 @@ namespace brep const char* label_; }; - // Generates email element. + // Generate email element. // class TR_EMAIL { @@ -402,7 +407,7 @@ namespace brep const char* label_; }; - // Generates package version priority element. + // Generate package version priority element. // class TR_PRIORITY { @@ -416,7 +421,7 @@ namespace brep const priority& priority_; }; - // Generates repository name element. + // Generate repository name element. // class TR_REPOSITORY { @@ -433,7 +438,7 @@ namespace brep const string& tenant_; }; - // Generates repository location element. + // Generate repository location element. // class TR_LOCATION { @@ -447,7 +452,7 @@ namespace brep const repository_location& location_; }; - // Generates package download URL element. + // Generate package download URL element. // class TR_DOWNLOAD { @@ -461,7 +466,7 @@ namespace brep const string& url_; }; - // Generates sha256sum element. + // Generate sha256sum element. // class TR_SHA256SUM { @@ -475,7 +480,7 @@ namespace brep const string& sha256sum_; }; - // Generates build results element. + // Generate build results element. // class TR_BUILD_RESULT { @@ -492,7 +497,7 @@ namespace brep const dir_path& root_; }; - // Generates comment element. + // Generate comment element. // class SPAN_COMMENT { @@ -506,7 +511,7 @@ namespace brep const string& comment_; }; - // Generates package build result status element. + // Generate package build result status element. // class SPAN_BUILD_RESULT_STATUS { @@ -520,7 +525,7 @@ namespace brep const bbot::result_status& status_; }; - // Generates paragraph elements converting a plain text into XHTML5 applying + // Generate paragraph elements converting a plain text into XHTML5 applying // some heuristics (see implementation for details). Truncate the text if // requested. // @@ -551,7 +556,7 @@ namespace brep string id_; }; - // Generates pre-formatted text element. Truncate the text if requested. + // Generate pre-formatted text element. Truncate the text if requested. // class PRE_TEXT { @@ -579,7 +584,64 @@ namespace brep string id_; }; - // Generates paging element. + // Generate a typed text element truncating it if requested. On the + // underlying parsing/rendering error, log it and generate the error + // description element instead. Note that such an error indicates an issue + // with the implementation, rather than with the specified text. + // + class DIV_TEXT + { + public: + // Generate a full text element. + // + DIV_TEXT (const string& t, + text_type tp, + const string& id, + const string& what, + const basic_mark& diag) + : text_ (t), + type_ (tp), + length_ (t.size ()), + url_ (nullptr), + id_ (id), + what_ (what), + diag_ (diag) + { + } + + // Generate a brief text element. + // + DIV_TEXT (const string& t, + text_type tp, + size_t l, + const string& u, + const string& id, + const string& what, + const basic_mark& diag) + : text_ (t), + type_ (tp), + length_ (l), + url_ (&u), + id_ (id), + what_ (what), + diag_ (diag) + { + } + + void + operator() (xml::serializer&) const; + + private: + const string& text_; + text_type type_; + size_t length_; + const string* url_; // Full page url. + string id_; + const string& what_; + const basic_mark& diag_; + }; + + // Generate paging element. // class DIV_PAGER { diff --git a/repositories.manifest b/repositories.manifest index 300bd3c..f6ba123 100644 --- a/repositories.manifest +++ b/repositories.manifest @@ -27,6 +27,10 @@ location: https://git.build2.org/packaging/libapreq/libapreq2.git##HEAD : role: prerequisite +location: https://git.build2.org/packaging/cmark-gfm/cmark-gfm.git##HEAD + +: +role: prerequisite location: https://git.codesynthesis.com/odb/libodb.git##HEAD : diff --git a/tests/load/1/math/libfoo-1.2.4+1.tar.gz b/tests/load/1/math/libfoo-1.2.4+1.tar.gz index 7c6875b..29b0c55 100644 Binary files a/tests/load/1/math/libfoo-1.2.4+1.tar.gz and b/tests/load/1/math/libfoo-1.2.4+1.tar.gz differ diff --git a/tests/load/1/math/packages.manifest b/tests/load/1/math/packages.manifest index d1c972d..1bb1c57 100644 --- a/tests/load/1/math/packages.manifest +++ b/tests/load/1/math/packages.manifest @@ -8,6 +8,7 @@ summary: The exponent license: MIT tags: mathlab, c++, exponent description: The exponent math function. +description-type: text/plain url: http://exp.example.com email: users@exp.example.com build-email: builds@exp.example.com @@ -50,10 +51,11 @@ A modern C++ library with easy to use linear algebra and lot of optimization tools. There are over 100 functions in total with an extensive test suite. The API is -similar to MATLAB. +similar to ~~mathlab~~ **MATLAB**. Useful for conversion of research code into production environments. \ +description-type: text/markdown changes: \ 1.2.4+1 * applied patch for critical bug-219 @@ -76,7 +78,7 @@ requires: c++11 requires: ? ; libc++ standard library if using Clang on Mac OS X. requires: ? vc++ >= 12.0; Only if using VC++ on Windows. location: libfoo-1.2.4+1.tar.gz -sha256sum: c5e593d8efdc34a258f8c0b8cc352dc7193ea4a1d666bcf8d48708c7dd82d0d6 +sha256sum: 92eb89770be390cbac9e0113763e0c10c43a4530667f5572571895617368369a : name: libpq version: 0 @@ -105,6 +107,7 @@ The packaging of PostgreSQL for build2 is tracked in a Git repository at: https://git.build2.org/cgit/packaging/postgresql/ \ +description-type: text/plain url: https://www.postgresql.org/ package-url: https://git.build2.org/cgit/packaging/postgresql/ email: pgsql-general@postgresql.org; Mailing list. diff --git a/tests/load/1/misc/packages.manifest b/tests/load/1/misc/packages.manifest index bf6efca..1f1571b 100644 --- a/tests/load/1/misc/packages.manifest +++ b/tests/load/1/misc/packages.manifest @@ -6,6 +6,7 @@ version: 2.4.0+3 priority: security; Very important to install. summary: The Bar library description: very very good library. +description-type: text/plain license: GPLv2 tags: c++, bar url: http://www.example.com/bar/ diff --git a/tests/load/1/stable/packages.manifest b/tests/load/1/stable/packages.manifest index 4c83ed5..9b9b242 100644 --- a/tests/load/1/stable/packages.manifest +++ b/tests/load/1/stable/packages.manifest @@ -54,6 +54,7 @@ summary: The Foo Library license: MIT; Permissive free software license. tags: c++, foo description: Very good foo library. +description-type: text/plain changes: some changes 1 changes: some changes 2 url: http://www.example.com/foo/ diff --git a/tests/load/1/stable/signature.manifest b/tests/load/1/stable/signature.manifest index decdf5b..861f9ea 100644 --- a/tests/load/1/stable/signature.manifest +++ b/tests/load/1/stable/signature.manifest @@ -1,13 +1,13 @@ : 1 -sha256sum: f48f71ccb83024007dedc63148d4682e7b2a478a3ca78583e3562ea4bcf396a5 +sha256sum: 31af43bc14d0fd99c623d3801a982a195f78690f1d3fd6fff24b26af774c2ebd signature: \ -K+F80RQE97ZmEVAl2wQwyYaw/SzviqfuR7FBTdF3PtnIorCxeokwvDL0ip7PiEJRp7QF5V0T69F+ -35MqTrRm/cLdKsxk1vsG8KWMuoK3LSXj+jcPkxKGmVoI09MvPxIQNgHKe/gk1f7uBExnpeOdfHyV -2zP6GMOhZIY4SScNeFr5lo/4bpEEtLNH1TbiYHaaGlBQDmzGzY+kdCrzZ/nWEBaWIHdT8GcqQF1D -P+KWYFoOoT5DnZtfk3ohTI1YHdDQWyWq8vFqkfpRDwxqtHltGeNLjmlwxF6gPJq9H6UZMZfEHGvs -qAScoiA5ozdPt1UXiYtPXpDoweHKcoVPHyqMxcciMTx8BYXDqY8V9EKwd/F3rD0ITzXW3eaRNygo -Jh6w6tjhZjFQ+NJ9GgsxE7NNU1dYhXuzKyBwus0GkGHriGTU7L0t2MoY/1GXihexwz3fHAmkOYxe -Iz4Xt+SZeRSLa5VFFjyEOtbtvbVv8R2WngG4xwNH0b3nMJ8Baa87rajSIfIoXJaeNFWhpEBwCToz -59aJXl76+0kQL40+wSO7+/o8LnLS2sW6+ooMjM8RLRBLTzNqSsVH5kA+W0qF8e0j7AJ0qcqU5HKZ -MzhB7jPyQ82LBq/eU29bgM9hLkOiJpqwplmiRyby98jBz4ppnIytfQeD52PtDCf4yWT9p3PsKmg= +nNA9y8ce0C509b5QrMclLYQv4x9AI+WRvV0KPY6b0F1UsLUbUOgDZpF7FkwTMcqRSxHgZfn8TDi4 +VyXMJwoQZLhDNXkb+uRCiUyJr6OO/pSzzfqK2ZWYgSD3WjhHBzzqTt6xWx/hac+8D/r2h0S5khaJ +eVFA4kv7yuk20CiCkjoApGsrSICLQVXBNYecaUhdZ4+8GoHZbYZDQs+a2H+7nfxqzkMlJWC08w+7 +XZc03M6ROogspfNOjGN5k5K4WVJgrWJTl7GLzlqkwSQ2sTkr9djmZaVLwHD2qOuZNCbxaqdEs6Ju +HopyEOjgQohL3YHf09sAvU11i9bOxgWMNQNLfJ8GwTJkt5Vhn5710sNA4fjBiU0+y6eqdFbH3BDt +GhtQ+ghkhQAqVjwlzNgf2VlSlk1hK9FnryhdYPCxsmf+MNYNrdFtPFZXNORix6st8IbmtbHh2Aml +VAFpt2yHiZ0uS8uMhU6QI52WILPAnrIYCVNwqz2tGbMPJ3MKF3rnvX66zwC+2cZ5fXn/X9e4oIA1 +7cdMmph0xKig+gWU2/+LbMMIPGGZJWt53uS5JFTcTR0It7FYq02f/EefbzPH9X4QhSeeZD8sDcnu +hZaH/As7FoIA5YLpgb4VR9hJ+pNjZyyhS0ylPuhJSrK73o3RrSEoukjmT7ojuWFd732AQIM+bc4= \ diff --git a/tests/load/driver.cxx b/tests/load/driver.cxx index 82a1447..27be0da 100644 --- a/tests/load/driver.cxx +++ b/tests/load/driver.cxx @@ -683,8 +683,8 @@ test_pkg_repos (const cstrings& loader_args, "A modern C++ library with easy to use linear algebra and lot " "of optimization\ntools.\n\nThere are over 100 functions in " "total with an extensive test suite. The API is\nsimilar to " - "MATLAB.\n\nUseful for conversion of research code into " - "production environments."); + "~~mathlab~~ **MATLAB**.\n\nUseful for conversion of research " + "code into production environments."); assert (fpv5->url && *fpv5->url == "http://www.example.com/foo/"); @@ -782,7 +782,7 @@ test_pkg_repos (const cstrings& loader_args, assert (check_location (fpv5)); assert (fpv5->sha256sum && *fpv5->sha256sum == - "c5e593d8efdc34a258f8c0b8cc352dc7193ea4a1d666bcf8d48708c7dd82d0d6"); + "92eb89770be390cbac9e0113763e0c10c43a4530667f5572571895617368369a"); // Verify libexp package version. // diff --git a/web/xhtml-fragment.cxx b/web/xhtml-fragment.cxx index 4d1c178..fe8a0a7 100644 --- a/web/xhtml-fragment.cxx +++ b/web/xhtml-fragment.cxx @@ -20,25 +20,38 @@ namespace web namespace xhtml { fragment:: - fragment (const string& text, const string& name) + fragment (const string& text, const string& name, size_t length) { // To parse the fragment make it a valid xml document, wrapping with the - // root element. + // root element. If requested, truncate the fragment before the + // first-level element when the content length limit is exceeded. // string doc ("" + text + ""); - parser p ( - doc.c_str (), - doc.size (), - name, - parser::receive_elements | parser::receive_characters | - parser::receive_attributes_event); + parser p (doc.c_str (), + doc.size (), + name, + parser::receive_elements | + parser::receive_characters | + parser::receive_attributes_event); + + size_t len (0); + size_t level (0); for (parser::event_type e: p) { switch (e) { case parser::start_element: + { + truncated = length != 0 && level == 1 && len >= length; + + if (truncated) + break; + + ++level; + } + // Fall through. case parser::start_attribute: { const auto& n (p.qname ()); @@ -51,6 +64,10 @@ namespace web break; } case parser::end_element: + { + --level; + } + // Fall through. case parser::end_attribute: { events_.emplace_back (e, ""); @@ -58,12 +75,25 @@ namespace web } case parser::characters: { - events_.emplace_back (e, p.value ()); + string& s (p.value ()); + + assert (!events_.empty ()); // Contains root element start. + + if (events_.back ().first != parser::start_attribute) + len += s.size (); + + events_.emplace_back (e, move (s)); break; } default: assert (false); } + + if (truncated) + { + events_.emplace_back (parser::end_element, ""); // Close root. + break; + } } // Unwrap the fragment removing the root element events. @@ -85,7 +115,6 @@ namespace web s.start_element (xmlns, e.second); break; } - case parser::start_attribute: { s.start_attribute (e.second); diff --git a/web/xhtml-fragment.hxx b/web/xhtml-fragment.hxx index 14b9a21..fd41967 100644 --- a/web/xhtml-fragment.hxx +++ b/web/xhtml-fragment.hxx @@ -22,16 +22,21 @@ namespace web class fragment { public: + bool truncated = false; + + public: fragment () = default; - // Parse string as XHTML document fragment. The fragment should be - // complete, in the sense that all elements should have closing tags. - // Elements and attributes are considered to be in the namespace of the - // entire XHTML document, so no namespace should be specified for them. - // Do not validate against XHTML vocabulary. Can throw xml::parsing - // exception. + // Parse string as an XHTML document fragment, truncating it if + // requested. The fragment should be complete, in the sense that all + // elements should have closing tags. Elements and attributes are + // considered to be in the namespace of the entire XHTML document, so no + // namespace should be specified for them. Do not validate against XHTML + // vocabulary. Can throw xml::parsing exception. // - fragment (const std::string& xhtml, const std::string& input_name); + fragment (const std::string& xhtml, + const std::string& input_name, + size_t length = 0); void operator() (xml::serializer&) const; diff --git a/www/package-details-body.css b/www/package-details-body.css index af443c0..a69a939 100644 --- a/www/package-details-body.css +++ b/www/package-details-body.css @@ -59,18 +59,56 @@ h1, h2 } /* - * Description. + * Description (plain text). * * This is a
 block that fits lines up to 80 characters long and
  * wraps longer ones.
  */
-#description
+#description.plain pre
 {
   font-size: 0.85em;
-  white-space: pre-wrap;
 }
 
 /*
+ * Description (Markdown).
+ *
+ * These are descendants of the 
block containing the result of + * Markdown-to-HTML translation. + * + * Note that the Markdown code blocks are translated into the + *
...
element construct. + */ +#description.markdown h1, +#description.markdown h2 +{ + white-space: normal; +} + +/* code-box.css */ +#description.markdown :not(pre) > code +{ + background-color: rgba(0, 0, 0, 0.05); + border-radius: 0.2em; + padding: .2em .32em .18em .32em; +} + +/* pre-box.css */ +#description.markdown pre +{ + background-color: rgba(0, 0, 0, 0.05); + border-radius: 0.2em; + padding: .8em .4em .8em .4em; + margin: 2em -.4em 2em -.4em; /* Use paddings of #content. */ +} + +#description.markdown pre > code +{ + font-size: inherit; +} + +#description.markdown .error {color: #ff0000;} + +/* * Package details table. */ #package diff --git a/www/package-version-details-body.css b/www/package-version-details-body.css index c2821f6..9e88432 100644 --- a/www/package-version-details-body.css +++ b/www/package-version-details-body.css @@ -73,18 +73,56 @@ h1, h2, h3 } /* - * Description. + * Description (plain text). * * This is a
 block that fits lines up to 80 characters long and
  * wraps longer ones.
  */
-#description
+#description.plain pre
 {
   font-size: 0.85em;
-  white-space: pre-wrap;
 }
 
 /*
+ * Description (Markdown).
+ *
+ * These are descendants of the 
block containing the result of + * Markdown-to-HTML translation. + * + * Note that the Markdown code blocks are translated into the + *
...
element construct. + */ +#description.markdown h1, +#description.markdown h2 +{ + white-space: normal; +} + +/* code-box.css */ +#description.markdown :not(pre) > code +{ + background-color: rgba(0, 0, 0, 0.05); + border-radius: 0.2em; + padding: .2em .32em .18em .32em; +} + +/* pre-box.css */ +#description.markdown pre +{ + background-color: rgba(0, 0, 0, 0.05); + border-radius: 0.2em; + padding: .8em .4em .8em .4em; + margin: 2em -.4em 2em -.4em; /* Use paddings of #content. */ +} + +#description.markdown pre > code +{ + font-size: inherit; +} + +#description.markdown .error {color: #ff0000;} + +/* * Version details table. */ #version @@ -218,6 +256,5 @@ h1, h2, h3 #changes { font-size: 0.85em; - white-space: pre-wrap; margin: .5em 0 .5em 0; } -- cgit v1.1