-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSemVersionLib.sol
More file actions
292 lines (257 loc) · 9.46 KB
/
SemVersionLib.sol
File metadata and controls
292 lines (257 loc) · 9.46 KB
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
pragma solidity ^0.4.0;
/// @title Library which implements a semver datatype and comparisons.
/// @author Piper Merriam <pipermerriam@gmail.com>
library SemVersionLib {
struct SemVersion {
bytes32 hash;
uint32 major;
uint32 minor;
uint32 patch;
string preRelease;
string build;
string[] preReleaseIdentifiers;
}
enum Comparison {
Before,
Same,
After
}
/// @dev Initialize a SemVersion struct
/// @param self The SemVersion object to initialize.
/// @param major The major portion of the semver version string.
/// @param minor The minor portion of the semver version string.
/// @param patch The patch portion of the semver version string.
/// @param preRelease The pre-release portion of the semver version string. Use empty string if the version string has no pre-release portion.
/// @param build The build portion of the semver version string. Use empty string if the version string has no build portion.
function init(SemVersion storage self,
uint32 major,
uint32 minor,
uint32 patch,
string preRelease,
string build) public returns (bool) {
self.major = major;
self.minor = minor;
self.patch = patch;
self.preRelease = preRelease;
self.preReleaseIdentifiers = splitIdentifiers(preRelease);
self.build = build;
self.hash = sha3(major, minor, patch, preRelease);
return true;
}
//
// Storage Operations
//
/// @dev Return boolean indicating if the two SemVersion objects are considered equal
/// @param self The first SemVersion
/// @param other The second SemVersion
function isEqual(SemVersion storage self, SemVersion storage other) public returns (bool) {
return self.hash == other.hash;
}
/// @dev Return boolean indicating if the first SemVersion object is considered strictly greater than the second.
/// @param self The first SemVersion
/// @param other The second SemVersion
function isGreater(SemVersion storage self, SemVersion storage other) public returns (bool) {
if (self.hash == other.hash) {
return false;
} else if (self.major > other.major) {
return true;
} else if (self.major < other.major) {
return false;
} else if (self.minor > other.minor) {
return true;
} else if (self.minor < other.minor) {
return false;
} else if (self.patch > other.patch) {
return true;
} else if (self.patch < other.patch) {
return false;
} else if (!isPreRelease(self) && isPreRelease(other)) {
return true;
} else if (isPreRelease(self) && !isPreRelease(other)) {
return false;
} else if (isPreReleaseGreater(self, other)) {
return true;
} else {
return false;
}
}
/// @dev Return boolean indicating if the first SemVersion object is considered greater than or equal to the second.
/// @param self The first SemVersion
/// @param other The second SemVersion
function isGreaterOrEqual(SemVersion storage self, SemVersion storage other) public returns (bool) {
return isEqual(self, other) || isGreater(self, other);
}
/*
* PreRelease comparisons
*/
/// @dev Return boolean indicating if the pre-release string from the first SemVersion object is considered greater than the pre-release string from the second SemVersion object.
/// @param left The first SemVersion
/// @param right The second SemVersion
function isPreReleaseGreater(SemVersion storage left, SemVersion storage right) internal returns (bool) {
return comparePreReleases(left, right) == Comparison.After;
}
/// @dev Return boolean indicating if the provided SemVersion is a pre-release.
/// @param self The SemVersion
function isPreRelease(SemVersion storage self) internal returns (bool) {
return self.preReleaseIdentifiers.length > 0;
}
/// @dev Return a comparison of the pre-release strings for the two provided SemVersion objects.
/// @param left The first SemVersion
/// @param right The second SemVersion
function comparePreReleases(SemVersion storage left, SemVersion storage right) internal returns (Comparison comparisonResult) {
uint minLength = min(left.preReleaseIdentifiers.length,
right.preReleaseIdentifiers.length);
for (uint i = 0; i < minLength; i++) {
if (isNumericString(left.preReleaseIdentifiers[i]) && isNumericString(right.preReleaseIdentifiers[i])) {
comparisonResult = compareNumericStrings(left.preReleaseIdentifiers[i], right.preReleaseIdentifiers[i]);
} else {
comparisonResult = compareStrings(left.preReleaseIdentifiers[i], right.preReleaseIdentifiers[i]);
}
if (comparisonResult != Comparison.Same) {
return comparisonResult;
}
continue;
}
if (left.preReleaseIdentifiers.length < right.preReleaseIdentifiers.length) {
return Comparison.Before;
} else if (left.preReleaseIdentifiers.length > right.preReleaseIdentifiers.length) {
return Comparison.After;
} else {
return Comparison.Same;
}
}
//
// PreRelease String Utils
//
/// @dev Return a comparison based on the ASCII ordering of the two strings
/// @param left The first string
/// @param right The second string
function compareStrings(string left, string right) internal returns (Comparison) {
for (uint i = 0; i < min(bytes(left).length, bytes(right).length); i++) {
if (bytes(left)[i] == bytes(right)[i]) {
continue;
} else if (uint(bytes(left)[i]) < uint(bytes(right)[i])) {
return Comparison.Before;
} else {
return Comparison.After;
}
}
if (bytes(left).length < bytes(right).length) {
return Comparison.Before;
} else if (bytes(left).length > bytes(right).length) {
return Comparison.After;
} else {
return Comparison.Same;
}
}
/// @dev Return a comparison based on the integer representation of the two string.
/// @param left The first string
/// @param right The second string
function compareNumericStrings(string left, string right) internal returns (Comparison) {
uint leftAsNumber = castStringToUInt(left);
uint rightAsNumber = castStringToUInt(right);
if (leftAsNumber < rightAsNumber) {
return Comparison.Before;
} else if (leftAsNumber > rightAsNumber) {
return Comparison.After;
} else {
return Comparison.Same;
}
}
/// @dev Splits a string on periods.
/// @param preRelease The string to split.
function splitIdentifiers(string preRelease) internal returns (string[]) {
if (bytes(preRelease).length == 0) {
return new string[](0);
}
uint i;
uint leftBound = 0;
uint numIdentifiers = 1;
for (i = 0; i < bytes(preRelease).length; i++) {
if (bytes(preRelease)[i] == PERIOD) {
numIdentifiers += 1;
}
}
string[] memory preReleaseIdentifiers = new string[](numIdentifiers);
numIdentifiers = 0;
for (i = 0; i <= bytes(preRelease).length; i++) {
if (i == bytes(preRelease).length || bytes(preRelease)[i] == PERIOD) {
uint identifierLength = i - leftBound;
bytes memory buffer = new bytes(identifierLength);
for (uint j = 0; j < identifierLength; j++) {
buffer[j] = bytes(preRelease)[j + leftBound];
}
preReleaseIdentifiers[numIdentifiers] = string(buffer);
leftBound = i + 1;
numIdentifiers += 1;
}
}
return preReleaseIdentifiers;
}
//
// Math utils
//
/// @dev Returns the minimum of two unsigned integers
/// @param a The first unsigned integer
/// @param b The first unsigned integer
function min(uint a, uint b) internal returns (uint) {
if (a <= b) {
return a;
} else {
return b;
}
}
//
// Char Utils
//
uint constant DIGIT_0 = uint(bytes1('0'));
uint constant DIGIT_9 = uint(bytes1('9'));
bytes1 constant PERIOD = bytes1('.');
/// @dev Returns boolean indicating if the provided character is a numeric digit.
/// @param v The character to check.
function isDigit(bytes1 v) internal returns (bool) {
return (uint(v) >= DIGIT_0 && uint(v) <= DIGIT_9);
}
//
// String Utils
//
/// @dev Returns boolean indicating if the provided string is all numeric.
/// @param value The string to check.
function isNumericString(string value) internal returns (bool) {
for (uint i = 0; i < bytes(value).length; i++) {
if (!isDigit(bytes(value)[i])) {
return false;
}
}
return bytes(value).length > 0;
}
/// @dev Returns the integer representation of a numeric string.
/// @param numericString The string to convert.
function castStringToUInt(string numericString) internal returns (uint) {
uint value = 0;
for (uint i = 0; i < bytes(numericString).length; i++) {
value *= 10;
value += uint(bytes(numericString)[i]) - 48;
}
return value;
}
/// @dev Concatenates the two strings together.
/// @param _head The first string
/// @param tail The second string
function concat(string storage _head, string tail) returns (bool) {
bytes head = bytes(_head);
for (uint i = 0; i < bytes(tail).length; i++) {
head.push(bytes(tail)[i]);
}
_head = string(head);
return true;
}
/// @dev Concatenates the provided byte to the end of the provided string.
/// @param value The string to append the byte to.
/// @param b The byte.
function concatByte(string storage value, bytes1 b) returns (bool) {
bytes memory _b = new bytes(1);
_b[0] = b;
return concat(value, string(_b));
}
}