aboutsummaryrefslogtreecommitdiff
path: root/libbutl/semantic-version.cxx
blob: 9e0a1ef84dd7d98620febc415424e3bd8bd375d5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// file      : libbutl/semantic-version.cxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#include <libbutl/semantic-version.hxx>

#include <cassert>
#include <cstring>   // strchr()
#include <utility>   // move()
#include <stdexcept> // invalid_argument

using namespace std;

namespace butl
{
  string semantic_version::
  string (bool ib) const
  {
    std::string r;
    r  = to_string (major);
    r += '.';
    r += to_string (minor);
    r += '.';
    r += to_string (patch);
    if (!ib) r += build;
    return r;
  }

  uint64_t semantic_version::
  numeric () const
  {
    if (const char* w = (major > 99999 ? "major version greater than 99999" :
                         minor > 99999 ? "minor version greater than 99999" :
                         patch > 99999 ? "patch version greater than 99999" :
                         nullptr))
      throw invalid_argument (w);

    //          AAAAABBBBBCCCCC0000         BBBBBCCCCC0000         CCCCC0000
    return (major * 100000000000000) + (minor * 1000000000) + (patch * 10000);
  }

  semantic_version::
  semantic_version (uint64_t n, std::string b)
      : build (move (b))
  {
    //      AAAAABBBBBCCCCC0000
    if (n > 9999999999999990000ULL || (n % 10000) != 0)
      throw invalid_argument ("invalid numeric representation");

    //      AAAAABBBBBCCCCC0000
    major = n / 100000000000000 % 100000;
    minor = n /      1000000000 % 100000;
    patch = n /           10000 % 100000;
  }

  semantic_version::
  semantic_version (const std::string& s, size_t p, flags fs, const char* bs)
  {
    semantic_version_result r (parse_semantic_version_impl (s, p, fs, bs));

    if (r.version)
      *this = move (*r.version);
    else
      throw invalid_argument (r.failure_reason);
  }

  // From standard-version.cxx
  //
  bool
  parse_uint64 (const string& s, size_t& p,
                uint64_t& r,
                uint64_t min = 0, uint64_t max = uint64_t (~0));

  semantic_version_result
  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)};
    };

    semantic_version r;

    if (!parse_uint64 (s, p, r.major))
      return bail ("invalid major version");

    if (s[p] == '.') // Is there a minor version?
    {
      // Try to parse the minor version and treat it as build on failure
      // (e.g., 1.alpha).
      //
      if (parse_uint64 (s, ++p, r.minor))
      {
        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_minor)
      return bail ("'.' expected after major version");

    if (char c = s[p])
    {
      if (!allow_build || (*bs != '\0' && strchr (bs, c) == nullptr))
        return bail ("junk after version");

      r.build.assign (s, p, string::npos);
    }

    return semantic_version_result {move (r), string ()};
  }
}