From 6eca8a647c79e9a5b100672b55f5d02273a28772 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 16 Nov 2015 12:08:41 +0200 Subject: Implement 'about' web page --- brep/buildfile | 4 +- brep/options.cli | 4 ++ brep/package-details.cxx | 5 +- brep/package-search.cxx | 6 +- brep/package-version-details.cxx | 7 +-- brep/page | 22 ++++--- brep/page.cxx | 65 +++++++++++++++++-- brep/repository-details | 32 ++++++++++ brep/repository-details.cxx | 96 +++++++++++++++++++++++++++++ brep/services.cxx | 7 +++ build.sh | 4 +- etc/httpd.conf | 12 ++++ tests/loader/driver.cxx | 20 ++++++ tests/loader/internal/1/math/repositories | 6 ++ tests/loader/internal/1/stable/repositories | 3 + web/mime-url-encoding.cxx | 2 +- www/repository-details.css | 27 ++++++++ 17 files changed, 293 insertions(+), 29 deletions(-) create mode 100644 brep/repository-details create mode 100644 brep/repository-details.cxx create mode 100644 www/repository-details.css diff --git a/brep/buildfile b/brep/buildfile index 6676095..464118d 100644 --- a/brep/buildfile +++ b/brep/buildfile @@ -22,8 +22,8 @@ libso{brep}: cxx.export.poptions = -I$out_root -I$src_root import libs += libstudxml%lib{studxml} brep = cxx{diagnostics module services package-search package-details \ - package-version-details shared-database page types-parsers} \ - cli.cxx{options} + package-version-details repository-details shared-database page \ + types-parsers} cli.cxx{options} web = ../web/apache/cxx{request service} ../web/cxx{mime-url-encoding} diff --git a/brep/options.cli b/brep/options.cli index 6c3c7f1..77dc45a 100644 --- a/brep/options.cli +++ b/brep/options.cli @@ -43,6 +43,10 @@ namespace brep std::uint16_t description-length = 400; std::uint16_t changes-length = 800; }; + + class repository_details: module, db + { + }; } // Web module HTTP request parameters. diff --git a/brep/package-details.cxx b/brep/package-details.cxx index ef787b0..780d98a 100644 --- a/brep/package-details.cxx +++ b/brep/package-details.cxx @@ -61,7 +61,6 @@ namespace brep void package_details:: handle (request& rq, response& rs) { - using namespace xml; using namespace web; using namespace web::xhtml; @@ -108,7 +107,7 @@ namespace brep return u; }); - serializer s (rs.content (), name); + xml::serializer s (rs.content (), name); const string& title (sq.empty () ? name : name + " " + sq); static const path sp ("package-details.css"); @@ -219,7 +218,7 @@ namespace brep // // Hm, I am not so sure about this. Consider: stable/testing/unstable. // - s << TR_LOCATION (p->internal_repository.object_id ()) + s << TR_LOCATION (p->internal_repository.object_id (), rt) << TR_DEPENDS (p->dependencies, rt) << TR_REQUIRES (p->requirements) << ~TBODY diff --git a/brep/package-search.cxx b/brep/package-search.cxx index b0352f3..1617e58 100644 --- a/brep/package-search.cxx +++ b/brep/package-search.cxx @@ -57,8 +57,6 @@ namespace brep void package_search:: handle (request& rq, response& rs) { - using namespace xml; - using namespace web; using namespace web::xhtml; MODULE_DIAG; @@ -82,10 +80,10 @@ namespace brep } const string& sq (pr.query ()); // Search query. - string qp (sq.empty () ? "" : "q=" + mime_url_encode (sq)); + string qp (sq.empty () ? "" : "q=" + web::mime_url_encode (sq)); size_t pg (pr.page ()); - serializer s (rs.content (), "Packages"); + xml::serializer s (rs.content (), "Packages"); const string& title ( sq.empty () ? s.output_name () : s.output_name () + " " + sq); diff --git a/brep/package-version-details.cxx b/brep/package-version-details.cxx index b8f30ff..d00c10a 100644 --- a/brep/package-version-details.cxx +++ b/brep/package-version-details.cxx @@ -46,7 +46,6 @@ namespace brep void package_version_details:: handle (request& rq, response& rs) { - using namespace xml; using namespace web; using namespace web::xhtml; @@ -99,7 +98,7 @@ namespace brep return u; }); - serializer s (rs.content (), name); + xml::serializer s (rs.content (), name); static const path go ("go"); static const path sp ("package-version-details.css"); @@ -171,7 +170,7 @@ namespace brep << TR_PRIORITY (p->priority) << TR_LICENSES (p->license_alternatives) - << TR_LOCATION (p->internal_repository.object_id ()) + << TR_LOCATION (p->internal_repository.object_id (), rt) << TR_DOWNLOAD (du) << ~TBODY << ~TABLE @@ -249,7 +248,7 @@ namespace brep s << ' ' << A(HREF=u / path (p->version.string ())) << *dc << ~A; } else - // Display the dependency as a plain text in no repository URL + // Display the dependency as a plain text if no repository URL // available. // s << d; diff --git a/brep/page b/brep/page index c968389..0d0887f 100644 --- a/brep/page +++ b/brep/page @@ -22,8 +22,7 @@ namespace brep class CSS_LINKS { public: - CSS_LINKS (const path& p, const dir_path& r): - path_ (p), root_ (r) {} + CSS_LINKS (const path& p, const dir_path& r): path_ (p), root_ (r) {} void operator() (xml::serializer& s) const; @@ -263,13 +262,15 @@ namespace brep class TR_LOCATION { public: - TR_LOCATION (const std::string& l): location_ (l) {} + TR_LOCATION (const std::string& n, const dir_path& r) + : name_ (n), root_ (r) {} void operator() (xml::serializer& s) const; private: - const std::string& location_; + const std::string& name_; + const dir_path& root_; }; // Generates package download URL element. @@ -307,13 +308,13 @@ namespace brep public: // Genereate full description. // - P_DESCRIPTION (const std::string& d) - : description_ (d), length_ (d.size ()), url_ (nullptr) {} + P_DESCRIPTION (const std::string& d, bool u = true) + : description_ (d), length_ (d.size ()), url_ (nullptr), unique_ (u) {} // Genereate brief description. // P_DESCRIPTION (const std::string& d, size_t l, const std::string& u) - : description_ (d), length_ (l), url_ (&u) {} + : description_ (d), length_ (l), url_ (&u), unique_ (false) {} void operator() (xml::serializer& s) const; @@ -322,6 +323,7 @@ namespace brep const std::string& description_; std::size_t length_; const std::string* url_; // Full page url. + bool unique_; }; // Generates package description element. @@ -369,6 +371,12 @@ namespace brep std::size_t page_number_count_; const std::string& url_; }; + + // Convert the argument to a string representing the valid HTML 5 'id' + // attribute value. + // + std::string + id_attribute (const std::string& v); } #endif // BREP_PAGE diff --git a/brep/page.cxx b/brep/page.cxx index fbbcda8..5b650a3 100644 --- a/brep/page.cxx +++ b/brep/page.cxx @@ -5,10 +5,13 @@ #include #include +#include // hex, uppercase, right #include #include // shared_ptr #include // size_t #include +#include +#include // setw(), setfill() #include // min() #include @@ -27,6 +30,7 @@ using namespace web::xhtml; namespace brep { static const path go ("go"); + static const path about ("about"); // CSS_LINKS // @@ -44,12 +48,10 @@ namespace brep void DIV_HEADER:: operator() (serializer& s) const { - static const path a ("about"); - s << DIV(ID="header") << DIV(ID="header-menu") << A(HREF=root_) << "packages" << ~A - << A(HREF=root_ / a) << "about" << ~A + << A(HREF=root_ / about) << "about" << ~A << ~DIV << ~DIV; } @@ -305,7 +307,7 @@ namespace brep else if (p->internal ()) s << A(HREF=root_ / go / path (en)) << n << ~A; else - // Display the dependency as a plain text in no repository URL + // Display the dependency as a plain text if no repository URL // available. // s << n; @@ -440,7 +442,16 @@ namespace brep { s << TR(CLASS="location") << TH << "location" << ~TH - << TD << SPAN(CLASS="value") << location_ << ~SPAN << ~TD + << TD + << SPAN(CLASS="value") + << A + << HREF + << root_ / about << "#" << mime_url_encode (id_attribute (name_)) + << ~HREF + << name_ + << ~A + << ~SPAN + << ~TD << ~TR; } @@ -486,7 +497,10 @@ namespace brep // Format the description into paragraphs, recognizing a blank line as // paragraph separator, and replacing single newlines with a space. // - s << P(ID="description"); + s << P; + + if (unique_) + s << ID("description"); bool nl (false); // The previous character is '\n'. for (const auto& c: d) @@ -614,4 +628,43 @@ namespace brep s << ~DIV; } } + + // Convert the argument to a string conformant to the section + // "3.2.5.1 The id attribute" of the HTML 5 specification at + // http://www.w3.org/TR/html5/dom.html#the-id-attribute. + // + string + id_attribute (const string& v) + { + ostringstream o; + o << hex << uppercase << right << setfill ('0'); + + // Replace space characters (as specified at + // http://www.w3.org/TR/html5/infrastructure.html#space-character) with + // the respective escape sequences. + // + for (auto c: v) + { + switch (c) + { + case ' ': + case '\t': + case '\n': + case '\r': + case '\f': + case '~': + { + // Intentionally use '~' as an escape character to leave it unescaped + // being a part of URL. For example + // http://cppget.org/about#cppget.org%2Fmath~20lab + // + o << "~" << setw (2) << static_cast (c); + break; + } + default: o << c; break; + } + } + + return o.str (); + } } diff --git a/brep/repository-details b/brep/repository-details new file mode 100644 index 0000000..a3008dc --- /dev/null +++ b/brep/repository-details @@ -0,0 +1,32 @@ +// file : brep/repository-details -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BREP_REPOSITORY_DETAILS +#define BREP_REPOSITORY_DETAILS + +#include // shared_ptr + +#include // database + +#include +#include + +namespace brep +{ + class repository_details: public module + { + private: + virtual void + handle (request&, response&); + + virtual void + init (cli::scanner&); + + private: + std::shared_ptr options_; + std::shared_ptr db_; + }; +} + +#endif // BREP_REPOSITORY_DETAILS diff --git a/brep/repository-details.cxx b/brep/repository-details.cxx new file mode 100644 index 0000000..4302c15 --- /dev/null +++ b/brep/repository-details.cxx @@ -0,0 +1,96 @@ +// file : brep/repository-details.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include // make_shared() + +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace std; +using namespace odb::core; + +namespace brep +{ + using namespace cli; + + void repository_details:: + init (scanner& s) + { + MODULE_DIAG; + + options_ = make_shared ( + s, unknown_mode::fail, unknown_mode::fail); + + db_ = shared_database (options_->db_host (), options_->db_port ()); + } + + void repository_details:: + handle (request&, response& rs) + { + using namespace web::xhtml; + + MODULE_DIAG; + + // The module options object is not changed after being created once per + // server process. + // + static const dir_path& rt (options_->root ()); + + xml::serializer s (rs.content (), "About"); + const string& title (s.output_name ()); + static const path sp ("repository-details.css"); + + s << HTML + << HEAD + << TITLE << title << ~TITLE + << CSS_LINKS (sp, rt) + << ~HEAD + << BODY + << DIV_HEADER (rt) + << DIV(ID="content"); + + transaction t (db_->begin ()); + + using query = query; + auto rp (db_->query (query::internal + "ORDER BY name")); + + for (const auto& r: rp) + { + string id (id_attribute (r.name)); + s << H1(ID=id) + << A(HREF="#" + web::mime_url_encode (id)) << r.name << ~A + << ~H1; + + if (r.email) + s << A << HREF << "mailto:" << *r.email << ~HREF << *r.email << ~A; + + if (r.summary) + s << H2 << *r.summary << ~H2; + + if (r.description) + s << P_DESCRIPTION (*r.description, false); + } + + t.commit (); + + s << ~DIV + << ~BODY + << ~HTML; + } +} diff --git a/brep/services.cxx b/brep/services.cxx index 9b7043c..e41d3f3 100644 --- a/brep/services.cxx +++ b/brep/services.cxx @@ -8,6 +8,7 @@ #include #include +#include #include using namespace brep; @@ -30,3 +31,9 @@ service AP_MODULE_DECLARE_DATA package_version_details_srv ( "package-version-details", package_version_details_mod, {"root", "db-host", "db-port", "conf"}); + +static repository_details repository_details_mod; +service AP_MODULE_DECLARE_DATA repository_details_srv ( + "repository-details", + repository_details_mod, + {"root", "db-host", "db-port", "conf"}); diff --git a/build.sh b/build.sh index 9596589..0a7fb34 100755 --- a/build.sh +++ b/build.sh @@ -34,8 +34,8 @@ cli --include-with-brackets --include-prefix brep --hxx-suffix "" \ echo "g++ libbrep-apache.so" s="package-search.cxx package-details.cxx package-version-details.cxx \ -module.cxx diagnostics.cxx page.cxx services.cxx options.cxx \ -shared-database.cxx \ +repository-details.cxx module.cxx diagnostics.cxx page.cxx services.cxx \ +options.cxx shared-database.cxx \ ../web/apache/request.cxx ../web/apache/service.cxx \ ../web/mime-url-encoding.cxx" diff --git a/etc/httpd.conf b/etc/httpd.conf index c8405ed..7c25a6d 100644 --- a/etc/httpd.conf +++ b/etc/httpd.conf @@ -67,6 +67,14 @@ LoadModule package_version_details_srv ${AP_MODULE_DIR}/libbrep-apache.so package-version-details-conf ${AP_CONFIG_DIR}/package-version-details.conf +LoadModule repository_details_srv ${AP_MODULE_DIR}/libbrep-apache.so + + + repository-details-root ${AP_ROOT}/ + repository-details-db-host ${AP_DB_HOST} + repository-details-db-port ${AP_DB_PORT} + + SetHandler package-search @@ -79,6 +87,10 @@ LoadModule package_version_details_srv ${AP_MODULE_DIR}/libbrep-apache.so SetHandler package-version-details + + SetHandler repository-details + + AliasMatch ^${AP_ROOT}/(.+) ${AP_WWW_DIR}/$1 ExtendedStatus On diff --git a/tests/loader/driver.cxx b/tests/loader/driver.cxx index cf81dca..0d361ab 100644 --- a/tests/loader/driver.cxx +++ b/tests/loader/driver.cxx @@ -118,6 +118,12 @@ main (int argc, char* argv[]) "http://pkg.cppget.org/internal/1/stable"); assert (sr->display_name == "stable"); assert (!sr->url); + assert (sr->email && *sr->email == "repoman@cppget.org"); + assert (sr->summary && + *sr->summary == "General C++ package stable repository"); + assert (sr->description && *sr->description == + "This is the awesome C++ package repository full of exciting " + "stuff."); dir_path srp (cp.directory () / dir_path ("internal/1/stable")); assert (sr->local_path == srp.normalize ()); @@ -283,6 +289,11 @@ main (int argc, char* argv[]) "http://pkg.cppget.org/internal/1/math"); assert (mr->display_name == "math"); assert (!mr->url); + assert (mr->email && *mr->email == "repoman@cppget.org"); + assert (mr->summary && *mr->summary == "Math C++ package repository"); + assert (mr->description && *mr->description == + "This is the awesome C++ package repository full of remarkable " + "algorithms and\nAPIs."); dir_path mrp (cp.directory () / dir_path ("internal/1/math")); assert (mr->local_path == mrp.normalize ()); @@ -480,6 +491,9 @@ main (int argc, char* argv[]) "http://pkg.cppget.org/external/1/misc"); assert (cr->display_name.empty ()); assert (cr->url && *cr->url == "http://misc.cppget.org/"); + assert (!cr->email); + assert (!cr->summary); + assert (!cr->description); dir_path crp (cp.directory () / dir_path ("external/1/misc")); assert (cr->local_path == crp.normalize ()); @@ -535,6 +549,9 @@ main (int argc, char* argv[]) "http://pkg.cppget.org/external/1/testing"); assert (tr->display_name.empty ()); assert (tr->url && *tr->url == "http://test.cppget.org/hello/"); + assert (!tr->email); + assert (!tr->summary); + assert (!tr->description); dir_path trp (cp.directory () / dir_path ("external/1/testing")); assert (tr->local_path == trp.normalize ()); @@ -568,6 +585,9 @@ main (int argc, char* argv[]) "http://pkg.cppget.org/external/1/staging"); assert (gr->display_name.empty ()); assert (gr->url && *gr->url == "http://stage.cppget.org/"); + assert (!gr->email); + assert (!gr->summary); + assert (!gr->description); dir_path grp (cp.directory () / dir_path ("external/1/staging")); assert (gr->local_path == grp.normalize ()); diff --git a/tests/loader/internal/1/math/repositories b/tests/loader/internal/1/math/repositories index 814d1db..c2df027 100644 --- a/tests/loader/internal/1/math/repositories +++ b/tests/loader/internal/1/math/repositories @@ -5,3 +5,9 @@ location: ../../../external/1/misc : # Local repository manifest (this repository). # +email: repoman@cppget.org +summary: Math C++ package repository +description: \ +This is the awesome C++ package repository full of remarkable algorithms and +APIs. +\ diff --git a/tests/loader/internal/1/stable/repositories b/tests/loader/internal/1/stable/repositories index 38fdd72..c06f731 100644 --- a/tests/loader/internal/1/stable/repositories +++ b/tests/loader/internal/1/stable/repositories @@ -9,3 +9,6 @@ location: ../math : # Local repository manifest (this repository). # +email: repoman@cppget.org +summary: General C++ package stable repository +description: This is the awesome C++ package repository full of exciting stuff. diff --git a/web/mime-url-encoding.cxx b/web/mime-url-encoding.cxx index 1d68bf3..04c5a63 100644 --- a/web/mime-url-encoding.cxx +++ b/web/mime-url-encoding.cxx @@ -6,7 +6,7 @@ #include // hex, uppercase, right #include -#include // setw(), setfill () +#include // setw(), setfill() #include #include #include // size_t, strspn() diff --git a/www/repository-details.css b/www/repository-details.css new file mode 100644 index 0000000..3a57b8d --- /dev/null +++ b/www/repository-details.css @@ -0,0 +1,27 @@ +h1 +{ + font-family: monospace; + font-weight: normal; + font-size: 2.074em; + line-height: 1.4em; + + margin: .6em 0 .2em 0; + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Since it is a link to itself, it will always be visited. */ +h1 a:visited {color: #006fbf;} +h1 a:hover, h1 a:active {color: #0087e7; text-decoration: none;} + +h2 +{ + font-style: italic; + font-weight: normal; + font-size: 1.32em; + line-height: 1.4em; + + margin: .4em 0 .4em 0; +} -- cgit v1.1