diff --git a/src/nimble.nim b/src/nimble.nim index 45b80ef3..3298e7cf 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -7,40 +7,41 @@ import httpclient, parseopt, os, strutils, osproc, pegs, tables, parseutils, from sequtils import toSeq import nimblepkg/packageinfo, nimblepkg/version, nimblepkg/tools, - nimblepkg/download, nimblepkg/config, nimblepkg/compat + nimblepkg/download, nimblepkg/config, nimblepkg/nimbletypes when not defined(windows): from posix import getpid type - TOptions = object - forcePrompts: TForcePrompt + Options = object + forcePrompts: ForcePrompt queryVersions: bool queryInstalled: bool - action: TAction - config: TConfig - nimbleData: PJsonNode ## Nimbledata.json - - TActionType = enum - ActionNil, ActionUpdate, ActionInit, ActionInstall, ActionSearch, - ActionList, ActionBuild, ActionPath, ActionUninstall - - TAction = object - case typ: TActionType - of ActionNil, ActionList, ActionBuild: nil - of ActionUpdate: + action: Action + config: Config + nimbleData: JsonNode ## Nimbledata.json + + ActionType = enum + actionNil, actionUpdate, actionInit, actionInstall, actionSearch, + actionList, actionBuild, actionPath, actionUninstall + + Action = object + case typ: ActionType + of actionNil, actionList, actionBuild: nil + of actionUpdate: optionalURL: string # Overrides default package list. - of ActionInstall, ActionPath, ActionUninstall: + of actionInstall, actionPath, actionUninstall: optionalName: seq[string] # \ # When this is @[], installs package from current dir. - packages: seq[TPkgTuple] # Optional only for ActionInstall. - of ActionSearch: + packages: seq[PkgTuple] # Optional only for actionInstall. + of actionSearch: search: seq[string] # Search string. - of ActionInit: + of actionInit: projName: string + else:nil - TForcePrompt = enum - DontForcePrompt, ForcePromptYes, ForcePromptNo + ForcePrompt = enum + dontForcePrompt, forcePromptYes, forcePromptNo const help = """ @@ -72,38 +73,40 @@ For more information read the Github readme: https://github.com/nim-lang/nimble#readme """ 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() = echo(help) quit(QuitSuccess) proc writeVersion() = - echo("nimble v$# compiled at $# $#" % [nimbleVersion, CompileDate, CompileTime]) + echo("nimble v$# compiled at $# $#" % + [nimbleVersion, CompileDate, CompileTime]) quit(QuitSuccess) -proc getNimbleDir(options: TOptions): string = +proc getNimbleDir(options: Options): string = options.config.nimbleDir -proc getPkgsDir(options: TOptions): string = +proc getPkgsDir(options: Options): string = options.config.nimbleDir / "pkgs" -proc getBinDir(options: TOptions): string = +proc getBinDir(options: Options): string = 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. ## ## 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 - of ForcePromptYes: + of forcePromptYes: echo(question & " -> [forced yes]") return true - of ForcePromptNo: + of forcePromptNo: echo(question & " -> [forced no]") return false - of DontForcePrompt: + of dontForcePrompt: echo(question & " [y/N]") let yn = stdin.readLine() case yn.normalize @@ -114,81 +117,88 @@ proc prompt(options: TOptions, question: string): bool = else: return false -proc renameBabelToNimble(options: TOptions) {.deprecated.} = +proc renameBabelToNimble(options: Options) {.deprecated.} = let babelDir = getHomeDir() / ".babel" let nimbleDir = getHomeDir() / ".nimble" 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) copyFile(babelDir / "babeldata.json", nimbleDir / "nimbledata.json") removeDir(babelDir) removeFile(nimbleDir / "babeldata.json") -proc parseCmdLine(): TOptions = - result.action.typ = ActionNil +proc parseCmdLine(): Options = + result.action.typ = actionNil result.config = parseConfig() for kind, key, val in getOpt(): case kind of cmdArgument: - if result.action.typ == ActionNil: + if result.action.typ == actionNil: case key of "install", "path": case key of "install": - result.action.typ = ActionInstall + result.action.typ = actionInstall of "path": - result.action.typ = ActionPath + result.action.typ = actionPath + else: + discard result.action.packages = @[] of "build": - result.action.typ = ActionBuild + result.action.typ = actionBuild of "init": - result.action.typ = ActionInit + result.action.typ = actionInit result.action.projName = "" of "update": - result.action.typ = ActionUpdate + result.action.typ = actionUpdate result.action.optionalURL = "" of "search": - result.action.typ = ActionSearch + result.action.typ = actionSearch result.action.search = @[] of "list": - result.action.typ = ActionList + result.action.typ = actionList of "uninstall", "remove", "delete", "del", "rm": - result.action.typ = ActionUninstall + result.action.typ = actionUninstall result.action.packages = @[] else: writeHelp() else: case result.action.typ - of ActionNil: + of actionNil: assert false - of ActionInstall, ActionPath, ActionUninstall: + of actionInstall, actionPath, actionUninstall: # Parse pkg@verRange if '@' in key: let i = find(key, '@') let pkgTup = (key[0 .. i-1], key[i+1 .. -1].parseVersionRange()) result.action.packages.add(pkgTup) else: - result.action.packages.add((key, PVersionRange(kind: verAny))) - of ActionUpdate: + result.action.packages.add((key, VersionRange(kind: verAny))) + of actionUpdate: result.action.optionalURL = key - of ActionSearch: + of actionSearch: result.action.search.add(key) - of ActionInit: + of actionInit: 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 - of ActionList, ActionBuild: + of actionList, actionBuild: writeHelp() + else: + discard of cmdLongOption, cmdShortOption: case key of "help", "h": writeHelp() of "version", "v": writeVersion() - of "accept", "y": result.forcePrompts = ForcePromptYes - of "reject", "n": result.forcePrompts = ForcePromptNo + of "accept", "y": result.forcePrompts = forcePromptYes + of "reject", "n": result.forcePrompts = forcePromptNo of "ver": result.queryVersions = true of "installed", "i": result.queryInstalled = true + else: discard of cmdEnd: assert(false) # cannot happen - if result.action.typ == ActionNil: + if result.action.typ == actionNil: writeHelp() # TODO: Remove this after a couple of versions. @@ -202,18 +212,18 @@ proc parseCmdLine(): TOptions = try: result.nimbleData = parseFile(nimbledataFilename) except: - raise newException(ENimble, "Couldn't parse nimbledata.json file " & + raise newException(NimbleError, "Couldn't parse nimbledata.json file " & "located at " & nimbledataFilename) else: result.nimbleData = %{"reverseDeps": newJObject()} -proc update(options: TOptions) = +proc update(options: Options) = ## Downloads the package list from the specified URL. ## ## If the download is successful, the global didUpdatePackages is set to ## true. Otherwise an exception is raised on error. let url = - if options.action.typ == ActionUpdate and options.action.optionalURL != "": + if options.action.typ == actionUpdate and options.action.optionalURL != "": options.action.optionalURL else: defaultPackageURL @@ -221,14 +231,14 @@ proc update(options: TOptions) = downloadFile(url, options.getNimbleDir() / "packages.json") echo("Done.") -proc checkInstallFile(pkgInfo: TPackageInfo, +proc checkInstallFile(pkgInfo: PackageInfo, origDir, file: string): bool = ## Checks whether ``file`` should be installed. ## ``True`` means file should be skipped. for ignoreFile in pkgInfo.skipFiles: if ignoreFile.endswith("nimble"): - raise newException(ENimble, ignoreFile & " must be installed.") + raise newException(NimbleError, ignoreFile & " must be installed.") if samePaths(file, origDir / ignoreFile): result = true break @@ -240,7 +250,7 @@ proc checkInstallFile(pkgInfo: TPackageInfo, if file.splitFile().name[0] == '.': result = true -proc checkInstallDir(pkgInfo: TPackageInfo, +proc checkInstallDir(pkgInfo: PackageInfo, origDir, dir: string): bool = ## Determines whether ``dir`` should be installed. ## ``True`` means dir should be skipped. @@ -255,7 +265,7 @@ proc checkInstallDir(pkgInfo: TPackageInfo, if thisDir == "nimcache": result = true proc copyWithExt(origDir, currentDir, dest: string, - pkgInfo: TPackageInfo): seq[string] = + pkgInfo: PackageInfo): seq[string] = ## Returns the filenames of the files that have been copied ## (their destination). result = @[] @@ -269,9 +279,9 @@ proc copyWithExt(origDir, currentDir, dest: string, result.add copyFileD(path, changeRoot(origDir, dest, path)) 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 - ## (TPackageInfo). + ## (PackageInfo). ## Returns a list of filepaths to files which have been installed. result = initSet[string]() let whitelistMode = @@ -320,13 +330,14 @@ proc copyFilesRec(origDir, currentDir, dest: string, result.incl copyFileD(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. - 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], - pkg: TPackageInfo) = - let depNameVer = dep.name & '-' & dep.version +proc addRevDep(options: Options, dep: tuple[name, version: string], + pkg: PackageInfo) = + # let depNameVer = dep.name & '-' & dep.version if not options.nimbleData["reverseDeps"].hasKey(dep.name): options.nimbleData["reverseDeps"][dep.name] = newJObject() 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: thisDep.add revDep -proc removeRevDep(options: TOptions, pkg: TPackageInfo) = +proc removeRevDep(options: Options, pkg: PackageInfo) = ## Removes ``pkg`` from the reverse dependencies of every package. - proc remove(options: TOptions, pkg: TPackageInfo, depTup: TPkgTuple, - thisDep: PJsonNode) = + proc remove(options: Options, pkg: PackageInfo, depTup: PkgTuple, + thisDep: JsonNode) = for ver, val in thisDep: if ver.newVersion in depTup.ver: var newVal = newJArray() @@ -373,10 +384,10 @@ proc removeRevDep(options: TOptions, pkg: TPackageInfo) = saveNimbleData(options) -proc install(packages: seq[TPkgTuple], - options: TOptions, - doPrompt = true): tuple[paths: seq[string], pkg: TPackageInfo] -proc processDeps(pkginfo: TPackageInfo, options: TOptions): seq[string] = +proc install(packages: seq[PkgTuple], + options: Options, + doPrompt = true): tuple[paths: seq[string], pkg: PackageInfo] +proc processDeps(pkginfo: PackageInfo, options: Options): seq[string] = ## Verifies and installs dependencies. ## ## 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 & ")") else: echo("Looking for ", dep.name, " (", $dep.ver, ")...") - var pkg: TPackageInfo + var pkg: PackageInfo if not findPkg(pkglist, dep, pkg): echo("None found, installing...") 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 # in the path. - var pkgsInPath: PStringTable = newStringTable(modeCaseSensitive) + var pkgsInPath: StringTableRef = newStringTable(modeCaseSensitive) for p in result: let (name, version) = getNameVersion(p) if pkgsInPath.hasKey(name) and pkgsInPath[name] != version: - raise newException(ENimble, + raise newException(NimbleError, "Cannot satisfy the dependency on $1 $2 and $1 $3" % [name, version, pkgsInPath[name]]) pkgsInPath[name] = version @@ -422,7 +433,7 @@ proc processDeps(pkginfo: TPackageInfo, options: TOptions): seq[string] = addRevDep(options, i, pkginfo) saveNimbleData(options) -proc buildFromDir(pkgInfo: TPackageInfo, paths: seq[string]) = +proc buildFromDir(pkgInfo: PackageInfo, paths: seq[string]) = ## Builds a package as specified by ``pkgInfo``. let realDir = pkgInfo.getRealDir() var args = "" @@ -433,19 +444,19 @@ proc buildFromDir(pkgInfo: TPackageInfo, paths: seq[string]) = doCmd(getNimBin() & " $# -d:release --noBabelPath $# \"$#\"" % [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} nimblemeta["files"] = newJArray() for file in filesInstalled: nimblemeta["files"].add(%changeRoot(pkgDestDir, "", file)) 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``. try: var nimblemeta = parseFile(dir / "nimblemeta.json") if not nimblemeta.hasKey("files"): - raise newException(EJsonParsingError, + raise newException(JsonParsingError, "Meta data does not contain required info.") for file in nimblemeta["files"]: removeFile(dir / file.str) @@ -458,15 +469,15 @@ proc removePkgDir(dir: string, options: TOptions) = else: echo("WARNING: Cannot completely remove " & dir & ". Files not installed by nimble are present.") - except EOS, EJsonParsingError: + except OSError, JsonParsingError: echo("Error: Unable to read nimblemeta.json: ", getCurrentExceptionMsg()) if not options.prompt("Would you like to COMPLETELY remove ALL files " & "in " & dir & "?"): quit(QuitSuccess) removeDir(dir) -proc installFromDir(dir: string, latest: bool, options: TOptions, - url: string): tuple[paths: seq[string], pkg: TPackageInfo] = +proc installFromDir(dir: string, latest: bool, options: Options, + url: string): tuple[paths: seq[string], pkg: PackageInfo] = ## Returns where package has been installed to, together with paths ## to the packages this package depends on. ## 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 pkgDestDir = pkgsDir / (pkgInfo.name & versionStr) 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) removePkgDir(pkgDestDir, options) # Remove any symlinked binaries @@ -503,7 +515,7 @@ proc installFromDir(dir: string, latest: bool, options: TOptions, removeFile(binDir / bin) ## Will contain a list of files which have been installed. - var filesInstalled: TSet[string] + var filesInstalled: HashSet[string] createDir(pkgDestDir) if pkgInfo.bin.len > 0: @@ -568,32 +580,34 @@ proc getNimbleTempDir(): string = else: result.add($getpid()) -proc downloadPkg(url: string, verRange: PVersionRange, - downMethod: TDownloadMethod): string = +proc downloadPkg(url: string, verRange: VersionRange, + downMethod: DownloadMethod): string = let downloadDir = (getNimbleTempDir() / getDownloadDirName(url, verRange)) createDir(downloadDir) echo("Downloading ", url, " into ", downloadDir, " using ", downMethod, "...") doDownload(url, downloadDir, verRange, downMethod) result = downloadDir -proc downloadPkg(pkg: TPackage, verRange: PVersionRange): string = +proc downloadPkg(pkg: Package, verRange: VersionRange): string = let downloadDir = (getNimbleTempDir() / getDownloadDirName(pkg, verRange)) let downMethod = pkg.downloadMethod.getDownloadMethod() createDir(downloadDir) - echo("Downloading ", pkg.name, " into ", downloadDir, " using ", downMethod, "...") + echo("Downloading ", pkg.name, " into ", downloadDir, " using ", downMethod, + "...") doDownload(pkg.url, downloadDir, verRange, downMethod) result = downloadDir -proc install(packages: seq[TPkgTuple], - options: TOptions, - doPrompt = true): tuple[paths: seq[string], pkg: TPackageInfo] = - if packages == @[]: +proc install(packages: seq[PkgTuple], + options: Options, + doPrompt = true): tuple[paths: seq[string], pkg: PackageInfo] = + if packages == @[]: result = installFromDir(getCurrentDir(), false, options, "") - else: + else: # If packages.json is not present ask the user if they want to download it. if not existsFile(options.getNimbleDir / "packages.json"): 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) else: quit("Please run nimble update.", QuitFailure) @@ -605,34 +619,35 @@ proc install(packages: seq[TPkgTuple], let downloadDir = downloadPkg(pv.name, pv.ver, meth) result = installFromDir(downloadDir, false, options, pv.name) else: - var pkg: TPackage + var pkg: Package if getPackage(pv.name, options.getNimbleDir() / "packages.json", pkg): let downloadDir = downloadPkg(pkg, pv.ver) result = installFromDir(downloadDir, false, options, pkg.url) 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 options.prompt(pv.name & " not found in local packages.json, " & "check internet for updated packages?"): update(options) result = install(@[pv], options, false) 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()) let paths = processDeps(pkginfo, options) buildFromDir(pkgInfo, paths) -proc search(options: TOptions) = +proc search(options: Options) = ## Searches for matches in ``options.action.search``. ## ## 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 == @[]: - raise newException(ENimble, "Please specify a search string.") + raise newException(NimbleError, "Please specify a search string.") 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") var found = false template onFound: stmt = @@ -656,9 +671,9 @@ proc search(options: TOptions) = if not found: echo("No package found.") -proc list(options: TOptions) = +proc list(options: Options) = 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") for pkg in pkgList: echoPackage(pkg) @@ -666,7 +681,7 @@ proc list(options: TOptions) = echoPackageVersions(pkg) echo(" ") -proc listInstalled(options: TOptions) = +proc listInstalled(options: Options) = var h = initTable[string, seq[string]]() let pkgs = getInstalledPkgs(options.getPkgsDir()) for x in pkgs.items(): @@ -680,9 +695,9 @@ proc listInstalled(options: TOptions) = for k in keys(h): 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. ## ## 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. ## ## On success the proc returns normally. - assert options.action.typ == ActionPath + assert options.action.typ == actionPath assert(not options.action.packages.isNil) var errors = 0 for name, version in options.action.packages.items: @@ -721,26 +736,28 @@ proc listPaths(options: TOptions) = echo "Warning: Package '" & name & "' not installed" errors += 1 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!") var pkgName, fName: string = "" - outFile: TFile + outFile: File if (options.action.projName != ""): pkgName = options.action.projName fName = pkgName & ".nimble" if (existsFile(os.getCurrentDir() / fName)): - raise newException(ENimble, "Already have a nimble file.") + raise newException(NimbleError, "Already have a nimble file.") 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) if (pkgName == ""): pkgName = os.getCurrentDir().splitPath.tail if (pkgName == ""): - raise newException(ENimble, "Could not get default file path.") + raise newException(NimbleError, "Could not get default file path.") fName = pkgName & ".nimble" # Now need to write out .nimble file with projName and other details @@ -759,18 +776,18 @@ proc init(options: TOptions) = close(outFile) else: - raise newException(ENimble, "Unable to open file " & fName & - " for writing: " & osErrorMsg()) + raise newException(NimbleError, "Unable to open file " & fName & + " for writing: " & osErrorMsg(osLastError())) -proc uninstall(options: TOptions) = - var pkgsToDelete: seq[TPackageInfo] = @[] +proc uninstall(options: Options) = + var pkgsToDelete: seq[PackageInfo] = @[] # Do some verification. for pkgTup in options.action.packages: echo("Looking for ", pkgTup.name, " (", $pkgTup.ver, ")...") let installedPkgs = getInstalledPkgs(options.getPkgsDir()) var pkgList = findAllPkgs(installedPkgs, pkgTup) if pkgList.len == 0: - raise newException(ENimble, "Package not found") + raise newException(NimbleError, "Package not found") echo("Checking reverse dependencies...") var errors: seq[string] = @[] @@ -796,7 +813,7 @@ proc uninstall(options: TOptions) = pkgsToDelete.add pkg if pkgsToDelete.len == 0: - raise newException(ENimble, "\n " & errors.join("\n ")) + raise newException(NimbleError, "\n " & errors.join("\n ")) var pkgNames = "" for i in 0 .. = 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 diff --git a/src/nimblepkg/config.nim b/src/nimblepkg/config.nim index d8d68f17..244509c5 100644 --- a/src/nimblepkg/config.nim +++ b/src/nimblepkg/config.nim @@ -2,15 +2,15 @@ # BSD License. Look at license.txt for more info. import parsecfg, streams, strutils, os -import tools, version +import tools, version, nimbletypes type - TConfig* = object + Config* = object nimbleDir*: string 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"): result.nimbleDir = getHomeDir() / ".nimble" else: @@ -18,7 +18,7 @@ proc initConfig(): TConfig = result.chcp = true -proc parseConfig*(): TConfig = +proc parseConfig*(): Config = result = initConfig() var confFile = getConfigDir() / "nimble" / "nimble.ini" @@ -32,7 +32,7 @@ proc parseConfig*(): TConfig = if f != nil: echo("Reading from config file at ", confFile) - var p: TCfgParser + var p: CfgParser open(p, f, confFile) while true: var e = next(p) @@ -49,8 +49,8 @@ proc parseConfig*(): TConfig = of "chcp": result.chcp = parseBool(e.value) else: - raise newException(ENimble, "Unable to parse config file:" & + raise newException(NimbleError, "Unable to parse config file:" & " Unknown key: " & e.key) of cfgError: - raise newException(ENimble, "Unable to parse config file: " & e.msg) + raise newException(NimbleError, "Unable to parse config file: " & e.msg) close(p) diff --git a/src/nimblepkg/download.nim b/src/nimblepkg/download.nim index e69d0d1e..9e2ebac4 100644 --- a/src/nimblepkg/download.nim +++ b/src/nimblepkg/download.nim @@ -3,47 +3,48 @@ import parseutils, os, osproc, strutils, tables, pegs -import packageinfo, version, tools +import packageinfo, version, tools, nimbletypes type - TDownloadMethod* {.pure.} = enum - Git = "git", Hg = "hg" + DownloadMethod* {.pure.} = enum + git = "git", hg = "hg" -proc getSpecificDir(meth: TDownloadMethod): string = +proc getSpecificDir(meth: DownloadMethod): string = case meth - of TDownloadMethod.Git: + of DownloadMethod.git: ".git" - of TDownloadMethod.Hg: + of DownloadMethod.hg: ".hg" -proc doCheckout(meth: TDownloadMethod, downloadDir, branch: string) = +proc doCheckout(meth: DownloadMethod, downloadDir, branch: string) = case meth - of TDownloadMethod.Git: + of DownloadMethod.git: cd downloadDir: # 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 # messes up the damn line endings. doCmd("git checkout --force " & branch) - of TDownloadMethod.Hg: + of DownloadMethod.hg: cd downloadDir: doCmd("hg checkout " & branch) -proc doPull(meth: TDownloadMethod, downloadDir: string) = +proc doPull(meth: DownloadMethod, downloadDir: string) = case meth - of TDownloadMethod.Git: + of DownloadMethod.git: doCheckout(meth, downloadDir, "master") cd downloadDir: doCmd("git pull") if existsFile(".gitmodules"): doCmd("git submodule update") - of TDownloadMethod.Hg: + of DownloadMethod.hg: doCheckout(meth, downloadDir, "default") cd downloadDir: 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 - of TDownloadMethod.Git: + of DownloadMethod.git: let depthArg = if tip: "--depth 1 " else: "" 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 checkout --force " & branchArg) doCmd("git submodule update --init --recursive") - of TDownloadMethod.Hg: + of DownloadMethod.hg: let tipArg = if tip: "-r tip " else: "" branchArg = if branch == "": "" else: "-b " & branch & " " 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: var output = execProcess("git tag") case meth - of TDownloadMethod.Git: + of DownloadMethod.git: output = execProcess("git tag") - of TDownloadMethod.Hg: + of DownloadMethod.hg: output = execProcess("hg tags") if output.len > 0: case meth - of TDownloadMethod.Git: + of DownloadMethod.git: result = @[] for i in output.splitLines(): if i == "": continue result.add(i) - of TDownloadMethod.Hg: + of DownloadMethod.hg: result = @[] for i in output.splitLines(): if i == "": continue @@ -92,13 +93,13 @@ proc getTagsList(dir: string, meth: TDownloadMethod): seq[string] = else: result = @[] -proc getTagsListRemote*(url: string, meth: TDownloadMethod): seq[string] = +proc getTagsListRemote*(url: string, meth: DownloadMethod): seq[string] = result = @[] case meth - of TDownloadMethod.Git: + of DownloadMethod.git: var (output, exitCode) = doCmdEx("git ls-remote --tags " & url) 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) for i in output.splitLines(): if i == "": continue @@ -106,47 +107,47 @@ proc getTagsListRemote*(url: string, meth: TDownloadMethod): seq[string] = let tag = i[start .. -1] if not tag.endswith("^{}"): result.add(tag) - of TDownloadMethod.Hg: + of DownloadMethod.hg: # 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 - result = initTable[TVersion, string]() + result = initTable[Version, string]() for tag in tags: if tag != "": let i = skipUntil(tag, Digits) # skip any chars before the version # TODO: Better checking, tags can have any names. Add warnings and such. result[newVersion(tag[i .. -1])] = tag -proc getDownloadMethod*(meth: string): TDownloadMethod = +proc getDownloadMethod*(meth: string): DownloadMethod = case meth - of "git": return TDownloadMethod.Git - of "hg", "mercurial": return TDownloadMethod.Hg + of "git": return DownloadMethod.git + of "hg", "mercurial": return DownloadMethod.hg 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 ## it's ``head`` for hg it's ``tip``. case meth - of TDownloadMethod.Git: "head" - of TDownloadMethod.Hg: "tip" + of DownloadMethod.git: "head" + of DownloadMethod.hg: "tip" -proc checkUrlType*(url: string): TDownloadMethod = +proc checkUrlType*(url: string): DownloadMethod = ## Determines the download method based on the URL. if doCmdEx("git ls-remote " & url).exitCode == QuitSuccess: - return TDownloadMethod.Git + return DownloadMethod.git elif doCmdEx("hg identify " & url).exitCode == QuitSuccess: - return TDownloadMethod.Hg + return DownloadMethod.hg else: - raise newException(ENimble, "Unable to identify url.") + raise newException(NimbleError, "Unable to identify url.") proc isURL*(name: string): bool = name.startsWith(peg" @'://' ") -proc doDownload*(url: string, downloadDir: string, verRange: PVersionRange, - downMethod: TDownloadMethod) = +proc doDownload*(url: string, downloadDir: string, verRange: VersionRange, + downMethod: DownloadMethod) = template getLatestByTag(meth: stmt): stmt {.dirty, immediate.} = echo("Found tags...") # Find latest version that fits our ``verRange``. @@ -164,7 +165,7 @@ proc doDownload*(url: string, downloadDir: string, verRange: PVersionRange, ## version range. let pkginfo = getPkgInfo(downloadDir) if pkginfo.version.newVersion notin verRange: - raise newException(ENimble, + raise newException(NimbleError, "Downloaded package's version does not satisfy requested version " & "range: wanted $1 got $2." % [$verRange, $pkginfo.version]) @@ -177,14 +178,14 @@ proc doDownload*(url: string, downloadDir: string, verRange: PVersionRange, else: # Mercurial requies a clone and checkout. The git clone operation is # already fragmented into multiple steps so we just call doClone(). - if downMethod == TDownloadMethod.Git: + if downMethod == DownloadMethod.git: doClone(downMethod, url, downloadDir, $verRange.spe) else: doClone(downMethod, url, downloadDir, tip = false) doCheckout(downMethod, downloadDir, $verRange.spe) else: case downMethod - of TDownloadMethod.Git: + of DownloadMethod.git: # 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. let versions = getTagsListRemote(url, downMethod).getVersionList() @@ -197,7 +198,7 @@ proc doDownload*(url: string, downloadDir: string, verRange: PVersionRange, doClone(downMethod, url, downloadDir) # Grab HEAD. verifyClone() - of TDownloadMethod.Hg: + of DownloadMethod.hg: doClone(downMethod, url, downloadDir) let versions = getTagsList(downloadDir, downMethod).getVersionList() @@ -208,10 +209,10 @@ proc doDownload*(url: string, downloadDir: string, verRange: PVersionRange, verifyClone() -proc echoPackageVersions*(pkg: TPackage) = +proc echoPackageVersions*(pkg: Package) = let downMethod = pkg.downloadMethod.getDownloadMethod() case downMethod - of TDownloadMethod.Git: + of DownloadMethod.git: try: let versions = getTagsListRemote(pkg.url, downMethod).getVersionList() if versions.len > 0: @@ -225,7 +226,8 @@ proc echoPackageVersions*(pkg: TPackage) = echo(" versions: " & vstr) else: echo(" versions: (No versions tagged in the remote repository)") - except EOS: + except OSError: echo(getCurrentExceptionMsg()) - of TDownloadMethod.Hg: - echo(" versions: (Remote tag retrieval not supported by " & pkg.downloadMethod & ")") + of DownloadMethod.hg: + echo(" versions: (Remote tag retrieval not supported by " & + pkg.downloadMethod & ")") diff --git a/src/nimblepkg/nimbletypes.nim b/src/nimblepkg/nimbletypes.nim new file mode 100644 index 00000000..cdfc6469 --- /dev/null +++ b/src/nimblepkg/nimbletypes.nim @@ -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 diff --git a/src/nimblepkg/packageinfo.nim b/src/nimblepkg/packageinfo.nim index 8b3056e6..42ccca12 100644 --- a/src/nimblepkg/packageinfo.nim +++ b/src/nimblepkg/packageinfo.nim @@ -1,12 +1,12 @@ # Copyright (C) Dominik Picheta. All rights reserved. # BSD License. Look at license.txt for more info. import parsecfg, json, streams, strutils, parseutils, os -import version, tools +import version, tools, nimbletypes type ## 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 name*: string version*: string @@ -19,12 +19,12 @@ type installDirs*: seq[string] installFiles*: seq[string] installExt*: seq[string] - requires*: seq[TPkgTuple] + requires*: seq[PkgTuple] bin*: seq[string] srcDir*: string backend*: string - TPackage* = object + Package* = object # Required fields in a package. name*: string url*: string # Download location. @@ -37,10 +37,10 @@ type dvcsTag*: string web*: string # Info url for humans. - TMetadata* = object + MetaData* = object url*: string -proc initPackageInfo(): TPackageInfo = +proc initPackageInfo(): PackageInfo = result.mypath = "" result.name = "" result.version = "" @@ -58,31 +58,32 @@ proc initPackageInfo(): TPackageInfo = result.srcDir = "" result.backend = "c" -proc validatePackageInfo(pkgInfo: TPackageInfo, path: string) = +proc validatePackageInfo(pkgInfo: PackageInfo, path: string) = if pkgInfo.name == "": - raise newException(ENimble, "Incorrect .nimble file: " & path & + raise newException(NimbleError, "Incorrect .nimble file: " & path & " does not contain a name field.") if pkgInfo.version == "": - raise newException(ENimble, "Incorrect .nimble file: " & path & + raise newException(NimbleError, "Incorrect .nimble file: " & path & " does not contain a version field.") if pkgInfo.author == "": - raise newException(ENimble, "Incorrect .nimble file: " & path & + raise newException(NimbleError, "Incorrect .nimble file: " & path & " does not contain an author field.") if pkgInfo.description == "": - raise newException(ENimble, "Incorrect .nimble file: " & path & + raise newException(NimbleError, "Incorrect .nimble file: " & path & " does not contain a description field.") if pkgInfo.license == "": - raise newException(ENimble, "Incorrect .nimble file: " & path & + raise newException(NimbleError, "Incorrect .nimble file: " & path & " does not contain a license field.") 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: if c notin ({'.'} + Digits): - raise newException(ENimble, + raise newException(NimbleError, "Version may only consist of numbers and the '.' character " & "but found '" & c & "'.") -proc parseRequires(req: string): TPkgTuple = +proc parseRequires(req: string): PkgTuple = try: if ' ' in req: var i = skipUntil(req, Whitespace) @@ -94,10 +95,10 @@ proc parseRequires(req: string): TPkgTuple = result.ver = parseVersionRange(req[i .. -1]) else: result.name = req.strip - result.ver = PVersionRange(kind: verAny) - except EParseVersion: - raise newException(ENimble, "Unable to parse dependency version range: " & - getCurrentExceptionMsg()) + result.ver = VersionRange(kind: verAny) + except ParseVersionError: + raise newException(NimbleError, + "Unable to parse dependency version range: " & getCurrentExceptionMsg()) proc multiSplit(s: string): seq[string] = ## Returns ``s`` split by newline and comma characters. @@ -115,12 +116,12 @@ proc multiSplit(s: string): seq[string] = if len(result) < 1: return @[s] -proc readPackageInfo*(path: string): TPackageInfo = +proc readPackageInfo*(path: string): PackageInfo = result = initPackageInfo() result.mypath = path var fs = newFileStream(path, fmRead) if fs != nil: - var p: TCfgParser + var p: CfgParser open(p, fs, path) var currentSection = "" while true: @@ -159,48 +160,52 @@ proc readPackageInfo*(path: string): TPackageInfo = result.backend = ev.value.toLower() case result.backend.normalize of "javascript": result.backend = "js" + else: discard else: - raise newException(ENimble, "Invalid field: " & ev.key) + raise newException(NimbleError, "Invalid field: " & ev.key) of "deps", "dependencies": case ev.key.normalize of "requires": for v in ev.value.multiSplit: result.requires.add(parseRequires(v.strip)) else: - raise newException(ENimble, "Invalid field: " & ev.key) - else: raise newException(ENimble, "Invalid section: " & currentSection) - of cfgOption: raise newException(ENimble, "Invalid package info, should not contain --" & ev.value) + raise newException(NimbleError, "Invalid field: " & ev.key) + else: raise newException(NimbleError, + "Invalid section: " & currentSection) + of cfgOption: raise newException(NimbleError, + "Invalid package info, should not contain --" & ev.value) of cfgError: - raise newException(ENimble, "Error parsing .nimble file: " & ev.msg) + raise newException(NimbleError, "Error parsing .nimble file: " & ev.msg) close(p) else: - raise newException(EInvalidValue, "Cannot open package info: " & path) + raise newException(ValueError, "Cannot open package info: " & 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. ## ## 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, ## returns ``default``. - if existsKey(obj, name): + if hasKey(obj, name): if obj[name].kind == JString: return obj[name].str 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 -proc requiredField(obj: PJsonNode, name: string): string = +proc requiredField(obj: JsonNode, name: string): string = ## Queries ``obj`` for the required ``name`` string. ## ## Aborts execution if the field does not exist or is of invalid json type. result = optionalField(obj, name, nil) if result == nil: - raise newException(ENimble, + raise newException(NimbleError, "Package in packages.json file does not contain a " & name & " field.") -proc fromJson(obj: PJSonNode): TPackage = - ## Constructs a TPackage object from a JSON node. +proc fromJson(obj: JSonNode): Package = + ## Constructs a Package object from a JSON node. ## ## Aborts execution if the JSON node doesn't contain the required fields. result.name = obj.requiredField("name") @@ -215,7 +220,7 @@ proc fromJson(obj: PJSonNode): TPackage = result.description = obj.requiredField("description") 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`` var bmeta = path / "nimblemeta.json" if not existsFile(bmeta): @@ -231,7 +236,7 @@ proc readMetadata*(path: string): TMetadata = let jsonmeta = parseJson(cont) 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. ## ## 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() return true -proc getPackageList*(packagesPath: string): seq[TPackage] = +proc getPackageList*(packagesPath: string): seq[Package] = ## Returns the list of packages found at the specified path. result = @[] let packages = parseFile(packagesPath) for p in packages: - let pkg: TPackage = p.fromJson() + let pkg: Package = p.fromJson() result.add(pkg) proc findNimbleFile*(dir: string): string = @@ -256,17 +261,20 @@ proc findNimbleFile*(dir: string): string = for kind, path in walkDir(dir): if kind == pcFile and path.splitFile.ext in [".babel", ".nimble"]: 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 -proc getPkgInfo*(dir: string): TPackageInfo = - ## Find the .nimble file in ``dir`` and parses it, returning a TPackageInfo. +proc getPkgInfo*(dir: string): PackageInfo = + ## Find the .nimble file in ``dir`` and parses it, returning a PackageInfo. let nimbleFile = findNimbleFile(dir) 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) -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. ## ## ``libsDir`` is in most cases: ~/.nimble/pkgs/ @@ -275,15 +283,15 @@ proc getInstalledPkgs*(libsDir: string): seq[tuple[pkginfo: TPackageInfo, meta: if kind == pcDir: let nimbleFile = findNimbleFile(path) if nimbleFile != "": - let meta = readMetadata(path) + let meta = readMetaData(path) result.add((readPackageInfo(nimbleFile), meta)) else: # TODO: Abstract logging. echo("WARNING: No .nimble file found for ", path) -proc findPkg*(pkglist: seq[tuple[pkginfo: TPackageInfo, meta: TMetaData]], - dep: TPkgTuple, - r: var TPackageInfo): bool = +proc findPkg*(pkglist: seq[tuple[pkginfo: PackageInfo, meta: MetaData]], + dep: PkgTuple, + r: var PackageInfo): bool = ## 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 ## 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 result = true -proc findAllPkgs*(pkglist: seq[tuple[pkginfo: TPackageInfo, meta: TMetaData]], - dep: TPkgTuple): seq[TPackageInfo] = +proc findAllPkgs*(pkglist: seq[tuple[pkginfo: PackageInfo, meta: MetaData]], + dep: PkgTuple): seq[PackageInfo] = ## Searches ``pkglist`` for packages of which version is within the range ## of ``dep.ver``. This is similar to ``findPkg`` but returns multiple ## 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): 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 ## not specify the src dir. if pkgInfo.srcDir != "": @@ -334,7 +342,7 @@ proc getNameVersion*(pkgpath: string): tuple[name, version: string] = result.version = tail[i+1 .. -1] break -proc echoPackage*(pkg: TPackage) = +proc echoPackage*(pkg: Package) = echo(pkg.name & ":") echo(" url: " & pkg.url & " (" & pkg.downloadMethod & ")") echo(" tags: " & pkg.tags.join(", ")) @@ -343,7 +351,7 @@ proc echoPackage*(pkg: TPackage) = if pkg.web.len > 0: echo(" website: " & pkg.web) -proc getDownloadDirName*(pkg: TPackage, verRange: PVersionRange): string = +proc getDownloadDirName*(pkg: Package, verRange: VersionRange): string = result = pkg.name let verSimple = getSimpleString(verRange) if verSimple != "": @@ -351,5 +359,7 @@ proc getDownloadDirName*(pkg: TPackage, verRange: PVersionRange): string = result.add verSimple when isMainModule: - doAssert getNameVersion("/home/user/.nimble/libs/packagea-0.1") == ("packagea", "0.1") - doAssert getNameVersion("/home/user/.nimble/libs/package-a-0.1") == ("package-a", "0.1") + doAssert getNameVersion("/home/user/.nimble/libs/packagea-0.1") == + ("packagea", "0.1") + doAssert getNameVersion("/home/user/.nimble/libs/package-a-0.1") == + ("package-a", "0.1") diff --git a/src/nimblepkg/tools.nim b/src/nimblepkg/tools.nim index 295c0bb6..7ced95a9 100644 --- a/src/nimblepkg/tools.nim +++ b/src/nimblepkg/tools.nim @@ -2,25 +2,28 @@ # BSD License. Look at license.txt for more info. # # Various miscellaneous utility functions reside here. -import osproc, pegs, strutils, os, parseurl, sets, json -import version, packageinfo - -type - ENimble* = object of EBase +import osproc, pegs, strutils, os, uri, sets, json +import version, packageinfo, nimbletypes proc doCmd*(cmd: string) = let bin = cmd.split(' ')[0] 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) + 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] = let bin = cmd.split(' ')[0] if findExe(bin) == "": - raise newException(ENimble, "'" & bin & "' not in PATH.") + raise newException(NimbleError, "'" & bin & "' not in PATH.") return execCmdEx(cmd) template cd*(dir: string, body: stmt) = @@ -36,7 +39,7 @@ proc getNimBin*: string = if findExe("nim") != "": result = "nim" elif findExe("nimrod") != "": result = "nimrod" -proc getNimrodVersion*: TVersion = +proc getNimrodVersion*: Version = let nimBin = getNimBin() let vOutput = doCmdEx(nimBin & " -v").output var matches: array[0..MaxSubpatterns, string] @@ -61,7 +64,7 @@ proc changeRoot*(origRoot, newRoot, path: string): string = if path.startsWith(origRoot): return newRoot / path[origRoot.len .. -1] else: - raise newException(EInvalidValue, + raise newException(ValueError, "Cannot change root of path: Path does not begin with original root.") proc copyFileD*(fro, to: string): string = @@ -78,37 +81,37 @@ proc copyDirD*(fro, to: string): seq[string] = createDir(changeRoot(fro, to, path.splitFile.dir)) result.add copyFileD(path, changeRoot(fro, to, path)) -proc getDownloadDirName*(url: string, verRange: PVersionRange): string = - ## Creates a directory name based on the specified ``url`` +proc getDownloadDirName*(uri: string, verRange: VersionRange): string = + ## Creates a directory name based on the specified ``uri`` (url) result = "" - let purl = parseUrl(url) - for i in purl.hostname: + let puri = parseUri(uri) + for i in puri.hostname: case i of strutils.Letters, strutils.Digits: result.add i - else: nil + else: discard result.add "_" - for i in purl.path: + for i in puri.path: case i of strutils.Letters, strutils.Digits: result.add i - else: nil + else: discard let verSimple = getSimpleString(verRange) if verSimple != "": result.add "_" 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: s.incl i -proc contains*(j: PJsonNode, elem: PJsonNode): bool = +proc contains*(j: JsonNode, elem: JsonNode): bool = for i in j: if i == elem: 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): if key == elem.key and val == elem.val: return true diff --git a/src/nimblepkg/version b/src/nimblepkg/version deleted file mode 100755 index c152294b..00000000 Binary files a/src/nimblepkg/version and /dev/null differ diff --git a/src/nimblepkg/version.nim b/src/nimblepkg/version.nim index 8cc4cd0d..f42eea6f 100644 --- a/src/nimblepkg/version.nim +++ b/src/nimblepkg/version.nim @@ -4,10 +4,10 @@ ## Module for handling versions and version ranges such as ``>= 1.0 & <= 1.5`` import strutils, tables, hashes, parseutils type - TVersion* = distinct string - TSpecial* = distinct string + Version* = distinct string + Special* = distinct string - TVersionRangeEnum* = enum + VersionRangeEnum* = enum verLater, # > V verEarlier, # < V verEqLater, # >= V -- Equal or later @@ -17,32 +17,32 @@ type verAny, # * verSpecial # #head - PVersionRange* = ref TVersionRange - TVersionRange* = object - case kind*: TVersionRangeEnum + VersionRange* = ref VersionRangeObj + VersionRangeObj = object + case kind*: VersionRangeEnum of verLater, verEarlier, verEqLater, verEqEarlier, verEq: - ver*: TVersion + ver*: Version of verSpecial: - spe*: TSpecial + spe*: Special of verIntersect: - verILeft, verIRight: PVersionRange + verILeft, verIRight: VersionRange of verAny: nil - EParseVersion* = object of EInvalidValue + ParseVersionError* = object of ValueError -proc newVersion*(ver: string): TVersion = return TVersion(ver) -proc newSpecial*(spe: string): TSpecial = return TSpecial(spe) +proc newVersion*(ver: string): Version = return Version(ver) +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 sVer2 = string(ver2).split('.') for i in 0..max(sVer.len, sVer2.len)-1: @@ -55,11 +55,11 @@ proc `<`*(ver: TVersion, ver2: TVersion): bool = if sVerI < sVerI2: return true elif sVerI == sVerI2: - nil + discard else: return false -proc `==`*(ver: TVersion, ver2: TVersion): bool = +proc `==`*(ver: Version, ver2: Version): bool = var sVer = string(ver).split('.') var sVer2 = string(ver2).split('.') for i in 0..max(sVer.len, sVer2.len)-1: @@ -74,13 +74,13 @@ proc `==`*(ver: TVersion, ver2: TVersion): bool = else: return false -proc `==`*(spe: TSpecial, spe2: TSpecial): bool = +proc `==`*(spe: Special, spe2: Special): bool = return ($spe).toLower() == ($spe2).toLower() -proc `<=`*(ver: TVersion, ver2: TVersion): bool = +proc `<=`*(ver: Version, ver2: Version): bool = return (ver == ver2) or (ver < ver2) -proc withinRange*(ver: TVersion, ran: PVersionRange): bool = +proc withinRange*(ver: Version, ran: VersionRange): bool = case ran.kind of verLater: return ver > ran.ver @@ -99,7 +99,7 @@ proc withinRange*(ver: TVersion, ran: PVersionRange): bool = of verAny: return true -proc withinRange*(spe: TSpecial, ran: PVersionRange): bool = +proc withinRange*(spe: Special, ran: VersionRange): bool = case ran.kind of verLater, verEarlier, verEqLater, verEqEarlier, verEq, verIntersect: return false @@ -108,16 +108,17 @@ proc withinRange*(spe: TSpecial, ran: PVersionRange): bool = of verAny: return true -proc contains*(ran: PVersionRange, ver: TVersion): bool = +proc contains*(ran: VersionRange, ver: Version): bool = return withinRange(ver, ran) -proc contains*(ran: PVersionRange, spe: TSpecial): bool = +proc contains*(ran: VersionRange, spe: Special): bool = return withinRange(spe, ran) -proc makeRange*(version: string, op: string): PVersionRange = +proc makeRange*(version: string, op: string): VersionRange = new(result) if version == "": - raise newException(EParseVersion, "A version needs to accompany the operator.") + raise newException(ParseVersionError, + "A version needs to accompany the operator.") case op of ">": result.kind = verLater @@ -130,15 +131,15 @@ proc makeRange*(version: string, op: string): PVersionRange = of "": result.kind = verEq else: - raise newException(EParseVersion, "Invalid operator: " & op) - result.ver = TVersion(version) + raise newException(ParseVersionError, "Invalid operator: " & op) + result.ver = Version(version) -proc parseVersionRange*(s: string): PVersionRange = +proc parseVersionRange*(s: string): VersionRange = # >= 1.5 & <= 1.8 new(result) if s[0] == '#': result.kind = verSpecial - result.spe = s[1 .. -1].TSpecial + result.spe = s[1 .. -1].Special return var i = 0 @@ -159,7 +160,7 @@ proc parseVersionRange*(s: string): PVersionRange = # Disallow more than one verIntersect. It's pointless and could lead to # major unpredictable mistakes. if result.verIRight.kind == verIntersect: - raise newException(EParseVersion, + raise newException(ParseVersionError, "Having more than one `&` in a version range is pointless") break @@ -175,13 +176,15 @@ proc parseVersionRange*(s: string): PVersionRange = # Make sure '0.9 8.03' is not allowed. if version != "" and i < s.len: 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: - raise newException(EParseVersion, "Unexpected char in version range: " & s[i]) + raise newException(ParseVersionError, + "Unexpected char in version range: " & s[i]) inc(i) -proc `$`*(verRange: PVersionRange): string = +proc `$`*(verRange: VersionRange): string = case verRange.kind of verLater: result = "> " @@ -202,34 +205,36 @@ proc `$`*(verRange: PVersionRange): string = result.add(string(verRange.ver)) -proc getSimpleString*(verRange: PVersionRange): string = - ## Gets a string with no special symbols and spaces. Used for dir name creation - ## in tools.nim +proc getSimpleString*(verRange: VersionRange): string = + ## Gets a string with no special symbols and spaces. Used for dir name + ## creation in tools.nim case verRange.kind of verSpecial: result = $verRange.spe of verLater, verEarlier, verEqLater, verEqEarlier, verEq: result = $verRange.ver of verIntersect: - result = getSimpleString(verRange.verILeft) & "_" & getSimpleString(verRange.verIRight) + result = getSimpleString(verRange.verILeft) & "_" & + getSimpleString(verRange.verIRight) of verAny: result = "" -proc newVRAny*(): PVersionRange = +proc newVRAny*(): VersionRange = new(result) result.kind = verAny -proc newVREarlier*(ver: string): PVersionRange = +proc newVREarlier*(ver: string): VersionRange = new(result) result.kind = verEarlier result.ver = newVersion(ver) -proc newVREq*(ver: string): PVersionRange = +proc newVREq*(ver: string): VersionRange = new(result) result.kind = verEq 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(""), "") for ver, tag in versions: if not withinRange(ver, verRange): continue @@ -263,8 +268,10 @@ when isMainModule: doAssert(newVersion("") < newVersion("1.0.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"}) - doAssert findLatest(parseVersionRange(">= 0.1 & <= 0.4"), versions) == (newVersion("0.2.3"), "v0.2.3") + var versions = toTable[Version, string]({newVersion("0.1.1"): "v0.1.1", + 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? #doAssert newVersion("0.1-rc1") < newVersion("0.2") diff --git a/tests/tester.nim b/tests/tester.nim index 3ad86140..f95e76f4 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -1,6 +1,6 @@ # Copyright (C) Dominik Picheta. All rights reserved. # 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" @@ -15,6 +15,35 @@ template cd*(dir: string, body: stmt) = body 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] = output.strip.splitLines().filter((x: string) => (x.len > 0)) @@ -24,14 +53,14 @@ test "can install packagebin2": QuitSuccess 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") - #echo outp - # TODO: outp is not in the correct order. - let ls = outp.strip.splitLines() + # We look at the error output here to avoid out-of-order problems caused by + # stderr output being generated and flushed without first flushing stdout + let ls = errp.strip.splitLines() check exitCode != QuitSuccess 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": check execCmdEx(path & " update").exitCode == QuitSuccess @@ -50,11 +79,12 @@ test "issue #27": test "can uninstall": block: - let (outp, exitCode) = execCmdEx(path & " uninstall -y issue27b") - let ls = outp.processOutput() + let (outp, errp, exitCode) = execCmdEx2(path & " uninstall -y issue27b") + # let ls = outp.processOutput() + let ls = errp.strip.splitLines() check exitCode != QuitSuccess 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 issue27a").exitCode == QuitSuccess @@ -62,9 +92,9 @@ test "can uninstall": # Remove Package* 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 - let ls = outp.processOutput() + let ls = errp.processOutput() check ls[ls.len-2].startsWith(" Cannot uninstall PackageA ") check ls[ls.len-1].startsWith(" Cannot uninstall PackageA ") check execCmdEx(path & " uninstall -y PackageBin2").exitCode == QuitSuccess @@ -76,5 +106,6 @@ test "can uninstall": # Remove the rest of the installed packages. 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"))