From 75825230abcc5f303a29f1f74b29c36e6140a064 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Sun, 13 Sep 2015 22:46:48 +0200 Subject: Implement package details page --- brep/buildfile | 2 +- brep/options.cli | 1 + brep/package | 7 ++ brep/package-version-search.cxx | 162 +++++++++++++++++++++++++++++++- brep/page | 52 ++++++++++ brep/page.cxx | 103 ++++++++++++++++++++ build.sh | 2 +- etc/package-version-search.conf | 3 +- tests/loader/driver.cxx | 95 +++++++++++++++---- tests/loader/internal/1/math/packages | 10 +- tests/loader/internal/1/stable/packages | 8 ++ web/xhtml | 37 +++++--- 12 files changed, 445 insertions(+), 37 deletions(-) create mode 100644 brep/page create mode 100644 brep/page.cxx diff --git a/brep/buildfile b/brep/buildfile index 9b64ed4..7e73d2b 100644 --- a/brep/buildfile +++ b/brep/buildfile @@ -22,7 +22,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 \ - shared-database} cli.cxx{options} + shared-database page} cli.cxx{options} web = ../web/apache/cxx{request service} ../web/cxx{mime-url-encoding} libso{brep-apache}: $brep $web libso{brep} $libs diff --git a/brep/options.cli b/brep/options.cli index 8062f59..a042f45 100644 --- a/brep/options.cli +++ b/brep/options.cli @@ -30,6 +30,7 @@ namespace brep class package_version_search: module, db { std::uint16_t results-on-page = 10; + std::uint16_t pages-in-pager = 10; }; } diff --git a/brep/package b/brep/package index 0132b73..0ef4407 100644 --- a/brep/package +++ b/brep/package @@ -482,6 +482,13 @@ namespace brep #pragma db member(id) virtual(package_version::_id_type) \ get() set(_id (std::move (?))) }; + + #pragma db view object(package_version) + struct package_version_count + { + #pragma db column("count(1)") + std::size_t count; + }; } // Nested container emulation support for ODB. diff --git a/brep/package-version-search.cxx b/brep/package-version-search.cxx index a989434..d95b392 100644 --- a/brep/package-version-search.cxx +++ b/brep/package-version-search.cxx @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -60,14 +61,171 @@ namespace brep throw invalid_request (400, e.what ()); } - const char* title ("Package"); + const char* ident ("\n "); + const string title ("Package " + name); serializer s (rs.content (), title); s << HTML << HEAD << TITLE << title << ~TITLE + << CSS_STYLE << ident + << pager_style () << ident + << "a {text-decoration: none;}" << ident + << "a:hover {text-decoration: underline;}" << ident + << ".name {font-size: xx-large; font-weight: bold;}" << ident + << ".summary {font-size: x-large; margin: 0.2em 0 0;}" << ident + << ".url {font-size: small;}" << ident + << ".email {font-size: small;}" << ident + << ".description {margin: 0.5em 0 0;}" << ident + << ".tags {margin: 0.5em 0 0;}" << ident + << ".tag {padding: 0 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 + << ~CSS_STYLE << ~HEAD - << BODY << name << ~BODY + << BODY; + + transaction t (db_->begin ()); + + shared_ptr p; + + try + { + p = db_->load (name); + } + catch (const object_not_persistent& ) + { + throw invalid_request (404, "Package '" + name + "' not found"); + } + + s << DIV(CLASS="name") + << name + << ~DIV + << DIV(CLASS="summary") + << p->summary + << ~DIV; + + s << DIV(CLASS="url") + << A << HREF << p->url << ~HREF << p->url << ~A + << ~DIV + << DIV(CLASS="email") + << A << HREF << "mailto:" << p->email << ~HREF << p->email << ~A + << ~DIV; + + if (p->description) + s << DIV(CLASS="description") + << *p->description + << ~DIV; + + if (!p->tags.empty ()) + { + s << DIV(CLASS="tags"); + + for (const auto& t: p->tags) + s << SPAN(CLASS="tag") << t << ~SPAN << " "; + + s << ~DIV; + } + + // @@ Query will also include search criteria if specified. + // + size_t pvc ( + db_->query_value ( + query::id.data.package == name).count); + + s << DIV(CLASS="versions") + << "Versions (" << pvc << ")" + << ~DIV; + + if (p->package_url) + s << DIV(CLASS="url") + << A << HREF << *p->package_url << ~HREF << *p->package_url << ~A + << ~DIV; + + if (p->package_email) + s << DIV(CLASS="email") + << A + << HREF << "mailto:" << *p->package_email << ~HREF + << *p->package_email + << ~A + << ~DIV; + + size_t rop (options_->results_on_page ()); + + // @@ Use appropriate view when clarify which package version info to be + // displayed and search index structure get implemented. Query will also + // include search criteria if specified. + // + using query = query; + auto r ( + db_->query ((query::id.data.package == name) + + "ORDER BY" + query::id.data.epoch + "DESC," + + query::id.data.canonical_upstream + "DESC," + + query::id.revision + "DESC " + + "OFFSET" + to_string (pr.page () * rop) + + "LIMIT" + to_string (rop))); + + for (const auto& v: r) + { + static const strings priority_names ( + {"low", "medium", "high", "security"}); + + assert (v.priority < priority_names.size ()); + + const string& vs (v.version.string ()); + + s << DIV(CLASS="package_version") + << DIV(CLASS="version") + << A + << HREF + << "/go/" << mime_url_encode (name) << "/" << vs << "/" + << ~HREF + << vs + << ~A + << ~DIV + << DIV(CLASS="priority") + << "Priority: " << priority_names[v.priority] + << ~DIV + << DIV(CLASS="licenses") + << "Licenses: "; + + for (const auto& la: v.license_alternatives) + { + if (&la != &v.license_alternatives[0]) + s << " or "; + + for (const auto& l: la) + { + if (&l != &la[0]) + s << ", "; + + s << l; + } + } + + s << ~DIV + << ~DIV; + } + + t.commit (); + + auto u ( + [&name, &pr](size_t p) + { + string url ("/go/" + name + "/"); + if (p > 0) + url += "?p=" + to_string (p); + + if (!pr.query ().empty ()) + url += + string (p > 0 ? "&" : "?") + "q=" + mime_url_encode (pr.query ()); + + return url; + }); + + s << pager (pr.page (), pvc, rop, options_->pages_in_pager (), u) + << ~BODY << ~HTML; } } diff --git a/brep/page b/brep/page new file mode 100644 index 0000000..81b6da8 --- /dev/null +++ b/brep/page @@ -0,0 +1,52 @@ +// file : brep/page -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BREP_PAGE +#define BREP_PAGE + +#include +#include // size_t +#include + +#include + +namespace brep +{ + // Page common building blocks. + // + + // Generates paging element block. + // + class pager + { + public: + using get_url_type = std::function; + + pager (std::size_t current_page, + std::size_t item_count, + std::size_t item_per_page, + std::size_t page_number_count, + get_url_type get_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_; + get_url_type get_url_; + }; + + // Default pager element block style. + // + struct pager_style + { + void + operator() (xml::serializer& s) const; + }; +} + +#endif // BREP_PAGE diff --git a/brep/page.cxx b/brep/page.cxx new file mode 100644 index 0000000..70c6d8c --- /dev/null +++ b/brep/page.cxx @@ -0,0 +1,103 @@ +// file : brep/page.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include // move() +#include // min() + +#include + +#include + +using namespace std; +using namespace xml; +using namespace web::xhtml; + +namespace brep +{ + // pager + // + pager:: + pager (size_t current_page, + size_t item_count, + size_t item_per_page, + size_t page_number_count, + get_url_type get_url) + : current_page_ (current_page), + item_count_ (item_count), + item_per_page_ (item_per_page), + page_number_count_ (page_number_count), + get_url_ (move (get_url)) + { + } + + void pager:: + operator() (serializer& s) const + { + 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) + { + // Can consider customizing class names if use-case appear. + // + s << DIV(CLASS="pager"); + + if (current_page_ > 0) + s << A(CLASS="pg-prev") + << HREF << get_url_ (current_page_ - 1) << ~HREF + << "<<" + << ~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) + { + if (p == current_page_) + s << SPAN(CLASS="pg-cpage") + << p + 1 + << ~SPAN; + else + s << A(CLASS="pg-page") + << HREF << get_url_ (p) << ~HREF + << p + 1 + << ~A; + + s << " "; + } + } + + if (current_page_ < pc - 1) + s << A(CLASS="pg-next") + << HREF << get_url_ (current_page_ + 1) << ~HREF + << ">>" + << ~A; + + s << ~DIV; + } + } + + // pager_style + // + void pager_style:: + operator() (xml::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;}"; + } +} diff --git a/build.sh b/build.sh index ae71e72..78d68e9 100755 --- a/build.sh +++ b/build.sh @@ -31,7 +31,7 @@ cli --include-with-brackets --include-prefix brep --hxx-suffix "" \ echo "g++ libbrep-apache.so" s="package-search.cxx package-version-search.cxx module.cxx diagnostics.cxx \ -services.cxx options.cxx shared-database.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/package-version-search.conf b/etc/package-version-search.conf index aa69170..f7f0d37 100644 --- a/etc/package-version-search.conf +++ b/etc/package-version-search.conf @@ -8,4 +8,5 @@ verb 1 # brep::package_version_search options # -results-on-page 10 +results-on-page 2 +pages-in-pager 10 diff --git a/tests/loader/driver.cxx b/tests/loader/driver.cxx index e828e79..501ae80 100644 --- a/tests/loader/driver.cxx +++ b/tests/loader/driver.cxx @@ -92,7 +92,7 @@ main (int argc, char* argv[]) assert (db.query ().size () == 3); assert (db.query ().size () == 4); - assert (db.query ().size () == 7); + assert (db.query ().size () == 8); shared_ptr sr (db.load ("cppget.org/stable")); shared_ptr mr (db.load ("cppget.org/math")); @@ -128,7 +128,7 @@ main (int argc, char* argv[]) sr->prerequisite_repositories[1].load ()) != pr.end ()); auto& srv (sr->package_versions); - assert (srv.size () == 4); + assert (srv.size () == 5); using lv_t = decltype (srv[0]); auto vc ([](const lv_t& a, const lv_t& b){ @@ -145,6 +145,17 @@ main (int argc, char* argv[]) sort (srv.begin (), srv.end (), vc); + version fv0 ("1.0"); + shared_ptr fpv0 ( + db.load ( + package_version_id { + "cppget.org/stable", + "libfoo", + fv0.epoch (), + fv0.canonical_upstream ()})); + assert (srv[0].load () == fpv0); + assert (check_location (fpv0)); + version fv1 ("1.2.2"); shared_ptr fpv1 ( db.load ( @@ -153,7 +164,7 @@ main (int argc, char* argv[]) "libfoo", fv1.epoch (), fv1.canonical_upstream ()})); - assert (srv[0].load () == fpv1); + assert (srv[1].load () == fpv1); assert (check_location (fpv1)); version fv2 ("1.2.3-4"); @@ -164,7 +175,7 @@ main (int argc, char* argv[]) "libfoo", fv2.epoch (), fv2.canonical_upstream ()})); - assert (srv[1].load () == fpv2); + assert (srv[2].load () == fpv2); assert (check_location (fpv2)); version fv3 ("1.2.4"); @@ -175,7 +186,7 @@ main (int argc, char* argv[]) "libfoo", fv3.epoch (), fv3.canonical_upstream ()})); - assert (srv[2].load () == fpv3); + assert (srv[3].load () == fpv3); assert (check_location (fpv3)); version xv ("1.0.0-1"); @@ -186,7 +197,7 @@ main (int argc, char* argv[]) "libstudxml", xv.epoch (), xv.canonical_upstream ()})); - assert (srv[3].load () == xpv); + assert (srv[4].load () == xpv); assert (check_location (xpv)); // Verify libstudxml package. @@ -242,24 +253,42 @@ main (int argc, char* argv[]) assert (pf->name == "libfoo"); assert (pf->summary == "The Foo Math Library"); assert (pf->tags == strings ({"c++", "foo", "math"})); - assert (*pf->description == "Very good math library."); + assert (*pf->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."); assert (pf->url == "http://www.example.com/foo/"); - assert (!pf->package_url); - assert (pf->email == email ("foo-users@example.com")); - assert (!pf->package_email); + assert (pf->package_url && + *pf->package_url == "http://www.example.com/foo/pack"); + assert (pf->email == "foo-users@example.com"); + assert (pf->package_email && *pf->package_email == "pack@example.com"); auto& fpv (pf->versions); - assert (fpv.size () == 4); + assert (fpv.size () == 5); sort (fpv.begin (), fpv.end (), vc); - assert (fpv[0].load () == fpv1); - assert (fpv[1].load () == fpv2); - assert (fpv[2].load () == fpv3); + assert (fpv[0].load () == fpv0); + assert (fpv[1].load () == fpv1); + assert (fpv[2].load () == fpv2); + assert (fpv[3].load () == fpv3); // Asserting fpv[3].load () == fpv4 goes later when fpv4, belonging to // another repository, get introduced. // // Verify libfoo package versions. // + assert (fpv0->repository.load () == sr); + assert (fpv0->package.load () == pf); + assert (fpv0->version == version ("1.0")); + assert (fpv0->priority == priority::low); + assert (fpv0->changes.empty ()); + + assert (fpv0->license_alternatives.size () == 1); + assert (fpv0->license_alternatives[0].size () == 1); + assert (fpv0->license_alternatives[0][0] == "MIT"); + + assert (fpv0->dependencies.empty ()); + assert (fpv0->requirements.empty ()); + assert (fpv1->repository.load () == sr); assert (fpv1->package.load () == pf); assert (fpv1->version == version ("1.2.2")); @@ -384,9 +413,39 @@ main (int argc, char* argv[]) fv4.epoch (), fv4.canonical_upstream ()})); assert (mrv[1].load () == fpv4); - assert (fpv[3].load () == fpv4); + assert (fpv[4].load () == fpv4); assert (check_location (fpv4)); + // Verify libfoo package versions. + // + assert (fpv4->repository.load () == mr); + assert (fpv4->package.load () == pf); + assert (fpv4->version == version ("1.2.4-1")); + assert (fpv4->priority == priority::high); + assert (fpv4->changes.empty ()); + + assert (fpv4->license_alternatives.size () == 2); + assert (fpv4->license_alternatives[0].comment == + "If using with GNU TLS."); + assert (fpv4->license_alternatives[0].size () == 2); + assert (fpv4->license_alternatives[0][0] == "LGPLv2"); + assert (fpv4->license_alternatives[0][1] == "MIT"); + assert (fpv4->license_alternatives[1].comment == + "If using with OpenSSL."); + assert (fpv4->license_alternatives[1].size () == 1); + assert (fpv4->license_alternatives[1][0] == "BSD"); + + assert (fpv4->dependencies.size () == 1); + assert (fpv4->dependencies[0].size () == 1); + + assert (fpv4->dependencies[0][0] == + (dependency { + "libmisc", + brep::optional ( + version_comparison{version ("2.3.0"), comparison::ge})})); + + assert (fpv4->requirements.empty ()); + // Verify libexp package. // shared_ptr pe (db.load ("libexp")); @@ -488,8 +547,8 @@ main (int argc, char* argv[]) // Update package summary, update package persistent state, rerun loader // and ensure the model were not rebuilt. // - pf->summary = "test"; - db.update (pf); + pb->summary = "test"; + db.update (pb); t.commit (); } @@ -497,7 +556,7 @@ main (int argc, char* argv[]) assert (process (ld_args).wait ()); transaction t (db.begin ()); - assert (db.load ("libfoo")->summary == "test"); + assert (db.load ("libbar")->summary == "test"); t.commit (); } // Fully qualified to avoid ambiguity with odb exception. diff --git a/tests/loader/internal/1/math/packages b/tests/loader/internal/1/math/packages index f2d7024..cbd8165 100644 --- a/tests/loader/internal/1/math/packages +++ b/tests/loader/internal/1/math/packages @@ -12,10 +12,16 @@ location: libexp-1+1.2.tar.gz name: libfoo version: 1.2.4-1 summary: The Foo Math Library -description: Very good math library. -license: MIT +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. +license: LGPLv2, MIT; If using with GNU TLS. +license: BSD; If using with OpenSSL. +priority: high tags: c++, foo, math url: http://www.example.com/foo/ email: foo-users@example.com +package-url: http://www.example.com/foo/pack +package-email: pack@example.com depends: libmisc >= 2.3.0 location: libfoo-1.2.4-1.tar.gz diff --git a/tests/loader/internal/1/stable/packages b/tests/loader/internal/1/stable/packages index b95aa62..d5f1551 100644 --- a/tests/loader/internal/1/stable/packages +++ b/tests/loader/internal/1/stable/packages @@ -51,3 +51,11 @@ depends: libmisc >= 2.0.0 changes: some changes 1 changes: some changes 2 location: libfoo-1.2.4.tar.gz +: +name: libfoo +version: 1.0 +summary: The Foo Library +license: MIT +url: http://www.example.com/foo/ +email: foo-users@example.com +location: libfoo-1.0.tar.gz diff --git a/web/xhtml b/web/xhtml index 708655c..d683469 100644 --- a/web/xhtml +++ b/web/xhtml @@ -278,6 +278,10 @@ namespace web s.start_element (xmlns, "meta"); s.attribute ("charset", "UTF-8"); s.end_element (); + s.start_element (xmlns, "meta"); + s.attribute ("name", "viewport"); + s.attribute ("content", "device-width, initial-scale=1"); + s.end_element (); } }; static const head_element HEAD; @@ -297,24 +301,33 @@ namespace web static const element BODY ("body"); static const element DIV ("div"); + static const element H1 ("h1"); + static const element H2 ("h2"); + static const element H3 ("h3"); + static const element H4 ("h4"); + static const element H5 ("h5"); + static const element H6 ("h6"); + static const element META ("meta"); static const element P ("p"); static const element TITLE ("title"); - static const inline_element A ("a"); - static const inline_element B ("b"); - static const inline_element I ("i"); - static const inline_element U ("u"); - - static const inline_element EM ("em"); - static const inline_element BR ("br"); + static const inline_element A ("a"); + static const inline_element B ("b"); + static const inline_element BR ("br"); + static const inline_element EM ("em"); + static const inline_element I ("i"); + static const inline_element SPAN ("span"); + static const inline_element U ("u"); // Attributes. // - static const attribute CLASS ("class"); - static const attribute HREF ("href"); - static const attribute ID ("id"); - static const attribute STYLE ("style"); - static const attribute TYPE ("type"); + 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"); } } -- cgit v1.1