Re-write, mostly works.

This commit is contained in:
Dominik Picheta 2012-12-02 01:24:47 +00:00
commit c6396a3bc3
7 changed files with 285 additions and 612 deletions

View file

@ -1,17 +1,10 @@
; Babel library
; Example babel file
[Package]
name = "babel"
version = "0.1.0"
author = "Dominik Picheta"
category = "Distribution"
description = """Babel framework: Specifies a common interface for programmers
to more easily build their applications in portable way."""
description = """Jester is a web framework inspired by Sinatra."""
[Library]
Depends = "nimrod >= 0.8.10"
ExposedModules = "parser, installer, version" ; No need for .nim
;Files = "babel.babel"
SkipDirs = "tests"
;[Exe]
;Depends = "nimrod >= 0.8.11"
;Exe = "babel"

201
babel.nim Normal file
View file

@ -0,0 +1,201 @@
import httpclient, parseopt, os, strutils, osproc
import packageinfo
type
TActionType = enum
ActionNil, ActionUpdate, ActionInstall
TAction = object
case typ: TActionType
of ActionNil: nil
of ActionUpdate:
optionalURL: string # Overrides default package list.
of ActionInstall:
optionalName: seq[string] # When this is @[], installs package from current dir.
const
help = """
Usage: babel COMMAND
Commands:
install Installs a list of packages.
update Updates package list. A package list URL can be optionally specificed.
"""
babelVersion = "0.1.0"
defaultPackageURL = "https://github.com/nimrod-code/packages/raw/master/packages.json"
proc writeHelp() =
echo(help)
quit(QuitSuccess)
proc writeVersion() =
echo(babelVersion)
quit(QuitSuccess)
proc parseCmdLine(): TAction =
result.typ = ActionNil
for kind, key, val in getOpt():
case kind
of cmdArgument:
if result.typ == ActionNil:
case key
of "install":
result.typ = ActionInstall
result.optionalName = @[]
of "update":
result.typ = ActionUpdate
result.optionalURL = ""
else: writeHelp()
else:
case result.typ
of ActionNil:
assert false
of ActionInstall:
result.optionalName.add(key)
of ActionUpdate:
result.optionalURL = key
of cmdLongOption, cmdShortOption:
case key
of "help", "h": writeHelp()
of "version", "v": writeVersion()
of cmdEnd: assert(false) # cannot happen
if result.typ == ActionNil:
writeHelp()
proc update(url: string = defaultPackageURL) =
echo("Downloading package list from " & url)
downloadFile(url, getHomeDir() / ".babel" / "packages.json")
echo("Done.")
proc findBabelFile(dir: string): string =
for kind, path in walkDir(dir):
if kind == pcFile and path.splitFile.ext == ".babel":
return path
return ""
proc copyFileD(fro, to: string) =
echo(fro, " -> ", to)
copyFile(fro, to)
proc getBabelDir: string = return getHomeDir() / ".babel"
proc getLibsDir: string = return getBabelDir() / "libs"
proc samePaths(p1, p2: string): bool =
## Normalizes path (by adding a trailing slash) and compares.
let cp1 = if not p1.endsWith("/"): p1 & "/" else: p1
let cp2 = if not p2.endsWith("/"): p2 & "/" else: p2
return cmpPaths(cp1, cp2) == 0
proc changeRoot(origRoot, newRoot, path: string): string =
## origRoot: /home/dom/
## newRoot: /home/test/
## path: /home/dom/bar/blah/2/foo.txt
## Return value -> /home/test/bar/blah/2/foo.txt
if path.startsWith(origRoot):
return newRoot / path[origRoot.len .. -1]
else:
raise newException(EInvalidValue,
"Cannot change root of path: Path does not begin with original root.")
proc copyFilesRec(origDir, currentDir: string, pkgInfo: TPackageInfo) =
for kind, file in walkDir(currentDir):
if kind == pcDir:
var skip = false
for ignoreDir in pkgInfo.skipDirs:
if samePaths(file, origDir / ignoreDir):
skip = true
break
let thisDir = splitPath(file).tail
assert thisDir != ""
if thisDir[0] == '.': skip = true
if thisDir == "nimcache": skip = true
if skip: continue
# Create the dir.
createDir(changeRoot(origDir, getLibsDir() / pkgInfo.name, file))
copyFilesRec(origDir, file, pkgInfo)
else:
var skip = false
if file.splitFile().name[0] == '.': skip = true
if file.splitFile().ext == "": skip = true
for ignoreFile in pkgInfo.skipFiles:
if samePaths(file, origDir / ignoreFile):
skip = true
break
if not skip:
copyFileD(file, changeRoot(origDir, getLibsDir() / pkgInfo.name, file))
proc installFromDir(dir: string) =
let babelFile = findBabelFile(dir)
if babelFile == "":
quit("Specified directory does not contain a .babel file.", QuitFailure)
var pkgInfo = readPackageInfo(babelFile)
if not existsDir(getLibsDir() / pkgInfo.name):
createDir(getLibsDir() / pkgInfo.name)
else: echo("Warning: Package already exists.")
# Find main project file.
let nimFile = dir / pkgInfo.name.addFileExt("nim")
let nimrodFile = dir / pkgInfo.name.addFileExt("nimrod")
if existsFile(nimFile) or existsFile(nimrodFile):
if existsFile(nimFile):
copyFileD(nimFile, changeRoot(dir, getLibsDir(), nimFile))
pkgInfo.skipFiles.add(changeRoot(dir, "", nimFile))
elif existsFile(nimrodFile):
copyFileD(nimrodFile, changeRoot(dir, getLibsDir(), nimrodFile))
pkgInfo.skipFiles.add(changeRoot(dir, "", nimrodFile))
else:
quit("Could not find main package file.", QuitFailure)
copyFilesRec(dir, dir, pkgInfo)
proc install(packages: seq[String]) =
if packages == @[]:
installFromDir(getCurrentDir())
else:
if not existsFile(getBabelDir() / "packages.json"):
quit("Please run babel update.", QuitFailure)
for p in packages:
var pkg: TPackage
if getPackage(p, getBabelDir() / "packages.json", pkg):
let downloadDir = (getTempDir() / "babel" / pkg.name)
case pkg.downloadMethod
of "git":
echo("Executing git...")
removeDir(downloadDir)
let exitCode = execCmd("git clone " & pkg.url & " " & downloadDir)
if exitCode != QuitSuccess:
quit("Execution of git failed.", QuitFailure)
else: quit("Unknown download method: " & pkg.downloadMethod, QuitFailure)
installFromDir(downloadDir)
else:
quit("Package not found.", QuitFailure)
proc doAction(action: TAction) =
case action.typ
of ActionUpdate:
if action.optionalURL != "":
update(action.optionalURL)
else:
update()
of ActionInstall:
install(action.optionalName)
of ActionNil:
assert false
when isMainModule:
if not existsDir(getHomeDir() / ".babel"):
createDir(getHomeDir() / ".babel")
if not existsDir(getHomeDir() / ".babel" / "libs"):
createDir(getHomeDir() / ".babel" / "libs")
parseCmdLine().doAction()

