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
|
namespace StardewModdingAPI.Toolkit.Framework
{
/// <summary>Reads strings into a semantic version.</summary>
internal static class SemanticVersionReader
{
/*********
** Public methods
*********/
/// <summary>Parse a semantic version string.</summary>
/// <param name="versionStr">The version string to parse.</param>
/// <param name="allowNonStandard">Whether to recognize non-standard semver extensions.</param>
/// <param name="major">The major version incremented for major API changes.</param>
/// <param name="minor">The minor version incremented for backwards-compatible changes.</param>
/// <param name="patch">The patch version for backwards-compatible fixes.</param>
/// <param name="platformRelease">The platform-specific version (if applicable).</param>
/// <param name="prereleaseTag">An optional prerelease tag.</param>
/// <param name="buildMetadata">Optional build metadata. This is ignored when determining version precedence.</param>
/// <returns>Returns whether the version was successfully parsed.</returns>
public static bool TryParse(string versionStr, bool allowNonStandard, out int major, out int minor, out int patch, out int platformRelease, out string prereleaseTag, out string buildMetadata)
{
// init
major = 0;
minor = 0;
patch = 0;
platformRelease = 0;
prereleaseTag = null;
buildMetadata = null;
// normalize
versionStr = versionStr?.Trim();
if (string.IsNullOrWhiteSpace(versionStr))
return false;
char[] raw = versionStr.ToCharArray();
// read major/minor version
int i = 0;
if (!TryParseVersionPart(raw, ref i, out major) || !TryParseLiteral(raw, ref i, '.') || !TryParseVersionPart(raw, ref i, out minor))
return false;
// read optional patch version
if (TryParseLiteral(raw, ref i, '.') && !TryParseVersionPart(raw, ref i, out patch))
return false;
// read optional non-standard platform release version
if (allowNonStandard && TryParseLiteral(raw, ref i, '.') && !TryParseVersionPart(raw, ref i, out platformRelease))
return false;
// read optional prerelease tag
if (TryParseLiteral(raw, ref i, '-') && !TryParseTag(raw, ref i, out prereleaseTag))
return false;
// read optional build tag
if (TryParseLiteral(raw, ref i, '+') && !TryParseTag(raw, ref i, out buildMetadata))
return false;
// validate
return i == versionStr.Length; // valid if we're at the end
}
/*********
** Private methods
*********/
/// <summary>Try to parse the next characters in a queue as a numeric part.</summary>
/// <param name="raw">The raw characters to parse.</param>
/// <param name="index">The index of the next character to read.</param>
/// <param name="part">The parsed part.</param>
private static bool TryParseVersionPart(char[] raw, ref int index, out int part)
{
part = 0;
// take digits
string str = "";
for (int i = index; i < raw.Length && char.IsDigit(raw[i]); i++)
str += raw[i];
// validate
if (str.Length == 0)
return false;
if (str.Length > 1 && str[0] == '0')
return false; // can't have leading zeros
// parse
part = int.Parse(str);
index += str.Length;
return true;
}
/// <summary>Try to parse a literal character.</summary>
/// <param name="raw">The raw characters to parse.</param>
/// <param name="index">The index of the next character to read.</param>
/// <param name="ch">The expected character.</param>
private static bool TryParseLiteral(char[] raw, ref int index, char ch)
{
if (index >= raw.Length || raw[index] != ch)
return false;
index++;
return true;
}
/// <summary>Try to parse a tag.</summary>
/// <param name="raw">The raw characters to parse.</param>
/// <param name="index">The index of the next character to read.</param>
/// <param name="tag">The parsed tag.</param>
private static bool TryParseTag(char[] raw, ref int index, out string tag)
{
// read tag length
int length = 0;
for (int i = index; i < raw.Length && (char.IsLetterOrDigit(raw[i]) || raw[i] == '-' || raw[i] == '.'); i++)
length++;
// validate
if (length == 0)
{
tag = null;
return false;
}
// parse
tag = new string(raw, index, length);
index += length;
return true;
}
}
}
|