Skip to content

Commit 8eca18a

Browse files
marcomqdom96
andauthored
Add ~ and ^ semver semantic to nimble "requires" (#908)
* GH-907, add partial semver semantic * added comments, small refactoring * undo previous change, added additional test case * fixed check for "=" * Update src/nimblepkg/version.nim Co-authored-by: Dominik Picheta <[email protected]> * Update src/nimblepkg/version.nim Co-authored-by: Dominik Picheta <[email protected]> * refactoring and fix of tilde and caret operator, added description in readme * missed initialization * fixed typo Co-authored-by: Dominik Picheta <[email protected]>
1 parent 919e201 commit 8eca18a

File tree

2 files changed

+104
-7
lines changed

2 files changed

+104
-7
lines changed

readme.markdown

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,35 @@ requires "fizzbuzz >= 1.0"
449449
requires "https://github.com/user/pkg#5a54b5e"
450450
```
451451

452+
There are also following version selector operators available for "requires":
453+
`<`,`>`, `>=`, `<=`, `==`, `^=` and `~=`.
454+
455+
The operator specification of `^=` is similar to `^` in
456+
[npm](https://github.com/npm/node-semver#caret-ranges-123-025-004), while the
457+
`~=` operator is similar to `~=` in
458+
[python](https://www.python.org/dev/peps/pep-0440/#compatible-release):
459+
- `^=` is selecting the latest compatible version according to
460+
[semver](https://semver.npmjs.com/). Major release number changes
461+
cause incompatibility.
462+
- `~=` is selecting the latest version by increasing the last given digit
463+
to the highest version.
464+
465+
Both operators `^=` and `~=` were not available yet for Nimble 0.13.1 and
466+
earlier and would cause error messages if used there.
467+
Other more complex comparison operators that would be available in npm like
468+
`!=`, `||`, `-`, `*` and `X` are also not available in Nimble.
469+
```
470+
# Examples for selector ^= and ~=
471+
472+
requires "nim ^= 1.2.2" # nim >= 1.2.2 & < 2.0.0
473+
requires "nim ~= 1.2.2" # nim >= 1.2.2 & < 1.3.0
474+
requires "jester ^= 0.4.1" # jester >= 0.4.1 & < 0.5.0
475+
requires "jester ~= 0.4.1" # jester >= 0.4.1 & < 0.5.0
476+
requires "jester ~= 0.4" # jester >= 0.4.0 & < 1.0.0
477+
requires "choosenim ~= 0" # choosenim >= 0.0.0 & < 1.0.0
478+
requires "choosenim ^= 0" # choosenim >= 0.0.0 & < 1.0.0
479+
```
480+
452481
Nimble currently supports installation of packages from a local directory, a
453482
Git repository and a mercurial repository. The .nimble file must be present in
454483
the root of the directory or repository being installed.

src/nimblepkg/version.nim

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ type
1212
verEqLater, # >= V -- Equal or later
1313
verEqEarlier, # <= V -- Equal or earlier
1414
verIntersect, # > V & < V
15+
verTilde, # ~= V
16+
verCaret, # ^= V
1517
verEq, # V
1618
verAny, # *
1719
verSpecial # #head
@@ -23,7 +25,7 @@ type
2325
ver*: Version
2426
of verSpecial:
2527
spe*: Version
26-
of verIntersect:
28+
of verIntersect, verTilde, verCaret:
2729
verILeft, verIRight: VersionRange
2830
of verAny:
2931
nil
@@ -108,7 +110,7 @@ proc `==`*(range1: VersionRange, range2: VersionRange): bool =
108110
range1.ver == range2.ver
109111
of verSpecial:
110112
range1.spe == range2.spe
111-
of verIntersect:
113+
of verIntersect, verTilde, verCaret:
112114
range1.verILeft == range2.verILeft and range1.verIRight == range2.verIRight
113115
of verAny: true
114116

@@ -126,14 +128,45 @@ proc withinRange*(ver: Version, ran: VersionRange): bool =
126128
return ver == ran.ver
127129
of verSpecial:
128130
return ver == ran.spe
129-
of verIntersect:
131+
of verIntersect, verTilde, verCaret:
130132
return withinRange(ver, ran.verILeft) and withinRange(ver, ran.verIRight)
131133
of verAny:
132134
return true
133135

134136
proc contains*(ran: VersionRange, ver: Version): bool =
135137
return withinRange(ver, ran)
136138

139+
proc getNextIncompatibleVersion(version: string, semver: bool): string =
140+
## try to get next higher version to exclude according to semver semantic
141+
var numbers = version.split('.')
142+
let originalNumberLen = numbers.len
143+
while numbers.len < 3:
144+
numbers.add("0")
145+
var zeros = 0
146+
for n in 0 ..< 2:
147+
if numbers[n] == "0":
148+
inc(zeros)
149+
else: break
150+
var increasePosition = 0
151+
if (semver):
152+
if originalNumberLen > 1:
153+
case zeros
154+
of 0:
155+
increasePosition = 0
156+
of 1:
157+
increasePosition = 1
158+
else:
159+
increasePosition = 2
160+
else:
161+
increasePosition = max(0, originalNumberLen - 2)
162+
163+
numbers[increasePosition] = $(numbers[increasePosition].parseInt() + 1)
164+
var zeroPosition = increasePosition + 1
165+
while zeroPosition < numbers.len:
166+
numbers[zeroPosition] = "0"
167+
inc(zeroPosition)
168+
result = numbers.join(".")
169+
137170
proc makeRange*(version: string, op: string): VersionRange =
138171
if version == "":
139172
raise newException(ParseVersionError,
@@ -149,6 +182,13 @@ proc makeRange*(version: string, op: string): VersionRange =
149182
result = VersionRange(kind: verEqEarlier)
150183
of "", "==":
151184
result = VersionRange(kind: verEq)
185+
of "^=", "~=":
186+
result = VersionRange(kind: if op == "^=": verCaret else: verTilde)
187+
result.verILeft = makeRange(version, ">=")
188+
var excludedVersion = getNextIncompatibleVersion(version,
189+
semver = (op == "^="))
190+
result.verIRight = makeRange(excludedVersion, "<")
191+
return
152192
else:
153193
raise newException(ParseVersionError, "Invalid operator: " & op)
154194
result.ver = Version(version)
@@ -169,7 +209,7 @@ proc parseVersionRange*(s: string): VersionRange =
169209
var version = ""
170210
while i < s.len:
171211
case s[i]
172-
of '>', '<', '=':
212+
of '>', '<', '=', '~', '^':
173213
op.add(s[i])
174214
of '&':
175215
result = VersionRange(kind: verIntersect)
@@ -186,7 +226,6 @@ proc parseVersionRange*(s: string): VersionRange =
186226
"Having more than one `&` in a version range is pointless")
187227

188228
return
189-
190229
of '0'..'9', '.':
191230
version.add(s[i])
192231

@@ -246,6 +285,10 @@ proc `$`*(verRange: VersionRange): string =
246285
return $verRange.spe
247286
of verIntersect:
248287
return $verRange.verILeft & " & " & $verRange.verIRight
288+
of verTilde:
289+
return " ~= " & $verRange.verILeft
290+
of verCaret:
291+
return " ^= " & $verRange.verILeft
249292
of verAny:
250293
return "any version"
251294

@@ -259,7 +302,7 @@ proc getSimpleString*(verRange: VersionRange): string =
259302
result = $verRange.spe
260303
of verLater, verEarlier, verEqLater, verEqEarlier, verEq:
261304
result = $verRange.ver
262-
of verIntersect:
305+
of verIntersect, verTilde, verCaret:
263306
result = getSimpleString(verRange.verILeft) & "_" &
264307
getSimpleString(verRange.verIRight)
265308
of verAny:
@@ -316,12 +359,37 @@ when isMainModule:
316359
doAssert(newVersion("") < newVersion("0.1.0"))
317360

318361
var versions = toOrderedTable[Version, string]({
362+
newVersion("0.0.1"): "v0.0.1",
363+
newVersion("0.0.2"): "v0.0.2",
319364
newVersion("0.1.1"): "v0.1.1",
365+
newVersion("0.2.2"): "v0.2.2",
320366
newVersion("0.2.3"): "v0.2.3",
321-
newVersion("0.5"): "v0.5"
367+
newVersion("0.5"): "v0.5",
368+
newVersion("1.2"): "v1.2",
369+
newVersion("2.2.2"): "v2.2.2",
370+
newVersion("2.2.3"): "v2.2.3",
371+
newVersion("2.3.2"): "v2.3.2",
372+
newVersion("3.2"): "v3.2",
373+
newVersion("3.3.2"): "v3.3.2"
322374
})
323375
doAssert findLatest(parseVersionRange(">= 0.1 & <= 0.4"), versions) ==
324376
(newVersion("0.2.3"), "v0.2.3")
377+
doAssert findLatest(parseVersionRange("^= 0.1"), versions) ==
378+
(newVersion("0.1.1"), "v0.1.1")
379+
doAssert findLatest(parseVersionRange("^= 0"), versions) ==
380+
(newVersion("0.5"), "v0.5")
381+
doAssert findLatest(parseVersionRange("~= 2"), versions) ==
382+
(newVersion("2.3.2"), "v2.3.2")
383+
doAssert findLatest(parseVersionRange("^= 0.0.1"), versions) ==
384+
(newVersion("0.0.1"), "v0.0.1")
385+
doAssert findLatest(parseVersionRange("^= 2.2.2"), versions) ==
386+
(newVersion("2.3.2"), "v2.3.2")
387+
doAssert findLatest(parseVersionRange("^= 2.1.1.1"), versions) ==
388+
(newVersion("2.3.2"), "v2.3.2")
389+
doAssert findLatest(parseVersionRange("~= 2.2"), versions) ==
390+
(newVersion("2.3.2"), "v2.3.2")
391+
doAssert findLatest(parseVersionRange("~= 0.2.2"), versions) ==
392+
(newVersion("0.2.3"), "v0.2.3")
325393

326394
# TODO: Allow these in later versions?
327395
#doAssert newVersion("0.1-rc1") < newVersion("0.2")

0 commit comments

Comments
 (0)