View file

@ -1,230 +0,0 @@
import parser, version, osproc, strutils, re, os, parseutils, streams
type
EInstall = object of EBase
TDepend = tuple[name: String, verRange: PVersionRange]
var debug* = 1 ## 0 = no messages, 1 - 3 = less to more verbose respectively.
proc echoD*(s: string, verbosity: int = 1, nl: bool = True) =
if debug >= verbosity:
stdout.write(s & (if nl: "\n" else: ""))
proc getBabelDir(): string =
when defined(windows):
result = getHomeDir() / "babel"
else:
result = getHomeDir() / ".babel"
proc getNimVersion(cmd: string = "nimrod"): String =
var output = execProcess(cmd & " -v")
var line = splitLines(output)[0]
# Thanks Araq :)
var i = 0
var nimrodVersion = ""
i = skipIgnoreCase(line, "Nimrod Compiler Version ")
if i <= 0: raise newException(EInstall, "Cannot detect Nimrod's version")
i = parseToken(line, nimrodVersion, {'.', '0'..'9'}, i)
if nimrodVersion.len == 0:
raise newException(EInstall, "Cannot detect Nimrod's version")
return nimrodVersion
proc compile*(file: string, flags: string = "") =
var args: string = flags & "c " & file
echoD("Compiling " & file & "...")
var code = execShellCmd(findExe("nimrod") & " " & args)
if code != quitSuccess:
raise newException(EInstall, "Compilation failed: Nimrod returned exit code " &
$code)
proc dependExists(name: string, verRange: PVersionRange): Bool =
if name == "nimrod":
var nimVer = getNimVersion()
if not withinRange(newVersion(nimVer), verRange):
raise newException(EInstall, "Nimrod version doesn't satisfy dependency: " &
nimVer & " " & $verRange)
else: return True
else:
for kind, path in walkDir(getBabelDir() / "packages"):
if kind == pcFile:
var file = path.extractFilename()
if file.startswith(name & "-"):
if verRange.kind != verAny:
var ver = ""
# This will have a dot at the end, it doesn't cause trouble though.
var ret = file.parseToken(ver, digits + {'.'}, name.len() + 1)
if ret != 0:
if withinRange(newVersion(ver), verRange):
return True
else:
raise newException(EInstall, "Invalid .babel file: " & file)
else: return True
return False
proc verifyDepends(proj: TProject): seq[TDepend] =
result = @[]
for nameStr, verStr in items(proj.depends):
echoD(" " & nameStr & " " & verStr & "...", 2, False)
var verRange: PVersionRange
if verStr == "":
new(verRange)
verRange.kind = verAny
else:
verRange = parseVersionRange(verStr)
if not dependExists(nameStr, verRange):
result.add((nameStr, verRange))
echoD(" FAIL", 2)
else:
echoD(" OK", 2)
proc createDirDebug(dir: string) =
# Have to check, so that it doesn't echo...
if not existsDir(dir):
echoD("Creating directory " & dir & "...", 3, False)
createDir(dir)
echoD(" Done!", 3)
proc createDirs(dirs: seq[string]) =
for i in items(dirs):
createDirDebug(i)
proc copyFileDebug(file, copyTo: string) =
echoD("Copying " & file & " to " & copyTo & "...", 3, False)
copyFile(file, copyTo)
echoD(" Done!", 3)
proc moveFileDebug(file, moveTo: string) =
echoD("Moving " & file & " to " & moveTo & "...", 3, False)
moveFile(file, moveTo)
echoD(" Done!", 3)
proc copyFiles(proj: TProject) =
# This will create a $home/.babel and lib/ or bin/. It will also copy all the
# files listed in proj.modules and proj.files and the .babel file.
var babelDir = getBabelDir()
var dirs = @[babelDir, babelDir / "lib", babelDir / "bin", babelDir / "packages"]
createDirs(dirs)
if proj.library:
# TODO: How will we handle multiple versions?
var projDir = babelDir / "lib" / proj.name # $babel/lib/name
createDirDebug(projDir)
# Copy the files
for i in items(proj.modules):
var file = proj.confDir / i.addFileExt("nim")
var copyTo = projDir / i.addFileExt("nim")
copyFileDebug(file, copyTo)
if proj.files.len > 0:
for i in items(proj.files):
var file = proj.confDir / i
var copyTo = projDir / i
copyFileDebug(file, copyTo)
elif proj.executable:
var exeSrcFile = proj.confDir / proj.exeFile.addFileExt("nim")
# Compile
compile(exeSrcFile)
var exeCmpFile = exeSrcFile.changeFileExt(ExeExt)
var copyTo = babelDir / "bin" / proj.exeFile.addFileExt(ExeExt)
copyFileDebug(exeCmpFile, copyTo)
# Copy the .babel file into the packages folder.
var babelFile = proj.confDir / proj.name.addFileExt("babel")
# addFileExt fails in this situation. Although it's fine without .babel
var copyTo = babelDir / "packages" /
(proj.name & "-" & proj.version)
copyFileDebug(babelFile, copyTo)
proc upgrade(proj: TProject) =
## This proc moves the files in lib/package into lib/package/lastVersion
## It then copies the new version into lib/package.
var babelDir = getBabelDir()
# Find the config for the latest (currently installed) package version in
# packages/
var latestVersion = "0"
var path = ""
for kind, confPath in walkDir(babelDir / "packages"):
if kind == pcFile:
var file = confPath.extractFilename()
if file.startsWith(proj.name & "-"):
var ver = ""
# This will have a dot at the end, it doesn't cause trouble though.
discard file.parseToken(ver, digits + {'.'}, proj.name.len() + 1)
if ver > latestVersion:
latestVersion = ver
path = confPath
assert(path != "")
if proj.library:
echoD("Reading " & path & "...", 3)
var latestConf = parseBabel(path)
var newVerDir = babelDir / "lib" / latestConf.name / latestConf.version
createDirDebug(newVerDir)
# Move the files
for kind, path in walkDir(babelDir / "lib" / latestConf.name):
if kind == pcFile:
moveFileDebug(path, newVerDir / path.extractFilename())
# Install the new version
copyFiles(proj)
proc install*(name: string, filename: string = "") =
## Install package by the name of ``name``, filename specifies where to look for it
## if left as "", the current working directory will be assumed.
# TODO: Add a `debug` variable? If true the status messages get echo-ed,
# vice-versa if false?
var babelFile: TProject
var path = ""
if filename == "":
path = name.addFileExt("babel")
else:
path = filename / name.addFileExt("babel")
echoD("Reading " & path & "...", 3)
babelFile = parseBabel(path)
var ret = babelFile.verify()
if ret != "":
raise newException(EInstall, "Verifying the .babel file failed: " & ret)
var upgrade = False # Specifies whether to upgrade
# Check whether this package is already installed
if dependExists(babelFile.name, newVREq(babelFile.version)):
raise newException(EInstall,
"This package is already installed!")
elif dependExists(babelFile.name, newVREarlier(babelFile.version)):
upgrade = True
if babelFile.depends.len == 1:
echoD("Verifying 1 dependency...")
else:
echoD("Verifying " & $babelFile.depends.len() & " dependencies...")
var dependsNeeded = babelFile.verifyDepends()
if dependsNeeded.len() > 0:
raise newException(EInstall, "TODO: Download & Install dependencies.")
else:
echoD("All dependencies verified!")
if not upgrade:
echoD("Installing " & babelFile.name & "-" & babelFile.version & "...")
babelFile.copyFiles()
else:
echoD("Upgrading " & babelFile.name & " to version " &
babelFile.version & "...")
babelFile.upgrade()
echoD("Package " & babelFile.name & " successfully installed.")
when isMainModule:
install(paramStr(1))

