aboutsummaryrefslogtreecommitdiff
path: root/butl
diff options
context:
space:
mode:
Diffstat (limited to 'butl')
-rw-r--r--butl/buildfile1
-rw-r--r--butl/standard-version111
-rw-r--r--butl/standard-version.cxx333
-rw-r--r--butl/standard-version.ixx94
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;
+ }
+}