aboutsummaryrefslogtreecommitdiff
path: root/mod
diff options
context:
space:
mode:
Diffstat (limited to 'mod')
-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
6 files changed, 289 insertions, 50 deletions
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
{