71
packageinfo.nim Normal file
View file

@ -0,0 +1,71 @@
import parsecfg, json, streams, strutils
type
TPackageInfo* = object
name*: string
version*: string
author*: string
description*: string
library*: bool
skipDirs*: seq[string]
skipFiles*: seq[string]
TPackage* = object
name*: string
url*: string
downloadMethod*: string
tags*: seq[string]
description*: string
proc readPackageInfo*(path: string): TPackageInfo =
result.skipDirs = @[]
result.skipFiles = @[]
var fs = newFileStream(path, fmRead)
if fs != nil:
var p: TCfgParser
open(p, fs, path)
var currentSection = ""
while true:
var ev = next(p)
case ev.kind
of cfgEof:
break
of cfgSectionStart:
currentSection = ev.section
of cfgKeyValuePair:
case currentSection.normalize
of "package":
case ev.key.normalize
of "name": result.name = ev.value
of "version": result.version = ev.value
of "author": result.author = ev.value
of "description": result.description = ev.value
of "library":
case ev.key.normalize
of "skipdirs":
result.skipDirs.add(ev.value.split(','))
of "skipfiles":
result.skipFiles.add(ev.value.split(','))
else: quit("Invalid section: " & currentSection, QuitFailure)
of cfgOption: quit("Invalid package info, should not contain --" & ev.value, QuitFailure)
of cfgError:
echo(ev.msg)
close(p)
else:
quit("Cannot open package info: " & path, QuitFailure)
proc getPackage*(pkg: string, packagesPath: string, resPkg: var TPackage): bool =
let packages = parseFile(packagesPath)
for p in packages:
if p["name"].str != pkg: continue
resPkg.name = pkg
resPkg.url = p["url"].str
resPkg.downloadMethod = p["method"].str
resPkg.tags = @[]
for t in p["tags"]:
resPkg.tags.add(t.str)
resPkg.description = p["description"].str
return true
return false

