OMCompiler/Compiler/Util/SemanticVersion.mo
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of OpenModelica. | ||
| 3 | * | ||
| 4 | * Copyright (c) 1998-2026, Open Source Modelica Consortium (OSMC), | ||
| 5 | * c/o Linköpings universitet, Department of Computer and Information Science, | ||
| 6 | * SE-58183 Linköping, Sweden. | ||
| 7 | * | ||
| 8 | * All rights reserved. | ||
| 9 | * | ||
| 10 | * THIS PROGRAM IS PROVIDED UNDER THE TERMS OF AGPL VERSION 3 LICENSE OR | ||
| 11 | * THIS OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.8. | ||
| 12 | * ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES | ||
| 13 | * RECIPIENT'S ACCEPTANCE OF THE OSMC PUBLIC LICENSE OR THE GNU AGPL | ||
| 14 | * VERSION 3, ACCORDING TO RECIPIENTS CHOICE. | ||
| 15 | * | ||
| 16 | * The OpenModelica software and the OSMC (Open Source Modelica Consortium) | ||
| 17 | * Public License (OSMC-PL) are obtained from OSMC, either from the above | ||
| 18 | * address, from the URLs: | ||
| 19 | * http://www.openmodelica.org or | ||
| 20 | * https://github.com/OpenModelica/ or | ||
| 21 | * http://www.ida.liu.se/projects/OpenModelica, | ||
| 22 | * and in the OpenModelica distribution. | ||
| 23 | * | ||
| 24 | * GNU AGPL version 3 is obtained from: | ||
| 25 | * https://www.gnu.org/licenses/licenses.html#GPL | ||
| 26 | * | ||
| 27 | * This program is distributed WITHOUT ANY WARRANTY; without | ||
| 28 | * even the implied warranty of MERCHANTABILITY or FITNESS | ||
| 29 | * FOR A PARTICULAR PURPOSE, EXCEPT AS EXPRESSLY SET FORTH | ||
| 30 | * IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE CONDITIONS OF OSMC-PL. | ||
| 31 | * | ||
| 32 | * See the full OSMC Public License conditions for more details. | ||
| 33 | * | ||
| 34 | */ | ||
| 35 | |||
| 36 | encapsulated package SemanticVersion | ||
| 37 | |||
| 38 | protected | ||
| 39 | |||
| 40 | import StringUtil; | ||
| 41 | import System; | ||
| 42 | import Util; | ||
| 43 | |||
| 44 | public | ||
| 45 | |||
| 46 | uniontype Version | ||
| 47 | record SEMVER | ||
| 48 | "Semantic version number MAJOR.MINOR.PATCH, see https://semver.org/." | ||
| 49 | Integer major, minor, patch; | ||
| 50 | list<String> prerelease, meta; | ||
| 51 | end SEMVER; | ||
| 52 | record NONSEMVER | ||
| 53 | "Non-semantic version number" | ||
| 54 | String version; | ||
| 55 | end NONSEMVER; | ||
| 56 | end Version; | ||
| 57 | |||
| 58 | function parse | ||
| 59 | "Parse version string into SemanticVersion.Version." | ||
| 60 | input String s; | ||
| 61 | input Boolean nonsemverAsZeroZeroZero = false; | ||
| 62 | output Version v; | ||
| 63 | protected | ||
| 64 | Integer n; | ||
| 65 | String major, minor, patch, versions; | ||
| 66 | list<String> prereleaseLst, metaLst, matches, split, versionsLst; | ||
| 67 | constant String semverRegex = "^([0-9][0-9]*\\.?[0-9]*\\.?[0-9]*)([+-][0-9A-Za-z.-]*)?$"; | ||
| 68 | algorithm | ||
| 69 | 205 | (n, matches) := System.regex(s, semverRegex, maxMatches=5, extended=true); | |
| 70 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 204 times.
|
205 | if n < 2 then |
| 71 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | if stringLength(s) == 0 then |
| 72 | v := NONSEMVER(""); | ||
| 73 | 1 | return; | |
| 74 | end if; | ||
| 75 | ✗ | if nonsemverAsZeroZeroZero then | |
| 76 | ✗ | (prereleaseLst, metaLst) := splitPrereleaseAndMeta(s); | |
| 77 | ✗ | v := SEMVER(0,0,0,prereleaseLst,metaLst); | |
| 78 | else | ||
| 79 | ✗ | v := NONSEMVER(s); | |
| 80 | end if; | ||
| 81 | ✗ | return; | |
| 82 | end if; | ||
| 83 | // OSX regex cannot handle everything in the same regex, so we have manual splitting of prerelease and meta strings | ||
| 84 | |||
| 85 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 204 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 204 times.
|
204 | _::versions::split := matches; |
| 86 | 204 | versionsLst := Util.stringSplitAtChar(versions, "."); | |
| 87 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 204 times.
|
204 | major::versionsLst := versionsLst; |
| 88 |
1/2✓ Branch 0 taken 204 times.
✗ Branch 1 not taken.
|
204 | if not listEmpty(versionsLst) then |
| 89 | 204 | minor::versionsLst := versionsLst; | |
| 90 | else | ||
| 91 | minor := "0"; | ||
| 92 | end if; | ||
| 93 |
2/2✓ Branch 0 taken 194 times.
✓ Branch 1 taken 10 times.
|
204 | if not listEmpty(versionsLst) then |
| 94 | 194 | patch::versionsLst := versionsLst; | |
| 95 | else | ||
| 96 | patch := "0"; | ||
| 97 | end if; | ||
| 98 | |||
| 99 |
1/2✓ Branch 0 taken 204 times.
✗ Branch 1 not taken.
|
204 | (prereleaseLst, metaLst) := splitPrereleaseAndMeta(if listEmpty(split) then "" else listGet(split, 1)); |
| 100 | 204 | v := SEMVER(stringInt(major),stringInt(minor),stringInt(patch),prereleaseLst,metaLst); | |
| 101 | end parse; | ||
| 102 | |||
| 103 | function compare | ||
| 104 | "Compare two versions v1 and v2. | ||
| 105 | If v1 and v2 both non-semver or both semver: | ||
| 106 | Return -1 if the first is smallest, | ||
| 107 | 1 if the second is smallest, | ||
| 108 | or 0 if they are equal. | ||
| 109 | If v1 non-semver and v2 semver: return -1. | ||
| 110 | If v1 semver and v2 non-semver: return 1." | ||
| 111 | input Version v1, v2; | ||
| 112 | input Boolean comparePrerelease = true; | ||
| 113 | input Boolean compareBuildInformation = false; | ||
| 114 | output Integer c; | ||
| 115 | algorithm | ||
| 116 | c := match (v1, v2) | ||
| 117 | ✗ | case (NONSEMVER(),NONSEMVER()) then stringCompare(v1.version, v2.version); | |
| 118 | case (NONSEMVER(),_) then -1; | ||
| 119 | case (_,NONSEMVER()) then 1; | ||
| 120 | case (SEMVER(),SEMVER()) | ||
| 121 | algorithm | ||
| 122 |
2/12✗ Branch 0 not taken.
✓ Branch 1 taken 143 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 143 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
143 | if (v1.major==0 and v1.minor==0 and v1.patch==0) or (v2.major==0 and v2.minor==0 and v2.patch==0) then |
| 123 | c := 0; | ||
| 124 | else | ||
| 125 | 143 | c := Util.intCompare(v1.major, v2.major); | |
| 126 |
2/2✓ Branch 0 taken 82 times.
✓ Branch 1 taken 61 times.
|
143 | if c <> 0 then |
| 127 | 82 | return; | |
| 128 | end if; | ||
| 129 | 61 | c := Util.intCompare(v1.minor, v2.minor); | |
| 130 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 61 times.
|
61 | if c <> 0 then |
| 131 | ✗ | return; | |
| 132 | end if; | ||
| 133 | 61 | c := Util.intCompare(v1.patch, v2.patch); | |
| 134 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 59 times.
|
61 | if c <> 0 then |
| 135 | 2 | return; | |
| 136 | end if; | ||
| 137 | end if; | ||
| 138 | |||
| 139 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 58 times.
|
59 | if comparePrerelease then |
| 140 | 1 | c := compareIdentifierList(v1.prerelease, v2.prerelease); | |
| 141 | end if; | ||
| 142 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
|
59 | if c == 0 and compareBuildInformation then |
| 143 | ✗ | c := compareIdentifierList(v1.meta, v2.meta); | |
| 144 | end if; | ||
| 145 | then c; | ||
| 146 | end match; | ||
| 147 | end compare; | ||
| 148 | |||
| 149 | function toString | ||
| 150 | input Version v; | ||
| 151 | output String out; | ||
| 152 | algorithm | ||
| 153 | out := match v | ||
| 154 | case SEMVER() | ||
| 155 | algorithm | ||
| 156 | 1 | out := String(v.major) + "." + String(v.minor) + "." + String(v.patch); | |
| 157 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1 | if not listEmpty(v.prerelease) then |
| 158 | ✗ | out := out + "-" + stringDelimitList(v.prerelease, "."); | |
| 159 | end if; | ||
| 160 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1 | if not listEmpty(v.meta) then |
| 161 | ✗ | out := out + "+" + stringDelimitList(v.meta, "."); | |
| 162 | end if; | ||
| 163 | then out; | ||
| 164 | ✗ | case NONSEMVER() then v.version; | |
| 165 | end match; | ||
| 166 | end toString; | ||
| 167 | |||
| 168 | function isPrerelease | ||
| 169 | "Return true if semver version has pre-release information." | ||
| 170 | input Version v; | ||
| 171 | output Boolean b; | ||
| 172 | algorithm | ||
| 173 | b := match v | ||
| 174 | case SEMVER(prerelease=_::_) then true; | ||
| 175 | else false; | ||
| 176 | end match; | ||
| 177 | end isPrerelease; | ||
| 178 | |||
| 179 | function hasMetaInformation | ||
| 180 | "Return true if semver version has meta information." | ||
| 181 | input Version v; | ||
| 182 | output Boolean b; | ||
| 183 | algorithm | ||
| 184 | b := match v | ||
| 185 | case SEMVER(meta={}) then false; | ||
| 186 | case NONSEMVER() then false; | ||
| 187 | else true; | ||
| 188 | end match; | ||
| 189 | end hasMetaInformation; | ||
| 190 | |||
| 191 | function isSemVer | ||
| 192 | "Return true if version is of semantic versioning type." | ||
| 193 | input Version v; | ||
| 194 | output Boolean b; | ||
| 195 | algorithm | ||
| 196 | b := match v | ||
| 197 | case SEMVER() then true; | ||
| 198 | else false; | ||
| 199 | end match; | ||
| 200 | end isSemVer; | ||
| 201 | |||
| 202 | protected | ||
| 203 | |||
| 204 | function splitPrereleaseAndMeta | ||
| 205 | input String s; | ||
| 206 | output list<String> prereleaseLst; | ||
| 207 | output list<String> metaLst; | ||
| 208 | protected | ||
| 209 | String meta, prerelease; | ||
| 210 | list<String> split; | ||
| 211 | algorithm | ||
| 212 | prereleaseLst := {}; | ||
| 213 | metaLst := {}; | ||
| 214 | |||
| 215 |
1/2✓ Branch 0 taken 204 times.
✗ Branch 1 not taken.
|
204 | if stringEmpty(s) then |
| 216 | 204 | return; | |
| 217 | end if; | ||
| 218 | |||
| 219 | ✗ | if stringGetStringChar(s, 1) == "+" then | |
| 220 | ✗ | metaLst := if stringLength(s) > 1 then Util.stringSplitAtChar(StringUtil.rest(s), ".") else {}; | |
| 221 | ✗ | return; | |
| 222 | end if; | ||
| 223 | |||
| 224 | ✗ | split := Util.stringSplitAtChar(s, "+"); | |
| 225 | ✗ | prerelease::split := split; | |
| 226 | ✗ | meta := if listEmpty(split) then "" else listGet(split, 1); | |
| 227 | ✗ | if stringGetStringChar(prerelease, 1) == "-" then | |
| 228 | ✗ | prerelease := StringUtil.rest(prerelease); | |
| 229 | end if; | ||
| 230 | ✗ | prereleaseLst := if stringLength(prerelease) > 0 then Util.stringSplitAtChar(prerelease, ".") else {}; | |
| 231 | ✗ | metaLst := if stringLength(meta) > 0 then Util.stringSplitAtChar(meta, ".") else {}; | |
| 232 | end splitPrereleaseAndMeta; | ||
| 233 | |||
| 234 | function compareIdentifierList | ||
| 235 | input list<String> w1, w2; | ||
| 236 | output Integer c; | ||
| 237 | protected | ||
| 238 | list<String> l1, l2; | ||
| 239 | String s1, s2; | ||
| 240 | algorithm | ||
| 241 | l1 := w1; | ||
| 242 | l2 := w2; | ||
| 243 | if listEmpty(l1) and not listEmpty(l2) then | ||
| 244 | c := 1; | ||
| 245 | end if; | ||
| 246 | if listEmpty(l2) and not listEmpty(l1) then | ||
| 247 | c := -1; | ||
| 248 | end if; | ||
| 249 |
2/4✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
|
1 | while not (listEmpty(l1) and listEmpty(l2)) loop |
| 250 | (c, l1, l2) := match (l1, l2) | ||
| 251 | case ({},_::_) then (-1, l1, l2); | ||
| 252 | case (_::_,{}) then (1, l1, l2); | ||
| 253 | ✗ | case (s1::l1, s2::l2) then (compareIdentifier(s1, s2), l1, l2); | |
| 254 | end match; | ||
| 255 | ✗ | if c <> 0 then | |
| 256 | ✗ | return; | |
| 257 | end if; | ||
| 258 | end while; | ||
| 259 | c := 0; | ||
| 260 | end compareIdentifierList; | ||
| 261 | |||
| 262 | function compareIdentifier | ||
| 263 | input String s1, s2; | ||
| 264 | output Integer c; | ||
| 265 | algorithm | ||
| 266 | ✗ | if Util.isIntegerString(s1) then | |
| 267 | ✗ | c := if Util.isIntegerString(s2) then Util.intCompare(stringInt(s1), stringInt(s2)) else -1; | |
| 268 | ✗ | return; | |
| 269 | end if; | ||
| 270 | ✗ | if Util.isIntegerString(s2) then | |
| 271 | c := 1; | ||
| 272 | end if; | ||
| 273 | ✗ | c := stringCompare(s1, s2); | |
| 274 | end compareIdentifier; | ||
| 275 | |||
| 276 | annotation(__OpenModelica_Interface="util"); | ||
| 277 | end SemanticVersion; | ||
| 278 |