aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2022-10-25 11:52:47 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2022-10-25 13:13:36 +0300
commit397d710073eae9ad282bc0df9482a41d621acde5 (patch)
tree10e68fd289a91d6879afdcdd77f6c50508587fc9
parent9c14033610bc00072d00389c879ac305efbb95df (diff)
Allow omitting minor version number in semantic_version
-rw-r--r--libbutl/git.cxx4
-rw-r--r--libbutl/openssl.txx1
-rw-r--r--libbutl/semantic-version.cxx70
-rw-r--r--libbutl/semantic-version.hxx70
-rw-r--r--libbutl/semantic-version.ixx64
-rw-r--r--tests/semantic-version/driver.cxx33
6 files changed, 159 insertions, 83 deletions
diff --git a/libbutl/git.cxx b/libbutl/git.cxx
index cc10c91..f37e16a 100644
--- a/libbutl/git.cxx
+++ b/libbutl/git.cxx
@@ -36,7 +36,9 @@ namespace butl
// MinGit: git version 2.16.1.windows.1
//
if (s.compare (0, 12, "git version ") == 0)
- return parse_semantic_version (s, 12, "" /* build_separators */);
+ return parse_semantic_version (s, 12,
+ semantic_version::allow_build,
+ "" /* build_separators */);
return nullopt;
}
diff --git a/libbutl/openssl.txx b/libbutl/openssl.txx
index 01e854c..f55432d 100644
--- a/libbutl/openssl.txx
+++ b/libbutl/openssl.txx
@@ -105,6 +105,7 @@ namespace butl
optional<semantic_version> ver (
parse_semantic_version (string (s, b, e != string::npos ? e - b : e),
+ semantic_version::allow_build,
"" /* build_separators */));
if (!ver)
diff --git a/libbutl/semantic-version.cxx b/libbutl/semantic-version.cxx
index 3be382f..9e0a1ef 100644
--- a/libbutl/semantic-version.cxx
+++ b/libbutl/semantic-version.cxx
@@ -3,6 +3,7 @@
#include <libbutl/semantic-version.hxx>
+#include <cassert>
#include <cstring> // strchr()
#include <utility> // move()
#include <stdexcept> // invalid_argument
@@ -52,9 +53,9 @@ namespace butl
}
semantic_version::
- semantic_version (const std::string& s, size_t p, const char* bs)
+ semantic_version (const std::string& s, size_t p, flags fs, const char* bs)
{
- semantic_version_result r (parse_semantic_version_impl (s, p, bs));
+ semantic_version_result r (parse_semantic_version_impl (s, p, fs, bs));
if (r.version)
*this = move (*r.version);
@@ -70,8 +71,27 @@ namespace butl
uint64_t min = 0, uint64_t max = uint64_t (~0));
semantic_version_result
- parse_semantic_version_impl (const string& s, size_t p, const char* bs)
+ parse_semantic_version_impl (const string& s, size_t p,
+ semantic_version::flags fs,
+ const char* bs)
{
+ bool allow_build ((fs & semantic_version::allow_build) != 0);
+
+ // If build separators are specified, then the allow_build flag must be
+ // specified explicitly.
+ //
+ assert (bs == nullptr || allow_build);
+
+ if (allow_build && bs == nullptr)
+ bs = "-+";
+
+ bool require_minor ((fs & semantic_version::allow_omit_minor) == 0);
+
+ if (!require_minor)
+ fs |= semantic_version::allow_omit_patch;
+
+ bool require_patch ((fs & semantic_version::allow_omit_patch) == 0);
+
auto bail = [] (string m)
{
return semantic_version_result {nullopt, move (m)};
@@ -82,31 +102,47 @@ namespace butl
if (!parse_uint64 (s, p, r.major))
return bail ("invalid major version");
- if (s[p] != '.')
- return bail ("'.' expected after major version");
-
- if (!parse_uint64 (s, ++p, r.minor))
- return bail ("invalid minor version");
-
- if (s[p] == '.')
+ if (s[p] == '.') // Is there a minor version?
{
- // Treat it as build if failed to parse as patch (e.g., 1.2.alpha).
+ // Try to parse the minor version and treat it as build on failure
+ // (e.g., 1.alpha).
//
- if (!parse_uint64 (s, ++p, r.patch))
+ if (parse_uint64 (s, ++p, r.minor))
{
- //if (require_patch)
- // return bail ("invalid patch version");
+ if (s[p] == '.') // Is there a patch version?
+ {
+ // Try to parse the patch version and treat it as build on failure
+ // (e.g., 1.2.alpha).
+ //
+ if (parse_uint64 (s, ++p, r.patch))
+ ;
+ else
+ {
+ if (require_patch)
+ return bail ("invalid patch version");
+
+ --p;
+ // Fall through.
+ }
+ }
+ else if (require_patch)
+ return bail ("'.' expected after minor version");
+ }
+ else
+ {
+ if (require_minor)
+ return bail ("invalid minor version");
--p;
// Fall through.
}
}
- //else if (require_patch)
- // return bail ("'.' expected after minor version");
+ else if (require_minor)
+ return bail ("'.' expected after major version");
if (char c = s[p])
{
- if (bs == nullptr || (*bs != '\0' && strchr (bs, c) == nullptr))
+ if (!allow_build || (*bs != '\0' && strchr (bs, c) == nullptr))
return bail ("junk after version");
r.build.assign (s, p, string::npos);
diff --git a/libbutl/semantic-version.hxx b/libbutl/semantic-version.hxx
index 16f3d56..1dc7d1d 100644
--- a/libbutl/semantic-version.hxx
+++ b/libbutl/semantic-version.hxx
@@ -27,15 +27,9 @@ namespace butl
{
// Semantic or semantic-like version.
//
- // <major>.<minor>[.<patch>][<build>]
+ // <major>[.<minor>[.<patch>]][<build>]
//
- // If the patch component is absent, then it defaults to 0.
- //
- // @@ Currently there is no way to enforce the three-component version.
- // Supporting this will require changing allow_build to a bit-wise
- // flag. See parse_semantic_version_impl() for some sketched code.
- // We may also want to pass these flags to string() to not print
- // 0 patch.
+ // If the minor and patch components are absent, then they default to 0.
//
// By default, a version containing the <build> component is considered
// valid only if separated from <patch> with '-' (semver pre-release) or '+'
@@ -63,23 +57,36 @@ namespace butl
std::uint64_t patch,
std::string build = "");
- // The build_separators argument can be NULL (no build component allowed),
- // empty (any build component allowed), or a string of characters to allow
- // as separators. When allow_build is true build_separators defaults to
- // "-+".
+ // If the allow_build flag is specified, then build_separators argument
+ // can be a string of characters to allow as separators, empty (any build
+ // component allowed), or NULL (defaults to "-+").
//
- explicit
- semantic_version (const std::string&, bool allow_build = true);
+ // Note: allow_omit_minor implies allow_omit_patch.
+ //
+ enum flags
+ {
+ none = 0, // Exact <major>.<minor>.<patch> form.
+ allow_omit_minor = 0x01, // Allow <major> form.
+ allow_omit_patch = 0x02, // Allow <major>.<minor> form.
+ allow_build = 0x04, // Allow <major>.<minor>.<patch>-<build> form.
+ };
- semantic_version (const std::string&, const char* build_separators);
+ explicit
+ semantic_version (const std::string&,
+ flags = none,
+ const char* build_separators = nullptr);
// As above but parse from the specified position until the end of the
// string.
//
- semantic_version (const std::string&, std::size_t pos, bool = true);
-
- semantic_version (const std::string&, std::size_t pos, const char*);
+ semantic_version (const std::string&,
+ std::size_t pos,
+ flags = none,
+ const char* = nullptr);
+ // @@ We may also want to pass allow_* flags not to print 0 minor/patch or
+ // maybe invent ignore_* flags.
+ //
std::string
string (bool ignore_build = false) const;
@@ -116,16 +123,15 @@ namespace butl
// Try to parse a string as a semantic version returning nullopt if invalid.
//
optional<semantic_version>
- parse_semantic_version (const std::string&, bool allow_build = true);
+ parse_semantic_version (const std::string&,
+ semantic_version::flags = semantic_version::none,
+ const char* build_separators = nullptr);
optional<semantic_version>
- parse_semantic_version (const std::string&, const char* build_separators);
-
- optional<semantic_version>
- parse_semantic_version (const std::string&, std::size_t pos, bool = true);
-
- optional<semantic_version>
- parse_semantic_version (const std::string&, std::size_t pos, const char*);
+ parse_semantic_version (const std::string&,
+ std::size_t pos,
+ semantic_version::flags = semantic_version::none,
+ const char* = nullptr);
// NOTE: comparison operators take the build component into account.
//
@@ -170,6 +176,18 @@ namespace butl
{
return o << x.string ();
}
+
+ inline semantic_version::flags
+ operator& (semantic_version::flags, semantic_version::flags);
+
+ inline semantic_version::flags
+ operator| (semantic_version::flags, semantic_version::flags);
+
+ inline semantic_version::flags
+ operator&= (semantic_version::flags&, semantic_version::flags);
+
+ inline semantic_version::flags
+ operator|= (semantic_version::flags&, semantic_version::flags);
}
#include <libbutl/semantic-version.ixx>
diff --git a/libbutl/semantic-version.ixx b/libbutl/semantic-version.ixx
index 6bf7584..67cd8c0 100644
--- a/libbutl/semantic-version.ixx
+++ b/libbutl/semantic-version.ixx
@@ -15,23 +15,9 @@ namespace butl
{
}
- // Note: the order is important to MinGW GCC (DLL linkage).
- //
inline semantic_version::
- semantic_version (const std::string& s, std::size_t p, bool ab)
- : semantic_version (s, p, ab ? "-+" : nullptr)
- {
- }
-
- inline semantic_version::
- semantic_version (const std::string& s, const char* bs)
- : semantic_version (s, 0, bs)
- {
- }
-
- inline semantic_version::
- semantic_version (const std::string& s, bool ab)
- : semantic_version (s, ab ? "-+" : nullptr)
+ semantic_version (const std::string& s, flags fs, const char* bs)
+ : semantic_version (s, 0, fs, bs)
{
}
@@ -42,29 +28,53 @@ namespace butl
};
LIBBUTL_SYMEXPORT semantic_version_result
- parse_semantic_version_impl (const std::string&, std::size_t, const char*);
+ parse_semantic_version_impl (const std::string&,
+ std::size_t,
+ semantic_version::flags,
+ const char*);
inline optional<semantic_version>
- parse_semantic_version (const std::string& s, bool ab)
+ parse_semantic_version (const std::string& s,
+ semantic_version::flags fs,
+ const char* bs)
{
- return parse_semantic_version (s, ab ? "-+" : nullptr);
+ return parse_semantic_version_impl (s, 0, fs, bs).version;
}
inline optional<semantic_version>
- parse_semantic_version (const std::string& s, const char* bs)
+ parse_semantic_version (const std::string& s,
+ std::size_t p,
+ semantic_version::flags fs,
+ const char* bs)
{
- return parse_semantic_version_impl (s, 0, bs).version;
+ return parse_semantic_version_impl (s, p, fs, bs).version;
}
- inline optional<semantic_version>
- parse_semantic_version (const std::string& s, std::size_t p, bool ab)
+ inline semantic_version::flags
+ operator& (semantic_version::flags x, semantic_version::flags y)
{
- return parse_semantic_version (s, p, ab ? "-+" : nullptr);
+ return x &= y;
}
- inline optional<semantic_version>
- parse_semantic_version (const std::string& s, std::size_t p, const char* bs)
+ inline semantic_version::flags
+ operator| (semantic_version::flags x, semantic_version::flags y)
+ {
+ return x |= y;
+ }
+
+ inline semantic_version::flags
+ operator&= (semantic_version::flags& x, semantic_version::flags y)
+ {
+ return x = static_cast<semantic_version::flags> (
+ static_cast<std::uint16_t> (x) &
+ static_cast<std::uint16_t> (y));
+ }
+
+ inline semantic_version::flags
+ operator|= (semantic_version::flags& x, semantic_version::flags y)
{
- return parse_semantic_version_impl (s, p, bs).version;
+ return x = static_cast<semantic_version::flags> (
+ static_cast<std::uint16_t> (x) |
+ static_cast<std::uint16_t> (y));
}
}
diff --git a/tests/semantic-version/driver.cxx b/tests/semantic-version/driver.cxx
index 2bdd415..3c20a6c 100644
--- a/tests/semantic-version/driver.cxx
+++ b/tests/semantic-version/driver.cxx
@@ -23,7 +23,6 @@ main ()
semver v;
assert (v.major == 0 && v.minor == 0 && v.patch == 0 && v.build.empty ());
}
-
{
semver v (1, 2, 3);
assert (v.major == 1 && v.minor == 2 && v.patch == 3 && v.build.empty ());
@@ -46,17 +45,27 @@ main ()
// String representation.
//
- assert (semver ("1.2") == semver (1, 2, 0));
- assert (semver ("1.2-3") == semver (1, 2, 0, "-3"));
- assert (semver ("1.2.a1", "+-.") == semver (1, 2, 0, ".a1"));
- assert (semver ("1.2.3") == semver (1, 2, 3));
- assert (semver ("1.2.3-4") == semver (1, 2, 3, "-4"));
- assert (semver ("1.2.3+4") == semver (1, 2, 3, "+4"));
- assert (semver ("1.2.3.4", "+-.") == semver (1, 2, 3, ".4"));
- assert (semver ("1.2.3a", "") == semver (1, 2, 3, "a"));
- try {semver v ("1.2.3-4", false); assert (false);} catch (failed) {}
- try {semver v ("1.2.3.4"); assert (false);} catch (failed) {}
- try {semver v ("1.2.3a"); assert (false);} catch (failed) {}
+ assert (semver ("1", semver::allow_omit_minor) == semver (1, 0, 0));
+ assert (semver ("1-2", semver::allow_omit_minor | semver::allow_build) == semver (1, 0, 0, "-2"));
+ assert (semver ("1.2", semver::allow_omit_minor) == semver (1, 2, 0));
+ assert (semver ("1.2+a", semver::allow_omit_minor | semver::allow_build) == semver (1, 2, 0, "+a"));
+ assert (semver ("1.2", semver::allow_omit_patch) == semver (1, 2, 0));
+ assert (semver ("1.2-3", semver::allow_omit_patch | semver::allow_build) == semver (1, 2, 0, "-3"));
+ assert (semver ("1.2.a1", semver::allow_omit_patch | semver::allow_build, ".+-") == semver (1, 2, 0, ".a1"));
+ assert (semver ("1.2.3") == semver (1, 2, 3));
+ assert (semver ("1.2.3-4", semver::allow_build) == semver (1, 2, 3, "-4"));
+ assert (semver ("1.2.3+4", semver::allow_build) == semver (1, 2, 3, "+4"));
+ assert (semver ("1.2.3.4", semver::allow_build, "+-.") == semver (1, 2, 3, ".4"));
+ assert (semver ("1.2.3a", semver::allow_build, "") == semver (1, 2, 3, "a"));
+
+ try {semver v ("1"); assert (false);} catch (failed) {}
+ try {semver v ("1.x.2"); assert (false);} catch (failed) {}
+ try {semver v ("1.2"); assert (false);} catch (failed) {}
+ try {semver v ("1.2.x"); assert (false);} catch (failed) {}
+ try {semver v ("1.2.3-4"); assert (false);} catch (failed) {}
+ try {semver v ("1.2.3.4"); assert (false);} catch (failed) {}
+ try {semver v ("1.2.3a"); assert (false);} catch (failed) {}
+
assert (!parse_semantic_version ("1.2.3.4"));
// Numeric representation.