Moved Nimble-specific Nimscript definitions to nimscriptapi module.

* Moved package info reading procedures to a packageparser module. This is to prevent recu
rsive dependencies between nimscriptsupport and packageinfo modules.
* Passed the Options object to all procedures which read package info (necessary for nimscriptsupport module, to find the path of the nimscriptapi module)
* Introduced an ``isInstalled`` field to the ``PackageInfo`` type. This is a bug fix for g
etRealDir: it no longer uses the ``src`` field in the path it returns for installed packag
es' PackageInfo objects.
* Improved error reporting from NimScript evaluator.
* Renamed compiler/options import to ``compiler_options`` in ``nimscriptsupport`` module.
* Backward compatibility for .babel packages in getNameVersion proc.
* Introduced a getInstalledPkgsMin procedure which does not read package info, but only pr
ovides minimal info instead.
This commit is contained in:
Dominik Picheta 2015-12-28 16:33:34 +00:00
commit ca99ad7d21
9 changed files with 426 additions and 300 deletions

View file

@ -1,10 +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
import version, tools, nimbletypes, nimscriptsupport
when not declared(system.map):
from sequtils import map
import version, tools, nimbletypes, options
type
Package* = object
@ -23,12 +20,7 @@ type
MetaData* = object
url*: string
NimbleFile* = string
ValidationError* = object of NimbleError
warnInstalled*: bool # Determines whether to show a warning for installed pkgs
proc initPackageInfo(path: string): PackageInfo =
proc initPackageInfo*(path: string): PackageInfo =
result.mypath = path
# reasonable default:
result.name = path.splitFile.name
@ -48,37 +40,6 @@ proc initPackageInfo(path: string): PackageInfo =
result.binDir = ""
result.backend = "c"
proc newValidationError(msg: string, warnInstalled: bool): ref ValidationError =
result = newException(ValidationError, msg)
result.warnInstalled = warnInstalled
proc validatePackageName*(name: string) =
## Raises an error if specified package name contains invalid characters.
##
## A valid package name is one which is a valid nim module name. So only
## underscores, letters and numbers allowed.
if name.len == 0: return
if name[0] in {'0'..'9'}:
raise newValidationError(name &
"\"$1\" is an invalid package name: cannot begin with $2" %
[name, $name[0]], true)
var prevWasUnderscore = false
for c in name:
case c
of '_':
if prevWasUnderscore:
raise newValidationError(
"$1 is an invalid package name: cannot contain \"__\"" % name, true)
prevWasUnderscore = true
of AllChars - IdentChars:
raise newValidationError(
"$1 is an invalid package name: cannot contain '$2'" % [name, $c],
true)
else:
prevWasUnderscore = false
proc toValidPackageName*(name: string): string =
result = ""
for c in name:
@ -88,128 +49,6 @@ proc toValidPackageName*(name: string): string =
of AllChars - IdentChars - {'-'}: discard
else: result.add(c)
proc validateVersion*(ver: string) =
for c in ver:
if c notin ({'.'} + Digits):
raise newValidationError(
"Version may only consist of numbers and the '.' character " &
"but found '" & c & "'.", false)
proc validatePackageInfo(pkgInfo: PackageInfo, path: string) =
if pkgInfo.name == "":
raise newValidationError("Incorrect .nimble file: " & path &
" does not contain a name field.", false)
if pkgInfo.name.normalize != path.splitFile.name.normalize:
raise newValidationError(
"The .nimble file name must match name specified inside " & path, true)
if pkgInfo.version == "":
raise newValidationError("Incorrect .nimble file: " & path &
" does not contain a version field.", false)
if not pkgInfo.isMinimal:
if pkgInfo.author == "":
raise newValidationError("Incorrect .nimble file: " & path &
" does not contain an author field.", false)
if pkgInfo.description == "":
raise newValidationError("Incorrect .nimble file: " & path &
" does not contain a description field.", false)
if pkgInfo.license == "":
raise newValidationError("Incorrect .nimble file: " & path &
" does not contain a license field.", false)
if pkgInfo.backend notin ["c", "cc", "objc", "cpp", "js"]:
raise newValidationError("'" & pkgInfo.backend &
"' is an invalid backend.", false)
validateVersion(pkgInfo.version)
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.")
proc multiSplit(s: string): seq[string] =
## Returns ``s`` split by newline and comma characters.
##
## Before returning, all individual entries are stripped of whitespace and
## also empty entries are purged from the list. If after all the cleanups are
## done no entries are found in the list, the proc returns a sequence with
## the original string as the only entry.
result = split(s, {char(0x0A), char(0x0D), ','})
map(result, proc(x: var string) = x = x.strip())
for i in countdown(result.len()-1, 0):
if len(result[i]) < 1:
result.del(i)
# Huh, nothing to return? Return given input.
if len(result) < 1:
return @[s]
proc readPackageInfoFromNimble(path: string; result: var PackageInfo) =
var fs = newFileStream(path, fmRead)
if fs != nil:
var p: CfgParser
open(p, fs, path)
var currentSection = ""
while true:
var ev = next(p)
case ev.kind
of cfgEof:
break
of cfgSectionStart:
currentSection = ev.section
of cfgKeyValuePair:
case currentSection.normalize
of "package":
case ev.key.normalize
of "name": result.name = ev.value
of "version": result.version = ev.value
of "author": result.author = ev.value
of "description": result.description = ev.value
of "license": result.license = ev.value
of "srcdir": result.srcDir = ev.value
of "bindir": result.binDir = ev.value
of "skipdirs":
result.skipDirs.add(ev.value.multiSplit)
of "skipfiles":
result.skipFiles.add(ev.value.multiSplit)
of "skipext":
result.skipExt.add(ev.value.multiSplit)
of "installdirs":
result.installDirs.add(ev.value.multiSplit)
of "installfiles":
result.installFiles.add(ev.value.multiSplit)
of "installext":
result.installExt.add(ev.value.multiSplit)
of "bin":
for i in ev.value.multiSplit:
result.bin.add(i.addFileExt(ExeExt))
of "backend":
result.backend = ev.value.toLower()
case result.backend.normalize
of "javascript": result.backend = "js"
else: discard
else:
raise newException(NimbleError, "Invalid field: " & ev.key)
of "deps", "dependencies":
case ev.key.normalize
of "requires":
for v in ev.value.multiSplit:
result.requires.add(parseRequires(v.strip))
else:
raise newException(NimbleError, "Invalid field: " & ev.key)
else: raise newException(NimbleError,
"Invalid section: " & currentSection)
of cfgOption: raise newException(NimbleError,
"Invalid package info, should not contain --" & ev.value)
of cfgError:
raise newException(NimbleError, "Error parsing .nimble file: " & ev.msg)
close(p)
else:
raise newException(ValueError, "Cannot open package info: " & path)
proc getNameVersion*(pkgpath: string): tuple[name, version: string] =
## Splits ``pkgpath`` in the format ``/home/user/.nimble/pkgs/package-0.1``
## into ``(packagea, 0.1)``
@ -217,7 +56,7 @@ proc getNameVersion*(pkgpath: string): tuple[name, version: string] =
## Also works for file paths like:
## ``/home/user/.nimble/pkgs/package-0.1/package.nimble``
if pkgPath.splitFile.ext == ".nimble":
if pkgPath.splitFile.ext == ".nimble" or pkgPath.splitFile.ext == ".babel":
return getNameVersion(pkgPath.splitPath.head)
result.name = ""
@ -233,51 +72,6 @@ proc getNameVersion*(pkgpath: string): tuple[name, version: string] =
result.version = tail[i+1 .. tail.len-1]
break
proc readPackageInfo*(nf: NimbleFile; onlyMinimalInfo=false): PackageInfo =
## Reads package info from the specified Nimble file.
##
## Attempts to read it using the "old" Nimble ini format first, if that
## fails attempts to evaluate it as a nimscript file.
##
## If both fail then returns an error.
##
## When ``onlyMinimalInfo`` is true, only the `name` and `version` fields are
## populated. The isNimScript field can also be relied on.
result = initPackageInfo(nf)
validatePackageName(nf.splitFile.name)
var success = false
var iniError: ref NimbleError
# Attempt ini-format first.
try:
readPackageInfoFromNimble(nf, result)
success = true
result.isNimScript = false
except NimbleError:
iniError = (ref NimbleError)(getCurrentException())
if not success:
if onlyMinimalInfo:
let tmp = getNameVersion(nf)
result.name = tmp.name
result.version = tmp.version
result.isNimScript = true
result.isMinimal = true
else:
try:
readPackageInfoFromNims(nf, result)
result.isNimScript = true
except NimbleError:
let msg = "Could not read package info file in " & nf & ";\n" &
" Reading as ini file failed with: \n" &
" " & iniError.msg & ".\n" &
" Evaluating as NimScript file failed with: \n" &
" " & getCurrentExceptionMsg() & "."
raise newException(NimbleError, msg)
validatePackageInfo(result, nf)
proc optionalField(obj: JsonNode, name: string, default = ""): string =
## Queries ``obj`` for the optional ``name`` string.
##
@ -353,7 +147,7 @@ proc getPackageList*(packagesPath: string): seq[Package] =
let pkg: Package = p.fromJson()
result.add(pkg)
proc findNimbleFile*(dir: string; error: bool): NimbleFile =
proc findNimbleFile*(dir: string; error: bool): string =
result = ""
var hits = 0
for kind, path in walkDir(dir):
@ -375,14 +169,11 @@ proc findNimbleFile*(dir: string; error: bool): NimbleFile =
# TODO: Abstract logging.
echo("WARNING: No .nimble file found for ", dir)
proc getPkgInfo*(dir: string): PackageInfo =
## Find the .nimble file in ``dir`` and parses it, returning a PackageInfo.
let nimbleFile = findNimbleFile(dir, true)
result = readPackageInfo(nimbleFile)
proc getInstalledPkgs*(libsDir: string):
proc getInstalledPkgsMin*(libsDir: string, options: Options):
seq[tuple[pkginfo: PackageInfo, meta: MetaData]] =
## Gets a list of installed packages.
## Gets a list of installed packages. The resulting package info is
## minimal. This has the advantage that it does not depend on the
## ``packageparser`` module, and so can be used by ``nimscriptsupport``.
##
## ``libsDir`` is in most cases: ~/.nimble/pkgs/
result = @[]
@ -391,23 +182,13 @@ proc getInstalledPkgs*(libsDir: string):
let nimbleFile = findNimbleFile(path, false)
if nimbleFile != "":
let meta = readMetaData(path)
try:
result.add((readPackageInfo(nimbleFile, true), meta))
except ValidationError:
let exc = (ref ValidationError)(getCurrentException())
if exc.warnInstalled:
echo("WARNING: Unable to read package info for " & path & "\n" &
" Package did not pass validation: " & exc.msg)
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
raise exc
let (name, version) = getNameVersion(nimbleFile)
var pkg = initPackageInfo(nimbleFile)
pkg.name = name
pkg.version = version
pkg.isMinimal = true
pkg.isInstalled = true
result.add((pkg, meta))
proc findPkg*(pkglist: seq[tuple[pkginfo: PackageInfo, meta: MetaData]],
dep: PkgTuple,
@ -441,7 +222,7 @@ proc findAllPkgs*(pkglist: seq[tuple[pkginfo: PackageInfo, meta: MetaData]],
proc getRealDir*(pkgInfo: PackageInfo): string =
## Returns the ``pkgInfo.srcDir`` or the .mypath directory if package does
## not specify the src dir.
if pkgInfo.srcDir != "":
if pkgInfo.srcDir != "" and not pkgInfo.isInstalled:
result = pkgInfo.mypath.splitFile.dir / pkgInfo.srcDir
else:
result = pkgInfo.mypath.splitFile.dir
@ -469,9 +250,6 @@ proc getDownloadDirName*(pkg: Package, verRange: VersionRange): string =
result.add "_"
result.add verSimple
proc isNimScript*(nf: NimbleFile): bool =
result = readPackageInfo(nf).isNimScript
when isMainModule:
doAssert getNameVersion("/home/user/.nimble/libs/packagea-0.1") ==
("packagea", "0.1")