View file

@ -1,193 +0,0 @@
import parsecfg, streams, strutils, os
type
TProject* = object
name*: String # Req
version*: String # Req
author*: String # Req
category*: String # Req
desc*: String # Req
license*: String
homepage*: String
library*: bool
depends*: seq[tuple[name, vRange: string]] # Dependencies
modules*: seq[string] # ExtraModules
files*: seq[string] # files
executable*: bool
exeFile*: string
unknownFields*: seq[string] # TODO:
confDir*: string # Directory of the babel file, "" if current work dir.
EParseErr* = object of EInvalidValue
proc initProj(): TProject =
result.name = ""
result.version = ""
result.author = ""
result.category = ""
result.desc = ""
result.license = ""
result.homepage = ""
result.library = False
result.executable = False
result.depends = @[]
result.modules = @[]
result.files = @[]
result.exeFile = ""
result.unknownFields = @[]
result.confDir = ""
proc parseList(s: string): seq[string] =
result = @[]
var many = s.split({',', ';'})
for i in items(many):
result.add(i.strip())
proc skipUntil(s, token: string, o: var string): int =
## Captures each char until `token` is found, sets `o` to the captured string,
## and returns the number of characters captured.
var i = 0
while True:
case s[i]
of '\0':
break
else:
var found = False
for t in 0..len(token)-1:
if s[i+t] == token[t]:
found = True
else:
found = False
break
if found:
break
inc(i)
o = copy(s, 0, i-1)
return i
proc parseDepends(s: string): seq[tuple[name, vRange: string]] =
result = @[]
var many = s.split({',', ';'})
for i in items(many):
var stripped = i.strip()
var name = ""
var index = skipUntil(stripped, " ", name)
var vRange = stripped.copy(index)
result.add((name.strip(), vRange.strip()))
proc parseErr(p: TCfgParser, msg: string) =
raise newException(EParseErr, "(" & $p.getLine() & ", " &
$p.getColumn() & ") " & msg)
proc parseBabel*(file: string): TProject =
result = initProj()
result.confDir = splitFile(file).dir
var f = newFileStream(file, fmRead)
if f != nil:
var p: TCfgParser
open(p, f, file)
var section: String = ""
while true:
var e = next(p)
case e.kind
of cfgEof:
break
of cfgKeyValuePair:
case section
of "package":
case normalize(e.key):
of "name":
result.name = e.value
of "version":
result.version = e.value
of "author":
result.author = e.value
of "category":
result.category = e.value
of "description":
result.desc = e.value
of "homepage":
result.homepage = e.value
of "license":
result.license = e.value
else:
p.parseErr("Unknown key: " & e.key)
of "library":
case normalize(e.key)
of "depends":
result.depends = e.value.parseDepends()
of "files":
result.files = e.value.parseList()
of "exposedmodules":
result.modules = e.value.parseList()
else:
p.parseErr("Unknown key: " & e.key)
of "exe":
case normalize(e.key)
of "depends":
result.depends = e.value.parseDepends()
of "files":
result.files = e.value.parseList()
of "exe":
result.exeFile = e.value
else:
p.parseErr("Unknown key: " & e.key)
else:
p.parseErr("Unknown section: " & section)
of cfgSectionStart:
section = normalize(e.section)
case normalize(e.section):
of "library":
result.library = True
of "exe":
result.executable = True
of "package":
nil
else:
p.parseErr("Unknown section: " & section)
of cfgError:
p.parseErr(e.msg)
of cfgOption:
p.parseErr("Unknown option: " & e.key)
close(p)
else:
raise newException(EIO, "Cannot open " & file)
proc isEmpty(s: string): Bool = return s == ""
proc verify*(proj: TProject): string =
## Checks whether the required fields have been specified.
if isEmpty(proj.name) or isEmpty(proj.version) or isEmpty(proj.author) or
isEmpty(proj.category) or isEmpty(proj.desc):
return "Missing required fields."
elif proj.library == false and proj.executable == false:
return "Either a valid Library needs to be specified or a valid Bin."
elif proj.library == true and proj.modules.len() == 0:
return "A valid library needs at least one ExposedModule listed."
# TODO: Rules for Bin.
return ""
when isMainModule:
for i in items(parseList("test, asdasd >sda; jsj, kk >>, sd")):
echo(i)
var project = parseBabel("babel.babel")
echo project.library
echo()
var o = ""
echo skipUntil("nimrod >= 0.8.10", " ", o)
echo(o)

