aboutsummaryrefslogtreecommitdiff
path: root/mod/page.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'mod/page.cxx')
-rw-r--r--mod/page.cxx314
1 files changed, 198 insertions, 116 deletions
diff --git a/mod/page.cxx b/mod/page.cxx
index e34e568..bc2e42d 100644
--- a/mod/page.cxx
+++ b/mod/page.cxx
@@ -1,5 +1,4 @@
// file : mod/page.cxx -*- C++ -*-
-// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
// license : MIT; see accompanying LICENSE file
#include <mod/page.hxx>
@@ -8,18 +7,17 @@
#include <cmark-gfm-extension_api.h>
#include <set>
-#include <ios> // hex, uppercase, right
+#include <ios> // hex, uppercase, right
#include <sstream>
-#include <iomanip> // setw(), setfill()
-#include <algorithm> // min(), find()
+#include <iomanip> // setw(), setfill()
+#include <iterator> // back_inserter()
#include <libstudxml/serializer.hxx>
-#include <libbutl/url.mxx>
+#include <web/xhtml/fragment.hxx>
+#include <web/xhtml/serialization.hxx>
-#include <web/xhtml.hxx>
-#include <web/xhtml-fragment.hxx>
-#include <web/mime-url-encoding.hxx>
+#include <web/server/mime-url-encoding.hxx>
#include <libbrep/package.hxx>
#include <libbrep/package-odb.hxx>
@@ -38,6 +36,20 @@ using namespace web::xhtml;
//
namespace brep
{
+ static inline string
+ label_to_class (const string& label)
+ {
+ if (label.find (' ') == string::npos)
+ return label;
+
+ string r;
+ transform (label.begin (), label.end (),
+ back_inserter (r),
+ [] (char c) {return c != ' ' ? c : '-';});
+
+ return r;
+ }
+
// CSS_LINKS
//
static const dir_path css_path ("@");
@@ -125,9 +137,17 @@ namespace brep
void DIV_COUNTER::
operator() (serializer& s) const
{
- s << DIV(ID="count")
- << count_ << " "
- << (count_ % 10 == 1 && count_ % 100 != 11 ? singular_ : plural_)
+ s << DIV(ID="count");
+
+ if (count_)
+ s << *count_;
+ else
+ s << '?';
+
+ s << ' '
+ << (count_ && *count_ % 10 == 1 && *count_ % 100 != 11
+ ? singular_
+ : plural_)
<< ~DIV;
}
@@ -136,7 +156,8 @@ namespace brep
void TR_VALUE::
operator() (serializer& s) const
{
- s << TR(CLASS=label_)
+ string c (label_to_class (label_));
+ s << TR(CLASS=c)
<< TH << label_ << ~TH
<< TD << SPAN(CLASS="value") << value_ << ~SPAN << ~TD
<< ~TR;
@@ -147,7 +168,8 @@ namespace brep
void TR_INPUT::
operator() (serializer& s) const
{
- s << TR(CLASS=label_)
+ string c (label_to_class (label_));
+ s << TR(CLASS=c)
<< TH << label_ << ~TH
<< TD
<< INPUT(TYPE="text", NAME=name_);
@@ -171,7 +193,8 @@ namespace brep
void TR_SELECT::
operator() (serializer& s) const
{
- s << TR(CLASS=label_)
+ string c (label_to_class (label_));
+ s << TR(CLASS=c)
<< TH << label_ << ~TH
<< TD
<< SELECT(NAME=name_);
@@ -222,15 +245,9 @@ namespace brep
<< A
<< HREF
<< tenant_dir (root_, tenant_) /
- path (mime_url_encode (name_.string (), false));
-
- // Propagate search criteria to the package details page.
- //
- if (!query_.empty ())
- s << "?q=" << query_;
-
- s << ~HREF
- << name_
+ path (mime_url_encode (name_.string (), false))
+ << ~HREF
+ << name_
<< ~A
<< ~SPAN
<< ~TD
@@ -418,47 +435,75 @@ namespace brep
if (!dependencies_.empty ())
s << "; ";
- for (const auto& d: dependencies_)
+ for (const dependency_alternatives& das: dependencies_)
{
- if (&d != &dependencies_[0])
+ if (&das != &dependencies_[0])
s << ", ";
- if (d.conditional)
- s << "?";
-
- if (d.buildtime)
+ if (das.buildtime)
s << "*";
- // Suppress package name duplicates.
+ // Suppress dependency alternative duplicates, like in
+ // `{foo bar} < 1.1 | {foo bar} > 1.5`.
+ //
+ // Return the dependency package name space-separated list.
//
- set<package_name> names;
- for (const auto& da: d)
- names.emplace (da.name);
+ auto deps_list = [] (const dependency_alternative& da)
+ {
+ string r;
+ for (const dependency& d: da)
+ {
+ if (!r.empty ())
+ r += ' ';
+
+ r += d.name.string ();
+ }
- bool mult (names.size () > 1);
+ return r;
+ };
+
+ set<string> alternatives;
+ for (const dependency_alternative& da: das)
+ alternatives.insert (deps_list (da));
+
+ // Note that we may end up with a single package name in parenthesis, if
+ // its duplicates were suppresses. This, however, may be helpful,
+ // indicating that there some alternatives for the package.
+ //
+ bool mult (das.size () > 1 ||
+ (das.size () == 1 && das[0].size () > 1));
if (mult)
- s << "(";
+ s << '(';
bool first (true);
- for (const auto& da: d)
+ for (const dependency_alternative& da: das)
{
- const package_name& n (da.name);
- if (names.find (n) != names.end ())
- {
- names.erase (n);
+ auto i (alternatives.find (deps_list (da)));
- if (first)
- first = false;
- else
- s << " | ";
+ if (i == alternatives.end ())
+ continue;
+
+ alternatives.erase (i);
+
+ if (!first)
+ s << " | ";
+ else
+ first = false;
+
+ for (const dependency& d: da)
+ {
+ if (&d != &da[0])
+ s << ' ';
// Try to display the dependency as a link if it is resolved.
// Otherwise display it as plain text.
//
- if (da.package != nullptr)
+ const package_name& n (d.name);
+
+ if (d.package != nullptr)
{
- shared_ptr<package> p (da.package.load ());
+ shared_ptr<package> p (d.package.load ());
assert (p->internal () || !p->other_repositories.empty ());
shared_ptr<repository> r (
@@ -481,10 +526,13 @@ namespace brep
else
s << n;
}
+
+ if (da.enable)
+ s << " ?";
}
if (mult)
- s << ")";
+ s << ')';
}
s << ~SPAN
@@ -509,25 +557,25 @@ namespace brep
<< SPAN(CLASS="value")
<< requirements_.size () << "; ";
- for (const auto& r: requirements_)
+ for (const auto& ras: requirements_)
{
- if (&r != &requirements_[0])
+ if (&ras != &requirements_[0])
s << ", ";
- if (r.conditional)
- s << "?";
+ if (ras.buildtime)
+ s << '*';
- if (r.buildtime)
- s << "*";
-
- if (r.empty ())
+ // If this is a simple requirement without id, then print the comment
+ // first word.
+ //
+ if (ras.simple () && ras[0][0].empty ())
{
- // If there is no requirement alternatives specified, then print the
- // comment first word.
- //
- const auto& c (r.comment);
+ const auto& c (ras.comment);
if (!c.empty ())
{
+ if (ras[0].enable)
+ s << "? ";
+
auto n (c.find (' '));
s << string (c, 0, n);
@@ -537,21 +585,31 @@ namespace brep
}
else
{
- bool mult (r.size () > 1);
+ bool mult (ras.size () > 1 ||
+ (ras.size () == 1 && ras[0].size () > 1));
if (mult)
- s << "(";
+ s << '(';
- for (const auto& ra: r)
+ for (const auto& ra: ras)
{
- if (&ra != &r[0])
+ if (&ra != &ras[0])
s << " | ";
- s << ra;
+ for (const string& r: ra)
+ {
+ if (&r != &ra[0])
+ s << ' ';
+
+ s << r;
+ }
+
+ if (ra.enable)
+ s << " ?";
}
if (mult)
- s << ")";
+ s << ')';
}
}
@@ -565,7 +623,8 @@ namespace brep
void TR_URL::
operator() (serializer& s) const
{
- s << TR(CLASS=label_)
+ string c (label_to_class (label_));
+ s << TR(CLASS=c)
<< TH << label_ << ~TH
<< TD
<< SPAN(CLASS="value");
@@ -576,7 +635,7 @@ namespace brep
if (icasecmp (url_.scheme, "https") == 0 ||
icasecmp (url_.scheme, "http") == 0)
{
- butl::url u (url_);
+ url u (url_);
u.scheme.clear ();
s << A(HREF=url_) << u << ~A;
@@ -595,7 +654,8 @@ namespace brep
void TR_EMAIL::
operator() (serializer& s) const
{
- s << TR(CLASS=label_)
+ string c (label_to_class (label_));
+ s << TR(CLASS=c)
<< TH << label_ << ~TH
<< TD
<< SPAN(CLASS="value")
@@ -645,32 +705,22 @@ namespace brep
<< A
<< HREF
<< tenant_dir (root_, tenant_) << "?about#"
- << mime_url_encode (html_id (name_), false)
+ << mime_url_encode (html_id (location_.canonical_name ()), false)
<< ~HREF
- << name_
+ << location_
<< ~A
<< ~SPAN
<< ~TD
<< ~TR;
}
- // TR_LOCATION
- //
- void TR_LOCATION::
- operator() (serializer& s) const
- {
- s << TR(CLASS="location")
- << TH << "location" << ~TH
- << TD << SPAN(CLASS="value") << location_ << ~SPAN << ~TD
- << ~TR;
- }
-
// TR_LINK
//
void TR_LINK::
operator() (serializer& s) const
{
- s << TR(CLASS=label_)
+ string c (label_to_class (label_));
+ s << TR(CLASS=c)
<< TH << label_ << ~TH
<< TD
<< SPAN(CLASS="value") << A(HREF=url_) << text_ << ~A << ~SPAN
@@ -699,8 +749,24 @@ namespace brep
<< TD
<< SPAN(CLASS="value");
+ // Print the ' | ' separator if this is not the first item and reset the
+ // `first` flag to false otherwise.
+ //
+ bool first (true);
+ auto separate = [&s, &first] ()
+ {
+ if (first)
+ first = false;
+ else
+ s << " | ";
+ };
+
if (build_.state == build_state::building)
- s << SPAN(CLASS="building") << "building" << ~SPAN << " | ";
+ {
+ separate ();
+
+ s << SPAN(CLASS="building") << "building" << ~SPAN;
+ }
else
{
// If no unsuccessful operation results available, then print the
@@ -713,7 +779,10 @@ namespace brep
if (build_.results.empty () || *build_.status == result_status::success)
{
assert (build_.status);
- s << SPAN_BUILD_RESULT_STATUS (*build_.status) << " | ";
+
+ separate ();
+
+ s << SPAN_BUILD_RESULT_STATUS (*build_.status);
}
if (!build_.results.empty ())
@@ -721,6 +790,9 @@ namespace brep
for (const auto& r: build_.results)
{
if (r.status != result_status::success)
+ {
+ separate ();
+
s << SPAN_BUILD_RESULT_STATUS (r.status) << " ("
<< A
<< HREF
@@ -728,26 +800,33 @@ namespace brep
<< ~HREF
<< r.operation
<< ~A
- << ") | ";
+ << ")";
+ }
}
+ separate ();
+
s << A
<< HREF << build_log_url (host_, root_, build_) << ~HREF
<< "log"
- << ~A
- << " | ";
+ << ~A;
}
}
- if (build_.force == (build_.state == build_state::building
- ? force_state::forcing
- : force_state::forced))
- s << SPAN(CLASS="pending") << "pending" << ~SPAN;
- else
- s << A
- << HREF << build_force_url (host_, root_, build_) << ~HREF
- << "rebuild"
- << ~A;
+ if (!archived_)
+ {
+ separate ();
+
+ if (build_.force == (build_.state == build_state::building
+ ? force_state::forcing
+ : force_state::forced))
+ s << SPAN(CLASS="pending") << "pending" << ~SPAN;
+ else
+ s << A
+ << HREF << build_force_url (host_, root_, build_) << ~HREF
+ << "rebuild"
+ << ~A;
+ }
s << ~SPAN
<< ~TD
@@ -875,14 +954,16 @@ namespace brep
void DIV_TEXT::
operator() (serializer& s) const
{
- switch (type_)
+ const string& t (text_.text);
+
+ switch (text_.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 */);
+ serialize_pre_text (s, t, length_, url_, "" /* id */);
s << ~DIV;
break;
}
@@ -902,9 +983,9 @@ namespace brep
// 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.
+ // probability of such an event by limiting the text size to 1M.
//
- if (text_.size () > 64 * 1024)
+ if (t.size () > 1024 * 1024)
{
print_error (what_ + " is too long");
return;
@@ -916,37 +997,38 @@ namespace brep
{
// Parse Markdown into the AST.
//
+ // Note that the footnotes extension needs to be enabled via the
+ // CMARK_OPT_FOOTNOTES flag rather than the
+ // cmark_parser_attach_syntax_extension() function call.
+ //
unique_ptr<cmark_parser, void (*)(cmark_parser*)> parser (
- cmark_parser_new (CMARK_OPT_DEFAULT | CMARK_OPT_VALIDATE_UTF8),
+ cmark_parser_new (CMARK_OPT_DEFAULT |
+ CMARK_OPT_FOOTNOTES |
+ 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)
+ if (text_.type == text_type::github_mark)
{
auto add = [&parser] (const char* ext)
- {
- cmark_syntax_extension* e (
- cmark_find_syntax_extension (ext));
+ {
+ cmark_syntax_extension* e (
+ cmark_find_syntax_extension (ext));
- // Built-in extension is only expected.
- //
- assert (e != nullptr);
+ // Built-in extension is only expected.
+ //
+ assert (e != nullptr);
- cmark_parser_attach_syntax_extension (parser.get (), e);
- };
+ 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 ());
+ cmark_parser_feed (parser.get (), t.c_str (), t.size ());
unique_ptr<cmark_node, void (*)(cmark_node*)> doc (
cmark_parser_finish (parser.get ()),