aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--brep/handler/ci/ci.bash.in3
-rw-r--r--brep/handler/submit/submit.bash.in2
-rw-r--r--libbrep/package.cxx2
-rw-r--r--libbrep/package.hxx21
-rw-r--r--libbrep/package.xml6
-rw-r--r--load/load.cli5
-rw-r--r--load/load.cxx49
-rw-r--r--manifest2
-rw-r--r--migrate/migrate.cxx8
-rw-r--r--mod/buildfile4
-rw-r--r--mod/mod-package-details.cxx16
-rw-r--r--mod/mod-package-version-details.cxx10
-rw-r--r--mod/mod-repository-root.cxx6
-rw-r--r--mod/page.cxx179
-rw-r--r--mod/page.hxx124
-rw-r--r--repositories.manifest4
-rw-r--r--tests/load/1/math/libfoo-1.2.4+1.tar.gzbin941 -> 964 bytes
-rw-r--r--tests/load/1/math/packages.manifest7
-rw-r--r--tests/load/1/misc/packages.manifest1
-rw-r--r--tests/load/1/stable/packages.manifest1
-rw-r--r--tests/load/1/stable/signature.manifest20
-rw-r--r--tests/load/driver.cxx6
-rw-r--r--web/xhtml-fragment.cxx49
-rw-r--r--web/xhtml-fragment.hxx19
-rw-r--r--www/package-details-body.css44
-rw-r--r--www/package-version-details-body.css45
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 () # <repo-url> <dir> <timeout>
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 () # <archive> <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<string> ds,
+ optional<text_type> dt,
string ch,
optional<url_type> ur,
optional<url_type> 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<text_type>;
+
+ #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<string> description,
+ optional<text_type> description_type,
string changes,
optional<url_type>,
optional<url_type> doc_url,
@@ -396,7 +412,8 @@ namespace brep
string summary;
license_alternatives_type license_alternatives;
strings tags;
- optional<string> description;
+ optional<string> description; // Absent if type is unknown.
+ optional<text_type> description_type; // Present if description is present.
string changes;
optional<url_type> url;
optional<url_type> 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 @@
<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="pgsql" schema-name="package" version="1">
+ <changeset version="12">
+ <alter-table name="package">
+ <add-column name="description_type" type="TEXT" null="true"/>
+ </alter-table>
+ </changeset>
+
<model version="11">
<table name="tenant" kind="object">
<column name="id" type="TEXT" null="false"/>
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<internal_repository>;
// 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<repository>& 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<repository>& 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<repository>& rp,
// Create internal package object.
//
optional<string> dsc;
+ optional<text_type> 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<repository>& 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<repository>& 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<repository>& rp,
static void
load_repositories (const shared_ptr<repository>& 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<repository>& 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<repository>& 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> (
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 <schema_version v>
using package_migration_entry_base =
data_migration_entry<v, LIBBREP_PACKAGE_SCHEMA_VERSION_BASE>;
@@ -220,10 +219,13 @@ struct package_migration_entry: package_migration_entry_base<v>
};
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<string>& 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<package_count> (
search_params<package_count> (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<string>& 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 <time.h> // tzset()
+#include <cmark-gfm-core-extensions.h>
+
#include <sstream>
#include <algorithm> // 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 <mod/page.hxx>
+#include <cmark-gfm.h>
+#include <cmark-gfm-extension_api.h>
+
#include <set>
#include <ios> // hex, uppercase, right
#include <sstream>
@@ -13,6 +16,7 @@
#include <libstudxml/serializer.hxx>
#include <web/xhtml.hxx>
+#include <web/xhtml-fragment.hxx>
#include <web/mime-url-encoding.hxx>
#include <libbrep/package.hxx>
@@ -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 <div>.
+ //
+ 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 <div> 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<cmark_parser, void (*)(cmark_parser*)> 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<cmark_node, void (*)(cmark_node*)> 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<char, void (*)(char*)> 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 <libbrep/build.hxx>
#include <libbrep/package.hxx>
+#include <mod/diagnostics.hxx>
#include <mod/options-types.hxx> // 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: <input type="text"/>'
+ // Generate table row element, that has the 'label: <input type="text"/>'
// layout.
//
class TR_INPUT
@@ -139,7 +144,7 @@ namespace brep
bool autofocus_;
};
- // Generates table row element, that has the 'label: <select></select>'
+ // Generate table row element, that has the 'label: <select></select>'
// layout. Option elements are represented as a list of value/inner-text
// pairs.
//
@@ -162,7 +167,7 @@ namespace brep
const vector<pair<string, string>>& 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
--- a/tests/load/1/math/libfoo-1.2.4+1.tar.gz
+++ b/tests/load/1/math/libfoo-1.2.4+1.tar.gz
Binary files 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 ("<d>" + text + "</d>");
- 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 <pre> 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 <div> block containing the result of
+ * Markdown-to-HTML translation.
+ *
+ * Note that the Markdown code blocks are translated into the
+ * <pre><code>...<code/></pre> 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 <pre> 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 <div> block containing the result of
+ * Markdown-to-HTML translation.
+ *
+ * Note that the Markdown code blocks are translated into the
+ * <pre><code>...<code/></pre> 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;
}