View file

@ -3,15 +3,18 @@ Babel is a work in progress package manager for Nimrod.
## Babel's folder structure
Babel stores everything that has been installed in ~/.babel on Unix systems and
in your $home/babel on Windows. Libraries are installed in lib/ in folders
which names contain the name of the package, the folders
contain the modules and any other files that the package wished to install.
Applications are installed into bin/. There is also a packages/ directory which
contains all the packages' .babel files.
in your $home/babel on Windows. Libraries are stored in ~/.babel/libs.
## Libraries
Libraries should contain a ``ProjectName.nim`` file, this file will be copied
to ~/.babel/libs/ProjectName.nim allowing anyone to import it by doing
``import ProjectName``. Any private files should be placed, by convention, in
a ``private`` folder, these are files which the user of your library should not
be using. Every other file and folder will be copied to ~/.babel/libs/ProjectName/.
## Contribution
If you would like to help, feel free to fork and make any additions you see
fit and then send a pull request.
If you have any questions about the project you can ask me directly on github,
ask on the nimrod [forum](http://force7.de/heimdall), or ask on Freenode in
the #nimrod channel.
ask on the nimrod [forum](http://forum.nimrod-code.org), or ask on Freenode in
the #nimrod channel.

View file

@ -1,172 +0,0 @@
## Module for handling versions and version ranges such as ``>= 1.0 & <= 1.5``
import strutils
type
TVersion* = distinct string
TVersionRangeEnum* = enum
verLater, # > V
verEarlier, # < V
verEqLater, # >= V -- Equal or later
verEqEarlier, # <= V -- Equal or earlier
verIntersect, # > V & < V
verEq, # V
verAny # *
PVersionRange* = ref TVersionRange
TVersionRange* = object
case kind*: TVersionRangeEnum
of verLater, verEarlier, verEqLater, verEqEarlier, verEq:
ver*: TVersion
of verIntersect:
verILeft, verIRight: PVersionRange
of verAny:
nil
EParseVersion = object of EInvalidValue
proc newVersion*(ver: string): TVersion = return TVersion(ver)
proc `$`*(ver: TVersion): String {.borrow.}
proc `<`*(ver: TVersion, ver2: TVersion): Bool =
var sVer = string(ver).split('.')
var sVer2 = string(ver2).split('.')
for i in 0..max(sVer.len, sVer2.len)-1:
if i > sVer.len-1:
return True
elif i > sVer2.len-1:
return False
var sVerI = parseInt(sVer[i])
var sVerI2 = parseInt(sVer2[i])
if sVerI < sVerI2:
return True
elif sVerI == sVerI2:
nil
else:
return False
proc `==`*(ver: TVersion, ver2: TVersion): Bool {.borrow.}
proc `<=`*(ver: TVersion, ver2: TVersion): Bool =
return (ver == ver2) or (ver < ver2)
proc withinRange*(ver: TVersion, ran: PVersionRange): Bool =
case ran.kind
of verLater:
return ver > ran.ver
of verEarlier:
return ver < ran.ver
of verEqLater:
return ver >= ran.ver
of verEqEarlier:
return ver <= ran.ver
of verEq:
return ver == ran.ver
of verIntersect:
return withinRange(ver, ran.verILeft) and withinRange(ver, ran.verIRight)
of verAny:
return True
proc makeRange*(version: string, op: string): PVersionRange =
new(result)
case op
of ">":
result.kind = verLater
of "<":
result.kind = verEarlier
of ">=":
result.kind = verEqLater
of "<=":
result.kind = verEqEarlier
else:
raise newException(EParseVersion, "Invalid operator: " & op)
result.ver = TVersion(version)
proc parseVersionRange*(s: string): PVersionRange =
# >= 1.5 & <= 1.8
new(result)
var i = 0
var op = ""
var version = ""
while True:
case s[i]
of '>', '<', '=':
op.add(s[i])
of '&':
result.kind = verIntersect
result.verILeft = makeRange(version, op)
# Parse everything after &
# Recursion <3
result.verIRight = parseVersionRange(copy(s, i + 1))
# Disallow more than one verIntersect. It's pointless and could lead to
# major unknown mistakes.
if result.verIRight.kind == verIntersect:
raise newException(EParseVersion,
"Having more than one `&` in a version range is pointless")
break
of '0'..'9', '.':
version.add(s[i])
of '\0':
result = makeRange(version, op)
break
of ' ':
nil # Ignore whitespace
else:
raise newException(EParseVersion, "Unexpected char in version range: " & s[i])
inc(i)
proc `$`*(verRange: PVersionRange): String =
case verRange.kind
of verLater:
result = "> "
of verEarlier:
result = "< "
of verEqLater:
result = ">= "
of verEqEarlier:
result = "<= "
of verEq:
result = ""
of verIntersect:
result = $verRange.verILeft & " & " & $verRange.verIRight
of verAny:
return "Any"
result.add(string(verRange.ver))
proc newVRAny*(): PVersionRange =
new(result)
result.kind = verAny
proc newVREarlier*(ver: String): PVersionRange =
new(result)
result.kind = verEarlier
result.ver = newVersion(ver)
proc newVREq*(ver: string): PVersionRange =
new(result)
result.kind = verEq
result.ver = newVersion(ver)
when isMainModule:
assert(newVersion("1.0") < newVersion("1.4"))
assert(newVersion("1.0.1") > newVersion("1.0"))
assert(newVersion("1.0.6") <= newVersion("1.0.6"))
var inter1 = parseVersionRange(">= 1.0 & <= 1.5 & > 1.1 ")
assert(not withinRange(newVersion("1.5.1"), inter1))
assert(withinRange(newVersion("1.0.2.3.4.5.6.7.8.9.10.11.12"), inter1))
assert(newVersion("1") == newVersion("1"))
echo("Everything works! Assuming that you didn't compile without assertions...")