1 /**
2 Copyright: Copyright (c) 2020, Joakim Brännström. All rights reserved.
3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 A type that holds a semantic version that provide ordering and easy
7 construction from a plain `string`.
8 */
9 module my.semver;
10 
11 @safe:
12 
13 /** A parsed semantic version.
14  *
15  * Currently only supports the numbering, no metadata after a `+`-sign.
16  */
17 struct SemVer {
18     import std.range : isOutputRange;
19 
20     private int[3] value_;
21 
22     /// Returns: major version number (first number).
23     int major() pure nothrow const @nogc {
24         return value_[0];
25     }
26 
27     /// Returns: minor version number (second number).
28     int minor() pure nothrow const @nogc {
29         return value_[1];
30     }
31 
32     /// Returns: bugfix version number (third number).
33     int bugFix() pure nothrow const @nogc {
34         return value_[2];
35     }
36 
37     ///
38     int opCmp(const SemVer rhs) pure nothrow const @nogc {
39         foreach (i; 0 .. value_.length) {
40             if (value_[i] < rhs.value_[i])
41                 return -1;
42             if (value_[i] > rhs.value_[i])
43                 return 1;
44         }
45         return 0;
46     }
47 
48     ///
49     bool opEquals(const SemVer x) pure nothrow const @nogc {
50         return value_ == x.value_;
51     }
52 
53     ///
54     bool opEquals(const int[] x) pure nothrow const @nogc {
55         return value_[] == x;
56     }
57 
58     ///
59     string toString() @safe pure const {
60         import std.array : appender;
61 
62         auto buf = appender!string;
63         toString(buf);
64         return buf.data;
65     }
66 
67     ///
68     void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) {
69         import std.format : formattedWrite;
70 
71         formattedWrite(w, "%s.%s.%s", value_[0], value_[1], value_[2]);
72     }
73 }
74 
75 /** Convert a string to a `SemVer`.
76  *
77  * An invalid `string` is returned as a `SemVer` with version `0.0.0`.
78  */
79 SemVer toSemVer(string s) {
80     import std.algorithm : filter;
81     import std.array : empty;
82     import std.conv : to;
83     import std.range : dropOne, enumerate;
84     import std.regex : regex, matchFirst;
85 
86     SemVer rval;
87 
88     const re = regex(`^(?:(\d+)\.)?(?:(\d+)\.)?(\d+)$`);
89     auto m = matchFirst(s, re);
90     if (m.empty)
91         return rval;
92 
93     try {
94         foreach (a; m.dropOne.filter!(a => !a.empty).enumerate) {
95             rval.value_[a.index] = a.value.to!int;
96         }
97     } catch (Exception e) {
98     }
99 
100     return rval;
101 }
102 
103 /// Example.
104 unittest {
105     import std.stdio : writeln;
106 
107     assert(toSemVer("1.2.3") == [1, 2, 3]);
108     assert(toSemVer("1.2") == [1, 2, 0]);
109     assert(toSemVer("1") == [1, 0, 0]);
110     assert(toSemVer("1.2.3.4") == [0, 0, 0]);
111 }