Merge pull request #81 from lou15b/master

Update to nim 0.10.2 coding convention
This commit is contained in:
Dominik Picheta 2015-01-04 11:44:49 +00:00
commit 4130771919
10 changed files with 398 additions and 350 deletions

View file

@ -7,40 +7,41 @@ import httpclient, parseopt, os, strutils, osproc, pegs, tables, parseutils,
from sequtils import toSeq from sequtils import toSeq
import nimblepkg/packageinfo, nimblepkg/version, nimblepkg/tools, import nimblepkg/packageinfo, nimblepkg/version, nimblepkg/tools,
nimblepkg/download, nimblepkg/config, nimblepkg/compat nimblepkg/download, nimblepkg/config, nimblepkg/nimbletypes
when not defined(windows): when not defined(windows):
from posix import getpid from posix import getpid
type type
TOptions = object Options = object
forcePrompts: TForcePrompt forcePrompts: ForcePrompt
queryVersions: bool queryVersions: bool
queryInstalled: bool queryInstalled: bool
action: TAction action: Action
config: TConfig config: Config
nimbleData: PJsonNode ## Nimbledata.json nimbleData: JsonNode ## Nimbledata.json
TActionType = enum ActionType = enum
ActionNil, ActionUpdate, ActionInit, ActionInstall, ActionSearch, actionNil, actionUpdate, actionInit, actionInstall, actionSearch,
ActionList, ActionBuild, ActionPath, ActionUninstall actionList, actionBuild, actionPath, actionUninstall
TAction = object Action = object
case typ: TActionType case typ: ActionType
of ActionNil, ActionList, ActionBuild: nil of actionNil, actionList, actionBuild: nil
of ActionUpdate: of actionUpdate:
optionalURL: string # Overrides default package list. optionalURL: string # Overrides default package list.
of ActionInstall, ActionPath, ActionUninstall: of actionInstall, actionPath, actionUninstall:
optionalName: seq[string] # \ optionalName: seq[string] # \
# When this is @[], installs package from current dir. # When this is @[], installs package from current dir.
packages: seq[TPkgTuple] # Optional only for ActionInstall. packages: seq[PkgTuple] # Optional only for actionInstall.
of ActionSearch: of actionSearch:
search: seq[string] # Search string. search: seq[string] # Search string.
of ActionInit: of actionInit:
projName: string projName: string
else:nil
TForcePrompt = enum ForcePrompt = enum
DontForcePrompt, ForcePromptYes, ForcePromptNo dontForcePrompt, forcePromptYes, forcePromptNo
const const
help = """ help = """
@ -72,38 +73,40 @@ For more information read the Github readme:
https://github.com/nim-lang/nimble#readme https://github.com/nim-lang/nimble#readme
""" """
nimbleVersion = "0.6.0" nimbleVersion = "0.6.0"
defaultPackageURL = "https://github.com/nim-lang/packages/raw/master/packages.json" defaultPackageURL =
"https://github.com/nim-lang/packages/raw/master/packages.json"
proc writeHelp() = proc writeHelp() =
echo(help) echo(help)
quit(QuitSuccess) quit(QuitSuccess)
proc writeVersion() = proc writeVersion() =
echo("nimble v$# compiled at $# $#" % [nimbleVersion, CompileDate, CompileTime]) echo("nimble v$# compiled at $# $#" %
[nimbleVersion, CompileDate, CompileTime])
quit(QuitSuccess) quit(QuitSuccess)
proc getNimbleDir(options: TOptions): string = proc getNimbleDir(options: Options): string =
options.config.nimbleDir options.config.nimbleDir
proc getPkgsDir(options: TOptions): string = proc getPkgsDir(options: Options): string =
options.config.nimbleDir / "pkgs" options.config.nimbleDir / "pkgs"
proc getBinDir(options: TOptions): string = proc getBinDir(options: Options): string =
options.config.nimbleDir / "bin" options.config.nimbleDir / "bin"
proc prompt(options: TOptions, question: string): bool = proc prompt(options: Options, question: string): bool =
## Asks an interactive question and returns the result. ## Asks an interactive question and returns the result.
## ##
## The proc will return immediately without asking the user if the global ## The proc will return immediately without asking the user if the global
## forcePrompts has a value different than DontForcePrompt. ## forcePrompts has a value different than dontForcePrompt.
case options.forcePrompts case options.forcePrompts
of ForcePromptYes: of forcePromptYes:
echo(question & " -> [forced yes]") echo(question & " -> [forced yes]")
return true return true
of ForcePromptNo: of forcePromptNo:
echo(question & " -> [forced no]") echo(question & " -> [forced no]")
return false return false
of DontForcePrompt: of dontForcePrompt:
echo(question & " [y/N]") echo(question & " [y/N]")
let yn = stdin.readLine() let yn = stdin.readLine()
case yn.normalize case yn.normalize
@ -114,81 +117,88 @@ proc prompt(options: TOptions, question: string): bool =
else: else:
return false return false
proc renameBabelToNimble(options: TOptions) {.deprecated.} = proc renameBabelToNimble(options: Options) {.deprecated.} =
let babelDir = getHomeDir() / ".babel" let babelDir = getHomeDir() / ".babel"
let nimbleDir = getHomeDir() / ".nimble" let nimbleDir = getHomeDir() / ".nimble"
if dirExists(babelDir): if dirExists(babelDir):
if options.prompt("Found deprecated babel package directory, would you like to rename it to nimble?"): if options.prompt("Found deprecated babel package directory, would you " &
"like to rename it to nimble?"):
copyDir(babelDir, nimbleDir) copyDir(babelDir, nimbleDir)
copyFile(babelDir / "babeldata.json", nimbleDir / "nimbledata.json") copyFile(babelDir / "babeldata.json", nimbleDir / "nimbledata.json")
removeDir(babelDir) removeDir(babelDir)
removeFile(nimbleDir / "babeldata.json") removeFile(nimbleDir / "babeldata.json")
proc parseCmdLine(): TOptions = proc parseCmdLine(): Options =
result.action.typ = ActionNil result.action.typ = actionNil
result.config = parseConfig() result.config = parseConfig()
for kind, key, val in getOpt(): for kind, key, val in getOpt():
case kind case kind
of cmdArgument: of cmdArgument:
if result.action.typ == ActionNil: if result.action.typ == actionNil:
case key case key
of "install", "path": of "install", "path":
case key case key
of "install": of "install":
result.action.typ = ActionInstall result.action.typ = actionInstall
of "path": of "path":
result.action.typ = ActionPath result.action.typ = actionPath
else:
discard
result.action.packages = @[] result.action.packages = @[]
of "build": of "build":
result.action.typ = ActionBuild result.action.typ = actionBuild
of "init": of "init":
result.action.typ = ActionInit result.action.typ = actionInit
result.action.projName = "" result.action.projName = ""
of "update": of "update":
result.action.typ = ActionUpdate result.action.typ = actionUpdate
result.action.optionalURL = "" result.action.optionalURL = ""
of "search": of "search":
result.action.typ = ActionSearch result.action.typ = actionSearch
result.action.search = @[] result.action.search = @[]
of "list": of "list":
result.action.typ = ActionList result.action.typ = actionList
of "uninstall", "remove", "delete", "del", "rm": of "uninstall", "remove", "delete", "del", "rm":
result.action.typ = ActionUninstall result.action.typ = actionUninstall
result.action.packages = @[] result.action.packages = @[]
else: writeHelp() else: writeHelp()
else: else:
case result.action.typ case result.action.typ
of ActionNil: of actionNil:
assert false assert false
of ActionInstall, ActionPath, ActionUninstall: of actionInstall, actionPath, actionUninstall:
# Parse pkg@verRange # Parse pkg@verRange
if '@' in key: if '@' in key:
let i = find(key, '@') let i = find(key, '@')
let pkgTup = (key[0 .. i-1], key[i+1 .. -1].parseVersionRange()) let pkgTup = (key[0 .. i-1], key[i+1 .. -1].parseVersionRange())
result.action.packages.add(pkgTup) result.action.packages.add(pkgTup)
else: else:
result.action.packages.add((key, PVersionRange(kind: verAny))) result.action.packages.add((key, VersionRange(kind: verAny)))
of ActionUpdate: of actionUpdate:
result.action.optionalURL = key result.action.optionalURL = key
of ActionSearch: of actionSearch:
result.action.search.add(key) result.action.search.add(key)
of ActionInit: of actionInit:
if result.action.projName != "": if result.action.projName != "":
raise newException(ENimble, "Can only initialize one package at a time.") raise newException(NimbleError,
"Can only initialize one package at a time.")
result.action.projName = key result.action.projName = key
of ActionList, ActionBuild: of actionList, actionBuild:
writeHelp() writeHelp()
else:
discard
of cmdLongOption, cmdShortOption: of cmdLongOption, cmdShortOption:
case key case key
of "help", "h": writeHelp() of "help", "h": writeHelp()
of "version", "v": writeVersion() of "version", "v": writeVersion()
of "accept", "y": result.forcePrompts = ForcePromptYes of "accept", "y": result.forcePrompts = forcePromptYes
of "reject", "n": result.forcePrompts = ForcePromptNo of "reject", "n": result.forcePrompts = forcePromptNo
of "ver": result.queryVersions = true of "ver": result.queryVersions = true
of "installed", "i": result.queryInstalled = true of "installed", "i": result.queryInstalled = true
else: discard
of cmdEnd: assert(false) # cannot happen of cmdEnd: assert(false) # cannot happen
if result.action.typ == ActionNil: if result.action.typ == actionNil:
writeHelp() writeHelp()
# TODO: Remove this after a couple of versions. # TODO: Remove this after a couple of versions.
@ -202,18 +212,18 @@ proc parseCmdLine(): TOptions =
try: try:
result.nimbleData = parseFile(nimbledataFilename) result.nimbleData = parseFile(nimbledataFilename)
except: except:
raise newException(ENimble, "Couldn't parse nimbledata.json file " & raise newException(NimbleError, "Couldn't parse nimbledata.json file " &
"located at " & nimbledataFilename) "located at " & nimbledataFilename)
else: else:
result.nimbleData = %{"reverseDeps": newJObject()} result.nimbleData = %{"reverseDeps": newJObject()}
proc update(options: TOptions) = proc update(options: Options) =
## Downloads the package list from the specified URL. ## Downloads the package list from the specified URL.
## ##
## If the download is successful, the global didUpdatePackages is set to ## If the download is successful, the global didUpdatePackages is set to
## true. Otherwise an exception is raised on error. ## true. Otherwise an exception is raised on error.
let url = let url =
if options.action.typ == ActionUpdate and options.action.optionalURL != "": if options.action.typ == actionUpdate and options.action.optionalURL != "":
options.action.optionalURL options.action.optionalURL
else: else:
defaultPackageURL defaultPackageURL
@ -221,14 +231,14 @@ proc update(options: TOptions) =
downloadFile(url, options.getNimbleDir() / "packages.json") downloadFile(url, options.getNimbleDir() / "packages.json")
echo("Done.") echo("Done.")
proc checkInstallFile(pkgInfo: TPackageInfo, proc checkInstallFile(pkgInfo: PackageInfo,
origDir, file: string): bool = origDir, file: string): bool =
## Checks whether ``file`` should be installed. ## Checks whether ``file`` should be installed.
## ``True`` means file should be skipped. ## ``True`` means file should be skipped.
for ignoreFile in pkgInfo.skipFiles: for ignoreFile in pkgInfo.skipFiles:
if ignoreFile.endswith("nimble"): if ignoreFile.endswith("nimble"):
raise newException(ENimble, ignoreFile & " must be installed.") raise newException(NimbleError, ignoreFile & " must be installed.")
if samePaths(file, origDir / ignoreFile): if samePaths(file, origDir / ignoreFile):
result = true result = true
break break
@ -240,7 +250,7 @@ proc checkInstallFile(pkgInfo: TPackageInfo,
if file.splitFile().name[0] == '.': result = true if file.splitFile().name[0] == '.': result = true
proc checkInstallDir(pkgInfo: TPackageInfo, proc checkInstallDir(pkgInfo: PackageInfo,
origDir, dir: string): bool = origDir, dir: string): bool =
## Determines whether ``dir`` should be installed. ## Determines whether ``dir`` should be installed.
## ``True`` means dir should be skipped. ## ``True`` means dir should be skipped.
@ -255,7 +265,7 @@ proc checkInstallDir(pkgInfo: TPackageInfo,
if thisDir == "nimcache": result = true if thisDir == "nimcache": result = true
proc copyWithExt(origDir, currentDir, dest: string, proc copyWithExt(origDir, currentDir, dest: string,
pkgInfo: TPackageInfo): seq[string] = pkgInfo: PackageInfo): seq[string] =
## Returns the filenames of the files that have been copied ## Returns the filenames of the files that have been copied
## (their destination). ## (their destination).
result = @[] result = @[]
@ -269,9 +279,9 @@ proc copyWithExt(origDir, currentDir, dest: string,
result.add copyFileD(path, changeRoot(origDir, dest, path)) result.add copyFileD(path, changeRoot(origDir, dest, path))
proc copyFilesRec(origDir, currentDir, dest: string, proc copyFilesRec(origDir, currentDir, dest: string,
options: TOptions, pkgInfo: TPackageInfo): TSet[string] = options: Options, pkgInfo: PackageInfo): HashSet[string] =
## Copies all the required files, skips files specified in the .nimble file ## Copies all the required files, skips files specified in the .nimble file
## (TPackageInfo). ## (PackageInfo).
## Returns a list of filepaths to files which have been installed. ## Returns a list of filepaths to files which have been installed.
result = initSet[string]() result = initSet[string]()
let whitelistMode = let whitelistMode =
@ -320,13 +330,14 @@ proc copyFilesRec(origDir, currentDir, dest: string,
result.incl copyFileD(pkgInfo.mypath, result.incl copyFileD(pkgInfo.mypath,
changeRoot(pkgInfo.mypath.splitFile.dir, dest, pkgInfo.mypath)) changeRoot(pkgInfo.mypath.splitFile.dir, dest, pkgInfo.mypath))
proc saveNimbleData(options: TOptions) = proc saveNimbleData(options: Options) =
# TODO: This file should probably be locked. # TODO: This file should probably be locked.
writeFile(options.getNimbleDir() / "nimbledata.json", pretty(options.nimbleData)) writeFile(options.getNimbleDir() / "nimbledata.json",
pretty(options.nimbleData))
proc addRevDep(options: TOptions, dep: tuple[name, version: string], proc addRevDep(options: Options, dep: tuple[name, version: string],
pkg: TPackageInfo) = pkg: PackageInfo) =
let depNameVer = dep.name & '-' & dep.version # let depNameVer = dep.name & '-' & dep.version
if not options.nimbleData["reverseDeps"].hasKey(dep.name): if not options.nimbleData["reverseDeps"].hasKey(dep.name):
options.nimbleData["reverseDeps"][dep.name] = newJObject() options.nimbleData["reverseDeps"][dep.name] = newJObject()
if not options.nimbleData["reverseDeps"][dep.name].hasKey(dep.version): if not options.nimbleData["reverseDeps"][dep.name].hasKey(dep.version):
@ -336,10 +347,10 @@ proc addRevDep(options: TOptions, dep: tuple[name, version: string],
if revDep notin thisDep: if revDep notin thisDep:
thisDep.add revDep thisDep.add revDep
proc removeRevDep(options: TOptions, pkg: TPackageInfo) = proc removeRevDep(options: Options, pkg: PackageInfo) =
## Removes ``pkg`` from the reverse dependencies of every package. ## Removes ``pkg`` from the reverse dependencies of every package.
proc remove(options: TOptions, pkg: TPackageInfo, depTup: TPkgTuple, proc remove(options: Options, pkg: PackageInfo, depTup: PkgTuple,
thisDep: PJsonNode) = thisDep: JsonNode) =
for ver, val in thisDep: for ver, val in thisDep:
if ver.newVersion in depTup.ver: if ver.newVersion in depTup.ver:
var newVal = newJArray() var newVal = newJArray()
@ -373,10 +384,10 @@ proc removeRevDep(options: TOptions, pkg: TPackageInfo) =
saveNimbleData(options) saveNimbleData(options)
proc install(packages: seq[TPkgTuple], proc install(packages: seq[PkgTuple],
options: TOptions, options: Options,
doPrompt = true): tuple[paths: seq[string], pkg: TPackageInfo] doPrompt = true): tuple[paths: seq[string], pkg: PackageInfo]
proc processDeps(pkginfo: TPackageInfo, options: TOptions): seq[string] = proc processDeps(pkginfo: PackageInfo, options: Options): seq[string] =
## Verifies and installs dependencies. ## Verifies and installs dependencies.
## ##
## Returns the list of paths to pass to the compiler during build phase. ## Returns the list of paths to pass to the compiler during build phase.
@ -390,7 +401,7 @@ proc processDeps(pkginfo: TPackageInfo, options: TOptions): seq[string] =
quit("Unsatisfied dependency: " & dep.name & " (" & $dep.ver & ")") quit("Unsatisfied dependency: " & dep.name & " (" & $dep.ver & ")")
else: else:
echo("Looking for ", dep.name, " (", $dep.ver, ")...") echo("Looking for ", dep.name, " (", $dep.ver, ")...")
var pkg: TPackageInfo var pkg: PackageInfo
if not findPkg(pkglist, dep, pkg): if not findPkg(pkglist, dep, pkg):
echo("None found, installing...") echo("None found, installing...")
let (paths, installedPkg) = install(@[(dep.name, dep.ver)], options) let (paths, installedPkg) = install(@[(dep.name, dep.ver)], options)
@ -406,11 +417,11 @@ proc processDeps(pkginfo: TPackageInfo, options: TOptions): seq[string] =
# Check if two packages of the same name (but different version) are listed # Check if two packages of the same name (but different version) are listed
# in the path. # in the path.
var pkgsInPath: PStringTable = newStringTable(modeCaseSensitive) var pkgsInPath: StringTableRef = newStringTable(modeCaseSensitive)
for p in result: for p in result:
let (name, version) = getNameVersion(p) let (name, version) = getNameVersion(p)
if pkgsInPath.hasKey(name) and pkgsInPath[name] != version: if pkgsInPath.hasKey(name) and pkgsInPath[name] != version:
raise newException(ENimble, raise newException(NimbleError,
"Cannot satisfy the dependency on $1 $2 and $1 $3" % "Cannot satisfy the dependency on $1 $2 and $1 $3" %
[name, version, pkgsInPath[name]]) [name, version, pkgsInPath[name]])
pkgsInPath[name] = version pkgsInPath[name] = version
@ -422,7 +433,7 @@ proc processDeps(pkginfo: TPackageInfo, options: TOptions): seq[string] =
addRevDep(options, i, pkginfo) addRevDep(options, i, pkginfo)
saveNimbleData(options) saveNimbleData(options)
proc buildFromDir(pkgInfo: TPackageInfo, paths: seq[string]) = proc buildFromDir(pkgInfo: PackageInfo, paths: seq[string]) =
## Builds a package as specified by ``pkgInfo``. ## Builds a package as specified by ``pkgInfo``.
let realDir = pkgInfo.getRealDir() let realDir = pkgInfo.getRealDir()
var args = "" var args = ""
@ -433,19 +444,19 @@ proc buildFromDir(pkgInfo: TPackageInfo, paths: seq[string]) =
doCmd(getNimBin() & " $# -d:release --noBabelPath $# \"$#\"" % doCmd(getNimBin() & " $# -d:release --noBabelPath $# \"$#\"" %
[pkgInfo.backend, args, realDir / bin.changeFileExt("nim")]) [pkgInfo.backend, args, realDir / bin.changeFileExt("nim")])
proc saveNimbleMeta(pkgDestDir, url: string, filesInstalled: TSet[string]) = proc saveNimbleMeta(pkgDestDir, url: string, filesInstalled: HashSet[string]) =
var nimblemeta = %{"url": %url} var nimblemeta = %{"url": %url}
nimblemeta["files"] = newJArray() nimblemeta["files"] = newJArray()
for file in filesInstalled: for file in filesInstalled:
nimblemeta["files"].add(%changeRoot(pkgDestDir, "", file)) nimblemeta["files"].add(%changeRoot(pkgDestDir, "", file))
writeFile(pkgDestDir / "nimblemeta.json", $nimblemeta) writeFile(pkgDestDir / "nimblemeta.json", $nimblemeta)
proc removePkgDir(dir: string, options: TOptions) = proc removePkgDir(dir: string, options: Options) =
## Removes files belonging to the package in ``dir``. ## Removes files belonging to the package in ``dir``.
try: try:
var nimblemeta = parseFile(dir / "nimblemeta.json") var nimblemeta = parseFile(dir / "nimblemeta.json")
if not nimblemeta.hasKey("files"): if not nimblemeta.hasKey("files"):
raise newException(EJsonParsingError, raise newException(JsonParsingError,
"Meta data does not contain required info.") "Meta data does not contain required info.")
for file in nimblemeta["files"]: for file in nimblemeta["files"]:
removeFile(dir / file.str) removeFile(dir / file.str)
@ -458,15 +469,15 @@ proc removePkgDir(dir: string, options: TOptions) =
else: else:
echo("WARNING: Cannot completely remove " & dir & echo("WARNING: Cannot completely remove " & dir &
". Files not installed by nimble are present.") ". Files not installed by nimble are present.")
except EOS, EJsonParsingError: except OSError, JsonParsingError:
echo("Error: Unable to read nimblemeta.json: ", getCurrentExceptionMsg()) echo("Error: Unable to read nimblemeta.json: ", getCurrentExceptionMsg())
if not options.prompt("Would you like to COMPLETELY remove ALL files " & if not options.prompt("Would you like to COMPLETELY remove ALL files " &
"in " & dir & "?"): "in " & dir & "?"):
quit(QuitSuccess) quit(QuitSuccess)
removeDir(dir) removeDir(dir)
proc installFromDir(dir: string, latest: bool, options: TOptions, proc installFromDir(dir: string, latest: bool, options: Options,
url: string): tuple[paths: seq[string], pkg: TPackageInfo] = url: string): tuple[paths: seq[string], pkg: PackageInfo] =
## Returns where package has been installed to, together with paths ## Returns where package has been installed to, together with paths
## to the packages this package depends on. ## to the packages this package depends on.
## The return value of this function is used by ## The return value of this function is used by
@ -487,7 +498,8 @@ proc installFromDir(dir: string, latest: bool, options: TOptions,
let versionStr = (if latest: "" else: '-' & pkgInfo.version) let versionStr = (if latest: "" else: '-' & pkgInfo.version)
let pkgDestDir = pkgsDir / (pkgInfo.name & versionStr) let pkgDestDir = pkgsDir / (pkgInfo.name & versionStr)
if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"): if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"):
if not options.prompt(pkgInfo.name & versionStr & " already exists. Overwrite?"): if not options.prompt(pkgInfo.name & versionStr &
" already exists. Overwrite?"):
quit(QuitSuccess) quit(QuitSuccess)
removePkgDir(pkgDestDir, options) removePkgDir(pkgDestDir, options)
# Remove any symlinked binaries # Remove any symlinked binaries
@ -503,7 +515,7 @@ proc installFromDir(dir: string, latest: bool, options: TOptions,
removeFile(binDir / bin) removeFile(binDir / bin)
## Will contain a list of files which have been installed. ## Will contain a list of files which have been installed.
var filesInstalled: TSet[string] var filesInstalled: HashSet[string]
createDir(pkgDestDir) createDir(pkgDestDir)
if pkgInfo.bin.len > 0: if pkgInfo.bin.len > 0:
@ -568,32 +580,34 @@ proc getNimbleTempDir(): string =
else: else:
result.add($getpid()) result.add($getpid())
proc downloadPkg(url: string, verRange: PVersionRange, proc downloadPkg(url: string, verRange: VersionRange,
downMethod: TDownloadMethod): string = downMethod: DownloadMethod): string =
let downloadDir = (getNimbleTempDir() / getDownloadDirName(url, verRange)) let downloadDir = (getNimbleTempDir() / getDownloadDirName(url, verRange))
createDir(downloadDir) createDir(downloadDir)
echo("Downloading ", url, " into ", downloadDir, " using ", downMethod, "...") echo("Downloading ", url, " into ", downloadDir, " using ", downMethod, "...")
doDownload(url, downloadDir, verRange, downMethod) doDownload(url, downloadDir, verRange, downMethod)
result = downloadDir result = downloadDir
proc downloadPkg(pkg: TPackage, verRange: PVersionRange): string = proc downloadPkg(pkg: Package, verRange: VersionRange): string =
let downloadDir = (getNimbleTempDir() / getDownloadDirName(pkg, verRange)) let downloadDir = (getNimbleTempDir() / getDownloadDirName(pkg, verRange))
let downMethod = pkg.downloadMethod.getDownloadMethod() let downMethod = pkg.downloadMethod.getDownloadMethod()
createDir(downloadDir) createDir(downloadDir)
echo("Downloading ", pkg.name, " into ", downloadDir, " using ", downMethod, "...") echo("Downloading ", pkg.name, " into ", downloadDir, " using ", downMethod,
"...")
doDownload(pkg.url, downloadDir, verRange, downMethod) doDownload(pkg.url, downloadDir, verRange, downMethod)
result = downloadDir result = downloadDir
proc install(packages: seq[TPkgTuple], proc install(packages: seq[PkgTuple],
options: TOptions, options: Options,
doPrompt = true): tuple[paths: seq[string], pkg: TPackageInfo] = doPrompt = true): tuple[paths: seq[string], pkg: PackageInfo] =
if packages == @[]: if packages == @[]:
result = installFromDir(getCurrentDir(), false, options, "") result = installFromDir(getCurrentDir(), false, options, "")
else: else:
# If packages.json is not present ask the user if they want to download it. # If packages.json is not present ask the user if they want to download it.
if not existsFile(options.getNimbleDir / "packages.json"): if not existsFile(options.getNimbleDir / "packages.json"):
if doPrompt and if doPrompt and
options.prompt("Local packages.json not found, download it from internet?"): options.prompt("Local packages.json not found, download it from " &
"internet?"):
update(options) update(options)
else: else:
quit("Please run nimble update.", QuitFailure) quit("Please run nimble update.", QuitFailure)
@ -605,34 +619,35 @@ proc install(packages: seq[TPkgTuple],
let downloadDir = downloadPkg(pv.name, pv.ver, meth) let downloadDir = downloadPkg(pv.name, pv.ver, meth)
result = installFromDir(downloadDir, false, options, pv.name) result = installFromDir(downloadDir, false, options, pv.name)
else: else:
var pkg: TPackage var pkg: Package
if getPackage(pv.name, options.getNimbleDir() / "packages.json", pkg): if getPackage(pv.name, options.getNimbleDir() / "packages.json", pkg):
let downloadDir = downloadPkg(pkg, pv.ver) let downloadDir = downloadPkg(pkg, pv.ver)
result = installFromDir(downloadDir, false, options, pkg.url) result = installFromDir(downloadDir, false, options, pkg.url)
else: else:
# If package is not found give the user a chance to update package.json # If package is not found give the user a chance to update
# package.json
if doPrompt and if doPrompt and
options.prompt(pv.name & " not found in local packages.json, " & options.prompt(pv.name & " not found in local packages.json, " &
"check internet for updated packages?"): "check internet for updated packages?"):
update(options) update(options)
result = install(@[pv], options, false) result = install(@[pv], options, false)
else: else:
raise newException(ENimble, "Package not found.") raise newException(NimbleError, "Package not found.")
proc build(options: TOptions) = proc build(options: Options) =
var pkgInfo = getPkgInfo(getCurrentDir()) var pkgInfo = getPkgInfo(getCurrentDir())
let paths = processDeps(pkginfo, options) let paths = processDeps(pkginfo, options)
buildFromDir(pkgInfo, paths) buildFromDir(pkgInfo, paths)
proc search(options: TOptions) = proc search(options: Options) =
## Searches for matches in ``options.action.search``. ## Searches for matches in ``options.action.search``.
## ##
## Searches are done in a case insensitive way making all strings lower case. ## Searches are done in a case insensitive way making all strings lower case.
assert options.action.typ == ActionSearch assert options.action.typ == actionSearch
if options.action.search == @[]: if options.action.search == @[]:
raise newException(ENimble, "Please specify a search string.") raise newException(NimbleError, "Please specify a search string.")
if not existsFile(options.getNimbleDir() / "packages.json"): if not existsFile(options.getNimbleDir() / "packages.json"):
raise newException(ENimble, "Please run nimble update.") raise newException(NimbleError, "Please run nimble update.")
let pkgList = getPackageList(options.getNimbleDir() / "packages.json") let pkgList = getPackageList(options.getNimbleDir() / "packages.json")
var found = false var found = false
template onFound: stmt = template onFound: stmt =
@ -656,9 +671,9 @@ proc search(options: TOptions) =
if not found: if not found:
echo("No package found.") echo("No package found.")
proc list(options: TOptions) = proc list(options: Options) =
if not existsFile(options.getNimbleDir() / "packages.json"): if not existsFile(options.getNimbleDir() / "packages.json"):
raise newException(ENimble, "Please run nimble update.") raise newException(NimbleError, "Please run nimble update.")
let pkgList = getPackageList(options.getNimbleDir() / "packages.json") let pkgList = getPackageList(options.getNimbleDir() / "packages.json")
for pkg in pkgList: for pkg in pkgList:
echoPackage(pkg) echoPackage(pkg)
@ -666,7 +681,7 @@ proc list(options: TOptions) =
echoPackageVersions(pkg) echoPackageVersions(pkg)
echo(" ") echo(" ")
proc listInstalled(options: TOptions) = proc listInstalled(options: Options) =
var h = initTable[string, seq[string]]() var h = initTable[string, seq[string]]()
let pkgs = getInstalledPkgs(options.getPkgsDir()) let pkgs = getInstalledPkgs(options.getPkgsDir())
for x in pkgs.items(): for x in pkgs.items():
@ -680,9 +695,9 @@ proc listInstalled(options: TOptions) =
for k in keys(h): for k in keys(h):
echo k & " [" & h[k].join(", ") & "]" echo k & " [" & h[k].join(", ") & "]"
type VersionAndPath = tuple[version: TVersion, path: string] type VersionAndPath = tuple[version: Version, path: string]
proc listPaths(options: TOptions) = proc listPaths(options: Options) =
## Loops over installing packages displaying their installed paths. ## Loops over installing packages displaying their installed paths.
## ##
## If there are several packages installed, only the last one (the version ## If there are several packages installed, only the last one (the version
@ -691,7 +706,7 @@ proc listPaths(options: TOptions) =
## but at the end quits with a non zero exit error. ## but at the end quits with a non zero exit error.
## ##
## On success the proc returns normally. ## On success the proc returns normally.
assert options.action.typ == ActionPath assert options.action.typ == actionPath
assert(not options.action.packages.isNil) assert(not options.action.packages.isNil)
var errors = 0 var errors = 0
for name, version in options.action.packages.items: for name, version in options.action.packages.items:
@ -721,26 +736,28 @@ proc listPaths(options: TOptions) =
echo "Warning: Package '" & name & "' not installed" echo "Warning: Package '" & name & "' not installed"
errors += 1 errors += 1
if errors > 0: if errors > 0:
raise newException(ENimble, "At least one of the specified packages was not found") raise newException(NimbleError,
"At least one of the specified packages was not found")
proc init(options: TOptions) = proc init(options: Options) =
echo("Initializing new Nimble project!") echo("Initializing new Nimble project!")
var var
pkgName, fName: string = "" pkgName, fName: string = ""
outFile: TFile outFile: File
if (options.action.projName != ""): if (options.action.projName != ""):
pkgName = options.action.projName pkgName = options.action.projName
fName = pkgName & ".nimble" fName = pkgName & ".nimble"
if (existsFile(os.getCurrentDir() / fName)): if (existsFile(os.getCurrentDir() / fName)):
raise newException(ENimble, "Already have a nimble file.") raise newException(NimbleError, "Already have a nimble file.")
else: else:
echo("Enter a project name for this (blank to use working directory), Ctrl-C to abort:") echo("Enter a project name for this (blank to use working directory), " &
"Ctrl-C to abort:")
pkgName = readline(stdin) pkgName = readline(stdin)
if (pkgName == ""): if (pkgName == ""):
pkgName = os.getCurrentDir().splitPath.tail pkgName = os.getCurrentDir().splitPath.tail
if (pkgName == ""): if (pkgName == ""):
raise newException(ENimble, "Could not get default file path.") raise newException(NimbleError, "Could not get default file path.")
fName = pkgName & ".nimble" fName = pkgName & ".nimble"
# Now need to write out .nimble file with projName and other details # Now need to write out .nimble file with projName and other details
@ -759,18 +776,18 @@ proc init(options: TOptions) =
close(outFile) close(outFile)
else: else:
raise newException(ENimble, "Unable to open file " & fName & raise newException(NimbleError, "Unable to open file " & fName &
" for writing: " & osErrorMsg()) " for writing: " & osErrorMsg(osLastError()))
proc uninstall(options: TOptions) = proc uninstall(options: Options) =
var pkgsToDelete: seq[TPackageInfo] = @[] var pkgsToDelete: seq[PackageInfo] = @[]
# Do some verification. # Do some verification.
for pkgTup in options.action.packages: for pkgTup in options.action.packages:
echo("Looking for ", pkgTup.name, " (", $pkgTup.ver, ")...") echo("Looking for ", pkgTup.name, " (", $pkgTup.ver, ")...")
let installedPkgs = getInstalledPkgs(options.getPkgsDir()) let installedPkgs = getInstalledPkgs(options.getPkgsDir())
var pkgList = findAllPkgs(installedPkgs, pkgTup) var pkgList = findAllPkgs(installedPkgs, pkgTup)
if pkgList.len == 0: if pkgList.len == 0:
raise newException(ENimble, "Package not found") raise newException(NimbleError, "Package not found")
echo("Checking reverse dependencies...") echo("Checking reverse dependencies...")
var errors: seq[string] = @[] var errors: seq[string] = @[]
@ -796,7 +813,7 @@ proc uninstall(options: TOptions) =
pkgsToDelete.add pkg pkgsToDelete.add pkg
if pkgsToDelete.len == 0: if pkgsToDelete.len == 0:
raise newException(ENimble, "\n " & errors.join("\n ")) raise newException(NimbleError, "\n " & errors.join("\n "))
var pkgNames = "" var pkgNames = ""
for i in 0 .. <pkgsToDelete.len: for i in 0 .. <pkgsToDelete.len:
@ -815,38 +832,38 @@ proc uninstall(options: TOptions) =
removePkgDir(options.getPkgsDir / (pkg.name & '-' & pkg.version), options) removePkgDir(options.getPkgsDir / (pkg.name & '-' & pkg.version), options)
echo("Removed ", pkg.name, " (", $pkg.version, ")") echo("Removed ", pkg.name, " (", $pkg.version, ")")
proc doAction(options: TOptions) = proc doAction(options: Options) =
if not existsDir(options.getNimbleDir()): if not existsDir(options.getNimbleDir()):
createDir(options.getNimbleDir()) createDir(options.getNimbleDir())
if not existsDir(options.getPkgsDir): if not existsDir(options.getPkgsDir):
createDir(options.getPkgsDir) createDir(options.getPkgsDir)
case options.action.typ case options.action.typ
of ActionUpdate: of actionUpdate:
update(options) update(options)
of ActionInstall: of actionInstall:
discard install(options.action.packages, options) discard install(options.action.packages, options)
of ActionUninstall: of actionUninstall:
uninstall(options) uninstall(options)
of ActionSearch: of actionSearch:
search(options) search(options)
of ActionList: of actionList:
if options.queryInstalled: listInstalled(options) if options.queryInstalled: listInstalled(options)
else: list(options) else: list(options)
of ActionPath: of actionPath:
listPaths(options) listPaths(options)
of ActionBuild: of actionBuild:
build(options) build(options)
of ActionInit: of actionInit:
init(options) init(options)
of ActionNil: of actionNil:
assert false assert false
when isMainModule: when isMainModule:
when defined(release): when defined(release):
try: try:
parseCmdLine().doAction() parseCmdLine().doAction()
except ENimble: except NimbleError:
quit("FAILURE: " & getCurrentExceptionMsg()) quit("FAILURE: " & getCurrentExceptionMsg())
finally: finally:
removeDir(getNimbleTempDir()) removeDir(getNimbleTempDir())

View file

@ -1,29 +0,0 @@
# Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info.
## This module contains additional code from the development version of
## Nimrod's standard library. These procs are required to be able to compile
## against the last stable release 0.9.4. Once 0.9.6 is release these procs
## will disappear.
import json
when false:
when not (NimrodPatch >= 5):
when not defined(`{}`):
proc `{}`*(node: PJsonNode, key: string): PJsonNode =
## Transverses the node and gets the given value. If any of the
## names does not exist, returns nil
result = node
if isNil(node): return nil
result = result[key]
when not defined(`{}=`):
proc `{}=`*(node: PJsonNode, names: varargs[string], value: PJsonNode) =
## Transverses the node and tries to set the value at the given location
## to `value` If any of the names are missing, they are added
var node = node
for i in 0..(names.len-2):
if isNil(node[names[i]]):
node[names[i]] = newJObject()
node = node[names[i]]
node[names[names.len-1]] = value

View file

@ -2,15 +2,15 @@
# BSD License. Look at license.txt for more info. # BSD License. Look at license.txt for more info.
import parsecfg, streams, strutils, os import parsecfg, streams, strutils, os
import tools, version import tools, version, nimbletypes
type type
TConfig* = object Config* = object
nimbleDir*: string nimbleDir*: string
chcp*: bool # Whether to change the code page in .cmd files on Win. chcp*: bool # Whether to change the code page in .cmd files on Win.
proc initConfig(): TConfig = proc initConfig(): Config =
if getNimrodVersion() > newVersion("0.9.6"): if getNimrodVersion() > newVersion("0.9.6"):
result.nimbleDir = getHomeDir() / ".nimble" result.nimbleDir = getHomeDir() / ".nimble"
else: else:
@ -18,7 +18,7 @@ proc initConfig(): TConfig =
result.chcp = true result.chcp = true
proc parseConfig*(): TConfig = proc parseConfig*(): Config =
result = initConfig() result = initConfig()
var confFile = getConfigDir() / "nimble" / "nimble.ini" var confFile = getConfigDir() / "nimble" / "nimble.ini"
@ -32,7 +32,7 @@ proc parseConfig*(): TConfig =
if f != nil: if f != nil:
echo("Reading from config file at ", confFile) echo("Reading from config file at ", confFile)
var p: TCfgParser var p: CfgParser
open(p, f, confFile) open(p, f, confFile)
while true: while true:
var e = next(p) var e = next(p)
@ -49,8 +49,8 @@ proc parseConfig*(): TConfig =
of "chcp": of "chcp":
result.chcp = parseBool(e.value) result.chcp = parseBool(e.value)
else: else:
raise newException(ENimble, "Unable to parse config file:" & raise newException(NimbleError, "Unable to parse config file:" &
" Unknown key: " & e.key) " Unknown key: " & e.key)
of cfgError: of cfgError:
raise newException(ENimble, "Unable to parse config file: " & e.msg) raise newException(NimbleError, "Unable to parse config file: " & e.msg)
close(p) close(p)

View file

@ -3,47 +3,48 @@
import parseutils, os, osproc, strutils, tables, pegs import parseutils, os, osproc, strutils, tables, pegs
import packageinfo, version, tools import packageinfo, version, tools, nimbletypes
type type
TDownloadMethod* {.pure.} = enum DownloadMethod* {.pure.} = enum
Git = "git", Hg = "hg" git = "git", hg = "hg"
proc getSpecificDir(meth: TDownloadMethod): string = proc getSpecificDir(meth: DownloadMethod): string =
case meth case meth
of TDownloadMethod.Git: of DownloadMethod.git:
".git" ".git"
of TDownloadMethod.Hg: of DownloadMethod.hg:
".hg" ".hg"
proc doCheckout(meth: TDownloadMethod, downloadDir, branch: string) = proc doCheckout(meth: DownloadMethod, downloadDir, branch: string) =
case meth case meth
of TDownloadMethod.Git: of DownloadMethod.git:
cd downloadDir: cd downloadDir:
# Force is used here because local changes may appear straight after a # Force is used here because local changes may appear straight after a
# clone has happened. Like in the case of git on Windows where it # clone has happened. Like in the case of git on Windows where it
# messes up the damn line endings. # messes up the damn line endings.
doCmd("git checkout --force " & branch) doCmd("git checkout --force " & branch)
of TDownloadMethod.Hg: of DownloadMethod.hg:
cd downloadDir: cd downloadDir:
doCmd("hg checkout " & branch) doCmd("hg checkout " & branch)
proc doPull(meth: TDownloadMethod, downloadDir: string) = proc doPull(meth: DownloadMethod, downloadDir: string) =
case meth case meth
of TDownloadMethod.Git: of DownloadMethod.git:
doCheckout(meth, downloadDir, "master") doCheckout(meth, downloadDir, "master")
cd downloadDir: cd downloadDir:
doCmd("git pull") doCmd("git pull")
if existsFile(".gitmodules"): if existsFile(".gitmodules"):
doCmd("git submodule update") doCmd("git submodule update")
of TDownloadMethod.Hg: of DownloadMethod.hg:
doCheckout(meth, downloadDir, "default") doCheckout(meth, downloadDir, "default")
cd downloadDir: cd downloadDir:
doCmd("hg pull") doCmd("hg pull")
proc doClone(meth: TDownloadMethod, url, downloadDir: string, branch = "", tip = true) = proc doClone(meth: DownloadMethod, url, downloadDir: string, branch = "",
tip = true) =
case meth case meth
of TDownloadMethod.Git: of DownloadMethod.git:
let let
depthArg = if tip: "--depth 1 " else: "" depthArg = if tip: "--depth 1 " else: ""
branchArg = if branch == "": "-b origin/master" else: "-b " & branch & " " branchArg = if branch == "": "-b origin/master" else: "-b " & branch & " "
@ -60,28 +61,28 @@ proc doClone(meth: TDownloadMethod, url, downloadDir: string, branch = "", tip =
doCmd("git reset --hard FETCH_HEAD") doCmd("git reset --hard FETCH_HEAD")
doCmd("git checkout --force " & branchArg) doCmd("git checkout --force " & branchArg)
doCmd("git submodule update --init --recursive") doCmd("git submodule update --init --recursive")
of TDownloadMethod.Hg: of DownloadMethod.hg:
let let
tipArg = if tip: "-r tip " else: "" tipArg = if tip: "-r tip " else: ""
branchArg = if branch == "": "" else: "-b " & branch & " " branchArg = if branch == "": "" else: "-b " & branch & " "
doCmd("hg clone " & tipArg & branchArg & url & " " & downloadDir) doCmd("hg clone " & tipArg & branchArg & url & " " & downloadDir)
proc getTagsList(dir: string, meth: TDownloadMethod): seq[string] = proc getTagsList(dir: string, meth: DownloadMethod): seq[string] =
cd dir: cd dir:
var output = execProcess("git tag") var output = execProcess("git tag")
case meth case meth
of TDownloadMethod.Git: of DownloadMethod.git:
output = execProcess("git tag") output = execProcess("git tag")
of TDownloadMethod.Hg: of DownloadMethod.hg:
output = execProcess("hg tags") output = execProcess("hg tags")
if output.len > 0: if output.len > 0:
case meth case meth
of TDownloadMethod.Git: of DownloadMethod.git:
result = @[] result = @[]
for i in output.splitLines(): for i in output.splitLines():
if i == "": continue if i == "": continue
result.add(i) result.add(i)
of TDownloadMethod.Hg: of DownloadMethod.hg:
result = @[] result = @[]
for i in output.splitLines(): for i in output.splitLines():
if i == "": continue if i == "": continue
@ -92,13 +93,13 @@ proc getTagsList(dir: string, meth: TDownloadMethod): seq[string] =
else: else:
result = @[] result = @[]
proc getTagsListRemote*(url: string, meth: TDownloadMethod): seq[string] = proc getTagsListRemote*(url: string, meth: DownloadMethod): seq[string] =
result = @[] result = @[]
case meth case meth
of TDownloadMethod.Git: of DownloadMethod.git:
var (output, exitCode) = doCmdEx("git ls-remote --tags " & url) var (output, exitCode) = doCmdEx("git ls-remote --tags " & url)
if exitCode != QuitSuccess: if exitCode != QuitSuccess:
raise newException(EOS, "Unable to query remote tags for " & url & raise newException(OSError, "Unable to query remote tags for " & url &
". Git returned: " & output) ". Git returned: " & output)
for i in output.splitLines(): for i in output.splitLines():
if i == "": continue if i == "": continue
@ -106,47 +107,47 @@ proc getTagsListRemote*(url: string, meth: TDownloadMethod): seq[string] =
let tag = i[start .. -1] let tag = i[start .. -1]
if not tag.endswith("^{}"): result.add(tag) if not tag.endswith("^{}"): result.add(tag)
of TDownloadMethod.Hg: of DownloadMethod.hg:
# http://stackoverflow.com/questions/2039150/show-tags-for-remote-hg-repository # http://stackoverflow.com/questions/2039150/show-tags-for-remote-hg-repository
raise newException(EInvalidValue, "Hg doesn't support remote tag querying.") raise newException(ValueError, "Hg doesn't support remote tag querying.")
proc getVersionList*(tags: seq[string]): TTable[TVersion, string] = proc getVersionList*(tags: seq[string]): Table[Version, string] =
# Returns: TTable of version -> git tag name # Returns: TTable of version -> git tag name
result = initTable[TVersion, string]() result = initTable[Version, string]()
for tag in tags: for tag in tags:
if tag != "": if tag != "":
let i = skipUntil(tag, Digits) # skip any chars before the version let i = skipUntil(tag, Digits) # skip any chars before the version
# TODO: Better checking, tags can have any names. Add warnings and such. # TODO: Better checking, tags can have any names. Add warnings and such.
result[newVersion(tag[i .. -1])] = tag result[newVersion(tag[i .. -1])] = tag
proc getDownloadMethod*(meth: string): TDownloadMethod = proc getDownloadMethod*(meth: string): DownloadMethod =
case meth case meth
of "git": return TDownloadMethod.Git of "git": return DownloadMethod.git
of "hg", "mercurial": return TDownloadMethod.Hg of "hg", "mercurial": return DownloadMethod.hg
else: else:
raise newException(ENimble, "Invalid download method: " & meth) raise newException(NimbleError, "Invalid download method: " & meth)
proc getHeadName*(meth: TDownloadMethod): string = proc getHeadName*(meth: DownloadMethod): string =
## Returns the name of the download method specific head. i.e. for git ## Returns the name of the download method specific head. i.e. for git
## it's ``head`` for hg it's ``tip``. ## it's ``head`` for hg it's ``tip``.
case meth case meth
of TDownloadMethod.Git: "head" of DownloadMethod.git: "head"
of TDownloadMethod.Hg: "tip" of DownloadMethod.hg: "tip"
proc checkUrlType*(url: string): TDownloadMethod = proc checkUrlType*(url: string): DownloadMethod =
## Determines the download method based on the URL. ## Determines the download method based on the URL.
if doCmdEx("git ls-remote " & url).exitCode == QuitSuccess: if doCmdEx("git ls-remote " & url).exitCode == QuitSuccess:
return TDownloadMethod.Git return DownloadMethod.git
elif doCmdEx("hg identify " & url).exitCode == QuitSuccess: elif doCmdEx("hg identify " & url).exitCode == QuitSuccess:
return TDownloadMethod.Hg return DownloadMethod.hg
else: else:
raise newException(ENimble, "Unable to identify url.") raise newException(NimbleError, "Unable to identify url.")
proc isURL*(name: string): bool = proc isURL*(name: string): bool =
name.startsWith(peg" @'://' ") name.startsWith(peg" @'://' ")
proc doDownload*(url: string, downloadDir: string, verRange: PVersionRange, proc doDownload*(url: string, downloadDir: string, verRange: VersionRange,
downMethod: TDownloadMethod) = downMethod: DownloadMethod) =
template getLatestByTag(meth: stmt): stmt {.dirty, immediate.} = template getLatestByTag(meth: stmt): stmt {.dirty, immediate.} =
echo("Found tags...") echo("Found tags...")
# Find latest version that fits our ``verRange``. # Find latest version that fits our ``verRange``.
@ -164,7 +165,7 @@ proc doDownload*(url: string, downloadDir: string, verRange: PVersionRange,
## version range. ## version range.
let pkginfo = getPkgInfo(downloadDir) let pkginfo = getPkgInfo(downloadDir)
if pkginfo.version.newVersion notin verRange: if pkginfo.version.newVersion notin verRange:
raise newException(ENimble, raise newException(NimbleError,
"Downloaded package's version does not satisfy requested version " & "Downloaded package's version does not satisfy requested version " &
"range: wanted $1 got $2." % "range: wanted $1 got $2." %
[$verRange, $pkginfo.version]) [$verRange, $pkginfo.version])
@ -177,14 +178,14 @@ proc doDownload*(url: string, downloadDir: string, verRange: PVersionRange,
else: else:
# Mercurial requies a clone and checkout. The git clone operation is # Mercurial requies a clone and checkout. The git clone operation is
# already fragmented into multiple steps so we just call doClone(). # already fragmented into multiple steps so we just call doClone().
if downMethod == TDownloadMethod.Git: if downMethod == DownloadMethod.git:
doClone(downMethod, url, downloadDir, $verRange.spe) doClone(downMethod, url, downloadDir, $verRange.spe)
else: else:
doClone(downMethod, url, downloadDir, tip = false) doClone(downMethod, url, downloadDir, tip = false)
doCheckout(downMethod, downloadDir, $verRange.spe) doCheckout(downMethod, downloadDir, $verRange.spe)
else: else:
case downMethod case downMethod
of TDownloadMethod.Git: of DownloadMethod.git:
# For Git we have to query the repo remotely for its tags. This is # For Git we have to query the repo remotely for its tags. This is
# necessary as cloning with a --depth of 1 removes all tag info. # necessary as cloning with a --depth of 1 removes all tag info.
let versions = getTagsListRemote(url, downMethod).getVersionList() let versions = getTagsListRemote(url, downMethod).getVersionList()
@ -197,7 +198,7 @@ proc doDownload*(url: string, downloadDir: string, verRange: PVersionRange,
doClone(downMethod, url, downloadDir) # Grab HEAD. doClone(downMethod, url, downloadDir) # Grab HEAD.
verifyClone() verifyClone()
of TDownloadMethod.Hg: of DownloadMethod.hg:
doClone(downMethod, url, downloadDir) doClone(downMethod, url, downloadDir)
let versions = getTagsList(downloadDir, downMethod).getVersionList() let versions = getTagsList(downloadDir, downMethod).getVersionList()
@ -208,10 +209,10 @@ proc doDownload*(url: string, downloadDir: string, verRange: PVersionRange,
verifyClone() verifyClone()
proc echoPackageVersions*(pkg: TPackage) = proc echoPackageVersions*(pkg: Package) =
let downMethod = pkg.downloadMethod.getDownloadMethod() let downMethod = pkg.downloadMethod.getDownloadMethod()
case downMethod case downMethod
of TDownloadMethod.Git: of DownloadMethod.git:
try: try:
let versions = getTagsListRemote(pkg.url, downMethod).getVersionList() let versions = getTagsListRemote(pkg.url, downMethod).getVersionList()
if versions.len > 0: if versions.len > 0:
@ -225,7 +226,8 @@ proc echoPackageVersions*(pkg: TPackage) =
echo(" versions: " & vstr) echo(" versions: " & vstr)
else: else:
echo(" versions: (No versions tagged in the remote repository)") echo(" versions: (No versions tagged in the remote repository)")
except EOS: except OSError:
echo(getCurrentExceptionMsg()) echo(getCurrentExceptionMsg())
of TDownloadMethod.Hg: of DownloadMethod.hg:
echo(" versions: (Remote tag retrieval not supported by " & pkg.downloadMethod & ")") echo(" versions: (Remote tag retrieval not supported by " &
pkg.downloadMethod & ")")

View file

@ -0,0 +1,7 @@
# BSD License. Look at license.txt for more info.
#
# Various miscellaneous common types reside here, to avoid problems with
# recursive imports
type
NimbleError* = object of Exception

View file

@ -1,12 +1,12 @@
# Copyright (C) Dominik Picheta. All rights reserved. # Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info. # BSD License. Look at license.txt for more info.
import parsecfg, json, streams, strutils, parseutils, os import parsecfg, json, streams, strutils, parseutils, os
import version, tools import version, tools, nimbletypes
type type
## Tuple containing package name and version range. ## Tuple containing package name and version range.
TPkgTuple* = tuple[name: string, ver: PVersionRange] PkgTuple* = tuple[name: string, ver: VersionRange]
TPackageInfo* = object PackageInfo* = object
mypath*: string ## The path of this .nimble file mypath*: string ## The path of this .nimble file
name*: string name*: string
version*: string version*: string
@ -19,12 +19,12 @@ type
installDirs*: seq[string] installDirs*: seq[string]
installFiles*: seq[string] installFiles*: seq[string]
installExt*: seq[string] installExt*: seq[string]
requires*: seq[TPkgTuple] requires*: seq[PkgTuple]
bin*: seq[string] bin*: seq[string]
srcDir*: string srcDir*: string
backend*: string backend*: string
TPackage* = object Package* = object
# Required fields in a package. # Required fields in a package.
name*: string name*: string
url*: string # Download location. url*: string # Download location.
@ -37,10 +37,10 @@ type
dvcsTag*: string dvcsTag*: string
web*: string # Info url for humans. web*: string # Info url for humans.
TMetadata* = object MetaData* = object
url*: string url*: string
proc initPackageInfo(): TPackageInfo = proc initPackageInfo(): PackageInfo =
result.mypath = "" result.mypath = ""
result.name = "" result.name = ""
result.version = "" result.version = ""
@ -58,31 +58,32 @@ proc initPackageInfo(): TPackageInfo =
result.srcDir = "" result.srcDir = ""
result.backend = "c" result.backend = "c"
proc validatePackageInfo(pkgInfo: TPackageInfo, path: string) = proc validatePackageInfo(pkgInfo: PackageInfo, path: string) =
if pkgInfo.name == "": if pkgInfo.name == "":
raise newException(ENimble, "Incorrect .nimble file: " & path & raise newException(NimbleError, "Incorrect .nimble file: " & path &
" does not contain a name field.") " does not contain a name field.")
if pkgInfo.version == "": if pkgInfo.version == "":
raise newException(ENimble, "Incorrect .nimble file: " & path & raise newException(NimbleError, "Incorrect .nimble file: " & path &
" does not contain a version field.") " does not contain a version field.")
if pkgInfo.author == "": if pkgInfo.author == "":
raise newException(ENimble, "Incorrect .nimble file: " & path & raise newException(NimbleError, "Incorrect .nimble file: " & path &
" does not contain an author field.") " does not contain an author field.")
if pkgInfo.description == "": if pkgInfo.description == "":
raise newException(ENimble, "Incorrect .nimble file: " & path & raise newException(NimbleError, "Incorrect .nimble file: " & path &
" does not contain a description field.") " does not contain a description field.")
if pkgInfo.license == "": if pkgInfo.license == "":
raise newException(ENimble, "Incorrect .nimble file: " & path & raise newException(NimbleError, "Incorrect .nimble file: " & path &
" does not contain a license field.") " does not contain a license field.")
if pkgInfo.backend notin ["c", "cc", "objc", "cpp", "js"]: if pkgInfo.backend notin ["c", "cc", "objc", "cpp", "js"]:
raise newException(ENimble, "'" & pkgInfo.backend & "' is an invalid backend.") raise newException(NimbleError, "'" & pkgInfo.backend &
"' is an invalid backend.")
for c in pkgInfo.version: for c in pkgInfo.version:
if c notin ({'.'} + Digits): if c notin ({'.'} + Digits):
raise newException(ENimble, raise newException(NimbleError,
"Version may only consist of numbers and the '.' character " & "Version may only consist of numbers and the '.' character " &
"but found '" & c & "'.") "but found '" & c & "'.")
proc parseRequires(req: string): TPkgTuple = proc parseRequires(req: string): PkgTuple =
try: try:
if ' ' in req: if ' ' in req:
var i = skipUntil(req, Whitespace) var i = skipUntil(req, Whitespace)
@ -94,10 +95,10 @@ proc parseRequires(req: string): TPkgTuple =
result.ver = parseVersionRange(req[i .. -1]) result.ver = parseVersionRange(req[i .. -1])
else: else:
result.name = req.strip result.name = req.strip
result.ver = PVersionRange(kind: verAny) result.ver = VersionRange(kind: verAny)
except EParseVersion: except ParseVersionError:
raise newException(ENimble, "Unable to parse dependency version range: " & raise newException(NimbleError,
getCurrentExceptionMsg()) "Unable to parse dependency version range: " & getCurrentExceptionMsg())
proc multiSplit(s: string): seq[string] = proc multiSplit(s: string): seq[string] =
## Returns ``s`` split by newline and comma characters. ## Returns ``s`` split by newline and comma characters.
@ -115,12 +116,12 @@ proc multiSplit(s: string): seq[string] =
if len(result) < 1: if len(result) < 1:
return @[s] return @[s]
proc readPackageInfo*(path: string): TPackageInfo = proc readPackageInfo*(path: string): PackageInfo =
result = initPackageInfo() result = initPackageInfo()
result.mypath = path result.mypath = path
var fs = newFileStream(path, fmRead) var fs = newFileStream(path, fmRead)
if fs != nil: if fs != nil:
var p: TCfgParser var p: CfgParser
open(p, fs, path) open(p, fs, path)
var currentSection = "" var currentSection = ""
while true: while true:
@ -159,48 +160,52 @@ proc readPackageInfo*(path: string): TPackageInfo =
result.backend = ev.value.toLower() result.backend = ev.value.toLower()
case result.backend.normalize case result.backend.normalize
of "javascript": result.backend = "js" of "javascript": result.backend = "js"
else: discard
else: else:
raise newException(ENimble, "Invalid field: " & ev.key) raise newException(NimbleError, "Invalid field: " & ev.key)
of "deps", "dependencies": of "deps", "dependencies":
case ev.key.normalize case ev.key.normalize
of "requires": of "requires":
for v in ev.value.multiSplit: for v in ev.value.multiSplit:
result.requires.add(parseRequires(v.strip)) result.requires.add(parseRequires(v.strip))
else: else:
raise newException(ENimble, "Invalid field: " & ev.key) raise newException(NimbleError, "Invalid field: " & ev.key)
else: raise newException(ENimble, "Invalid section: " & currentSection) else: raise newException(NimbleError,
of cfgOption: raise newException(ENimble, "Invalid package info, should not contain --" & ev.value) "Invalid section: " & currentSection)
of cfgOption: raise newException(NimbleError,
"Invalid package info, should not contain --" & ev.value)
of cfgError: of cfgError:
raise newException(ENimble, "Error parsing .nimble file: " & ev.msg) raise newException(NimbleError, "Error parsing .nimble file: " & ev.msg)
close(p) close(p)
else: else:
raise newException(EInvalidValue, "Cannot open package info: " & path) raise newException(ValueError, "Cannot open package info: " & path)
validatePackageInfo(result, path) validatePackageInfo(result, path)
proc optionalField(obj: PJsonNode, name: string, default = ""): string = proc optionalField(obj: JsonNode, name: string, default = ""): string =
## Queries ``obj`` for the optional ``name`` string. ## Queries ``obj`` for the optional ``name`` string.
## ##
## Returns the value of ``name`` if it is a valid string, or aborts execution ## Returns the value of ``name`` if it is a valid string, or aborts execution
## if the field exists but is not of string type. If ``name`` is not present, ## if the field exists but is not of string type. If ``name`` is not present,
## returns ``default``. ## returns ``default``.
if existsKey(obj, name): if hasKey(obj, name):
if obj[name].kind == JString: if obj[name].kind == JString:
return obj[name].str return obj[name].str
else: else:
raise newException(ENimble, "Corrupted packages.json file. " & name & " field is of unexpected type.") raise newException(NimbleError, "Corrupted packages.json file. " & name &
" field is of unexpected type.")
else: return default else: return default
proc requiredField(obj: PJsonNode, name: string): string = proc requiredField(obj: JsonNode, name: string): string =
## Queries ``obj`` for the required ``name`` string. ## Queries ``obj`` for the required ``name`` string.
## ##
## Aborts execution if the field does not exist or is of invalid json type. ## Aborts execution if the field does not exist or is of invalid json type.
result = optionalField(obj, name, nil) result = optionalField(obj, name, nil)
if result == nil: if result == nil:
raise newException(ENimble, raise newException(NimbleError,
"Package in packages.json file does not contain a " & name & " field.") "Package in packages.json file does not contain a " & name & " field.")
proc fromJson(obj: PJSonNode): TPackage = proc fromJson(obj: JSonNode): Package =
## Constructs a TPackage object from a JSON node. ## Constructs a Package object from a JSON node.
## ##
## Aborts execution if the JSON node doesn't contain the required fields. ## Aborts execution if the JSON node doesn't contain the required fields.
result.name = obj.requiredField("name") result.name = obj.requiredField("name")
@ -215,7 +220,7 @@ proc fromJson(obj: PJSonNode): TPackage =
result.description = obj.requiredField("description") result.description = obj.requiredField("description")
result.web = obj.optionalField("web") result.web = obj.optionalField("web")
proc readMetadata*(path: string): TMetadata = proc readMetaData*(path: string): MetaData =
## Reads the metadata present in ``~/.nimble/pkgs/pkg-0.1/nimblemeta.json`` ## Reads the metadata present in ``~/.nimble/pkgs/pkg-0.1/nimblemeta.json``
var bmeta = path / "nimblemeta.json" var bmeta = path / "nimblemeta.json"
if not existsFile(bmeta): if not existsFile(bmeta):
@ -231,7 +236,7 @@ proc readMetadata*(path: string): TMetadata =
let jsonmeta = parseJson(cont) let jsonmeta = parseJson(cont)
result.url = jsonmeta["url"].str result.url = jsonmeta["url"].str
proc getPackage*(pkg: string, packagesPath: string, resPkg: var TPackage): bool = proc getPackage*(pkg: string, packagesPath: string, resPkg: var Package): bool =
## Searches ``packagesPath`` file saving into ``resPkg`` the found package. ## Searches ``packagesPath`` file saving into ``resPkg`` the found package.
## ##
## Pass in ``pkg`` the name of the package you are searching for. As ## Pass in ``pkg`` the name of the package you are searching for. As
@ -243,12 +248,12 @@ proc getPackage*(pkg: string, packagesPath: string, resPkg: var TPackage): bool
resPkg = p.fromJson() resPkg = p.fromJson()
return true return true
proc getPackageList*(packagesPath: string): seq[TPackage] = proc getPackageList*(packagesPath: string): seq[Package] =
## Returns the list of packages found at the specified path. ## Returns the list of packages found at the specified path.
result = @[] result = @[]
let packages = parseFile(packagesPath) let packages = parseFile(packagesPath)
for p in packages: for p in packages:
let pkg: TPackage = p.fromJson() let pkg: Package = p.fromJson()
result.add(pkg) result.add(pkg)
proc findNimbleFile*(dir: string): string = proc findNimbleFile*(dir: string): string =
@ -256,17 +261,20 @@ proc findNimbleFile*(dir: string): string =
for kind, path in walkDir(dir): for kind, path in walkDir(dir):
if kind == pcFile and path.splitFile.ext in [".babel", ".nimble"]: if kind == pcFile and path.splitFile.ext in [".babel", ".nimble"]:
if result != "": if result != "":
raise newException(ENimble, "Only one .nimble file should be present in " & dir) raise newException(NimbleError,
"Only one .nimble file should be present in " & dir)
result = path result = path
proc getPkgInfo*(dir: string): TPackageInfo = proc getPkgInfo*(dir: string): PackageInfo =
## Find the .nimble file in ``dir`` and parses it, returning a TPackageInfo. ## Find the .nimble file in ``dir`` and parses it, returning a PackageInfo.
let nimbleFile = findNimbleFile(dir) let nimbleFile = findNimbleFile(dir)
if nimbleFile == "": if nimbleFile == "":
raise newException(ENimble, "Specified directory does not contain a .nimble file.") raise newException(NimbleError,
"Specified directory does not contain a .nimble file.")
result = readPackageInfo(nimbleFile) result = readPackageInfo(nimbleFile)
proc getInstalledPkgs*(libsDir: string): seq[tuple[pkginfo: TPackageInfo, meta: TMetaData]] = proc getInstalledPkgs*(libsDir: string):
seq[tuple[pkginfo: PackageInfo, meta: MetaData]] =
## Gets a list of installed packages. ## Gets a list of installed packages.
## ##
## ``libsDir`` is in most cases: ~/.nimble/pkgs/ ## ``libsDir`` is in most cases: ~/.nimble/pkgs/
@ -275,15 +283,15 @@ proc getInstalledPkgs*(libsDir: string): seq[tuple[pkginfo: TPackageInfo, meta:
if kind == pcDir: if kind == pcDir:
let nimbleFile = findNimbleFile(path) let nimbleFile = findNimbleFile(path)
if nimbleFile != "": if nimbleFile != "":
let meta = readMetadata(path) let meta = readMetaData(path)
result.add((readPackageInfo(nimbleFile), meta)) result.add((readPackageInfo(nimbleFile), meta))
else: else:
# TODO: Abstract logging. # TODO: Abstract logging.
echo("WARNING: No .nimble file found for ", path) echo("WARNING: No .nimble file found for ", path)
proc findPkg*(pkglist: seq[tuple[pkginfo: TPackageInfo, meta: TMetaData]], proc findPkg*(pkglist: seq[tuple[pkginfo: PackageInfo, meta: MetaData]],
dep: TPkgTuple, dep: PkgTuple,
r: var TPackageInfo): bool = r: var PackageInfo): bool =
## Searches ``pkglist`` for a package of which version is within the range ## Searches ``pkglist`` for a package of which version is within the range
## of ``dep.ver``. ``True`` is returned if a package is found. If multiple ## of ``dep.ver``. ``True`` is returned if a package is found. If multiple
## packages are found the newest one is returned (the one with the highest ## packages are found the newest one is returned (the one with the highest
@ -298,8 +306,8 @@ proc findPkg*(pkglist: seq[tuple[pkginfo: TPackageInfo, meta: TMetaData]],
r = pkg.pkginfo r = pkg.pkginfo
result = true result = true
proc findAllPkgs*(pkglist: seq[tuple[pkginfo: TPackageInfo, meta: TMetaData]], proc findAllPkgs*(pkglist: seq[tuple[pkginfo: PackageInfo, meta: MetaData]],
dep: TPkgTuple): seq[TPackageInfo] = dep: PkgTuple): seq[PackageInfo] =
## Searches ``pkglist`` for packages of which version is within the range ## Searches ``pkglist`` for packages of which version is within the range
## of ``dep.ver``. This is similar to ``findPkg`` but returns multiple ## of ``dep.ver``. This is similar to ``findPkg`` but returns multiple
## packages if multiple are found. ## packages if multiple are found.
@ -310,7 +318,7 @@ proc findAllPkgs*(pkglist: seq[tuple[pkginfo: TPackageInfo, meta: TMetaData]],
if withinRange(newVersion(pkg.pkginfo.version), dep.ver): if withinRange(newVersion(pkg.pkginfo.version), dep.ver):
result.add pkg.pkginfo result.add pkg.pkginfo
proc getRealDir*(pkgInfo: TPackageInfo): string = proc getRealDir*(pkgInfo: PackageInfo): string =
## Returns the ``pkgInfo.srcDir`` or the .mypath directory if package does ## Returns the ``pkgInfo.srcDir`` or the .mypath directory if package does
## not specify the src dir. ## not specify the src dir.
if pkgInfo.srcDir != "": if pkgInfo.srcDir != "":
@ -334,7 +342,7 @@ proc getNameVersion*(pkgpath: string): tuple[name, version: string] =
result.version = tail[i+1 .. -1] result.version = tail[i+1 .. -1]
break break
proc echoPackage*(pkg: TPackage) = proc echoPackage*(pkg: Package) =
echo(pkg.name & ":") echo(pkg.name & ":")
echo(" url: " & pkg.url & " (" & pkg.downloadMethod & ")") echo(" url: " & pkg.url & " (" & pkg.downloadMethod & ")")
echo(" tags: " & pkg.tags.join(", ")) echo(" tags: " & pkg.tags.join(", "))
@ -343,7 +351,7 @@ proc echoPackage*(pkg: TPackage) =
if pkg.web.len > 0: if pkg.web.len > 0:
echo(" website: " & pkg.web) echo(" website: " & pkg.web)
proc getDownloadDirName*(pkg: TPackage, verRange: PVersionRange): string = proc getDownloadDirName*(pkg: Package, verRange: VersionRange): string =
result = pkg.name result = pkg.name
let verSimple = getSimpleString(verRange) let verSimple = getSimpleString(verRange)
if verSimple != "": if verSimple != "":
@ -351,5 +359,7 @@ proc getDownloadDirName*(pkg: TPackage, verRange: PVersionRange): string =
result.add verSimple result.add verSimple
when isMainModule: when isMainModule:
doAssert getNameVersion("/home/user/.nimble/libs/packagea-0.1") == ("packagea", "0.1") doAssert getNameVersion("/home/user/.nimble/libs/packagea-0.1") ==
doAssert getNameVersion("/home/user/.nimble/libs/package-a-0.1") == ("package-a", "0.1") ("packagea", "0.1")
doAssert getNameVersion("/home/user/.nimble/libs/package-a-0.1") ==
("package-a", "0.1")

View file

@ -2,25 +2,28 @@
# BSD License. Look at license.txt for more info. # BSD License. Look at license.txt for more info.
# #
# Various miscellaneous utility functions reside here. # Various miscellaneous utility functions reside here.
import osproc, pegs, strutils, os, parseurl, sets, json import osproc, pegs, strutils, os, uri, sets, json
import version, packageinfo import version, packageinfo, nimbletypes
type
ENimble* = object of EBase
proc doCmd*(cmd: string) = proc doCmd*(cmd: string) =
let bin = cmd.split(' ')[0] let bin = cmd.split(' ')[0]
if findExe(bin) == "": if findExe(bin) == "":
raise newException(ENimble, "'" & bin & "' not in PATH.") raise newException(NimbleError, "'" & bin & "' not in PATH.")
# To keep output in sequence
stdout.flushFile()
stderr.flushFile()
let exitCode = execCmd(cmd) let exitCode = execCmd(cmd)
if exitCode != QuitSuccess: if exitCode != QuitSuccess:
raise newException(ENimble, "Execution failed with exit code " & $exitCode) raise newException(NimbleError,
"Execution failed with exit code " & $exitCode)
proc doCmdEx*(cmd: string): tuple[output: TaintedString, exitCode: int] = proc doCmdEx*(cmd: string): tuple[output: TaintedString, exitCode: int] =
let bin = cmd.split(' ')[0] let bin = cmd.split(' ')[0]
if findExe(bin) == "": if findExe(bin) == "":
raise newException(ENimble, "'" & bin & "' not in PATH.") raise newException(NimbleError, "'" & bin & "' not in PATH.")
return execCmdEx(cmd) return execCmdEx(cmd)
template cd*(dir: string, body: stmt) = template cd*(dir: string, body: stmt) =
@ -36,7 +39,7 @@ proc getNimBin*: string =
if findExe("nim") != "": result = "nim" if findExe("nim") != "": result = "nim"
elif findExe("nimrod") != "": result = "nimrod" elif findExe("nimrod") != "": result = "nimrod"
proc getNimrodVersion*: TVersion = proc getNimrodVersion*: Version =
let nimBin = getNimBin() let nimBin = getNimBin()
let vOutput = doCmdEx(nimBin & " -v").output let vOutput = doCmdEx(nimBin & " -v").output
var matches: array[0..MaxSubpatterns, string] var matches: array[0..MaxSubpatterns, string]
@ -61,7 +64,7 @@ proc changeRoot*(origRoot, newRoot, path: string): string =
if path.startsWith(origRoot): if path.startsWith(origRoot):
return newRoot / path[origRoot.len .. -1] return newRoot / path[origRoot.len .. -1]
else: else:
raise newException(EInvalidValue, raise newException(ValueError,
"Cannot change root of path: Path does not begin with original root.") "Cannot change root of path: Path does not begin with original root.")
proc copyFileD*(fro, to: string): string = proc copyFileD*(fro, to: string): string =
@ -78,37 +81,37 @@ proc copyDirD*(fro, to: string): seq[string] =
createDir(changeRoot(fro, to, path.splitFile.dir)) createDir(changeRoot(fro, to, path.splitFile.dir))
result.add copyFileD(path, changeRoot(fro, to, path)) result.add copyFileD(path, changeRoot(fro, to, path))
proc getDownloadDirName*(url: string, verRange: PVersionRange): string = proc getDownloadDirName*(uri: string, verRange: VersionRange): string =
## Creates a directory name based on the specified ``url`` ## Creates a directory name based on the specified ``uri`` (url)
result = "" result = ""
let purl = parseUrl(url) let puri = parseUri(uri)
for i in purl.hostname: for i in puri.hostname:
case i case i
of strutils.Letters, strutils.Digits: of strutils.Letters, strutils.Digits:
result.add i result.add i
else: nil else: discard
result.add "_" result.add "_"
for i in purl.path: for i in puri.path:
case i case i
of strutils.Letters, strutils.Digits: of strutils.Letters, strutils.Digits:
result.add i result.add i
else: nil else: discard
let verSimple = getSimpleString(verRange) let verSimple = getSimpleString(verRange)
if verSimple != "": if verSimple != "":
result.add "_" result.add "_"
result.add verSimple result.add verSimple
proc incl*(s: var TSet[string], v: seq[string] | TSet[string]) = proc incl*(s: var HashSet[string], v: seq[string] | HashSet[string]) =
for i in v: for i in v:
s.incl i s.incl i
proc contains*(j: PJsonNode, elem: PJsonNode): bool = proc contains*(j: JsonNode, elem: JsonNode): bool =
for i in j: for i in j:
if i == elem: if i == elem:
return true return true
proc contains*(j: PJsonNode, elem: tuple[key: string, val: PJsonNode]): bool = proc contains*(j: JsonNode, elem: tuple[key: string, val: JsonNode]): bool =
for key, val in pairs(j): for key, val in pairs(j):
if key == elem.key and val == elem.val: if key == elem.key and val == elem.val:
return true return true

Binary file not shown.

View file

@ -4,10 +4,10 @@
## Module for handling versions and version ranges such as ``>= 1.0 & <= 1.5`` ## Module for handling versions and version ranges such as ``>= 1.0 & <= 1.5``
import strutils, tables, hashes, parseutils import strutils, tables, hashes, parseutils
type type
TVersion* = distinct string Version* = distinct string
TSpecial* = distinct string Special* = distinct string
TVersionRangeEnum* = enum VersionRangeEnum* = enum
verLater, # > V verLater, # > V
verEarlier, # < V verEarlier, # < V
verEqLater, # >= V -- Equal or later verEqLater, # >= V -- Equal or later
@ -17,32 +17,32 @@ type
verAny, # * verAny, # *
verSpecial # #head verSpecial # #head
PVersionRange* = ref TVersionRange VersionRange* = ref VersionRangeObj
TVersionRange* = object VersionRangeObj = object
case kind*: TVersionRangeEnum case kind*: VersionRangeEnum
of verLater, verEarlier, verEqLater, verEqEarlier, verEq: of verLater, verEarlier, verEqLater, verEqEarlier, verEq:
ver*: TVersion ver*: Version
of verSpecial: of verSpecial:
spe*: TSpecial spe*: Special
of verIntersect: of verIntersect:
verILeft, verIRight: PVersionRange verILeft, verIRight: VersionRange
of verAny: of verAny:
nil nil
EParseVersion* = object of EInvalidValue ParseVersionError* = object of ValueError
proc newVersion*(ver: string): TVersion = return TVersion(ver) proc newVersion*(ver: string): Version = return Version(ver)
proc newSpecial*(spe: string): TSpecial = return TSpecial(spe) proc newSpecial*(spe: string): Special = return Special(spe)
proc `$`*(ver: TVersion): string {.borrow.} proc `$`*(ver: Version): string {.borrow.}
proc hash*(ver: TVersion): THash {.borrow.} proc hash*(ver: Version): THash {.borrow.}
proc `$`*(ver: TSpecial): string {.borrow.} proc `$`*(ver: Special): string {.borrow.}
proc hash*(ver: TSpecial): THash {.borrow.} proc hash*(ver: Special): THash {.borrow.}
proc `<`*(ver: TVersion, ver2: TVersion): bool = proc `<`*(ver: Version, ver2: Version): bool =
var sVer = string(ver).split('.') var sVer = string(ver).split('.')
var sVer2 = string(ver2).split('.') var sVer2 = string(ver2).split('.')
for i in 0..max(sVer.len, sVer2.len)-1: for i in 0..max(sVer.len, sVer2.len)-1:
@ -55,11 +55,11 @@ proc `<`*(ver: TVersion, ver2: TVersion): bool =
if sVerI < sVerI2: if sVerI < sVerI2:
return true return true
elif sVerI == sVerI2: elif sVerI == sVerI2:
nil discard
else: else:
return false return false
proc `==`*(ver: TVersion, ver2: TVersion): bool = proc `==`*(ver: Version, ver2: Version): bool =
var sVer = string(ver).split('.') var sVer = string(ver).split('.')
var sVer2 = string(ver2).split('.') var sVer2 = string(ver2).split('.')
for i in 0..max(sVer.len, sVer2.len)-1: for i in 0..max(sVer.len, sVer2.len)-1:
@ -74,13 +74,13 @@ proc `==`*(ver: TVersion, ver2: TVersion): bool =
else: else:
return false return false
proc `==`*(spe: TSpecial, spe2: TSpecial): bool = proc `==`*(spe: Special, spe2: Special): bool =
return ($spe).toLower() == ($spe2).toLower() return ($spe).toLower() == ($spe2).toLower()
proc `<=`*(ver: TVersion, ver2: TVersion): bool = proc `<=`*(ver: Version, ver2: Version): bool =
return (ver == ver2) or (ver < ver2) return (ver == ver2) or (ver < ver2)
proc withinRange*(ver: TVersion, ran: PVersionRange): bool = proc withinRange*(ver: Version, ran: VersionRange): bool =
case ran.kind case ran.kind
of verLater: of verLater:
return ver > ran.ver return ver > ran.ver
@ -99,7 +99,7 @@ proc withinRange*(ver: TVersion, ran: PVersionRange): bool =
of verAny: of verAny:
return true return true
proc withinRange*(spe: TSpecial, ran: PVersionRange): bool = proc withinRange*(spe: Special, ran: VersionRange): bool =
case ran.kind case ran.kind
of verLater, verEarlier, verEqLater, verEqEarlier, verEq, verIntersect: of verLater, verEarlier, verEqLater, verEqEarlier, verEq, verIntersect:
return false return false
@ -108,16 +108,17 @@ proc withinRange*(spe: TSpecial, ran: PVersionRange): bool =
of verAny: of verAny:
return true return true
proc contains*(ran: PVersionRange, ver: TVersion): bool = proc contains*(ran: VersionRange, ver: Version): bool =
return withinRange(ver, ran) return withinRange(ver, ran)
proc contains*(ran: PVersionRange, spe: TSpecial): bool = proc contains*(ran: VersionRange, spe: Special): bool =
return withinRange(spe, ran) return withinRange(spe, ran)
proc makeRange*(version: string, op: string): PVersionRange = proc makeRange*(version: string, op: string): VersionRange =
new(result) new(result)
if version == "": if version == "":
raise newException(EParseVersion, "A version needs to accompany the operator.") raise newException(ParseVersionError,
"A version needs to accompany the operator.")
case op case op
of ">": of ">":
result.kind = verLater result.kind = verLater
@ -130,15 +131,15 @@ proc makeRange*(version: string, op: string): PVersionRange =
of "": of "":
result.kind = verEq result.kind = verEq
else: else:
raise newException(EParseVersion, "Invalid operator: " & op) raise newException(ParseVersionError, "Invalid operator: " & op)
result.ver = TVersion(version) result.ver = Version(version)
proc parseVersionRange*(s: string): PVersionRange = proc parseVersionRange*(s: string): VersionRange =
# >= 1.5 & <= 1.8 # >= 1.5 & <= 1.8
new(result) new(result)
if s[0] == '#': if s[0] == '#':
result.kind = verSpecial result.kind = verSpecial
result.spe = s[1 .. -1].TSpecial result.spe = s[1 .. -1].Special
return return
var i = 0 var i = 0
@ -159,7 +160,7 @@ proc parseVersionRange*(s: string): PVersionRange =
# Disallow more than one verIntersect. It's pointless and could lead to # Disallow more than one verIntersect. It's pointless and could lead to
# major unpredictable mistakes. # major unpredictable mistakes.
if result.verIRight.kind == verIntersect: if result.verIRight.kind == verIntersect:
raise newException(EParseVersion, raise newException(ParseVersionError,
"Having more than one `&` in a version range is pointless") "Having more than one `&` in a version range is pointless")
break break
@ -175,13 +176,15 @@ proc parseVersionRange*(s: string): PVersionRange =
# Make sure '0.9 8.03' is not allowed. # Make sure '0.9 8.03' is not allowed.
if version != "" and i < s.len: if version != "" and i < s.len:
if s[i+1] in {'0'..'9', '.'}: if s[i+1] in {'0'..'9', '.'}:
raise newException(EParseVersion, "Whitespace is not allowed in a version literal.") raise newException(ParseVersionError,
"Whitespace is not allowed in a version literal.")
else: else:
raise newException(EParseVersion, "Unexpected char in version range: " & s[i]) raise newException(ParseVersionError,
"Unexpected char in version range: " & s[i])
inc(i) inc(i)
proc `$`*(verRange: PVersionRange): string = proc `$`*(verRange: VersionRange): string =
case verRange.kind case verRange.kind
of verLater: of verLater:
result = "> " result = "> "
@ -202,34 +205,36 @@ proc `$`*(verRange: PVersionRange): string =
result.add(string(verRange.ver)) result.add(string(verRange.ver))
proc getSimpleString*(verRange: PVersionRange): string = proc getSimpleString*(verRange: VersionRange): string =
## Gets a string with no special symbols and spaces. Used for dir name creation ## Gets a string with no special symbols and spaces. Used for dir name
## in tools.nim ## creation in tools.nim
case verRange.kind case verRange.kind
of verSpecial: of verSpecial:
result = $verRange.spe result = $verRange.spe
of verLater, verEarlier, verEqLater, verEqEarlier, verEq: of verLater, verEarlier, verEqLater, verEqEarlier, verEq:
result = $verRange.ver result = $verRange.ver
of verIntersect: of verIntersect:
result = getSimpleString(verRange.verILeft) & "_" & getSimpleString(verRange.verIRight) result = getSimpleString(verRange.verILeft) & "_" &
getSimpleString(verRange.verIRight)
of verAny: of verAny:
result = "" result = ""
proc newVRAny*(): PVersionRange = proc newVRAny*(): VersionRange =
new(result) new(result)
result.kind = verAny result.kind = verAny
proc newVREarlier*(ver: string): PVersionRange = proc newVREarlier*(ver: string): VersionRange =
new(result) new(result)
result.kind = verEarlier result.kind = verEarlier
result.ver = newVersion(ver) result.ver = newVersion(ver)
proc newVREq*(ver: string): PVersionRange = proc newVREq*(ver: string): VersionRange =
new(result) new(result)
result.kind = verEq result.kind = verEq
result.ver = newVersion(ver) result.ver = newVersion(ver)
proc findLatest*(verRange: PVersionRange, versions: TTable[TVersion, string]): tuple[ver: TVersion, tag: string] = proc findLatest*(verRange: VersionRange,
versions: Table[Version, string]): tuple[ver: Version, tag: string] =
result = (newVersion(""), "") result = (newVersion(""), "")
for ver, tag in versions: for ver, tag in versions:
if not withinRange(ver, verRange): continue if not withinRange(ver, verRange): continue
@ -263,8 +268,10 @@ when isMainModule:
doAssert(newVersion("") < newVersion("1.0.0")) doAssert(newVersion("") < newVersion("1.0.0"))
doAssert(newVersion("") < newVersion("0.1.0")) doAssert(newVersion("") < newVersion("0.1.0"))
var versions = toTable[TVersion, string]({newVersion("0.1.1"): "v0.1.1", newVersion("0.2.3"): "v0.2.3", newVersion("0.5"): "v0.5"}) var versions = toTable[Version, string]({newVersion("0.1.1"): "v0.1.1",
doAssert findLatest(parseVersionRange(">= 0.1 & <= 0.4"), versions) == (newVersion("0.2.3"), "v0.2.3") newVersion("0.2.3"): "v0.2.3", newVersion("0.5"): "v0.5"})
doAssert findLatest(parseVersionRange(">= 0.1 & <= 0.4"), versions) ==
(newVersion("0.2.3"), "v0.2.3")
# TODO: Allow these in later versions? # TODO: Allow these in later versions?
#doAssert newVersion("0.1-rc1") < newVersion("0.2") #doAssert newVersion("0.1-rc1") < newVersion("0.2")

View file

@ -1,6 +1,6 @@
# Copyright (C) Dominik Picheta. All rights reserved. # Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info. # BSD License. Look at license.txt for more info.
import osproc, unittest, strutils, os, sequtils, future import osproc, streams, unittest, strutils, os, sequtils, future
const path = "../src/nimble" const path = "../src/nimble"
@ -15,6 +15,35 @@ template cd*(dir: string, body: stmt) =
body body
setCurrentDir(lastDir) setCurrentDir(lastDir)
proc execCmdEx2*(command: string, options: set[ProcessOption] = {
poUsePath}): tuple[
output: TaintedString,
error: TaintedString,
exitCode: int] {.tags: [ExecIOEffect, ReadIOEffect], gcsafe.} =
## A slightly altered version of osproc.execCmdEx
## Runs the `command` and returns the standard output, error output, and
## exit code.
var p = startProcess(command = command, options = options + {poEvalCommand})
var outp = outputStream(p)
var errp = errorStream(p)
result = (TaintedString"", TaintedString"", -1)
var outLine = newStringOfCap(120).TaintedString
var errLine = newStringOfCap(120).TaintedString
while true:
var checkForExit = true
if outp.readLine(outLine):
result[0].string.add(outLine.string)
result[0].string.add("\n")
checkForExit = false
if errp.readLine(errLine):
result[1].string.add(errLine.string)
result[1].string.add("\n")
checkForExit = false
if checkForExit:
result[2] = peekExitCode(p)
if result[2] != -1: break
close(p)
proc processOutput(output: string): seq[string] = proc processOutput(output: string): seq[string] =
output.strip.splitLines().filter((x: string) => (x.len > 0)) output.strip.splitLines().filter((x: string) => (x.len > 0))
@ -24,14 +53,14 @@ test "can install packagebin2":
QuitSuccess QuitSuccess
test "can reject same version dependencies": test "can reject same version dependencies":
let (outp, exitCode) = execCmdEx(path & let (outp, errp, exitCode) = execCmdEx2(path &
" install -y https://github.com/nimble-test/packagebin.git") " install -y https://github.com/nimble-test/packagebin.git")
#echo outp # We look at the error output here to avoid out-of-order problems caused by
# TODO: outp is not in the correct order. # stderr output being generated and flushed without first flushing stdout
let ls = outp.strip.splitLines() let ls = errp.strip.splitLines()
check exitCode != QuitSuccess check exitCode != QuitSuccess
check ls[ls.len-1] == "Error: unhandled exception: Cannot satisfy the " & check ls[ls.len-1] == "Error: unhandled exception: Cannot satisfy the " &
"dependency on PackageA 0.2.0 and PackageA 0.5.0 [ENimble]" "dependency on PackageA 0.2.0 and PackageA 0.5.0 [NimbleError]"
test "can update": test "can update":
check execCmdEx(path & " update").exitCode == QuitSuccess check execCmdEx(path & " update").exitCode == QuitSuccess
@ -50,11 +79,12 @@ test "issue #27":
test "can uninstall": test "can uninstall":
block: block:
let (outp, exitCode) = execCmdEx(path & " uninstall -y issue27b") let (outp, errp, exitCode) = execCmdEx2(path & " uninstall -y issue27b")
let ls = outp.processOutput() # let ls = outp.processOutput()
let ls = errp.strip.splitLines()
check exitCode != QuitSuccess check exitCode != QuitSuccess
check ls[ls.len-1] == " Cannot uninstall issue27b (0.1.0) because " & check ls[ls.len-1] == " Cannot uninstall issue27b (0.1.0) because " &
"issue27a (0.1.0) depends on it [ENimble]" "issue27a (0.1.0) depends on it [NimbleError]"
check execCmdEx(path & " uninstall -y issue27").exitCode == QuitSuccess check execCmdEx(path & " uninstall -y issue27").exitCode == QuitSuccess
check execCmdEx(path & " uninstall -y issue27a").exitCode == QuitSuccess check execCmdEx(path & " uninstall -y issue27a").exitCode == QuitSuccess
@ -62,9 +92,9 @@ test "can uninstall":
# Remove Package* # Remove Package*
check execCmdEx(path & " uninstall -y PackageA@0.5").exitCode == QuitSuccess check execCmdEx(path & " uninstall -y PackageA@0.5").exitCode == QuitSuccess
let (outp, exitCode) = execCmdEx(path & " uninstall -y PackageA") let (outp, errp, exitCode) = execCmdEx2(path & " uninstall -y PackageA")
check exitCode != QuitSuccess check exitCode != QuitSuccess
let ls = outp.processOutput() let ls = errp.processOutput()
check ls[ls.len-2].startsWith(" Cannot uninstall PackageA ") check ls[ls.len-2].startsWith(" Cannot uninstall PackageA ")
check ls[ls.len-1].startsWith(" Cannot uninstall PackageA ") check ls[ls.len-1].startsWith(" Cannot uninstall PackageA ")
check execCmdEx(path & " uninstall -y PackageBin2").exitCode == QuitSuccess check execCmdEx(path & " uninstall -y PackageBin2").exitCode == QuitSuccess
@ -76,5 +106,6 @@ test "can uninstall":
# Remove the rest of the installed packages. # Remove the rest of the installed packages.
check execCmdEx(path & " uninstall -y PackageB").exitCode == QuitSuccess check execCmdEx(path & " uninstall -y PackageB").exitCode == QuitSuccess
check execCmdEx(path & " uninstall -y PackageA@0.2 issue27b").exitCode == QuitSuccess check execCmdEx(path & " uninstall -y PackageA@0.2 issue27b").exitCode ==
QuitSuccess
check (not dirExists(getHomeDir() / ".nimble" / "pkgs" / "PackageA-0.2.0")) check (not dirExists(getHomeDir() / ".nimble" / "pkgs" / "PackageA-0.2.0"))