Merge branch 'master' into native-pkg-support
This commit is contained in:
commit
e813aa6448
14 changed files with 646 additions and 305 deletions
266
src/nimble.nim
266
src/nimble.nim
|
|
@ -1,6 +1,8 @@
|
|||
# Copyright (C) Dominik Picheta. All rights reserved.
|
||||
# BSD License. Look at license.txt for more info.
|
||||
|
||||
import system except TResult
|
||||
|
||||
import httpclient, parseopt, os, osproc, pegs, tables, parseutils,
|
||||
strtabs, json, algorithm, sets, uri
|
||||
|
||||
|
|
@ -10,7 +12,8 @@ from sequtils import toSeq
|
|||
|
||||
import nimblepkg/packageinfo, nimblepkg/version, nimblepkg/tools,
|
||||
nimblepkg/download, nimblepkg/config, nimblepkg/common,
|
||||
nimblepkg/publish, nimblepkg/options, nimblepkg/packageparser
|
||||
nimblepkg/publish, nimblepkg/options, nimblepkg/packageparser,
|
||||
nimblepkg/cli
|
||||
|
||||
import nimblepkg/nimscriptsupport
|
||||
|
||||
|
|
@ -32,24 +35,7 @@ else:
|
|||
proc GetVersionExA*(VersionInformation: var OSVERSIONINFO): WINBOOL{.stdcall,
|
||||
dynlib: "kernel32", importc: "GetVersionExA".}
|
||||
|
||||
proc writeVersion() =
|
||||
echo("nimble v$# compiled at $# $#" %
|
||||
[nimbleVersion, CompileDate, CompileTime])
|
||||
quit(QuitSuccess)
|
||||
|
||||
proc promptCustom(question, default: string): string =
|
||||
if default == "":
|
||||
stdout.write(question, ": ")
|
||||
let user = stdin.readLine()
|
||||
if user.len == 0: return promptCustom(question, default)
|
||||
else: return user
|
||||
else:
|
||||
stdout.write(question, " [", default, "]: ")
|
||||
let user = stdin.readLine()
|
||||
if user == "": return default
|
||||
else: return user
|
||||
|
||||
proc update(options: Options) =
|
||||
proc refresh(options: Options) =
|
||||
## Downloads the package list from the specified URL.
|
||||
##
|
||||
## If the download is not successful, an exception is raised.
|
||||
|
|
@ -60,10 +46,12 @@ proc update(options: Options) =
|
|||
""
|
||||
|
||||
proc downloadList(list: PackageList, options: Options) =
|
||||
echo("Downloading \"", list.name, "\" package list")
|
||||
display("Downloading", list.name & " package list", priority = HighPriority)
|
||||
|
||||
var lastError = ""
|
||||
for i in 0 .. <list.urls.len:
|
||||
let url = list.urls[i]
|
||||
echo("Trying ", url, "...")
|
||||
display("Trying", url)
|
||||
let tempPath = options.getNimbleDir() / "packages_temp.json"
|
||||
|
||||
# Grab the proxy
|
||||
|
|
@ -71,25 +59,41 @@ proc update(options: Options) =
|
|||
if not proxy.isNil:
|
||||
var maskedUrl = proxy.url
|
||||
if maskedUrl.password.len > 0: maskedUrl.password = "***"
|
||||
echo("Using proxy ", maskedUrl)
|
||||
display("Connecting", "to proxy at " & $maskedUrl,
|
||||
priority = LowPriority)
|
||||
|
||||
try:
|
||||
downloadFile(url, tempPath, proxy = getProxy(options))
|
||||
except:
|
||||
if i == <list.urls.len:
|
||||
raise
|
||||
echo("Could not download: ", getCurrentExceptionMsg())
|
||||
let message = "Could not download: " & getCurrentExceptionMsg()
|
||||
display("Warning:", message, Warning)
|
||||
lastError = message
|
||||
continue
|
||||
|
||||
if not validatePackagesList(tempPath):
|
||||
echo("Downloaded packages.json file is invalid, discarding.")
|
||||
lastError = "Downloaded packages.json file is invalid"
|
||||
display("Warning:", lastError & ", discarding.", Warning)
|
||||
continue
|
||||
|
||||
copyFile(tempPath,
|
||||
options.getNimbleDir() / "packages_$1.json" % list.name.toLowerAscii())
|
||||
echo("Done.")
|
||||
display("Success", "Package list downloaded.", Success, HighPriority)
|
||||
lastError = ""
|
||||
break
|
||||
|
||||
if parameter.isUrl:
|
||||
downloadList(PackageList(name: "commandline", urls: @[parameter]), options)
|
||||
if lastError.len != 0:
|
||||
raise newException(NimbleError, "Refresh failed\n" & lastError)
|
||||
|
||||
if parameter.len > 0:
|
||||
if parameter.isUrl:
|
||||
let cmdLine = PackageList(name: "commandline", urls: @[parameter])
|
||||
downloadList(cmdLine, options)
|
||||
else:
|
||||
if parameter notin options.config.packageLists:
|
||||
let msg = "Package list with the specified name not found."
|
||||
raise newException(NimbleError, msg)
|
||||
|
||||
downloadList(options.config.packageLists[parameter], options)
|
||||
else:
|
||||
# Try each package list in config
|
||||
for name, list in options.config.packageLists:
|
||||
|
|
@ -258,24 +262,31 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[string] =
|
|||
## Returns the list of paths to pass to the compiler during build phase.
|
||||
result = @[]
|
||||
assert(not pkginfo.isMinimal, "processDeps needs pkginfo.requires")
|
||||
display("Verifying",
|
||||
"dependencies for $1 v$2" % [pkginfo.name, pkginfo.version],
|
||||
priority = HighPriority)
|
||||
|
||||
let pkglist = getInstalledPkgs(options.getPkgsDir(), options)
|
||||
var reverseDeps: seq[tuple[name, version: string]] = @[]
|
||||
for dep in pkginfo.requires:
|
||||
if dep.name == "nimrod" or dep.name == "nim":
|
||||
let nimVer = getNimrodVersion()
|
||||
if not withinRange(nimVer, dep.ver):
|
||||
quit("Unsatisfied dependency: " & dep.name & " (" & $dep.ver & ")")
|
||||
let msg = "Unsatisfied dependency: " & dep.name & " (" & $dep.ver & ")"
|
||||
raise newException(NimbleError, msg)
|
||||
else:
|
||||
echo("Looking for ", dep.name, " (", $dep.ver, ")...")
|
||||
let depDesc = "$1 ($2)" % [dep.name, $dep.ver]
|
||||
display("Checking", "for $1" % depDesc, priority = MediumPriority)
|
||||
var pkg: PackageInfo
|
||||
if not findPkg(pkglist, dep, pkg):
|
||||
echo("None found, installing...")
|
||||
display("Installing", depDesc, priority = HighPriority)
|
||||
let (paths, installedPkg) = install(@[(dep.name, dep.ver)], options)
|
||||
result.add(paths)
|
||||
|
||||
pkg = installedPkg # For addRevDep
|
||||
else:
|
||||
echo("Dependency already satisfied.")
|
||||
display("Info:", "Dependency on $1 already satisfied" % depDesc,
|
||||
priority = HighPriority)
|
||||
result.add(pkg.mypath.splitFile.dir)
|
||||
# Process the dependencies of this dependency.
|
||||
result.add(processDeps(pkg, options))
|
||||
|
|
@ -311,8 +322,8 @@ proc buildFromDir(pkgInfo: PackageInfo, paths: seq[string], forRelease: bool) =
|
|||
for path in paths: args.add("--path:\"" & path & "\" ")
|
||||
for bin in pkgInfo.bin:
|
||||
let outputOpt = "-o:\"" & pkgInfo.getOutputDir(bin) & "\""
|
||||
echo("Building ", pkginfo.name, "/", bin, " using ", pkgInfo.backend,
|
||||
" backend...")
|
||||
display("Building", "$1/$2 using $3 backend" %
|
||||
[pkginfo.name, bin, pkgInfo.backend], priority = HighPriority)
|
||||
|
||||
let outputDir = pkgInfo.getOutputDir("")
|
||||
if not existsDir(outputDir):
|
||||
|
|
@ -352,13 +363,14 @@ proc removePkgDir(dir: string, options: Options) =
|
|||
if toSeq(walkDirRec(dir)).len == 0:
|
||||
removeDir(dir)
|
||||
else:
|
||||
echo("WARNING: Cannot completely remove " & dir &
|
||||
". Files not installed by nimble are present.")
|
||||
display("Warning:", ("Cannot completely remove $1. Files not installed " &
|
||||
"by nimble are present.") % dir, Warning, HighPriority)
|
||||
except OSError, JsonParsingError:
|
||||
echo("Error: Unable to read nimblemeta.json: ", getCurrentExceptionMsg())
|
||||
display("Warning", "Unable to read nimblemeta.json: " &
|
||||
getCurrentExceptionMsg(), Warning, HighPriority)
|
||||
if not options.prompt("Would you like to COMPLETELY remove ALL files " &
|
||||
"in " & dir & "?"):
|
||||
quit(QuitSuccess)
|
||||
raise NimbleQuit(msg: "")
|
||||
removeDir(dir)
|
||||
|
||||
proc vcsRevisionInDir(dir: string): string =
|
||||
|
|
@ -398,7 +410,8 @@ proc installFromDir(dir: string, latest: bool, options: Options,
|
|||
result.pkg = pkgInfo
|
||||
return result
|
||||
|
||||
echo("Installing ", pkginfo.name, "-", pkginfo.version)
|
||||
display("Installing", "$1 v$2" % [pkginfo.name, pkginfo.version],
|
||||
priority = HighPriority)
|
||||
|
||||
# Build before removing an existing package (if one exists). This way
|
||||
# if the build fails then the old package will still be installed.
|
||||
|
|
@ -409,7 +422,7 @@ proc installFromDir(dir: string, latest: bool, options: Options,
|
|||
if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"):
|
||||
if not options.prompt(pkgInfo.name & versionStr &
|
||||
" already exists. Overwrite?"):
|
||||
quit(QuitSuccess)
|
||||
raise NimbleQuit(msg: "")
|
||||
removePkgDir(pkgDestDir, options)
|
||||
# Remove any symlinked binaries
|
||||
for bin in pkgInfo.bin:
|
||||
|
|
@ -446,12 +459,13 @@ proc installFromDir(dir: string, latest: bool, options: Options,
|
|||
# TODO: Verify that we are removing an old bin of this package, not
|
||||
# some other package's binary!
|
||||
if existsFile(binDir / cleanBin): removeFile(binDir / cleanBin)
|
||||
echo("Creating symlink: ", pkgDestDir / bin, " -> ", binDir / cleanBin)
|
||||
display("Creating", "symlink: $1 -> $2" %
|
||||
[pkgDestDir / bin, binDir / cleanBin], priority = MediumPriority)
|
||||
createSymlink(pkgDestDir / bin, binDir / cleanBin)
|
||||
elif defined(windows):
|
||||
# There is a bug on XP, described here:
|
||||
# http://stackoverflow.com/questions/2182568/batch-script-is-not-executed-if-chcp-was-called
|
||||
# But this workaround brokes code page on newer systems, so we need to detect OS version
|
||||
# But this workaround brakes code page on newer systems, so we need to detect OS version
|
||||
var osver = OSVERSIONINFO()
|
||||
osver.dwOSVersionInfoSize = cast[DWORD](sizeof(OSVERSIONINFO))
|
||||
if GetVersionExA(osver) == WINBOOL(0):
|
||||
|
|
@ -460,7 +474,8 @@ proc installFromDir(dir: string, latest: bool, options: Options,
|
|||
let fixChcp = osver.dwMajorVersion <= 5
|
||||
|
||||
let dest = binDir / cleanBin.changeFileExt("cmd")
|
||||
echo("Creating stub: ", pkgDestDir / bin, " -> ", dest)
|
||||
display("Creating", "stub: $1 -> $2" % [pkgDestDir / bin, dest],
|
||||
priority = MediumPriority)
|
||||
var contents = "@"
|
||||
if options.config.chcp:
|
||||
if fixChcp:
|
||||
|
|
@ -470,7 +485,8 @@ proc installFromDir(dir: string, latest: bool, options: Options,
|
|||
writeFile(dest, contents)
|
||||
# For bash on Windows (Cygwin/Git bash).
|
||||
let bashDest = dest.changeFileExt("")
|
||||
echo("Creating Cygwin stub: ", pkgDestDir / bin, " -> ", bashDest)
|
||||
display("Creating", "Cygwin stub: $1 -> $2" %
|
||||
[pkgDestDir / bin, bashDest], priority = MediumPriority)
|
||||
writeFile(bashDest, "\"" & pkgDestDir / bin & "\" \"$@\"\n")
|
||||
else:
|
||||
{.error: "Sorry, your platform is not supported.".}
|
||||
|
|
@ -491,7 +507,8 @@ proc installFromDir(dir: string, latest: bool, options: Options,
|
|||
result.paths.add pkgDestDir
|
||||
result.pkg = pkgInfo
|
||||
|
||||
echo(pkgInfo.name & " installed successfully.")
|
||||
display("Success:", pkgInfo.name & " installed successfully.",
|
||||
Success, HighPriority)
|
||||
|
||||
proc getNimbleTempDir(): string =
|
||||
## Returns a path to a temporary directory.
|
||||
|
|
@ -529,8 +546,8 @@ proc downloadPkg(url: string, verRange: VersionRange,
|
|||
if modUrl.contains("github.com") and modUrl.endswith("/"):
|
||||
modUrl = modUrl[0 .. ^2]
|
||||
|
||||
echo("Downloading ", modUrl, " into ", downloadDir, " using ",
|
||||
downMethod, "...")
|
||||
display("Downloading", "$1 using $2" % [modUrl, $downMethod],
|
||||
priority = HighPriority)
|
||||
result = (
|
||||
downloadDir,
|
||||
doDownload(modUrl, downloadDir, verRange, downMethod, options)
|
||||
|
|
@ -545,15 +562,15 @@ proc getDownloadInfo*(pv: PkgTuple, options: Options,
|
|||
if getPackage(pv.name, options, pkg):
|
||||
return (pkg.downloadMethod.getDownloadMethod(), pkg.url)
|
||||
else:
|
||||
# If package is not found give the user a chance to update
|
||||
# If package is not found give the user a chance to refresh
|
||||
# package.json
|
||||
if doPrompt and
|
||||
options.prompt(pv.name & " not found in any local packages.json, " &
|
||||
"check internet for updated packages?"):
|
||||
update(options)
|
||||
refresh(options)
|
||||
|
||||
# Once we've updated, try again, but don't prompt if not found
|
||||
# (as we've already updated and a failure means it really
|
||||
# Once we've refreshed, try again, but don't prompt if not found
|
||||
# (as we've already refreshed and a failure means it really
|
||||
# isn't there)
|
||||
return getDownloadInfo(pv, options, false)
|
||||
else:
|
||||
|
|
@ -570,9 +587,9 @@ proc install(packages: seq[PkgTuple],
|
|||
if doPrompt and
|
||||
options.prompt("No local packages.json found, download it from " &
|
||||
"internet?"):
|
||||
update(options)
|
||||
refresh(options)
|
||||
else:
|
||||
quit("Please run nimble refresh.", QuitFailure)
|
||||
raise newException(NimbleError, "Please run nimble refresh.")
|
||||
|
||||
# Install each package.
|
||||
for pv in packages:
|
||||
|
|
@ -608,6 +625,13 @@ proc build(options: Options) =
|
|||
buildFromDir(pkgInfo, paths, false)
|
||||
|
||||
proc compile(options: Options) =
|
||||
let bin = options.action.file
|
||||
if bin == "":
|
||||
raise newException(NimbleError, "You need to specify a file to compile.")
|
||||
|
||||
if not fileExists(bin):
|
||||
raise newException(NimbleError, "Specified file does not exist.")
|
||||
|
||||
var pkgInfo = getPkgInfo(getCurrentDir(), options)
|
||||
nimScriptHint(pkgInfo)
|
||||
let paths = processDeps(pkginfo, options)
|
||||
|
|
@ -617,20 +641,17 @@ proc compile(options: Options) =
|
|||
for option in options.action.compileOptions:
|
||||
args.add(option & " ")
|
||||
|
||||
let bin = options.action.file
|
||||
let backend =
|
||||
if options.action.backend.len > 0:
|
||||
options.action.backend
|
||||
else:
|
||||
pkgInfo.backend
|
||||
|
||||
if bin == "":
|
||||
raise newException(NimbleError, "You need to specify a file to compile.")
|
||||
|
||||
echo("Compiling ", bin, " (", pkgInfo.name, ") using ", backend,
|
||||
" backend...")
|
||||
display("Compiling", "$1 (from package $2) using $3 backend" %
|
||||
[bin, pkgInfo.name, backend], priority = HighPriority)
|
||||
doCmd("\"" & getNimBin() & "\" $# --noBabelPath $# \"$#\"" %
|
||||
[backend, args, bin])
|
||||
display("Success:", "Compilation finished", Success, HighPriority)
|
||||
|
||||
proc search(options: Options) =
|
||||
## Searches for matches in ``options.action.search``.
|
||||
|
|
@ -663,7 +684,7 @@ proc search(options: Options) =
|
|||
onFound()
|
||||
|
||||
if not found:
|
||||
echo("No package found.")
|
||||
display("Error", "No package found.", Error, HighPriority)
|
||||
|
||||
proc list(options: Options) =
|
||||
if needsRefresh(options):
|
||||
|
|
@ -694,7 +715,7 @@ proc listInstalled(options: Options) =
|
|||
type VersionAndPath = tuple[version: Version, path: string]
|
||||
|
||||
proc listPaths(options: Options) =
|
||||
## Loops over installing packages displaying their installed paths.
|
||||
## Loops over the specified packages displaying their installed paths.
|
||||
##
|
||||
## If there are several packages installed, only the last one (the version
|
||||
## listed in the packages.json) will be displayed. If any package name is not
|
||||
|
|
@ -704,6 +725,10 @@ proc listPaths(options: Options) =
|
|||
## On success the proc returns normally.
|
||||
assert options.action.typ == actionPath
|
||||
assert(not options.action.packages.isNil)
|
||||
|
||||
if options.action.packages.len == 0:
|
||||
raise newException(NimbleError, "A package name needs to be specified")
|
||||
|
||||
var errors = 0
|
||||
for name, version in options.action.packages.items:
|
||||
var installed: seq[VersionAndPath] = @[]
|
||||
|
|
@ -713,11 +738,9 @@ proc listPaths(options: Options) =
|
|||
continue
|
||||
|
||||
let
|
||||
nimScriptFile = path / name.addFileExt("nims")
|
||||
babelFile = path / name.addFileExt("babel")
|
||||
nimbleFile = path / name.addFileExt("nimble")
|
||||
hasSpec = nimScriptFile.existsFile or
|
||||
nimbleFile.existsFile or babelFile.existsFile
|
||||
hasSpec = nimbleFile.existsFile
|
||||
|
||||
if hasSpec:
|
||||
var pkgInfo = getPkgInfo(path, options)
|
||||
var v: VersionAndPath
|
||||
|
|
@ -725,13 +748,16 @@ proc listPaths(options: Options) =
|
|||
v.path = options.getPkgsDir / (pkgInfo.name & '-' & pkgInfo.version)
|
||||
installed.add(v)
|
||||
else:
|
||||
echo "Warning: No .nimble file found for ", path
|
||||
display("Warning:", "No .nimble file found for " & path, Warning,
|
||||
MediumPriority)
|
||||
|
||||
if installed.len > 0:
|
||||
sort(installed, system.cmp[VersionAndPath], Descending)
|
||||
# The output for this command is used by tools so we do not use display().
|
||||
echo installed[0].path
|
||||
else:
|
||||
echo "Warning: Package '" & name & "' not installed"
|
||||
display("Warning:", "Package '$1' is not installed" % name, Warning,
|
||||
MediumPriority)
|
||||
errors += 1
|
||||
if errors > 0:
|
||||
raise newException(NimbleError,
|
||||
|
|
@ -768,9 +794,10 @@ proc dump(options: Options) =
|
|||
proc init(options: Options) =
|
||||
var nimbleFile: string = ""
|
||||
|
||||
echo("In order to initialise a new Nimble package, I will need to ask you\n" &
|
||||
display("Info:",
|
||||
"In order to initialise a new Nimble package, I will need to ask you\n" &
|
||||
"some questions. Default values are shown in square brackets, press\n" &
|
||||
"enter to use them.")
|
||||
"enter to use them.", priority = HighPriority)
|
||||
|
||||
# Ask for package name.
|
||||
if options.action.projName != "":
|
||||
|
|
@ -778,7 +805,7 @@ proc init(options: Options) =
|
|||
nimbleFile = pkgName.changeFileExt("nimble")
|
||||
else:
|
||||
var pkgName = os.getCurrentDir().splitPath.tail.toValidPackageName()
|
||||
pkgName = promptCustom("Enter package name", pkgName)
|
||||
pkgName = promptCustom("Package name?", pkgName)
|
||||
nimbleFile = pkgName.changeFileExt("nimble")
|
||||
|
||||
validatePackageName(nimbleFile.changeFileExt(""))
|
||||
|
|
@ -787,7 +814,7 @@ proc init(options: Options) =
|
|||
raise newException(NimbleError, "Nimble file already exists.")
|
||||
|
||||
# Ask for package version.
|
||||
let pkgVersion = promptCustom("Enter initial version of package", "0.1.0")
|
||||
let pkgVersion = promptCustom("Initial version of package?", "0.1.0")
|
||||
validateVersion(pkgVersion)
|
||||
|
||||
# Ask for package author
|
||||
|
|
@ -800,25 +827,20 @@ proc init(options: Options) =
|
|||
let (name, exitCode) = doCmdEx("hg config ui.username")
|
||||
if exitCode == QuitSuccess and name.len > 0:
|
||||
defaultAuthor = name.strip()
|
||||
let pkgAuthor = promptCustom("Enter your name", defaultAuthor)
|
||||
let pkgAuthor = promptCustom("Your name?", defaultAuthor)
|
||||
|
||||
# Ask for description
|
||||
let pkgDesc = promptCustom("Enter package description", "")
|
||||
let pkgDesc = promptCustom("Package description?", "")
|
||||
|
||||
# Ask for license
|
||||
# TODO: Provide selection of licenses, or select random default license.
|
||||
let pkgLicense = promptCustom("Enter package license", "MIT")
|
||||
let pkgLicense = promptCustom("Package license?", "MIT")
|
||||
|
||||
# Ask for Nim dependency
|
||||
let nimDepDef = getNimrodVersion()
|
||||
let pkgNimDep = promptCustom("Enter lowest supported Nim version", $nimDepDef)
|
||||
let pkgNimDep = promptCustom("Lowest supported Nim version?", $nimDepDef)
|
||||
validateVersion(pkgNimDep)
|
||||
|
||||
# Now generate the .nimble file.
|
||||
if existsFile(os.getCurrentDir() / nimbleFile):
|
||||
raise newException(NimbleError,
|
||||
"Looks like a Nimble file has already been created.")
|
||||
|
||||
var outFile: File
|
||||
if open(f = outFile, filename = nimbleFile, mode = fmWrite):
|
||||
outFile.writeLine """# Package
|
||||
|
|
@ -838,6 +860,8 @@ requires "nim >= $#"
|
|||
raise newException(NimbleError, "Unable to open file " & nimbleFile &
|
||||
" for writing: " & osErrorMsg(osLastError()))
|
||||
|
||||
display("Success:", "Nimble file created successfully", Success, HighPriority)
|
||||
|
||||
proc uninstall(options: Options) =
|
||||
if options.action.packages.len == 0:
|
||||
raise newException(NimbleError,
|
||||
|
|
@ -846,13 +870,14 @@ proc uninstall(options: Options) =
|
|||
var pkgsToDelete: seq[PackageInfo] = @[]
|
||||
# Do some verification.
|
||||
for pkgTup in options.action.packages:
|
||||
echo("Looking for ", pkgTup.name, " (", $pkgTup.ver, ")...")
|
||||
display("Looking", "for $1 ($2)" % [pkgTup.name, $pkgTup.ver],
|
||||
priority = HighPriority)
|
||||
let installedPkgs = getInstalledPkgs(options.getPkgsDir(), options)
|
||||
var pkgList = findAllPkgs(installedPkgs, pkgTup)
|
||||
if pkgList.len == 0:
|
||||
raise newException(NimbleError, "Package not found")
|
||||
|
||||
echo("Checking reverse dependencies...")
|
||||
display("Checking", "reverse dependencies", priority = HighPriority)
|
||||
var errors: seq[string] = @[]
|
||||
for pkg in pkgList:
|
||||
# Check whether any packages depend on the ones the user is trying to
|
||||
|
|
@ -861,17 +886,17 @@ proc uninstall(options: Options) =
|
|||
if not thisPkgsDep.isNil:
|
||||
var reason = ""
|
||||
if thisPkgsDep.len == 1:
|
||||
reason = thisPkgsDep[0]["name"].str &
|
||||
" (" & thisPkgsDep[0]["version"].str & ") depends on it"
|
||||
reason = "$1 ($2) depends on it" % [thisPkgsDep[0]["name"].str,
|
||||
thisPkgsDep[0]["version"].str]
|
||||
else:
|
||||
for i in 0 .. <thisPkgsDep.len:
|
||||
reason.add thisPkgsDep[i]["name"].str &
|
||||
" (" & thisPkgsDep[i]["version"].str & ")"
|
||||
reason.add("$1 ($2)" % [thisPkgsDep[i]["name"].str,
|
||||
thisPkgsDep[i]["version"].str])
|
||||
if i != <thisPkgsDep.len:
|
||||
reason.add ", "
|
||||
reason.add " depend on it"
|
||||
errors.add("Cannot uninstall " & pkgTup.name & " (" & pkg.version &
|
||||
")" & " because " & reason)
|
||||
errors.add("Cannot uninstall $1 ($2) because $3" % [pkgTup.name,
|
||||
pkg.version, reason])
|
||||
else:
|
||||
pkgsToDelete.add pkg
|
||||
|
||||
|
|
@ -882,12 +907,13 @@ proc uninstall(options: Options) =
|
|||
for i in 0 .. <pkgsToDelete.len:
|
||||
if i != 0: pkgNames.add ", "
|
||||
let pkg = pkgsToDelete[i]
|
||||
pkgNames.add pkg.name & " (" & pkg.version & ")"
|
||||
pkgNames.add("$1 ($2)" % [pkg.name, pkg.version])
|
||||
|
||||
# Let's confirm that the user wants these packages removed.
|
||||
if not options.prompt("The following packages will be removed:\n " &
|
||||
pkgNames & "\nDo you wish to continue?"):
|
||||
quit(QuitSuccess)
|
||||
let msg = ("The following packages will be removed:\n $1\n" &
|
||||
"Do you wish to continue?") % pkgNames
|
||||
if not options.prompt(msg):
|
||||
raise NimbleQuit(msg: "")
|
||||
|
||||
for pkg in pkgsToDelete:
|
||||
# If we reach this point then the package can be safely removed.
|
||||
|
|
@ -897,7 +923,8 @@ proc uninstall(options: Options) =
|
|||
let pkgFull = readPackageInfo(pkg.mypath, options, false)
|
||||
removeRevDep(options, pkgFull)
|
||||
removePkgDir(options.getPkgsDir / (pkg.name & '-' & pkg.version), options)
|
||||
echo("Removed ", pkg.name, " (", $pkg.version, ")")
|
||||
display("Removed", "$1 ($2)" % [pkg.name, $pkg.version], Success,
|
||||
HighPriority)
|
||||
|
||||
proc listTasks(options: Options) =
|
||||
let nimbleFile = findNimbleFile(getCurrentDir(), true)
|
||||
|
|
@ -930,11 +957,12 @@ proc doAction(options: Options) =
|
|||
createDir(options.getPkgsDir)
|
||||
|
||||
if not execHook(options, true):
|
||||
echo("Pre-hook prevented further execution.")
|
||||
display("Warning", "Pre-hook prevented further execution.", Warning,
|
||||
HighPriority)
|
||||
return
|
||||
case options.action.typ
|
||||
of actionRefresh:
|
||||
update(options)
|
||||
refresh(options)
|
||||
of actionInstall:
|
||||
let (_, pkgInfo) = install(options.action.packages, options)
|
||||
if options.action.packages.len == 0:
|
||||
|
|
@ -965,8 +993,6 @@ proc doAction(options: Options) =
|
|||
dump(options)
|
||||
of actionTasks:
|
||||
listTasks(options)
|
||||
of actionVersion:
|
||||
writeVersion()
|
||||
of actionNil:
|
||||
assert false
|
||||
of actionCustom:
|
||||
|
|
@ -977,14 +1003,19 @@ proc doAction(options: Options) =
|
|||
|
||||
let execResult = execTask(nimbleFile, options.action.command, options)
|
||||
if not execResult.success:
|
||||
echo("FAILURE: Could not find task ", options.action.command, " in ",
|
||||
nimbleFile)
|
||||
writeHelp()
|
||||
raiseNimbleError(msg = "Could not find task $1 in $2" %
|
||||
[options.action.command, nimbleFile],
|
||||
hint = "Run `nimble --help` and/or `nimble tasks` for" &
|
||||
" a list of possible commands.")
|
||||
|
||||
if execResult.command.normalize == "nop":
|
||||
echo("WARNING: Using `setCommand 'nop'` is not necessary.")
|
||||
display("Warning:", "Using `setCommand 'nop'` is not necessary.", Warning,
|
||||
HighPriority)
|
||||
return
|
||||
|
||||
if not execHook(options, false):
|
||||
return
|
||||
|
||||
if execResult.hasTaskRequestedCommand():
|
||||
var newOptions = initOptions()
|
||||
newOptions.config = options.config
|
||||
|
|
@ -1000,12 +1031,27 @@ proc doAction(options: Options) =
|
|||
discard execHook(options, false)
|
||||
|
||||
when isMainModule:
|
||||
when defined(release):
|
||||
try:
|
||||
parseCmdLine().doAction()
|
||||
except NimbleError:
|
||||
quit("FAILURE: " & getCurrentExceptionMsg())
|
||||
finally:
|
||||
removeDir(getNimbleTempDir())
|
||||
else:
|
||||
var error = ""
|
||||
var hint = ""
|
||||
|
||||
try:
|
||||
parseCmdLine().doAction()
|
||||
except NimbleError:
|
||||
let err = (ref NimbleError)(getCurrentException())
|
||||
error = err.msg
|
||||
when not defined(release):
|
||||
let stackTrace = getStackTrace(getCurrentException())
|
||||
error = stackTrace & "\n\n" & error
|
||||
if not err.isNil:
|
||||
hint = err.hint
|
||||
except NimbleQuit:
|
||||
nil
|
||||
finally:
|
||||
removeDir(getNimbleTempDir())
|
||||
|
||||
if error.len > 0:
|
||||
displayTip()
|
||||
display("Error:", error, Error, HighPriority)
|
||||
if hint.len > 0:
|
||||
display("Hint:", hint, Warning, HighPriority)
|
||||
quit(1)
|
||||
|
|
|
|||
166
src/nimblepkg/cli.nim
Normal file
166
src/nimblepkg/cli.nim
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
# Copyright (C) Dominik Picheta. All rights reserved.
|
||||
# BSD License. Look at license.txt for more info.
|
||||
#
|
||||
# Rough rules/philosophy for the messages that Nimble displays are the following:
|
||||
# - Green is only shown when the requested operation is successful.
|
||||
# - Blue can be used to emphasise certain keywords, for example actions such
|
||||
# as "Downloading" or "Reading".
|
||||
# - Red is used when the requested operation fails with an error.
|
||||
# - Yellow is used for warnings.
|
||||
#
|
||||
# - Dim for LowPriority.
|
||||
# - Bright for HighPriority.
|
||||
# - Normal for MediumPriority.
|
||||
|
||||
import logging, terminal, sets, strutils
|
||||
|
||||
type
|
||||
CLI* = ref object
|
||||
level: Priority
|
||||
warnings: HashSet[(string, string)]
|
||||
suppressionCount: int ## Amount of messages which were not shown.
|
||||
|
||||
Priority* = enum
|
||||
DebugPriority, LowPriority, MediumPriority, HighPriority
|
||||
|
||||
DisplayType* = enum
|
||||
Error, Warning, Message, Success
|
||||
|
||||
ForcePrompt* = enum
|
||||
dontForcePrompt, forcePromptYes, forcePromptNo
|
||||
|
||||
const
|
||||
longestCategory = len("Downloading")
|
||||
foregrounds: array[Error .. Success, ForegroundColor] =
|
||||
[fgRed, fgYellow, fgCyan, fgGreen]
|
||||
styles: array[DebugPriority .. HighPriority, set[Style]] =
|
||||
[{styleDim}, {styleDim}, {}, {styleBright}]
|
||||
|
||||
proc newCLI(): CLI =
|
||||
result = CLI(
|
||||
level: HighPriority,
|
||||
warnings: initSet[(string, string)](),
|
||||
suppressionCount: 0
|
||||
)
|
||||
|
||||
var globalCLI = newCLI()
|
||||
|
||||
|
||||
proc calculateCategoryOffset(category: string): int =
|
||||
assert category.len <= longestCategory
|
||||
return longestCategory - category.len
|
||||
|
||||
proc displayCategory(category: string, displayType: DisplayType,
|
||||
priority: Priority) =
|
||||
# Calculate how much the `category` must be offset to align along a center
|
||||
# line.
|
||||
let offset = calculateCategoryOffset(category)
|
||||
# Display the category.
|
||||
if priority != DebugPriority:
|
||||
setForegroundColor(stdout, foregrounds[displayType])
|
||||
writeStyled("$1$2 " % [repeatChar(offset), category], styles[priority])
|
||||
resetAttributes()
|
||||
|
||||
proc displayLine(category, line: string, displayType: DisplayType,
|
||||
priority: Priority) =
|
||||
displayCategory(category, displayType, priority)
|
||||
|
||||
# Display the message.
|
||||
echo(line)
|
||||
|
||||
proc display*(category, msg: string, displayType = Message,
|
||||
priority = MediumPriority) =
|
||||
# Multiple warnings containing the same messages should not be shown.
|
||||
let warningPair = (category, msg)
|
||||
if displayType == Warning:
|
||||
if warningPair in globalCLI.warnings:
|
||||
return
|
||||
else:
|
||||
globalCLI.warnings.incl(warningPair)
|
||||
|
||||
# Suppress this message if its priority isn't high enough.
|
||||
# TODO: Per-priority suppression counts?
|
||||
if priority < globalCLI.level:
|
||||
if priority != DebugPriority:
|
||||
globalCLI.suppressionCount.inc
|
||||
return
|
||||
|
||||
# Display each line in the message.
|
||||
var i = 0
|
||||
for line in msg.splitLines():
|
||||
if len(line) == 0: continue
|
||||
displayLine(if i == 0: category else: "...", line, displayType, priority)
|
||||
i.inc
|
||||
|
||||
proc displayDebug*(category, msg: string) =
|
||||
## Convenience for displaying debug messages.
|
||||
display(category, msg, priority = DebugPriority)
|
||||
|
||||
proc displayDebug*(msg: string) =
|
||||
## Convenience for displaying debug messages with a default category.
|
||||
displayDebug("Debug:", msg)
|
||||
|
||||
proc displayTip*() =
|
||||
## Called just before Nimble exits. Shows some tips for the user, for example
|
||||
## the amount of messages that were suppressed and how to show them.
|
||||
if globalCLI.suppressionCount > 0:
|
||||
let msg = "$1 messages have been suppressed, use --verbose to show them." %
|
||||
$globalCLI.suppressionCount
|
||||
display("Tip:", msg, Warning, HighPriority)
|
||||
|
||||
proc prompt*(forcePrompts: ForcePrompt, question: string): bool =
|
||||
case forcePrompts
|
||||
of forcePromptYes:
|
||||
display("Prompt:", question & " -> [forced yes]", Warning, HighPriority)
|
||||
return true
|
||||
of forcePromptNo:
|
||||
display("Prompt:", question & " -> [forced no]", Warning, HighPriority)
|
||||
return false
|
||||
of dontForcePrompt:
|
||||
display("Prompt:", question & " [y/N]", Warning, HighPriority)
|
||||
displayCategory("Answer:", Warning, HighPriority)
|
||||
let yn = stdin.readLine()
|
||||
case yn.normalize
|
||||
of "y", "yes":
|
||||
return true
|
||||
of "n", "no":
|
||||
return false
|
||||
else:
|
||||
return false
|
||||
|
||||
proc promptCustom*(question, default: string): string =
|
||||
if default == "":
|
||||
display("Prompt:", question, Warning, HighPriority)
|
||||
displayCategory("Answer:", Warning, HighPriority)
|
||||
let user = stdin.readLine()
|
||||
if user.len == 0: return promptCustom(question, default)
|
||||
else: return user
|
||||
else:
|
||||
display("Prompt:", question & " [" & default & "]", Warning, HighPriority)
|
||||
displayCategory("Answer:", Warning, HighPriority)
|
||||
let user = stdin.readLine()
|
||||
if user == "": return default
|
||||
else: return user
|
||||
|
||||
proc setVerbosity*(level: Priority) =
|
||||
globalCLI.level = level
|
||||
|
||||
when isMainModule:
|
||||
display("Reading", "config file at /Users/dom/.config/nimble/nimble.ini",
|
||||
priority = LowPriority)
|
||||
|
||||
display("Reading", "official package list",
|
||||
priority = LowPriority)
|
||||
|
||||
display("Downloading", "daemonize v0.0.2 using Git",
|
||||
priority = HighPriority)
|
||||
|
||||
display("Warning", "dashes in package names will be deprecated", Warning,
|
||||
priority = HighPriority)
|
||||
|
||||
display("Error", """Unable to read package info for /Users/dom/.nimble/pkgs/nimble-0.7.11
|
||||
Reading as ini file failed with:
|
||||
Invalid section: .
|
||||
Evaluating as NimScript file failed with:
|
||||
Users/dom/.nimble/pkgs/nimble-0.7.11/nimble.nimble(3, 23) Error: cannot open 'src/nimblepkg/common'.
|
||||
""", Error, HighPriority)
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
# Copyright (C) Dominik Picheta. All rights reserved.
|
||||
# BSD License. Look at license.txt for more info.
|
||||
#
|
||||
# Various miscellaneous common types reside here, to avoid problems with
|
||||
|
|
@ -7,7 +8,7 @@ when not defined(nimscript):
|
|||
import sets
|
||||
|
||||
import version
|
||||
export version.NimbleError
|
||||
export version.NimbleError # TODO: Surely there is a better way?
|
||||
|
||||
type
|
||||
BuildFailed* = object of NimbleError
|
||||
|
|
@ -37,5 +38,13 @@ when not defined(nimscript):
|
|||
backend*: string
|
||||
foreignDeps*: seq[string]
|
||||
|
||||
## Same as quit(QuitSuccess), but allows cleanup.
|
||||
NimbleQuit* = ref object of Exception
|
||||
|
||||
proc raiseNimbleError*(msg: string, hint = "") =
|
||||
var exc = newException(NimbleError, msg)
|
||||
exc.hint = hint
|
||||
raise exc
|
||||
|
||||
const
|
||||
nimbleVersion* = "0.7.11"
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
# BSD License. Look at license.txt for more info.
|
||||
import parsecfg, streams, strutils, os, tables, Uri
|
||||
|
||||
import tools, version, common
|
||||
import tools, version, common, cli
|
||||
|
||||
type
|
||||
Config* = object
|
||||
nimbleDir*: string
|
||||
chcp*: bool # Whether to change the code page in .cmd files on Win.
|
||||
packageLists*: Table[string, PackageList] ## URLs to packages.json files
|
||||
packageLists*: Table[string, PackageList] ## Names -> packages.json files
|
||||
cloneUsingHttps*: bool # Whether to replace git:// for https://
|
||||
httpProxy*: Uri # Proxy for package list downloads.
|
||||
|
||||
|
|
@ -50,13 +50,15 @@ proc parseConfig*(): Config =
|
|||
var f = newFileStream(confFile, fmRead)
|
||||
if f == nil:
|
||||
# Try the old deprecated babel.ini
|
||||
# TODO: This can be removed.
|
||||
confFile = getConfigDir() / "babel" / "babel.ini"
|
||||
f = newFileStream(confFile, fmRead)
|
||||
if f != nil:
|
||||
echo("[Warning] Using deprecated config file at ", confFile)
|
||||
display("Warning", "Using deprecated config file at " & confFile,
|
||||
Warning, HighPriority)
|
||||
|
||||
if f != nil:
|
||||
echo("Reading from config file at ", confFile)
|
||||
display("Reading", "config file at " & confFile, priority = LowPriority)
|
||||
var p: CfgParser
|
||||
open(p, f, confFile)
|
||||
var currentSection = ""
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import parseutils, os, osproc, strutils, tables, pegs
|
||||
|
||||
import packageinfo, packageparser, version, tools, common, options
|
||||
import packageinfo, packageparser, version, tools, common, options, cli
|
||||
|
||||
type
|
||||
DownloadMethod* {.pure.} = enum
|
||||
|
|
@ -142,7 +142,6 @@ proc doDownload*(url: string, downloadDir: string, verRange: VersionRange,
|
|||
##
|
||||
## Returns the version of the repository which has been downloaded.
|
||||
template getLatestByTag(meth: untyped) {.dirty.} =
|
||||
echo("Found tags...")
|
||||
# Find latest version that fits our ``verRange``.
|
||||
var latest = findLatest(verRange, versions)
|
||||
## Note: HEAD is not used when verRange.kind is verAny. This is
|
||||
|
|
@ -150,7 +149,7 @@ proc doDownload*(url: string, downloadDir: string, verRange: VersionRange,
|
|||
|
||||
# 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.
|
||||
# https://github.com/nimrod-code/nimble/issues/22
|
||||
# https://github.com/nim-lang/nimble/issues/22
|
||||
meth
|
||||
if $latest.ver != "":
|
||||
result = parseVersionRange($latest.ver)
|
||||
|
|
@ -188,7 +187,8 @@ proc doDownload*(url: string, downloadDir: string, verRange: VersionRange,
|
|||
let versions = getTagsListRemote(url, downMethod).getVersionList()
|
||||
if versions.len > 0:
|
||||
getLatestByTag:
|
||||
echo("Cloning latest tagged version: ", latest.tag)
|
||||
display("Cloning", "latest tagged version: " & latest.tag,
|
||||
priority = MediumPriority)
|
||||
doClone(downMethod, url, downloadDir, latest.tag)
|
||||
else:
|
||||
# If no commits have been tagged on the repo we just clone HEAD.
|
||||
|
|
@ -202,7 +202,8 @@ proc doDownload*(url: string, downloadDir: string, verRange: VersionRange,
|
|||
|
||||
if versions.len > 0:
|
||||
getLatestByTag:
|
||||
echo("Switching to latest tagged version: ", latest.tag)
|
||||
display("Switching", "to latest tagged version: " & latest.tag,
|
||||
priority = MediumPriority)
|
||||
doCheckout(downMethod, downloadDir, latest.tag)
|
||||
|
||||
verifyClone()
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from compiler/scriptconfig import setupVM
|
|||
from compiler/astalgo import strTableGet
|
||||
import compiler/options as compiler_options
|
||||
|
||||
import common, version, options, packageinfo
|
||||
import common, version, options, packageinfo, cli
|
||||
import os, strutils, strtabs, times, osproc, sets
|
||||
|
||||
when not declared(resetAllModulesHard):
|
||||
|
|
@ -299,7 +299,7 @@ proc readPackageInfoFromNims*(scriptName: string, options: Options,
|
|||
if msgs.gErrorCounter > 0:
|
||||
raise newException(NimbleError, previousMsg)
|
||||
elif previousMsg.len > 0:
|
||||
echo(previousMsg)
|
||||
display("Info", previousMsg, priority = HighPriority)
|
||||
if output.normalize.startsWith("error"):
|
||||
raise newException(NimbleError, output)
|
||||
previousMsg = output
|
||||
|
|
@ -387,7 +387,8 @@ proc execTask*(scriptName, taskName: string,
|
|||
result.success = true
|
||||
result.flags = newStringTable()
|
||||
compiler_options.command = internalCmd
|
||||
echo("Executing task ", taskName, " in ", scriptName)
|
||||
display("Executing", "task $# in $#" % [taskName, scriptName],
|
||||
priority = HighPriority)
|
||||
|
||||
let thisModule = execScript(scriptName, result.flags, options)
|
||||
let prc = thisModule.tab.strTableGet(getIdent(taskName & "Task"))
|
||||
|
|
@ -417,7 +418,8 @@ proc execHook*(scriptName, actionName: string, before: bool,
|
|||
let hookName =
|
||||
if before: actionName.toLowerAscii & "Before"
|
||||
else: actionName.toLowerAscii & "After"
|
||||
echo("Attempting to execute hook ", hookName, " in ", scriptName)
|
||||
display("Attempting", "to execute hook $# in $#" % [hookName, scriptName],
|
||||
priority = MediumPriority)
|
||||
|
||||
let thisModule = execScript(scriptName, result.flags, options)
|
||||
# Explicitly execute the task procedure, instead of relying on hack.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import json, strutils, os, parseopt, strtabs, uri, tables
|
||||
from httpclient import Proxy, newProxy
|
||||
|
||||
import config, version, tools, common
|
||||
import config, version, tools, common, cli
|
||||
|
||||
type
|
||||
Options* = object
|
||||
|
|
@ -12,6 +12,8 @@ type
|
|||
depsOnly*: bool
|
||||
queryVersions*: bool
|
||||
queryInstalled*: bool
|
||||
nimbleDir*: string
|
||||
verbosity*: cli.Priority
|
||||
action*: Action
|
||||
config*: Config
|
||||
nimbleData*: JsonNode ## Nimbledata.json
|
||||
|
|
@ -21,12 +23,11 @@ type
|
|||
actionNil, actionRefresh, actionInit, actionDump, actionPublish,
|
||||
actionInstall, actionSearch,
|
||||
actionList, actionBuild, actionPath, actionUninstall, actionCompile,
|
||||
actionCustom, actionTasks, actionVersion
|
||||
actionCustom, actionTasks
|
||||
|
||||
Action* = object
|
||||
case typ*: ActionType
|
||||
of actionNil, actionList, actionBuild, actionPublish, actionTasks,
|
||||
actionVersion: nil
|
||||
of actionNil, actionList, actionBuild, actionPublish, actionTasks: nil
|
||||
of actionRefresh:
|
||||
optionalURL*: string # Overrides default package list.
|
||||
of actionInstall, actionPath, actionUninstall:
|
||||
|
|
@ -44,9 +45,6 @@ type
|
|||
arguments*: seq[string]
|
||||
flags*: StringTableRef
|
||||
|
||||
ForcePrompt* = enum
|
||||
dontForcePrompt, forcePromptYes, forcePromptNo
|
||||
|
||||
|
||||
const
|
||||
help* = """
|
||||
|
|
@ -54,6 +52,7 @@ Usage: nimble COMMAND [opts]
|
|||
|
||||
Commands:
|
||||
install [pkgname, ...] Installs a list of packages.
|
||||
[-d, --depsOnly] Install only dependencies.
|
||||
init [pkgname] Initializes a new Nimble project.
|
||||
publish Publishes a package on nim-lang/packages.
|
||||
The current working directory needs to be the
|
||||
|
|
@ -64,9 +63,11 @@ Commands:
|
|||
to the Nim compiler.
|
||||
refresh [url] Refreshes the package list. A package list URL
|
||||
can be optionally specified.
|
||||
search [--ver] pkg/tag Searches for a specified package. Search is
|
||||
search pkg/tag Searches for a specified package. Search is
|
||||
performed by tag and by name.
|
||||
list [--ver] Lists all packages.
|
||||
[--ver] Query remote server for package version.
|
||||
list Lists all packages.
|
||||
[--ver] Query remote server for package version.
|
||||
[-i, --installed] Lists all installed packages.
|
||||
tasks Lists the tasks specified in the Nimble
|
||||
package's Nimble file.
|
||||
|
|
@ -83,26 +84,29 @@ Options:
|
|||
--ver Query remote server for package version
|
||||
information when searching or listing packages
|
||||
--nimbleDir dirname Set the Nimble directory.
|
||||
-d --depsOnly Install only dependencies.
|
||||
--verbose Show all non-debug output.
|
||||
--debug Show all output including debug messages.
|
||||
|
||||
For more information read the Github readme:
|
||||
https://github.com/nim-lang/nimble#readme
|
||||
"""
|
||||
|
||||
proc writeHelp*() =
|
||||
proc writeHelp*(quit=true) =
|
||||
echo(help)
|
||||
quit(QuitSuccess)
|
||||
if quit:
|
||||
raise NimbleQuit(msg: "")
|
||||
|
||||
proc writeVersion() =
|
||||
echo("nimble v$# compiled at $# $#" %
|
||||
[nimbleVersion, CompileDate, CompileTime])
|
||||
raise NimbleQuit(msg: "")
|
||||
|
||||
proc parseActionType*(action: string): ActionType =
|
||||
case action.normalize()
|
||||
of "install", "path":
|
||||
case action.normalize()
|
||||
of "install":
|
||||
result = actionInstall
|
||||
of "path":
|
||||
result = actionPath
|
||||
else:
|
||||
discard
|
||||
of "install":
|
||||
result = actionInstall
|
||||
of "path":
|
||||
result = actionPath
|
||||
of "build":
|
||||
result = actionBuild
|
||||
of "c", "compile", "js", "cpp", "cc":
|
||||
|
|
@ -153,30 +157,14 @@ proc initAction*(options: var Options, key: string) =
|
|||
options.action.arguments = @[]
|
||||
options.action.flags = newStringTable()
|
||||
of actionBuild, actionPublish, actionList, actionTasks,
|
||||
actionNil, actionVersion: discard
|
||||
actionNil: discard
|
||||
|
||||
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.
|
||||
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
|
||||
return prompt(options.forcePrompts, question)
|
||||
|
||||
proc renameBabelToNimble(options: Options) {.deprecated.} =
|
||||
let babelDir = getHomeDir() / ".babel"
|
||||
|
|
@ -191,7 +179,13 @@ proc renameBabelToNimble(options: Options) {.deprecated.} =
|
|||
removeFile(nimbleDir / "babeldata.json")
|
||||
|
||||
proc getNimbleDir*(options: Options): string =
|
||||
expandTilde(options.config.nimbleDir)
|
||||
result =
|
||||
if options.nimbleDir.len == 0:
|
||||
options.config.nimbleDir
|
||||
else:
|
||||
options.nimbleDir
|
||||
|
||||
return expandTilde(result)
|
||||
|
||||
proc getPkgsDir*(options: Options): string =
|
||||
options.getNimbleDir() / "pkgs"
|
||||
|
|
@ -235,33 +229,53 @@ proc parseArgument*(key: string, result: var Options) =
|
|||
discard
|
||||
|
||||
proc parseFlag*(flag, val: string, result: var Options) =
|
||||
case result.action.typ
|
||||
of actionCompile:
|
||||
if val == "":
|
||||
result.action.compileOptions.add("--" & flag)
|
||||
var wasFlagHandled = true
|
||||
|
||||
# Global flags.
|
||||
case flag.normalize()
|
||||
of "help", "h": writeHelp()
|
||||
of "version", "v": writeVersion()
|
||||
of "accept", "y": result.forcePrompts = forcePromptYes
|
||||
of "reject", "n": result.forcePrompts = forcePromptNo
|
||||
of "nimbledir": result.nimbleDir = val
|
||||
of "verbose": result.verbosity = LowPriority
|
||||
of "debug": result.verbosity = DebugPriority
|
||||
# Action-specific flags.
|
||||
of "installed", "i":
|
||||
if result.action.typ in [actionSearch, actionList]:
|
||||
result.queryInstalled = true
|
||||
else:
|
||||
result.action.compileOptions.add("--" & flag & ":" & val)
|
||||
of actionCustom:
|
||||
result.action.flags[flag] = val
|
||||
wasFlagHandled = false
|
||||
of "depsonly", "d":
|
||||
if result.action.typ == actionInstall:
|
||||
result.depsOnly = true
|
||||
else:
|
||||
wasFlagHandled = false
|
||||
of "ver":
|
||||
if result.action.typ in [actionSearch, actionList]:
|
||||
result.queryVersions = true
|
||||
else:
|
||||
wasFlagHandled = false
|
||||
else:
|
||||
# TODO: These should not be global.
|
||||
case flag.normalize()
|
||||
of "help", "h": writeHelp()
|
||||
of "version", "v":
|
||||
assert result.action.typ == actionNil
|
||||
result.action.typ = actionVersion
|
||||
of "accept", "y": result.forcePrompts = forcePromptYes
|
||||
of "reject", "n": result.forcePrompts = forcePromptNo
|
||||
of "ver": result.queryVersions = true
|
||||
of "nimbledir": result.config.nimbleDir = val # overrides option from file
|
||||
of "installed", "i": result.queryInstalled = true
|
||||
of "depsonly", "d": result.depsOnly = true
|
||||
case result.action.typ
|
||||
of actionCompile:
|
||||
if val == "":
|
||||
result.action.compileOptions.add("--" & flag)
|
||||
else:
|
||||
result.action.compileOptions.add("--" & flag & ":" & val)
|
||||
of actionCustom:
|
||||
result.action.flags[flag] = val
|
||||
else:
|
||||
raise newException(NimbleError, "Unknown option: --" & flag)
|
||||
wasFlagHandled = false
|
||||
|
||||
if not wasFlagHandled:
|
||||
raise newException(NimbleError, "Unknown option: --" & flag)
|
||||
|
||||
proc initOptions*(): Options =
|
||||
result.action.typ = actionNil
|
||||
result.pkgInfoCache = newTable[string, PackageInfo]()
|
||||
result.nimbleDir = ""
|
||||
result.verbosity = HighPriority
|
||||
|
||||
proc parseMisc(options: var Options) =
|
||||
# Load nimbledata.json
|
||||
|
|
@ -278,9 +292,9 @@ proc parseMisc(options: var Options) =
|
|||
|
||||
proc parseCmdLine*(): Options =
|
||||
result = initOptions()
|
||||
result.config = parseConfig()
|
||||
|
||||
# Parse command line params.
|
||||
# Parse command line params first. A simple `--version` shouldn't require
|
||||
# a config to be parsed.
|
||||
for kind, key, val in getOpt():
|
||||
case kind
|
||||
of cmdArgument:
|
||||
|
|
@ -292,6 +306,12 @@ proc parseCmdLine*(): Options =
|
|||
parseFlag(key, val, result)
|
||||
of cmdEnd: assert(false) # cannot happen
|
||||
|
||||
# Set verbosity level.
|
||||
setVerbosity(result.verbosity)
|
||||
|
||||
# Parse config.
|
||||
result.config = parseConfig()
|
||||
|
||||
# Parse other things, for example the nimbledata.json file.
|
||||
parseMisc(result)
|
||||
|
||||
|
|
@ -310,8 +330,8 @@ proc getProxy*(options: Options): Proxy =
|
|||
elif existsEnv("https_proxy"):
|
||||
url = getEnv("https_proxy")
|
||||
except ValueError:
|
||||
echo("WARNING: Unable to parse proxy from environment: ",
|
||||
getCurrentExceptionMsg())
|
||||
display("Warning:", "Unable to parse proxy from environment: " &
|
||||
getCurrentExceptionMsg(), Warning, HighPriority)
|
||||
|
||||
if url.len > 0:
|
||||
var parsed = parseUri(url)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (C) Dominik Picheta. All rights reserved.
|
||||
# BSD License. Look at license.txt for more info.
|
||||
import parsecfg, json, streams, strutils, parseutils, os, sets, tables
|
||||
import version, tools, common, options
|
||||
import version, tools, common, options, cli
|
||||
|
||||
type
|
||||
Package* = object
|
||||
|
|
@ -117,13 +117,10 @@ proc fromJson(obj: JSonNode): Package =
|
|||
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):
|
||||
bmeta = path / "babelmeta.json"
|
||||
if existsFile(bmeta):
|
||||
echo("WARNING: using deprecated babelmeta.json file in " & path)
|
||||
if not existsFile(bmeta):
|
||||
result.url = ""
|
||||
echo("WARNING: No nimblemeta.json file found in " & path)
|
||||
display("Warning:", "No nimblemeta.json file found in " & path,
|
||||
Warning, HighPriority)
|
||||
return
|
||||
# TODO: Make this an error.
|
||||
let cont = readFile(bmeta)
|
||||
|
|
@ -139,7 +136,7 @@ proc getPackage*(pkg: string, options: Options,
|
|||
## convenience the proc returns a boolean specifying if the ``resPkg`` was
|
||||
## successfully filled with good data.
|
||||
for name, list in options.config.packageLists:
|
||||
echo("Searching in \"", name, "\" package list...")
|
||||
display("Reading", "$1 package list" % name, priority = LowPriority)
|
||||
let packages = parseFile(options.getNimbleDir() /
|
||||
"packages_" & name.toLowerAscii() & ".json")
|
||||
for p in packages:
|
||||
|
|
@ -179,8 +176,8 @@ proc findNimbleFile*(dir: string; error: bool): string =
|
|||
raise newException(NimbleError,
|
||||
"Specified directory does not contain a .nimble file.")
|
||||
else:
|
||||
# TODO: Abstract logging.
|
||||
echo("WARNING: No .nimble file found for ", dir)
|
||||
display("Warning:", "No .nimble file found for " & dir, Warning,
|
||||
HighPriority)
|
||||
|
||||
proc getInstalledPkgsMin*(libsDir: string, options: Options):
|
||||
seq[tuple[pkginfo: PackageInfo, meta: MetaData]] =
|
||||
|
|
@ -279,7 +276,8 @@ proc validatePackagesList*(path: string): bool =
|
|||
let pkgList = parseFile(path)
|
||||
if pkgList.kind == JArray:
|
||||
if pkgList.len == 0:
|
||||
echo("WARNING: ", path, " contains no packages.")
|
||||
display("Warning:", path & " contains no packages.", Warning,
|
||||
HighPriority)
|
||||
return true
|
||||
except ValueError, JsonParsingError:
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (C) Dominik Picheta. All rights reserved.
|
||||
# BSD License. Look at license.txt for more info.
|
||||
import parsecfg, json, streams, strutils, parseutils, os, tables
|
||||
import version, tools, common, nimscriptsupport, options, packageinfo
|
||||
import version, tools, common, nimscriptsupport, options, packageinfo, cli
|
||||
|
||||
## Contains procedures for parsing .nimble files. Moved here from ``packageinfo``
|
||||
## because it depends on ``nimscriptsupport`` (``nimscriptsupport`` also
|
||||
|
|
@ -20,11 +20,7 @@ proc newValidationError(msg: string, warnInstalled: bool): ref ValidationError =
|
|||
result.warnInstalled = warnInstalled
|
||||
|
||||
proc raiseNewValidationError(msg: string, warnInstalled: bool) =
|
||||
if warnInstalled:
|
||||
# TODO: We warn everywhere for now. Raise the error in the next version.
|
||||
echo("WARNING: ", msg, ". Will be an error in next version!")
|
||||
else:
|
||||
raise newValidationError(msg, warnInstalled)
|
||||
raise newValidationError(msg, warnInstalled)
|
||||
|
||||
proc validatePackageName*(name: string) =
|
||||
## Raises an error if specified package name contains invalid characters.
|
||||
|
|
@ -91,10 +87,11 @@ proc validatePackageInfo(pkgInfo: PackageInfo, path: string) =
|
|||
|
||||
proc nimScriptHint*(pkgInfo: PackageInfo) =
|
||||
if not pkgInfo.isNimScript:
|
||||
# TODO: Turn this into a warning.
|
||||
# TODO: Add a URL explaining more.
|
||||
echo("NOTE: The .nimble file for this project could make use of " &
|
||||
"additional features, if converted into the new NimScript format.")
|
||||
display("Warning:", "The .nimble file for this project could make use of " &
|
||||
"additional features, if converted into the new NimScript format." &
|
||||
"\nFor more details see:" &
|
||||
"https://github.com/nim-lang/nimble#creating-packages",
|
||||
Warning, HighPriority)
|
||||
|
||||
proc multiSplit(s: string): seq[string] =
|
||||
## Returns ``s`` split by newline and comma characters.
|
||||
|
|
@ -242,6 +239,18 @@ proc getInstalledPkgs*(libsDir: string, options: Options):
|
|||
## Gets a list of installed packages.
|
||||
##
|
||||
## ``libsDir`` is in most cases: ~/.nimble/pkgs/
|
||||
const
|
||||
readErrorMsg = "Installed package $1 v$2 is outdated or corrupt."
|
||||
validationErrorMsg = readErrorMsg & "\nPackage did not pass validation: $3"
|
||||
hintMsg = "The corrupted package will need to be removed manually. To fix" &
|
||||
" this error message, remove $1."
|
||||
|
||||
proc createErrorMsg(tmplt, path, msg: string): string =
|
||||
let (name, version) = getNameVersion(path)
|
||||
return tmplt % [name, version, msg]
|
||||
|
||||
display("Loading", "list of installed packages", priority = MediumPriority)
|
||||
|
||||
result = @[]
|
||||
for kind, path in walkDir(libsDir):
|
||||
if kind == pcDir:
|
||||
|
|
@ -254,17 +263,17 @@ proc getInstalledPkgs*(libsDir: string, options: Options):
|
|||
result.add((pkg, meta))
|
||||
except ValidationError:
|
||||
let exc = (ref ValidationError)(getCurrentException())
|
||||
exc.msg = createErrorMsg(validationErrorMsg, path, exc.msg)
|
||||
exc.hint = hintMsg % path
|
||||
if exc.warnInstalled:
|
||||
echo("WARNING: Unable to read package info for " & path & "\n" &
|
||||
" Package did not pass validation: " & exc.msg)
|
||||
display("Warning:", exc.msg, Warning, HighPriority)
|
||||
else:
|
||||
exc.msg = "Unable to read package info for " & path & "\n" &
|
||||
" Package did not pass validation: " & exc.msg
|
||||
raise exc
|
||||
except:
|
||||
let exc = getCurrentException()
|
||||
exc.msg = "Unable to read package info for " & path & "\n" &
|
||||
" Error: " & exc.msg
|
||||
let tmplt = readErrorMsg & "\nMore info: $3"
|
||||
let msg = createErrorMsg(tmplt, path, getCurrentException().msg)
|
||||
var exc = newException(NimbleError, msg)
|
||||
exc.hint = hintMsg % path
|
||||
raise exc
|
||||
|
||||
proc isNimScript*(nf: string, options: Options): bool =
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@
|
|||
## Implements 'nimble publish' to create a pull request against
|
||||
## nim-lang/packages automatically.
|
||||
|
||||
import system except TResult
|
||||
import httpclient, base64, strutils, rdstdin, json, os, browsers, times, uri
|
||||
import tools, common
|
||||
import tools, common, cli
|
||||
|
||||
type
|
||||
Auth = object
|
||||
|
|
@ -22,19 +23,20 @@ proc createHeaders(a: Auth): string =
|
|||
"Accept: */*\c\L")
|
||||
|
||||
proc getGithubAuth(): Auth =
|
||||
echo("Please create a new personal access token on Github in order to " &
|
||||
"allow Nimble to fork the packages repository.")
|
||||
display("Info:", "Please create a new personal access token on Github in" &
|
||||
" order to allow Nimble to fork the packages repository.",
|
||||
priority = HighPriority)
|
||||
sleep(5000)
|
||||
echo("Your default browser should open with the following URL: " &
|
||||
"https://github.com/settings/tokens/new")
|
||||
display("Info:", "Your default browser should open with the following URL: " &
|
||||
"https://github.com/settings/tokens/new", priority = HighPriority)
|
||||
sleep(3000)
|
||||
openDefaultBrowser("https://github.com/settings/tokens/new")
|
||||
result.token = readLineFromStdin("Personal access token: ").strip()
|
||||
result.token = promptCustom("Personal access token?", "").strip()
|
||||
let resp = getContent("https://api.github.com/user",
|
||||
extraHeaders=createHeaders(result)).parseJson()
|
||||
|
||||
result.user = resp["login"].str
|
||||
echo("Successfully verified as ", result.user)
|
||||
display("Success:", "Verified as " & result.user, Success, HighPriority)
|
||||
|
||||
proc isCorrectFork(j: JsonNode): bool =
|
||||
# Check whether this is a fork of the nimble packages repo.
|
||||
|
|
@ -56,7 +58,7 @@ proc createFork(a: Auth) =
|
|||
extraHeaders=createHeaders(a))
|
||||
|
||||
proc createPullRequest(a: Auth, packageName, branch: string) =
|
||||
echo("Creating PR")
|
||||
display("Info", "Creating PR", priority = HighPriority)
|
||||
discard postContent("https://api.github.com/repos/nim-lang/packages/pulls",
|
||||
extraHeaders=createHeaders(a),
|
||||
body="""{"title": "Add package $1", "head": "$2:$3",
|
||||
|
|
@ -129,17 +131,19 @@ proc publish*(p: PackageInfo) =
|
|||
var pkgsDir = getTempDir() / "nimble-packages-fork"
|
||||
if not forkExists(auth):
|
||||
createFork(auth)
|
||||
echo "waiting 10s to let Github create a fork ..."
|
||||
display("Info:", "Waiting 10s to let Github create a fork",
|
||||
priority = HighPriority)
|
||||
os.sleep(10_000)
|
||||
|
||||
echo "... done"
|
||||
display("Info:", "Finished waiting", priority = LowPriority)
|
||||
if dirExists(pkgsDir):
|
||||
echo("Removing old packages fork git directory.")
|
||||
display("Removing", "old packages fork git directory.",
|
||||
priority = LowPriority)
|
||||
removeDir(pkgsDir)
|
||||
echo "Cloning packages into: ", pkgsDir
|
||||
display("Cloning", "packages into: " & pkgsDir, priority = HighPriority)
|
||||
doCmd("git clone git@github.com:" & auth.user & "/packages " & pkgsDir)
|
||||
# Make sure to update the clone.
|
||||
echo("Updating the fork...")
|
||||
display("Updating", "the fork", priority = HighPriority)
|
||||
cd pkgsDir:
|
||||
doCmd("git pull https://github.com/nim-lang/packages.git master")
|
||||
doCmd("git push origin master")
|
||||
|
|
@ -175,20 +179,20 @@ proc publish*(p: PackageInfo) =
|
|||
"No .git nor .hg directory found. Stopping.")
|
||||
|
||||
if url.len == 0:
|
||||
url = readLineFromStdin("Github URL of " & p.name & ": ")
|
||||
url = promptCustom("Github URL of " & p.name & "?", "")
|
||||
if url.len == 0: userAborted()
|
||||
|
||||
let tags = readLineFromStdin("Please enter a whitespace separated list of tags: ")
|
||||
let tags = promptCustom("Whitespace separated list of tags?", "")
|
||||
|
||||
cd pkgsDir:
|
||||
editJson(p, url, tags, downloadMethod)
|
||||
let branchName = "add-" & p.name & getTime().getGMTime().format("HHmm")
|
||||
doCmd("git checkout -B " & branchName)
|
||||
doCmd("git commit packages.json -m \"Added package " & p.name & "\"")
|
||||
echo("Pushing to remote of fork.")
|
||||
display("Pushing", "to remote of fork.", priority = HighPriority)
|
||||
doCmd("git push " & getPackageOriginUrl(auth) & " " & branchName)
|
||||
createPullRequest(auth, p.name, branchName)
|
||||
echo "Pull request successful."
|
||||
display("Success:", "Pull request successful.", Success, HighPriority)
|
||||
|
||||
when isMainModule:
|
||||
import packageinfo
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
#
|
||||
# Various miscellaneous utility functions reside here.
|
||||
import osproc, pegs, strutils, os, uri, sets, json, parseutils
|
||||
import version, common
|
||||
import version, common, cli
|
||||
|
||||
proc extractBin(cmd: string): string =
|
||||
if cmd[0] == '"':
|
||||
|
|
@ -20,11 +20,16 @@ proc doCmd*(cmd: string) =
|
|||
stdout.flushFile()
|
||||
stderr.flushFile()
|
||||
|
||||
let exitCode = execCmd(cmd)
|
||||
displayDebug("Executing", cmd)
|
||||
let (output, exitCode) = execCmdEx(cmd)
|
||||
displayDebug("Finished", "with exit code " & $exitCode)
|
||||
# TODO: Improve to show output in real-time.
|
||||
displayDebug("Output", output)
|
||||
|
||||
if exitCode != QuitSuccess:
|
||||
raise newException(NimbleError,
|
||||
"Execution failed with exit code " & $exitCode)
|
||||
"Execution failed with exit code $1\nCommand: $2\nOutput: $3" %
|
||||
[$exitCode, cmd, output])
|
||||
|
||||
proc doCmdEx*(cmd: string): tuple[output: TaintedString, exitCode: int] =
|
||||
let bin = extractBin(cmd)
|
||||
|
|
@ -50,7 +55,7 @@ proc getNimrodVersion*: Version =
|
|||
let vOutput = doCmdEx('"' & nimBin & "\" -v").output
|
||||
var matches: array[0..MaxSubpatterns, string]
|
||||
if vOutput.find(peg"'Version'\s{(\d+\.)+\d}", matches) == -1:
|
||||
quit("Couldn't find Nim version.", QuitFailure)
|
||||
raise newException(NimbleError, "Couldn't find Nim version.")
|
||||
newVersion(matches[0])
|
||||
|
||||
proc samePaths*(p1, p2: string): bool =
|
||||
|
|
@ -75,14 +80,14 @@ proc changeRoot*(origRoot, newRoot, path: string): string =
|
|||
|
||||
proc copyFileD*(fro, to: string): string =
|
||||
## Returns the destination (``to``).
|
||||
echo(fro, " -> ", to)
|
||||
display("Copying", "file $# to $#" % [fro, to], priority = LowPriority)
|
||||
copyFileWithPermissions(fro, to)
|
||||
result = to
|
||||
|
||||
proc copyDirD*(fro, to: string): seq[string] =
|
||||
## Returns the filenames of the files in the directory that were copied.
|
||||
result = @[]
|
||||
echo("Copying directory: ", fro, " -> ", to)
|
||||
display("Copying", "directory $# to $#" % [fro, to], priority = LowPriority)
|
||||
for path in walkDirRec(fro):
|
||||
createDir(changeRoot(fro, to, path.splitFile.dir))
|
||||
result.add copyFileD(path, changeRoot(fro, to, path))
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ type
|
|||
|
||||
ParseVersionError* = object of ValueError
|
||||
NimbleError* = object of Exception
|
||||
hint*: string
|
||||
|
||||
proc newVersion*(ver: string): Version = return Version(ver)
|
||||
proc newSpecial*(spe: string): Special = return Special(spe)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue