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] [Package]
name = "babel" name = "babel"
version = "0.1.0" version = "0.1.0"
author = "Dominik Picheta" author = "Dominik Picheta"
category = "Distribution" description = """Jester is a web framework inspired by Sinatra."""
description = """Babel framework: Specifies a common interface for programmers
to more easily build their applications in portable way."""
[Library] [Library]
Depends = "nimrod >= 0.8.10" SkipDirs = "tests"
ExposedModules = "parser, installer, version" ; No need for .nim
;Files = "babel.babel"
;[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's folder structure
Babel stores everything that has been installed in ~/.babel on Unix systems and 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 in your $home/babel on Windows. Libraries are stored in ~/.babel/libs.
which names contain the name of the package, the folders
contain the modules and any other files that the package wished to install. ## Libraries
Applications are installed into bin/. There is also a packages/ directory which Libraries should contain a ``ProjectName.nim`` file, this file will be copied
contains all the packages' .babel files. 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 ## Contribution
If you would like to help, feel free to fork and make any additions you see If you would like to help, feel free to fork and make any additions you see
fit and then send a pull request. fit and then send a pull request.
If you have any questions about the project you can ask me directly on github, 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 ask on the nimrod [forum](http://forum.nimrod-code.org), or ask on Freenode in
the #nimrod channel. 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...")