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:
parent
84f371982b
commit
ca99ad7d21
9 changed files with 426 additions and 300 deletions
254
src/nimblepkg/packageparser.nim
Normal file
254
src/nimblepkg/packageparser.nim
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
# 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, options, packageinfo
|
||||
|
||||
## Contains procedures for parsing .nimble files. Moved here from ``packageinfo``
|
||||
## because it depends on ``nimscriptsupport`` (``nimscriptsupport`` also
|
||||
## depends on other procedures in ``packageinfo``.
|
||||
|
||||
when not declared(system.map):
|
||||
from sequtils import map
|
||||
|
||||
type
|
||||
NimbleFile* = string
|
||||
|
||||
ValidationError* = object of NimbleError
|
||||
warnInstalled*: bool # Determines whether to show a warning for installed pkgs
|
||||
|
||||
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 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 readPackageInfo*(nf: NimbleFile, options: Options,
|
||||
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, options, 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 getPkgInfo*(dir: string, options: Options): PackageInfo =
|
||||
## Find the .nimble file in ``dir`` and parses it, returning a PackageInfo.
|
||||
let nimbleFile = findNimbleFile(dir, true)
|
||||
result = readPackageInfo(nimbleFile, options)
|
||||
|
||||
proc getInstalledPkgs*(libsDir: string, options: Options):
|
||||
seq[tuple[pkginfo: PackageInfo, meta: MetaData]] =
|
||||
## Gets a list of installed packages.
|
||||
##
|
||||
## ``libsDir`` is in most cases: ~/.nimble/pkgs/
|
||||
result = @[]
|
||||
for kind, path in walkDir(libsDir):
|
||||
if kind == pcDir:
|
||||
let nimbleFile = findNimbleFile(path, false)
|
||||
if nimbleFile != "":
|
||||
let meta = readMetaData(path)
|
||||
try:
|
||||
var pkg = readPackageInfo(nimbleFile, options, true)
|
||||
pkg.isInstalled = true
|
||||
result.add((pkg, 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
|
||||
|
||||
proc isNimScript*(nf: string, options: Options): bool =
|
||||
result = readPackageInfo(nf, options).isNimScript
|
||||
Loading…
Add table
Add a link
Reference in a new issue