Renamed babel to nimble.

This commit is contained in:
Dominik Picheta 2014-10-11 22:38:44 +01:00
commit adbc30c22e
9 changed files with 216 additions and 190 deletions

View file

@ -6,8 +6,8 @@ import httpclient, parseopt, os, strutils, osproc, pegs, tables, parseutils,
from sequtils import toSeq from sequtils import toSeq
import babelpkg/packageinfo, babelpkg/version, babelpkg/tools, import nimblepkg/packageinfo, nimblepkg/version, nimblepkg/tools,
babelpkg/download, babelpkg/config, babelpkg/compat nimblepkg/download, nimblepkg/config, nimblepkg/compat
when not defined(windows): when not defined(windows):
from posix import getpid from posix import getpid
@ -18,7 +18,7 @@ type
queryVersions: bool queryVersions: bool
action: TAction action: TAction
config: TConfig config: TConfig
babelData: PJsonNode ## Babeldata.json nimbleData: PJsonNode ## Nimbledata.json
TActionType = enum TActionType = enum
ActionNil, ActionUpdate, ActionInit, ActionInstall, ActionSearch, ActionNil, ActionUpdate, ActionInit, ActionInstall, ActionSearch,
@ -43,11 +43,11 @@ type
const const
help = """ help = """
Usage: babel COMMAND [opts] Usage: nimble COMMAND [opts]
Commands: Commands:
install [pkgname, ...] Installs a list of packages. install [pkgname, ...] Installs a list of packages.
init [pkgname] Initializes a new Babel project. init [pkgname] Initializes a new Nimble project.
uninstall [pkgname, ...] Uninstalls a list of packages. uninstall [pkgname, ...] Uninstalls a list of packages.
build Builds a package. build Builds a package.
update [url] Updates package list. A package list URL can update [url] Updates package list. A package list URL can
@ -66,7 +66,7 @@ Options:
--ver Query remote server for package version --ver Query remote server for package version
information when searching or listing packages information when searching or listing packages
""" """
babelVersion = "0.4.0" nimbleVersion = "0.4.0"
defaultPackageURL = "https://github.com/nimrod-code/packages/raw/master/packages.json" defaultPackageURL = "https://github.com/nimrod-code/packages/raw/master/packages.json"
proc writeHelp() = proc writeHelp() =
@ -74,17 +74,51 @@ proc writeHelp() =
quit(QuitSuccess) quit(QuitSuccess)
proc writeVersion() = proc writeVersion() =
echo("babel v$# compiled at $# $#" % [babelVersion, CompileDate, CompileTime]) echo("nimble v$# compiled at $# $#" % [nimbleVersion, CompileDate, CompileTime])
quit(QuitSuccess) quit(QuitSuccess)
proc getBabelDir(options: TOptions): string = proc getNimbleDir(options: TOptions): string =
options.config.babelDir options.config.nimbleDir
proc getPkgsDir(options: TOptions): string = proc getPkgsDir(options: TOptions): string =
options.config.babelDir / "pkgs" options.config.nimbleDir / "pkgs"
proc getBinDir(options: TOptions): string = proc getBinDir(options: TOptions): string =
options.config.babelDir / "bin" options.config.nimbleDir / "bin"
proc prompt(options: TOptions, 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.
case options.forcePrompts
of ForcePromptYes:
echo(question & " -> [forced yes]")
return true
of ForcePromptNo:
echo(question & " -> [forced no]")
return false
of DontForcePrompt:
echo(question & " [y/N]")
let yn = stdin.readLine()
case yn.normalize
of "y", "yes":
return true
of "n", "no":
return false
else:
return false
proc renameBabelToNimble(options: TOptions) {.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?"):
copyDir(babelDir, nimbleDir)
removeDir(babelDir)
copyFile(nimbleDir / "babeldata.json", nimbleDir / "nimbledata.json")
removeFile(nimbleDir / "babeldata.json")
proc parseCmdLine(): TOptions = proc parseCmdLine(): TOptions =
result.action.typ = ActionNil result.action.typ = ActionNil
@ -136,7 +170,7 @@ proc parseCmdLine(): TOptions =
result.action.search.add(key) result.action.search.add(key)
of ActionInit: of ActionInit:
if result.action.projName != "": if result.action.projName != "":
raise newException(EBabel, "Can only initialize one package at a time.") raise newException(ENimble, "Can only initialize one package at a time.")
result.action.projName = key result.action.projName = key
of ActionList, ActionBuild: of ActionList, ActionBuild:
writeHelp() writeHelp()
@ -151,35 +185,15 @@ proc parseCmdLine(): TOptions =
if result.action.typ == ActionNil: if result.action.typ == ActionNil:
writeHelp() writeHelp()
# Load babeldata.json # Rename deprecated babel dir.
let babeldataFilename = result.getBabelDir() / "babeldata.json" renameBabelToNimble(result)
if fileExists(babeldataFilename):
result.babelData = parseFile(babeldataFilename)
else:
result.babelData = %{"reverseDeps": newJObject()}
proc prompt(options: TOptions, question: string): bool = # Load nimbledata.json
## Asks an interactive question and returns the result. let nimbledataFilename = result.getNimbleDir() / "nimbledata.json"
## if fileExists(nimbledataFilename):
## The proc will return immediately without asking the user if the global result.nimbleData = parseFile(nimbledataFilename)
## forcePrompts has a value different than DontForcePrompt.
case options.forcePrompts
of ForcePromptYes:
echo(question & " -> [forced yes]")
return true
of ForcePromptNo:
echo(question & " -> [forced no]")
return false
of DontForcePrompt:
echo(question & " [y/N]")
let yn = stdin.readLine()
case yn.normalize
of "y", "yes":
return true
of "n", "no":
return false
else: else:
return false result.nimbleData = %{"reverseDeps": newJObject()}
proc update(options: TOptions) = proc update(options: TOptions) =
## Downloads the package list from the specified URL. ## Downloads the package list from the specified URL.
@ -192,7 +206,7 @@ proc update(options: TOptions) =
else: else:
defaultPackageURL defaultPackageURL
echo("Downloading package list from " & url) echo("Downloading package list from " & url)
downloadFile(url, options.getBabelDir() / "packages.json") downloadFile(url, options.getNimbleDir() / "packages.json")
echo("Done.") echo("Done.")
proc checkInstallFile(pkgInfo: TPackageInfo, proc checkInstallFile(pkgInfo: TPackageInfo,
@ -201,8 +215,8 @@ proc checkInstallFile(pkgInfo: TPackageInfo,
## ``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("babel"): if ignoreFile.endswith("nimble"):
raise newException(EBabel, ignoreFile & " must be installed.") raise newException(ENimble, ignoreFile & " must be installed.")
if samePaths(file, origDir / ignoreFile): if samePaths(file, origDir / ignoreFile):
result = true result = true
break break
@ -244,7 +258,7 @@ proc copyWithExt(origDir, currentDir, dest: string,
proc copyFilesRec(origDir, currentDir, dest: string, proc copyFilesRec(origDir, currentDir, dest: string,
options: TOptions, pkgInfo: TPackageInfo): TSet[string] = options: TOptions, pkgInfo: TPackageInfo): TSet[string] =
## Copies all the required files, skips files specified in the .babel file ## Copies all the required files, skips files specified in the .nimble file
## (TPackageInfo). ## (TPackageInfo).
## 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]()
@ -294,19 +308,19 @@ 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 saveBabelData(options: TOptions) = proc saveNimbleData(options: TOptions) =
# TODO: This file should probably be locked. # TODO: This file should probably be locked.
writeFile(options.getBabelDir() / "babeldata.json", pretty(options.babelData)) writeFile(options.getNimbleDir() / "nimbledata.json", pretty(options.nimbleData))
proc addRevDep(options: TOptions, dep: tuple[name, version: string], proc addRevDep(options: TOptions, dep: tuple[name, version: string],
pkg: TPackageInfo) = pkg: TPackageInfo) =
let depNameVer = dep.name & '-' & dep.version let depNameVer = dep.name & '-' & dep.version
if not options.babelData["reverseDeps"].hasKey(dep.name): if not options.nimbleData["reverseDeps"].hasKey(dep.name):
options.babelData["reverseDeps"][dep.name] = newJObject() options.nimbleData["reverseDeps"][dep.name] = newJObject()
if not options.babelData["reverseDeps"][dep.name].hasKey(dep.version): if not options.nimbleData["reverseDeps"][dep.name].hasKey(dep.version):
options.babelData["reverseDeps"][dep.name][dep.version] = newJArray() options.nimbleData["reverseDeps"][dep.name][dep.version] = newJArray()
let revDep = %{ "name": %pkg.name, "version": %pkg.version} let revDep = %{ "name": %pkg.name, "version": %pkg.version}
let thisDep = options.babelData["reverseDeps"][dep.name][dep.version] let thisDep = options.nimbleData["reverseDeps"][dep.name][dep.version]
if revDep notin thisDep: if revDep notin thisDep:
thisDep.add revDep thisDep.add revDep
@ -326,16 +340,16 @@ proc removeRevDep(options: TOptions, pkg: TPackageInfo) =
for depTup in pkg.requires: for depTup in pkg.requires:
if depTup.name.isURL(): if depTup.name.isURL():
# We sadly must go through everything in this case... # We sadly must go through everything in this case...
for key, val in options.babelData["reverseDeps"]: for key, val in options.nimbleData["reverseDeps"]:
options.remove(pkg, depTup, val) options.remove(pkg, depTup, val)
else: else:
let thisDep = options.babelData["reverseDeps"][depTup.name] let thisDep = options.nimbleData["reverseDeps"][depTup.name]
if thisDep.isNil: continue if thisDep.isNil: continue
options.remove(pkg, depTup, thisDep) options.remove(pkg, depTup, thisDep)
# Clean up empty objects/arrays # Clean up empty objects/arrays
var newData = newJObject() var newData = newJObject()
for key, val in options.babelData["reverseDeps"]: for key, val in options.nimbleData["reverseDeps"]:
if val.len != 0: if val.len != 0:
var newVal = newJObject() var newVal = newJObject()
for ver, elem in val: for ver, elem in val:
@ -343,9 +357,9 @@ proc removeRevDep(options: TOptions, pkg: TPackageInfo) =
newVal[ver] = elem newVal[ver] = elem
if newVal.len != 0: if newVal.len != 0:
newData[key] = newVal newData[key] = newVal
options.babelData["reverseDeps"] = newData options.nimbleData["reverseDeps"] = newData
saveBabelData(options) saveNimbleData(options)
proc install(packages: seq[TPkgTuple], proc install(packages: seq[TPkgTuple],
options: TOptions, options: TOptions,
@ -384,7 +398,7 @@ proc processDeps(pkginfo: TPackageInfo, options: TOptions): seq[string] =
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(EBabel, raise newException(ENimble,
"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
@ -394,7 +408,7 @@ proc processDeps(pkginfo: TPackageInfo, options: TOptions): seq[string] =
# (unsatisfiable dependendencies). # (unsatisfiable dependendencies).
for i in reverseDeps: for i in reverseDeps:
addRevDep(options, i, pkginfo) addRevDep(options, i, pkginfo)
saveBabelData(options) saveNimbleData(options)
proc buildFromDir(pkgInfo: TPackageInfo, paths: seq[string]) = proc buildFromDir(pkgInfo: TPackageInfo, paths: seq[string]) =
## Builds a package as specified by ``pkgInfo``. ## Builds a package as specified by ``pkgInfo``.
@ -404,36 +418,36 @@ proc buildFromDir(pkgInfo: TPackageInfo, paths: seq[string]) =
for bin in pkgInfo.bin: for bin in pkgInfo.bin:
echo("Building ", pkginfo.name, "/", bin, " using ", pkgInfo.backend, echo("Building ", pkginfo.name, "/", bin, " using ", pkgInfo.backend,
" backend...") " backend...")
doCmd(getNimBin() & " $# -d:release --noBabelPath $# \"$#\"" % doCmd(getNimBin() & " $# -d:release --noNimblePath $# \"$#\"" %
[pkgInfo.backend, args, realDir / bin.changeFileExt("nim")]) [pkgInfo.backend, args, realDir / bin.changeFileExt("nim")])
proc saveBabelMeta(pkgDestDir, url: string, filesInstalled: TSet[string]) = proc saveNimbleMeta(pkgDestDir, url: string, filesInstalled: TSet[string]) =
var babelmeta = %{"url": %url} var nimblemeta = %{"url": %url}
babelmeta["files"] = newJArray() nimblemeta["files"] = newJArray()
for file in filesInstalled: for file in filesInstalled:
babelmeta["files"].add(%changeRoot(pkgDestDir, "", file)) nimblemeta["files"].add(%changeRoot(pkgDestDir, "", file))
writeFile(pkgDestDir / "babelmeta.json", $babelmeta) writeFile(pkgDestDir / "nimblemeta.json", $nimblemeta)
proc removePkgDir(dir: string, options: TOptions) = proc removePkgDir(dir: string, options: TOptions) =
## Removes files belonging to the package in ``dir``. ## Removes files belonging to the package in ``dir``.
try: try:
var babelmeta = parseFile(dir / "babelmeta.json") var nimblemeta = parseFile(dir / "nimblemeta.json")
if not babelmeta.hasKey("files"): if not nimblemeta.hasKey("files"):
raise newException(EJsonParsingError, raise newException(EJsonParsingError,
"Meta data does not contain required info.") "Meta data does not contain required info.")
for file in babelmeta["files"]: for file in nimblemeta["files"]:
removeFile(dir / file.str) removeFile(dir / file.str)
removeFile(dir / "babelmeta.json") removeFile(dir / "nimblemeta.json")
# If there are no files left in the directory, remove the directory. # If there are no files left in the directory, remove the directory.
if toSeq(walkDirRec(dir)).len == 0: if toSeq(walkDirRec(dir)).len == 0:
removeDir(dir) removeDir(dir)
else: else:
echo("WARNING: Cannot completely remove " & dir & echo("WARNING: Cannot completely remove " & dir &
". Files not installed by babel are present.") ". Files not installed by nimble are present.")
except EOS, EJsonParsingError: except EOS, EJsonParsingError:
echo("Error: Unable to read babelmeta.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)
@ -460,7 +474,7 @@ 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 / "babelmeta.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)
@ -482,7 +496,7 @@ proc installFromDir(dir: string, latest: bool, options: TOptions,
filesInstalled = copyFilesRec(realDir, realDir, pkgDestDir, options, filesInstalled = copyFilesRec(realDir, realDir, pkgDestDir, options,
pkgInfo) pkgInfo)
# Set file permissions to +x for all binaries built, # Set file permissions to +x for all binaries built,
# and symlink them on *nix OS' to $babelDir/bin/ # and symlink them on *nix OS' to $nimbleDir/bin/
for bin in pkgInfo.bin: for bin in pkgInfo.bin:
if not existsFile(pkgDestDir / bin): if not existsFile(pkgDestDir / bin):
filesInstalled.incl copyFileD(realDir / bin, pkgDestDir / bin) filesInstalled.incl copyFileD(realDir / bin, pkgDestDir / bin)
@ -506,8 +520,8 @@ proc installFromDir(dir: string, latest: bool, options: TOptions,
filesInstalled = copyFilesRec(realDir, realDir, pkgDestDir, options, filesInstalled = copyFilesRec(realDir, realDir, pkgDestDir, options,
pkgInfo) pkgInfo)
# Save a babelmeta.json file. # Save a nimblemeta.json file.
saveBabelMeta(pkgDestDir, url, filesInstalled) saveNimbleMeta(pkgDestDir, url, filesInstalled)
# Return the paths to the dependencies of this package. # Return the paths to the dependencies of this package.
result.paths.add pkgDestDir result.paths.add pkgDestDir
@ -515,14 +529,14 @@ proc installFromDir(dir: string, latest: bool, options: TOptions,
echo(pkgInfo.name & " installed successfully.") echo(pkgInfo.name & " installed successfully.")
proc getBabelTempDir(): string = proc getNimbleTempDir(): string =
## Returns a path to a temporary directory. ## Returns a path to a temporary directory.
## ##
## The returned path will be the same for the duration of the process but ## The returned path will be the same for the duration of the process but
## different for different runs of it. You have to make sure to create it ## different for different runs of it. You have to make sure to create it
## first. In release builds the directory will be removed when babel finishes ## first. In release builds the directory will be removed when nimble finishes
## its work. ## its work.
result = getTempDir() / "babel_" result = getTempDir() / "nimble_"
when defined(windows): when defined(windows):
proc GetCurrentProcessId(): int32 {.stdcall, dynlib: "kernel32", proc GetCurrentProcessId(): int32 {.stdcall, dynlib: "kernel32",
importc: "GetCurrentProcessId".} importc: "GetCurrentProcessId".}
@ -532,14 +546,14 @@ proc getBabelTempDir(): string =
proc downloadPkg(url: string, verRange: PVersionRange, proc downloadPkg(url: string, verRange: PVersionRange,
downMethod: TDownloadMethod): string = downMethod: TDownloadMethod): string =
let downloadDir = (getBabelTempDir() / 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: TPackage, verRange: PVersionRange): string =
let downloadDir = (getBabelTempDir() / 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, "...")
@ -553,12 +567,12 @@ proc install(packages: seq[TPkgTuple],
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.getBabelDir / "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 babel update.", QuitFailure) quit("Please run nimble update.", QuitFailure)
# Install each package. # Install each package.
for pv in packages: for pv in packages:
@ -568,7 +582,7 @@ proc install(packages: seq[TPkgTuple],
result = installFromDir(downloadDir, false, options, pv.name) result = installFromDir(downloadDir, false, options, pv.name)
else: else:
var pkg: TPackage var pkg: TPackage
if getPackage(pv.name, options.getBabelDir() / "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:
@ -579,7 +593,7 @@ proc install(packages: seq[TPkgTuple],
update(options) update(options)
result = install(@[pv], options, false) result = install(@[pv], options, false)
else: else:
raise newException(EBabel, "Package not found.") raise newException(ENimble, "Package not found.")
proc build(options: TOptions) = proc build(options: TOptions) =
var pkgInfo = getPkgInfo(getCurrentDir()) var pkgInfo = getPkgInfo(getCurrentDir())
@ -592,10 +606,10 @@ proc search(options: TOptions) =
## 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(EBabel, "Please specify a search string.") raise newException(ENimble, "Please specify a search string.")
if not existsFile(options.getBabelDir() / "packages.json"): if not existsFile(options.getNimbleDir() / "packages.json"):
raise newException(EBabel, "Please run babel update.") raise newException(ENimble, "Please run nimble update.")
let pkgList = getPackageList(options.getBabelDir() / "packages.json") let pkgList = getPackageList(options.getNimbleDir() / "packages.json")
var found = false var found = false
template onFound: stmt = template onFound: stmt =
echoPackage(pkg) echoPackage(pkg)
@ -619,9 +633,9 @@ proc search(options: TOptions) =
echo("No package found.") echo("No package found.")
proc list(options: TOptions) = proc list(options: TOptions) =
if not existsFile(options.getBabelDir() / "packages.json"): if not existsFile(options.getNimbleDir() / "packages.json"):
raise newException(EBabel, "Please run babel update.") raise newException(ENimble, "Please run nimble update.")
let pkgList = getPackageList(options.getBabelDir() / "packages.json") let pkgList = getPackageList(options.getNimbleDir() / "packages.json")
for pkg in pkgList: for pkg in pkgList:
echoPackage(pkg) echoPackage(pkg)
if options.queryVersions: if options.queryVersions:
@ -647,15 +661,15 @@ proc listPaths(options: TOptions) =
if kind != pcDir or not path.startsWith(options.getPkgsDir / name): if kind != pcDir or not path.startsWith(options.getPkgsDir / name):
continue continue
let babelFile = path / name.addFileExt("babel") let nimbleFile = path / name.addFileExt("nimble")
if existsFile(babelFile): if existsFile(nimbleFile):
var pkgInfo = getPkgInfo(path) var pkgInfo = getPkgInfo(path)
var v: VersionAndPath var v: VersionAndPath
v.version = newVersion(pkgInfo.version) v.version = newVersion(pkgInfo.version)
v.path = options.getPkgsDir / (pkgInfo.name & '-' & pkgInfo.version) v.path = options.getPkgsDir / (pkgInfo.name & '-' & pkgInfo.version)
installed.add(v) installed.add(v)
else: else:
echo "Warning: No .babel file found for ", path echo "Warning: No .nimble file found for ", path
if installed.len > 0: if installed.len > 0:
sort(installed, system.cmp[VersionAndPath], Descending) sort(installed, system.cmp[VersionAndPath], Descending)
@ -664,36 +678,36 @@ 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(EBabel, "At least one of the specified packages was not found") raise newException(ENimble, "At least one of the specified packages was not found")
proc init(options: TOptions) = proc init(options: TOptions) =
echo("Initializing new Babel project!") echo("Initializing new Nimble project!")
var var
pkgName, fName: string = "" pkgName, fName: string = ""
outFile: TFile outFile: TFile
if (options.action.projName != ""): if (options.action.projName != ""):
pkgName = options.action.projName pkgName = options.action.projName
fName = pkgName & ".babel" fName = pkgName & ".nimble"
if ( existsFile( os.getCurrentDir() / fName ) ): if ( existsFile( os.getCurrentDir() / fName ) ):
raise newException(EBabel, "Already have a babel file.") raise newException(ENimble, "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(EBabel, "Could not get default file path.") raise newException(ENimble, "Could not get default file path.")
fName = pkgName & ".babel" fName = pkgName & ".nimble"
# Now need to write out .babel file with projName and other details # Now need to write out .nimble file with projName and other details
if (not existsFile(os.getCurrentDir() / fName) and open(f=outFile, filename = fName, mode = fmWrite)): if (not existsFile(os.getCurrentDir() / fName) and open(f=outFile, filename = fName, mode = fmWrite)):
outFile.writeln("[Package]") outFile.writeln("[Package]")
outFile.writeln("name = \"" & pkgName & "\"") outFile.writeln("name = \"" & pkgName & "\"")
outFile.writeln("version = \"0.01\"") outFile.writeln("version = \"0.01\"")
outFile.writeln("author = \"Anonymous\"") outFile.writeln("author = \"Anonymous\"")
outFile.writeln("description = \"New Babel project for Nimrod\"") outFile.writeln("description = \"New Nimble project for Nimrod\"")
outFile.writeln("license = \"BSD\"") outFile.writeln("license = \"BSD\"")
outFile.writeln("") outFile.writeln("")
outFile.writeln("[Deps]") outFile.writeln("[Deps]")
@ -701,7 +715,7 @@ proc init(options: TOptions) =
close(outFile) close(outFile)
else: else:
raise newException(EBabel, "Unable to open file " & fName & " for writing: " & osErrorMsg()) raise newException(ENimble, "Unable to open file " & fName & " for writing: " & osErrorMsg())
proc uninstall(options: TOptions) = proc uninstall(options: TOptions) =
var pkgsToDelete: seq[TPackageInfo] = @[] var pkgsToDelete: seq[TPackageInfo] = @[]
@ -711,14 +725,14 @@ proc uninstall(options: TOptions) =
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(EBabel, "Package not found") raise newException(ENimble, "Package not found")
echo("Checking reverse dependencies...") echo("Checking reverse dependencies...")
var errors: seq[string] = @[] var errors: seq[string] = @[]
for pkg in pkgList: for pkg in pkgList:
# Check whether any packages depend on the ones the user is trying to # Check whether any packages depend on the ones the user is trying to
# uninstall. # uninstall.
let thisPkgsDep = options.babelData["reverseDeps"]{pkg.name}{pkg.version} let thisPkgsDep = options.nimbleData["reverseDeps"]{pkg.name}{pkg.version}
if not thisPkgsDep.isNil: if not thisPkgsDep.isNil:
var reason = "" var reason = ""
if thisPkgsDep.len == 1: if thisPkgsDep.len == 1:
@ -737,7 +751,7 @@ proc uninstall(options: TOptions) =
pkgsToDelete.add pkg pkgsToDelete.add pkg
if pkgsToDelete.len == 0: if pkgsToDelete.len == 0:
raise newException(EBabel, "\n " & errors.join("\n ")) raise newException(ENimble, "\n " & errors.join("\n "))
var pkgNames = "" var pkgNames = ""
for i in 0 .. <pkgsToDelete.len: for i in 0 .. <pkgsToDelete.len:
@ -757,8 +771,8 @@ proc uninstall(options: TOptions) =
echo("Removed ", pkg.name, " (", $pkg.version, ")") echo("Removed ", pkg.name, " (", $pkg.version, ")")
proc doAction(options: TOptions) = proc doAction(options: TOptions) =
if not existsDir(options.getBabelDir()): if not existsDir(options.getNimbleDir()):
createDir(options.getBabelDir()) createDir(options.getNimbleDir())
if not existsDir(options.getPkgsDir): if not existsDir(options.getPkgsDir):
createDir(options.getPkgsDir) createDir(options.getPkgsDir)
@ -783,12 +797,14 @@ proc doAction(options: TOptions) =
assert false assert false
when isMainModule: when isMainModule:
# TODO: Check for .babel dir and rename everything to nimble.
when defined(release): when defined(release):
try: try:
parseCmdLine().doAction() parseCmdLine().doAction()
except EBabel: except ENimble:
quit("FAILURE: " & getCurrentExceptionMsg()) quit("FAILURE: " & getCurrentExceptionMsg())
finally: finally:
removeDir(getBabelTempDir()) removeDir(getNimbleTempDir())
else: else:
parseCmdLine().doAction() parseCmdLine().doAction()

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 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

29
src/nimblepkg/compat.nim Normal file
View file

@ -0,0 +1,29 @@
# 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

@ -6,16 +6,23 @@ import tools
type type
TConfig* = object TConfig* = object
babelDir*: string nimbleDir*: string
proc initConfig(): TConfig = proc initConfig(): TConfig =
result.babelDir = getHomeDir() / ".babel" result.nimbleDir = getHomeDir() / ".nimble"
proc parseConfig*(): TConfig = proc parseConfig*(): TConfig =
result = initConfig() result = initConfig()
let confFile = getConfigDir() / "babel" / "babel.ini" var confFile = getConfigDir() / "nimble" / "nimble.ini"
var f = newFileStream(confFile, fmRead) var f = newFileStream(confFile, fmRead)
if f == nil:
# Try the old deprecated babel.ini
confFile = getConfigDir() / "babel" / "babel.ini"
f = newFileStream(confFile, fmRead)
if f != nil:
echo("[Warning] Using deprecated config file at ", confFile)
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: TCfgParser
@ -28,11 +35,11 @@ proc parseConfig*(): TConfig =
of cfgSectionStart: discard of cfgSectionStart: discard
of cfgKeyValuePair, cfgOption: of cfgKeyValuePair, cfgOption:
case e.key.normalize case e.key.normalize
of "babeldir": of "nimbledir":
result.babelDir = e.value result.nimbleDir = e.value
else: else:
raise newException(EBabel, "Unable to parse config file:" & raise newException(ENimble, "Unable to parse config file:" &
" Unknown key: " & e.key) " Unknown key: " & e.key)
of cfgError: of cfgError:
raise newException(EBabel, "Unable to parse config file: " & e.msg) raise newException(ENimble, "Unable to parse config file: " & e.msg)
close(p) close(p)

View file

@ -111,7 +111,7 @@ proc getDownloadMethod*(meth: string): TDownloadMethod =
of "git": return TDownloadMethod.Git of "git": return TDownloadMethod.Git
of "hg", "mercurial": return TDownloadMethod.Hg of "hg", "mercurial": return TDownloadMethod.Hg
else: else:
raise newException(EBabel, "Invalid download method: " & meth) raise newException(ENimble, "Invalid download method: " & meth)
proc getHeadName*(meth: TDownloadMethod): string = proc getHeadName*(meth: TDownloadMethod): 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
@ -127,7 +127,7 @@ proc checkUrlType*(url: string): TDownloadMethod =
elif doCmdEx("hg identify " & url).exitCode == QuitSuccess: elif doCmdEx("hg identify " & url).exitCode == QuitSuccess:
return TDownloadMethod.Hg return TDownloadMethod.Hg
else: else:
raise newException(EBabel, "Unable to identify url.") raise newException(ENimble, "Unable to identify url.")
proc isURL*(name: string): bool = proc isURL*(name: string): bool =
name.startsWith(peg" @'://' ") name.startsWith(peg" @'://' ")
@ -143,7 +143,7 @@ proc doDownload*(url: string, downloadDir: string, verRange: PVersionRange,
# If no tagged versions satisfy our range latest.tag will be "". # If no tagged versions satisfy our range latest.tag will be "".
# We still clone in that scenario because we want to try HEAD in that case. # We still clone in that scenario because we want to try HEAD in that case.
# https://github.com/nimrod-code/babel/issues/22 # https://github.com/nimrod-code/nimble/issues/22
meth meth
proc verifyClone() = proc verifyClone() =
@ -151,7 +151,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(EBabel, raise newException(ENimble,
"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])

View file

@ -7,7 +7,7 @@ type
TPkgTuple* = tuple[name: string, ver: PVersionRange] TPkgTuple* = tuple[name: string, ver: PVersionRange]
TPackageInfo* = object TPackageInfo* = object
mypath*: string ## The path of this .babel file mypath*: string ## The path of this .nimble file
name*: string name*: string
version*: string version*: string
author*: string author*: string
@ -60,25 +60,25 @@ proc initPackageInfo(): TPackageInfo =
proc validatePackageInfo(pkgInfo: TPackageInfo, path: string) = proc validatePackageInfo(pkgInfo: TPackageInfo, path: string) =
if pkgInfo.name == "": if pkgInfo.name == "":
raise newException(EBabel, "Incorrect .babel file: " & path & raise newException(ENimble, "Incorrect .nimble file: " & path &
" does not contain a name field.") " does not contain a name field.")
if pkgInfo.version == "": if pkgInfo.version == "":
raise newException(EBabel, "Incorrect .babel file: " & path & raise newException(ENimble, "Incorrect .nimble file: " & path &
" does not contain a version field.") " does not contain a version field.")
if pkgInfo.author == "": if pkgInfo.author == "":
raise newException(EBabel, "Incorrect .babel file: " & path & raise newException(ENimble, "Incorrect .nimble file: " & path &
" does not contain an author field.") " does not contain an author field.")
if pkgInfo.description == "": if pkgInfo.description == "":
raise newException(EBabel, "Incorrect .babel file: " & path & raise newException(ENimble, "Incorrect .nimble file: " & path &
" does not contain a description field.") " does not contain a description field.")
if pkgInfo.license == "": if pkgInfo.license == "":
raise newException(EBabel, "Incorrect .babel file: " & path & raise newException(ENimble, "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(EBabel, "'" & pkgInfo.backend & "' is an invalid backend.") raise newException(ENimble, "'" & 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(EBabel, raise newException(ENimble,
"Version may only consist of numbers and the '.' character " & "Version may only consist of numbers and the '.' character " &
"but found '" & c & "'.") "but found '" & c & "'.")
@ -96,7 +96,7 @@ proc parseRequires(req: string): TPkgTuple =
result.name = req.strip result.name = req.strip
result.ver = PVersionRange(kind: verAny) result.ver = PVersionRange(kind: verAny)
except EParseVersion: except EParseVersion:
raise newException(EBabel, "Unable to parse dependency version range: " & raise newException(ENimble, "Unable to parse dependency version range: " &
getCurrentExceptionMsg()) getCurrentExceptionMsg())
proc multiSplit(s: string): seq[string] = proc multiSplit(s: string): seq[string] =
@ -160,18 +160,18 @@ proc readPackageInfo*(path: string): TPackageInfo =
case result.backend.normalize case result.backend.normalize
of "javascript": result.backend = "js" of "javascript": result.backend = "js"
else: else:
raise newException(EBabel, "Invalid field: " & ev.key) raise newException(ENimble, "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(EBabel, "Invalid field: " & ev.key) raise newException(ENimble, "Invalid field: " & ev.key)
else: raise newException(EBabel, "Invalid section: " & currentSection) else: raise newException(ENimble, "Invalid section: " & currentSection)
of cfgOption: raise newException(EBabel, "Invalid package info, should not contain --" & ev.value) of cfgOption: raise newException(ENimble, "Invalid package info, should not contain --" & ev.value)
of cfgError: of cfgError:
raise newException(EBabel, "Error parsing .babel file: " & ev.msg) raise newException(ENimble, "Error parsing .nimble file: " & ev.msg)
close(p) close(p)
else: else:
raise newException(EInvalidValue, "Cannot open package info: " & path) raise newException(EInvalidValue, "Cannot open package info: " & path)
@ -187,7 +187,7 @@ proc optionalField(obj: PJsonNode, name: string, default = ""): string =
if obj[name].kind == JString: if obj[name].kind == JString:
return obj[name].str return obj[name].str
else: else:
raise newException(EBabel, "Corrupted packages.json file. " & name & " field is of unexpected type.") raise newException(ENimble, "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: PJsonNode, name: string): string =
@ -196,7 +196,7 @@ proc requiredField(obj: PJsonNode, name: string): 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(EBabel, raise newException(ENimble,
"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: PJSonNode): TPackage =
@ -216,14 +216,18 @@ proc fromJson(obj: PJSonNode): TPackage =
result.web = obj.optionalField("web") result.web = obj.optionalField("web")
proc readMetadata*(path: string): TMetadata = proc readMetadata*(path: string): TMetadata =
## Reads the metadata present in ``~/.babel/pkgs/pkg-0.1/babelmeta.json`` ## Reads the metadata present in ``~/.nimble/pkgs/pkg-0.1/nimblemeta.json``
let bmeta = path / "babelmeta.json" var bmeta = path / "nimblemeta.json"
if not existsFile(bmeta):
bmeta = path / "babelmeta.json"
if existsFile(bmeta):
echo("WARNING: using deprecated babelmeta.json file in " & path)
if not existsFile(bmeta): if not existsFile(bmeta):
result.url = "" result.url = ""
echo("WARNING: No babelmeta.json file found in " & path) echo("WARNING: No nimblemeta.json file found in " & path)
return return
# TODO: Make this an error. # TODO: Make this an error.
let cont = readFile(path / "babelmeta.json") let cont = readFile(bmeta)
let jsonmeta = parseJson(cont) let jsonmeta = parseJson(cont)
result.url = jsonmeta["url"].str result.url = jsonmeta["url"].str
@ -247,35 +251,35 @@ proc getPackageList*(packagesPath: string): seq[TPackage] =
let pkg: TPackage = p.fromJson() let pkg: TPackage = p.fromJson()
result.add(pkg) result.add(pkg)
proc findBabelFile*(dir: string): string = proc findNimbleFile*(dir: string): string =
result = "" result = ""
for kind, path in walkDir(dir): for kind, path in walkDir(dir):
if kind == pcFile and path.splitFile.ext == ".babel": if kind == pcFile and path.splitFile.ext in [".babel", ".nimble"]:
if result != "": if result != "":
raise newException(EBabel, "Only one .babel file should be present in " & dir) raise newException(ENimble, "Only one .nimble file should be present in " & dir)
result = path result = path
proc getPkgInfo*(dir: string): TPackageInfo = proc getPkgInfo*(dir: string): TPackageInfo =
## Find the .babel file in ``dir`` and parses it, returning a TPackageInfo. ## Find the .nimble file in ``dir`` and parses it, returning a TPackageInfo.
let babelFile = findBabelFile(dir) let nimbleFile = findNimbleFile(dir)
if babelFile == "": if nimbleFile == "":
raise newException(EBabel, "Specified directory does not contain a .babel file.") raise newException(ENimble, "Specified directory does not contain a .nimble file.")
result = readPackageInfo(babelFile) result = readPackageInfo(nimbleFile)
proc getInstalledPkgs*(libsDir: string): seq[tuple[pkginfo: TPackageInfo, meta: TMetaData]] = proc getInstalledPkgs*(libsDir: string): seq[tuple[pkginfo: TPackageInfo, meta: TMetaData]] =
## Gets a list of installed packages. ## Gets a list of installed packages.
## ##
## ``libsDir`` is in most cases: ~/.babel/pkgs/ ## ``libsDir`` is in most cases: ~/.nimble/pkgs/
result = @[] result = @[]
for kind, path in walkDir(libsDir): for kind, path in walkDir(libsDir):
if kind == pcDir: if kind == pcDir:
let babelFile = findBabelFile(path) let nimbleFile = findNimbleFile(path)
if babelFile != "": if nimbleFile != "":
let meta = readMetadata(path) let meta = readMetadata(path)
result.add((readPackageInfo(babelFile), meta)) result.add((readPackageInfo(nimbleFile), meta))
else: else:
# TODO: Abstract logging. # TODO: Abstract logging.
echo("WARNING: No .babel 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: TPackageInfo, meta: TMetaData]],
dep: TPkgTuple, dep: TPkgTuple,
@ -315,7 +319,7 @@ proc getRealDir*(pkgInfo: TPackageInfo): string =
result = pkgInfo.mypath.splitFile.dir result = pkgInfo.mypath.splitFile.dir
proc getNameVersion*(pkgpath: string): tuple[name, version: string] = proc getNameVersion*(pkgpath: string): tuple[name, version: string] =
## Splits ``pkgpath`` in the format ``/home/user/.babel/pkgs/package-0.1`` ## Splits ``pkgpath`` in the format ``/home/user/.nimble/pkgs/package-0.1``
## into ``(packagea, 0.1)`` ## into ``(packagea, 0.1)``
result.name = "" result.name = ""
result.version = "" result.version = ""
@ -347,5 +351,5 @@ proc getDownloadDirName*(pkg: TPackage, verRange: PVersionRange): string =
result.add verSimple result.add verSimple
when isMainModule: when isMainModule:
doAssert getNameVersion("/home/user/.babel/libs/packagea-0.1") == ("packagea", "0.1") doAssert getNameVersion("/home/user/.nimble/libs/packagea-0.1") == ("packagea", "0.1")
doAssert getNameVersion("/home/user/.babel/libs/package-a-0.1") == ("package-a", "0.1") doAssert getNameVersion("/home/user/.nimble/libs/package-a-0.1") == ("package-a", "0.1")

View file

@ -6,21 +6,21 @@ import osproc, pegs, strutils, os, parseurl, sets, json
import version, packageinfo import version, packageinfo
type type
EBabel* = object of EBase 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(EBabel, "'" & bin & "' not in PATH.") raise newException(ENimble, "'" & bin & "' not in PATH.")
let exitCode = execCmd(cmd) let exitCode = execCmd(cmd)
if exitCode != QuitSuccess: if exitCode != QuitSuccess:
raise newException(EBabel, "Execution failed with exit code " & $exitCode) raise newException(ENimble, "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(EBabel, "'" & bin & "' not in PATH.") raise newException(ENimble, "'" & bin & "' not in PATH.")
return execCmdEx(cmd) return execCmdEx(cmd)
template cd*(dir: string, body: stmt) = template cd*(dir: string, body: stmt) =

BIN
src/nimblepkg/version Executable file

Binary file not shown.

View file

@ -280,5 +280,4 @@ when isMainModule:
doAssert newSpecial("ab26sgdt362") in sp doAssert newSpecial("ab26sgdt362") in sp
doAssert newSpecial("ab26saggdt362") notin sp doAssert newSpecial("ab26saggdt362") notin sp
echo("Everything works!") echo("Everything works!")