1 /** 2 * Authors: Azbuka-slovensko 3 * License: MIT, see LICENCE.md 4 * Copyright: Azbuka-slovensko 2016 5 * See_Also: 6 * Semantic Versioning http://semver.org/ 7 */ 8 module BrightProof; 9 10 /** 11 * Exception for easy error handling 12 */ 13 class SemVerException : Exception { 14 /** 15 * Params: 16 * msg = message 17 * file = file, where SemVerException have been throwed 18 * line = line number in file 19 * next = next exception 20 */ 21 @safe pure nothrow this(string msg, 22 string file = __FILE__, 23 size_t line = __LINE__, 24 Throwable next = null) { 25 super(msg, file, line, next); 26 } 27 } 28 29 /** 30 * Main struct 31 * Examples: 32 * --- 33 * SemVer("1.0.0"); 34 * SemVer("1.0.0+4444"); 35 * SemVer("1.0.0-eyyyyup"); 36 * SemVer("1.0.0-yay+build"); 37 * --- 38 */ 39 struct SemVer { 40 size_t Major, Minor, Patch; 41 string PreRelease, Build; 42 43 /** 44 * Constructor 45 * Define BrightProof_ForceSTD version for force using standard library functions. 46 * Standard library functions is slower, but more safe to use. 47 * Usualy you don't have to use it. 48 * Params: 49 * i = input string 50 * Throws: SemVerException if there is any syntax errors. 51 */ 52 pure this(string i) { 53 import std.string : indexOf; 54 import std.conv : to; 55 56 auto MajorDot = indexOf(i, '.', 0); 57 auto MinorDot = indexOf(i, '.', MajorDot + 1); 58 auto PreReleaseStart = indexOf(i, '-', MinorDot + 1); 59 auto BuildStart = indexOf(i, '+', PreReleaseStart + 1); 60 61 if((MajorDot == -1) || (MinorDot == -1)) { 62 // If there is no 2 dots - this is not complete semver. 63 throw new SemVerException("There is no major, minor or patch"); 64 } else if(MajorDot < 1) { 65 // If first symbol is a dot, there is no Major. 66 throw new SemVerException("There is no major version number"); 67 } else if((MinorDot < 1) || (MinorDot - MajorDot < 2)) { 68 // If there is nothing between MajorDot and MinorDot. 69 throw new SemVerException("There is no minor version number"); 70 } else if( 71 ((PreReleaseStart < 1) && (i.length - MinorDot < 2)) || 72 ((PreReleaseStart >= 0) && (PreReleaseStart - MinorDot < 2))) { 73 // There is no Patch, if there is nothing after MinorDot. 74 // and string end or `-`. 75 throw new SemVerException("There is no patch version number"); 76 } else if( 77 ((BuildStart < 1) && (i.length - PreReleaseStart < 2)) || 78 ((BuildStart >= 0) && (BuildStart - PreReleaseStart < 2))) { 79 // There is nothing in PreRelease, if nothing follow `-` . 80 throw new SemVerException("There is no prerelease version string"); 81 } else if(i.length - BuildStart < 2) { 82 // There is no in Build, if string ends after `+`. 83 throw new SemVerException("There is no build version string"); 84 } 85 86 // Now we know where Major, Minor, Patch, PreRelease, Build starts and ends. 87 try { 88 Major = to!size_t(i[0..MajorDot]); 89 } catch { 90 throw new SemVerException("There is a non-number characters in major"); 91 } 92 93 try { 94 Minor = to!size_t(i[MajorDot+1..MinorDot]); 95 } catch { 96 throw new SemVerException("There is a non-number characters in minor"); 97 } 98 99 if(PreReleaseStart != -1) { 100 try { 101 Patch = to!size_t(i[MinorDot+1..PreReleaseStart]); 102 } catch { 103 throw new SemVerException("There is a non-number in patch"); 104 } 105 if(BuildStart != -1) { 106 PreRelease = i[PreReleaseStart+1..BuildStart]; 107 Build = i[BuildStart+1..$]; 108 } else { 109 PreRelease = i[PreReleaseStart+1..$]; 110 } 111 } else { 112 if(BuildStart != -1) { 113 try { 114 Patch = to!size_t(i[MinorDot+1..BuildStart]); 115 } catch { 116 throw new SemVerException("There is a non-number in patch"); 117 } 118 Build = i[BuildStart+1..$]; 119 } else { 120 try { 121 Patch = to!size_t(i[MinorDot+1..$]); 122 } catch { 123 throw new SemVerException("There is a non-number in patch"); 124 } 125 } 126 } 127 } 128 129 /** 130 * Next Major/Minor/Patch version 131 * Increments numbers with semver rules. 132 * Example: 133 * 1.2.3 -> nextMajor -> 2.0.0 134 * 1.2.3 -> nextMinor -> 1.3.0 135 * 1.2.3 -> nextPatch -> 1.2.4 136 * 1.2.3-rc.1+build.5 -> nextPatch -> 1.2.4 137 */ 138 @safe @nogc pure nothrow void nextMajor() { 139 Major++; 140 Minor = Patch = 0; 141 PreRelease = Build = ""; 142 } 143 /// ditto 144 @safe @nogc pure nothrow void nextMinor() { 145 Minor++; 146 Patch = 0; 147 PreRelease = Build = ""; 148 } 149 /// ditto 150 @safe @nogc pure nothrow void nextPatch() { 151 Patch++; 152 PreRelease = Build = ""; 153 } 154 155 /** 156 * Convert SemVer to string 157 * Returns: SemVer in string (MAJOR.MINOR.PATCH-PRERELEASE+BUILD) 158 */ 159 @safe pure string toString() { 160 import std.array : appender; 161 import std.format : formattedWrite; 162 163 auto writer = appender!string(); 164 formattedWrite(writer, "%d.%d.%d", Major, Minor, Patch); 165 if(PreRelease != "") 166 formattedWrite(writer, "-%s", PreRelease); 167 if(Build != "") 168 formattedWrite(writer, "+%s", Build); 169 return writer.data; 170 } 171 172 /** 173 * true, if this == b 174 */ 175 @safe @nogc pure nothrow const bool opEquals()(auto ref const SemVer b) { 176 return (this.Major == b.Major) && 177 (this.Minor == b.Minor) && 178 (this.Patch == b.Patch) && 179 (this.PreRelease == b.PreRelease); 180 } 181 182 /** 183 * Compares two SemVer structs. 184 */ 185 @safe const int opCmp(ref const SemVer b) { 186 import natcmp; 187 188 if(this == b) 189 return 0; 190 191 if(this.Major != b.Major) 192 return this.Major < b.Major ? -1 : 1; 193 else if(this.Minor != b.Minor) 194 return this.Minor < b.Minor ? -1 : 1; 195 else if(this.Major != b.Major) 196 return this.Major < b.Major ? -1 : 1; 197 198 if((this.PreRelease != "") && (b.PreRelease != "")) { 199 int result = compareNatural(this.PreRelease, b.PreRelease); 200 if(result) { 201 return result; 202 } 203 } else if(this.PreRelease != "") { 204 return -1; 205 } else if(b.PreRelease != "") { 206 return 1; 207 } 208 209 throw new SemVerException("I don't know, how you got that error: SemVer is not an equal, but also not an different"); 210 } 211 /// ditto 212 @safe const int opCmp(in SemVer b) { 213 return this.opCmp(b); 214 } 215 /// 216 unittest { 217 assert(SemVer("1.0.0-alpha") < SemVer("1.0.0-alpha.1")); 218 assert(SemVer("1.0.0-alpha.1") < SemVer("1.0.0-alpha.beta")); 219 assert(SemVer("1.0.0-alpha.beta") < SemVer("1.0.0-beta")); 220 assert(SemVer("1.0.0-beta") < SemVer("1.0.0-beta.2")); 221 assert(SemVer("1.0.0-beta.2") < SemVer("1.0.0-beta.11")); 222 assert(SemVer("1.0.0-beta.11") < SemVer("1.0.0-rc.1")); 223 assert(SemVer("1.0.0-rc.1") < SemVer("1.0.0")); 224 assert(SemVer("1.0.0-rc.1") == SemVer("1.0.0+build.9")); 225 assert(SemVer("1.0.0-rc.1") == SemVer("1.0.0-rc.1+build.5")); 226 assert(SemVer("1.0.0-rc.1+build.5") == SemVer("1.0.0-rc.1+build.5")); 227 } 228 }