Merge branch 'master' into native-pkg-support

This commit is contained in:
Araq 2016-12-23 16:01:37 +01:00
commit e813aa6448
14 changed files with 646 additions and 305 deletions

View file

@ -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
View 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)

View file

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

View file

@ -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 = ""

View file

@ -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()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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