From 2a0f39b29c1bea6a4497c0f1826052ffa453af9e Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 21 Apr 2016 16:05:13 +0200 Subject: Move module implementation from brep/ to mod/ --- INSTALL-DEV | 2 +- brep/.gitignore | 2 - brep/buildfile | 59 +-- brep/database | 26 -- brep/database-module | 55 --- brep/database-module.cxx | 50 --- brep/database.cxx | 60 --- brep/diagnostics | 306 ---------------- brep/diagnostics.cxx | 30 -- brep/mod-package-details | 42 --- brep/mod-package-details.cxx | 257 ------------- brep/mod-package-search | 42 --- brep/mod-package-search.cxx | 181 --------- brep/mod-package-version-details | 45 --- brep/mod-package-version-details.cxx | 319 ---------------- brep/mod-repository-details | 42 --- brep/mod-repository-details.cxx | 146 -------- brep/mod-repository-root | 60 --- brep/mod-repository-root.cxx | 263 ------------- brep/module | 201 ---------- brep/module.cxx | 410 --------------------- brep/options-types | 32 -- brep/options.cli | 211 ----------- brep/page | 402 -------------------- brep/page.cxx | 693 ----------------------------------- brep/services.cxx | 15 - brep/types-parsers | 57 --- brep/types-parsers.cxx | 114 ------ buildfile | 2 +- mod/.gitignore | 2 + mod/buildfile | 55 +++ mod/database | 26 ++ mod/database-module | 55 +++ mod/database-module.cxx | 50 +++ mod/database.cxx | 60 +++ mod/diagnostics | 306 ++++++++++++++++ mod/diagnostics.cxx | 30 ++ mod/mod-package-details | 42 +++ mod/mod-package-details.cxx | 258 +++++++++++++ mod/mod-package-search | 42 +++ mod/mod-package-search.cxx | 181 +++++++++ mod/mod-package-version-details | 45 +++ mod/mod-package-version-details.cxx | 320 ++++++++++++++++ mod/mod-repository-details | 42 +++ mod/mod-repository-details.cxx | 147 ++++++++ mod/mod-repository-root | 60 +++ mod/mod-repository-root.cxx | 263 +++++++++++++ mod/module | 201 ++++++++++ mod/module.cxx | 410 +++++++++++++++++++++ mod/options-types | 32 ++ mod/options.cli | 211 +++++++++++ mod/page | 403 ++++++++++++++++++++ mod/page.cxx | 693 +++++++++++++++++++++++++++++++++++ mod/services.cxx | 15 + mod/types-parsers | 57 +++ mod/types-parsers.cxx | 114 ++++++ 56 files changed, 4126 insertions(+), 4118 deletions(-) delete mode 100644 brep/database delete mode 100644 brep/database-module delete mode 100644 brep/database-module.cxx delete mode 100644 brep/database.cxx delete mode 100644 brep/diagnostics delete mode 100644 brep/diagnostics.cxx delete mode 100644 brep/mod-package-details delete mode 100644 brep/mod-package-details.cxx delete mode 100644 brep/mod-package-search delete mode 100644 brep/mod-package-search.cxx delete mode 100644 brep/mod-package-version-details delete mode 100644 brep/mod-package-version-details.cxx delete mode 100644 brep/mod-repository-details delete mode 100644 brep/mod-repository-details.cxx delete mode 100644 brep/mod-repository-root delete mode 100644 brep/mod-repository-root.cxx delete mode 100644 brep/module delete mode 100644 brep/module.cxx delete mode 100644 brep/options-types delete mode 100644 brep/options.cli delete mode 100644 brep/page delete mode 100644 brep/page.cxx delete mode 100644 brep/services.cxx delete mode 100644 brep/types-parsers delete mode 100644 brep/types-parsers.cxx create mode 100644 mod/.gitignore create mode 100644 mod/buildfile create mode 100644 mod/database create mode 100644 mod/database-module create mode 100644 mod/database-module.cxx create mode 100644 mod/database.cxx create mode 100644 mod/diagnostics create mode 100644 mod/diagnostics.cxx create mode 100644 mod/mod-package-details create mode 100644 mod/mod-package-details.cxx create mode 100644 mod/mod-package-search create mode 100644 mod/mod-package-search.cxx create mode 100644 mod/mod-package-version-details create mode 100644 mod/mod-package-version-details.cxx create mode 100644 mod/mod-repository-details create mode 100644 mod/mod-repository-details.cxx create mode 100644 mod/mod-repository-root create mode 100644 mod/mod-repository-root.cxx create mode 100644 mod/module create mode 100644 mod/module.cxx create mode 100644 mod/options-types create mode 100644 mod/options.cli create mode 100644 mod/page create mode 100644 mod/page.cxx create mode 100644 mod/services.cxx create mode 100644 mod/types-parsers create mode 100644 mod/types-parsers.cxx diff --git a/INSTALL-DEV b/INSTALL-DEV index 51dad72..d9eef3b 100644 --- a/INSTALL-DEV +++ b/INSTALL-DEV @@ -66,7 +66,7 @@ replacing and with the actual absolute paths # Load the brep module. # - LoadModule brep_module /brep/mod_brep.so + LoadModule brep_module /mod/mod_brep.so # Repository root. Use / for web server root. And don't forget to also # update the Location and Alias directives below. diff --git a/brep/.gitignore b/brep/.gitignore index 852a40d..687b168 100644 --- a/brep/.gitignore +++ b/brep/.gitignore @@ -1,5 +1,3 @@ -options -options.?xx package-odb* package.sql package-extra diff --git a/brep/buildfile b/brep/buildfile index 4caf93a..529916a 100644 --- a/brep/buildfile +++ b/brep/buildfile @@ -2,16 +2,10 @@ # copyright : Copyright (c) 2014-2016 Code Synthesis Ltd # license : MIT; see accompanying LICENSE file -define mod: libso -mod{*}: bin.libprefix = mod_ -mod{*}: install = libexec - define sql: file sql{*}: extension = sql sql{*}: install = data -./: lib{brep} mod{brep} sql{package package-extra} - # lib{brep} # import libs += libodb%lib{odb} @@ -28,57 +22,12 @@ lib{brep}: \ {hxx }{ types } \ {hxx }{ utility } \ {hxx }{ version } \ -$libs +{hxx }{ wrapper-traits } \ +$libs \ +sql{package package-extra} -libso{brep}: cxx.export.poptions = -I$out_root -I$src_root +lib{brep}: cxx.export.poptions = -I$out_root -I$src_root # Install into the brep/ subdirectory of, say, /usr/include/. # install.include = $install.include/brep - -# mod{brep} -# -import libs += libstudxml%lib{studxml} - -gen = {hxx ixx cxx}{ options } -src = \ - {hxx cxx}{ database } \ - {hxx cxx}{ database-module } \ - {hxx cxx}{ diagnostics } \ - {hxx cxx}{ mod-package-details } \ - {hxx cxx}{ mod-package-search } \ - {hxx cxx}{ mod-package-version-details } \ - {hxx cxx}{ mod-repository-details } \ - {hxx cxx}{ mod-repository-root } \ - {hxx cxx}{ module } \ - {hxx }{ options-types } \ - {hxx cxx}{ page } \ - { cxx}{ services } \ - {hxx cxx}{ types-parsers } \ - {hxx }{ wrapper-traits } \ - ../web/{hxx cxx}{ mime-url-encoding } \ - ../web/{hxx }{ module } \ - ../web/{hxx }{ xhtml } \ - ../web/{hxx cxx}{ xhtml-fragment } \ -../web/apache/{hxx }{ log } \ -../web/apache/{hxx ixx cxx}{ request } \ -../web/apache/{hxx txx cxx}{ service } \ -../web/apache/{hxx }{ stream } - -mod{brep}: $src $gen lib{brep} $libs - -# Don't install any of the mod{brep} headers. -# -$out_base/{$gen}: install = false -$src_base/{$src}: install = false - -# Set option prefix to the empty value to handle all unknown request parameters -# uniformly with a single catch block. -# -cli.options += --std c++11 -I $src_root --include-with-brackets \ ---include-prefix brep --guard-prefix BREP \ ---cxx-prologue "#include " \ ---cli-namespace brep::cli --generate-file-scanner --suppress-usage \ ---generate-modifier --generate-description --option-prefix "" - -{hxx ixx cxx}{options}: cli{options} diff --git a/brep/database b/brep/database deleted file mode 100644 index d29d4a5..0000000 --- a/brep/database +++ /dev/null @@ -1,26 +0,0 @@ -// file : brep/database -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BREP_DATABASE -#define BREP_DATABASE - -#include // database - -#include -#include - -#include - -namespace brep -{ - // Returns pointer to the shared database instance, creating one on the - // first call. On subsequent calls ensures passed host and port equals - // to ones of the existing database instance throwing runtime_error - // otherwise. Is not thread-safe. - // - shared_ptr - shared_database (const options::db&); -} - -#endif // BREP_DATABASE diff --git a/brep/database-module b/brep/database-module deleted file mode 100644 index 64d5aaf..0000000 --- a/brep/database-module +++ /dev/null @@ -1,55 +0,0 @@ -// file : brep/database-module -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BREP_DATABASE_MODULE -#define BREP_DATABASE_MODULE - -#include // database - -#include -#include - -#include -#include - -namespace brep -{ - // A module that utilises the database. Specifically, it will retry the - // request in the face of recoverable database failures (deadlock, loss of - // connection, etc) up to a certain number of times. - // - class database_module: public module - { - protected: - database_module () = default; - - // Create a shallow copy (handling instance) if initialized and a deep - // copy (context exemplar) otherwise. - // - explicit - database_module (const database_module&); - - // Required to avoid getting warning from clang that - // database_module::init() hides module::init() virtual functions. This - // way all functions get to the same scope and become overloaded set. - // - using module::init; - - void - init (const options::db&); - - virtual bool - handle (request&, response&) = 0; - - protected: - size_t retry_; - shared_ptr db_; - - private: - virtual bool - handle (request&, response&, log&); - }; -} - -#endif // BREP_DATABASE_MODULE diff --git a/brep/database-module.cxx b/brep/database-module.cxx deleted file mode 100644 index 630fd89..0000000 --- a/brep/database-module.cxx +++ /dev/null @@ -1,50 +0,0 @@ -// file : brep/database-module.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -#include - -#include -#include - -namespace brep -{ - // While currently the user-defined copy constructor is not required (we - // don't need to deep copy nullptr's), it is a good idea to keep the - // placeholder ready for less trivial cases. - // - database_module:: - database_module (const database_module& r) - : module (r), - retry_ (r.retry_), - db_ (r.initialized_ ? r.db_ : nullptr) - { - } - - void database_module:: - init (const options::db& o) - { - retry_ = o.db_retry (); - db_ = shared_database (o); - } - - bool database_module:: - handle (request& rq, response& rs, log& l) - try - { - return module::handle (rq, rs, l); - } - catch (const odb::recoverable& e) - { - if (retry_-- > 0) - { - MODULE_DIAG; - l1 ([&]{trace << e.what () << "; " << retry_ + 1 << " retries left";}); - throw retry (); - } - - throw; - } -} diff --git a/brep/database.cxx b/brep/database.cxx deleted file mode 100644 index 4b56c37..0000000 --- a/brep/database.cxx +++ /dev/null @@ -1,60 +0,0 @@ -// file : brep/database.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -#include - -#include -#include - -namespace brep -{ - namespace options - { - bool - operator< (const db& x, const db& y) - { - int r; - if ((r = x.db_user ().compare (y.db_user ())) != 0 || - (r = x.db_password ().compare (y.db_password ())) != 0 || - (r = x.db_name ().compare (y.db_name ())) != 0 || - (r = x.db_host ().compare (y.db_host ()))) - return r < 0; - - return x.db_port () < y.db_port (); - } - } - - using namespace odb; - - shared_ptr - shared_database (const options::db& o) - { - static std::map> databases; - - auto i (databases.find (o)); - if (i != databases.end ()) - { - if (shared_ptr d = i->second.lock ()) - return d; - } - - unique_ptr - f (new pgsql::connection_pool_factory (o.db_max_connections ())); - - shared_ptr d ( - make_shared ( - o.db_user (), - o.db_password (), - o.db_name (), - o.db_host (), - o.db_port (), - "options='-c default_transaction_isolation=serializable'", - move (f))); - - databases[o] = d; - return d; - } -} diff --git a/brep/diagnostics b/brep/diagnostics deleted file mode 100644 index b5b1b36..0000000 --- a/brep/diagnostics +++ /dev/null @@ -1,306 +0,0 @@ -// file : brep/diagnostics -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BREP_DIAGNOSTICS -#define BREP_DIAGNOSTICS - -#include - -#include -#include - -namespace brep -{ - struct location - { - location (): line (0), column (0) {} - location (string f, uint64_t l, uint64_t c) - : file (move (f)), line (l), column (c) {} - - string file; - uint64_t line; - uint64_t column; - }; - - enum class severity {error, warning, info, trace}; - - struct diag_entry - { - severity sev; - const char* name {nullptr}; // E.g., a function name in tracing. - location loc; - string msg; - }; - - using diag_data = vector; - - // - // - template struct diag_prologue; - template struct diag_mark; - - using diag_epilogue = function; - - struct diag_record - { - template - friend const diag_record& - operator<< (const diag_record& r, const T& x) - { - r.os_ << x; - return r; - } - - diag_record () = default; - - template - explicit - diag_record (const diag_prologue& p) {*this << p;} // See below. - - template - explicit - diag_record (const diag_mark& m) {*this << m;} // See below. - - ~diag_record () noexcept(false); - - void - append (const diag_epilogue& e) const - { - if (epilogue_ == nullptr) // Keep the first epilogue (think 'fail'). - epilogue_ = &e; - - if (!data_.empty ()) - { - data_.back ().msg = os_.str (); - - // Reset the stream. There got to be a more efficient way to do it. - // - os_.clear (); - os_.str (""); - } - - data_.push_back (diag_entry ()); - } - - diag_entry& - current () const {return data_.back ();} - - // Move constructible-only type. - // - /* - @@ libstdc++ doesn't yet have the ostringstream move support. - - diag_record (diag_record&& r) - : data_ (move (r.data_)), os_ (move (r.os_)) - { - epilogue_ = r.epilogue_; - r.data_.clear (); // Empty. - } - */ - - diag_record (diag_record&& r): data_ (move (r.data_)) - { - if (!data_.empty ()) - os_ << r.os_.str (); - - epilogue_ = r.epilogue_; - r.data_.clear (); // Empty. - } - - diag_record& operator= (diag_record&&) = delete; - - diag_record (const diag_record&) = delete; - diag_record& operator= (const diag_record&) = delete; - - private: - mutable diag_data data_; - mutable std::ostringstream os_; - mutable const diag_epilogue* epilogue_ {nullptr}; - }; - - // Base (B) should provide operator() that configures diag_record. - // - template - struct diag_prologue: B - { - diag_prologue (const diag_epilogue& e): B (), epilogue_ (e) {} - - template - diag_prologue (const diag_epilogue& e, A&&... a) - : B (forward (a)...), epilogue_ (e) {} - - template - diag_record - operator<< (const T& x) const - { - diag_record r; - r.append (epilogue_); - B::operator() (r); - r << x; - return r; - } - - friend const diag_record& - operator<< (const diag_record& r, const diag_prologue& p) - { - r.append (p.epilogue_); - p (r); - return r; - } - - private: - const diag_epilogue& epilogue_; - }; - - // Base (B) should provide operator() that returns diag_prologue. - // - template - struct diag_mark: B - { - diag_mark (): B () {} - - template - diag_mark (A&&... a): B (forward (a)...) {} - - template - diag_record - operator<< (const T& x) const - { - return B::operator() () << x; - } - - friend const diag_record& - operator<< (const diag_record& r, const diag_mark& m) - { - return r << m (); - } - }; - - // Prologues. - // - struct simple_prologue_base - { - explicit - simple_prologue_base (severity s, const char* name) - : sev_ (s), name_ (name) {} - - void - operator() (const diag_record& r) const - { - diag_entry& e (r.current ()); - e.sev = sev_; - e.name = name_; - } - - private: - severity sev_; - const char* name_; - }; - typedef diag_prologue simple_prologue; - - struct location_prologue_base - { - location_prologue_base (severity s, - const char* name, - const location& l) - : sev_ (s), name_ (name), loc_ (l) {} - - void - operator() (const diag_record& r) const - { - diag_entry& e (r.current ()); - e.sev = sev_; - e.name = name_; - e.loc = loc_; //@@ I think we can probably move it. - } - - private: - severity sev_; - const char* name_; - const location loc_; - }; - typedef diag_prologue location_prologue; - - // Marks. - // - struct basic_mark_base - { - explicit - basic_mark_base (severity s, - const diag_epilogue& e, - const char* name = nullptr, - const void* data = nullptr) - : sev_ (s), epilogue_ (e), name_ (name), data_ (data) {} - - simple_prologue - operator() () const - { - return simple_prologue (epilogue_, sev_, name_); - } - - location_prologue - operator() (const location& l) const - { - return location_prologue (epilogue_, sev_, name_, l); - } - - template - location_prologue - operator() (const L& l) const - { - // get_location() is the user-supplied ADL-searched function. - // - return location_prologue ( - epilogue_, sev_, name_, get_location (l, data_)); - } - - private: - severity sev_; - const diag_epilogue& epilogue_; - const char* name_; - const void* data_; - }; - typedef diag_mark basic_mark; - - template - struct fail_mark_base - { - explicit - fail_mark_base (const char* name = nullptr, const void* data = nullptr) - : name_ (name), data_ (data) {} - - simple_prologue - operator() () const - { - return simple_prologue (epilogue_, severity::error, name_); - } - - location_prologue - operator() (const location& l) const - { - return location_prologue (epilogue_, severity::error, name_, l); - } - - template - location_prologue - operator() (const L& l) const - { - return location_prologue ( - epilogue_, severity::error, name_, get_location (l, data_)); - } - - static void - epilogue (diag_data&& d) {throw E (move (d));} - - private: - const diag_epilogue epilogue_ {&epilogue}; - const char* name_; - const void* data_; - }; - - template - using fail_mark = diag_mark>; -} - -#endif // BREP_DIAGNOSTICS diff --git a/brep/diagnostics.cxx b/brep/diagnostics.cxx deleted file mode 100644 index b0d122f..0000000 --- a/brep/diagnostics.cxx +++ /dev/null @@ -1,30 +0,0 @@ -// file : brep/diagnostics.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -using namespace std; - -namespace brep -{ - diag_record:: - ~diag_record () noexcept(false) - { - // Don't flush the record if this destructor was called as part of - // the stack unwinding. Right now this means we cannot use this - // mechanism in destructors, which is not a big deal, except for - // one place: exception_guard. So for now we are going to have - // this ugly special check which we will be able to get rid of - // once C++17 uncaught_exceptions() becomes available. - // - if (!data_.empty () && - (!uncaught_exception () /*|| exception_unwinding_dtor*/)) - { - data_.back ().msg = os_.str (); // Save last message. - - assert (epilogue_ != nullptr); - (*epilogue_) (move (data_)); // Can throw. - } - } -} diff --git a/brep/mod-package-details b/brep/mod-package-details deleted file mode 100644 index 4dc6b18..0000000 --- a/brep/mod-package-details +++ /dev/null @@ -1,42 +0,0 @@ -// file : brep/mod-package-details -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BREP_MOD_PACKAGE_DETAILS -#define BREP_MOD_PACKAGE_DETAILS - -#include -#include - -#include -#include - -namespace brep -{ - class package_details: public database_module - { - public: - package_details () = default; - - // Create a shallow copy (handling instance) if initialized and a deep - // copy (context exemplar) otherwise. - // - explicit - package_details (const package_details&); - - virtual bool - handle (request&, response&); - - virtual const cli::options& - cli_options () const {return options::package_details::description ();} - - private: - virtual void - init (cli::scanner&); - - private: - shared_ptr options_; - }; -} - -#endif // BREP_MOD_PACKAGE_DETAILS diff --git a/brep/mod-package-details.cxx b/brep/mod-package-details.cxx deleted file mode 100644 index 10bd72c..0000000 --- a/brep/mod-package-details.cxx +++ /dev/null @@ -1,257 +0,0 @@ -// file : brep/mod-package-details.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -#include - -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -using namespace odb::core; -using namespace brep::cli; - -// While currently the user-defined copy constructor is not required (we don't -// need to deep copy nullptr's), it is a good idea to keep the placeholder -// ready for less trivial cases. -// -brep::package_details:: -package_details (const package_details& r) - : database_module (r), - options_ (r.initialized_ ? r.options_ : nullptr) -{ -} - -void brep::package_details:: -init (scanner& s) -{ - MODULE_DIAG; - - options_ = make_shared ( - s, unknown_mode::fail, unknown_mode::fail); - - database_module::init (*options_); - - if (options_->root ().empty ()) - options_->root (dir_path ("/")); -} - -template -static inline query -search_params (const brep::string& n, const brep::string& q) -{ - using query = query; - - return "(" + - (q.empty () - ? query ("NULL") - : "plainto_tsquery (" + query::_val (q) + ")") + - "," + - query::_val (n) + - ")"; -} - -bool brep::package_details:: -handle (request& rq, response& rs) -{ - using namespace web; - using namespace web::xhtml; - - MODULE_DIAG; - - const size_t res_page (options_->search_results ()); - const dir_path& root (options_->root ()); - - const string& name (*rq.path ().rbegin ()); - const string ename (mime_url_encode (name)); - - params::package_details params; - bool full; - - try - { - name_value_scanner s (rq.parameters ()); - params = params::package_details ( - s, unknown_mode::fail, unknown_mode::fail); - - full = params.form () == page_form::full; - } - catch (const cli::exception& e) - { - throw invalid_request (400, e.what ()); - } - - size_t page (params.page ()); - const string& squery (params.query ()); - - auto url ( - [&ename](bool f = false, - const string& q = "", - size_t p = 0, - const string& a = "") -> string - { - string s ("?"); - string u (ename); - - if (f) { u += "?f=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; - }); - - xml::serializer s (rs.content (), name); - - s << HTML - << HEAD - << TITLE - << name; - - if (!squery.empty ()) - s << " " << squery; - - s << ~TITLE - << CSS_LINKS (path ("package-details.css"), root) - // - // This hack is required to avoid the "flash of unstyled content", which - // happens due to the presence of the autofocus attribute in the input - // element of the search form. The problem appears in Firefox and has a - // (4-year old, at the time of this writing) bug report: - // - // https://bugzilla.mozilla.org/show_bug.cgi?id=712130. - // - << SCRIPT << " " << ~SCRIPT - << ~HEAD - << BODY - << DIV_HEADER (root, options_->logo (), options_->menu ()) - << DIV(ID="content"); - - if (full) - s << CLASS("full"); - - s << DIV(ID="heading") - << H1 << A(HREF=url ()) << name << ~A << ~H1 - << A(HREF=url (!full, squery, page)) - << (full ? "[brief]" : "[full]") - << ~A - << ~DIV; - - session sn; - transaction t (db_->begin ()); - - shared_ptr pkg; - { - latest_package lp; - if (!db_->query_one ( - query( - "(" + query::_val (name) + ")"), lp)) - throw invalid_request (404, "Package '" + name + "' not found"); - - pkg = db_->load (lp.id); - } - - const auto& licenses (pkg->license_alternatives); - - if (page == 0) - { - // Display package details on the first page only. - // - s << H2 << pkg->summary << ~H2; - - static const string id ("description"); - if (const auto& d = pkg->description) - s << (full - ? P_DESCRIPTION (*d, id) - : P_DESCRIPTION (*d, options_->package_description (), - url (!full, squery, page, id))); - - s << TABLE(CLASS="proplist", ID="package") - << TBODY - << TR_LICENSE (licenses) - << TR_URL (pkg->url) - << TR_EMAIL (pkg->email) - << TR_TAGS (pkg->tags, root) - << ~TBODY - << ~TABLE; - } - - auto pkg_count ( - db_->query_value ( - search_params (name, squery))); - - s << FORM_SEARCH (squery) - << DIV_COUNTER (pkg_count, "Version", "Versions"); - - // Enclose the subsequent tables to be able to use nth-child CSS selector. - // - s << DIV; - for (const auto& pr: - db_->query ( - search_params (name, squery) + - "ORDER BY rank DESC, version_epoch DESC, " - "version_canonical_upstream DESC, version_canonical_release DESC, " - "version_revision DESC" + - "OFFSET" + to_string (page * res_page) + - "LIMIT" + to_string (res_page))) - { - shared_ptr p (db_->load (pr.id)); - - s << TABLE(CLASS="proplist version") - << TBODY - << TR_VERSION (name, p->version.string (), root) - - // @@ Shouldn't we skip low priority row ? Don't think so, why? - // - << TR_PRIORITY (p->priority); - - // Comparing objects of the license_alternatives class as being of the - // vector> class, so comments are not considered. - // - if (p->license_alternatives != licenses) - s << TR_LICENSE (p->license_alternatives); - - assert (p->internal ()); - - // @@ Shouldn't we make package location to be a link to the proper - // place of the About page, describing corresponding repository? - // Yes, I think that's sounds reasonable, once we have about. - // Or maybe it can be something more valuable like a link to the - // repository package search page ? - // - // @@ 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 differs - // from the one in the summary ? - // - // Hm, I am not so sure about this. Consider: stable/testing/unstable. - // - s << TR_LOCATION (p->internal_repository.object_id (), root) - << TR_DEPENDS (p->dependencies, root) - << TR_REQUIRES (p->requirements) - << ~TBODY - << ~TABLE; - } - s << ~DIV; - - t.commit (); - - s << DIV_PAGER (page, pkg_count, res_page, options_->search_pages (), - url (full, squery)) - << ~DIV - << ~BODY - << ~HTML; - - return true; -} diff --git a/brep/mod-package-search b/brep/mod-package-search deleted file mode 100644 index bb762cd..0000000 --- a/brep/mod-package-search +++ /dev/null @@ -1,42 +0,0 @@ -// file : brep/mod-package-search -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BREP_MOD_PACKAGE_SEARCH -#define BREP_MOD_PACKAGE_SEARCH - -#include -#include - -#include -#include - -namespace brep -{ - class package_search: public database_module - { - public: - package_search () = default; - - // Create a shallow copy (handling instance) if initialized and a deep - // copy (context exemplar) otherwise. - // - explicit - package_search (const package_search&); - - virtual bool - handle (request&, response&); - - virtual const cli::options& - cli_options () const {return options::package_search::description ();} - - private: - virtual void - init (cli::scanner&); - - private: - shared_ptr options_; - }; -} - -#endif // BREP_MOD_PACKAGE_SEARCH diff --git a/brep/mod-package-search.cxx b/brep/mod-package-search.cxx deleted file mode 100644 index 9769783..0000000 --- a/brep/mod-package-search.cxx +++ /dev/null @@ -1,181 +0,0 @@ -// file : brep/mod-package-search.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -#include -#include -#include -#include - -using namespace odb::core; -using namespace brep::cli; - -// While currently the user-defined copy constructor is not required (we don't -// need to deep copy nullptr's), it is a good idea to keep the placeholder -// ready for less trivial cases. -// -brep::package_search:: -package_search (const package_search& r) - : database_module (r), - options_ (r.initialized_ ? r.options_ : nullptr) -{ -} - -void brep::package_search:: -init (scanner& s) -{ - MODULE_DIAG; - - options_ = make_shared ( - s, unknown_mode::fail, unknown_mode::fail); - - database_module::init (*options_); - - if (options_->root ().empty ()) - options_->root (dir_path ("/")); - - // Check that the database schema matches the current one. It's enough to - // perform the check in just a single module implementation (and we don't - // do in the dispatcher because it doesn't use the database). - // - // Note that the failure can be reported by each web server worker process. - // While it could be tempting to move the check to the - // repository_root::version() function, it would be wrong. The function can - // be called by a different process (usually the web server root one) not - // having the proper permissions to access the database. - // - if (schema_catalog::current_version (*db_) != db_->schema_version ()) - fail << "database schema differs from the current one (module " - << BREP_VERSION_STR << ")"; -} - -template -static inline query -search_param (const brep::string& q) -{ - using query = query; - return "(" + - (q.empty () - ? query ("NULL") - : "plainto_tsquery (" + query::_val (q) + ")") + - ")"; -} - -bool brep::package_search:: -handle (request& rq, response& rs) -{ - using namespace web::xhtml; - - MODULE_DIAG; - - const size_t res_page (options_->search_results ()); - const dir_path& root (options_->root ()); - - params::package_search params; - - try - { - name_value_scanner s (rq.parameters ()); - params = params::package_search ( - s, unknown_mode::fail, unknown_mode::fail); - } - catch (const unknown_argument& e) - { - throw invalid_request (400, e.what ()); - } - - size_t page (params.page ()); - const string& squery (params.query ()); - string squery_param (squery.empty () - ? "" - : "?q=" + web::mime_url_encode (squery)); - - - static const string title ("Packages"); - xml::serializer s (rs.content (), title); - - s << HTML - << HEAD - << TITLE - << title; - - if (!squery.empty ()) - s << " " << squery; - - s << ~TITLE - << CSS_LINKS (path ("package-search.css"), root) - // - // This hack is required to avoid the "flash of unstyled content", which - // happens due to the presence of the autofocus attribute in the input - // element of the search form. The problem appears in Firefox and has a - // (4-year old, at the time of this writing) bug report: - // - // https://bugzilla.mozilla.org/show_bug.cgi?id=712130. - // - << SCRIPT << " " << ~SCRIPT - << ~HEAD - << BODY - << DIV_HEADER (root, options_->logo (), options_->menu ()) - << DIV(ID="content"); - - session sn; - transaction t (db_->begin ()); - - auto pkg_count ( - db_->query_value ( - search_param (squery))); - - s << FORM_SEARCH (squery) - << DIV_COUNTER (pkg_count, "Package", "Packages"); - - // Enclose the subsequent tables to be able to use nth-child CSS selector. - // - s << DIV; - for (const auto& pr: - db_->query ( - search_param (squery) + - "ORDER BY rank DESC, name" + - "OFFSET" + to_string (page * res_page) + - "LIMIT" + to_string (res_page))) - { - shared_ptr p (db_->load (pr.id)); - - s << TABLE(CLASS="proplist package") - << TBODY - << TR_NAME (p->id.name, squery_param, root) - << TR_SUMMARY (p->summary) - << TR_LICENSE (p->license_alternatives) - << TR_TAGS (p->tags, root) - << TR_DEPENDS (p->dependencies, root) - << TR_REQUIRES (p->requirements) - << ~TBODY - << ~TABLE; - } - s << ~DIV; - - t.commit (); - - s << DIV_PAGER (page, pkg_count, res_page, options_->search_pages (), - root.string () + squery_param) - << ~DIV - << ~BODY - << ~HTML; - - return true; -} diff --git a/brep/mod-package-version-details b/brep/mod-package-version-details deleted file mode 100644 index a463511..0000000 --- a/brep/mod-package-version-details +++ /dev/null @@ -1,45 +0,0 @@ -// file : brep/mod-package-version-details -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BREP_MOD_PACKAGE_VERSION_DETAILS -#define BREP_MOD_PACKAGE_VERSION_DETAILS - -#include -#include - -#include -#include - -namespace brep -{ - class package_version_details: public database_module - { - public: - package_version_details () = default; - - // Create a shallow copy (handling instance) if initialized and a deep - // copy (context exemplar) otherwise. - // - explicit - package_version_details (const package_version_details&); - - virtual bool - handle (request&, response&); - - virtual const cli::options& - cli_options () const - { - return options::package_version_details::description (); - } - - private: - virtual void - init (cli::scanner&); - - private: - shared_ptr options_; - }; -} - -#endif // BREP_MOD_PACKAGE_VERSION_DETAILS diff --git a/brep/mod-package-version-details.cxx b/brep/mod-package-version-details.cxx deleted file mode 100644 index 21da41f..0000000 --- a/brep/mod-package-version-details.cxx +++ /dev/null @@ -1,319 +0,0 @@ -// file : brep/mod-package-version-details.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -#include - -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -using namespace std; -using namespace odb::core; -using namespace brep::cli; - -// While currently the user-defined copy constructor is not required (we don't -// need to deep copy nullptr's), it is a good idea to keep the placeholder -// ready for less trivial cases. -// -brep::package_version_details:: -package_version_details (const package_version_details& r) - : database_module (r), - options_ (r.initialized_ ? r.options_ : nullptr) -{ -} - -void brep::package_version_details:: -init (scanner& s) -{ - MODULE_DIAG; - - options_ = make_shared ( - s, unknown_mode::fail, unknown_mode::fail); - - database_module::init (*options_); - - if (options_->root ().empty ()) - options_->root (dir_path ("/")); -} - -bool brep::package_version_details:: -handle (request& rq, response& rs) -{ - using namespace web; - using namespace web::xhtml; - using brep::version; // Not to confuse with module::version. - - MODULE_DIAG; - - const dir_path& root (options_->root ()); - - auto i (rq.path ().rbegin ()); - version ver; - - try - { - ver = version (*i++); - } - catch (const invalid_argument& ) - { - throw invalid_request (400, "invalid package version format"); - } - - const string& sver (ver.string ()); - - assert (i != rq.path ().rend ()); - const string& name (*i); - - params::package_version_details params; - bool full; - - try - { - name_value_scanner s (rq.parameters ()); - params = params::package_version_details ( - s, unknown_mode::fail, unknown_mode::fail); - - full = params.form () == page_form::full; - } - catch (const unknown_argument& e) - { - throw invalid_request (400, e.what ()); - } - - auto url ( - [&sver](bool f = false, const string& a = "") -> string - { - string u (sver); - - if (f) { u += "?f=full"; } - if (!a.empty ()) { u += '#' + a; } - return u; - }); - - const string title (name + " " + sver); - xml::serializer s (rs.content (), title); - - s << HTML - << HEAD - << TITLE << title << ~TITLE - << CSS_LINKS (path ("package-version-details.css"), root) - << ~HEAD - << BODY - << DIV_HEADER (root, options_->logo (), options_->menu ()) - << DIV(ID="content"); - - if (full) - s << CLASS("full"); - - s << DIV(ID="heading") - << H1 - << A(HREF=root / path (mime_url_encode (name))) << name << ~A - << "/" - << A(HREF=url ()) << sver << ~A - << ~H1 - << A(HREF=url (!full)) << (full ? "[brief]" : "[full]") << ~A - << ~DIV; - - bool not_found (false); - shared_ptr pkg; - - session sn; - transaction t (db_->begin ()); - - try - { - pkg = db_->load (package_id (name, ver)); - - // If the requested package turned up to be an "external" one just - // respond that no "internal" package is present. - // - not_found = !pkg->internal (); - } - catch (const object_not_persistent& ) - { - not_found = true; - } - - if (not_found) - throw invalid_request (404, "Package '" + title + "' not found"); - - s << H2 << pkg->summary << ~H2; - - static const string id ("description"); - if (const auto& d = pkg->description) - s << (full - ? P_DESCRIPTION (*d, id) - : P_DESCRIPTION (*d, options_->package_description (), - url (!full, id))); - - assert (pkg->location && pkg->sha256sum); - - s << TABLE(CLASS="proplist", ID="version") - << TBODY - - // Repeat version here since it can be cut out in the header. - // - << TR_VERSION (pkg->version.string ()) - - << TR_PRIORITY (pkg->priority) - << TR_LICENSES (pkg->license_alternatives) - << TR_LOCATION (pkg->internal_repository.object_id (), root) - << TR_DOWNLOAD (pkg->internal_repository.load ()->location.string () + - "/" + pkg->location->string ()) - << TR_SHA256SUM (*pkg->sha256sum) - << ~TBODY - << ~TABLE - - << TABLE(CLASS="proplist", ID="package") - << TBODY - << TR_URL (pkg->url) - << TR_EMAIL (pkg->email); - - const auto& pu (pkg->package_url); - if (pu && *pu != pkg->url) - s << TR_URL (*pu, "pkg-url"); - - const auto& pe (pkg->package_email); - if (pe && *pe != pkg->email) - s << TR_EMAIL (*pe, "pkg-email"); - - s << TR_TAGS (pkg->tags, root) - << ~TBODY - << ~TABLE; - - const auto& ds (pkg->dependencies); - if (!ds.empty ()) - { - s << H3 << "Depends" << ~H3 - << TABLE(CLASS="proplist", ID="depends") - << TBODY; - - for (const auto& da: ds) - { - s << TR(CLASS="depends") - << TH; - - if (da.conditional) - s << "?"; - - s << ~TH - << TD - << SPAN(CLASS="value"); - - for (const auto& d: da) - { - if (&d != &da[0]) - s << " | "; - - shared_ptr p (d.package.load ()); - assert (p->internal () || !p->other_repositories.empty ()); - - shared_ptr r ( - p->internal () - ? p->internal_repository.load () - : p->other_repositories[0].load ()); - - const auto& dcon (d.constraint); - const string& dname (p->id.name); - string ename (mime_url_encode (dname)); - - if (r->url) - { - string u (*r->url + ename); - s << A(HREF=u) << dname << ~A; - - if (dcon) - s << ' ' << A(HREF=u + "/" + p->version.string ()) << *dcon << ~A; - } - else if (p->internal ()) - { - path u (root / path (ename)); - s << A(HREF=u) << dname << ~A; - - if (dcon) - s << ' ' << A(HREF=u / path (p->version.string ())) << *dcon << ~A; - } - else - // Display the dependency as a plain text if no repository URL - // available. - // - s << d; - } - - s << ~SPAN - << SPAN_COMMENT (da.comment) - << ~TD - << ~TR; - } - - s << ~TBODY - << ~TABLE; - } - - t.commit (); - - const auto& rm (pkg->requirements); - if (!rm.empty ()) - { - s << H3 << "Requires" << ~H3 - << TABLE(CLASS="proplist", ID="requires") - << TBODY; - - for (const auto& ra: rm) - { - s << TR(CLASS="requires") - << TH; - - if (ra.conditional) - s << "?"; - - s << ~TH - << TD - << SPAN(CLASS="value"); - - for (const auto& r: ra) - { - if (&r != &ra[0]) - s << " | "; - - s << r; - } - - s << ~SPAN - << SPAN_COMMENT (ra.comment) - << ~TD - << ~TR; - } - - s << ~TBODY - << ~TABLE; - } - - const auto& ch (pkg->changes); - if (!ch.empty ()) - s << H3 << "Changes" << ~H3 - << (full - ? PRE_CHANGES (ch) - : PRE_CHANGES (ch, - options_->package_changes (), - url (!full, "changes"))); - - s << ~DIV - << ~BODY - << ~HTML; - - return true; -} diff --git a/brep/mod-repository-details b/brep/mod-repository-details deleted file mode 100644 index 49ad629..0000000 --- a/brep/mod-repository-details +++ /dev/null @@ -1,42 +0,0 @@ -// file : brep/mod-repository-details -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BREP_MOD_REPOSITORY_DETAILS -#define BREP_MOD_REPOSITORY_DETAILS - -#include -#include - -#include -#include - -namespace brep -{ - class repository_details: public database_module - { - public: - repository_details () = default; - - // Create a shallow copy (handling instance) if initialized and a deep - // copy (context exemplar) otherwise. - // - explicit - repository_details (const repository_details&); - - virtual bool - handle (request&, response&); - - virtual const cli::options& - cli_options () const {return options::repository_details::description ();} - - private: - virtual void - init (cli::scanner&); - - private: - shared_ptr options_; - }; -} - -#endif // BREP_MOD_REPOSITORY_DETAILS diff --git a/brep/mod-repository-details.cxx b/brep/mod-repository-details.cxx deleted file mode 100644 index b0e2b95..0000000 --- a/brep/mod-repository-details.cxx +++ /dev/null @@ -1,146 +0,0 @@ -// file : brep/mod-repository-details.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -#include // tzset() - -#include -#include // max() - -#include - -#include -#include - -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -using namespace std; -using namespace odb::core; -using namespace brep::cli; - -// While currently the user-defined copy constructor is not required (we don't -// need to deep copy nullptr's), it is a good idea to keep the placeholder -// ready for less trivial cases. -// -brep::repository_details:: -repository_details (const repository_details& r) - : database_module (r), - options_ (r.initialized_ ? r.options_ : nullptr) -{ -} - -void brep::repository_details:: -init (scanner& s) -{ - MODULE_DIAG; - - options_ = make_shared ( - s, unknown_mode::fail, unknown_mode::fail); - - database_module::init (*options_); - - if (options_->root ().empty ()) - options_->root (dir_path ("/")); - - tzset (); // To use butl::to_stream() later on. -} - -bool brep::repository_details:: -handle (request& rq, response& rs) -{ - using namespace web::xhtml; - - MODULE_DIAG; - - const dir_path& root (options_->root ()); - - // Make sure no parameters passed. - // - try - { - name_value_scanner s (rq.parameters ()); - params::repository_details (s, unknown_mode::fail, unknown_mode::fail); - } - catch (const unknown_argument& e) - { - throw invalid_request (400, e.what ()); - } - - static const string title ("About"); - xml::serializer s (rs.content (), title); - - s << HTML - << HEAD - << TITLE << title << ~TITLE - << CSS_LINKS (path ("repository-details.css"), root) - << ~HEAD - << BODY - << DIV_HEADER (root, options_->logo (), options_->menu ()) - << DIV(ID="content"); - - transaction t (db_->begin ()); - - using query = query; - - for (const auto& r: - db_->query ( - query::internal + "ORDER BY" + query::priority)) - { - //@@ Feels like a lot of trouble (e.g., id_attribute()) for very - // dubious value. A link to the package search page just for - // this repository would probably be more useful. - // - string id (html_id (r.name)); - s << H1(ID=id) - << A(HREF="#" + web::mime_url_encode (id)) << r.display_name << ~A - << ~H1; - - if (r.summary) - s << H2 << *r.summary << ~H2; - - if (r.description) - s << P_DESCRIPTION (*r.description); - - if (r.email) - { - const email& e (*r.email); - - s << P - << A(HREF="mailto:" + e) << e << ~A; - - if (!e.comment.empty ()) - s << " (" << e.comment << ")"; - - s << ~P; - } - - ostringstream o; - butl::to_stream (o, - max (r.packages_timestamp, r.repositories_timestamp), - "%Y-%m-%d %H:%M:%S%[.N] %Z", - true, - true); - - s << P << o.str () << ~P; - } - - t.commit (); - - s << ~DIV - << ~BODY - << ~HTML; - - return true; -} diff --git a/brep/mod-repository-root b/brep/mod-repository-root deleted file mode 100644 index f2c2ba0..0000000 --- a/brep/mod-repository-root +++ /dev/null @@ -1,60 +0,0 @@ -// file : brep/mod-repository-root -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BREP_MOD_REPOSITORY_ROOT -#define BREP_MOD_REPOSITORY_ROOT - -#include -#include - -#include -#include - -namespace brep -{ - class package_search; - class package_details; - class package_version_details; - class repository_details; - - class repository_root: public module - { - public: - repository_root (); - - // Create a shallow copy (handling instance) if initialized and a deep - // copy (context exemplar) otherwise. - // - explicit - repository_root (const repository_root&); - - private: - virtual bool - handle (request&, response&); - - virtual const cli::options& - cli_options () const {return options::repository_root::description ();} - - virtual option_descriptions - options (); - - virtual void - init (const name_values&); - - virtual void - init (cli::scanner&); - - virtual void - version (); - - private: - shared_ptr package_search_; - shared_ptr package_details_; - shared_ptr package_version_details_; - shared_ptr repository_details_; - shared_ptr options_; - }; -} - -#endif // BREP_MOD_REPOSITORY_ROOT diff --git a/brep/mod-repository-root.cxx b/brep/mod-repository-root.cxx deleted file mode 100644 index 7d0adc9..0000000 --- a/brep/mod-repository-root.cxx +++ /dev/null @@ -1,263 +0,0 @@ -// file : brep/mod-repository-root.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -#include - -#include - -#include - -#include -#include -#include -#include -#include -#include - -using namespace std; -using namespace brep::cli; - -namespace brep -{ - // request_proxy - // - class request_proxy: public request - { - public: - request_proxy (request& r, const name_values& p) - : request_ (r), parameters_ (p) {} - - virtual const path_type& - path () {return request_.path ();} - - virtual const name_values& - parameters () {return parameters_;} - - virtual const name_values& - cookies () {return request_.cookies ();} - - virtual istream& - content (bool buffer) {return request_.content (buffer);} - - private: - request& request_; - const name_values& parameters_; - }; - - // repository_root - // - repository_root:: - repository_root () - : package_search_ (make_shared ()), - package_details_ (make_shared ()), - package_version_details_ (make_shared ()), - repository_details_ (make_shared ()) - { - } - - repository_root:: - repository_root (const repository_root& r) - : module (r), - // - // Deep/shallow-copy sub-modules depending on whether this is an - // exemplar/handler. - // - package_search_ ( - r.initialized_ - ? r.package_search_ - : make_shared (*r.package_search_)), - package_details_ ( - r.initialized_ - ? r.package_details_ - : make_shared (*r.package_details_)), - package_version_details_ ( - r.initialized_ - ? r.package_version_details_ - : make_shared ( - *r.package_version_details_)), - repository_details_ ( - r.initialized_ - ? r.repository_details_ - : make_shared (*r.repository_details_)), - options_ ( - r.initialized_ - ? r.options_ - : nullptr) - { - } - - // Return amalgamation of repository_root and all its sub-modules option - // descriptions. - // - option_descriptions repository_root:: - options () - { - option_descriptions r (module::options ()); - append (r, package_search_->options ()); - append (r, package_details_->options ()); - append (r, package_version_details_->options ()); - append (r, repository_details_->options ()); - return r; - } - - // Initialize sub-modules and parse own configuration options. - // - void repository_root:: - init (const name_values& v) - { - auto sub_init ([this, &v](module& m) - { - m.init (filter (v, m.options ()), *log_); - }); - - // Initialize sub-modules. - // - sub_init (*package_search_); - sub_init (*package_details_); - sub_init (*package_version_details_); - sub_init (*repository_details_); - - // Parse own configuration options. - // - module::init ( - filter (v, convert (options::repository_root::description ()))); - } - - void repository_root:: - init (scanner& s) - { - MODULE_DIAG; - - options_ = make_shared ( - s, unknown_mode::fail, unknown_mode::fail); - - if (options_->root ().empty ()) - options_->root (dir_path ("/")); - } - - bool repository_root:: - handle (request& rq, response& rs) - { - MODULE_DIAG; - - const dir_path& root (options_->root ()); - - const path& rpath (rq.path ()); - if (!rpath.sub (root)) - return false; - - const path& lpath (rpath.leaf (root)); - - // Delegate the request handling to the sub-module. Intercept exception - // handling to add sub-module attribution. - // - auto handle = [&rs, this](module& m, request& rq, const char* name) -> bool - { - try - { - return m.handle (rq, rs, *log_); - } - catch (const invalid_request&) - { - // Preserve invalid_request exception type, so the web server can - // properly respond to the client with a 4XX error code. - // - throw; - } - catch (const std::exception& e) - { - // All exception types inherited from std::exception (and different - // from invalid_request) are handled by the web server as - // std::exception. The only sensible way to handle them is to respond - // to the client with the internal server error (500) code. By that - // reason it is valid to reduce all these types to a single one. - // Note that the server_error exception is handled internally by the - // module::handle() function call. - // - throw runtime_error (string (name) + ": " + e.what ()); - } - }; - - if (lpath.empty ()) - { - // Dispatch request handling to the repository_details or the - // package_search module depending on the function name passed as a - // first HTTP request parameter. The parameter should have no value - // specified. Example: cppget.org/?about - // - const name_values& params (rq.parameters ()); - if (!params.empty () && !params.front ().value) - { - if (params.front ().name == "about") - { - // Cleanup not to confuse the selected module with the unknown - // parameter. - // - name_values p (params); - p.erase (p.begin ()); - - request_proxy rp (rq, p); - repository_details m (*repository_details_); - return handle (m, rp, "repository_details"); - } - - throw invalid_request (400, "unknown function"); - } - else - { - package_search m (*package_search_); - return handle (m, rq, "package_search"); - } - } - else - { - // Dispatch request handling to the package_details or the - // package_version_details module depending on the HTTP request URL path. - // - auto i (lpath.begin ()); - assert (i != lpath.end ()); - - const string& n (*i++); // Package name. - - // Check if this is a package name and not a brep static content files - // (CSS) directory name, a repository directory name, or a special file - // name (the one starting with '.'). - // - // @@ Shouldn't we validate that the package name is not "@", is not - // digit-only, does not start with '.' while parsing and serializing - // the package manifest ? Probably also need to mention these - // contraints in the manifest.txt file. - // - if (n != "@" && n.find_first_not_of ("0123456789") != string::npos && - n[0] != '.') - { - if (i == lpath.end ()) - { - package_details m (*package_details_); - return handle (m, rq, "package_details"); - } - else if (++i == lpath.end ()) - { - package_version_details m (*package_version_details_); - return handle (m, rq, "package_version_details"); - } - } - } - - return false; - } - - void repository_root:: - version () - { - MODULE_DIAG; - - info << "module " << BREP_VERSION_STR - << ", libbrep " << LIBBREP_VERSION_STR - << ", libbpkg " << LIBBPKG_VERSION_STR - << ", libbutl " << LIBBUTL_VERSION_STR; - } -} diff --git a/brep/module b/brep/module deleted file mode 100644 index 7e5763a..0000000 --- a/brep/module +++ /dev/null @@ -1,201 +0,0 @@ -// file : brep/module -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BREP_MODULE -#define BREP_MODULE - -#include - -#include -#include - -#include -#include - -namespace brep -{ - // Bring in commonly used names from the web namespace. - // - // @@ Maybe doing using namespace is the right way to handle this. - // There will, however, most likely be a conflict between - // web::module and our module. Or maybe not, need to try. - // - using web::status_code; - using web::invalid_request; - using web::sequence_error; - using web::option_descriptions; - using web::name_value; - using web::name_values; - using web::request; - using web::response; - using web::log; - - // This exception indicated a server error (5XX). In particular, - // it is thrown by the fail diagnostics stream and is caught by the - // module implementation where it is both logged as an error and - // returned to the user with the 5XX status code. - // - struct server_error - { - diag_data data; - - server_error (diag_data&& d): data (move (d)) {} - }; - - // Every module member function that needs to produce any diagnostics - // shall begin with: - // - // MODULE_DIAG; - // - // This will instantiate the fail, error, warn, info, and trace - // diagnostics streams with the function's name. - // -#define MODULE_DIAG \ - const fail_mark fail (__PRETTY_FUNCTION__); \ - const basic_mark error (severity::error, \ - this->log_writer_, \ - __PRETTY_FUNCTION__); \ - const basic_mark warn (severity::warning, \ - this->log_writer_, \ - __PRETTY_FUNCTION__); \ - const basic_mark info (severity::info, \ - this->log_writer_, \ - __PRETTY_FUNCTION__); \ - const basic_mark trace (severity::trace, \ - this->log_writer_, \ - __PRETTY_FUNCTION__) - - // Adaptation of the web::module to our needs. - // - class module: public web::module - { - // Diagnostics. - // - protected: - // Trace verbosity level. - // - // 0 - tracing disabled. - // 1 - brief information regarding irregular situations, which not being - // an error can be of some interest. - // 2 - @@ TODO: document - // - // While uint8 is more than enough, use uint16 for the ease of printing. - // - uint16_t verb_ = 0; - - template void l1 (const F& f) const {if (verb_ >= 1) f ();} - template void l2 (const F& f) const {if (verb_ >= 2) f ();} - - // Set to true when the module is successfully initialized. - // - bool initialized_ {false}; - - // Implementation details. - // - protected: - module (); - module (const module& ); - - static name_values - filter (const name_values&, const option_descriptions&); - - static option_descriptions - convert (const cli::options&); - - static void - append (option_descriptions& dst, const cli::options& src); - - static void - append (option_descriptions& dst, const option_descriptions& src); - - // Can be used by module implementation to parse HTTP request parameters. - // - class name_value_scanner: public cli::scanner - { - public: - name_value_scanner (const name_values&) noexcept; - - virtual bool - more (); - - virtual const char* - peek (); - - virtual const char* - next (); - - virtual void - skip (); - - private: - const name_values& name_values_; - name_values::const_iterator i_; - bool name_; - }; - - public: - virtual const cli::options& - cli_options () const = 0; - - virtual void - init (cli::scanner&) = 0; - - // Can be overriden by custom request dispatcher to initialize - // sub-modules. - // - virtual void - init (const name_values&); - - virtual void - init (const name_values&, log&); - - virtual bool - handle (request&, response&) = 0; - - virtual bool - handle (request&, response&, log&); - - // web::module interface. - // - public: - // Custom request dispatcher can aggregate its own option descriptions - // with sub-modules option descriptions. In this case it should still call - // the base implementation in order to include the brep::module's options. - // - virtual option_descriptions - options (); - - private: - virtual void - version (log&); - - // Can be overriden by the module implementation to log version, etc. - // - virtual void - version () {} - - name_values - expand_options (const name_values&); - - // Diagnostics implementation details. - // - protected: - log* log_ {nullptr}; // Diagnostics backend provided by the web server. - - private: - // Extract function name from a __PRETTY_FUNCTION__. - // Throw invalid_argument if fail to parse. - // - static string - func_name (const char* pretty_name); - - void - log_write (const diag_data&) const; - - protected: - const diag_epilogue log_writer_; - }; -} - -#endif // BREP_MODULE diff --git a/brep/module.cxx b/brep/module.cxx deleted file mode 100644 index 68969eb..0000000 --- a/brep/module.cxx +++ /dev/null @@ -1,410 +0,0 @@ -// file : brep/module.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -#include -#include - -#include -#include // strchr() -#include // bind() - -#include -#include - -#include - -using namespace std; -using namespace placeholders; // For std::bind's _1, etc. - -namespace brep -{ - // module - // - bool module:: - handle (request& rq, response& rs, log& l) - { - log_ = &l; - - try - { - // Web server should terminate if initialization failed. - // - assert (initialized_); - - return handle (rq, rs); - } - catch (const server_error& e) - { - log_write (e.data); - - try - { - static const char* sev_str[] = {"error", "warning", "info", "trace"}; - ostream& o (rs.content (500, "text/plain;charset=utf-8")); - - for (const auto& d: e.data) - { - string name; - - try - { - name = func_name (d.name); - } - catch (const invalid_argument&) - { - // Log "pretty" function description, see in log file & fix. - name = d.name; - } - - o << name << ": " << sev_str[static_cast (d.sev)] << ": " - << d.msg << endl; - } - } - catch (const sequence_error&) - { - // We tried to return the error status/description but some - // content has already been written. Nothing we can do about - // it. - } - } - - return true; - } - - option_descriptions module:: - convert (const cli::options& o) - { - option_descriptions r; - append (r, o); - return r; - } - - void module:: - append (option_descriptions& dst, const cli::options& src) - { - for (const auto& o: src) - { - bool v (!o.flag ()); - auto i (dst.emplace (o.name (), v)); - assert (i.first->second == v); // Consistent option/flag. - - for (const auto& a: o.aliases ()) - { - i = dst.emplace (a, v); - assert (i.first->second == v); - } - } - } - - void module:: - append (option_descriptions& dst, const option_descriptions& src) - { - for (const auto& o: src) - { - auto i (dst.emplace (o)); - assert (i.first->second == o.second); // Consistent option/flag. - } - } - - name_values module:: - filter (const name_values& v, const option_descriptions& d) - { - name_values r; - for (const auto& nv: v) - { - if (d.find (nv.name) != d.end ()) - r.push_back (nv); - } - - return r; - } - - // Convert CLI option descriptions to the general interface of option - // descriptions, extend with brep::module own option descriptions. - // - option_descriptions module:: - options () - { - option_descriptions r ({{"conf", true}}); - append (r, options::module::description ()); - append (r, cli_options ()); - return r; - } - - // Expand option list parsing configuration files. - // - name_values module:: - expand_options (const name_values& v) - { - using namespace cli; - - vector argv; - for (const auto& nv: v) - { - argv.push_back (nv.name.c_str ()); - - if (nv.value) - argv.push_back (nv.value->c_str ()); - } - - int argc (argv.size ()); - argv_file_scanner s (0, argc, const_cast (argv.data ()), "conf"); - - name_values r; - const option_descriptions& o (options ()); - - while (s.more ()) - { - string n (s.next ()); - auto i (o.find (n)); - - if (i == o.end ()) - throw unknown_argument (n); - - optional v; - if (i->second) - v = s.next (); - - r.emplace_back (move (n), move (v)); - } - - return r; - } - - // Parse options with a cli-generated scanner. Options verb and conf are - // recognized by brep::module::init while others to be interpreted by the - // derived init(). If there is an option which can not be interpreted - // neither by brep::module nor by the derived class, then the web server - // is terminated with a corresponding error message being logged. Though - // this should not happen if the options() function returned the correct - // set of options. - // - void module:: - init (const name_values& options, log& log) - { - assert (!initialized_); - - log_ = &log; - - try - { - name_values opts (expand_options (options)); - - // Read module implementation configuration. - // - init (opts); - - // Read brep::module configuration. - // - static option_descriptions od ( - convert (options::module::description ())); - - name_values mo (filter (opts, od)); - name_value_scanner s (mo); - options::module o (s, cli::unknown_mode::fail, cli::unknown_mode::fail); - - verb_ = o.verbosity (); - initialized_ = true; - } - catch (const server_error& e) - { - log_write (e.data); - throw runtime_error ("initialization failed"); - } - catch (const cli::exception& e) - { - ostringstream o; - e.print (o); - throw runtime_error (o.str ()); - } - } - - void module:: - init (const name_values& options) - { - name_value_scanner s (options); - init (s); - assert (!s.more ()); // Module didn't handle its options. - } - - module:: - module (): log_writer_ (bind (&module::log_write, this, _1)) {} - - // Custom copy constructor is required to initialize log_writer_ properly. - // - module:: - module (const module& m): module () - { - verb_ = m.verb_; - initialized_ = m.initialized_; - } - -// For function func declared like this: -// using B = std::string (*)(int); -// using A = B (*)(int,int); -// A func(B (*)(char),B (*)(wchar_t)); -// __PRETTY_FUNCTION__ looks like this: -// virtual std::string (* (* brep::search::func(std::string (* (*)(char))(int) -// ,std::string (* (*)(wchar_t))(int)) const)(int, int))(int) -// - string module:: - func_name (const char* pretty_name) - { - const char* e (strchr (pretty_name, ')')); - - if (e && e > pretty_name) - { - // Position e at last matching '(' which is the beginning of the - // argument list.. - // - size_t d (1); - - do - { - switch (*--e) - { - case ')': ++d; break; - case '(': --d; break; - } - } - while (d && e > pretty_name); - - if (!d && e > pretty_name) - { - // Position e at the character following the function name. - // - while (e > pretty_name && - (*e != '(' || *(e - 1) == ' ' || *(e - 1) == ')')) - --e; - - if (e > pretty_name) - { - // Position b at the beginning of the qualified function name. - // - const char* b (e); - while (--b > pretty_name && *b != ' '); - if (*b == ' ') ++b; - - return string (b, e - b); - } - } - } - - throw invalid_argument ("::brep::module::func_name"); - } - - void module:: - log_write (const diag_data& d) const - { - if (log_ == nullptr) - return; // No backend yet. - - //@@ Cast log_ to apache::log and write the records. - // - auto al (dynamic_cast (log_)); - - if (al) - { - // Considered using lambda for mapping but looks too verbose while can - // be a bit safer in runtime. - // - // Use APLOG_INFO (as opposed to APLOG_TRACE1) as a mapping for - // severity::trace. "LogLevel trace1" configuration directive switches - // on the avalanche of log messages from various modules. Would be good - // to avoid wading through them. - // - static int s[] = {APLOG_ERR, APLOG_WARNING, APLOG_INFO, APLOG_INFO}; - - for (const auto& e: d) - { - string name; - - try - { - name = func_name (e.name); - } - catch (const invalid_argument&) - { - // Log "pretty" function description, see in log file & fix. - name = e.name; - } - - al->write (e.loc.file.c_str (), - e.loc.line, - name.c_str (), - s[static_cast (e.sev)], - e.msg.c_str ()); - } - } - } - - void module:: - version (log& l) - { - log_ = &l; - version (); - } - - // module::name_value_scanner - // - module::name_value_scanner:: - name_value_scanner (const name_values& nv) noexcept - : name_values_ (nv), - i_ (nv.begin ()), - name_ (true) - { - } - - bool module::name_value_scanner:: - more () - { - return i_ != name_values_.end (); - } - - const char* module::name_value_scanner:: - peek () - { - if (i_ != name_values_.end ()) - return name_ ? i_->name.c_str () : i_->value->c_str (); - else - throw cli::eos_reached (); - } - - const char* module::name_value_scanner:: - next () - { - if (i_ != name_values_.end ()) - { - const char* r (name_ ? i_->name.c_str () : i_->value->c_str ()); - skip (); - return r; - } - else - throw cli::eos_reached (); - } - - void module::name_value_scanner:: - skip () - { - if (i_ != name_values_.end ()) - { - if (name_) - { - if (i_->value) - name_ = false; - else - ++i_; - } - else - { - ++i_; - name_ = true; - } - } - else - throw cli::eos_reached (); - } -} diff --git a/brep/options-types b/brep/options-types deleted file mode 100644 index 922637b..0000000 --- a/brep/options-types +++ /dev/null @@ -1,32 +0,0 @@ -// file : brep/options-types -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BREP_OPTIONS_TYPES -#define BREP_OPTIONS_TYPES - -#include -#include - -namespace brep -{ - // brep types - // - enum class page_form - { - full, - brief - }; - - struct page_menu - { - string label; - string link; - - page_menu () = default; - page_menu (string b, string l): label (move (b)), link (move (l)) {} - }; - -} - -#endif // BREP_OPTIONS_TYPES diff --git a/brep/options.cli b/brep/options.cli deleted file mode 100644 index d63a561..0000000 --- a/brep/options.cli +++ /dev/null @@ -1,211 +0,0 @@ -// file : brep/options.cli -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -include ; - -include ; - -include ; - -namespace brep -{ - // Web module configuration options. - // - namespace options - { - // Option groups. - // - class module - { - dir_path root = "/" - { - "" - "Repository root. That is, this is the part of the URL between the - host name and the start of the repository. For example, root value - '\cb{/pkg}' means the repository URL is http://example.org/pkg/. - Specify '\cb{/}' to use the web server root (http://example.org/)." - } - - uint16_t verbosity = 0 - { - "", - "Trace verbosity level. Level 0 disables tracing, which is also the - default." - } - }; - - class db - { - string db-user - { - "", - "Database user name. If not specified, then operating system (login) - name is used." - } - - string db-password - { - "", - "Database password. If not specified, then login without password is - expected to work." - } - - string db-name = "brep" - { - "", - "Database name. If not specified, then '\cb{brep}' is used by - default." - } - - string db-host - { - "", - "Database host name, address, or socket. If not specified, then - connect to \cb{localhost} using the operating system-default - mechanism (Unix-domain socket, etc)." - } - - uint16_t db-port = 0 - { - "", - "Database port number. If not specified, the default port is used." - } - - size_t db-max-connections = 5 - { - "", - "The maximum number of concurrent database connections per web server - process. If 0, then no limitation is applied. The default is 5." - } - - size_t db-retry = 10 - { - "", - "The maximum number of times to retry database transactions in the - face of recoverable failures (deadlock, loss of connection, etc). The - default is 10." - } - }; - - class page - { - web::xhtml::fragment logo - { - "", - "Web page logo. It is displayed in the page header aligned to the left - edge. The value is treated as an XHTML5 fragment." - } - - vector menu; - { - "", - "Web page menu. Each entry is displayed in the page header in the - order specified and aligned to the right edge. A link target that - starts with '\cb{/}' or contains '\cb{:}' is used as is. Otherwise, - it is prefixed with the repository web interface root." - } - }; - - class search - { - uint16_t search-results = 10 - { - "", - "Number of results per page. The default is 10." - } - - uint16_t search-pages = 5 - { - "", - "Number of pages in navigation (pager). The default is 5." - } - }; - - class package - { - uint16_t package-description = 500 - { - "", - "Number of package description characters to display in brief pages. - The default is 500 (~ 80 characters * 6 lines)." - } - - uint16_t package-changes = 5000; - { - "", - "Number of package changes characters to display in brief pages. The - default is 5000 (~ 80 chars x 60 lines)." - } - }; - - // Module options. - // - class package_search: search, db, page, module - { - }; - - class package_details: package, search, db, page, module - { - }; - - class package_version_details: package, db, page, module - { - }; - - class repository_details: db, page, module - { - }; - - class repository_root: module - { - }; - } - - // Web module HTTP request parameters. - // - namespace params - { - // Use parameters long names in the C++ code, short aliases (if present) - // in HTTP URL. - // - class package_search - { - // Display package search result list starting from this page. - // - uint16_t page | p; - - // Package search criteria. - // - string query | q; - }; - - class package_details - { - // Display package version search result list starting from this page. - // - uint16_t page | p; - - // Package version search criteria. - // - string query | q; - - // Page form. - // - page_form form | f = page_form::brief; - }; - - class package_version_details - { - // Page form. - // - page_form form | f = page_form::brief; - }; - - class repository_details - { - // No parameters so far. - // - }; - } -} diff --git a/brep/page b/brep/page deleted file mode 100644 index 512c11a..0000000 --- a/brep/page +++ /dev/null @@ -1,402 +0,0 @@ -// file : brep/page -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BREP_PAGE -#define BREP_PAGE - -#include - -#include - -#include -#include - -#include -#include // page_menu - -namespace brep -{ - // Page common building blocks. - // - - // Generates CSS link elements. - // - class CSS_LINKS - { - public: - CSS_LINKS (const path& p, const dir_path& r): path_ (p), root_ (r) {} - - void - operator() (xml::serializer&) const; - - private: - const path& path_; - const dir_path& root_; - }; - - // Generates page header element. - // - class DIV_HEADER - { - public: - DIV_HEADER (const dir_path& root, - const web::xhtml::fragment& logo, - const vector& menu): - root_ (root), logo_ (logo), menu_ (menu) {} - - void - operator() (xml::serializer&) const; - - private: - const dir_path& root_; - const web::xhtml::fragment& logo_; - const vector& menu_; - }; - - // Generates package search form element. - // - class FORM_SEARCH - { - public: - FORM_SEARCH (const string& q): query_ (q) {} - - void - operator() (xml::serializer&) const; - - private: - const string& query_; - }; - - // 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. - // - class DIV_COUNTER - { - public: - DIV_COUNTER (size_t c, const char* s, const char* p) - : count_ (c), singular_ (s), plural_ (p) {} - - void - operator() (xml::serializer&) const; - - private: - size_t count_; - const char* singular_; - const char* plural_; - }; - - // Generates package name element. - // - class TR_NAME - { - public: - TR_NAME (const string& n, const string& q, const dir_path& r) - : name_ (n), query_param_ (q), root_ (r) {} - - void - operator() (xml::serializer&) const; - - private: - const string& name_; - const string& query_param_; - const dir_path& root_; - }; - - // Generates package version element. - // - class TR_VERSION - { - public: - // Display the version as a link to the package version details page. - // - TR_VERSION (const string& p, const string& v, const dir_path& r) - : package_ (&p), version_ (v), root_ (&r) {} - - // Display the version as a regular text. - // - TR_VERSION (const string& v) - : package_ (nullptr), version_ (v), root_ (nullptr) {} - - void - operator() (xml::serializer&) const; - - private: - const string* package_; - const string& version_; - const dir_path* root_; - }; - - // Generates package summary element. - // - class TR_SUMMARY - { - public: - TR_SUMMARY (const string& s): summary_ (s) {} - - void - operator() (xml::serializer&) const; - - private: - const string& summary_; - }; - - // Generates package license alternatives element. - // - class TR_LICENSE - { - public: - TR_LICENSE (const license_alternatives& l): licenses_ (l) {} - - void - operator() (xml::serializer&) 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&) const; - - private: - const license_alternatives& licenses_; - }; - - // Generates package tags element. - // - class TR_TAGS - { - public: - TR_TAGS (const strings& ts, const dir_path& r): tags_ (ts), root_ (r) {} - - void - operator() (xml::serializer&) const; - - private: - const strings& tags_; - const dir_path& root_; - }; - - // Generates package dependencies element. - // - class TR_DEPENDS - { - public: - TR_DEPENDS (const dependencies& d, const dir_path& r) - : dependencies_ (d), root_ (r) {} - - void - operator() (xml::serializer&) const; - - private: - const dependencies& dependencies_; - const dir_path& root_; - }; - - // Generates package requirements element. - // - class TR_REQUIRES - { - public: - TR_REQUIRES (const requirements& r): requirements_ (r) {} - - void - operator() (xml::serializer&) 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&) 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&) const; - - private: - const email& email_; - const char* label_; - }; - - // Generates package version priority element. - // - class TR_PRIORITY - { - public: - TR_PRIORITY (const priority& p): priority_ (p) {} - - void - operator() (xml::serializer&) const; - - private: - const priority& priority_; - }; - - // Generates package location element. - // - class TR_LOCATION - { - public: - TR_LOCATION (const string& n, const dir_path& r) - : name_ (n), root_ (r) {} - - void - operator() (xml::serializer&) const; - - private: - const string& name_; - const dir_path& root_; - }; - - // Generates package download URL element. - // - class TR_DOWNLOAD - { - public: - TR_DOWNLOAD (const string& u): url_ (u) {} - - void - operator() (xml::serializer&) const; - - private: - const string& url_; - }; - - // Generates sha256sum element. - // - class TR_SHA256SUM - { - public: - TR_SHA256SUM (const string& s): sha256sum_ (s) {} - - void - operator() (xml::serializer&) const; - - private: - const string& sha256sum_; - }; - - // Generates comment element. - // - class SPAN_COMMENT - { - public: - SPAN_COMMENT (const string& c): comment_ (c) {} - - void - operator() (xml::serializer&) const; - - private: - const string& comment_; - }; - - // Generates package description element. - // - class P_DESCRIPTION - { - public: - // Genereate full description. - // - P_DESCRIPTION (const string& d, const string& id = "") - : description_ (d), length_ (d.size ()), url_ (nullptr), id_ (id) {} - - // Genereate brief description. - // - P_DESCRIPTION (const string& d, size_t l, const string& u) - : description_ (d), length_ (l), url_ (&u) {} - - void - operator() (xml::serializer&) const; - - private: - const string& description_; - size_t length_; - const string* url_; // Full page url. - string id_; - }; - - // Generates package description element. - // - class PRE_CHANGES - { - public: - // Genereate full changes info. - // - PRE_CHANGES (const string& c) - : changes_ (c), length_ (c.size ()), url_ (nullptr) {} - - // Genereate brief changes info. - // - PRE_CHANGES (const string& c, size_t l, const string& u) - : changes_ (c), length_ (l), url_ (&u) {} - - void - operator() (xml::serializer&) const; - - private: - const string& changes_; - size_t length_; - const string* url_; // Full page url. - }; - - // Generates paging element. - // - class DIV_PAGER - { - public: - DIV_PAGER (size_t current_page, - size_t item_count, - size_t item_per_page, - size_t page_number_count, - const string& url); - - void - operator() (xml::serializer&) const; - - private: - size_t current_page_; - size_t item_count_; - size_t item_per_page_; - size_t page_number_count_; - const string& url_; - }; - - // Convert the argument to a string representing the valid HTML 5 'id' - // attribute value. - // - string - html_id (const string&); -} - -#endif // BREP_PAGE diff --git a/brep/page.cxx b/brep/page.cxx deleted file mode 100644 index 689ee20..0000000 --- a/brep/page.cxx +++ /dev/null @@ -1,693 +0,0 @@ -// file : brep/page.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -#include -#include // hex, uppercase, right -#include -#include // setw(), setfill() -#include // min() - -#include - -#include -#include - -#include -#include - -using namespace std; -using namespace xml; -using namespace web; -using namespace web::xhtml; - -namespace brep -{ - // CSS_LINKS - // - void CSS_LINKS:: - operator() (serializer& s) const - { - static const path css ("@"); - - s << *LINK(REL="stylesheet", TYPE="text/css", HREF=root_ / css / path_); - } - - // DIV_HEADER - // - void DIV_HEADER:: - operator() (serializer& s) const - { - if (!logo_.empty () || !menu_.empty ()) - { - s << DIV(ID="header-bar") - << DIV(ID="header"); - - if (!logo_.empty ()) - s << DIV(ID="header-logo") << logo_ << ~DIV; - - if (!menu_.empty ()) - { - s << DIV(ID="header-menu") - << DIV(ID="header-menu-body"); - - for (const auto& m: menu_) - { - const string& l (m.link[0] == '/' || m.link.find (':') != string::npos - ? m.link - : root_.string () + m.link); - - s << A(HREF=l) << m.label << ~A; - } - - s << ~DIV - << ~DIV; - } - - s << ~DIV - << ~DIV; - } - } - - // FORM_SEARCH - // - void FORM_SEARCH:: - operator() (serializer& s) const - { - // The 'action' attribute is optional in HTML5. While the standard doesn'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; - } - - // DIV_COUNTER - // - void DIV_COUNTER:: - operator() (serializer& s) const - { - s << DIV(ID="count") - << count_ << " " - << (count_ % 10 == 1 && count_ % 100 != 11 ? singular_ : plural_) - << ~DIV; - } - - // TR_NAME - // - void TR_NAME:: - operator() (serializer& s) const - { - s << TR(CLASS="name") - << TH << "name" << ~TH - << TD - << SPAN(CLASS="value") - << A - << HREF - - // Propagate search criteria to the package details page. - // - << root_ / path (mime_url_encode (name_)) << query_param_ - - << ~HREF - << name_ - << ~A - << ~SPAN - << ~TD - << ~TR; - } - - void TR_VERSION:: - operator() (serializer& s) const - { - s << TR(CLASS="version") - << TH << "version" << ~TH - << TD - << SPAN(CLASS="value"); - - if (package_ == nullptr) - s << version_; - else - { - assert (root_ != nullptr); - s << A(HREF=*root_ / path (mime_url_encode (*package_)) / path (version_)) - << version_ - << ~A; - } - - s << ~SPAN - << ~TD - << ~TR; - } - - // TR_SUMMARY - // - void TR_SUMMARY:: - operator() (serializer& s) const - { - s << TR(CLASS="summary") - << TH << "summary" << ~TH - << TD << SPAN(CLASS="value") << summary_ << ~SPAN << ~TD - << ~TR; - } - - // TR_LICENSE - // - void TR_LICENSE:: - operator() (serializer& s) const - { - s << TR(CLASS="license") - << TH << "license" << ~TH - << TD - << SPAN(CLASS="value"); - - for (const auto& la: licenses_) - { - if (&la != &licenses_[0]) - s << " " << EM << "or" << ~EM << " "; - - bool m (la.size () > 1); - - if (m) - s << "("; - - for (const auto& l: la) - { - if (&l != &la[0]) - s << " " << EM << "and" << ~EM << " "; - - s << l; - } - - if (m) - s << ")"; - } - - s << ~SPAN - << ~TD - << ~TR; - } - - // 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; - } - } - - // TR_TAGS - // - void TR_TAGS:: - operator() (serializer& s) const - { - 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 << root_ << "?q=" << mime_url_encode (t) << ~HREF - << t - << ~A; - } - - s << ~SPAN - << ~TD - << ~TR; - } - } - - // TR_DEPENDS - // - void TR_DEPENDS:: - operator() (serializer& s) const - { - s << TR(CLASS="depends") - << TH << "depends" << ~TH - << TD - << SPAN(CLASS="value") - << dependencies_.size (); - - if (!dependencies_.empty ()) - s << "; "; - - for (const auto& d: dependencies_) - { - if (&d != &dependencies_[0]) - s << ", "; - - if (d.conditional) - s << "?"; - - // Suppress package name duplicates. - // - set names; - for (const auto& da: d) - names.emplace (da.name ()); - - bool mult (names.size () > 1); - - if (mult) - s << "("; - - bool first (true); - for (const auto& da: d) - { - string n (da.name ()); - if (names.find (n) != names.end ()) - { - names.erase (n); - - if (first) - first = false; - else - s << " | "; - - shared_ptr p (da.package.load ()); - assert (p->internal () || !p->other_repositories.empty ()); - - shared_ptr r ( - p->internal () - ? p->internal_repository.load () - : p->other_repositories[0].load ()); - - auto en (mime_url_encode (n)); - - if (r->url) - s << A(HREF=*r->url + en) << n << ~A; - else if (p->internal ()) - s << A(HREF=root_ / path (en)) << n << ~A; - else - // Display the dependency as a plain text if no repository URL - // available. - // - s << n; - } - } - - if (mult) - s << ")"; - } - - s << ~SPAN - << ~TD - << ~TR; - } - - // TR_REQUIRES - // - void TR_REQUIRES:: - operator() (serializer& s) const - { - // 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 (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 mult (r.size () > 1); - - if (mult) - s << "("; - - for (const auto& ra: r) - { - if (&ra != &r[0]) - s << " | "; - - s << ra; - } - - if (mult) - 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_) << email_ << ~A - << ~SPAN - << SPAN_COMMENT (email_.comment) - << ~TD - << ~TR; - } - - // TR_PRIORITY - // - void TR_PRIORITY:: - operator() (serializer& s) const - { - static const strings priority_names ({"low", "medium", "high", "security"}); - assert (priority_ < priority_names.size ()); - - s << TR(CLASS="priority") - << TH << "priority" << ~TH - << TD - << SPAN(CLASS="value") << priority_names[priority_] << ~SPAN - << SPAN_COMMENT (priority_.comment) - << ~TD - << ~TR; - } - - // TR_LOCATION - // - void TR_LOCATION:: - operator() (serializer& s) const - { - s << TR(CLASS="location") - << TH << "location" << ~TH - << TD - << SPAN(CLASS="value") - << A - << HREF - << root_ << "?about#" << mime_url_encode (html_id (name_)) - << ~HREF - << name_ - << ~A - << ~SPAN - << ~TD - << ~TR; - } - - // TR_DOWNLOAD - // - void TR_DOWNLOAD:: - operator() (serializer& s) const - { - s << TR(CLASS="download") - << TH << "download" << ~TH - << TD - << SPAN(CLASS="value") << A(HREF=url_) << url_ << ~A << ~SPAN - << ~TD - << ~TR; - } - - // TR_SHA256SUM - // - void TR_SHA256SUM:: - operator() (serializer& s) const - { - s << TR(CLASS="sha256") - << TH << "sha256" << ~TH - << TD << SPAN(CLASS="value") << sha256sum_ << ~SPAN << ~TD - << ~TR; - } - - // SPAN_COMMENT - // - void SPAN_COMMENT:: - operator() (serializer& s) const - { - if (size_t l = comment_.size ()) - s << SPAN(CLASS="comment") - << (comment_.back () == '.' ? string (comment_, 0, l - 1) : comment_) - << ~SPAN; - } - - // P_DESCRIPTION - // - void P_DESCRIPTION:: - operator() (serializer& s) const - { - if (description_.empty ()) - return; - - auto n (description_.find_first_of (" \t\n", length_)); - bool full (n == string::npos); // Description length is below the limit. - - // Truncate description if length exceed the limit. - // - const string& d (full ? 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; - - if (!id_.empty ()) - s << ID(id_); - - bool nl (false); // The previous character is '\n'. - for (const auto& c: d) - { - 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; - } - - s << c; - } - } - - if (!full) - { - assert (url_ != nullptr); - s << "... " << A(HREF=*url_) << "More" << ~A; - } - - s << ~P; - } - - // PRE_CHANGES - // - void PRE_CHANGES:: - operator() (serializer& s) const - { - if (changes_.empty ()) - return; - - auto n (changes_.find_first_of (" \t\n", length_)); - bool full (n == string::npos); // Changes length is below the limit. - - // Truncate changes if length exceed the limit. - // - const string& c (full ? changes_ : string (changes_, 0, n)); - s << PRE(ID="changes") << c; - - if (!full) - { - assert (url_ != nullptr); - s << "... " << A(HREF=*url_) << "More" << ~A; - } - - s << ~PRE; - } - - // DIV_PAGER - // - 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 - { - if (item_count_ == 0 || item_per_page_ == 0) - return; - - size_t pcount (item_count_ / item_per_page_); // Page count. - - if (item_count_ % item_per_page_) - ++pcount; - - if (pcount > 1) - { - auto url ( - [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=url (current_page_ - 1)) << "Prev" << ~A; - - if (page_number_count_) - { - size_t offset (page_number_count_ / 2); - size_t from (current_page_ > offset ? current_page_ - offset : 0); - size_t to (min (from + page_number_count_, pcount)); - - for (size_t p (from); p < to; ++p) - { - s << A(HREF=url (p)); - - if (p == current_page_) - s << ID("curr"); - - s << p + 1 - << ~A; - } - } - - if (current_page_ < pcount - 1) - s << A(ID="next", HREF=url (current_page_ + 1)) << "Next" << ~A; - - 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 - html_id (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 '~': - { - // We use '~' as an escape character because it doesn't require - // escaping in URLs. - // - o << "~" << setw (2) << static_cast (c); - break; - } - default: o << c; break; - } - } - - return o.str (); - } -} diff --git a/brep/services.cxx b/brep/services.cxx deleted file mode 100644 index 94c7fc6..0000000 --- a/brep/services.cxx +++ /dev/null @@ -1,15 +0,0 @@ -// file : brep/services.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include // AP_MODULE_DECLARE_DATA - -#include - -#include -#include - -#include - -static brep::repository_root mod; -web::apache::service AP_MODULE_DECLARE_DATA brep_module ("brep", mod); diff --git a/brep/types-parsers b/brep/types-parsers deleted file mode 100644 index 78b7088..0000000 --- a/brep/types-parsers +++ /dev/null @@ -1,57 +0,0 @@ -// file : brep/types-parsers -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -// CLI parsers, included into the generated source files. -// - -#ifndef BREP_TYPES_PARSERS -#define BREP_TYPES_PARSERS - -#include - -#include -#include - -#include - -namespace brep -{ - namespace cli - { - class scanner; - - template - struct parser; - - template <> - struct parser - { - static void - parse (dir_path&, scanner&); - }; - - template <> - struct parser - { - static void - parse (page_form&, scanner&); - }; - - template <> - struct parser - { - static void - parse (page_menu&, scanner&); - }; - - template <> - struct parser - { - static void - parse (web::xhtml::fragment&, scanner&); - }; - } -} - -#endif // BREP_TYPES_PARSERS diff --git a/brep/types-parsers.cxx b/brep/types-parsers.cxx deleted file mode 100644 index 67f4812..0000000 --- a/brep/types-parsers.cxx +++ /dev/null @@ -1,114 +0,0 @@ -// file : brep/types-parsers.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -#include - -using namespace std; -using namespace web::xhtml; - -namespace brep -{ - namespace cli - { - // Parse path. - // - template - static void - parse_path (T& x, scanner& s) - { - const char* o (s.next ()); - - if (!s.more ()) - throw missing_value (o); - - const char* v (s.next ()); - - try - { - x = T (v); - } - catch (const invalid_path&) - { - throw invalid_value (o, v); - } - } - - void parser:: - parse (dir_path& x, scanner& s) - { - parse_path (x, s); - } - - // Parse page_form. - // - void parser:: - parse (page_form& x, scanner& s) - { - const char* o (s.next ()); - - if (!s.more ()) - throw missing_value (o); - - const string v (s.next ()); - if (v == "full") - x = page_form::full; - else if (v == "brief") - x = page_form::brief; - else - throw invalid_value (o, v); - } - - // Parse page_menu. - // - void parser:: - parse (page_menu& x, scanner& s) - { - const char* o (s.next ()); - - if (!s.more ()) - throw missing_value (o); - - const string v (s.next ()); - - auto p (v.find ('=')); - if (p != string::npos) - { - string label (v, 0, p); - string link (v, p + 1); - - if (!label.empty ()) - { - x = page_menu (move (label), move (link)); - return; - } - } - - throw invalid_value (o, v); - } - - // Parse web::xhtml::fragment. - // - void parser:: - parse (fragment& x, scanner& s) - { - const char* o (s.next ()); - - if (!s.more ()) - throw missing_value (o); - - const char* v (s.next ()); - - try - { - x = fragment (v, o); - } - catch (const xml::parsing&) - { - throw invalid_value (o, v); - } - } - } -} diff --git a/buildfile b/buildfile index 521be1f..c74b0c9 100644 --- a/buildfile +++ b/buildfile @@ -2,7 +2,7 @@ # copyright : Copyright (c) 2014-2016 Code Synthesis Ltd # license : MIT; see accompanying LICENSE file -d = brep/ etc/ load/ migrate/ tests/ www/ doc/ +d = brep/ mod/ load/ migrate/ tests/ www/ doc/ etc/ ./: $d doc{INSTALL INSTALL-DEV LICENSE NEWS README version} file{manifest} include $d diff --git a/mod/.gitignore b/mod/.gitignore new file mode 100644 index 0000000..ddd62b8 --- /dev/null +++ b/mod/.gitignore @@ -0,0 +1,2 @@ +options +options.?xx diff --git a/mod/buildfile b/mod/buildfile new file mode 100644 index 0000000..574b34a --- /dev/null +++ b/mod/buildfile @@ -0,0 +1,55 @@ +# file : mod/buildfile +# copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +define mod: libso +mod{*}: bin.libprefix = mod_ +mod{*}: install = libexec + +import libs += libodb%lib{odb} +import libs += libodb-pgsql%lib{odb-pgsql} +import libs += libbpkg%lib{bpkg} +import libs += libstudxml%lib{studxml} + +include ../brep/ + +mod{brep}: \ + {hxx cxx}{ database } \ + {hxx cxx}{ database-module } \ + {hxx cxx}{ diagnostics } \ + {hxx cxx}{ mod-package-details } \ + {hxx cxx}{ mod-package-search } \ + {hxx cxx}{ mod-package-version-details } \ + {hxx cxx}{ mod-repository-details } \ + {hxx cxx}{ mod-repository-root } \ + {hxx cxx}{ module } \ + {hxx ixx cxx}{ options } \ + {hxx }{ options-types } \ + {hxx cxx}{ page } \ + { cxx}{ services } \ + {hxx cxx}{ types-parsers } \ + ../web/{hxx cxx}{ mime-url-encoding } \ + ../web/{hxx }{ module } \ + ../web/{hxx }{ xhtml } \ + ../web/{hxx cxx}{ xhtml-fragment } \ +../web/apache/{hxx }{ log } \ +../web/apache/{hxx ixx cxx}{ request } \ +../web/apache/{hxx txx cxx}{ service } \ +../web/apache/{hxx }{ stream } \ +../brep/lib{brep} $libs + +# Don't install any of the module's headers. +# +{hxx ixx txx}{*}: install = false +../web/{hxx ixx txx}{*}: install = false + +# Set option prefix to the empty value to handle all unknown request parameters +# uniformly with a single catch block. +# +cli.options += --std c++11 -I $src_root --include-with-brackets \ +--include-prefix mod --guard-prefix MOD \ +--cxx-prologue "#include " \ +--cli-namespace brep::cli --generate-file-scanner --suppress-usage \ +--generate-modifier --generate-description --option-prefix "" + +{hxx ixx cxx}{options}: cli{options} diff --git a/mod/database b/mod/database new file mode 100644 index 0000000..2730449 --- /dev/null +++ b/mod/database @@ -0,0 +1,26 @@ +// file : mod/database -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef MOD_DATABASE +#define MOD_DATABASE + +#include // database + +#include +#include + +#include + +namespace brep +{ + // Returns pointer to the shared database instance, creating one on the + // first call. On subsequent calls ensures passed host and port equals + // to ones of the existing database instance throwing runtime_error + // otherwise. Is not thread-safe. + // + shared_ptr + shared_database (const options::db&); +} + +#endif // MOD_DATABASE diff --git a/mod/database-module b/mod/database-module new file mode 100644 index 0000000..0933794 --- /dev/null +++ b/mod/database-module @@ -0,0 +1,55 @@ +// file : mod/database-module -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef MOD_DATABASE_MODULE +#define MOD_DATABASE_MODULE + +#include // database + +#include +#include + +#include +#include + +namespace brep +{ + // A module that utilises the database. Specifically, it will retry the + // request in the face of recoverable database failures (deadlock, loss of + // connection, etc) up to a certain number of times. + // + class database_module: public module + { + protected: + database_module () = default; + + // Create a shallow copy (handling instance) if initialized and a deep + // copy (context exemplar) otherwise. + // + explicit + database_module (const database_module&); + + // Required to avoid getting warning from clang that + // database_module::init() hides module::init() virtual functions. This + // way all functions get to the same scope and become overloaded set. + // + using module::init; + + void + init (const options::db&); + + virtual bool + handle (request&, response&) = 0; + + protected: + size_t retry_; + shared_ptr db_; + + private: + virtual bool + handle (request&, response&, log&); + }; +} + +#endif // MOD_DATABASE_MODULE diff --git a/mod/database-module.cxx b/mod/database-module.cxx new file mode 100644 index 0000000..a794672 --- /dev/null +++ b/mod/database-module.cxx @@ -0,0 +1,50 @@ +// file : mod/database-module.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include +#include + +namespace brep +{ + // While currently the user-defined copy constructor is not required (we + // don't need to deep copy nullptr's), it is a good idea to keep the + // placeholder ready for less trivial cases. + // + database_module:: + database_module (const database_module& r) + : module (r), + retry_ (r.retry_), + db_ (r.initialized_ ? r.db_ : nullptr) + { + } + + void database_module:: + init (const options::db& o) + { + retry_ = o.db_retry (); + db_ = shared_database (o); + } + + bool database_module:: + handle (request& rq, response& rs, log& l) + try + { + return module::handle (rq, rs, l); + } + catch (const odb::recoverable& e) + { + if (retry_-- > 0) + { + MODULE_DIAG; + l1 ([&]{trace << e.what () << "; " << retry_ + 1 << " retries left";}); + throw retry (); + } + + throw; + } +} diff --git a/mod/database.cxx b/mod/database.cxx new file mode 100644 index 0000000..0f0b703 --- /dev/null +++ b/mod/database.cxx @@ -0,0 +1,60 @@ +// file : mod/database.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include +#include + +namespace brep +{ + namespace options + { + bool + operator< (const db& x, const db& y) + { + int r; + if ((r = x.db_user ().compare (y.db_user ())) != 0 || + (r = x.db_password ().compare (y.db_password ())) != 0 || + (r = x.db_name ().compare (y.db_name ())) != 0 || + (r = x.db_host ().compare (y.db_host ()))) + return r < 0; + + return x.db_port () < y.db_port (); + } + } + + using namespace odb; + + shared_ptr + shared_database (const options::db& o) + { + static std::map> databases; + + auto i (databases.find (o)); + if (i != databases.end ()) + { + if (shared_ptr d = i->second.lock ()) + return d; + } + + unique_ptr + f (new pgsql::connection_pool_factory (o.db_max_connections ())); + + shared_ptr d ( + make_shared ( + o.db_user (), + o.db_password (), + o.db_name (), + o.db_host (), + o.db_port (), + "options='-c default_transaction_isolation=serializable'", + move (f))); + + databases[o] = d; + return d; + } +} diff --git a/mod/diagnostics b/mod/diagnostics new file mode 100644 index 0000000..38286d4 --- /dev/null +++ b/mod/diagnostics @@ -0,0 +1,306 @@ +// file : mod/diagnostics -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef MOD_DIAGNOSTICS +#define MOD_DIAGNOSTICS + +#include + +#include +#include + +namespace brep +{ + struct location + { + location (): line (0), column (0) {} + location (string f, uint64_t l, uint64_t c) + : file (move (f)), line (l), column (c) {} + + string file; + uint64_t line; + uint64_t column; + }; + + enum class severity {error, warning, info, trace}; + + struct diag_entry + { + severity sev; + const char* name {nullptr}; // E.g., a function name in tracing. + location loc; + string msg; + }; + + using diag_data = vector; + + // + // + template struct diag_prologue; + template struct diag_mark; + + using diag_epilogue = function; + + struct diag_record + { + template + friend const diag_record& + operator<< (const diag_record& r, const T& x) + { + r.os_ << x; + return r; + } + + diag_record () = default; + + template + explicit + diag_record (const diag_prologue& p) {*this << p;} // See below. + + template + explicit + diag_record (const diag_mark& m) {*this << m;} // See below. + + ~diag_record () noexcept(false); + + void + append (const diag_epilogue& e) const + { + if (epilogue_ == nullptr) // Keep the first epilogue (think 'fail'). + epilogue_ = &e; + + if (!data_.empty ()) + { + data_.back ().msg = os_.str (); + + // Reset the stream. There got to be a more efficient way to do it. + // + os_.clear (); + os_.str (""); + } + + data_.push_back (diag_entry ()); + } + + diag_entry& + current () const {return data_.back ();} + + // Move constructible-only type. + // + /* + @@ libstdc++ doesn't yet have the ostringstream move support. + + diag_record (diag_record&& r) + : data_ (move (r.data_)), os_ (move (r.os_)) + { + epilogue_ = r.epilogue_; + r.data_.clear (); // Empty. + } + */ + + diag_record (diag_record&& r): data_ (move (r.data_)) + { + if (!data_.empty ()) + os_ << r.os_.str (); + + epilogue_ = r.epilogue_; + r.data_.clear (); // Empty. + } + + diag_record& operator= (diag_record&&) = delete; + + diag_record (const diag_record&) = delete; + diag_record& operator= (const diag_record&) = delete; + + private: + mutable diag_data data_; + mutable std::ostringstream os_; + mutable const diag_epilogue* epilogue_ {nullptr}; + }; + + // Base (B) should provide operator() that configures diag_record. + // + template + struct diag_prologue: B + { + diag_prologue (const diag_epilogue& e): B (), epilogue_ (e) {} + + template + diag_prologue (const diag_epilogue& e, A&&... a) + : B (forward (a)...), epilogue_ (e) {} + + template + diag_record + operator<< (const T& x) const + { + diag_record r; + r.append (epilogue_); + B::operator() (r); + r << x; + return r; + } + + friend const diag_record& + operator<< (const diag_record& r, const diag_prologue& p) + { + r.append (p.epilogue_); + p (r); + return r; + } + + private: + const diag_epilogue& epilogue_; + }; + + // Base (B) should provide operator() that returns diag_prologue. + // + template + struct diag_mark: B + { + diag_mark (): B () {} + + template + diag_mark (A&&... a): B (forward (a)...) {} + + template + diag_record + operator<< (const T& x) const + { + return B::operator() () << x; + } + + friend const diag_record& + operator<< (const diag_record& r, const diag_mark& m) + { + return r << m (); + } + }; + + // Prologues. + // + struct simple_prologue_base + { + explicit + simple_prologue_base (severity s, const char* name) + : sev_ (s), name_ (name) {} + + void + operator() (const diag_record& r) const + { + diag_entry& e (r.current ()); + e.sev = sev_; + e.name = name_; + } + + private: + severity sev_; + const char* name_; + }; + typedef diag_prologue simple_prologue; + + struct location_prologue_base + { + location_prologue_base (severity s, + const char* name, + const location& l) + : sev_ (s), name_ (name), loc_ (l) {} + + void + operator() (const diag_record& r) const + { + diag_entry& e (r.current ()); + e.sev = sev_; + e.name = name_; + e.loc = loc_; //@@ I think we can probably move it. + } + + private: + severity sev_; + const char* name_; + const location loc_; + }; + typedef diag_prologue location_prologue; + + // Marks. + // + struct basic_mark_base + { + explicit + basic_mark_base (severity s, + const diag_epilogue& e, + const char* name = nullptr, + const void* data = nullptr) + : sev_ (s), epilogue_ (e), name_ (name), data_ (data) {} + + simple_prologue + operator() () const + { + return simple_prologue (epilogue_, sev_, name_); + } + + location_prologue + operator() (const location& l) const + { + return location_prologue (epilogue_, sev_, name_, l); + } + + template + location_prologue + operator() (const L& l) const + { + // get_location() is the user-supplied ADL-searched function. + // + return location_prologue ( + epilogue_, sev_, name_, get_location (l, data_)); + } + + private: + severity sev_; + const diag_epilogue& epilogue_; + const char* name_; + const void* data_; + }; + typedef diag_mark basic_mark; + + template + struct fail_mark_base + { + explicit + fail_mark_base (const char* name = nullptr, const void* data = nullptr) + : name_ (name), data_ (data) {} + + simple_prologue + operator() () const + { + return simple_prologue (epilogue_, severity::error, name_); + } + + location_prologue + operator() (const location& l) const + { + return location_prologue (epilogue_, severity::error, name_, l); + } + + template + location_prologue + operator() (const L& l) const + { + return location_prologue ( + epilogue_, severity::error, name_, get_location (l, data_)); + } + + static void + epilogue (diag_data&& d) {throw E (move (d));} + + private: + const diag_epilogue epilogue_ {&epilogue}; + const char* name_; + const void* data_; + }; + + template + using fail_mark = diag_mark>; +} + +#endif // MOD_DIAGNOSTICS diff --git a/mod/diagnostics.cxx b/mod/diagnostics.cxx new file mode 100644 index 0000000..6512517 --- /dev/null +++ b/mod/diagnostics.cxx @@ -0,0 +1,30 @@ +// file : mod/diagnostics.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +using namespace std; + +namespace brep +{ + diag_record:: + ~diag_record () noexcept(false) + { + // Don't flush the record if this destructor was called as part of + // the stack unwinding. Right now this means we cannot use this + // mechanism in destructors, which is not a big deal, except for + // one place: exception_guard. So for now we are going to have + // this ugly special check which we will be able to get rid of + // once C++17 uncaught_exceptions() becomes available. + // + if (!data_.empty () && + (!uncaught_exception () /*|| exception_unwinding_dtor*/)) + { + data_.back ().msg = os_.str (); // Save last message. + + assert (epilogue_ != nullptr); + (*epilogue_) (move (data_)); // Can throw. + } + } +} diff --git a/mod/mod-package-details b/mod/mod-package-details new file mode 100644 index 0000000..b324bfb --- /dev/null +++ b/mod/mod-package-details @@ -0,0 +1,42 @@ +// file : mod/mod-package-details -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef MOD_MOD_PACKAGE_DETAILS +#define MOD_MOD_PACKAGE_DETAILS + +#include +#include + +#include +#include + +namespace brep +{ + class package_details: public database_module + { + public: + package_details () = default; + + // Create a shallow copy (handling instance) if initialized and a deep + // copy (context exemplar) otherwise. + // + explicit + package_details (const package_details&); + + virtual bool + handle (request&, response&); + + virtual const cli::options& + cli_options () const {return options::package_details::description ();} + + private: + virtual void + init (cli::scanner&); + + private: + shared_ptr options_; + }; +} + +#endif // MOD_MOD_PACKAGE_DETAILS diff --git a/mod/mod-package-details.cxx b/mod/mod-package-details.cxx new file mode 100644 index 0000000..2ff93c9 --- /dev/null +++ b/mod/mod-package-details.cxx @@ -0,0 +1,258 @@ +// file : mod/mod-package-details.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +using namespace odb::core; +using namespace brep::cli; + +// While currently the user-defined copy constructor is not required (we don't +// need to deep copy nullptr's), it is a good idea to keep the placeholder +// ready for less trivial cases. +// +brep::package_details:: +package_details (const package_details& r) + : database_module (r), + options_ (r.initialized_ ? r.options_ : nullptr) +{ +} + +void brep::package_details:: +init (scanner& s) +{ + MODULE_DIAG; + + options_ = make_shared ( + s, unknown_mode::fail, unknown_mode::fail); + + database_module::init (*options_); + + if (options_->root ().empty ()) + options_->root (dir_path ("/")); +} + +template +static inline query +search_params (const brep::string& n, const brep::string& q) +{ + using query = query; + + return "(" + + (q.empty () + ? query ("NULL") + : "plainto_tsquery (" + query::_val (q) + ")") + + "," + + query::_val (n) + + ")"; +} + +bool brep::package_details:: +handle (request& rq, response& rs) +{ + using namespace web; + using namespace web::xhtml; + + MODULE_DIAG; + + const size_t res_page (options_->search_results ()); + const dir_path& root (options_->root ()); + + const string& name (*rq.path ().rbegin ()); + const string ename (mime_url_encode (name)); + + params::package_details params; + bool full; + + try + { + name_value_scanner s (rq.parameters ()); + params = params::package_details ( + s, unknown_mode::fail, unknown_mode::fail); + + full = params.form () == page_form::full; + } + catch (const cli::exception& e) + { + throw invalid_request (400, e.what ()); + } + + size_t page (params.page ()); + const string& squery (params.query ()); + + auto url ( + [&ename](bool f = false, + const string& q = "", + size_t p = 0, + const string& a = "") -> string + { + string s ("?"); + string u (ename); + + if (f) { u += "?f=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; + }); + + xml::serializer s (rs.content (), name); + + s << HTML + << HEAD + << TITLE + << name; + + if (!squery.empty ()) + s << " " << squery; + + s << ~TITLE + << CSS_LINKS (path ("package-details.css"), root) + // + // This hack is required to avoid the "flash of unstyled content", which + // happens due to the presence of the autofocus attribute in the input + // element of the search form. The problem appears in Firefox and has a + // (4-year old, at the time of this writing) bug report: + // + // https://bugzilla.mozilla.org/show_bug.cgi?id=712130. + // + << SCRIPT << " " << ~SCRIPT + << ~HEAD + << BODY + << DIV_HEADER (root, options_->logo (), options_->menu ()) + << DIV(ID="content"); + + if (full) + s << CLASS("full"); + + s << DIV(ID="heading") + << H1 << A(HREF=url ()) << name << ~A << ~H1 + << A(HREF=url (!full, squery, page)) + << (full ? "[brief]" : "[full]") + << ~A + << ~DIV; + + session sn; + transaction t (db_->begin ()); + + shared_ptr pkg; + { + latest_package lp; + if (!db_->query_one ( + query( + "(" + query::_val (name) + ")"), lp)) + throw invalid_request (404, "Package '" + name + "' not found"); + + pkg = db_->load (lp.id); + } + + const auto& licenses (pkg->license_alternatives); + + if (page == 0) + { + // Display package details on the first page only. + // + s << H2 << pkg->summary << ~H2; + + static const string id ("description"); + if (const auto& d = pkg->description) + s << (full + ? P_DESCRIPTION (*d, id) + : P_DESCRIPTION (*d, options_->package_description (), + url (!full, squery, page, id))); + + s << TABLE(CLASS="proplist", ID="package") + << TBODY + << TR_LICENSE (licenses) + << TR_URL (pkg->url) + << TR_EMAIL (pkg->email) + << TR_TAGS (pkg->tags, root) + << ~TBODY + << ~TABLE; + } + + auto pkg_count ( + db_->query_value ( + search_params (name, squery))); + + s << FORM_SEARCH (squery) + << DIV_COUNTER (pkg_count, "Version", "Versions"); + + // Enclose the subsequent tables to be able to use nth-child CSS selector. + // + s << DIV; + for (const auto& pr: + db_->query ( + search_params (name, squery) + + "ORDER BY rank DESC, version_epoch DESC, " + "version_canonical_upstream DESC, version_canonical_release DESC, " + "version_revision DESC" + + "OFFSET" + to_string (page * res_page) + + "LIMIT" + to_string (res_page))) + { + shared_ptr p (db_->load (pr.id)); + + s << TABLE(CLASS="proplist version") + << TBODY + << TR_VERSION (name, p->version.string (), root) + + // @@ Shouldn't we skip low priority row ? Don't think so, why? + // + << TR_PRIORITY (p->priority); + + // Comparing objects of the license_alternatives class as being of the + // vector> class, so comments are not considered. + // + if (p->license_alternatives != licenses) + s << TR_LICENSE (p->license_alternatives); + + assert (p->internal ()); + + // @@ Shouldn't we make package location to be a link to the proper + // place of the About page, describing corresponding repository? + // Yes, I think that's sounds reasonable, once we have about. + // Or maybe it can be something more valuable like a link to the + // repository package search page ? + // + // @@ 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 differs + // from the one in the summary ? + // + // Hm, I am not so sure about this. Consider: stable/testing/unstable. + // + s << TR_LOCATION (p->internal_repository.object_id (), root) + << TR_DEPENDS (p->dependencies, root) + << TR_REQUIRES (p->requirements) + << ~TBODY + << ~TABLE; + } + s << ~DIV; + + t.commit (); + + s << DIV_PAGER (page, pkg_count, res_page, options_->search_pages (), + url (full, squery)) + << ~DIV + << ~BODY + << ~HTML; + + return true; +} diff --git a/mod/mod-package-search b/mod/mod-package-search new file mode 100644 index 0000000..e463e2a --- /dev/null +++ b/mod/mod-package-search @@ -0,0 +1,42 @@ +// file : mod/mod-package-search -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef MOD_MOD_PACKAGE_SEARCH +#define MOD_MOD_PACKAGE_SEARCH + +#include +#include + +#include +#include + +namespace brep +{ + class package_search: public database_module + { + public: + package_search () = default; + + // Create a shallow copy (handling instance) if initialized and a deep + // copy (context exemplar) otherwise. + // + explicit + package_search (const package_search&); + + virtual bool + handle (request&, response&); + + virtual const cli::options& + cli_options () const {return options::package_search::description ();} + + private: + virtual void + init (cli::scanner&); + + private: + shared_ptr options_; + }; +} + +#endif // MOD_MOD_PACKAGE_SEARCH diff --git a/mod/mod-package-search.cxx b/mod/mod-package-search.cxx new file mode 100644 index 0000000..5654427 --- /dev/null +++ b/mod/mod-package-search.cxx @@ -0,0 +1,181 @@ +// file : mod/mod-package-search.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +using namespace odb::core; +using namespace brep::cli; + +// While currently the user-defined copy constructor is not required (we don't +// need to deep copy nullptr's), it is a good idea to keep the placeholder +// ready for less trivial cases. +// +brep::package_search:: +package_search (const package_search& r) + : database_module (r), + options_ (r.initialized_ ? r.options_ : nullptr) +{ +} + +void brep::package_search:: +init (scanner& s) +{ + MODULE_DIAG; + + options_ = make_shared ( + s, unknown_mode::fail, unknown_mode::fail); + + database_module::init (*options_); + + if (options_->root ().empty ()) + options_->root (dir_path ("/")); + + // Check that the database schema matches the current one. It's enough to + // perform the check in just a single module implementation (and we don't + // do in the dispatcher because it doesn't use the database). + // + // Note that the failure can be reported by each web server worker process. + // While it could be tempting to move the check to the + // repository_root::version() function, it would be wrong. The function can + // be called by a different process (usually the web server root one) not + // having the proper permissions to access the database. + // + if (schema_catalog::current_version (*db_) != db_->schema_version ()) + fail << "database schema differs from the current one (module " + << BREP_VERSION_STR << ")"; +} + +template +static inline query +search_param (const brep::string& q) +{ + using query = query; + return "(" + + (q.empty () + ? query ("NULL") + : "plainto_tsquery (" + query::_val (q) + ")") + + ")"; +} + +bool brep::package_search:: +handle (request& rq, response& rs) +{ + using namespace web::xhtml; + + MODULE_DIAG; + + const size_t res_page (options_->search_results ()); + const dir_path& root (options_->root ()); + + params::package_search params; + + try + { + name_value_scanner s (rq.parameters ()); + params = params::package_search ( + s, unknown_mode::fail, unknown_mode::fail); + } + catch (const unknown_argument& e) + { + throw invalid_request (400, e.what ()); + } + + size_t page (params.page ()); + const string& squery (params.query ()); + string squery_param (squery.empty () + ? "" + : "?q=" + web::mime_url_encode (squery)); + + + static const string title ("Packages"); + xml::serializer s (rs.content (), title); + + s << HTML + << HEAD + << TITLE + << title; + + if (!squery.empty ()) + s << " " << squery; + + s << ~TITLE + << CSS_LINKS (path ("package-search.css"), root) + // + // This hack is required to avoid the "flash of unstyled content", which + // happens due to the presence of the autofocus attribute in the input + // element of the search form. The problem appears in Firefox and has a + // (4-year old, at the time of this writing) bug report: + // + // https://bugzilla.mozilla.org/show_bug.cgi?id=712130. + // + << SCRIPT << " " << ~SCRIPT + << ~HEAD + << BODY + << DIV_HEADER (root, options_->logo (), options_->menu ()) + << DIV(ID="content"); + + session sn; + transaction t (db_->begin ()); + + auto pkg_count ( + db_->query_value ( + search_param (squery))); + + s << FORM_SEARCH (squery) + << DIV_COUNTER (pkg_count, "Package", "Packages"); + + // Enclose the subsequent tables to be able to use nth-child CSS selector. + // + s << DIV; + for (const auto& pr: + db_->query ( + search_param (squery) + + "ORDER BY rank DESC, name" + + "OFFSET" + to_string (page * res_page) + + "LIMIT" + to_string (res_page))) + { + shared_ptr p (db_->load (pr.id)); + + s << TABLE(CLASS="proplist package") + << TBODY + << TR_NAME (p->id.name, squery_param, root) + << TR_SUMMARY (p->summary) + << TR_LICENSE (p->license_alternatives) + << TR_TAGS (p->tags, root) + << TR_DEPENDS (p->dependencies, root) + << TR_REQUIRES (p->requirements) + << ~TBODY + << ~TABLE; + } + s << ~DIV; + + t.commit (); + + s << DIV_PAGER (page, pkg_count, res_page, options_->search_pages (), + root.string () + squery_param) + << ~DIV + << ~BODY + << ~HTML; + + return true; +} diff --git a/mod/mod-package-version-details b/mod/mod-package-version-details new file mode 100644 index 0000000..0fba2bf --- /dev/null +++ b/mod/mod-package-version-details @@ -0,0 +1,45 @@ +// file : mod/mod-package-version-details -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef MOD_MOD_PACKAGE_VERSION_DETAILS +#define MOD_MOD_PACKAGE_VERSION_DETAILS + +#include +#include + +#include +#include + +namespace brep +{ + class package_version_details: public database_module + { + public: + package_version_details () = default; + + // Create a shallow copy (handling instance) if initialized and a deep + // copy (context exemplar) otherwise. + // + explicit + package_version_details (const package_version_details&); + + virtual bool + handle (request&, response&); + + virtual const cli::options& + cli_options () const + { + return options::package_version_details::description (); + } + + private: + virtual void + init (cli::scanner&); + + private: + shared_ptr options_; + }; +} + +#endif // MOD_MOD_PACKAGE_VERSION_DETAILS diff --git a/mod/mod-package-version-details.cxx b/mod/mod-package-version-details.cxx new file mode 100644 index 0000000..149c8f9 --- /dev/null +++ b/mod/mod-package-version-details.cxx @@ -0,0 +1,320 @@ +// file : mod/mod-package-version-details.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +using namespace std; +using namespace odb::core; +using namespace brep::cli; + +// While currently the user-defined copy constructor is not required (we don't +// need to deep copy nullptr's), it is a good idea to keep the placeholder +// ready for less trivial cases. +// +brep::package_version_details:: +package_version_details (const package_version_details& r) + : database_module (r), + options_ (r.initialized_ ? r.options_ : nullptr) +{ +} + +void brep::package_version_details:: +init (scanner& s) +{ + MODULE_DIAG; + + options_ = make_shared ( + s, unknown_mode::fail, unknown_mode::fail); + + database_module::init (*options_); + + if (options_->root ().empty ()) + options_->root (dir_path ("/")); +} + +bool brep::package_version_details:: +handle (request& rq, response& rs) +{ + using namespace web; + using namespace web::xhtml; + using brep::version; // Not to confuse with module::version. + + MODULE_DIAG; + + const dir_path& root (options_->root ()); + + auto i (rq.path ().rbegin ()); + version ver; + + try + { + ver = version (*i++); + } + catch (const invalid_argument& ) + { + throw invalid_request (400, "invalid package version format"); + } + + const string& sver (ver.string ()); + + assert (i != rq.path ().rend ()); + const string& name (*i); + + params::package_version_details params; + bool full; + + try + { + name_value_scanner s (rq.parameters ()); + params = params::package_version_details ( + s, unknown_mode::fail, unknown_mode::fail); + + full = params.form () == page_form::full; + } + catch (const unknown_argument& e) + { + throw invalid_request (400, e.what ()); + } + + auto url ( + [&sver](bool f = false, const string& a = "") -> string + { + string u (sver); + + if (f) { u += "?f=full"; } + if (!a.empty ()) { u += '#' + a; } + return u; + }); + + const string title (name + " " + sver); + xml::serializer s (rs.content (), title); + + s << HTML + << HEAD + << TITLE << title << ~TITLE + << CSS_LINKS (path ("package-version-details.css"), root) + << ~HEAD + << BODY + << DIV_HEADER (root, options_->logo (), options_->menu ()) + << DIV(ID="content"); + + if (full) + s << CLASS("full"); + + s << DIV(ID="heading") + << H1 + << A(HREF=root / path (mime_url_encode (name))) << name << ~A + << "/" + << A(HREF=url ()) << sver << ~A + << ~H1 + << A(HREF=url (!full)) << (full ? "[brief]" : "[full]") << ~A + << ~DIV; + + bool not_found (false); + shared_ptr pkg; + + session sn; + transaction t (db_->begin ()); + + try + { + pkg = db_->load (package_id (name, ver)); + + // If the requested package turned up to be an "external" one just + // respond that no "internal" package is present. + // + not_found = !pkg->internal (); + } + catch (const object_not_persistent& ) + { + not_found = true; + } + + if (not_found) + throw invalid_request (404, "Package '" + title + "' not found"); + + s << H2 << pkg->summary << ~H2; + + static const string id ("description"); + if (const auto& d = pkg->description) + s << (full + ? P_DESCRIPTION (*d, id) + : P_DESCRIPTION (*d, options_->package_description (), + url (!full, id))); + + assert (pkg->location && pkg->sha256sum); + + s << TABLE(CLASS="proplist", ID="version") + << TBODY + + // Repeat version here since it can be cut out in the header. + // + << TR_VERSION (pkg->version.string ()) + + << TR_PRIORITY (pkg->priority) + << TR_LICENSES (pkg->license_alternatives) + << TR_LOCATION (pkg->internal_repository.object_id (), root) + << TR_DOWNLOAD (pkg->internal_repository.load ()->location.string () + + "/" + pkg->location->string ()) + << TR_SHA256SUM (*pkg->sha256sum) + << ~TBODY + << ~TABLE + + << TABLE(CLASS="proplist", ID="package") + << TBODY + << TR_URL (pkg->url) + << TR_EMAIL (pkg->email); + + const auto& pu (pkg->package_url); + if (pu && *pu != pkg->url) + s << TR_URL (*pu, "pkg-url"); + + const auto& pe (pkg->package_email); + if (pe && *pe != pkg->email) + s << TR_EMAIL (*pe, "pkg-email"); + + s << TR_TAGS (pkg->tags, root) + << ~TBODY + << ~TABLE; + + const auto& ds (pkg->dependencies); + if (!ds.empty ()) + { + s << H3 << "Depends" << ~H3 + << TABLE(CLASS="proplist", ID="depends") + << TBODY; + + for (const auto& da: ds) + { + s << TR(CLASS="depends") + << TH; + + if (da.conditional) + s << "?"; + + s << ~TH + << TD + << SPAN(CLASS="value"); + + for (const auto& d: da) + { + if (&d != &da[0]) + s << " | "; + + shared_ptr p (d.package.load ()); + assert (p->internal () || !p->other_repositories.empty ()); + + shared_ptr r ( + p->internal () + ? p->internal_repository.load () + : p->other_repositories[0].load ()); + + const auto& dcon (d.constraint); + const string& dname (p->id.name); + string ename (mime_url_encode (dname)); + + if (r->url) + { + string u (*r->url + ename); + s << A(HREF=u) << dname << ~A; + + if (dcon) + s << ' ' << A(HREF=u + "/" + p->version.string ()) << *dcon << ~A; + } + else if (p->internal ()) + { + path u (root / path (ename)); + s << A(HREF=u) << dname << ~A; + + if (dcon) + s << ' ' << A(HREF=u / path (p->version.string ())) << *dcon << ~A; + } + else + // Display the dependency as a plain text if no repository URL + // available. + // + s << d; + } + + s << ~SPAN + << SPAN_COMMENT (da.comment) + << ~TD + << ~TR; + } + + s << ~TBODY + << ~TABLE; + } + + t.commit (); + + const auto& rm (pkg->requirements); + if (!rm.empty ()) + { + s << H3 << "Requires" << ~H3 + << TABLE(CLASS="proplist", ID="requires") + << TBODY; + + for (const auto& ra: rm) + { + s << TR(CLASS="requires") + << TH; + + if (ra.conditional) + s << "?"; + + s << ~TH + << TD + << SPAN(CLASS="value"); + + for (const auto& r: ra) + { + if (&r != &ra[0]) + s << " | "; + + s << r; + } + + s << ~SPAN + << SPAN_COMMENT (ra.comment) + << ~TD + << ~TR; + } + + s << ~TBODY + << ~TABLE; + } + + const auto& ch (pkg->changes); + if (!ch.empty ()) + s << H3 << "Changes" << ~H3 + << (full + ? PRE_CHANGES (ch) + : PRE_CHANGES (ch, + options_->package_changes (), + url (!full, "changes"))); + + s << ~DIV + << ~BODY + << ~HTML; + + return true; +} diff --git a/mod/mod-repository-details b/mod/mod-repository-details new file mode 100644 index 0000000..2532613 --- /dev/null +++ b/mod/mod-repository-details @@ -0,0 +1,42 @@ +// file : mod/mod-repository-details -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef MOD_MOD_REPOSITORY_DETAILS +#define MOD_MOD_REPOSITORY_DETAILS + +#include +#include + +#include +#include + +namespace brep +{ + class repository_details: public database_module + { + public: + repository_details () = default; + + // Create a shallow copy (handling instance) if initialized and a deep + // copy (context exemplar) otherwise. + // + explicit + repository_details (const repository_details&); + + virtual bool + handle (request&, response&); + + virtual const cli::options& + cli_options () const {return options::repository_details::description ();} + + private: + virtual void + init (cli::scanner&); + + private: + shared_ptr options_; + }; +} + +#endif // MOD_MOD_REPOSITORY_DETAILS diff --git a/mod/mod-repository-details.cxx b/mod/mod-repository-details.cxx new file mode 100644 index 0000000..91f0d1b --- /dev/null +++ b/mod/mod-repository-details.cxx @@ -0,0 +1,147 @@ +// file : mod/mod-repository-details.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include // tzset() + +#include +#include // max() + +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +using namespace std; +using namespace odb::core; +using namespace brep::cli; + +// While currently the user-defined copy constructor is not required (we don't +// need to deep copy nullptr's), it is a good idea to keep the placeholder +// ready for less trivial cases. +// +brep::repository_details:: +repository_details (const repository_details& r) + : database_module (r), + options_ (r.initialized_ ? r.options_ : nullptr) +{ +} + +void brep::repository_details:: +init (scanner& s) +{ + MODULE_DIAG; + + options_ = make_shared ( + s, unknown_mode::fail, unknown_mode::fail); + + database_module::init (*options_); + + if (options_->root ().empty ()) + options_->root (dir_path ("/")); + + tzset (); // To use butl::to_stream() later on. +} + +bool brep::repository_details:: +handle (request& rq, response& rs) +{ + using namespace web::xhtml; + + MODULE_DIAG; + + const dir_path& root (options_->root ()); + + // Make sure no parameters passed. + // + try + { + name_value_scanner s (rq.parameters ()); + params::repository_details (s, unknown_mode::fail, unknown_mode::fail); + } + catch (const unknown_argument& e) + { + throw invalid_request (400, e.what ()); + } + + static const string title ("About"); + xml::serializer s (rs.content (), title); + + s << HTML + << HEAD + << TITLE << title << ~TITLE + << CSS_LINKS (path ("repository-details.css"), root) + << ~HEAD + << BODY + << DIV_HEADER (root, options_->logo (), options_->menu ()) + << DIV(ID="content"); + + transaction t (db_->begin ()); + + using query = query; + + for (const auto& r: + db_->query ( + query::internal + "ORDER BY" + query::priority)) + { + //@@ Feels like a lot of trouble (e.g., id_attribute()) for very + // dubious value. A link to the package search page just for + // this repository would probably be more useful. + // + string id (html_id (r.name)); + s << H1(ID=id) + << A(HREF="#" + web::mime_url_encode (id)) << r.display_name << ~A + << ~H1; + + if (r.summary) + s << H2 << *r.summary << ~H2; + + if (r.description) + s << P_DESCRIPTION (*r.description); + + if (r.email) + { + const email& e (*r.email); + + s << P + << A(HREF="mailto:" + e) << e << ~A; + + if (!e.comment.empty ()) + s << " (" << e.comment << ")"; + + s << ~P; + } + + ostringstream o; + butl::to_stream (o, + max (r.packages_timestamp, r.repositories_timestamp), + "%Y-%m-%d %H:%M:%S%[.N] %Z", + true, + true); + + s << P << o.str () << ~P; + } + + t.commit (); + + s << ~DIV + << ~BODY + << ~HTML; + + return true; +} diff --git a/mod/mod-repository-root b/mod/mod-repository-root new file mode 100644 index 0000000..db13b58 --- /dev/null +++ b/mod/mod-repository-root @@ -0,0 +1,60 @@ +// file : mod/mod-repository-root -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef MOD_MOD_REPOSITORY_ROOT +#define MOD_MOD_REPOSITORY_ROOT + +#include +#include + +#include +#include + +namespace brep +{ + class package_search; + class package_details; + class package_version_details; + class repository_details; + + class repository_root: public module + { + public: + repository_root (); + + // Create a shallow copy (handling instance) if initialized and a deep + // copy (context exemplar) otherwise. + // + explicit + repository_root (const repository_root&); + + private: + virtual bool + handle (request&, response&); + + virtual const cli::options& + cli_options () const {return options::repository_root::description ();} + + virtual option_descriptions + options (); + + virtual void + init (const name_values&); + + virtual void + init (cli::scanner&); + + virtual void + version (); + + private: + shared_ptr package_search_; + shared_ptr package_details_; + shared_ptr package_version_details_; + shared_ptr repository_details_; + shared_ptr options_; + }; +} + +#endif // MOD_MOD_REPOSITORY_ROOT diff --git a/mod/mod-repository-root.cxx b/mod/mod-repository-root.cxx new file mode 100644 index 0000000..295117e --- /dev/null +++ b/mod/mod-repository-root.cxx @@ -0,0 +1,263 @@ +// file : mod/mod-repository-root.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace brep::cli; + +namespace brep +{ + // request_proxy + // + class request_proxy: public request + { + public: + request_proxy (request& r, const name_values& p) + : request_ (r), parameters_ (p) {} + + virtual const path_type& + path () {return request_.path ();} + + virtual const name_values& + parameters () {return parameters_;} + + virtual const name_values& + cookies () {return request_.cookies ();} + + virtual istream& + content (bool buffer) {return request_.content (buffer);} + + private: + request& request_; + const name_values& parameters_; + }; + + // repository_root + // + repository_root:: + repository_root () + : package_search_ (make_shared ()), + package_details_ (make_shared ()), + package_version_details_ (make_shared ()), + repository_details_ (make_shared ()) + { + } + + repository_root:: + repository_root (const repository_root& r) + : module (r), + // + // Deep/shallow-copy sub-modules depending on whether this is an + // exemplar/handler. + // + package_search_ ( + r.initialized_ + ? r.package_search_ + : make_shared (*r.package_search_)), + package_details_ ( + r.initialized_ + ? r.package_details_ + : make_shared (*r.package_details_)), + package_version_details_ ( + r.initialized_ + ? r.package_version_details_ + : make_shared ( + *r.package_version_details_)), + repository_details_ ( + r.initialized_ + ? r.repository_details_ + : make_shared (*r.repository_details_)), + options_ ( + r.initialized_ + ? r.options_ + : nullptr) + { + } + + // Return amalgamation of repository_root and all its sub-modules option + // descriptions. + // + option_descriptions repository_root:: + options () + { + option_descriptions r (module::options ()); + append (r, package_search_->options ()); + append (r, package_details_->options ()); + append (r, package_version_details_->options ()); + append (r, repository_details_->options ()); + return r; + } + + // Initialize sub-modules and parse own configuration options. + // + void repository_root:: + init (const name_values& v) + { + auto sub_init ([this, &v](module& m) + { + m.init (filter (v, m.options ()), *log_); + }); + + // Initialize sub-modules. + // + sub_init (*package_search_); + sub_init (*package_details_); + sub_init (*package_version_details_); + sub_init (*repository_details_); + + // Parse own configuration options. + // + module::init ( + filter (v, convert (options::repository_root::description ()))); + } + + void repository_root:: + init (scanner& s) + { + MODULE_DIAG; + + options_ = make_shared ( + s, unknown_mode::fail, unknown_mode::fail); + + if (options_->root ().empty ()) + options_->root (dir_path ("/")); + } + + bool repository_root:: + handle (request& rq, response& rs) + { + MODULE_DIAG; + + const dir_path& root (options_->root ()); + + const path& rpath (rq.path ()); + if (!rpath.sub (root)) + return false; + + const path& lpath (rpath.leaf (root)); + + // Delegate the request handling to the sub-module. Intercept exception + // handling to add sub-module attribution. + // + auto handle = [&rs, this](module& m, request& rq, const char* name) -> bool + { + try + { + return m.handle (rq, rs, *log_); + } + catch (const invalid_request&) + { + // Preserve invalid_request exception type, so the web server can + // properly respond to the client with a 4XX error code. + // + throw; + } + catch (const std::exception& e) + { + // All exception types inherited from std::exception (and different + // from invalid_request) are handled by the web server as + // std::exception. The only sensible way to handle them is to respond + // to the client with the internal server error (500) code. By that + // reason it is valid to reduce all these types to a single one. + // Note that the server_error exception is handled internally by the + // module::handle() function call. + // + throw runtime_error (string (name) + ": " + e.what ()); + } + }; + + if (lpath.empty ()) + { + // Dispatch request handling to the repository_details or the + // package_search module depending on the function name passed as a + // first HTTP request parameter. The parameter should have no value + // specified. Example: cppget.org/?about + // + const name_values& params (rq.parameters ()); + if (!params.empty () && !params.front ().value) + { + if (params.front ().name == "about") + { + // Cleanup not to confuse the selected module with the unknown + // parameter. + // + name_values p (params); + p.erase (p.begin ()); + + request_proxy rp (rq, p); + repository_details m (*repository_details_); + return handle (m, rp, "repository_details"); + } + + throw invalid_request (400, "unknown function"); + } + else + { + package_search m (*package_search_); + return handle (m, rq, "package_search"); + } + } + else + { + // Dispatch request handling to the package_details or the + // package_version_details module depending on the HTTP request URL path. + // + auto i (lpath.begin ()); + assert (i != lpath.end ()); + + const string& n (*i++); // Package name. + + // Check if this is a package name and not a brep static content files + // (CSS) directory name, a repository directory name, or a special file + // name (the one starting with '.'). + // + // @@ Shouldn't we validate that the package name is not "@", is not + // digit-only, does not start with '.' while parsing and serializing + // the package manifest ? Probably also need to mention these + // contraints in the manifest.txt file. + // + if (n != "@" && n.find_first_not_of ("0123456789") != string::npos && + n[0] != '.') + { + if (i == lpath.end ()) + { + package_details m (*package_details_); + return handle (m, rq, "package_details"); + } + else if (++i == lpath.end ()) + { + package_version_details m (*package_version_details_); + return handle (m, rq, "package_version_details"); + } + } + } + + return false; + } + + void repository_root:: + version () + { + MODULE_DIAG; + + info << "module " << BREP_VERSION_STR + << ", libbrep " << LIBBREP_VERSION_STR + << ", libbpkg " << LIBBPKG_VERSION_STR + << ", libbutl " << LIBBUTL_VERSION_STR; + } +} diff --git a/mod/module b/mod/module new file mode 100644 index 0000000..b704a53 --- /dev/null +++ b/mod/module @@ -0,0 +1,201 @@ +// file : mod/module -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef MOD_MODULE +#define MOD_MODULE + +#include + +#include +#include + +#include +#include + +namespace brep +{ + // Bring in commonly used names from the web namespace. + // + // @@ Maybe doing using namespace is the right way to handle this. + // There will, however, most likely be a conflict between + // web::module and our module. Or maybe not, need to try. + // + using web::status_code; + using web::invalid_request; + using web::sequence_error; + using web::option_descriptions; + using web::name_value; + using web::name_values; + using web::request; + using web::response; + using web::log; + + // This exception indicated a server error (5XX). In particular, + // it is thrown by the fail diagnostics stream and is caught by the + // module implementation where it is both logged as an error and + // returned to the user with the 5XX status code. + // + struct server_error + { + diag_data data; + + server_error (diag_data&& d): data (move (d)) {} + }; + + // Every module member function that needs to produce any diagnostics + // shall begin with: + // + // MODULE_DIAG; + // + // This will instantiate the fail, error, warn, info, and trace + // diagnostics streams with the function's name. + // +#define MODULE_DIAG \ + const fail_mark fail (__PRETTY_FUNCTION__); \ + const basic_mark error (severity::error, \ + this->log_writer_, \ + __PRETTY_FUNCTION__); \ + const basic_mark warn (severity::warning, \ + this->log_writer_, \ + __PRETTY_FUNCTION__); \ + const basic_mark info (severity::info, \ + this->log_writer_, \ + __PRETTY_FUNCTION__); \ + const basic_mark trace (severity::trace, \ + this->log_writer_, \ + __PRETTY_FUNCTION__) + + // Adaptation of the web::module to our needs. + // + class module: public web::module + { + // Diagnostics. + // + protected: + // Trace verbosity level. + // + // 0 - tracing disabled. + // 1 - brief information regarding irregular situations, which not being + // an error can be of some interest. + // 2 - @@ TODO: document + // + // While uint8 is more than enough, use uint16 for the ease of printing. + // + uint16_t verb_ = 0; + + template void l1 (const F& f) const {if (verb_ >= 1) f ();} + template void l2 (const F& f) const {if (verb_ >= 2) f ();} + + // Set to true when the module is successfully initialized. + // + bool initialized_ {false}; + + // Implementation details. + // + protected: + module (); + module (const module& ); + + static name_values + filter (const name_values&, const option_descriptions&); + + static option_descriptions + convert (const cli::options&); + + static void + append (option_descriptions& dst, const cli::options& src); + + static void + append (option_descriptions& dst, const option_descriptions& src); + + // Can be used by module implementation to parse HTTP request parameters. + // + class name_value_scanner: public cli::scanner + { + public: + name_value_scanner (const name_values&) noexcept; + + virtual bool + more (); + + virtual const char* + peek (); + + virtual const char* + next (); + + virtual void + skip (); + + private: + const name_values& name_values_; + name_values::const_iterator i_; + bool name_; + }; + + public: + virtual const cli::options& + cli_options () const = 0; + + virtual void + init (cli::scanner&) = 0; + + // Can be overriden by custom request dispatcher to initialize + // sub-modules. + // + virtual void + init (const name_values&); + + virtual void + init (const name_values&, log&); + + virtual bool + handle (request&, response&) = 0; + + virtual bool + handle (request&, response&, log&); + + // web::module interface. + // + public: + // Custom request dispatcher can aggregate its own option descriptions + // with sub-modules option descriptions. In this case it should still call + // the base implementation in order to include the brep::module's options. + // + virtual option_descriptions + options (); + + private: + virtual void + version (log&); + + // Can be overriden by the module implementation to log version, etc. + // + virtual void + version () {} + + name_values + expand_options (const name_values&); + + // Diagnostics implementation details. + // + protected: + log* log_ {nullptr}; // Diagnostics backend provided by the web server. + + private: + // Extract function name from a __PRETTY_FUNCTION__. + // Throw invalid_argument if fail to parse. + // + static string + func_name (const char* pretty_name); + + void + log_write (const diag_data&) const; + + protected: + const diag_epilogue log_writer_; + }; +} + +#endif // MOD_MODULE diff --git a/mod/module.cxx b/mod/module.cxx new file mode 100644 index 0000000..5e3a4b1 --- /dev/null +++ b/mod/module.cxx @@ -0,0 +1,410 @@ +// file : mod/module.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include + +#include +#include // strchr() +#include // bind() + +#include +#include + +#include + +using namespace std; +using namespace placeholders; // For std::bind's _1, etc. + +namespace brep +{ + // module + // + bool module:: + handle (request& rq, response& rs, log& l) + { + log_ = &l; + + try + { + // Web server should terminate if initialization failed. + // + assert (initialized_); + + return handle (rq, rs); + } + catch (const server_error& e) + { + log_write (e.data); + + try + { + static const char* sev_str[] = {"error", "warning", "info", "trace"}; + ostream& o (rs.content (500, "text/plain;charset=utf-8")); + + for (const auto& d: e.data) + { + string name; + + try + { + name = func_name (d.name); + } + catch (const invalid_argument&) + { + // Log "pretty" function description, see in log file & fix. + name = d.name; + } + + o << name << ": " << sev_str[static_cast (d.sev)] << ": " + << d.msg << endl; + } + } + catch (const sequence_error&) + { + // We tried to return the error status/description but some + // content has already been written. Nothing we can do about + // it. + } + } + + return true; + } + + option_descriptions module:: + convert (const cli::options& o) + { + option_descriptions r; + append (r, o); + return r; + } + + void module:: + append (option_descriptions& dst, const cli::options& src) + { + for (const auto& o: src) + { + bool v (!o.flag ()); + auto i (dst.emplace (o.name (), v)); + assert (i.first->second == v); // Consistent option/flag. + + for (const auto& a: o.aliases ()) + { + i = dst.emplace (a, v); + assert (i.first->second == v); + } + } + } + + void module:: + append (option_descriptions& dst, const option_descriptions& src) + { + for (const auto& o: src) + { + auto i (dst.emplace (o)); + assert (i.first->second == o.second); // Consistent option/flag. + } + } + + name_values module:: + filter (const name_values& v, const option_descriptions& d) + { + name_values r; + for (const auto& nv: v) + { + if (d.find (nv.name) != d.end ()) + r.push_back (nv); + } + + return r; + } + + // Convert CLI option descriptions to the general interface of option + // descriptions, extend with brep::module own option descriptions. + // + option_descriptions module:: + options () + { + option_descriptions r ({{"conf", true}}); + append (r, options::module::description ()); + append (r, cli_options ()); + return r; + } + + // Expand option list parsing configuration files. + // + name_values module:: + expand_options (const name_values& v) + { + using namespace cli; + + vector argv; + for (const auto& nv: v) + { + argv.push_back (nv.name.c_str ()); + + if (nv.value) + argv.push_back (nv.value->c_str ()); + } + + int argc (argv.size ()); + argv_file_scanner s (0, argc, const_cast (argv.data ()), "conf"); + + name_values r; + const option_descriptions& o (options ()); + + while (s.more ()) + { + string n (s.next ()); + auto i (o.find (n)); + + if (i == o.end ()) + throw unknown_argument (n); + + optional v; + if (i->second) + v = s.next (); + + r.emplace_back (move (n), move (v)); + } + + return r; + } + + // Parse options with a cli-generated scanner. Options verb and conf are + // recognized by brep::module::init while others to be interpreted by the + // derived init(). If there is an option which can not be interpreted + // neither by brep::module nor by the derived class, then the web server + // is terminated with a corresponding error message being logged. Though + // this should not happen if the options() function returned the correct + // set of options. + // + void module:: + init (const name_values& options, log& log) + { + assert (!initialized_); + + log_ = &log; + + try + { + name_values opts (expand_options (options)); + + // Read module implementation configuration. + // + init (opts); + + // Read brep::module configuration. + // + static option_descriptions od ( + convert (options::module::description ())); + + name_values mo (filter (opts, od)); + name_value_scanner s (mo); + options::module o (s, cli::unknown_mode::fail, cli::unknown_mode::fail); + + verb_ = o.verbosity (); + initialized_ = true; + } + catch (const server_error& e) + { + log_write (e.data); + throw runtime_error ("initialization failed"); + } + catch (const cli::exception& e) + { + ostringstream o; + e.print (o); + throw runtime_error (o.str ()); + } + } + + void module:: + init (const name_values& options) + { + name_value_scanner s (options); + init (s); + assert (!s.more ()); // Module didn't handle its options. + } + + module:: + module (): log_writer_ (bind (&module::log_write, this, _1)) {} + + // Custom copy constructor is required to initialize log_writer_ properly. + // + module:: + module (const module& m): module () + { + verb_ = m.verb_; + initialized_ = m.initialized_; + } + +// For function func declared like this: +// using B = std::string (*)(int); +// using A = B (*)(int,int); +// A func(B (*)(char),B (*)(wchar_t)); +// __PRETTY_FUNCTION__ looks like this: +// virtual std::string (* (* brep::search::func(std::string (* (*)(char))(int) +// ,std::string (* (*)(wchar_t))(int)) const)(int, int))(int) +// + string module:: + func_name (const char* pretty_name) + { + const char* e (strchr (pretty_name, ')')); + + if (e && e > pretty_name) + { + // Position e at last matching '(' which is the beginning of the + // argument list.. + // + size_t d (1); + + do + { + switch (*--e) + { + case ')': ++d; break; + case '(': --d; break; + } + } + while (d && e > pretty_name); + + if (!d && e > pretty_name) + { + // Position e at the character following the function name. + // + while (e > pretty_name && + (*e != '(' || *(e - 1) == ' ' || *(e - 1) == ')')) + --e; + + if (e > pretty_name) + { + // Position b at the beginning of the qualified function name. + // + const char* b (e); + while (--b > pretty_name && *b != ' '); + if (*b == ' ') ++b; + + return string (b, e - b); + } + } + } + + throw invalid_argument ("::brep::module::func_name"); + } + + void module:: + log_write (const diag_data& d) const + { + if (log_ == nullptr) + return; // No backend yet. + + //@@ Cast log_ to apache::log and write the records. + // + auto al (dynamic_cast (log_)); + + if (al) + { + // Considered using lambda for mapping but looks too verbose while can + // be a bit safer in runtime. + // + // Use APLOG_INFO (as opposed to APLOG_TRACE1) as a mapping for + // severity::trace. "LogLevel trace1" configuration directive switches + // on the avalanche of log messages from various modules. Would be good + // to avoid wading through them. + // + static int s[] = {APLOG_ERR, APLOG_WARNING, APLOG_INFO, APLOG_INFO}; + + for (const auto& e: d) + { + string name; + + try + { + name = func_name (e.name); + } + catch (const invalid_argument&) + { + // Log "pretty" function description, see in log file & fix. + name = e.name; + } + + al->write (e.loc.file.c_str (), + e.loc.line, + name.c_str (), + s[static_cast (e.sev)], + e.msg.c_str ()); + } + } + } + + void module:: + version (log& l) + { + log_ = &l; + version (); + } + + // module::name_value_scanner + // + module::name_value_scanner:: + name_value_scanner (const name_values& nv) noexcept + : name_values_ (nv), + i_ (nv.begin ()), + name_ (true) + { + } + + bool module::name_value_scanner:: + more () + { + return i_ != name_values_.end (); + } + + const char* module::name_value_scanner:: + peek () + { + if (i_ != name_values_.end ()) + return name_ ? i_->name.c_str () : i_->value->c_str (); + else + throw cli::eos_reached (); + } + + const char* module::name_value_scanner:: + next () + { + if (i_ != name_values_.end ()) + { + const char* r (name_ ? i_->name.c_str () : i_->value->c_str ()); + skip (); + return r; + } + else + throw cli::eos_reached (); + } + + void module::name_value_scanner:: + skip () + { + if (i_ != name_values_.end ()) + { + if (name_) + { + if (i_->value) + name_ = false; + else + ++i_; + } + else + { + ++i_; + name_ = true; + } + } + else + throw cli::eos_reached (); + } +} diff --git a/mod/options-types b/mod/options-types new file mode 100644 index 0000000..e696139 --- /dev/null +++ b/mod/options-types @@ -0,0 +1,32 @@ +// file : mod/options-types -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef MOD_OPTIONS_TYPES +#define MOD_OPTIONS_TYPES + +#include +#include + +namespace brep +{ + // brep types + // + enum class page_form + { + full, + brief + }; + + struct page_menu + { + string label; + string link; + + page_menu () = default; + page_menu (string b, string l): label (move (b)), link (move (l)) {} + }; + +} + +#endif // MOD_OPTIONS_TYPES diff --git a/mod/options.cli b/mod/options.cli new file mode 100644 index 0000000..ba8b37b --- /dev/null +++ b/mod/options.cli @@ -0,0 +1,211 @@ +// file : mod/options.cli -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +include ; + +include ; + +include ; + +namespace brep +{ + // Web module configuration options. + // + namespace options + { + // Option groups. + // + class module + { + dir_path root = "/" + { + "" + "Repository root. That is, this is the part of the URL between the + host name and the start of the repository. For example, root value + '\cb{/pkg}' means the repository URL is http://example.org/pkg/. + Specify '\cb{/}' to use the web server root (http://example.org/)." + } + + uint16_t verbosity = 0 + { + "", + "Trace verbosity level. Level 0 disables tracing, which is also the + default." + } + }; + + class db + { + string db-user + { + "", + "Database user name. If not specified, then operating system (login) + name is used." + } + + string db-password + { + "", + "Database password. If not specified, then login without password is + expected to work." + } + + string db-name = "brep" + { + "", + "Database name. If not specified, then '\cb{brep}' is used by + default." + } + + string db-host + { + "", + "Database host name, address, or socket. If not specified, then + connect to \cb{localhost} using the operating system-default + mechanism (Unix-domain socket, etc)." + } + + uint16_t db-port = 0 + { + "", + "Database port number. If not specified, the default port is used." + } + + size_t db-max-connections = 5 + { + "", + "The maximum number of concurrent database connections per web server + process. If 0, then no limitation is applied. The default is 5." + } + + size_t db-retry = 10 + { + "", + "The maximum number of times to retry database transactions in the + face of recoverable failures (deadlock, loss of connection, etc). The + default is 10." + } + }; + + class page + { + web::xhtml::fragment logo + { + "", + "Web page logo. It is displayed in the page header aligned to the left + edge. The value is treated as an XHTML5 fragment." + } + + vector menu; + { + "", + "Web page menu. Each entry is displayed in the page header in the + order specified and aligned to the right edge. A link target that + starts with '\cb{/}' or contains '\cb{:}' is used as is. Otherwise, + it is prefixed with the repository web interface root." + } + }; + + class search + { + uint16_t search-results = 10 + { + "", + "Number of results per page. The default is 10." + } + + uint16_t search-pages = 5 + { + "", + "Number of pages in navigation (pager). The default is 5." + } + }; + + class package + { + uint16_t package-description = 500 + { + "", + "Number of package description characters to display in brief pages. + The default is 500 (~ 80 characters * 6 lines)." + } + + uint16_t package-changes = 5000; + { + "", + "Number of package changes characters to display in brief pages. The + default is 5000 (~ 80 chars x 60 lines)." + } + }; + + // Module options. + // + class package_search: search, db, page, module + { + }; + + class package_details: package, search, db, page, module + { + }; + + class package_version_details: package, db, page, module + { + }; + + class repository_details: db, page, module + { + }; + + class repository_root: module + { + }; + } + + // Web module HTTP request parameters. + // + namespace params + { + // Use parameters long names in the C++ code, short aliases (if present) + // in HTTP URL. + // + class package_search + { + // Display package search result list starting from this page. + // + uint16_t page | p; + + // Package search criteria. + // + string query | q; + }; + + class package_details + { + // Display package version search result list starting from this page. + // + uint16_t page | p; + + // Package version search criteria. + // + string query | q; + + // Page form. + // + page_form form | f = page_form::brief; + }; + + class package_version_details + { + // Page form. + // + page_form form | f = page_form::brief; + }; + + class repository_details + { + // No parameters so far. + // + }; + } +} diff --git a/mod/page b/mod/page new file mode 100644 index 0000000..b326d6c --- /dev/null +++ b/mod/page @@ -0,0 +1,403 @@ +// file : mod/page -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef MOD_PAGE +#define MOD_PAGE + +#include + +#include + +#include +#include + +#include + +#include // page_menu + +namespace brep +{ + // Page common building blocks. + // + + // Generates CSS link elements. + // + class CSS_LINKS + { + public: + CSS_LINKS (const path& p, const dir_path& r): path_ (p), root_ (r) {} + + void + operator() (xml::serializer&) const; + + private: + const path& path_; + const dir_path& root_; + }; + + // Generates page header element. + // + class DIV_HEADER + { + public: + DIV_HEADER (const dir_path& root, + const web::xhtml::fragment& logo, + const vector& menu): + root_ (root), logo_ (logo), menu_ (menu) {} + + void + operator() (xml::serializer&) const; + + private: + const dir_path& root_; + const web::xhtml::fragment& logo_; + const vector& menu_; + }; + + // Generates package search form element. + // + class FORM_SEARCH + { + public: + FORM_SEARCH (const string& q): query_ (q) {} + + void + operator() (xml::serializer&) const; + + private: + const string& query_; + }; + + // 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. + // + class DIV_COUNTER + { + public: + DIV_COUNTER (size_t c, const char* s, const char* p) + : count_ (c), singular_ (s), plural_ (p) {} + + void + operator() (xml::serializer&) const; + + private: + size_t count_; + const char* singular_; + const char* plural_; + }; + + // Generates package name element. + // + class TR_NAME + { + public: + TR_NAME (const string& n, const string& q, const dir_path& r) + : name_ (n), query_param_ (q), root_ (r) {} + + void + operator() (xml::serializer&) const; + + private: + const string& name_; + const string& query_param_; + const dir_path& root_; + }; + + // Generates package version element. + // + class TR_VERSION + { + public: + // Display the version as a link to the package version details page. + // + TR_VERSION (const string& p, const string& v, const dir_path& r) + : package_ (&p), version_ (v), root_ (&r) {} + + // Display the version as a regular text. + // + TR_VERSION (const string& v) + : package_ (nullptr), version_ (v), root_ (nullptr) {} + + void + operator() (xml::serializer&) const; + + private: + const string* package_; + const string& version_; + const dir_path* root_; + }; + + // Generates package summary element. + // + class TR_SUMMARY + { + public: + TR_SUMMARY (const string& s): summary_ (s) {} + + void + operator() (xml::serializer&) const; + + private: + const string& summary_; + }; + + // Generates package license alternatives element. + // + class TR_LICENSE + { + public: + TR_LICENSE (const license_alternatives& l): licenses_ (l) {} + + void + operator() (xml::serializer&) 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&) const; + + private: + const license_alternatives& licenses_; + }; + + // Generates package tags element. + // + class TR_TAGS + { + public: + TR_TAGS (const strings& ts, const dir_path& r): tags_ (ts), root_ (r) {} + + void + operator() (xml::serializer&) const; + + private: + const strings& tags_; + const dir_path& root_; + }; + + // Generates package dependencies element. + // + class TR_DEPENDS + { + public: + TR_DEPENDS (const dependencies& d, const dir_path& r) + : dependencies_ (d), root_ (r) {} + + void + operator() (xml::serializer&) const; + + private: + const dependencies& dependencies_; + const dir_path& root_; + }; + + // Generates package requirements element. + // + class TR_REQUIRES + { + public: + TR_REQUIRES (const requirements& r): requirements_ (r) {} + + void + operator() (xml::serializer&) 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&) 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&) const; + + private: + const email& email_; + const char* label_; + }; + + // Generates package version priority element. + // + class TR_PRIORITY + { + public: + TR_PRIORITY (const priority& p): priority_ (p) {} + + void + operator() (xml::serializer&) const; + + private: + const priority& priority_; + }; + + // Generates package location element. + // + class TR_LOCATION + { + public: + TR_LOCATION (const string& n, const dir_path& r) + : name_ (n), root_ (r) {} + + void + operator() (xml::serializer&) const; + + private: + const string& name_; + const dir_path& root_; + }; + + // Generates package download URL element. + // + class TR_DOWNLOAD + { + public: + TR_DOWNLOAD (const string& u): url_ (u) {} + + void + operator() (xml::serializer&) const; + + private: + const string& url_; + }; + + // Generates sha256sum element. + // + class TR_SHA256SUM + { + public: + TR_SHA256SUM (const string& s): sha256sum_ (s) {} + + void + operator() (xml::serializer&) const; + + private: + const string& sha256sum_; + }; + + // Generates comment element. + // + class SPAN_COMMENT + { + public: + SPAN_COMMENT (const string& c): comment_ (c) {} + + void + operator() (xml::serializer&) const; + + private: + const string& comment_; + }; + + // Generates package description element. + // + class P_DESCRIPTION + { + public: + // Genereate full description. + // + P_DESCRIPTION (const string& d, const string& id = "") + : description_ (d), length_ (d.size ()), url_ (nullptr), id_ (id) {} + + // Genereate brief description. + // + P_DESCRIPTION (const string& d, size_t l, const string& u) + : description_ (d), length_ (l), url_ (&u) {} + + void + operator() (xml::serializer&) const; + + private: + const string& description_; + size_t length_; + const string* url_; // Full page url. + string id_; + }; + + // Generates package description element. + // + class PRE_CHANGES + { + public: + // Genereate full changes info. + // + PRE_CHANGES (const string& c) + : changes_ (c), length_ (c.size ()), url_ (nullptr) {} + + // Genereate brief changes info. + // + PRE_CHANGES (const string& c, size_t l, const string& u) + : changes_ (c), length_ (l), url_ (&u) {} + + void + operator() (xml::serializer&) const; + + private: + const string& changes_; + size_t length_; + const string* url_; // Full page url. + }; + + // Generates paging element. + // + class DIV_PAGER + { + public: + DIV_PAGER (size_t current_page, + size_t item_count, + size_t item_per_page, + size_t page_number_count, + const string& url); + + void + operator() (xml::serializer&) const; + + private: + size_t current_page_; + size_t item_count_; + size_t item_per_page_; + size_t page_number_count_; + const string& url_; + }; + + // Convert the argument to a string representing the valid HTML 5 'id' + // attribute value. + // + string + html_id (const string&); +} + +#endif // MOD_PAGE diff --git a/mod/page.cxx b/mod/page.cxx new file mode 100644 index 0000000..7fc2e90 --- /dev/null +++ b/mod/page.cxx @@ -0,0 +1,693 @@ +// file : mod/page.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include // hex, uppercase, right +#include +#include // setw(), setfill() +#include // min() + +#include + +#include +#include + +#include +#include + +using namespace std; +using namespace xml; +using namespace web; +using namespace web::xhtml; + +namespace brep +{ + // CSS_LINKS + // + void CSS_LINKS:: + operator() (serializer& s) const + { + static const path css ("@"); + + s << *LINK(REL="stylesheet", TYPE="text/css", HREF=root_ / css / path_); + } + + // DIV_HEADER + // + void DIV_HEADER:: + operator() (serializer& s) const + { + if (!logo_.empty () || !menu_.empty ()) + { + s << DIV(ID="header-bar") + << DIV(ID="header"); + + if (!logo_.empty ()) + s << DIV(ID="header-logo") << logo_ << ~DIV; + + if (!menu_.empty ()) + { + s << DIV(ID="header-menu") + << DIV(ID="header-menu-body"); + + for (const auto& m: menu_) + { + const string& l (m.link[0] == '/' || m.link.find (':') != string::npos + ? m.link + : root_.string () + m.link); + + s << A(HREF=l) << m.label << ~A; + } + + s << ~DIV + << ~DIV; + } + + s << ~DIV + << ~DIV; + } + } + + // FORM_SEARCH + // + void FORM_SEARCH:: + operator() (serializer& s) const + { + // The 'action' attribute is optional in HTML5. While the standard doesn'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; + } + + // DIV_COUNTER + // + void DIV_COUNTER:: + operator() (serializer& s) const + { + s << DIV(ID="count") + << count_ << " " + << (count_ % 10 == 1 && count_ % 100 != 11 ? singular_ : plural_) + << ~DIV; + } + + // TR_NAME + // + void TR_NAME:: + operator() (serializer& s) const + { + s << TR(CLASS="name") + << TH << "name" << ~TH + << TD + << SPAN(CLASS="value") + << A + << HREF + + // Propagate search criteria to the package details page. + // + << root_ / path (mime_url_encode (name_)) << query_param_ + + << ~HREF + << name_ + << ~A + << ~SPAN + << ~TD + << ~TR; + } + + void TR_VERSION:: + operator() (serializer& s) const + { + s << TR(CLASS="version") + << TH << "version" << ~TH + << TD + << SPAN(CLASS="value"); + + if (package_ == nullptr) + s << version_; + else + { + assert (root_ != nullptr); + s << A(HREF=*root_ / path (mime_url_encode (*package_)) / path (version_)) + << version_ + << ~A; + } + + s << ~SPAN + << ~TD + << ~TR; + } + + // TR_SUMMARY + // + void TR_SUMMARY:: + operator() (serializer& s) const + { + s << TR(CLASS="summary") + << TH << "summary" << ~TH + << TD << SPAN(CLASS="value") << summary_ << ~SPAN << ~TD + << ~TR; + } + + // TR_LICENSE + // + void TR_LICENSE:: + operator() (serializer& s) const + { + s << TR(CLASS="license") + << TH << "license" << ~TH + << TD + << SPAN(CLASS="value"); + + for (const auto& la: licenses_) + { + if (&la != &licenses_[0]) + s << " " << EM << "or" << ~EM << " "; + + bool m (la.size () > 1); + + if (m) + s << "("; + + for (const auto& l: la) + { + if (&l != &la[0]) + s << " " << EM << "and" << ~EM << " "; + + s << l; + } + + if (m) + s << ")"; + } + + s << ~SPAN + << ~TD + << ~TR; + } + + // 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; + } + } + + // TR_TAGS + // + void TR_TAGS:: + operator() (serializer& s) const + { + 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 << root_ << "?q=" << mime_url_encode (t) << ~HREF + << t + << ~A; + } + + s << ~SPAN + << ~TD + << ~TR; + } + } + + // TR_DEPENDS + // + void TR_DEPENDS:: + operator() (serializer& s) const + { + s << TR(CLASS="depends") + << TH << "depends" << ~TH + << TD + << SPAN(CLASS="value") + << dependencies_.size (); + + if (!dependencies_.empty ()) + s << "; "; + + for (const auto& d: dependencies_) + { + if (&d != &dependencies_[0]) + s << ", "; + + if (d.conditional) + s << "?"; + + // Suppress package name duplicates. + // + set names; + for (const auto& da: d) + names.emplace (da.name ()); + + bool mult (names.size () > 1); + + if (mult) + s << "("; + + bool first (true); + for (const auto& da: d) + { + string n (da.name ()); + if (names.find (n) != names.end ()) + { + names.erase (n); + + if (first) + first = false; + else + s << " | "; + + shared_ptr p (da.package.load ()); + assert (p->internal () || !p->other_repositories.empty ()); + + shared_ptr r ( + p->internal () + ? p->internal_repository.load () + : p->other_repositories[0].load ()); + + auto en (mime_url_encode (n)); + + if (r->url) + s << A(HREF=*r->url + en) << n << ~A; + else if (p->internal ()) + s << A(HREF=root_ / path (en)) << n << ~A; + else + // Display the dependency as a plain text if no repository URL + // available. + // + s << n; + } + } + + if (mult) + s << ")"; + } + + s << ~SPAN + << ~TD + << ~TR; + } + + // TR_REQUIRES + // + void TR_REQUIRES:: + operator() (serializer& s) const + { + // 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 (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 mult (r.size () > 1); + + if (mult) + s << "("; + + for (const auto& ra: r) + { + if (&ra != &r[0]) + s << " | "; + + s << ra; + } + + if (mult) + 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_) << email_ << ~A + << ~SPAN + << SPAN_COMMENT (email_.comment) + << ~TD + << ~TR; + } + + // TR_PRIORITY + // + void TR_PRIORITY:: + operator() (serializer& s) const + { + static const strings priority_names ({"low", "medium", "high", "security"}); + assert (priority_ < priority_names.size ()); + + s << TR(CLASS="priority") + << TH << "priority" << ~TH + << TD + << SPAN(CLASS="value") << priority_names[priority_] << ~SPAN + << SPAN_COMMENT (priority_.comment) + << ~TD + << ~TR; + } + + // TR_LOCATION + // + void TR_LOCATION:: + operator() (serializer& s) const + { + s << TR(CLASS="location") + << TH << "location" << ~TH + << TD + << SPAN(CLASS="value") + << A + << HREF + << root_ << "?about#" << mime_url_encode (html_id (name_)) + << ~HREF + << name_ + << ~A + << ~SPAN + << ~TD + << ~TR; + } + + // TR_DOWNLOAD + // + void TR_DOWNLOAD:: + operator() (serializer& s) const + { + s << TR(CLASS="download") + << TH << "download" << ~TH + << TD + << SPAN(CLASS="value") << A(HREF=url_) << url_ << ~A << ~SPAN + << ~TD + << ~TR; + } + + // TR_SHA256SUM + // + void TR_SHA256SUM:: + operator() (serializer& s) const + { + s << TR(CLASS="sha256") + << TH << "sha256" << ~TH + << TD << SPAN(CLASS="value") << sha256sum_ << ~SPAN << ~TD + << ~TR; + } + + // SPAN_COMMENT + // + void SPAN_COMMENT:: + operator() (serializer& s) const + { + if (size_t l = comment_.size ()) + s << SPAN(CLASS="comment") + << (comment_.back () == '.' ? string (comment_, 0, l - 1) : comment_) + << ~SPAN; + } + + // P_DESCRIPTION + // + void P_DESCRIPTION:: + operator() (serializer& s) const + { + if (description_.empty ()) + return; + + auto n (description_.find_first_of (" \t\n", length_)); + bool full (n == string::npos); // Description length is below the limit. + + // Truncate description if length exceed the limit. + // + const string& d (full ? 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; + + if (!id_.empty ()) + s << ID(id_); + + bool nl (false); // The previous character is '\n'. + for (const auto& c: d) + { + 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; + } + + s << c; + } + } + + if (!full) + { + assert (url_ != nullptr); + s << "... " << A(HREF=*url_) << "More" << ~A; + } + + s << ~P; + } + + // PRE_CHANGES + // + void PRE_CHANGES:: + operator() (serializer& s) const + { + if (changes_.empty ()) + return; + + auto n (changes_.find_first_of (" \t\n", length_)); + bool full (n == string::npos); // Changes length is below the limit. + + // Truncate changes if length exceed the limit. + // + const string& c (full ? changes_ : string (changes_, 0, n)); + s << PRE(ID="changes") << c; + + if (!full) + { + assert (url_ != nullptr); + s << "... " << A(HREF=*url_) << "More" << ~A; + } + + s << ~PRE; + } + + // DIV_PAGER + // + 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 + { + if (item_count_ == 0 || item_per_page_ == 0) + return; + + size_t pcount (item_count_ / item_per_page_); // Page count. + + if (item_count_ % item_per_page_) + ++pcount; + + if (pcount > 1) + { + auto url ( + [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=url (current_page_ - 1)) << "Prev" << ~A; + + if (page_number_count_) + { + size_t offset (page_number_count_ / 2); + size_t from (current_page_ > offset ? current_page_ - offset : 0); + size_t to (min (from + page_number_count_, pcount)); + + for (size_t p (from); p < to; ++p) + { + s << A(HREF=url (p)); + + if (p == current_page_) + s << ID("curr"); + + s << p + 1 + << ~A; + } + } + + if (current_page_ < pcount - 1) + s << A(ID="next", HREF=url (current_page_ + 1)) << "Next" << ~A; + + 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 + html_id (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 '~': + { + // We use '~' as an escape character because it doesn't require + // escaping in URLs. + // + o << "~" << setw (2) << static_cast (c); + break; + } + default: o << c; break; + } + } + + return o.str (); + } +} diff --git a/mod/services.cxx b/mod/services.cxx new file mode 100644 index 0000000..b0c5834 --- /dev/null +++ b/mod/services.cxx @@ -0,0 +1,15 @@ +// file : mod/services.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include // AP_MODULE_DECLARE_DATA + +#include + +#include +#include + +#include + +static brep::repository_root mod; +web::apache::service AP_MODULE_DECLARE_DATA brep_module ("brep", mod); diff --git a/mod/types-parsers b/mod/types-parsers new file mode 100644 index 0000000..cbea1bd --- /dev/null +++ b/mod/types-parsers @@ -0,0 +1,57 @@ +// file : mod/types-parsers -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +// CLI parsers, included into the generated source files. +// + +#ifndef MOD_TYPES_PARSERS +#define MOD_TYPES_PARSERS + +#include + +#include +#include + +#include + +namespace brep +{ + namespace cli + { + class scanner; + + template + struct parser; + + template <> + struct parser + { + static void + parse (dir_path&, scanner&); + }; + + template <> + struct parser + { + static void + parse (page_form&, scanner&); + }; + + template <> + struct parser + { + static void + parse (page_menu&, scanner&); + }; + + template <> + struct parser + { + static void + parse (web::xhtml::fragment&, scanner&); + }; + } +} + +#endif // MOD_TYPES_PARSERS diff --git a/mod/types-parsers.cxx b/mod/types-parsers.cxx new file mode 100644 index 0000000..279ab58 --- /dev/null +++ b/mod/types-parsers.cxx @@ -0,0 +1,114 @@ +// file : mod/types-parsers.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +using namespace std; +using namespace web::xhtml; + +namespace brep +{ + namespace cli + { + // Parse path. + // + template + static void + parse_path (T& x, scanner& s) + { + const char* o (s.next ()); + + if (!s.more ()) + throw missing_value (o); + + const char* v (s.next ()); + + try + { + x = T (v); + } + catch (const invalid_path&) + { + throw invalid_value (o, v); + } + } + + void parser:: + parse (dir_path& x, scanner& s) + { + parse_path (x, s); + } + + // Parse page_form. + // + void parser:: + parse (page_form& x, scanner& s) + { + const char* o (s.next ()); + + if (!s.more ()) + throw missing_value (o); + + const string v (s.next ()); + if (v == "full") + x = page_form::full; + else if (v == "brief") + x = page_form::brief; + else + throw invalid_value (o, v); + } + + // Parse page_menu. + // + void parser:: + parse (page_menu& x, scanner& s) + { + const char* o (s.next ()); + + if (!s.more ()) + throw missing_value (o); + + const string v (s.next ()); + + auto p (v.find ('=')); + if (p != string::npos) + { + string label (v, 0, p); + string link (v, p + 1); + + if (!label.empty ()) + { + x = page_menu (move (label), move (link)); + return; + } + } + + throw invalid_value (o, v); + } + + // Parse web::xhtml::fragment. + // + void parser:: + parse (fragment& x, scanner& s) + { + const char* o (s.next ()); + + if (!s.more ()) + throw missing_value (o); + + const char* v (s.next ()); + + try + { + x = fragment (v, o); + } + catch (const xml::parsing&) + { + throw invalid_value (o, v); + } + } + } +} -- cgit v1.1