💾 Archived View for gmi.noulin.net › gitRepositories › version › file › version.c.gmi captured on 2023-01-29 at 11:26:10. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
version.c (47289B)
1 2 3 /* Libsheepy documentation: http://spartatek.se/libsheepy/ */ 4 5 #include "libsheepyObject.h" 6 #include "version.h" 7 #include "versionInternal.h" 8 #include "shpPackages/short/short.h" 9 10 #include <stdlib.h> 11 #include <string.h> 12 #include <stdio.h> 13 14 void initiateVersion(versiont *self); 15 void registerMethodsVersion(versionFunctionst *f); 16 void initiateAllocateVersion(versiont **self); 17 void finalizeVersion(void); 18 versiont* allocVersion(const char *v); 19 local void freeVersion(versiont *self); 20 local void terminateVersion(versiont **self); 21 local char* toStringVersion(versiont *self); 22 local versiont* duplicateVersion(versiont *self); 23 local void smashVersion(versiont **self); 24 local void finishVersion(versiont **self); 25 local const char* helpVersion(versiont *self); 26 local void logVersion(versiont *self); 27 local bool validVersion(versiont *self, const char *v); 28 local bool parseVersion(versiont *self, const char *v); 29 local bool parseStrictVersion(versiont *self, const char *v); 30 local bool cleanVersion(versiont *self, char *v); 31 local bool equalVersion(versiont *self, versiont *ver); 32 local bool equalSVersion(versiont *self, const char *v); 33 local bool equalSSVersion(versiont *self, const char *v1, const char *v2); 34 local int cmpVersion(versiont *self, versiont *ver); 35 local int cmpSVersion(versiont *self, const char *v); 36 local int cmpSSVersion(versiont *self, const char *v1, const char *v2); 37 local bool incrVersion(versiont *self, u32 level); 38 local smallArrayt* sortVersion(versiont *self, smallArrayt *vlist); 39 local bool satisfiesVersion(versiont *self, const char *vrange); 40 local smallJsont* toJsonVersion(versiont *self); 41 local char* toJsonStrVersion(versiont *self); 42 local bool fromJsonVersion(versiont *self, smallJsont *json); 43 local bool fromJsonStrVersion(versiont *self, const char *json); 44 45 /* enable/disable logging */ 46 #undef pLog 47 #define pLog(...) 48 49 // all valid characters in version strings 50 #define DELIMITER "." 51 #define PR_DELIMITER "-" 52 #define BD_DELIMITER "+" 53 #define NUMBERS "0123456789" 54 #define ALPHA "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 55 #define DELIMITERS DELIMITER PR_DELIMITER BD_DELIMITER 56 #define ANYTOKENS "xX*" 57 #define VALID_CHARS NUMBERS ALPHA DELIMITERS ANYTOKENS 58 59 // bitset used to strip invalid characters 60 staticBitsetT(validt, u64 , 256); 61 // bug in gcc 4.9, ignore: warning: missing braces around initializer [-Wmissing-braces] 62 local validt validChars = {0}; 63 64 // parser states 65 // STATE[START] to convert state value to string 66 enum {START, REL, MAJOR, MINOR, PATCH, OTHERS, PRE, BUILD, CSTRING, NUMBER, ANY}; 67 local const char *STATE[] UNUSED = {"START", "REL", "MAJOR", "MINOR", "PATCH", "OTHERS", "PRE", "BUILD", "STRING", "NUMBER", "ANY xX*"}; 68 69 /** 70 * true when c is a valid character 71 */ 72 local inline bool isValidChar(char c) { 73 ret staticBitsetGet(&validChars, c); 74 } 75 76 void initiateVersion(versiont *self) { 77 78 self->type = "version"; 79 if (!versionF) { 80 versionF = malloc(sizeof(versionFunctionst)); 81 registerMethodsVersion(versionF); 82 pErrorNot0(atexit(finalizeVersion)); 83 84 // initialize list of valid characters 85 range(i,strlen(VALID_CHARS)) { 86 staticBitset1(&validChars, VALID_CHARS[i]); 87 } 88 } 89 self->f = versionF; 90 91 #define setToZero\ 92 self->originalVer = NULL;\ 93 self->empty = true;\ 94 self->mainComponentCount = 0;\ 95 self->release = 0;\ 96 self->major = 0;\ 97 self->minor = 0;\ 98 self->patch = 0 99 setToZero; 100 initiateG(&self->others); 101 initiateG(&self->prerelease); 102 initiateG(&self->build); 103 104 } 105 106 void registerMethodsVersion(versionFunctionst *f) { 107 108 f->free = freeVersion; 109 f->terminate = terminateVersion; 110 f->toString = toStringVersion; 111 f->duplicate = duplicateVersion; 112 f->smash = smashVersion; 113 f->finish = finishVersion; 114 f->help = helpVersion; 115 f->log = logVersion; 116 f->valid = validVersion; 117 f->parse = parseVersion; 118 f->parseStrict = parseStrictVersion; 119 f->clean = cleanVersion; 120 f->equal = equalVersion; 121 f->equalS = equalSVersion; 122 f->equalSS = equalSSVersion; 123 f->cmp = cmpVersion; 124 f->cmpS = cmpSVersion; 125 f->cmpSS = cmpSSVersion; 126 f->incr = incrVersion; 127 f->sort = sortVersion; 128 f->satisfies = satisfiesVersion; 129 f->toJson = toJsonVersion; 130 f->toJsonStr = toJsonStrVersion; 131 f->fromJson = fromJsonVersion; 132 f->fromJsonStr = fromJsonStrVersion; 133 } 134 135 void initiateAllocateVersion(versiont **self) { 136 137 if (self) { 138 (*self) = malloc(sizeof(versiont)); 139 if (*self) { 140 initiateVersion(*self); 141 } 142 } 143 } 144 145 void finalizeVersion(void) { 146 147 if (versionF) { 148 free(versionF); 149 versionF = NULL; 150 } 151 } 152 153 versiont* allocVersion(const char *v) { 154 versiont *r = NULL; 155 156 initiateAllocateVersion(&r); 157 parseVersion(r, v); 158 ret r; 159 } 160 161 162 local void freeVersion(versiont *self) { 163 164 free(self->originalVer); 165 setToZero; 166 freeG(&self->others); 167 freeG(&self->prerelease); 168 freeG(&self->build); 169 } 170 171 local void terminateVersion(versiont **self) { 172 173 freeVersion(*self); 174 finishVersion(self); 175 } 176 177 178 local char* toStringVersion(versiont *self) { 179 180 char *r = NULL; 181 r = formatS("%"PRIu64".%"PRIu64".%"PRIu64, self->release, self->major, self->minor); 182 if (self->patch) { 183 pushNFreeG(&r, formatS(".%"PRIu64, self->patch)); 184 } 185 if (!isEmptyG(&self->others)) { 186 var o = joinSG(&self->others, '.'); 187 pushNFreeG(&r, formatS(".%s", o)); 188 free(o); 189 } 190 if (!isEmptyG(&self->prerelease)) { 191 #define labelString(label, delim) \ 192 iter(&self->label, c) {\ 193 if (isOSmallIntG(c) and iterIndexG(&self->label)) {\ 194 /* the left delimiter for numbers is . (DELIMITER) */\ 195 pushG(&r, DELIMITER);\ 196 }\ 197 else {\ 198 pushG(&r, delim);\ 199 }\ 200 var cs = toStringO(c);\ 201 pushNFreeG(&r, cs);\ 202 } 203 labelString(prerelease, PR_DELIMITER); 204 } 205 if (!isEmptyG(&self->build)) { 206 labelString(build, BD_DELIMITER); 207 } 208 ret r; 209 } 210 211 local versiont* duplicateVersion(versiont *self) { 212 213 createAllocateVersion(dup); 214 dup->originalVer = dupG(self->originalVer); 215 dup->empty = self->empty; 216 dup->mainComponentCount = self->mainComponentCount; 217 dup->release = self->release; 218 dup->major = self->major; 219 dup->minor = self->minor; 220 dup->patch = self->patch; 221 222 smallArrayt *dp; 223 #define dupComponents(label)\ 224 dp = dupG(&self->label);\ 225 setsoG(&dup->label, getsoG(dp));\ 226 finishG(dp) 227 dupComponents(others); 228 dupComponents(prerelease); 229 dupComponents(build); 230 ret dup; 231 } 232 233 local void smashVersion(versiont **self) { 234 235 finishVersion(self); 236 } 237 238 #if NFreeStackCheck 239 local void finishVersion(versiont **self) { 240 241 register u64 rsp asm("rsp"); 242 if ((u64)*self > rsp) { 243 logW("Probably trying to free a smallArray on stack: "BLD"%p"RST" sp: "BLD"%p"RST, *self, rsp); 244 logBtrace; 245 } 246 else { 247 free(*self); 248 *self = NULL; 249 } 250 } 251 #else 252 // #if NFreeStackCheck 253 local void finishVersion(versiont **self) { 254 255 free(*self); 256 *self = NULL; 257 } 258 #endif 259 // #if NFreeStackCheck 260 261 local const char* helpVersion(versiont UNUSED *self) { 262 ret "TODO - version help"; 263 } 264 265 266 267 268 local void logVersion(versiont *self) { 269 var s = toStringVersion(self); 270 puts(s); 271 free(s); 272 } 273 274 275 /** 276 * internal function parsing a version string 277 * 278 * version string format is Release.Major.minor.patch.others-prerelease+build 279 * 280 * when validate is set, self is not updated 281 * when loose is set, incomplete version strings are accepted 282 * set isCmp to true when parsing version in comparators 283 */ 284 local bool parse(versiont *self UNUSED, const char *ver, bool validate, bool loose, bool isCmp) { 285 if (isBlankG(ver)) ret false; 286 287 // the code for the parser is in !validate blocks 288 // the code interpreting an invalid version string as much as possible is in loose blocks 289 // the code for version in compares is isCmp blocks (used in satisfiesVersion) 290 291 // remove spaces and duplicate the string 292 char *v = trimS(ver); 293 294 // main state 295 u8 state = START; 296 // state for parsing others, prerelease and build 297 u8 state2 = START; 298 299 // for detecting state transition and empty components 300 u8 prevState; 301 302 // leading 0 are not accepted for any version number 303 bool leading0 = false; 304 // allow prerelease and build anywhere (0-pre) when true 305 bool release0 = loose; 306 307 // curs is cursor in version string 308 // component is the start of a component string 309 char *curs, *component; 310 /* lv(isCmp); */ 311 range(i, strlen(v)) { 312 curs = &v[i]; 313 /* lv(i); */ 314 /* lv(STATE[state]); */ 315 /* lv(STATE[state2]); */ 316 /* lv(leading0); */ 317 /* putchar(v[i]); */ 318 /* put */ 319 320 // fail when a character is invalid 321 if (!isValidChar(v[i])) goto fail; 322 switch(state) { 323 case START: 324 // find the start of the version string 325 if (!isdigit(v[i]) and v[i] != 'x' and v[i] != 'X' and v[i] != '*') { 326 // ignore characters in front of release version when loose 327 if (loose) continue; else goto fail; 328 } 329 state = REL; 330 prevState = state; 331 leading0 = v[i] == '0' ? true : false; 332 // dont set self when validating 333 if (!validate) component = curs; 334 break; 335 case REL: 336 if (isCmp and (v[i-1] == 'x' or v[i-1] == 'X' or v[i-1] == '*')) { 337 // set component to -1 when an any character is detected in a comparator 338 #define validStrictNumIsCmp(comp, delimIndex, nstate)\ 339 self->comp = -1;\ 340 prevState = state;\ 341 /* must be x. or x- or x+ or x */\ 342 /* when delimiter is at the end of the string, parsing is finished */\ 343 if (!v[i+delimIndex] /* avoid reading outside the buffer */ or (hasG(DELIMITERS, v[i+delimIndex]) and !v[i+delimIndex+1])) goto parsingFinish;\ 344 if (!delimIndex) switch(v[i+delimIndex]) {\ 345 case '.':\ 346 state = nstate;\ 347 break;\ 348 case '-':\ 349 state = PRE;\ 350 break;\ 351 case '+':\ 352 state = BUILD;\ 353 break;\ 354 default:\ 355 goto fail;\ 356 }\ 357 break; 358 validStrictNumIsCmp(release, 0/*delimIndex*/, MAJOR); 359 } 360 // any characters are not accepted in normal version strings 361 if (!isCmp and (v[i-1] == 'x' or v[i-1] == 'X' or v[i-1] == '*')) goto fail; 362 #define parseNumber(comp) \ 363 /* state switch, point to next component */\ 364 if (!validate) {\ 365 char c = *curs;\ 366 *curs = 0;\ 367 if (!isCmp or !hasG(ANYTOKENS, v[i-1])) self->comp = parseI64(component);\ 368 *curs = c;\ 369 component = curs+1;\ 370 self->mainComponentCount++;\ 371 } 372 #define validStrictNum(comp, nextState, transCond, code, leadingCond)\ 373 if (isCmp) {\ 374 if (hasG(ANYTOKENS, v[i])) {\ 375 /* multiple separator following each other is not accepted */\ 376 if (v[i-1] != '.') goto fail;\ 377 if (loose) {\ 378 /* set component to -1 when an any character is detected in a comparator */\ 379 validStrictNumIsCmp(comp, 1 /*delimIndex*/, nextState)\ 380 }\ 381 }\ 382 }\ 383 /* any characters are not accepted in normal version strings */\ 384 elif (!isCmp and (v[i] == 'x' or v[i] == 'X' or v[i] == '*')) goto fail;\ 385 u8 UNIQVAR(nextSte) = nextState;\ 386 if (!isdigit(v[i])) {\ 387 /* end of component detected */\ 388 /* no empty components */\ 389 if (i and hasG(DELIMITERS, v[i-1])) goto fail;\ 390 if (i == 1 and leading0) {\ 391 /* prerelease 0 allow prerelease and build components anywhere*/\ 392 if (v[i] == PR_DELIMITER[0]) {\ 393 release0 = true;\ 394 state = PRE;\ 395 parseNumber(comp);\ 396 break;\ 397 }\ 398 elif (v[i] == BD_DELIMITER[0]) {\ 399 release0 = true;\ 400 state = BUILD;\ 401 parseNumber(comp);\ 402 break;\ 403 }\ 404 }\ 405 if (release0) {\ 406 /* prerelease 0 allow prerelease and build components anywhere*/\ 407 if (v[i] == PR_DELIMITER[0]) {\ 408 state = PRE;\ 409 parseNumber(comp);\ 410 break;\ 411 }\ 412 elif (v[i] == BD_DELIMITER[0]) {\ 413 state = BUILD;\ 414 parseNumber(comp);\ 415 break;\ 416 }\ 417 }\ 418 /* end of release version or invalid, must have one digit */\ 419 if ((prevState != state) or (v[i] and (v[i] != DELIMITER[0] transCond))) goto fail;\ 420 code;\ 421 /* parse component and go to next state */\ 422 prevState = state;\ 423 state = UNIQVAR(nextSte);\ 424 leading0 = v[i+1] == '0' ? true : false;\ 425 /* parse numbers only, not any tokens "xX*" */\ 426 parseNumber(comp);\ 427 break;\ 428 }\ 429 /* search end of component */\ 430 /* no leading 0 */\ 431 if (leading0 and v[i-1] != DELIMITER[0] leadingCond) goto fail;\ 432 /* not in state transition anymore */\ 433 prevState = state; 434 // set release0 to true when loose to allow incomplete versions, the missing numbers are set to 0 435 validStrictNum(release, MAJOR,/*transCond*/,if (loose) release0 = true; else release0 = leading0 ? true : false,/*leadingCond*/); 436 break; 437 case MAJOR: 438 validStrictNum(major, MINOR,/*transCond*/,/*code*/,/*leadingCond*/); 439 break; 440 case MINOR: 441 validStrictNum(minor, PATCH, and v[i] != PR_DELIMITER[0] and v[i] != BD_DELIMITER[0] /*transCond*/, if (v[i] == PR_DELIMITER[0]) UNIQVAR(nextSte) = PRE; /*code*/, and v[i-1] != PR_DELIMITER[0] and v[i-1] != BD_DELIMITER[0] /*leadingCond*/); 442 break; 443 case PATCH: 444 // check if prerelease only when the state was changed 445 if (prevState != state and v[i-1] == PR_DELIMITER[0]) { 446 prevState = state; 447 state = PRE; 448 if (!validate) component = curs; 449 i--; 450 break; 451 } 452 validStrictNum(patch, OTHERS, and v[i] != PR_DELIMITER[0] and v[i] != BD_DELIMITER[0] /*transCond*/, if (v[i] == PR_DELIMITER[0]) UNIQVAR(nextSte) = PRE; /*code*/, and v[i-1] != PR_DELIMITER[0] and v[i-1] != BD_DELIMITER[0] /*leadingCond*/); 453 break; 454 case OTHERS: 455 // no empty components 456 if (hasG(DELIMITERS, v[i-1]) and hasG(DELIMITERS, v[i])) goto fail; 457 #define switchLabel(delim, nextState, label) \ 458 if (v[i] == delim) {\ 459 prevState = state;\ 460 state = nextState;\ 461 if (!validate) {\ 462 /* parse number or string or any character */\ 463 char c = *curs;\ 464 *curs = 0;\ 465 if (state2 == NUMBER) {\ 466 pushG(&self->label, parseI64(component));\ 467 }\ 468 elif (state2 == CSTRING) {\ 469 pushG(&self->label, component);\ 470 }\ 471 elif (state2 == ANY) {\ 472 pushG(&self->label, -1);\ 473 }\ 474 *curs = c;\ 475 state2 = START;\ 476 component = curs+1;\ 477 }\ 478 break;\ 479 } 480 #define validStrictLabel(label)\ 481 /* after state transition, select prerelease state, stored in state2 */\ 482 if (hasG(DELIMITERS, v[i-1])) {\ 483 if (!validate and prevState == state) {\ 484 char c = *(curs-1);\ 485 /* dont parse just after a state switch since the component token doesn't exist */\ 486 *(curs-1) = 0;\ 487 if (state2 == NUMBER) {\ 488 pushG(&self->label, parseI64(component));\ 489 }\ 490 elif (state2 == CSTRING) {\ 491 pushG(&self->label, component);\ 492 }\ 493 elif (state2 == ANY) {\ 494 pushG(&self->label, -1);\ 495 }\ 496 *(curs-1) = c;\ 497 component = curs;\ 498 }\ 499 if (isCmp and (v[i] == 'x' or v[i] == 'X' or v[i] == '*')) {\ 500 /* must be x. or x- or x+ or x */\ 501 state2 = ANY;\ 502 }\ 503 else {\ 504 state2 = !isdigit(v[i]) ? CSTRING : NUMBER;\ 505 }\ 506 leading0 = v[i] == '0' ? true : false;\ 507 }\ 508 else {\ 509 /* in a component */\ 510 if (state2 == NUMBER and !hasG(DELIMITERS, v[i])) {\ 511 /* number must have only digit */\ 512 if (!isdigit(v[i])) goto fail;\ 513 /* no leading 0 in numbers */\ 514 if (leading0) goto fail;\ 515 }\ 516 }\ 517 prevState = state 518 519 switchLabel(PR_DELIMITER[0], PRE, others); 520 switchLabel(BD_DELIMITER[0], BUILD, others); 521 validStrictLabel(others); 522 break; 523 case PRE: 524 // no empty components 525 if (hasG(DELIMITERS, v[i-1]) and hasG(DELIMITERS, v[i])) goto fail; 526 switchLabel(BD_DELIMITER[0], BUILD, prerelease); 527 validStrictLabel(prerelease); 528 break; 529 case BUILD: 530 if (hasG(DELIMITERS, v[i-1]) and hasG(DELIMITERS, v[i])) goto fail; 531 validStrictLabel(build); 532 break; 533 } 534 } 535 parsingFinish: 536 537 /* the version string has been parsed */ 538 self->empty = false; 539 540 /* lv(STATE[state]); */ 541 /* lv(STATE[prevState]); */ 542 /* lv(STATE[state2]); */ 543 /* lv(leading0); */ 544 545 // MUST NOT end with empty component 546 if (loose) { 547 if (hasG(DELIMITER, *curs)) goto fail; 548 prevState = state; 549 550 if (isCmp) { 551 // handle x alone "x" to match any version, signaled with self->empty=true 552 if (state == REL and (hasG(ANYTOKENS, *curs) or (curs != v and hasG(ANYTOKENS, *(curs-1))))) { 553 self->empty = true; 554 goto end; 555 } 556 // handle x in components 557 if (state == MAJOR and hasG(DELIMITERS, *(curs+1)) and hasG(ANYTOKENS, *curs)) { 558 // ignore last - or + for compares 559 self->major = -1; 560 goto end; 561 } 562 // if last component has an any token, it doesn't need to be parsed 563 if (hasG(ANYTOKENS, component) and state < OTHERS) goto end; 564 } 565 } 566 else { 567 if (hasG(DELIMITERS, curs)) goto fail; 568 } 569 570 // check for incomplete components 571 if (prevState != state) goto fail; 572 573 // versions must have 3 components (except when loose is set) Release.Major.minor 574 if (state < MINOR and !release0) goto fail; 575 576 if (!validate) { 577 // parse the last component 578 switch(state) { 579 case REL: 580 self->release = parseI64(component); 581 self->mainComponentCount++; 582 break; 583 case MAJOR: 584 self->major = parseI64(component); 585 self->mainComponentCount++; 586 break; 587 case MINOR: 588 self->minor = parseI64(component); 589 self->mainComponentCount++; 590 break; 591 case PATCH: 592 self->patch = parseI64(component); 593 self->mainComponentCount++; 594 break; 595 case OTHERS: 596 #define lastComponent(label)\ 597 if (state2 == NUMBER) {\ 598 pushG(&self->label, parseI64(component));\ 599 }\ 600 elif (state2 == CSTRING) {\ 601 pushG(&self->label, component);\ 602 }\ 603 elif (state2 == ANY) {\ 604 pushG(&self->label, -1);\ 605 } 606 lastComponent(others); 607 break; 608 case PRE: 609 lastComponent(prerelease); 610 break; 611 case BUILD: 612 lastComponent(build); 613 break; 614 } 615 } 616 617 end: 618 free(v); 619 ret true; 620 621 fail: 622 free(v); 623 ret false; 624 } 625 626 local bool validVersion(versiont *self UNUSED, const char *v) { 627 ret parse(self, (char*)v, true /*validate*/, false /*loose*/, false /*isCmp*/); 628 } 629 630 local bool parseCmpVersion(versiont *self, const char *v, bool isCmp) { 631 if (!v) ret false; 632 633 freeVersion(self); 634 self->originalVer = strdup(v); 635 636 if (!cleanVersion(self, self->originalVer)) ret false; 637 638 ret parse(self, self->originalVer, false /*validate*/, true /*loose*/, isCmp); 639 } 640 641 local bool parseVersion(versiont *self, const char *v) { 642 ret parseCmpVersion(self, v, false /*isCmp*/); 643 } 644 645 local bool parseStrictVersion(versiont *self, const char *v) { 646 647 if (!v) ret false; 648 649 freeVersion(self); 650 self->originalVer = strdup(v); 651 652 if (!cleanVersion(self, self->originalVer) or !validVersion(self, self->originalVer)) ret false; 653 654 ret parse(self, self->originalVer, false /*validate*/, false /*loose*/, false /*isCmp*/); 655 } 656 657 local bool cleanVersion(versiont *self UNUSED, char *v) { 658 if (!v) ret false; 659 660 size_t dest = 0; 661 // keep only valid characters 662 range(i, strlen(v)) { 663 if (isValidChar(v[i])) { 664 if (dest < i) { 665 v[dest] = v[i]; 666 } 667 dest++; 668 } 669 } 670 671 // terminate string 672 v[dest] = 0; 673 674 ret true; 675 } 676 677 local bool equalVersion(versiont *self, versiont *ver) { 678 if (!ver) ret false; 679 680 #define eqComp(comp) if (self->comp != ver->comp) ret false 681 eqComp(release); 682 eqComp(major); 683 eqComp(minor); 684 eqComp(patch); 685 #define eqLabel(label) if (!eqG(&self->label, &ver->label)) ret false 686 eqLabel(others); 687 eqLabel(prerelease); 688 eqLabel(build); 689 690 ret true; 691 } 692 693 local bool equalSVersion(versiont *self, const char *v) { 694 if (!v) ret false; 695 696 bool r = false; 697 createVersion(ver); 698 r = parseO(&ver, v); 699 if (!r) /* error parsing*/ goto end; 700 701 r = equalVersion(self, &ver); 702 703 end: 704 freeO(&ver); 705 ret r; 706 } 707 708 local bool equalSSVersion(versiont *self UNUSED, const char *v1, const char *v2) { 709 if (!v1 or !v2) ret false; 710 711 bool r = false; 712 createVersion(ver1); 713 r = parseO(&ver1, v1); 714 if (!r) /* error parsing*/ ret false; 715 createVersion(ver2); 716 r = parseO(&ver2, v2); 717 if (!r) /* error parsing*/ ret false; 718 719 r = eqO(&ver1, &ver2); 720 721 freeO(&ver1); 722 freeO(&ver2); 723 ret r; 724 } 725 726 local int cmpVersion(versiont *self, versiont *ver) { 727 if (!ver) ret 0; 728 729 int r = 0; 730 731 #define cmpComp(comp)\ 732 r = CMP(self->comp, ver->comp);\ 733 if (r) ret r; 734 cmpComp(release); 735 cmpComp(major); 736 cmpComp(minor); 737 cmpComp(patch); 738 739 if (lenG(&self->others) > lenG(&ver->others)) { 740 #define cmpLabel(label, x, y, returnSign)\ 741 iter(&x->label, c) {\ 742 if (isOSmallIntG(c) and isEIntG(&y->label, iterIndexG(&x->label))) {\ 743 /* both ints */\ 744 var a = getG(&y->label, rtI64, iterIndexG(&x->label));\ 745 cast(smallIntt*, b, c);\ 746 r = cmpOrder(a, getValG(b));\ 747 if (r) ret r;\ 748 }\ 749 elif (!isOSmallIntG(c) and !isEIntG(&y->label, iterIndexG(&x->label))) {\ 750 /* both strings */\ 751 var a = getG(&y->label, rtChar, iterIndexG(&x->label));\ 752 r = scmpOrder(a, ssGet(c));\ 753 if (r) ret r;\ 754 }\ 755 else {\ 756 /* one is int and the other is string */\ 757 baset *ao = getG(&y->label, rtBaset, iterIndexG(&x->label));\ 758 char *a = toStringO(ao);\ 759 finishG(ao);\ 760 char *b = toStringO(c);\ 761 r = scmpOrder(a,b);\ 762 freeManyS(a,b);\ 763 if (r) ret r;\ 764 }\ 765 }\ 766 /* y has components left, so it is greater than x */\ 767 if (lenG(&y->label) > lenG(&x->label)) ret 1 * returnSign;\ 768 if (lenG(&y->label) < lenG(&x->label)) ret -1 * returnSign 769 #define cmpOrder(a,b) CMP(a,b) 770 #define scmpOrder(a,b) strcmp(a,b) 771 cmpLabel(others, ver, self, 1 /*returnSign*/); 772 #undef cmpOrder 773 #undef scmpOrder 774 } 775 else { 776 #define cmpOrder(a,b) CMP(b,a) 777 #define scmpOrder(a,b) strcmp(b,a) 778 cmpLabel(others, self, ver, -1 /*returnSign*/); 779 #undef cmpOrder 780 #undef scmpOrder 781 } 782 // prereleases have lower precedence 783 if (!lenG(&self->prerelease) and lenG(&ver->prerelease)) ret 1; 784 if (lenG(&self->prerelease) and !lenG(&ver->prerelease)) ret -1; 785 // self and ver are both normal versions or both prereleases 786 if (lenG(&self->prerelease) > lenG(&ver->prerelease)) { 787 #define cmpOrder(a,b) CMP(a,b) 788 #define scmpOrder(a,b) strcmp(a,b) 789 cmpLabel(prerelease, ver, self, 1 /*returnSign*/); 790 #undef cmpOrder 791 #undef scmpOrder 792 } 793 else { 794 #define cmpOrder(a,b) CMP(b,a) 795 #define scmpOrder(a,b) strcmp(b,a) 796 cmpLabel(prerelease, self, ver, -1 /*returnSign*/); 797 #undef cmpOrder 798 #undef scmpOrder 799 } 800 if (lenG(&self->build) > lenG(&ver->build)) { 801 #define cmpOrder(a,b) CMP(a,b) 802 #define scmpOrder(a,b) strcmp(a,b) 803 cmpLabel(build, ver, self, 1 /*returnSign*/); 804 #undef cmpOrder 805 #undef scmpOrder 806 } 807 else { 808 #define cmpOrder(a,b) CMP(b,a) 809 #define scmpOrder(a,b) strcmp(b,a) 810 cmpLabel(build, self, ver, -1 /*returnSign*/); 811 #undef cmpOrder 812 #undef scmpOrder 813 } 814 815 ret r; 816 } 817 818 local int cmpSVersion(versiont *self, const char *v) { 819 if (!v) ret 0; 820 821 int r = 0; 822 createVersion(ver); 823 r = parseO(&ver, v); 824 if (!r) /* error parsing*/ ret 0; 825 826 r = cmpVersion(self, &ver); 827 828 freeO(&ver); 829 ret r; 830 } 831 832 local int cmpSSVersion(versiont *self UNUSED, const char *v1, const char *v2) { 833 if (!v1 or !v2) ret 0; 834 835 int r = 0; 836 createVersion(ver1); 837 r = parseO(&ver1, v1); 838 if (!r) /* error parsing*/ ret 0; 839 createVersion(ver2); 840 r = parseO(&ver2, v2); 841 if (!r) /* error parsing*/ ret 0; 842 843 r = cmpO(&ver1, &ver2); 844 845 freeO(&ver1); 846 freeO(&ver2); 847 ret r; 848 } 849 850 local bool incrVersion(versiont *self, u32 level) { 851 switch(level) { 852 case releaseVer: 853 self->release++; 854 break; 855 case majorVer: 856 self->major++; 857 // Patch and minor version MUST be reset to 0 when major version is incremented 858 self->minor = 0; 859 self->patch = 0; 860 emptyG(&self->others); 861 emptyG(&self->prerelease); 862 emptyG(&self->build); 863 break; 864 case minorVer: 865 self->minor++; 866 // Patch version MUST be reset to 0 when major version is incremented 867 self->patch = 0; 868 emptyG(&self->others); 869 emptyG(&self->prerelease); 870 emptyG(&self->build); 871 break; 872 case patchVer: 873 self->patch++; 874 break; 875 default: 876 level -= 4; 877 if (lenG(&self->others)) { 878 baset *c = getG(&self->others, rtBaset, level); 879 if (!c) { 880 // out of range 881 level -= lenG(&self->others); 882 if (lenG(&self->prerelease)) goto prerel; 883 if (lenG(&self->build)) goto buildl; 884 ret false; 885 } 886 #define incC\ 887 if (!isOSmallInt(c)) {\ 888 finishG(c);\ 889 ret false;\ 890 }\ 891 cast(smallIntt*, v, c);\ 892 (*getPG(v))++;\ 893 finishG(c) 894 incC; 895 } 896 elif (lenG(&self->prerelease)) { 897 prerel:; 898 baset *c = getG(&self->prerelease, rtBaset, level); 899 if (!c) { 900 // out of range 901 level -= lenG(&self->prerelease); 902 if (lenG(&self->build)) goto buildl; 903 ret false; 904 } 905 incC; 906 } 907 elif (lenG(&self->build)) { 908 buildl:; 909 baset *c = getG(&self->build, rtBaset, level); 910 if (!c) { 911 // out of range 912 ret false; 913 } 914 incC; 915 } 916 } 917 ret true; 918 } 919 920 local int sortVer(const void * A, const void * B) { 921 castS(a, A); 922 castS(b, B); 923 createVersion(va); 924 parseO(&va, ssGet(a)); 925 createVersion(vb); 926 parseO(&vb, ssGet(b)); 927 928 int r = cmpO(&va, &vb); 929 930 freeO(&va); 931 freeO(&vb); 932 933 ret r; 934 } 935 936 local smallArrayt* sortVersion(versiont *self UNUSED, smallArrayt *vlist) { 937 if (!vlist and !isOSmallArray(vlist)) ret NULL; 938 939 createAllocateSmallArray(r); 940 createVersion(v); 941 942 iter(vlist, VS) { 943 if (!isOSmallString(VS)) continue; 944 // keep only valid version string 945 if (!parseO(&v, ssGet(VS))) continue; 946 pushG(r, ssGet(VS)); 947 } 948 949 freeO(&v); 950 951 sortFG(r, sortVer); 952 ret r; 953 } 954 955 956 957 local bool satisfiesVersion(versiont *self, const char *vrange) { 958 if (!vrange) ret false; 959 960 if (isBlankG(vrange)) { 961 /* range string is empty, match anything */ 962 ret true; 963 } 964 965 bool r = false; 966 967 enum {SEARCH, SEARCH_OR, SEARCH_OR_END, EQ, GT, GTE, LT, LTE, CMP_LAST, 968 /*hyphen state*/ SPACE, HYPHEN, HYPHEN_CHAR, HYPHEN_SEARCH, HYPHEN_LAST, 969 TILDE, 970 CARET}; 971 const char* STATE[] UNUSED = {"SEARCH", "SEARCH_OR", "SEARCH_OR_END", "EQ", "GT", "GTE", "LT", "LTE", "CMP_LAST", 972 /*hyphen state*/ "SPACE", "HYPHEN", "HYPHEN_CHAR", "HYPHEN_SEARCH", "HYPHEN_LAST", 973 "TILDE", 974 "CARET"}; 975 u8 state = SEARCH; 976 // First or second comparator in range: >=ver1 <ver2 977 enum {FIRST, LAST, NONE}; 978 const char*CMPTOR[] UNUSED = {"FIRST", "LAST", "NONE"}; 979 u8 cmptor = NONE; 980 char *vr = dupG(vrange); 981 char *c = vr; 982 char *vers = NULL; 983 984 enum {NOP, CMP_EQ, CMP_LT, CMP_LTE, CMP_GT, CMP_GTE, 985 HYPHEN_GTE, HYPHEN_LT, 986 CMP_XRANGE, 987 CMP_TILDE, 988 CMP_CARET}; 989 const char* OPTYPE[] UNUSED = {"NOP", "CMP_EQ", "CMP_LT", "CMP_LTE", "CMP_GT", "CMP_GTE", 990 "HYPHEN_GTE", "HYPHEN_LT", 991 "CMP_XRANGE", 992 "CMP_TILDE", 993 "CMP_CARET"}; 994 995 // parsed comparator 996 struct { 997 u8 type; 998 versiont v; 999 } cmp; 1000 // comparator results 1001 bool res[2]; 1002 1003 cmp.type = NOP; 1004 initiateVersion(&cmp.v); 1005 1006 void doLT(void) { 1007 logI("<"); 1008 1009 #define parseCmpVer(str, freeV)\ 1010 if (!parseCmpVersion(&v, str, true /*isCmp*/)) {\ 1011 r = false;\ 1012 freeV;\ 1013 goto end;\ 1014 } 1015 #define anyValue(comp) if ((i64)v.comp == -1) v.comp = self->comp; 1016 #define anyLabel(label)\ 1017 iter(&v.label, o) {\ 1018 if (isOSmallInt(o)) {\ 1019 cast(smallIntt*, i, o);\ 1020 if (getValG(i) == -1) {\ 1021 baset *val = getG(&self->label, rtBaset, iterIndexG(&v.label));\ 1022 if (val) {\ 1023 setNFreeG(&v.label, iterIndexG(&v.label), dupG(val));\ 1024 }\ 1025 }\ 1026 }\ 1027 } 1028 #define anyVer\ 1029 anyValue(release);\ 1030 anyValue(major);\ 1031 anyValue(minor);\ 1032 anyValue(patch);\ 1033 anyLabel(others);\ 1034 anyLabel(prerelease);\ 1035 anyLabel(build) 1036 1037 // TODO assign any in LT LTE GT GTE 1038 #define doComp(comp) if (self->comp != v.comp) { res[cmptor] = false; freeO(&v); goto end;} 1039 #define doLabel(label) if (!eqG(&self->label, &v.label)) { res[cmptor] = false; freeO(&v); goto end;} 1040 #define genVerForCmp(compareResult, checkEqual, isLT)\ 1041 /* when there is -, prereleases are not matched */\ 1042 /* when there is no -, prereleases are matched */\ 1043 char *cver;\ 1044 char *sl = sliceS(vers, 0, -1);\ 1045 cver = strdup(vers);\ 1046 createVersion(v);\ 1047 if (!hasG(sl, '-') and *(c-1) != '-') {\ 1048 /* not selecting a prerelease and not - at the end */\ 1049 /* self MUST be a normal version */\ 1050 if (isLT and !isEmptyG(&self->prerelease)) { res[cmptor] = false; goto end;}\ 1051 if (checkEqual) {res[cmptor] = true; goto end;}\ 1052 if (isLT) pushG(&cver, "-0"); /* for GT: dont add -0 so that prereleases are not selected */\ 1053 if (!isLT and !isEmptyG(&self->prerelease)) {\ 1054 /* select normal versions only for GT ops */\ 1055 res[cmptor] = false; goto end;\ 1056 }\ 1057 parseCmpVer(cver,);\ 1058 anyVer;\ 1059 }\ 1060 else {\ 1061 if (!isEmptyG(&self->prerelease)) {\ 1062 /* consider prerelease of version in the compare only */\ 1063 parseCmpVer(cver,);\ 1064 anyVer;\ 1065 if (self->release != v.release) { res[cmptor] = false; freeO(&v); goto end;}\ 1066 doComp(release);\ 1067 doComp(major);\ 1068 doComp(minor);\ 1069 doComp(patch);\ 1070 doLabel(others);\ 1071 }\ 1072 if (!isLT and !hasG(sl, '-') and *(c-1) == '-') pushG(&cver, "0"); /* for GT: add -0 so that prereleases are selected */\ 1073 parseCmpVer(cver,);\ 1074 }\ 1075 int R = cmpVersion(self, &v);\ 1076 compareResult\ 1077 end:\ 1078 freeO(&v);\ 1079 free(sl);\ 1080 free(cver); 1081 genVerForCmp(if (R < 0) res[cmptor] = true; else res[cmptor] = false;, false/*checkEqual*/, true/*isLT*/); 1082 } 1083 1084 void doLTE(void) { 1085 logI("<="); 1086 1087 genVerForCmp(if (R <= 0) res[cmptor] = true; else res[cmptor] = false;, equalSVersion(self, cver)/*checkEqual*/, true/*isLT*/); 1088 } 1089 1090 void doGT(void) { 1091 logI(">"); 1092 1093 genVerForCmp(if (R > 0) res[cmptor] = true; else res[cmptor] = false;, false/*checkEqual*/, false/*isLT*/); 1094 } 1095 1096 void doGTE(void) { 1097 logI(">="); 1098 1099 genVerForCmp(if (R >= 0) res[cmptor] = true; else res[cmptor] = false;, equalSVersion(self, cver)/*checkEqual*/, false/*isLT*/); 1100 } 1101 1102 void doEq(void) { 1103 createVersion(v); 1104 parseCmpVer(vers,); 1105 if (v.empty) { 1106 // v is any version, eq always matches self 1107 r = true; 1108 goto end; 1109 } 1110 // check if it is x range 1111 // maybe any component is at the end 1112 anyValue(release); 1113 char c = getG(vers,unusedV,-1); 1114 if (c == 'x' or c == 'X' or c == '*') { 1115 #define xrange(high, relOrPreOrBuild)\ 1116 v.minor = 0;\ 1117 vers = toStringO(&v);\ 1118 relOrPreOrBuild;\ 1119 cmptor = FIRST;\ 1120 doGTE();\ 1121 free(vers);\ 1122 v.high++;\ 1123 vers = toStringO(&v);\ 1124 cmptor = LAST;\ 1125 doLT();\ 1126 free(vers) 1127 #define xrangeRelOrPreOrBuild(relOrPreOrBuild)\ 1128 if ((i64)v.major != -1 and (i64)v.minor == -1) {\ 1129 xrange(major, relOrPreOrBuild);\ 1130 }\ 1131 elif ((v.minor == 0 or (i64)v.minor == -1) and (i64)v.major == -1) {\ 1132 v.major = 0;\ 1133 xrange(release, relOrPreOrBuild);\ 1134 } 1135 xrangeRelOrPreOrBuild(); 1136 1137 #define xrangeIsDetected\ 1138 cmp.type = CMP_XRANGE;\ 1139 cmptor = LAST;\ 1140 goto end 1141 xrangeIsDetected; 1142 } 1143 elif (c == '-') { 1144 c = getG(vers,unusedV,-2); 1145 if (c == 'x' or c == 'X' or c == '*') { 1146 xrangeRelOrPreOrBuild(pushG(&vers, '-')); 1147 if (res[LAST] == false and res[FIRST] == true and lenG(&self->prerelease)) { 1148 // prerelease on lower bound of xrange is accepted when - is at the end of comparator 1149 res[LAST] = true; 1150 } 1151 xrangeIsDetected; 1152 } 1153 } 1154 elif (c == '+') { 1155 c = getG(vers,unusedV,-2); 1156 if (c == 'x' or c == 'X' or c == '*') { 1157 xrangeRelOrPreOrBuild(); 1158 xrangeIsDetected; 1159 } 1160 } 1161 anyVer; 1162 r = res[0] = res[1] = equalVersion(self, &v); 1163 logP(BLD WHT"Cmptor result: %b"RST, r); 1164 end: 1165 freeO(&v); 1166 } 1167 1168 i16 index = 0; 1169 while(*c) { 1170 /* lv(STATE[state]); */ 1171 /* lv(CMPTOR[cmptor]); */ 1172 /* lv(index); */ 1173 /* lv(res[cmptor]); */ 1174 1175 if (*c == '|' and (*(c+1) == '|')) { 1176 /* lv(STATE[state]); */ 1177 /* lv(CMPTOR[cmptor]); */ 1178 /* lv(OPTYPE[cmp.type]); */ 1179 if (cmptor == FIRST) { 1180 r = res[0]; 1181 logP(BLD WHT"Cmptor result: %b"RST, r); 1182 if (r) /* matching, stop. */ goto end; 1183 cmptor = NONE; 1184 } 1185 state = SEARCH_OR_END; 1186 } 1187 switch(state) { 1188 #define skipSpace if (isspace(*c)) break 1189 #define switchCmptor\ 1190 if (cmptor == LAST) goto fail;\ 1191 if (cmptor == FIRST) cmptor = LAST;\ 1192 if (cmptor == NONE) {\ 1193 /* reset results */\ 1194 res[0] = res[1] = true;\ 1195 cmptor = FIRST;\ 1196 } 1197 case EQ: 1198 case TILDE: 1199 case CARET: 1200 cmp_eq: 1201 if (isspace(*c)) { 1202 state = CMP_LAST; 1203 goto cmp_last; 1204 } 1205 break; 1206 case LT: 1207 case LTE: 1208 case GT: 1209 case GTE: 1210 if (!vers) { 1211 skipSpace; 1212 if (isdigit(*c) or *c == 'x' or *c == 'X' or *c == '*') vers = c; 1213 break; 1214 } 1215 if (isspace(*c)) { 1216 state = CMP_LAST; 1217 goto cmp_last; 1218 } 1219 break; 1220 case HYPHEN_CHAR: 1221 if (*c == '-') state = HYPHEN_SEARCH; 1222 break; 1223 case HYPHEN: 1224 if (isspace(*c)) { 1225 state = HYPHEN_LAST; 1226 goto hyphen_last; 1227 } 1228 break; 1229 case HYPHEN_SEARCH: 1230 skipSpace; 1231 if (isdigit(*c) or *c == 'x' or *c == 'X' or *c == '*') { 1232 switchCmptor; 1233 cmp.type = HYPHEN_LT; 1234 vers = c; 1235 state = HYPHEN; 1236 break; 1237 } 1238 goto fail; 1239 break; 1240 case SEARCH: 1241 skipSpace; 1242 // any must be x, X or * in a component 1243 if ((*c == 'x' or *c == 'X' or *c == '*') and (*(c+1) != '.' and *(c+1) != '-' and *(c+1) != '+') and *(c+1) != ' ' and *(c+1) != 0) goto fail; 1244 if (*c == '=') { 1245 cmp.type = CMP_EQ; 1246 vers = c+1; 1247 state = EQ; 1248 //allow equal in comparators - if (cmptor != NONE) goto fail; 1249 cmptor = LAST; 1250 break; 1251 } 1252 if (isdigit(*c) or *c == 'x' or *c == 'X' or *c == '*') { 1253 // scan further to distinguish between equal hyphen range 1254 char *sc = c+1; 1255 bool isHyphen = false; 1256 int hyphenState = SEARCH; 1257 do { 1258 if (*sc == '|' and *(sc+1) == '|') /*end of comparator*/ break; 1259 if (hyphenState == SPACE and *sc == '-') { 1260 isHyphen = true; 1261 break; 1262 } 1263 if (hyphenState == SEARCH and isspace(*sc)) hyphenState = SPACE; 1264 } while(*(sc++)); 1265 if (isHyphen) { 1266 cmp.type = HYPHEN_GTE; 1267 vers = c; 1268 state = HYPHEN; 1269 /* reset results */ 1270 res[0] = res[1] = true; 1271 cmptor = FIRST; 1272 break; 1273 } 1274 else { 1275 cmp.type = CMP_EQ; 1276 vers = c; 1277 state = EQ; 1278 //allow equal in comparators - if (cmptor != NONE) goto fail; 1279 cmptor = LAST; 1280 goto cmp_eq; 1281 } 1282 } 1283 if (*c == '>') { 1284 switchCmptor; 1285 vers = NULL; 1286 if (*(c+1) == '=') { 1287 cmp.type = CMP_GTE; 1288 state = GTE; 1289 } 1290 else { 1291 cmp.type = CMP_GT; 1292 state = GT; 1293 } 1294 break; 1295 } 1296 if (*c == '<') { 1297 switchCmptor; 1298 vers = NULL; 1299 if (*(c+1) == '=') { 1300 cmp.type = CMP_LTE; 1301 state = LTE; 1302 } 1303 else { 1304 cmp.type = CMP_LT; 1305 state = LT; 1306 } 1307 break; 1308 } 1309 if (*c == '~') { 1310 cmp.type = CMP_TILDE; 1311 vers = c+1; 1312 state = TILDE; 1313 cmptor = LAST; 1314 break; 1315 } 1316 if (*c == '^') { 1317 cmp.type = CMP_CARET; 1318 vers = c+1; 1319 state = CARET; 1320 cmptor = LAST; 1321 break; 1322 } 1323 goto fail; 1324 break; 1325 case SEARCH_OR: 1326 // empty because the comparator has 1 or 2 compares, extra compares are ignored 1327 break; 1328 case SEARCH_OR_END: 1329 if (*(c+1) != '|') state = SEARCH; 1330 break; 1331 case CMP_LAST: 1332 cmp_last: 1333 switch(cmp.type) { 1334 case CMP_EQ:; 1335 char cc = *c; 1336 *c = 0; 1337 doEq(); 1338 if (cmp.type == CMP_XRANGE) goto next; 1339 if (r) /* matching, stop. */ goto end; 1340 *c = cc; 1341 break; 1342 case CMP_LT: 1343 #define you(op)\ 1344 cc = *c;\ 1345 *c = 0;\ 1346 op();\ 1347 *c = cc 1348 you(doLT); 1349 break; 1350 case CMP_LTE: 1351 you(doLTE); 1352 break; 1353 case CMP_GT: 1354 you(doGT); 1355 break; 1356 case CMP_GTE: 1357 you(doGTE); 1358 break; 1359 case CMP_TILDE: 1360 // TODO any component 1361 #define tilde(comp, zero)\ 1362 bool acceptPreRel = false;\ 1363 if (hasG(vers, '-')) {\ 1364 pushG(&v.prerelease, 0);\ 1365 acceptPreRel = true;\ 1366 }\ 1367 cmptor = FIRST;\ 1368 doGTE();\ 1369 v.comp++;\ 1370 zero;\ 1371 v.patch = 0;\ 1372 freeG(&v.others);\ 1373 freeG(&v.prerelease);\ 1374 freeG(&v.build);\ 1375 vers = toStringO(&v);\ 1376 cmptor = LAST;\ 1377 doLT();\ 1378 free(vers);\ 1379 if (acceptPreRel and res[LAST] == false and res[FIRST] == true and lenG(&self->prerelease)) {\ 1380 /* prerelease on lower bound of xrange is accepted when - is at the end of comparator */\ 1381 res[LAST] = true;\ 1382 } 1383 #define tildeCmp(label)\ 1384 createVersion(v);\ 1385 parseCmpVer(vers, freeO(&v));\ 1386 if (v.mainComponentCount >= 3) {\ 1387 tilde(minor,);\ 1388 freeO(&v);\ 1389 goto label;\ 1390 }\ 1391 elif (v.mainComponentCount == 2) {\ 1392 tilde(major, v.minor = 0);\ 1393 freeO(&v);\ 1394 goto label;\ 1395 }\ 1396 elif (v.mainComponentCount == 1) {\ 1397 tilde(release, v.major = 0 ; v.minor = 0);\ 1398 freeO(&v);\ 1399 goto label;\ 1400 }\ 1401 freeO(&v) 1402 tildeCmp(next); 1403 break; 1404 case CMP_CARET: { 1405 // TODO any component 1406 #define caret(comp, zero)\ 1407 bool acceptPreRel = false;\ 1408 if (hasG(vers, '-')) {\ 1409 pushG(&v.prerelease, 0);\ 1410 acceptPreRel = true;\ 1411 }\ 1412 cmptor = FIRST;\ 1413 doGTE();\ 1414 v.comp++;\ 1415 zero;\ 1416 v.minor = 0;\ 1417 v.patch = 0;\ 1418 freeG(&v.others);\ 1419 freeG(&v.prerelease);\ 1420 freeG(&v.build);\ 1421 vers = toStringO(&v);\ 1422 cmptor = LAST;\ 1423 doLT();\ 1424 free(vers);\ 1425 if (acceptPreRel and res[LAST] == false and res[FIRST] == true and lenG(&self->prerelease)) {\ 1426 /* prerelease on lower bound of xrange is accepted when - is at the end of comparator */\ 1427 res[LAST] = true;\ 1428 } 1429 #define caretCmp(label)\ 1430 createVersion(v);\ 1431 parseCmpVer(vers, freeO(&v));\ 1432 if (v.mainComponentCount >= 2) {\ 1433 caret(major,);\ 1434 freeO(&v);\ 1435 goto label;\ 1436 }\ 1437 elif (v.mainComponentCount == 1) {\ 1438 caret(release, v.major = 0);\ 1439 freeO(&v);\ 1440 goto label;\ 1441 }\ 1442 freeO(&v) 1443 caretCmp(next); 1444 } 1445 break; 1446 } 1447 next: 1448 cmp.type = NOP; 1449 switch(cmptor) { 1450 case FIRST: 1451 state = SEARCH; 1452 break; 1453 case LAST: 1454 r = res[0] and res[1]; 1455 logP(BLD WHT"Cmptor result: %b"RST, r); 1456 if (r) /* matching, stop. */ goto end; 1457 cmptor = NONE; 1458 state = SEARCH_OR; 1459 break; 1460 } 1461 break; 1462 case HYPHEN_LAST: 1463 hyphen_last: 1464 switch(cmp.type) { 1465 char cc; 1466 case HYPHEN_GTE: 1467 you(doGTE); 1468 state = HYPHEN_CHAR; 1469 break; 1470 case HYPHEN_LT: 1471 #define hyphenLTVer\ 1472 char *cs = toStringO(&v);\ 1473 vers = cs;\ 1474 doLT();\ 1475 free(cs) 1476 #define hyphenCmp\ 1477 /* LTE when there are a least 3 components 1478 LT when there are less than 3 components */\ 1479 createVersion(v);\ 1480 parseCmpVer(vers, freeO(&v));\ 1481 if (v.minor) {\ 1482 UNIQVAR(atleast):\ 1483 /*at least 3 components: LTE*/\ 1484 doLTE();\ 1485 }\ 1486 else {\ 1487 if (v.patch or lenG(&v.others)) goto UNIQVAR(atleast);\ 1488 /*less than 3 components LT */\ 1489 if (v.major) {\ 1490 v.major++;\ 1491 hyphenLTVer;\ 1492 }\ 1493 elif (v.release) {\ 1494 v.release++;\ 1495 hyphenLTVer;\ 1496 }\ 1497 else doLT();\ 1498 }\ 1499 r = res[0] and res[1];\ 1500 logP(BLD WHT"Hyphen Cmptor result: %b"RST, r);\ 1501 if (r) /* matching, stop. */ { freeO(&v); goto end;}\ 1502 freeO(&v) 1503 hyphenCmp; 1504 cmptor = NONE; 1505 state = SEARCH_OR; 1506 break; 1507 } 1508 break; 1509 } 1510 c++; 1511 index++; 1512 } 1513 1514 // finish comparing 1515 switch(state) { 1516 case SEARCH_OR: 1517 finalResult: 1518 lv(res[0]); 1519 lv(res[1]); 1520 r = res[0] and res[1]; 1521 logP("Cmptor result: %b", r); 1522 break; 1523 case EQ: 1524 doEq(); 1525 if (cmp.type == CMP_XRANGE) goto finalResult; 1526 break; 1527 case LT: 1528 #define finalOp(op)\ 1529 if (!vers) goto fail;\ 1530 op();\ 1531 goto finalResult;\ 1532 break 1533 finalOp(doLT); 1534 case LTE: 1535 finalOp(doLTE); 1536 case GT: 1537 finalOp(doGT); 1538 case GTE: 1539 finalOp(doGTE); 1540 case HYPHEN: 1541 if (cmp.type == HYPHEN_GTE) /*incomplete hyphen range*/ goto fail; 1542 if (cmp.type == HYPHEN_LT) { 1543 hyphenCmp; 1544 } 1545 break; 1546 case HYPHEN_CHAR: 1547 case HYPHEN_SEARCH: 1548 /*incomplete hyphen range*/ 1549 goto fail; 1550 break; 1551 case TILDE: 1552 tildeCmp(finalResult); 1553 goto fail; 1554 break; 1555 case CARET: { 1556 caretCmp(finalResult); 1557 goto fail; 1558 } 1559 break; 1560 } 1561 1562 end: 1563 free(vr); 1564 ret r; 1565 1566 fail: 1567 free(vr); 1568 ret false; 1569 } 1570 1571 local smallJsont* toJsonVersion(versiont *self) { 1572 createAllocateSmallJson(r); 1573 setG(r, "release", self->release); 1574 setG(r, "major", self->major); 1575 setG(r, "minor", self->minor); 1576 setG(r, "patch", self->patch); 1577 setNFreeG(r, "others", dupG(&self->others)); 1578 setNFreeG(r, "prerelease", dupG(&self->prerelease)); 1579 setNFreeG(r, "build", dupG(&self->build)); 1580 ret r; 1581 } 1582 1583 local char* toJsonStrVersion(versiont *self) { 1584 var j = toJsonVersion(self); 1585 char *r = toStringG(j); 1586 terminateG(j); 1587 ret r; 1588 } 1589 1590 local bool fromJsonVersion(versiont *self, smallJsont *json) { 1591 freeVersion(self); 1592 if (!json or isEmptyG(json)) ret false; 1593 1594 if (!hasG(json, "release")) ret false; 1595 1596 self->release = getG(json, rtI64, "release"); 1597 self->major = getG(json, rtI64, "major"); 1598 self->minor = getG(json, rtI64, "minor"); 1599 self->patch = getG(json, rtI64, "patch"); 1600 1601 smallArrayt *a; 1602 a = getNDupG(json, rtSmallArrayt, "others"); 1603 if (a) { 1604 setsoG(&self->others, getsoG(a)); 1605 finishG(a); 1606 } 1607 a = getNDupG(json, rtSmallArrayt, "prerelease"); 1608 if (a) { 1609 setsoG(&self->prerelease, getsoG(a)); 1610 finishG(a); 1611 } 1612 a = getNDupG(json, rtSmallArrayt, "build"); 1613 if (a) { 1614 setsoG(&self->build, getsoG(a)); 1615 finishG(a); 1616 } 1617 1618 ret true; 1619 } 1620 1621 local bool fromJsonStrVersion(versiont *self, const char *json) { 1622 freeVersion(self); 1623 if (!json) ret false; 1624 createSmallJson(j); 1625 parseG(&j, (char*)json); 1626 bool r = fromJsonVersion(self, &j); 1627 freeG(&j); 1628 ret r; 1629 } 1630 1631 // vim: set expandtab ts=2 sw=2: 1632 1633 bool checkLibsheepyVersionVersion(const char *currentLibsheepyVersion) { 1634 return eqG(currentLibsheepyVersion, LIBSHEEPY_VERSION); 1635 } 1636