diff options
Diffstat (limited to 'butl')
-rw-r--r-- | butl/buildfile | 1 | ||||
-rw-r--r-- | butl/standard-version | 111 | ||||
-rw-r--r-- | butl/standard-version.cxx | 333 | ||||
-rw-r--r-- | butl/standard-version.ixx | 94 |
4 files changed, 539 insertions, 0 deletions
diff --git a/butl/buildfile b/butl/buildfile index 75217fa..04e6e09 100644 --- a/butl/buildfile +++ b/butl/buildfile @@ -28,6 +28,7 @@ lib{butl}: \ {hxx ixx cxx}{ sendmail } \ {hxx cxx}{ sha256 } \ {hxx }{ small-vector } \ + {hxx ixx cxx}{ standard-version } \ {hxx cxx}{ string-parser } \ {hxx txx }{ string-table } \ {hxx cxx}{ tab-parser } \ diff --git a/butl/standard-version b/butl/standard-version new file mode 100644 index 0000000..0888900 --- /dev/null +++ b/butl/standard-version @@ -0,0 +1,111 @@ +// file : butl/standard-version -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUTL_STANDARD_VERSION +#define BUTL_STANDARD_VERSION + +#include <string> +#include <cstdint> // uint*_t +#include <cstddef> // size_t +#include <ostream> + +#include <butl/export> + +namespace butl +{ + // The build2 "standard version": + // + // [<epoch>~]<maj>.<min>.<patch>[-(a|b).<num>[.<snapsn>[.<snapid>]]][+<rev>] + // + struct LIBBUTL_EXPORT standard_version + { + // Invariants: + // + // 1. (E == 0) == (snapshot_sn == 0 && snapshot_id.empty ()) + // + // 2. snapshot_sn != latest_sn || snapshot_id.empty () + // + static const std::uint64_t latest_sn = std::uint64_t (~0); + + std::uint16_t epoch = 0; // 0 if not specified. + std::uint64_t version = 0; // AAABBBCCCDDDE + std::uint64_t snapshot_sn = 0; // 0 if not specifed, latest_sn if 'z'. + std::string snapshot_id; // Empty if not specified. + std::uint16_t revision = 0; // 0 if not specified. + + std::uint16_t major () const; + std::uint16_t minor () const; + std::uint16_t patch () const; + std::uint16_t pre_release () const; // Note: 0 is ambiguous (-a.0.z). + + // Note: return empty if the corresponding component is unspecified. + // + std::string string () const; // Package version. + std::string string_project () const; // Project version (no epoch/rev). + std::string string_version () const; // Version only (no snapshot). + std::string string_pre_release () const; // Pre-release part only (a.1). + std::string string_snapshot () const; // Snapshot part only (1234.1f23). + + bool empty () const {return version == 0;} + + bool alpha () const; + bool beta () const; + bool snapshot () const {return snapshot_sn != 0;} + + int + compare (const standard_version&) const; + + // Parse the version. Throw std::invalid_argument if the format is not + // recognizable or components are invalid. + // + explicit + standard_version (const std::string&); + + explicit + standard_version (std::uint64_t version); + + standard_version (std::uint64_t version, const std::string& snapshot); + + standard_version (std::uint16_t epoch, + std::uint64_t version, + const std::string& snapshot, + std::uint16_t revision); + + standard_version (std::uint16_t epoch, + std::uint64_t version, + std::uint64_t snapshot_sn, + std::string snapshot_id, + std::uint16_t revision); + + // Create empty version. + // + standard_version () = default; + + private: + void + parse_snapshot (const std::string&, std::size_t&); + }; + + inline bool + operator== (const standard_version& x, const standard_version& y) + { + return x.compare (y) == 0; + } + + inline bool + operator!= (const standard_version& x, const standard_version& y) + { + return !(x == y); + } + + inline std::ostream& + operator<< (std::ostream& o, const standard_version& x) + { + return o << x.string (); + } +} + +#include <butl/standard-version.ixx> + +#endif // BUTL_STANDARD_VERSION diff --git a/butl/standard-version.cxx b/butl/standard-version.cxx new file mode 100644 index 0000000..7e144a6 --- /dev/null +++ b/butl/standard-version.cxx @@ -0,0 +1,333 @@ +// file : butl/standard-version.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <butl/standard-version> + +#include <cstdlib> // strtoull() +#include <cstddef> // size_t +#include <stdexcept> // invalid_argument + +#include <butl/utility> // alnum() + +using namespace std; + +namespace butl +{ + // Utility functions + // + static uint64_t + parse_num (const string& s, size_t& p, + const char* m, + uint64_t min = 0, uint64_t max = 999) + { + if (s[p] == '-' || s[p] == '+') // strtoull() allows these. + throw invalid_argument (m); + + const char* b (s.c_str () + p); + char* e (nullptr); + uint64_t r (strtoull (b, &e, 10)); + + if (b == e || r < min || r > max) + throw invalid_argument (m); + + p = e - s.c_str (); + return static_cast<uint64_t> (r); + } + + static void + check_version (uint64_t version, bool snapshot) + { + // Check that the version isn't too large. + // + // AAABBBCCCDDDE + bool r (version < 10000000000000ULL); + + // Check that E version component is consistent with the snapshot flag. + // + if (r) + r = (version % 10) == (snapshot ? 1 : 0); + + // Check that pre-release number is consistent with the snapshot flag. + // + if (r) + { + uint64_t ab (version / 10 % 1000); + + // Note that if ab is 0, it can either mean non-pre-release version in + // the absence of snapshot number, or 'a.0' pre-release otherwise. If ab + // is 500, it can only mean 'b.0', that must be followed by a snapshot + // number. + // + if (ab != 0) + r = ab != 500 || snapshot; + } + + // Check that the major, the minor and the bugfix versions are not + // simultaneously zeros. + // + if (r) + r = (version / 10000) != 0; + + if (!r) + throw invalid_argument ("invalid project version"); + } + + // standard_version + // + standard_version:: + standard_version (const std::string& s) + { + auto bail = [] (const char* m) {throw invalid_argument (m);}; + + // Pre-parse the first component to see if the version starts with epoch, + // to keep the subsequent parsing straightforward. + // + bool ep (false); + { + char* e (nullptr); + strtoull (s.c_str (), &e, 10); + ep = *e == '~'; + } + + size_t p (0), n (s.size ()); + + if (ep) + { + epoch = parse_num (s, p, "invalid epoch", 1, uint16_t (~0)); + ++p; // Skip '~'. + } + + uint16_t ma, mi, bf, ab (0); + + ma = parse_num (s, p, "invalid major version"); + + // Note that here and below p is less or equal n, and so s[p] is always + // valid. + // + if (s[p] != '.') + bail ("'.' expected after major version"); + + mi = parse_num (s, ++p, "invalid minor version"); + + if (s[p] != '.') + bail ("'.' expected after minor version"); + + bf = parse_num (s, ++p, "invalid bugfix version"); + + // AAABBBCCCDDDE + version = ma * 10000000000ULL + + mi * 10000000ULL + + bf * 10000ULL; + + if (version == 0) + bail ("0.0.0 version"); + + // Parse the pre-release component if present. + // + if (s[p] == '-') + { + char k (s[++p]); + + if (k != 'a' && k != 'b') + bail ("'a' or 'b' expected in pre-release"); + + if (s[++p] != '.') + bail ("'.' expected after pre-release letter"); + + ab = parse_num (s, ++p, "invalid pre-release", 0, 499); + + if (k == 'b') + ab += 500; + + // Parse the snapshot components if present. Note that pre-release number + // can't be zero for the final pre-release. + // + if (s[p] == '.') + parse_snapshot (s, ++p); + else if (ab == 0 || ab == 500) + bail ("invalid final pre-release"); + } + + if (s[p] == '+') + revision = parse_num (s, ++p, "invalid revision", 1, uint16_t (~0)); + + if (p != n) + bail ("junk after version"); + + if (ab != 0) + version -= 10000 - ab * 10; + + if (snapshot_sn != 0) + version += 1; + } + + standard_version:: + standard_version (uint64_t v) + : version (v) + { + check_version (v, false); + } + + standard_version:: + standard_version (uint64_t v, const std::string& s) + : version (v) + { + bool snapshot (!s.empty ()); + check_version (version, snapshot); + + if (snapshot) + { + size_t p (0); + parse_snapshot (s, p); + + if (p != s.size ()) + throw invalid_argument ("junk after snapshot"); + } + } + + standard_version:: + standard_version (uint16_t ep, + uint64_t vr, + uint64_t sn, + std::string si, + uint16_t rv) + : epoch (ep), + version (vr), + snapshot_sn (sn), + snapshot_id (move (si)), + revision (rv) + { + check_version (vr, true); + + if (!snapshot_id.empty () && (snapshot_id.size () > 16 || + snapshot_sn == 0 || + snapshot_sn == latest_sn)) + throw invalid_argument ("invalid snapshot"); + } + + void standard_version:: + parse_snapshot (const std::string& s, size_t& p) + { + // Note that snapshot id must be empty for 'z' snapshot number. + // + if (s[p] == 'z') + { + snapshot_sn = latest_sn; + ++p; + return; + } + + uint64_t sn (parse_num (s, + p, + "invalid snapshot number", + 1, latest_sn - 1)); + std::string id; + if (s[p] == '.') + { + char c; + for (++p; alnum (c = s[p]); ++p) + id += c; + + if (id.empty () || id.size () > 16) + throw invalid_argument ("invalid snapshot id"); + } + + snapshot_sn = sn; + snapshot_id = move (id); + } + + string standard_version:: + string_pre_release () const + { + std::string r; + + if (alpha () || beta ()) + { + uint64_t ab (version / 10 % 1000); + + if (ab < 500) + { + r += "a."; + r += to_string (ab); + } + else + { + r += "b."; + r += to_string (ab - 500); + } + } + + return r; + } + + string standard_version:: + string_version () const + { + std::string r (to_string (major ()) + '.' + to_string (minor ()) + '.' + + to_string (patch ())); + + if (alpha () || beta ()) + { + r += '-'; + r += string_pre_release (); + + if (snapshot ()) + r += '.'; + } + + return r; + } + + string standard_version:: + string_snapshot () const + { + std::string r; + + if (snapshot ()) + { + r = snapshot_sn == latest_sn ? "z" : to_string (snapshot_sn); + + if (!snapshot_id.empty ()) + { + r += '.'; + r += snapshot_id; + } + } + + return r; + } + + string standard_version:: + string_project () const + { + std::string r (string_version ()); + + if (snapshot ()) + r += string_snapshot (); // string_version() includes trailing dot. + + return r; + } + + string standard_version:: + string () const + { + std::string r; + + if (epoch != 0) + { + r = to_string (epoch); + r += '~'; + } + + r += string_project (); + + if (revision != 0) + { + r += '+'; + r += to_string (revision); + } + + return r; + } +} diff --git a/butl/standard-version.ixx b/butl/standard-version.ixx new file mode 100644 index 0000000..e414deb --- /dev/null +++ b/butl/standard-version.ixx @@ -0,0 +1,94 @@ +// file : butl/standard-version.ixx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +namespace butl +{ + inline standard_version:: + standard_version ( std::uint16_t e, + std::uint64_t v, + const std::string& s, + std::uint16_t r) + : standard_version (v, s) + { + // Can't initialize above due to ctor delegating. + // + epoch = e; + revision = r; + } + + inline std::uint16_t standard_version:: + major () const + { + std::uint64_t v (version / 10); + std::uint64_t ab (v % 1000); + if (ab != 0) + v += 1000 - ab; + + return static_cast<std::uint16_t> (v / 1000000000 % 1000); + } + + inline std::uint16_t standard_version:: + minor () const + { + std::uint64_t v (version / 10); + std::uint64_t ab (v % 1000); + if (ab != 0) + v += 1000 - ab; + + return static_cast<std::uint16_t> (v / 1000000 % 1000); + } + + inline std::uint16_t standard_version:: + patch () const + { + std::uint64_t v (version / 10); + std::uint64_t ab (v % 1000); + if (ab != 0) + v += 1000 - ab; + + return static_cast<std::uint16_t> (v / 1000 % 1000); + } + + inline std::uint16_t standard_version:: + pre_release () const + { + std::uint64_t ab (version / 10 % 1000); + if (ab > 500) + ab -= 500; + + return static_cast<std::uint16_t> (ab); + } + + inline bool standard_version:: + alpha () const + { + std::uint64_t abe (version % 10000); + return abe > 0 && abe < 5000; + } + + inline bool standard_version:: + beta () const + { + std::uint64_t abe (version % 10000); + return abe > 5000; + } + + inline int standard_version:: + compare (const standard_version& v) const + { + if (epoch != v.epoch) + return epoch < v.epoch ? -1 : 1; + + if (version != v.version) + return version < v.version ? -1 : 1; + + if (snapshot_sn != v.snapshot_sn) + return snapshot_sn < v.snapshot_sn ? -1 : 1; + + if (revision != v.revision) + return revision < v.revision ? -1 : 1; + + return 0; + } +} |