aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--brep/buildfile2
-rw-r--r--brep/module.cxx28
-rw-r--r--brep/options.cli20
-rw-r--r--brep/package-details (renamed from brep/package-version-search)12
-rw-r--r--brep/package-details.cxx225
-rw-r--r--brep/package-search.cxx86
-rw-r--r--brep/package-version-details.cxx267
-rw-r--r--brep/package-version-search.cxx179
-rw-r--r--brep/page287
-rw-r--r--brep/page.cxx604
-rw-r--r--brep/services.cxx10
-rwxr-xr-xbuild.sh2
-rw-r--r--etc/httpd.conf14
-rw-r--r--etc/package-details.conf15
-rw-r--r--etc/package-search.conf2
-rw-r--r--etc/package-version-details.conf7
-rw-r--r--etc/package-version-search.conf12
-rw-r--r--tests/loader/driver.cxx33
-rw-r--r--tests/loader/internal/1/math/packages24
-rw-r--r--tests/web/xhtml/test.out1
-rw-r--r--web/apache/request.cxx32
-rw-r--r--web/apache/service.cxx2
-rw-r--r--web/module8
-rw-r--r--web/xhtml25
-rw-r--r--www/common.css317
-rw-r--r--www/package-details.css107
-rw-r--r--www/package-search.css41
-rw-r--r--www/package-version-details.css161
28 files changed, 1889 insertions, 634 deletions
diff --git a/brep/buildfile b/brep/buildfile
index 79dadc2..808de15 100644
--- a/brep/buildfile
+++ b/brep/buildfile
@@ -21,7 +21,7 @@ libso{brep}: cxx.export.poptions = -I$out_root -I$src_root
#
import libs += libstudxml%lib{studxml}
-brep = cxx{diagnostics module services package-search package-version-search \
+brep = cxx{diagnostics module services package-search package-details \
package-version-details shared-database page} cli.cxx{options}
web = ../web/apache/cxx{request service} ../web/cxx{mime-url-encoding}
diff --git a/brep/module.cxx b/brep/module.cxx
index 62c70c9..fa7f479 100644
--- a/brep/module.cxx
+++ b/brep/module.cxx
@@ -89,7 +89,9 @@ namespace brep
for (const auto& nv: options)
{
argv.push_back (nv.name.c_str ());
- argv.push_back (nv.value.c_str ());
+
+ if (nv.value)
+ argv.push_back (nv.value->c_str ());
}
int argc (argv.size ());
@@ -252,7 +254,7 @@ namespace brep
peek ()
{
if (i_ != name_values_.end ())
- return name_ ? i_->name.c_str () : i_->value.c_str ();
+ return name_ ? i_->name.c_str () : i_->value->c_str ();
else
throw eos_reached ();
}
@@ -262,12 +264,8 @@ namespace brep
{
if (i_ != name_values_.end ())
{
- const char* r (name_ ? i_->name.c_str () : i_->value.c_str ());
-
- if (!name_)
- ++i_;
-
- name_ = !name_;
+ const char* r (name_ ? i_->name.c_str () : i_->value->c_str ());
+ skip ();
return r;
}
else
@@ -279,10 +277,18 @@ namespace brep
{
if (i_ != name_values_.end ())
{
- if (!name_)
+ if (name_)
+ {
+ if (i_->value)
+ name_ = false;
+ else
+ ++i_;
+ }
+ else
+ {
++i_;
-
- name_ = !name_;
+ name_ = true;
+ }
}
else
throw eos_reached ();
diff --git a/brep/options.cli b/brep/options.cli
index 90063c1..86cb70c 100644
--- a/brep/options.cli
+++ b/brep/options.cli
@@ -28,14 +28,17 @@ namespace brep
std::uint16_t pages-in-pager = 10;
};
- class package_version_search: module, db
+ class package_details: module, db
{
std::uint16_t results-on-page = 10;
std::uint16_t pages-in-pager = 10;
+ std::uint16_t description-length = 400;
};
class package_version_details: module, db
{
+ std::uint16_t description-length = 400;
+ std::uint16_t changes-length = 800;
};
}
@@ -43,7 +46,8 @@ namespace brep
//
namespace params
{
- // Use parameters long names in the C++ code, short aliases in HTTP URL.
+ // Use parameters long names in the C++ code, short aliases (if present)
+ // in HTTP URL.
//
class package_search
{
@@ -56,7 +60,7 @@ namespace brep
std::string query | q = "";
};
- class package_version_search
+ class package_details
{
// Display package version search result list starting from this page.
//
@@ -65,10 +69,20 @@ namespace brep
// Package version search criteria.
//
std::string query | q = "";
+
+ // Full page variant.
+ //
+ // @@ Shouldn't we use one letter alias for URL as well ?
+ // I like full. It is descriptive. q= is kind now a convention.
+ //
+ bool full = false;
};
class package_version_details
{
+ // Full page variant.
+ //
+ bool full = false;
};
}
}
diff --git a/brep/package-version-search b/brep/package-details
index 0292ae0..471dcee 100644
--- a/brep/package-version-search
+++ b/brep/package-details
@@ -1,9 +1,9 @@
-// file : brep/package-version-search -*- C++ -*-
+// file : brep/package-details -*- C++ -*-
// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
// license : MIT; see accompanying LICENSE file
-#ifndef BREP_PACKAGE_VERSION_SEARCH
-#define BREP_PACKAGE_VERSION_SEARCH
+#ifndef BREP_PACKAGE_DETAILS
+#define BREP_PACKAGE_DETAILS
#include <memory> // shared_ptr
@@ -14,7 +14,7 @@
namespace brep
{
- class package_version_search: public module
+ class package_details: public module
{
private:
virtual void
@@ -24,9 +24,9 @@ namespace brep
init (cli::scanner&);
private:
- std::shared_ptr<options::package_version_search> options_;
+ std::shared_ptr<options::package_details> options_;
std::shared_ptr<odb::core::database> db_;
};
}
-#endif // BREP_PACKAGE_VERSION_SEARCH
+#endif // BREP_PACKAGE_DETAILS
diff --git a/brep/package-details.cxx b/brep/package-details.cxx
new file mode 100644
index 0000000..f814ef8
--- /dev/null
+++ b/brep/package-details.cxx
@@ -0,0 +1,225 @@
+// file : brep/package-details.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <brep/package-details>
+
+#include <string>
+#include <memory> // make_shared(), shared_ptr
+#include <cstddef> // size_t
+
+#include <xml/serializer>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <web/xhtml>
+#include <web/module>
+#include <web/mime-url-encoding>
+
+#include <brep/page>
+#include <brep/options>
+#include <brep/package>
+#include <brep/package-odb>
+#include <brep/shared-database>
+
+using namespace std;
+using namespace cli;
+using namespace odb::core;
+
+namespace brep
+{
+ void package_details::
+ init (scanner& s)
+ {
+ MODULE_DIAG;
+
+ options_ = make_shared<options::package_details> (
+ s, unknown_mode::fail, unknown_mode::fail);
+
+ db_ = shared_database (options_->db_host (), options_->db_port ());
+ }
+
+ template <typename T>
+ static inline query<T>
+ search_params (const string& n, const string& q)
+ {
+ using query = query<T>;
+
+ return "(" +
+ (q.empty ()
+ ? query ("NULL")
+ : "plainto_tsquery (" + query::_val (q) + ")") +
+ "," +
+ query::_val (n) +
+ ")";
+ }
+
+ void package_details::
+ handle (request& rq, response& rs)
+ {
+ using namespace xml;
+ using namespace web;
+ using namespace web::xhtml;
+
+ MODULE_DIAG;
+
+ const string& name (*rq.path ().rbegin ());
+ params::package_details pr;
+
+ try
+ {
+ param_scanner s (rq.parameters ());
+ pr = params::package_details (s, unknown_mode::fail, unknown_mode::fail);
+ }
+ catch (const unknown_argument& e)
+ {
+ throw invalid_request (400, e.what ());
+ }
+
+ const string& sq (pr.query ()); // Search query.
+ size_t pg (pr.page ());
+ bool f (pr.full ());
+ string en (mime_url_encode (name));
+ size_t rp (options_->results_on_page ());
+
+ auto url (
+ [&en](bool f = false,
+ const string& q = "",
+ size_t p = 0,
+ const string& a = "") -> string
+ {
+ string s ("?");
+ string u (en);
+
+ if (f) { u += "?full"; s = "&"; }
+ if (!q.empty ()) { u += s + "q=" + mime_url_encode (q); s = "&"; }
+ if (p > 0) { u += s + "p=" + to_string (p); s = "&"; }
+ if (!a.empty ()) { u += '#' + a; }
+ return u;
+ });
+
+ serializer s (rs.content (), name);
+ const string title (sq.empty () ? name : name + " " + sq);
+
+ s << HTML
+ << HEAD
+ << TITLE << title << ~TITLE
+ << CSS_LINKS ("/package-details.css")
+ << ~HEAD
+ << BODY
+ << DIV_HEADER ()
+ << DIV(ID="content");
+
+ if (f)
+ s << CLASS << "full" << ~CLASS;
+
+ s << DIV(ID="heading")
+ << H1 << A(HREF=url ()) << name << ~A << ~H1
+ << A(HREF=url (!f, sq, pg)) << (f ? "[brief]" : "[full]") << ~A
+ << ~DIV;
+
+ transaction t (db_->begin ());
+
+ shared_ptr<package> p;
+ {
+ latest_package lp;
+ if (!db_->query_one<latest_package> (
+ query<latest_package>(
+ "(" + query<latest_package>::_val (name) + ")"), lp))
+ {
+ throw invalid_request (404, "Package '" + name + "' not found");
+ }
+
+ p = db_->load<package> (lp.id);
+ }
+
+ const license_alternatives& ll (p->license_alternatives);
+
+ if (pg == 0)
+ {
+ // Display package details on the first page only.
+ //
+ s << H2 << p->summary << ~H2;
+
+ if (const auto& d = p->description)
+ s << (f
+ ? P_DESCRIPTION (*d)
+ : P_DESCRIPTION (
+ *d,
+ options_->description_length (),
+ url (!f, sq, pg, "description")));
+
+ s << TABLE(CLASS="proplist", ID="package")
+ << TBODY
+ << TR_LICENSE (ll)
+ << TR_URL (p->url)
+ << TR_EMAIL (p->email)
+ << TR_TAGS (p->tags)
+ << ~TBODY
+ << ~TABLE;
+ }
+
+ auto pc (
+ db_->query_value<package_count> (
+ search_params<package_count> (name, sq)));
+
+ auto r (
+ db_->query<package_search_rank> (
+ search_params<package_search_rank> (name, sq) +
+ "ORDER BY rank DESC, version_epoch DESC, "
+ "version_canonical_upstream DESC, version_revision DESC" +
+ "OFFSET" + to_string (pg * rp) +
+ "LIMIT" + to_string (rp)));
+
+ s << FORM_SEARCH (sq.c_str ())
+ << DIV_COUNTER (pc, "Version", "Versions")
+
+ // Enclose the subsequent tables to be able to use nth-child CSS selector.
+ //
+ << DIV;
+
+ for (const auto& pr: r)
+ {
+ shared_ptr<package> p (db_->load<package> (pr.id));
+
+ s << TABLE(CLASS="proplist version")
+ << TBODY
+ << TR_VERSION (name, p->version.string ())
+
+ // @@ Shouldn't we skip low priority row ?
+ //
+ << TR_PRIORITY (p->priority);
+
+ // Comparing objects of the license_alternatives class as being of the
+ // vector<vector<string>> class, so comments are not considered.
+ //
+ if (p->license_alternatives != ll)
+ s << TR_LICENSE (p->license_alternatives);
+
+ assert (p->internal_repository != nullptr);
+
+ // @@ Shouldn't we make package location to be a link to the proper
+ // place of the About page, describing corresponding repository ?
+ //
+ // @@ In most cases package location will be the same for all versions
+ // of the same package. Shouldn't we put package location to the
+ // package summary part and display it here only if it differes
+ // from the one in the summary ?
+ //
+ s << TR_LOCATION (p->internal_repository.object_id ())
+ << TR_DEPENDS (p->dependencies)
+ << TR_REQUIRES (p->requirements)
+ << ~TBODY
+ << ~TABLE;
+ }
+
+ t.commit ();
+
+ s << ~DIV
+ << DIV_PAGER (pg, pc, rp, options_->pages_in_pager (), url (f, sq))
+ << ~DIV
+ << ~BODY
+ << ~HTML;
+ }
+}
diff --git a/brep/package-search.cxx b/brep/package-search.cxx
index ffe5cb9..64e43f1 100644
--- a/brep/package-search.cxx
+++ b/brep/package-search.cxx
@@ -73,80 +73,68 @@ namespace brep
throw invalid_request (400, e.what ());
}
- // @@ Would be nice to have a manipulator indenting string properly
- // according to the most nested element identation.
- //
- const char* ident ("\n ");
- const char* title ("Package Search");
- serializer s (rs.content (), title);
+ const string& sq (pr.query ()); // Search query.
+ size_t pg (pr.page ());
+ string qp (sq.empty () ? "" : "q=" + mime_url_encode (sq));
+ size_t rp (options_->results_on_page ());
+
+ serializer s (rs.content (), "Packages");
+
+ const string& title (
+ sq.empty () ? s.output_name () : s.output_name () + " " + sq);
s << HTML
<< HEAD
<< TITLE << title << ~TITLE
- << CSS_STYLE << ident
- << A_STYLE () << ident
- << DIV_PAGER_STYLE () << ident
- << "#packages {font-size: x-large;}" << ident
- << ".package {margin: 0.5em 0 0;}" << ident
- << ".name {font-size: x-large;}" << ident
- << ".tags {margin: 0.3em 0 0;}" << ident
- << "form {margin: 0.5em 0 0 0;}"
- << ~CSS_STYLE
+ << CSS_LINKS ("/package-search.css")
<< ~HEAD
- << BODY;
-
- const string& sq (pr.query ()); // Search query.
- string qp (sq.empty () ? "" : "q=" + mime_url_encode (sq));
- size_t rop (options_->results_on_page ());
+ << BODY
+ << DIV_HEADER ()
+ << DIV(ID="content");
transaction t (db_->begin ());
- size_t pc (
+ auto pc (
db_->query_value<latest_package_count> (
search_param<latest_package_count> (sq)));
- s << DIV(ID="packages") << "Packages (" << pc << ")" << ~DIV
- << FORM_SEARCH (sq);
-
auto r (
db_->query<latest_package_search_rank> (
search_param<latest_package_search_rank> (sq) +
"ORDER BY rank DESC, name" +
- "OFFSET" + to_string (pr.page () * rop) +
- "LIMIT" + to_string (rop)));
+ "OFFSET" + to_string (pg * rp) +
+ "LIMIT" + to_string (rp)));
+
+ s << FORM_SEARCH (sq.c_str ())
+ << DIV_COUNTER (pc, "Package", "Packages")
+
+ // Enclose the subsequent tables to be able to use nth-child CSS selector.
+ //
+ << DIV;
for (const auto& pr: r)
{
shared_ptr<package> p (db_->load<package> (pr.id));
- s << DIV(CLASS="package")
- << DIV(CLASS="name")
- << A
- << HREF << "/go/" << mime_url_encode (p->id.name);
-
- // Propagate search criteria to the package version search url.
- //
- if (!qp.empty ())
- s << "?" << qp;
-
- s << ~HREF
- << p->id.name
- << ~A
- << ~DIV
- << DIV(CLASS="summary") << p->summary << ~DIV
- << DIV_TAGS (p->tags)
- << DIV_LICENSES (p->license_alternatives)
- << DIV(CLASS="dependencies")
- << "Dependencies: " << p->dependencies.size ()
- << ~DIV
- << ~DIV;
+ s << TABLE(CLASS="proplist package")
+ << TBODY
+ << TR_NAME (p->id.name, qp)
+ << TR_SUMMARY (p->summary)
+ << TR_LICENSE (p->license_alternatives)
+ << TR_TAGS (p->tags)
+ << TR_DEPENDS (p->dependencies)
+ << TR_REQUIRES (p->requirements)
+ << ~TBODY
+ << ~TABLE;
}
t.commit ();
- string u (qp.empty () ? "/" : ("/?" + qp));
+ string url (qp.empty () ? "/" : ("/?" + qp));
- s << DIV_PAGER (pr.page (), pc, rop, options_->pages_in_pager (), u)
+ s << ~DIV
+ << DIV_PAGER (pg, pc, rp, options_->pages_in_pager (), url)
+ << ~DIV
<< ~BODY
<< ~HTML;
}
diff --git a/brep/package-version-details.cxx b/brep/package-version-details.cxx
index 64945c4..71559b9 100644
--- a/brep/package-version-details.cxx
+++ b/brep/package-version-details.cxx
@@ -78,39 +78,44 @@ namespace brep
throw invalid_request (400, e.what ());
}
- const char* ident ("\n ");
+ bool f (pr.full ());
const string& vs (v.string ());
+
+ auto url ([&vs](bool f = false, const string& a = "") -> string
+ {
+ string u (vs);
+
+ if (f) { u += "?full"; }
+ if (!a.empty ()) { u += '#' + a; }
+ return u;
+ });
+
const string name (n + " " + vs);
- const string title ("Package Version " + name);
- serializer s (rs.content (), title);
+ serializer s (rs.content (), name);
s << HTML
<< HEAD
- << TITLE << title << ~TITLE
- << CSS_STYLE << ident
- << A_STYLE () << ident
- << "#name {font-size: xx-large; font-weight: bold;}" << ident
- << ".url, .email {font-size: medium;}" << ident
- << ".comment {font-size: small;}" << ident
- << "#summary {font-size: x-large; margin: 0.2em 0 0;}" << ident
- << "#description {margin: 0.5em 0 0;}" << ident
- << ".tags {margin: 0.3em 0 0;}" << ident
- << "#package, .priority, #licenses, #dependencies, #requirements, "
- "#locations, #changes {" << ident
- << " font-size: x-large;" << ident
- << " margin: 0.5em 0 0;" << ident
- << "}" << ident
- << "ul {margin: 0; padding: 0 0 0 1em;}" << ident
- << "li {font-size: large; margin: 0.1em 0 0;}" << ident
- << ".conditional {font-weight: bold;}" << ident
- << "pre {font-size: medium; margin: 0.1em 0 0 1em;}"
- << ~CSS_STYLE
+ << TITLE << name << ~TITLE
+ << CSS_LINKS ("/package-version-details.css")
<< ~HEAD
<< BODY
- << DIV(ID="name")
- << A << HREF << "/go/" << mime_url_encode (n) << ~HREF << n << ~A
- << " " << vs
- << ~DIV;
+ << DIV_HEADER ()
+ << DIV(ID="content");
+
+ if (f)
+ s << CLASS << "full" << ~CLASS;
+
+ s << DIV(ID="heading")
+ << H1
+ << A
+ << HREF << "/go/" << mime_url_encode (n) << ~HREF
+ << n
+ << ~A
+ << "/"
+ << A(HREF=url ()) << vs << ~A
+ << ~H1
+ << A(HREF=url (!f)) << (f ? "[brief]" : "[full]") << ~A
+ << ~DIV;
bool not_found (false);
shared_ptr<package> p;
@@ -134,182 +139,138 @@ namespace brep
if (not_found)
throw invalid_request (404, "Package '" + name + "' not found");
- assert (p->location);
- const string u (p->internal_repository.load ()->location.string () +
- "/" + p->location->string ());
-
- s << DIV(CLASS="url") << A << HREF << u << ~HREF << u << ~A << ~DIV
- << DIV(ID="summary") << p->summary << ~DIV
- << DIV_URL (p->url)
- << DIV_EMAIL (p->email);
-
- if (p->description)
- s << DIV(ID="description") << *p->description << ~DIV;
-
- const priority& pt (p->priority);
+ s << H2 << p->summary << ~H2;
- s << DIV_TAGS (p->tags)
- << DIV_PRIORITY (pt);
+ if (const auto& d = p->description)
+ s << (f
+ ? P_DESCRIPTION (*d)
+ : P_DESCRIPTION (
+ *d, options_->description_length (), url (!f, "description")));
- if (!pt.comment.empty ())
- s << DIV(CLASS="comment") << pt.comment << ~DIV;
+ // Link to download from the internal repository.
+ //
+ assert (p->location);
+ const string du (p->internal_repository.load ()->location.string () +
+ "/" + p->location->string ());
- const auto& ls (p->license_alternatives);
+ t.commit ();
- s << DIV(ID="licenses")
- << "Licenses:"
- << UL;
+ s << TABLE(CLASS="proplist", ID="version")
+ << TBODY
- for (const auto& la: ls)
- {
- s << LI;
+ // Repeat version here since it can be cut out in the header.
+ //
+ << TR_VERSION (p->version.string ())
- for (const auto& l: la)
- {
- if (&l != &la[0])
- s << " & ";
+ << TR_PRIORITY (p->priority)
+ << TR_LICENSES (p->license_alternatives)
+ << TR_LOCATION (p->internal_repository.object_id ())
+ << TR_DOWNLOAD (du)
+ << ~TBODY
+ << ~TABLE
- s << l;
- }
+ << TABLE(CLASS="proplist", ID="package")
+ << TBODY
+ << TR_URL (p->url)
+ << TR_EMAIL (p->email);
- if (!la.comment.empty ())
- s << DIV(CLASS="comment") << la.comment << ~DIV;
+ if (p->package_url && *p->package_url != p->url)
+ s << TR_URL (*p->package_url, "pkg-url");
- s << ~LI;
- }
+ if (p->package_email && *p->package_email != p->email)
+ s << TR_EMAIL (*p->package_email, "pkg-email");
- s << ~UL
- << ~DIV;
+ s << TR_TAGS (p->tags)
+ << ~TBODY
+ << ~TABLE;
const auto& ds (p->dependencies);
if (!ds.empty ())
{
- s << DIV(ID="dependencies")
- << "Dependencies:"
- << UL;
+ s << H3 << "Depends" << ~H3
+ << TABLE(CLASS="proplist", ID="depends")
+ << TBODY;
for (const auto& da: ds)
{
- s << LI;
+ s << TR(CLASS="depends")
+ << TH;
if (da.conditional)
- s << SPAN(CLASS="conditional") << "? " << ~SPAN;
+ s << "?";
+
+ s << ~TH
+ << TD
+ << SPAN(CLASS="value");
for (const auto& d: da)
{
if (&d != &da[0])
s << " | ";
- // @@ Should it be a link to the package version search page or
- // the best matching package version details page on the
- // corresponding repository site ?
- //
- s << d;
+ s << d; // @@ Should it be a link ?
}
- if (!da.comment.empty ())
- s << DIV(CLASS="comment") << da.comment << ~DIV;
-
- s << ~LI;
+ s << ~SPAN
+ << SPAN_COMMENT (da.comment)
+ << ~TD
+ << ~TR;
}
- s << ~UL
- << ~DIV;
+ s << ~TBODY
+ << ~TABLE;
}
- const auto& rm (p->requirements);
+ const auto& rt (p->requirements);
- if (!rm.empty ())
+ if (!rt.empty ())
{
- s << DIV(ID="requirements")
- << "Requirements:"
- << UL;
+ s << H3 << "Requires" << ~H3
+ << TABLE(CLASS="proplist", ID="requires")
+ << TBODY;
- for (const auto& ra: rm)
+ for (const auto& ra: rt)
{
- s << LI;
+ s << TR(CLASS="requires")
+ << TH;
if (ra.conditional)
- s << SPAN(CLASS="conditional") << "? " << ~SPAN;
-
- if (ra.empty ())
- // If there is no requirement alternatives specified, then
- // print the comment instead.
- //
- s << ra.comment;
- else
- {
- for (const auto& r: ra)
- {
- if (&r != &ra[0])
- s << " | ";
-
- s << r;
- }
-
- if (!ra.comment.empty ())
- s << DIV(CLASS="comment") << ra.comment << ~DIV;
- }
-
- s << ~LI;
- }
-
- s << ~UL
- << ~DIV;
- }
-
- if (p->package_url || p->package_email)
- {
- s << DIV(ID="package")
- << "Package:"
- << UL;
-
- if (p->package_url)
- s << LI << DIV_URL (*p->package_url) << ~LI;
-
- if (p->package_email)
- s << LI << DIV_EMAIL (*p->package_email) << ~LI;
-
- s << ~UL
- << ~DIV;
- }
-
- const auto& er (p->external_repositories);
+ s << "?";
- if (!er.empty ())
- {
- s << DIV(ID="locations")
- << "Alternative Locations:"
- << UL;
+ s << ~TH
+ << TD
+ << SPAN(CLASS="value");
- for (const auto& r: er)
- {
- repository_location l (move (r.load ()->location));
- assert (l.remote ());
+ for (const auto& r: ra)
+ {
+ if (&r != &ra[0])
+ s << " | ";
- string u ("http://" + l.host ());
- if (l.port () != 0)
- u += ":" + to_string (l.port ());
+ s << r;
+ }
- u += "/go/" + mime_url_encode (n) + "/" + vs;
- s << LI
- << DIV(CLASS="url") << A << HREF << u << ~HREF << u << ~A << ~DIV
- << ~LI;
+ s << ~SPAN
+ << SPAN_COMMENT (ra.comment)
+ << ~TD
+ << ~TR;
}
- s << ~UL
- << ~DIV;
+ s << ~TBODY
+ << ~TABLE;
}
- t.commit ();
-
const string& ch (p->changes);
if (!ch.empty ())
- s << DIV(ID="changes") << "Changes:" << PRE << ch << ~PRE << ~DIV;
-
- s << ~BODY
+ s << H3 << "Changes" << ~H3
+ << (f
+ ? PRE_CHANGES (ch)
+ : PRE_CHANGES (
+ ch, options_->changes_length (), url (!f, "changes")));
+
+ s << ~DIV
+ << ~BODY
<< ~HTML;
}
}
diff --git a/brep/package-version-search.cxx b/brep/package-version-search.cxx
deleted file mode 100644
index 84c06e1..0000000
--- a/brep/package-version-search.cxx
+++ /dev/null
@@ -1,179 +0,0 @@
-// file : brep/package-version-search.cxx -*- C++ -*-
-// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
-// license : MIT; see accompanying LICENSE file
-
-#include <brep/package-version-search>
-
-#include <string>
-#include <memory> // make_shared(), shared_ptr
-#include <cstddef> // size_t
-
-#include <xml/serializer>
-
-#include <odb/database.hxx>
-#include <odb/transaction.hxx>
-
-#include <web/xhtml>
-#include <web/module>
-#include <web/mime-url-encoding>
-
-#include <brep/page>
-#include <brep/options>
-#include <brep/package>
-#include <brep/package-odb>
-#include <brep/shared-database>
-
-using namespace std;
-using namespace cli;
-using namespace odb::core;
-
-namespace brep
-{
- void package_version_search::
- init (scanner& s)
- {
- MODULE_DIAG;
-
- options_ = make_shared<options::package_version_search> (
- s, unknown_mode::fail, unknown_mode::fail);
-
- db_ = shared_database (options_->db_host (), options_->db_port ());
- }
-
- template <typename T>
- static inline query<T>
- search_params (const string& n, const string& q)
- {
- using query = query<T>;
-
- return "(" +
- (q.empty ()
- ? query ("NULL")
- : "plainto_tsquery (" + query::_val (q) + ")") +
- "," +
- query::_val (n) +
- ")";
- }
-
- void package_version_search::
- handle (request& rq, response& rs)
- {
- using namespace xml;
- using namespace web;
- using namespace web::xhtml;
-
- MODULE_DIAG;
-
- const string& name (*rq.path ().rbegin ());
- params::package_version_search pr;
-
- try
- {
- param_scanner s (rq.parameters ());
- pr = params::package_version_search (
- s, unknown_mode::fail, unknown_mode::fail);
- }
- catch (const unknown_argument& e)
- {
- throw invalid_request (400, e.what ());
- }
-
- const char* ident ("\n ");
- const string title ("Package " + name);
- serializer s (rs.content (), title);
-
- s << HTML
- << HEAD
- << TITLE << title << ~TITLE
- << CSS_STYLE << ident
- << A_STYLE () << ident
- << DIV_PAGER_STYLE () << ident
- << "#name {font-size: xx-large; font-weight: bold;}" << ident
- << "#summary {font-size: x-large; margin: 0.2em 0 0;}" << ident
- << ".url, .email {font-size: medium;}" << ident
- << ".comment {font-size: small;}" << ident
- << "#description {margin: 0.5em 0 0;}" << ident
- << ".tags {margin: 0.3em 0 0;}" << ident
- << "#versions {font-size: x-large; margin: 0.5em 0 0;}" << ident
- << ".package_version {margin: 0.5em 0 0;}" << ident
- << ".version {font-size: x-large;}" << ident
- << ".priority {margin: 0.3em 0 0;}" << ident
- << "form {margin: 0.5em 0 0 0;}"
- << ~CSS_STYLE
- << ~HEAD
- << BODY
- << DIV(ID="name") << name << ~DIV;
-
- const string& sq (pr.query ()); // Search query.
- size_t rop (options_->results_on_page ());
-
- transaction t (db_->begin ());
-
- shared_ptr<package> p;
- {
- latest_package lp;
- if (!db_->query_one<latest_package> (
- query<latest_package>(
- "(" + query<latest_package>::_val (name) + ")"), lp))
- {
- throw invalid_request (404, "Package '" + name + "' not found");
- }
-
- p = db_->load<package> (lp.id);
- }
-
- s << DIV(ID="summary") << p->summary << ~DIV
- << DIV_URL (p->url)
- << DIV_EMAIL (p->email);
-
- if (p->description)
- s << DIV(ID="description") << *p->description << ~DIV;
-
- s << DIV_TAGS (p->tags);
-
- size_t pvc (
- db_->query_value<package_count> (
- search_params<package_count> (name, sq)));
-
- s << DIV(ID="versions") << "Versions (" << pvc << ")" << ~DIV
- << FORM_SEARCH (sq);
-
- auto r (
- db_->query<package_search_rank> (
- search_params<package_search_rank> (name, sq) +
- "ORDER BY rank DESC, version_epoch DESC, "
- "version_canonical_upstream DESC, version_revision DESC" +
- "OFFSET" + to_string (pr.page () * rop) +
- "LIMIT" + to_string (rop)));
-
- for (const auto& pr: r)
- {
- shared_ptr<package> p (db_->load<package> (pr.id));
- const string& v (p->version.string ());
-
- s << DIV(CLASS="package_version")
- << DIV(CLASS="version")
- << A
- << HREF << "/go/" << mime_url_encode (name) << "/" << v << ~HREF
- << v
- << ~A
- << ~DIV
- << DIV_PRIORITY (p->priority)
- << DIV_LICENSES (p->license_alternatives)
- << DIV(CLASS="dependencies")
- << "Dependencies: " << p->dependencies.size ()
- << ~DIV
- << ~DIV;
- }
-
- t.commit ();
-
- string u (mime_url_encode (name));
- if (!sq.empty ())
- u += "?q=" + mime_url_encode (sq);
-
- s << DIV_PAGER (pr.page (), pvc, rop, options_->pages_in_pager (), u)
- << ~BODY
- << ~HTML;
- }
-}
diff --git a/brep/page b/brep/page
index b923c7f..309caa9 100644
--- a/brep/page
+++ b/brep/page
@@ -17,78 +17,150 @@ namespace brep
// Page common building blocks.
//
- // A element default style.
+ // Generates CSS link elements.
//
- struct A_STYLE
+ class CSS_LINKS
{
+ public:
+ CSS_LINKS (const char* u): url_ (u) {}
+
void
operator() (xml::serializer& s) const;
+
+ private:
+ const char* url_;
};
- // Generates paging element.
+ // Generates page header element.
//
- class DIV_PAGER
+ struct DIV_HEADER
+ {
+ void
+ operator() (xml::serializer& s) const;
+ };
+
+ // Generates package search form element.
+ //
+ class FORM_SEARCH
{
public:
- DIV_PAGER (std::size_t current_page,
- std::size_t item_count,
- std::size_t item_per_page,
- std::size_t page_number_count,
- const std::string& url);
+ FORM_SEARCH (const std::string& q): query_ (q) {}
void
operator() (xml::serializer& s) const;
private:
- std::size_t current_page_;
- std::size_t item_count_;
- std::size_t item_per_page_;
- std::size_t page_number_count_;
- const std::string& url_;
+ const std::string& query_;
};
- // DIV_PAGER element default style.
+ // Generates 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
+ // look that ugly.
//
- struct DIV_PAGER_STYLE
+ class DIV_COUNTER
{
+ public:
+ DIV_COUNTER (std::size_t c, const char* s, const char* p)
+ : count_ (c), singular_ (s), plural_ (p) {}
+
void
operator() (xml::serializer& s) const;
+
+ private:
+ std::size_t count_;
+ const char* singular_;
+ const char* plural_;
};
- // Generates url element.
+ // Generates package name element.
//
- class DIV_URL
+ class TR_NAME
{
public:
- DIV_URL (const url& u): url_ (u) {}
+ TR_NAME (const std::string& n, const std::string& q)
+ : name_ (n), query_param_ (q) {}
void
operator() (xml::serializer& s) const;
private:
- const url& url_;
+ const std::string& name_;
+ const std::string& query_param_;
};
- // Generates email element.
+ // Generates package version element.
//
- class DIV_EMAIL
+ class TR_VERSION
{
public:
- DIV_EMAIL (const email& e): email_ (e) {}
+ // Display the version as a link to the package version details page.
+ //
+ TR_VERSION (const std::string& p, const std::string& v)
+ : package_ (&p), version_ (v) {}
+
+ // Display the version as a regular text.
+ //
+ TR_VERSION (const std::string& v): package_ (nullptr), version_ (v) {}
void
operator() (xml::serializer& s) const;
private:
- const email& email_;
+ const std::string* package_;
+ const std::string& version_;
+ };
+
+ // Generates package summary element.
+ //
+ class TR_SUMMARY
+ {
+ public:
+ TR_SUMMARY (const std::string& s): summary_ (s) {}
+
+ void
+ operator() (xml::serializer& s) const;
+
+ private:
+ const std::string& summary_;
+ };
+
+ // Generates package license alternatives element.
+ //
+ class TR_LICENSE
+ {
+ public:
+ TR_LICENSE (const license_alternatives& l): licenses_ (l) {}
+
+ void
+ operator() (xml::serializer& s) const;
+
+ private:
+ const license_alternatives& licenses_;
+ };
+
+ // Generates package license alternatives elements. Differs from TR_LICENSE
+ // by producing multiple rows instead of a single one.
+ //
+ class TR_LICENSES
+ {
+ public:
+ TR_LICENSES (const license_alternatives& l): licenses_ (l) {}
+
+ void
+ operator() (xml::serializer& s) const;
+
+ private:
+ const license_alternatives& licenses_;
};
// Generates package tags element.
//
- class DIV_TAGS
+ class TR_TAGS
{
public:
- DIV_TAGS (const strings& ts): tags_ (ts) {}
+ TR_TAGS (const strings& ts): tags_ (ts) {}
void
operator() (xml::serializer& s) const;
@@ -97,26 +169,71 @@ namespace brep
const strings& tags_;
};
- // Generates package version license alternatives element.
+ // Generates package dependencies element.
//
- class DIV_LICENSES
+ class TR_DEPENDS
{
public:
- DIV_LICENSES (const license_alternatives& l): license_alternatives_ (l) {}
+ TR_DEPENDS (const dependencies& d): dependencies_ (d) {}
void
operator() (xml::serializer& s) const;
private:
- const license_alternatives& license_alternatives_;
+ const dependencies& dependencies_;
+ };
+
+ // Generates package requirements element.
+ //
+ class TR_REQUIRES
+ {
+ public:
+ TR_REQUIRES (const requirements& r): requirements_ (r) {}
+
+ void
+ operator() (xml::serializer& s) const;
+
+ private:
+ const requirements& requirements_;
+ };
+
+ // Generates url element.
+ //
+ class TR_URL
+ {
+ public:
+ TR_URL (const url& u, const char* l = "url"): url_ (u), label_ (l) {}
+
+ void
+ operator() (xml::serializer& s) const;
+
+ private:
+ const url& url_;
+ const char* label_;
+ };
+
+ // Generates email element.
+ //
+ class TR_EMAIL
+ {
+ public:
+ TR_EMAIL (const email& e, const char* l = "email")
+ : email_ (e), label_ (l) {}
+
+ void
+ operator() (xml::serializer& s) const;
+
+ private:
+ const email& email_;
+ const char* label_;
};
// Generates package version priority element.
//
- class DIV_PRIORITY
+ class TR_PRIORITY
{
public:
- DIV_PRIORITY (const priority& p): priority_ (p) {}
+ TR_PRIORITY (const priority& p): priority_ (p) {}
void
operator() (xml::serializer& s) const;
@@ -125,18 +242,116 @@ namespace brep
const priority& priority_;
};
- // Generates package search element.
+ // Generates package location element.
//
- class FORM_SEARCH
+ class TR_LOCATION
{
public:
- FORM_SEARCH (const std::string& q): query_ (q) {}
+ TR_LOCATION (const std::string& l): location_ (l) {}
void
operator() (xml::serializer& s) const;
private:
- const std::string& query_;
+ const std::string& location_;
+ };
+
+ // Generates package download URL element.
+ //
+ class TR_DOWNLOAD
+ {
+ public:
+ TR_DOWNLOAD (const std::string& u): url_ (u) {}
+
+ void
+ operator() (xml::serializer& s) const;
+
+ private:
+ const std::string& url_;
+ };
+
+ // Generates comment element.
+ //
+ class SPAN_COMMENT
+ {
+ public:
+ SPAN_COMMENT (const std::string& c): comment_ (c) {}
+
+ void
+ operator() (xml::serializer& s) const;
+
+ private:
+ const std::string& comment_;
+ };
+
+ // Generates package description element.
+ //
+ class P_DESCRIPTION
+ {
+ public:
+ // Genereate full description.
+ //
+ P_DESCRIPTION (const std::string& d)
+ : description_ (d), length_ (d.size ()), url_ (nullptr) {}
+
+ // Genereate brief description.
+ //
+ P_DESCRIPTION (const std::string& d, size_t l, const std::string& u)
+ : description_ (d), length_ (l), url_ (&u) {}
+
+ void
+ operator() (xml::serializer& s) const;
+
+ private:
+ const std::string& description_;
+ std::size_t length_;
+ const std::string* url_; // Full page url.
+ };
+
+ // Generates package description element.
+ //
+ class PRE_CHANGES
+ {
+ public:
+ // Genereate full changes info.
+ //
+ PRE_CHANGES (const std::string& c)
+ : changes_ (c), length_ (c.size ()), url_ (nullptr) {}
+
+ // Genereate brief changes info.
+ //
+ PRE_CHANGES (const std::string& c, size_t l, const std::string& u)
+ : changes_ (c), length_ (l), url_ (&u) {}
+
+ void
+ operator() (xml::serializer& s) const;
+
+ private:
+ const std::string& changes_;
+ std::size_t length_;
+ const std::string* url_; // Full page url.
+ };
+
+ // Generates paging element.
+ //
+ class DIV_PAGER
+ {
+ public:
+ DIV_PAGER (std::size_t current_page,
+ std::size_t item_count,
+ std::size_t item_per_page,
+ std::size_t page_number_count,
+ const std::string& url);
+
+ void
+ operator() (xml::serializer& s) const;
+
+ private:
+ std::size_t current_page_;
+ std::size_t item_count_;
+ std::size_t item_per_page_;
+ std::size_t page_number_count_;
+ const std::string& url_;
};
}
diff --git a/brep/page.cxx b/brep/page.cxx
index c68a7eb..a23ce17 100644
--- a/brep/page.cxx
+++ b/brep/page.cxx
@@ -4,6 +4,7 @@
#include <brep/page>
+#include <set>
#include <string>
#include <cassert>
#include <utility> // move()
@@ -12,209 +13,576 @@
#include <xml/serializer>
#include <web/xhtml>
+#include <web/mime-url-encoding>
#include <brep/package>
using namespace std;
using namespace xml;
+using namespace web;
using namespace web::xhtml;
namespace brep
{
- // A_STYLE
+ // CSS_LINKS
//
- void A_STYLE::
- operator() (xml::serializer& s) const
+ void CSS_LINKS::
+ operator() (serializer& s) const
{
- const char* ident ("\n ");
- s << "a {text-decoration: none;}" << ident
- << "a:hover {text-decoration: underline;}";
+ s << *LINK(REL="stylesheet", TYPE="text/css", HREF="/common.css")
+ << *LINK(REL="stylesheet", TYPE="text/css", HREF=url_);
}
- // DIV_PAGER
+ // DIV_HEADER
//
- DIV_PAGER::
- DIV_PAGER (size_t current_page,
- size_t item_count,
- size_t item_per_page,
- size_t page_number_count,
- const string& url)
- : current_page_ (current_page),
- item_count_ (item_count),
- item_per_page_ (item_per_page),
- page_number_count_ (page_number_count),
- url_ (url)
+ void DIV_HEADER::
+ operator() (serializer& s) const
{
+ s << DIV(ID="header")
+ << DIV(ID="header-menu")
+ << A(HREF="/") << "packages" << ~A
+ << A(HREF="/about") << "about" << ~A
+ << ~DIV
+ << ~DIV;
}
- void DIV_PAGER::
+ // FORM_SEARCH
+ //
+ void FORM_SEARCH::
operator() (serializer& s) const
{
- if (item_count_ == 0 || item_per_page_ == 0)
- return;
+ // The 'action' attribute is optional in HTML5. While the standard don't
+ // specify browser behavior explicitly for the case the attribute is
+ // ommited, the only reasonable behavior is to default it to the current
+ // document URL.
+ //
+ s << FORM(ID="search")
+ << TABLE(CLASS="form-table")
+ << TBODY
+ << TR
+ << TD(ID="search-txt")
+ << *INPUT(TYPE="search", NAME="q", VALUE=query_,
+ AUTOFOCUS="autofocus")
+ << ~TD
+ << TD(ID="search-btn")
+ << *INPUT(TYPE="submit", VALUE="Search")
+ << ~TD
+ << ~TR
+ << ~TBODY
+ << ~TABLE
+ << ~FORM;
+ }
- size_t pc (item_count_ / item_per_page_); // Page count.
+ // DIV_COUNTER
+ //
+ void DIV_COUNTER::
+ operator() (serializer& s) const
+ {
+ s << DIV(ID="count")
+ << count_ << " "
+ << (count_ % 10 == 1 && count_ % 100 != 11 ? singular_ : plural_)
+ << ~DIV;
+ }
- if (item_count_ % item_per_page_)
- ++pc;
+ // TR_NAME
+ //
+ void TR_NAME::
+ operator() (serializer& s) const
+ {
+ s << TR(CLASS="name")
+ << TH << "name" << ~TH
+ << TD
+ << SPAN(CLASS="value")
+ << A
+ << HREF
+ << "/go/" << mime_url_encode (name_);
+
+ // Propagate search criteria to the package details page.
+ //
+ if (!query_param_.empty ())
+ s << "?" << query_param_;
+
+ s << ~HREF
+ << name_
+ << ~A
+ << ~SPAN
+ << ~TD
+ << ~TR;
+ }
- if (pc > 1)
- {
- auto u (
- [this](size_t page) -> string
- {
- return page == 0
- ? url_
- : url_ + (url_.find ('?') == string::npos ? "?p=" : "&p=") +
- to_string (page);
- });
+ void TR_VERSION::
+ operator() (serializer& s) const
+ {
+ s << TR(CLASS="version")
+ << TH << "version" << ~TH
+ << TD
+ << SPAN(CLASS="value");
+
+ if (package_ == nullptr)
+ s << version_;
+ else
+ s << A
+ << HREF
+ << "/go/" << mime_url_encode (*package_) << "/" << version_
+ << ~HREF
+ << version_
+ << ~A;
+
+ s << ~SPAN
+ << ~TD
+ << ~TR;
+ }
- // Can consider customizing class names if use-case appear.
- //
- s << DIV(CLASS="pager");
+ // TR_SUMMARY
+ //
+ void TR_SUMMARY::
+ operator() (serializer& s) const
+ {
+ s << TR(CLASS="summary")
+ << TH << "summary" << ~TH
+ << TD << SPAN(CLASS="value") << summary_ << ~SPAN << ~TD
+ << ~TR;
+ }
- if (current_page_ > 0)
- s << A(CLASS="pg-prev")
- << HREF << u (current_page_ - 1) << ~HREF
- << "<<"
- << ~A
- << " ";
+ // TR_LICENSE
+ //
+ void TR_LICENSE::
+ operator() (serializer& s) const
+ {
+ s << TR(CLASS="license")
+ << TH << "license" << ~TH
+ << TD
+ << SPAN(CLASS="value");
- if (page_number_count_)
+ for (const auto& la: licenses_)
{
- size_t offset (page_number_count_ / 2);
- size_t fp (current_page_ > offset ? current_page_ - offset : 0);
- size_t tp (min (fp + page_number_count_, pc));
+ if (&la != &licenses_[0])
+ s << " " << EM << "or" << ~EM << " ";
- for (size_t p (fp); p < tp; ++p)
+ bool m (la.size () > 1);
+
+ if (m)
+ s << "(";
+
+ for (const auto& l: la)
{
- if (p == current_page_)
- s << SPAN(CLASS="pg-cpage") << p + 1 << ~SPAN;
- else
- s << A(CLASS="pg-page")
- << HREF << u (p) << ~HREF
- << p + 1
- << ~A;
+ if (&l != &la[0])
+ s << " " << EM << "and" << ~EM << " ";
- s << " ";
+ s << l;
}
+
+ if (m)
+ s << ")";
}
- if (current_page_ < pc - 1)
- s << A(CLASS="pg-next")
- << HREF << u (current_page_ + 1) << ~HREF
- << ">>"
- << ~A;
+ s << ~SPAN
+ << ~TD
+ << ~TR;
+ }
- s << ~DIV;
+ // TR_LICENSES
+ //
+ void TR_LICENSES::
+ operator() (serializer& s) const
+ {
+ for (const auto& la: licenses_)
+ {
+ s << TR(CLASS="license")
+ << TH << "license" << ~TH
+ << TD
+ << SPAN(CLASS="value");
+
+ for (const auto& l: la)
+ {
+ if (&l != &la[0])
+ s << " " << EM << "and" << ~EM << " ";
+
+ s << l;
+ }
+
+ s << ~SPAN
+ << SPAN_COMMENT (la.comment)
+ << ~TD
+ << ~TR;
}
}
- // DIV_PAGER_STYLE
+ // TR_TAGS
//
- void DIV_PAGER_STYLE::
- operator() (xml::serializer& s) const
+ void TR_TAGS::
+ operator() (serializer& s) const
{
- const char* ident ("\n ");
- s << ".pager {margin: 0.5em 0 0;}" << ident
- << ".pg-prev {padding: 0 0.3em 0 0;}" << ident
- << ".pg-page {padding: 0 0.3em 0 0;}" << ident
- << ".pg-cpage {padding: 0 0.3em 0 0; font-weight: bold;}";
+ if (!tags_.empty ())
+ {
+ s << TR(CLASS="tags")
+ << TH << "tags" << ~TH
+ << TD
+ << SPAN(CLASS="value");
+
+ for (const auto& t: tags_)
+ {
+ if (&t != &tags_[0])
+ s << " ";
+
+ s << A << HREF << "/?q=" << mime_url_encode (t) << ~HREF << t << ~A;
+ }
+
+ s << ~SPAN
+ << ~TD
+ << ~TR;
+ }
}
- // DIV_LICENSES
+ // TR_DEPENDS
//
- void DIV_LICENSES::
+ void TR_DEPENDS::
operator() (serializer& s) const
{
- s << DIV(CLASS="licenses")
- << "Licenses: ";
+ s << TR(CLASS="depends")
+ << TH << "depends" << ~TH
+ << TD
+ << SPAN(CLASS="value")
+ << dependencies_.size ();
- for (const auto& la: license_alternatives_)
+ if (!dependencies_.empty ())
+ s << "; ";
+
+ for (const auto& d: dependencies_)
{
- if (&la != &license_alternatives_[0])
- s << " | ";
+ if (&d != &dependencies_[0])
+ s << ", ";
- for (const auto& l: la)
+ if (d.conditional)
+ s << "?";
+
+ // Suppress package name duplicates.
+ //
+ set<string> ds;
+ for (const auto& da: d)
+ ds.emplace (da.name);
+
+ bool m (ds.size () > 1);
+
+ if (m)
+ s << "(";
+
+ bool first (true);
+ for (const auto& da: d)
{
- if (&l != &la[0])
- s << " & ";
+ if (ds.find (da.name) != ds.end ())
+ {
+ ds.erase (da.name);
- s << l;
+ if (first)
+ first = false;
+ else
+ s << " | ";
+
+ s << da.name; // @@ Make it a link.
+ }
}
+
+ if (m)
+ s << ")";
}
- s << ~DIV;
+ s << ~SPAN
+ << ~TD
+ << ~TR;
}
- // DIV_URL
+ // TR_REQUIRES
//
- void DIV_URL::
+ void TR_REQUIRES::
operator() (serializer& s) const
{
- s << DIV(CLASS="url")
- << A << HREF << url_ << ~HREF << url_ << ~A;
+ // If there are no requirements, then we omit it, unlike depends, where we
+ // show 0 explicitly.
+ //
+ if (requirements_.empty ())
+ return;
+
+ s << TR(CLASS="requires")
+ << TH << "requires" << ~TH
+ << TD
+ << SPAN(CLASS="value")
+ << requirements_.size () << "; ";
+
+ for (const auto& r: requirements_)
+ {
+ if (&r != &requirements_[0])
+ s << ", ";
- if (!url_.comment.empty ())
- s << DIV(CLASS="comment") << url_.comment << ~DIV;
+ if (r.conditional)
+ s << "?";
+
+ if (r.empty ())
+ {
+ // If there is no requirement alternatives specified, then
+ // print the comment first word.
+ //
+ const auto& c (r.comment);
+ if (!c.empty ())
+ {
+ auto n (c.find (' '));
+ s << string (c, 0, n);
+
+ if (n != string::npos)
+ s << "...";
+ }
+ }
+ else
+ {
+ bool m (r.size () > 1);
- s << ~DIV;
+ if (m)
+ s << "(";
+
+ for (const auto& ra: r)
+ {
+ if (&ra != &r[0])
+ s << " | ";
+
+ s << ra;
+ }
+
+ if (m)
+ s << ")";
+ }
+ }
+
+ s << ~SPAN
+ << ~TD
+ << ~TR;
+ }
+
+ // TR_URL
+ //
+ void TR_URL::
+ operator() (serializer& s) const
+ {
+ s << TR(CLASS=label_)
+ << TH << label_ << ~TH
+ << TD
+ << SPAN(CLASS="value") << A(HREF=url_) << url_ << ~A << ~SPAN
+ << SPAN_COMMENT (url_.comment)
+ << ~TD
+ << ~TR;
+ }
+
+ // TR_EMAIL
+ //
+ void TR_EMAIL::
+ operator() (serializer& s) const
+ {
+ s << TR(CLASS=label_)
+ << TH << label_ << ~TH
+ << TD
+ << SPAN(CLASS="value")
+ << A << HREF << "mailto:" << email_ << ~HREF << email_ << ~A
+ << ~SPAN
+ << SPAN_COMMENT (email_.comment)
+ << ~TD
+ << ~TR;
}
- // DIV_EMAIL
+ // TR_PRIORITY
//
- void DIV_EMAIL::
+ void TR_PRIORITY::
operator() (serializer& s) const
{
- s << DIV(CLASS="email")
- << A << HREF << "mailto:" << email_ << ~HREF << email_ << ~A;
+ static const strings priority_names ({"low", "medium", "high", "security"});
+ assert (priority_ < priority_names.size ());
- if (!email_.comment.empty ())
- s << DIV(CLASS="comment") << email_.comment << ~DIV;
+ s << TR(CLASS="priority")
+ << TH << "priority" << ~TH
+ << TD
+ << SPAN(CLASS="value") << priority_names[priority_] << ~SPAN
+ << SPAN_COMMENT (priority_.comment)
+ << ~TD
+ << ~TR;
+ }
- s << ~DIV;
+ // TR_LOCATION
+ //
+ void TR_LOCATION::
+ operator() (serializer& s) const
+ {
+ s << TR(CLASS="location")
+ << TH << "location" << ~TH
+ << TD << SPAN(CLASS="value") << location_ << ~SPAN << ~TD
+ << ~TR;
}
- // DIV_TAGS
+ // TR_DOWNLOAD
//
- void DIV_TAGS::
+ void TR_DOWNLOAD::
operator() (serializer& s) const
{
- if (!tags_.empty ())
+ s << TR(CLASS="download")
+ << TH << "download" << ~TH
+ << TD
+ << SPAN(CLASS="value") << A(HREF=url_) << url_ << ~A << ~SPAN
+ << ~TD
+ << ~TR;
+ }
+
+ // SPAN_COMMENT
+ //
+ void SPAN_COMMENT::
+ operator() (serializer& s) const
+ {
+ if (size_t l = comment_.size ())
+ s << SPAN(CLASS="comment")
+ << (comment_[l - 1] == '.' ? string (comment_, 0, l - 1) : comment_)
+ << ~SPAN;
+ }
+
+ // P_DESCRIPTION
+ //
+ void P_DESCRIPTION::
+ operator() (serializer& s) const
+ {
+ if (description_.empty ())
+ return;
+
+ string::size_type n (description_.find_first_of (" \t\n", length_));
+ bool f (n == string::npos); // Description length is below the limit.
+
+ // Truncate description if length exceed the limit.
+ //
+ const string& d (f ? description_ : string (description_, 0, n));
+
+ // Format the description into paragraphs, recognizing a blank line as
+ // paragraph separator, and replacing single newlines with a space.
+ //
+ s << P(ID="description");
+
+ bool nl (false); // The previous character is '\n'.
+ for (const auto& c: d)
{
- s << DIV(CLASS="tags")
- << "Tags: ";
+ if (c == '\n')
+ {
+ if (nl)
+ {
+ s << ~P << P;
+ nl = false;
+ }
+ else
+ nl = true; // Delay printing until the next character.
+ }
+ else
+ {
+ if (nl)
+ {
+ s << ' '; // Replace the previous newline with a space.
+ nl = false;
+ }
- for (const auto& t: tags_)
- s << t << " ";
+ s << c;
+ }
+ }
- s << ~DIV;
+ if (!f)
+ {
+ assert (url_ != nullptr);
+ s << "... " << A(HREF=*url_) << "More" << ~A;
}
+
+ s << ~P;
}
- // DIV_PRIORITY
+ // PRE_CHANGES
//
- void DIV_PRIORITY::
+ void PRE_CHANGES::
operator() (serializer& s) const
{
- static const strings priority_names (
- {"low", "medium", "high", "security"});
+ if (changes_.empty ())
+ return;
- assert (priority_ < priority_names.size ());
+ string::size_type n (changes_.find_first_of (" \t\n", length_));
+ bool f (n == string::npos); // Changes length is below the limit.
- s << DIV(CLASS="priority")
- << "Priority: " << priority_names[priority_]
- << ~DIV;
+ // Truncate changes if length exceed the limit.
+ //
+ const string& c (f ? changes_ : string (changes_, 0, n));
+ s << PRE(ID="changes") << c;
+
+ if (!f)
+ {
+ assert (url_ != nullptr);
+ s << "... " << A(HREF=*url_) << "More" << ~A;
+ }
+
+ s << ~PRE;
}
- // FORM_SEARCH
+ // DIV_PAGER
//
- void FORM_SEARCH::
+ DIV_PAGER::
+ DIV_PAGER (size_t current_page,
+ size_t item_count,
+ size_t item_per_page,
+ size_t page_number_count,
+ const string& url)
+ : current_page_ (current_page),
+ item_count_ (item_count),
+ item_per_page_ (item_per_page),
+ page_number_count_ (page_number_count),
+ url_ (url)
+ {
+ }
+
+ void DIV_PAGER::
operator() (serializer& s) const
{
- s << FORM
- << *INPUT(TYPE="search", NAME="q", VALUE=query_)
- << *INPUT(TYPE="submit", VALUE="Search")
- << ~FORM;
+ if (item_count_ == 0 || item_per_page_ == 0)
+ return;
+
+ size_t pc (item_count_ / item_per_page_); // Page count.
+
+ if (item_count_ % item_per_page_)
+ ++pc;
+
+ if (pc > 1)
+ {
+ auto u (
+ [this](size_t page) -> string
+ {
+ return page == 0
+ ? url_
+ : url_ + (url_.find ('?') == string::npos ? "?p=" : "&p=") +
+ to_string (page);
+ });
+
+ s << DIV(ID="pager");
+
+ if (current_page_ > 0)
+ s << A(ID="prev", HREF=u (current_page_ - 1)) << "Prev" << ~A;
+
+ if (page_number_count_)
+ {
+ size_t offset (page_number_count_ / 2);
+ size_t fp (current_page_ > offset ? current_page_ - offset : 0);
+ size_t tp (min (fp + page_number_count_, pc));
+
+ for (size_t p (fp); p < tp; ++p)
+ {
+ s << A(HREF=u (p));
+
+ if (p == current_page_)
+ s << ID << "curr" << ~ID;
+
+ s << p + 1
+ << ~A;
+ }
+ }
+
+ if (current_page_ < pc - 1)
+ s << A(ID="next", HREF=u (current_page_ + 1)) << "Next" << ~A;
+
+ s << ~DIV;
+ }
}
}
diff --git a/brep/services.cxx b/brep/services.cxx
index c022a7d..eb99f3d 100644
--- a/brep/services.cxx
+++ b/brep/services.cxx
@@ -7,7 +7,7 @@
#include <web/apache/service>
#include <brep/package-search>
-#include <brep/package-version-search>
+#include <brep/package-details>
#include <brep/package-version-details>
using namespace brep;
@@ -19,10 +19,10 @@ service AP_MODULE_DECLARE_DATA package_search_srv (
package_search_mod,
{"db-host", "db-port", "conf"});
-static package_version_search package_version_search_mod;
-service AP_MODULE_DECLARE_DATA package_version_search_srv (
- "package-version-search",
- package_version_search_mod,
+static package_details package_details_mod;
+service AP_MODULE_DECLARE_DATA package_details_srv (
+ "package-details",
+ package_details_mod,
{"db-host", "db-port", "conf"});
static package_version_details package_version_details_mod;
diff --git a/build.sh b/build.sh
index 8a88216..77437db 100755
--- a/build.sh
+++ b/build.sh
@@ -32,7 +32,7 @@ cli --include-with-brackets --include-prefix brep --hxx-suffix "" \
echo "g++ libbrep-apache.so"
-s="package-search.cxx package-version-search.cxx package-version-details.cxx \
+s="package-search.cxx package-details.cxx package-version-details.cxx \
module.cxx diagnostics.cxx page.cxx services.cxx options.cxx \
shared-database.cxx \
../web/apache/request.cxx ../web/apache/service.cxx \
diff --git a/etc/httpd.conf b/etc/httpd.conf
index bf1d9e2..33658a8 100644
--- a/etc/httpd.conf
+++ b/etc/httpd.conf
@@ -5,7 +5,7 @@ ServerAdmin "${AP_ADMIN_EMAIL}"
User apache
Group apache
-DocumentRoot "${AP_WWW_DIR}/htdocs"
+DocumentRoot "${AP_WWW_DIR}"
CoreDumpDirectory "${AP_WORKSPACE_DIR}"
PidFile "${AP_WORKSPACE_DIR}/httpd.pid"
@@ -48,12 +48,12 @@ LoadModule package_search_srv ${AP_MODULE_DIR}/libbrep-apache.so
package-search-conf "${AP_CONFIG_DIR}/package-search.conf"
</IfModule>
-LoadModule package_version_search_srv ${AP_MODULE_DIR}/libbrep-apache.so
+LoadModule package_details_srv ${AP_MODULE_DIR}/libbrep-apache.so
-<IfModule package_version_search_srv>
- package-version-search-db-host ${AP_DB_HOST}
- package-version-search-db-port ${AP_DB_PORT}
- package-version-search-conf "${AP_CONFIG_DIR}/package-version-search.conf"
+<IfModule package_details_srv>
+ package-details-db-host ${AP_DB_HOST}
+ package-details-db-port ${AP_DB_PORT}
+ package-details-conf "${AP_CONFIG_DIR}/package-details.conf"
</IfModule>
LoadModule package_version_details_srv ${AP_MODULE_DIR}/libbrep-apache.so
@@ -69,7 +69,7 @@ LoadModule package_version_details_srv ${AP_MODULE_DIR}/libbrep-apache.so
</LocationMatch>
<LocationMatch ^/go/[^/]+$>
- SetHandler package-version-search
+ SetHandler package-details
</LocationMatch>
<LocationMatch ^/go/[^/]+/[^/]+$>
diff --git a/etc/package-details.conf b/etc/package-details.conf
new file mode 100644
index 0000000..49b3dce
--- /dev/null
+++ b/etc/package-details.conf
@@ -0,0 +1,15 @@
+# file : etc/package-details.conf
+# copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+#
+# brep::module options
+#
+verb 1
+
+# brep::package_details options
+#
+# @@ Set to 10
+results-on-page 2
+pages-in-pager 5
+# @@ Set to 500 (~ 80 chars x 6 lines)
+description-length 100
diff --git a/etc/package-search.conf b/etc/package-search.conf
index f7eb07c..80562b2 100644
--- a/etc/package-search.conf
+++ b/etc/package-search.conf
@@ -8,4 +8,6 @@ verb 1
# brep::package_search options
#
+# @@ Set to 10
results-on-page 2
+pages-in-pager 5
diff --git a/etc/package-version-details.conf b/etc/package-version-details.conf
index 61a6fb9..84fd75d 100644
--- a/etc/package-version-details.conf
+++ b/etc/package-version-details.conf
@@ -5,3 +5,10 @@
# brep::module options
#
verb 1
+
+# brep::package_version_details options
+#
+# @@ Set to 500 (~ 80 chars x 6 lines)
+description-length 100
+# @@ Set to 5000 (~ 80 chars x 60 lines)
+changes-length 100
diff --git a/etc/package-version-search.conf b/etc/package-version-search.conf
deleted file mode 100644
index f7f0d37..0000000
--- a/etc/package-version-search.conf
+++ /dev/null
@@ -1,12 +0,0 @@
-# file : etc/package-version-search.conf
-# copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
-# license : MIT; see accompanying LICENSE file
-#
-# brep::module options
-#
-verb 1
-
-# brep::package_version_search options
-#
-results-on-page 2
-pages-in-pager 10
diff --git a/tests/loader/driver.cxx b/tests/loader/driver.cxx
index 6d9b950..f1e1385 100644
--- a/tests/loader/driver.cxx
+++ b/tests/loader/driver.cxx
@@ -320,9 +320,11 @@ main (int argc, char* argv[])
assert (fpv5->summary == "The Foo Math Library");
assert (fpv5->tags == strings ({"c++", "foo", "math"}));
assert (*fpv5->description ==
- "A modern C++ library with easy to use linear algebra and "
- "optimization tools. There are over 100 functions in total with "
- "an extensive test suite. The API is similar to MATLAB.");
+ "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.");
assert (fpv5->url == "http://www.example.com/foo/");
assert (fpv5->package_url &&
*fpv5->package_url == "http://www.example.com/foo/pack");
@@ -335,7 +337,8 @@ main (int argc, char* argv[])
assert (fpv5->external_repositories[0].load () == cr);
assert (fpv5->priority == priority::high);
- assert (fpv5->priority.comment == "Due to critical bug fix.");
+ assert (fpv5->priority.comment ==
+ "Critical bug fixes, performance improvement.");
const char ch[] = R"DLM(1.2.4-1
* applied patch for critical bug-219
@@ -360,7 +363,7 @@ main (int argc, char* argv[])
assert (fpv5->dependencies.size () == 2);
assert (fpv5->dependencies[0].size () == 2);
assert (fpv5->dependencies[0].comment ==
- "Crashes in range [1.1, 2.3.0].");
+ "Crashes with 1.1.0-2.3.0.");
assert (fpv5->dependencies[0][0] ==
(dependency {
@@ -374,31 +377,31 @@ main (int argc, char* argv[])
brep::optional<dependency_constraint> (
dependency_constraint{comparison::gt, version ("2.3.0")})}));
- assert (fpv5->dependencies[1].size () == 1);
- assert (fpv5->dependencies[1].comment == "Newer - better.");
+ assert (fpv5->dependencies[1].size () == 2);
+ assert (fpv5->dependencies[1].comment == "The newer the better.");
- assert (fpv5->dependencies[1][0] ==
- (dependency {"libstudxml", nullopt}));
+ assert (fpv5->dependencies[1][0] == (dependency {"libstudxml", nullopt}));
+ assert (fpv5->dependencies[1][1] == (dependency {"libexpat", nullopt}));
requirements& fpvr5 (fpv5->requirements);
assert (fpvr5.size () == 4);
assert (fpvr5[0] == strings ({"linux", "windows", "macosx"}));
assert (!fpvr5[0].conditional);
- assert (fpvr5[0].comment == "Symbian is coming.");
+ assert (fpvr5[0].comment == "Symbian support is coming.");
assert (fpvr5[1] == strings ({"c++11"}));
assert (!fpvr5[1].conditional);
assert (fpvr5[1].comment.empty ());
- assert (fpvr5[2] == strings ({"VC++"}));
+ assert (fpvr5[2].empty ());
assert (fpvr5[2].conditional);
- assert (fpvr5[2].comment == "12.0 or later if targeting Windows.");
+ assert (fpvr5[2].comment ==
+ "libc++ standard library if using Clang on Mac OS X.");
- assert (fpvr5[3].empty ());
+ assert (fpvr5[3] == strings ({"vc++ >= 12.0"}));
assert (fpvr5[3].conditional);
- assert (fpvr5[3].comment ==
- "libc++ standard library if using Clang on Mac OS X.");
+ assert (fpvr5[3].comment == "Only if using VC++ on Windows.");
// Verify libexp package version.
//
diff --git a/tests/loader/internal/1/math/packages b/tests/loader/internal/1/math/packages
index 98a21d2..d55a9e3 100644
--- a/tests/loader/internal/1/math/packages
+++ b/tests/loader/internal/1/math/packages
@@ -13,23 +13,29 @@ location: libexp-1+1.2.tar.gz
name: libfoo
version: 1.2.4-1
summary: The Foo Math Library
-description: A modern C++ library with easy to use linear algebra and \
-optimization tools. There are over 100 functions in total with an extensive \
-test suite. The API is similar to MATLAB.
+description:\
+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.
+
+Useful for conversion of research code into production environments.
+\
license: LGPLv2, MIT; If using with GNU TLS.
license: BSD; If using with OpenSSL.
-priority: high; Due to critical bug fix.
+priority: high; Critical bug fixes, performance improvement.
tags: c++, foo, math
url: http://www.example.com/foo/; Project home page.
-email: foo-users@example.com; Public mailing list.
+email: foo-users@example.com; Public mailing list. Read FAQ before posting.
package-url: http://www.example.com/foo/pack; Package details.
package-email: pack@example.com; Current packager.
-depends: libmisc < 1.1 | libmisc > 2.3.0; Crashes in range [1.1, 2.3.0].
-depends: ? libstudxml; Newer - better.
-requires: linux | windows | macosx; Symbian is coming.
+depends: libmisc < 1.1 | libmisc > 2.3.0; Crashes with 1.1.0-2.3.0.
+depends: ? libstudxml | libexpat; The newer the better.
+requires: linux | windows | macosx; Symbian support is coming.
requires: c++11
-requires: ? VC++; 12.0 or later if targeting Windows.
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
changes:\
1.2.4-1
diff --git a/tests/web/xhtml/test.out b/tests/web/xhtml/test.out
index ffa6df6..a590e1f 100644
--- a/tests/web/xhtml/test.out
+++ b/tests/web/xhtml/test.out
@@ -2,6 +2,7 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8"/>
+ <meta name="viewport" content="device-width, initial-scale=1"/>
<title>Example XHTML5 document</title>
</head>
<body>
diff --git a/web/apache/request.cxx b/web/apache/request.cxx
index 497d2d6..9887104 100644
--- a/web/apache/request.cxx
+++ b/web/apache/request.cxx
@@ -99,33 +99,31 @@ namespace web
{
if (::strcasecmp (h->key, "Cookie") == 0)
{
- for (const char* n (h->val); n != 0; )
+ for (const char* n (h->val); n != nullptr; )
{
const char* v (strchr (n, '='));
const char* e (strchr (n, ';'));
- if (e && e < v)
- v = 0;
+ if (e != nullptr && e < v)
+ v = nullptr;
- string name (v
+ string name (v != nullptr
? mime_url_decode (n, v, true)
: (e
? mime_url_decode (n, e, true)
: mime_url_decode (n, n + strlen (n), true)));
- string value;
+ optional<string> value;
if (v++)
- {
value = e
? mime_url_decode (v, e, true)
: mime_url_decode (v, v + strlen (v), true);
- }
- if (!name.empty () || !value.empty ())
+ if (!name.empty () || value)
cookies_->emplace_back (move (name), move (value));
- n = e ? e + 1 : 0;
+ n = e ? e + 1 : nullptr;
}
}
}
@@ -252,33 +250,31 @@ namespace web
void request::
parse_parameters (const char* args)
{
- for (auto n (args); n != 0; )
+ for (auto n (args); n != nullptr; )
{
const char* v (strchr (n, '='));
const char* e (strchr (n, '&'));
- if (e && e < v)
- v = 0;
+ if (e != nullptr && e < v)
+ v = nullptr;
- string name (v
+ string name (v != nullptr
? mime_url_decode (n, v) :
(e
? mime_url_decode (n, e)
: mime_url_decode (n, n + strlen (n))));
- string value;
+ optional<string> value;
if (v++)
- {
value = e
? mime_url_decode (v, e)
: mime_url_decode (v, v + strlen (v));
- }
- if (!name.empty () || !value.empty ())
+ if (!name.empty () || value)
parameters_->emplace_back (move (name), move (value));
- n = e ? e + 1 : 0;
+ n = e ? e + 1 : nullptr;
}
}
}
diff --git a/web/apache/service.cxx b/web/apache/service.cxx
index 69bb874..782e09b 100644
--- a/web/apache/service.cxx
+++ b/web/apache/service.cxx
@@ -70,7 +70,7 @@ namespace web
return 0;
}
- srv.options_.emplace_back (name, value);
+ srv.options_.emplace_back (name, string (value));
return 0;
}
diff --git a/web/module b/web/module
index 7398d45..25c4bf2 100644
--- a/web/module
+++ b/web/module
@@ -14,6 +14,7 @@
#include <stdexcept> // runtime_error
#include <butl/path>
+#include <butl/optional>
namespace web
{
@@ -57,15 +58,18 @@ namespace web
sequence_error (std::string d): std::runtime_error (std::move (d)) {}
};
+ template <typename T>
+ using optional = butl::optional<T>;
+
struct name_value
{
// These should eventually become string_view's.
//
std::string name;
- std::string value;
+ optional<std::string> value;
name_value () {}
- name_value (std::string n, std::string v)
+ name_value (std::string n, optional<std::string> v)
: name (std::move (n)), value (std::move (v)) {}
};
diff --git a/web/xhtml b/web/xhtml
index b4c1a0d..7446a27 100644
--- a/web/xhtml
+++ b/web/xhtml
@@ -309,10 +309,16 @@ namespace web
static const element H5 ("h5");
static const element H6 ("h6");
static const element LI ("li");
+ static const element LINK ("link");
static const element META ("meta");
static const element P ("p");
static const element PRE ("pre");
+ static const element TABLE ("table");
+ static const element TBODY ("tbody");
+ static const element TD ("td");
+ static const element TH ("th");
static const element TITLE ("title");
+ static const element TR ("tr");
static const element UL ("ul");
static const inline_element A ("a");
@@ -326,14 +332,17 @@ namespace web
// Attributes.
//
- static const attribute CLASS ("class");
- static const attribute CONTENT ("content");
- static const attribute HREF ("href");
- static const attribute ID ("id");
- static const attribute NAME ("name");
- static const attribute STYLE ("style");
- static const attribute TYPE ("type");
- static const attribute VALUE ("value");
+
+ static const attribute AUTOFOCUS ("autofocus");
+ static const attribute CLASS ("class");
+ static const attribute CONTENT ("content");
+ static const attribute HREF ("href");
+ static const attribute ID ("id");
+ static const attribute NAME ("name");
+ static const attribute REL ("rel");
+ static const attribute STYLE ("style");
+ static const attribute TYPE ("type");
+ static const attribute VALUE ("value");
}
}
diff --git a/www/common.css b/www/common.css
new file mode 100644
index 0000000..f1023df
--- /dev/null
+++ b/www/common.css
@@ -0,0 +1,317 @@
+/*
+Nexus 5 360 598
+Nexus 6 412 690
+iPhone 5 320 568
+iPhone 6 375 667
+iPhone 6+ 414 736
+Galaxy 5 360 640
+Galaxy 6 360 640
+
+iPad 768 1024
+Galaxy 2,3 10" 800 1280
+Galaxy 2 7" 600 1024
+
+320-359 small phone portrait
+360-567 big phone portrate
+568-1023 phone landscape, tablets portrate
+1024- tablets landscape, monitor
+*/
+
+html
+{
+ font-family: sans-serif;
+ font-weight: normal;
+ font-size: 18px;
+ line-height: 1.4em;
+}
+
+input
+{
+ font-family: inherit;
+ font-weight: inherit;
+ font-size: inherit;
+ line-height: inherit;
+}
+
+body
+{
+ margin: 0; /* There is non-0 default margin on body. */
+}
+
+/* 320px ~ 20em @ 1 font-size. This is factored in when doing font
+ boosting. Specifying 320px directly seems to mess it up.
+
+ Font boosting results for font-size:1.1em, min-width:18em on Nexus 5:
+
+ - about 40 characters in portrait
+ - about 70 characters in landscape
+ - landscape font appears slightly smaller
+
+ 18em on smaller phones (e.g., iPhone 4) appears to trigger scrolling.
+ 17em works well, however.
+*/
+
+body {min-width: 17em;}
+
+@media only screen and (min-width: 360px)
+{
+ body {min-width: 19em;}
+}
+
+/*
+ * Header.
+ */
+#header
+{
+ width: 100%;
+
+ background: rgba(0, 0, 0, 0.7);
+ border-bottom: 1px solid rgba(255, 255, 255, 0.26); /* Off background. */
+
+ padding: .3em 0 .3em 0;
+ margin: 0 0 1.4em 0;
+}
+
+#header-menu
+{
+ /* Same as in #content below. */
+ max-width: 40em;
+ margin: 0 auto 0 auto;
+ padding: 0 .4em 0 .4em;
+
+ text-align: right;
+}
+
+#header-menu a:nth-child(n + 2) {padding-left: 1.2em;}
+
+#header-menu a {font-size: 1.12em; color: #eee; text-decoration: none;}
+#header-menu a:visited {color: #eee;}
+#header-menu a:hover, #header-menu a:active {color: #fff; text-decoration: underline;}
+
+
+/*
+ * Content.
+ */
+#content
+{
+ max-width: 40em;
+ margin: 0 auto 0 auto;
+ padding: 0 .4em 0 .4em; /* Space between text and browser frame. */
+}
+
+/*
+ * Footer.
+ */
+#footer
+{
+ text-align: center;
+}
+
+/* Until we have actual content in the footer. */
+#footer:before {content: "\A0";}
+#footer:after {content: "\A0";}
+
+/* Screen size indicator in the footer. */
+#footer
+{
+ border-left: 1px solid;
+ border-right: 1px solid;
+ margin: 0 1px 0 1px; /* To see the border. */
+}
+
+@media only screen and (max-width: 359px)
+{
+ #footer {border-color: red;}
+}
+
+@media only screen and (min-width: 360px) and (max-width: 567px)
+{
+ #footer {border-color: orange;}
+}
+
+@media only screen and (min-width: 568px) and (max-width: 1023px)
+{
+ #footer {border-color: blue;}
+}
+
+@media only screen and (min-width: 1024px)
+{
+ #footer {border-color: green;}
+}
+
+/*
+ * Common elements.
+ */
+p {text-align: justify;}
+
+code, pre
+{
+ font-size: 0.94em;
+}
+
+a {color: #006fbf; text-decoration: none;}
+a:hover, a:active {color: #0087e7; text-decoration: underline;}
+a:visited {color: #003388;}
+
+/*
+ * Form layout table.
+ */
+.form-table
+{
+ width: 100%;
+ border-collapse: collapse;
+ margin-top: 1.5em;
+}
+
+.form-table table, .form-table th, .form-table td
+{
+ border: none;
+ padding: 0;
+}
+
+/*
+ * Property list table.
+ */
+.proplist
+{
+ /* Extend the table into #content's margins that are used to separate
+ from the browser frame. If this table gets background/stripe, then
+ we want it to cover that extra space so that we get a margin between
+ the text and the background edge. This also means that the background
+ will touch the browser frame. This looks ok as long as we don't use
+ fancy things like rounded corners. */
+
+ width: calc(100% + .8rem); /* Fill the page plus #content margin. */
+ padding-right: .4rem;
+ padding-left: .4rem;
+ margin-left: -.4rem;
+
+ table-layout: fixed;
+
+ border: none;
+ border-spacing: 0 0;
+}
+
+.proplist th, .proplist td {padding: .08em 0 .08em 0;}
+
+.proplist th
+{
+ font-family: monospace;
+ font-weight: normal;
+ text-align: left;
+}
+.proplist th:after {content: ":";}
+
+.proplist td .comment
+{
+ color: #666;
+ font-size: 0.833em;
+}
+
+/* Flexbox-based left/right aligned value/comment implementation. */
+.proplist td
+{
+ width: 100%;
+ white-space: nowrap;
+
+ display: -webkit-inline-flex;
+ display: inline-flex;
+
+ -webkit-justify-content: space-between;
+ justify-content: space-between;
+}
+
+.proplist td .value
+{
+ display: inline-block;
+
+ -webkit-flex-shrink: 1;
+ flex-shrink: 1;
+
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.proplist td .comment
+{
+ display: inline-block;
+
+ margin-left: 1em;
+
+ -webkit-flex-shrink: 100000;
+ flex-shrink: 100000;
+
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+/* Fallback for browsers that still don't support flexbox. */
+
+.proplist td
+{
+ text-align: right;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.proplist td .value
+{
+ float: left;
+ text-align: left;
+}
+
+/* Re-styling for full page variant. */
+
+.full .proplist td
+{
+ flex-wrap: wrap;
+ -webkit-flex-wrap: wrap;
+
+ white-space: normal;
+}
+
+.full .proplist th
+{
+ vertical-align: top;
+}
+
+.full .proplist td .value
+{
+ margin-right: 1em;
+
+ white-space: normal;
+}
+
+.full .proplist td .comment
+{
+ margin-left: 0;
+
+ text-align: left;
+ white-space: normal;
+}
+
+/*
+ * Pager.
+ */
+#pager
+{
+ margin-top: 1.8em;
+
+ font-size: 0.916em;
+ text-align: center;
+}
+
+#pager a
+{
+ padding: 0 0.4em 0 0.4em;
+}
+
+#pager #prev:before {content: "<\A0";}
+
+#pager #curr {font-weight: bold;}
+#pager #curr:before {content: "["; font-weight: normal;}
+#pager #curr:after {content: "]"; font-weight: normal;}
+
+#pager #next:after {content: "\A0>";}
diff --git a/www/package-details.css b/www/package-details.css
new file mode 100644
index 0000000..941f97e
--- /dev/null
+++ b/www/package-details.css
@@ -0,0 +1,107 @@
+#heading
+{
+ display: table;
+ table-layout: fixed;
+ width: 100%;
+ border: none;
+}
+
+/* Since it is a link to itself, it will always be visited. */
+#heading a:visited {color: #006fbf;}
+#heading a:hover, h1 a:active {color: #0087e7; text-decoration: none;}
+
+h1
+{
+ font-family: monospace;
+ font-weight: normal;
+ font-size: 2.074em;
+ line-height: 1.4em;
+
+ margin: .6em 0 .6em 0;
+
+ display: table-cell;
+ text-align: left;
+}
+
+#heading > a
+{
+ font-size: 1.32em;
+ line-height: 1.4em;
+
+ display: table-cell;
+ text-align: right;
+ width: 3.2em;
+ vertical-align: middle;
+}
+
+h2
+{
+ font-style: italic;
+ font-weight: normal;
+ font-size: 1.32em;
+ line-height: 1.4em;
+
+ margin: .4em 0 .4em 0;
+}
+
+h1, h2
+{
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+/*
+ * Package details table.
+ */
+#package
+{
+ margin-top: 1.5em;
+ margin-bottom: 1.5em;
+}
+#package th {width: 6.4em;}
+
+/*
+ * Search form (based on form-table)
+ */
+#search-txt, #search-txt input {width: 100%;}
+#search-btn {padding-left: .4em;}
+
+/*
+ * Version count.
+ */
+#count
+{
+ font-size: 1.32em;
+ line-height: 1.4em;
+ color: #555;
+
+ margin: 1.2em 0 0 0;
+}
+
+/*
+ * Version table.
+ */
+table.version
+{
+ margin-top: .8em;
+ margin-bottom: .8em;
+
+ padding-top: .4em;
+ padding-bottom: .4em;
+
+}
+table.version:nth-child(even) {background-color: rgba(0, 0, 0, 0.07);}
+
+table.version th {width: 6.4em;}
+
+table.version tr.version td .value,
+table.version tr.priority td .value,
+table.version tr.location td .value,
+table.version tr.depends td .value,
+table.version tr.requires td .value
+{
+ /* <code> style. */
+ font-family: monospace;
+ font-size: 0.94em;
+}
diff --git a/www/package-search.css b/www/package-search.css
new file mode 100644
index 0000000..ef8b59c
--- /dev/null
+++ b/www/package-search.css
@@ -0,0 +1,41 @@
+/*
+ * Search form (based on form-table)
+ */
+#search-txt, #search-txt input {width: 100%;}
+#search-btn {padding-left: .4em;}
+
+/*
+ * Package count.
+ */
+#count
+{
+ font-size: 1.32em;
+ line-height: 1.4em;
+ color: #555;
+
+ margin: 1.2em 0 0 0;
+}
+
+/*
+ * Version table.
+ */
+.package
+{
+ margin-top: .8em;
+ margin-bottom: .8em;
+
+ padding-top: .4em;
+ padding-bottom: .4em;
+}
+.package:nth-child(even) {background-color: rgba(0, 0, 0, 0.07);}
+
+.package th {width: 6.4em;}
+
+.package tr.name td .value,
+.package tr.depends td .value,
+.package tr.requires td .value
+{
+ /* <code> style. */
+ font-family: monospace;
+ font-size: 0.94em;
+}
diff --git a/www/package-version-details.css b/www/package-version-details.css
new file mode 100644
index 0000000..a701a5e
--- /dev/null
+++ b/www/package-version-details.css
@@ -0,0 +1,161 @@
+#heading
+{
+ display: table;
+ table-layout: fixed;
+ width: 100%;
+ border: none;
+}
+
+/* Since it is a link to itself, it will always be visited. */
+#heading a:visited {color: #006fbf;}
+#heading a:hover, h1 a:active {color: #0087e7; text-decoration: none;}
+
+h1
+{
+ font-family: monospace;
+ font-weight: normal;
+ font-size: 2.074em;
+ line-height: 1.4em;
+ color: #444;
+
+ margin: .6em 0 .6em 0;
+
+ display: table-cell;
+ text-align: left;
+}
+
+#heading > a
+{
+ font-size: 1.32em;
+ line-height: 1.4em;
+
+ display: table-cell;
+ text-align: right;
+ width: 3.2em;
+ vertical-align: middle;
+}
+
+h1 a:first-child {margin-right: .14em;}
+h1 a:last-child {margin-left: .14em;}
+
+h2
+{
+ font-style: italic;
+ font-weight: normal;
+ font-size: 1.32em;
+ line-height: 1.4em;
+
+ margin: .4em 0 .4em 0;
+}
+
+h3
+{
+ font-family: monospace;
+ font-weight: normal;
+ font-size: 1.26em;
+ line-height: 1.4em;
+
+ margin: 1.8em 0 0 0;
+}
+h3:after{content: ":";}
+
+h1, h2, h3
+{
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+/*
+ * Version details table.
+ */
+#version
+{
+ margin-top: .5em;
+ margin-bottom: 1em;
+
+ padding-top: .4em;
+ padding-bottom: .4em;
+
+ background-color: rgba(0, 0, 0, 0.07); /* Emphasize. */
+
+ /*
+ background: #eee;
+ border-radius: 6px;
+ border: 1px outset rgba(230,230,230,.24);
+ */
+}
+#version th {width: 6.4em;}
+
+#version tr.version td .value,
+#version tr.priority td .value,
+#version tr.location td .value
+{
+ /* <code> style. */
+ font-family: monospace;
+ font-size: 0.94em;
+}
+
+/*
+ * Package details table.
+ */
+#package
+{
+ margin-top: 1.2em;
+ margin-bottom: 1em;
+}
+#package th {width: 6.4em;}
+
+/*
+ * Dependencies and requirements tables.
+ */
+#depends {margin-top: .4em; margin-bottom: 1em;}
+#depends th {width: 2.8em; text-align: center;}
+#depends th:after{content: "";}
+
+/* Striping. For some reason (related to flexbox), this works exactly as
+ we want, that is, the background extends all the way to the browser's
+ right frame. */
+#depends tr:nth-child(even) td {background-color: rgba(0, 0, 0, 0.07);}
+#depends td {padding-left: .4em;}
+#depends td .comment {padding-right: .4em;}
+
+/*
+#depends td .value {padding-left: .4em;}
+#depends td .comment {padding-right: .4em;}
+*/
+
+#depends tr.depends td .value
+{
+ /* <code> style. */
+ font-family: monospace;
+ font-size: 0.94em;
+}
+
+#requires {margin-top: .4em; margin-bottom: 1em;}
+#requires th {width: 2.8em; text-align: center;}
+#requires th:after{content: "";}
+
+#requires tr:nth-child(even) td {background-color: rgba(0, 0, 0, 0.07);}
+#requires td {padding-left: .4em;}
+#requires td .comment {padding-right: .4em;}
+
+#requires tr.requires td .value
+{
+ /* <code> style. */
+ font-family: monospace;
+ font-size: 0.94em;
+}
+
+/*
+ * Changes.
+ *
+ * This is a <pre> block that fits lines up to 80 characters long and
+ * wraps longer ones.
+ */
+#changes
+{
+ font-size: 0.78em;
+ white-space: pre-wrap;
+ margin: .5em 0 .5em 0;
+}