Browse Source

Strongly typed versions

default_compile_flags
vector-of-bool 5 years ago
parent
commit
ebbda2040d
3 changed files with 145 additions and 0 deletions
  1. +76
    -0
      src/semver/version.cpp
  2. +45
    -0
      src/semver/version.hpp
  3. +24
    -0
      src/semver/version.test.cpp

+ 76
- 0
src/semver/version.cpp View File

@@ -0,0 +1,76 @@
#include "./version.hpp"

#include <algorithm>
#include <array>
#include <cassert>
#include <charconv>

using namespace semver;

using siter = std::string_view::iterator;

namespace {

version parse(const char* ptr, std::size_t size) {
version ret;
// const auto str_begin = ptr;
const auto str_end = ptr + size;

std::from_chars_result fc_res;
auto did_error = [&](int elem) { return fc_res.ec == std::errc::invalid_argument || elem < 0; };

// Parse major
fc_res = std::from_chars(ptr, str_end, ret.major);
if (did_error(ret.major) || fc_res.ptr == str_end || *fc_res.ptr != '.') {
throw invalid_version(0);
}

// Parse minor
ptr = fc_res.ptr + 1;
fc_res = std::from_chars(ptr, str_end, ret.minor);
if (did_error(ret.minor) || fc_res.ptr == str_end || *fc_res.ptr != '.') {
throw invalid_version(ptr - str_end);
}

// Parse patch
ptr = fc_res.ptr + 1;
fc_res = std::from_chars(ptr, str_end, ret.patch);
if (did_error(ret.patch)) {
throw invalid_version(ptr - str_end);
}

if (fc_res.ptr != str_end) {
assert(false && "More complex version numbers are not ready yet!");
throw invalid_version(-42);
}

return ret;
}

} // namespace

version version::parse(std::string_view s) { return ::parse(s.data(), s.size()); }

std::string version::to_string() const noexcept {
std::array<char, 256> buffer;
const auto buf_begin = buffer.data();
auto buf_ptr = buf_begin;
const auto buf_end = buf_ptr + buffer.size();

auto conv_one = [&](auto n) {
auto tc_res = std::to_chars(buf_ptr, buf_end, n);
if (tc_res.ec == std::errc::value_too_large || tc_res.ptr == buf_end) {
assert(false && "Our buffers weren't big enough! This is a bug!");
std::terminate();
}
buf_ptr = tc_res.ptr;
};

conv_one(major);
*buf_ptr++ = '.';
conv_one(minor);
*buf_ptr++ = '.';
conv_one(patch);

return std::string(buf_begin, (buf_ptr - buf_begin));
}

+ 45
- 0
src/semver/version.hpp View File

@@ -0,0 +1,45 @@
#pragma once

#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
#include <tuple>

namespace semver {

class invalid_version : std::runtime_error {
std::ptrdiff_t _offset = 0;

public:
invalid_version(std::ptrdiff_t n)
: runtime_error("Invalid version number")
, _offset(n) {}

auto offset() const noexcept { return _offset; }
};

struct version {
int major = 0;
int minor = 0;
int patch = 0;

static version parse(std::string_view s);

std::string to_string() const noexcept;

auto tie() const noexcept { return std::tie(major, minor, patch); }
auto tie() noexcept { return std::tie(major, minor, patch); }
};

inline bool operator!=(const version& lhs, const version& rhs) noexcept {
return lhs.tie() != rhs.tie();
}

inline bool operator<(const version& lhs, const version& rhs) noexcept {
return lhs.tie() < rhs.tie();
}

inline std::string to_string(const version& ver) noexcept { return ver.to_string(); }

} // namespace semver

+ 24
- 0
src/semver/version.test.cpp View File

@@ -0,0 +1,24 @@
#include "./version.hpp"

#include <dds/util.test.hpp>

namespace {

void test_simple_parse() {
auto v1 = semver::version::parse("1.2.3");
CHECK(v1.major == 1);
CHECK(v1.minor == 2);
CHECK(v1.patch == 3);

CHECK(v1.to_string() == "1.2.3");
v1.patch = 55;
CHECK(v1.to_string() == "1.2.55");
v1.major = 999999;
CHECK(v1.to_string() == "999999.2.55");
}

void run_tests() { test_simple_parse(); }

} // namespace

DDS_TEST_MAIN;

Loading…
Cancel
Save