Merge branch 'nimscript-support'

This commit is contained in:
Dominik Picheta 2015-12-24 01:35:32 +00:00
commit d94501234a
10 changed files with 747 additions and 180 deletions

View file

@ -10,6 +10,8 @@ import nimblepkg/packageinfo, nimblepkg/version, nimblepkg/tools,
nimblepkg/download, nimblepkg/config, nimblepkg/nimbletypes,
nimblepkg/publish
import nimblepkg/nimscriptsupport
when not defined(windows):
from posix import getpid
else:
@ -38,13 +40,14 @@ type
nimbleData: JsonNode ## Nimbledata.json
ActionType = enum
actionNil, actionUpdate, actionInit, actionPublish,
actionNil, actionUpdate, actionInit, actionDump, actionPublish,
actionInstall, actionSearch,
actionList, actionBuild, actionPath, actionUninstall, actionCompile
actionList, actionBuild, actionPath, actionUninstall, actionCompile,
actionCustom, actionTasks
Action = object
case typ: ActionType
of actionNil, actionList, actionBuild, actionPublish: nil
of actionNil, actionList, actionBuild, actionPublish, actionTasks: nil
of actionUpdate:
optionalURL: string # Overrides default package list.
of actionInstall, actionPath, actionUninstall:
@ -53,7 +56,7 @@ type
packages: seq[PkgTuple] # Optional only for actionInstall.
of actionSearch:
search: seq[string] # Search string.
of actionInit:
of actionInit, actionDump:
projName: string
of actionCompile:
file: string
@ -84,8 +87,12 @@ Commands:
performed by tag and by name.
list [--ver] Lists all packages.
[-i, --installed] Lists all installed packages.
tasks Lists the tasks specified in the Nimble
package's Nimble file.
path pkgname ... Shows absolute path to the installed packages
specified.
dump [pkgname] Outputs Nimble package information for
external tools.
Options:
-h, --help Print this help message.
@ -99,7 +106,7 @@ Options:
For more information read the Github readme:
https://github.com/nim-lang/nimble#readme
"""
nimbleVersion = "0.6.2"
nimbleVersion = "0.7.0"
defaultPackageURL =
"https://github.com/nim-lang/packages/raw/master/packages.json"
@ -144,6 +151,18 @@ proc prompt(options: Options, question: string): bool =
else:
return false
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 renameBabelToNimble(options: Options) {.deprecated.} =
let babelDir = getHomeDir() / ".babel"
let nimbleDir = getHomeDir() / ".nimble"
@ -156,6 +175,64 @@ proc renameBabelToNimble(options: Options) {.deprecated.} =
removeDir(babelDir)
removeFile(nimbleDir / "babeldata.json")
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 "build":
result = actionBuild
of "c", "compile", "js", "cpp", "cc":
result = actionCompile
of "init":
result = actionInit
of "dump":
result = actionDump
of "update", "refresh":
result = actionUpdate
of "search":
result = actionSearch
of "list":
result = actionList
of "uninstall", "remove", "delete", "del", "rm":
result = actionUninstall
of "publish":
result = actionPublish
of "tasks":
result = actionTasks
else:
result = actionCustom
proc initAction(options: var Options, key: string) =
## Intialises `options.actions` fields based on `options.actions.typ` and
## `key`.
let keyNorm = key.normalize()
case options.action.typ
of actionInstall, actionPath:
options.action.packages = @[]
of actionCompile:
options.action.compileOptions = @[]
options.action.file = ""
if keyNorm == "c" or keyNorm == "compile": options.action.backend = ""
else: options.action.backend = keyNorm
of actionInit:
options.action.projName = ""
of actionDump:
options.action.projName = ""
of actionUpdate:
options.action.optionalURL = ""
of actionSearch:
options.action.search = @[]
of actionUninstall:
options.action.packages = @[]
of actionBuild, actionPublish, actionCustom, actionList, actionTasks,
actionNil: discard
proc parseCmdLine(): Options =
result.action.typ = actionNil
result.config = parseConfig()
@ -163,41 +240,9 @@ proc parseCmdLine(): Options =
case kind
of cmdArgument:
if result.action.typ == actionNil:
case key
of "install", "path":
case key
of "install":
result.action.typ = actionInstall
of "path":
result.action.typ = actionPath
else:
discard
result.action.packages = @[]
of "build":
result.action.typ = actionBuild
of "c", "compile", "js", "cpp", "cc":
result.action.typ = actionCompile
result.action.compileOptions = @[]
result.action.file = ""
if key == "c" or key == "compile": result.action.backend = ""
else: result.action.backend = key
of "init":
result.action.typ = actionInit
result.action.projName = ""
of "update", "refresh":
result.action.typ = actionUpdate
result.action.optionalURL = ""
of "search":
result.action.typ = actionSearch
result.action.search = @[]
of "list":
result.action.typ = actionList
of "uninstall", "remove", "delete", "del", "rm":
result.action.typ = actionUninstall
result.action.packages = @[]
of "publish":
result.action.typ = actionPublish
else: writeHelp()
setNimScriptCommand(key)
result.action.typ = parseActionType(key)
initAction(result, key)
else:
case result.action.typ
of actionNil:
@ -215,7 +260,7 @@ proc parseCmdLine(): Options =
result.action.optionalURL = key
of actionSearch:
result.action.search.add(key)
of actionInit:
of actionInit, actionDump:
if result.action.projName != "":
raise newException(NimbleError,
"Can only initialize one package at a time.")
@ -721,11 +766,13 @@ proc install(packages: seq[PkgTuple],
proc build(options: Options) =
var pkgInfo = getPkgInfo(getCurrentDir())
nimScriptHint(pkgInfo)
let paths = processDeps(pkginfo, options)
buildFromDir(pkgInfo, paths, false)
proc compile(options: Options) =
var pkgInfo = getPkgInfo(getCurrentDir())
nimScriptHint(pkgInfo)
let paths = processDeps(pkginfo, options)
let realDir = pkgInfo.getRealDir()
@ -827,9 +874,11 @@ proc listPaths(options: Options) =
continue
let
nimScriptFile = path / name.addFileExt("nims")
babelFile = path / name.addFileExt("babel")
nimbleFile = path / name.addFileExt("nimble")
hasSpec = nimbleFile.existsFile or babelFile.existsFile
hasSpec = nimScriptFile.existsFile or
nimbleFile.existsFile or babelFile.existsFile
if hasSpec:
var pkgInfo = getPkgInfo(path)
var v: VersionAndPath
@ -849,44 +898,105 @@ proc listPaths(options: Options) =
raise newException(NimbleError,
"At least one of the specified packages was not found")
proc join(x: seq[PkgTuple]; y: string): string =
if x.len == 0: return ""
result = x[0][0] & " " & $x[0][1]
for i in 1 ..< x.len:
result.add y
result.add x[i][0] & " " & $x[i][1]
proc dump(options: Options) =
let proj = addFileExt(options.action.projName, "nimble")
let p = if fileExists(proj): readPackageInfo(proj)
else: getPkgInfo(os.getCurrentDir())
echo "name: ", p.name.escape
echo "version: ", p.version.escape
echo "author: ", p.author.escape
echo "desc: ", p.description.escape
echo "license: ", p.license.escape
echo "skipDirs: ", p.skipDirs.join(", ").escape
echo "skipFiles: ", p.skipFiles.join(", ").escape
echo "skipExt: ", p.skipExt.join(", ").escape
echo "installDirs: ", p.installDirs.join(", ").escape
echo "installFiles: ", p.installFiles.join(", ").escape
echo "installExt: ", p.installExt.join(", ").escape
echo "requires: ", p.requires.join(", ").escape
echo "bin: ", p.bin.join(", ").escape
echo "binDir: ", p.binDir.escape
echo "srcDir: ", p.srcDir.escape
echo "backend: ", p.backend.escape
proc init(options: Options) =
echo("Initializing new Nimble project!")
var
pkgName, fName: string = ""
outFile: File
var nimbleFile: string = ""
if (options.action.projName != ""):
pkgName = options.action.projName
fName = pkgName & ".nimble"
if (existsFile(os.getCurrentDir() / fName)):
raise newException(NimbleError, "Already have a nimble file.")
echo("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.")
# Ask for package name.
if options.action.projName != "":
let pkgName = options.action.projName
nimbleFile = pkgName.changeFileExt("nimble")
else:
echo("Enter a project name for this (blank to use working directory), " &
"Ctrl-C to abort:")
pkgName = readline(stdin)
if (pkgName == ""):
pkgName = os.getCurrentDir().splitPath.tail
if (pkgName == ""):
raise newException(NimbleError, "Could not get default file path.")
fName = pkgName & ".nimble"
var pkgName = os.getCurrentDir().splitPath.tail.toValidPackageName()
pkgName = promptCustom("Enter package name", pkgName)
nimbleFile = pkgName.changeFileExt("nimble")
# Now need to write out .nimble file with projName and other details
validatePackageName(nimbleFile.changeFileExt(""))
if (not existsFile(os.getCurrentDir() / fName) and
open(f=outFile, filename = fName, mode = fmWrite)):
outFile.writeln("[Package]")
outFile.writeln("name = \"" & pkgName & "\"")
outFile.writeln("version = \"0.1.0\"")
outFile.writeln("author = \"Anonymous\"")
outFile.writeln("description = \"New Nimble project for Nim\"")
outFile.writeln("license = \"MIT\"")
outFile.writeln("")
outFile.writeln("[Deps]")
outFile.writeln("Requires: \"nim >= 0.10.0\"")
if existsFile(os.getCurrentDir() / nimbleFile):
raise newException(NimbleError, "Nimble file already exists.")
# Ask for package version.
let pkgVersion = promptCustom("Enter intial version of package", "0.1.0")
validateVersion(pkgVersion)
# Ask for package author
var defaultAuthor = "Anonymous"
if findExe("git") != "":
let (name, exitCode) = doCmdEx("git config --global user.name")
if exitCode == QuitSuccess and name.len > 0:
defaultAuthor = name.strip()
elif defaultAuthor == "Anonymous" and findExe("hg") != "":
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)
# Ask for description
let pkgDesc = promptCustom("Enter package description", "")
# Ask for license
# TODO: Provide selection of licenses, or select random default license.
let pkgLicense = promptCustom("Enter package license", "MIT")
# Ask for Nim dependency
let nimDepDef = getNimrodVersion()
let pkgNimDep = promptCustom("Enter 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
version = $#
author = $#
description = $#
license = $#
# Dependencies
requires "nim >= $#"
""" % [pkgVersion.escape(), pkgAuthor.escape(), pkgDesc.escape(),
pkgLicense.escape(), pkgNimDep]
close(outFile)
else:
raise newException(NimbleError, "Unable to open file " & fName &
raise newException(NimbleError, "Unable to open file " & nimbleFile &
" for writing: " & osErrorMsg(osLastError()))
proc uninstall(options: Options) =
@ -946,37 +1056,67 @@ proc uninstall(options: Options) =
removePkgDir(options.getPkgsDir / (pkg.name & '-' & pkg.version), options)
echo("Removed ", pkg.name, " (", $pkg.version, ")")
proc listTasks(options: Options) =
let nimbleFile = findNimbleFile(getCurrentDir(), true)
nimscriptsupport.listTasks(nimbleFile)
proc doAction(options: Options) =
if not existsDir(options.getNimbleDir()):
createDir(options.getNimbleDir())
if not existsDir(options.getPkgsDir):
createDir(options.getPkgsDir)
case options.action.typ
of actionUpdate:
update(options)
of actionInstall:
discard install(options.action.packages, options)
of actionUninstall:
uninstall(options)
of actionSearch:
search(options)
of actionList:
if options.queryInstalled: listInstalled(options)
else: list(options)
of actionPath:
listPaths(options)
of actionBuild:
build(options)
of actionCompile:
compile(options)
of actionInit:
init(options)
of actionPublish:
var pkgInfo = getPkgInfo(getCurrentDir())
publish(pkgInfo)
of actionNil:
assert false
var command = getNimScriptCommand().parseActionType()
# The loop is necessary to support tasks using `setCommand`.
var moreCommands = true
while moreCommands:
moreCommands = false
case command
of actionUpdate:
update(options)
of actionInstall:
let (_, pkgInfo) = install(options.action.packages, options)
if options.action.packages.len == 0:
nimScriptHint(pkgInfo)
of actionUninstall:
uninstall(options)
of actionSearch:
search(options)
of actionList:
if options.queryInstalled: listInstalled(options)
else: list(options)
of actionPath:
listPaths(options)
of actionBuild:
build(options)
of actionCompile:
compile(options)
of actionInit:
init(options)
of actionPublish:
var pkgInfo = getPkgInfo(getCurrentDir())
publish(pkgInfo)
of actionDump:
dump(options)
of actionTasks:
listTasks(options)
of actionNil:
assert false
of actionCustom:
# Custom command. Attempt to call a NimScript task.
let nimbleFile = findNimbleFile(getCurrentDir(), true)
let oldCmd = getNimScriptCommand()
if not nimbleFile.isNimScript():
writeHelp()
if not execTask(nimbleFile, oldCmd):
echo("FAILURE: Could not find task ", oldCmd, " in ", nimbleFile)
writeHelp()
if getNimScriptCommand().normalize == "nop":
echo("WARNING: Using `setCommand 'nop'` is not necessary.")
break
command = getNimScriptCommand().parseActionType()
moreCommands = hasTaskRequestedCommand()
when isMainModule:
when defined(release):

View file

@ -1 +1,4 @@
-d:ssl
--path:"$lib/packages/docutils"
--noNimblePath
--path:"$nim/"

2
src/nimblepkg/nim.cfg Normal file
View file

@ -0,0 +1,2 @@
--path:"$nim/"
--path:"$lib/packages/docutils"

View file

@ -4,13 +4,15 @@
# recursive imports
import version
export version.NimbleError
type
NimbleError* = object of Exception
BuildFailed* = object of NimbleError
PackageInfo* = object
mypath*: string ## The path of this .nimble file
isNimScript*: bool ## Determines if this pkg info was read from a nims file
isMinimal*: bool
name*: string
version*: string
author*: string
@ -27,4 +29,3 @@ type
binDir*: string
srcDir*: string
backend*: string

View file

@ -0,0 +1,185 @@
# Copyright (C) Andreas Rumpf. All rights reserved.
# BSD License. Look at license.txt for more info.
## Implements the new configuration system for Nimble. Uses Nim as a
## scripting language.
import
compiler/ast, compiler/modules, compiler/passes, compiler/passaux,
compiler/condsyms, compiler/options, compiler/sem, compiler/semdata,
compiler/llstream, compiler/vm, compiler/vmdef, compiler/commands,
compiler/msgs, compiler/magicsys, compiler/lists
from compiler/scriptconfig import setupVM
from compiler/idents import getIdent
from compiler/astalgo import strTableGet, `$`
import nimbletypes, version
import os, strutils
const
internalCmd = "NimbleInternal"
proc raiseVariableError(ident, typ: string) {.noinline.} =
raise newException(NimbleError,
"NimScript's variable '" & ident & "' needs a value of type '" & typ & "'.")
proc isStrLit(n: PNode): bool = n.kind in {nkStrLit..nkTripleStrLit}
proc getGlobal(ident: string): string =
let n = vm.globalCtx.getGlobalValue(getSysSym ident)
if n.isStrLit:
result = if n.strVal.isNil: "" else: n.strVal
else:
raiseVariableError(ident, "string")
proc getGlobalAsSeq(ident: string): seq[string] =
let n = vm.globalCtx.getGlobalValue(getSysSym ident)
result = @[]
if n.kind == nkBracket:
for x in n:
if x.isStrLit:
result.add x.strVal
else:
raiseVariableError(ident, "seq[string]")
else:
raiseVariableError(ident, "seq[string]")
proc extractRequires(result: var seq[PkgTuple]) =
let n = vm.globalCtx.getGlobalValue(getSysSym "requiresData")
if n.kind == nkBracket:
for x in n:
if x.kind == nkPar and x.len == 2 and x[0].isStrLit and x[1].isStrLit:
result.add(parseRequires(x[0].strVal & x[1].strVal))
elif x.isStrLit:
result.add(parseRequires(x.strVal))
else:
raiseVariableError("requiresData", "seq[(string, VersionReq)]")
else:
raiseVariableError("requiresData", "seq[(string, VersionReq)]")
proc execScript(scriptName: string) =
## Executes the specified script.
##
## No clean up is performed and must be done manually!
setDefaultLibpath()
passes.gIncludeFile = includeModule
passes.gImportModule = importModule
initDefines()
defineSymbol("nimscript")
defineSymbol("nimconfig")
defineSymbol("nimble")
registerPass(semPass)
registerPass(evalPass)
appendStr(searchPaths, options.libpath)
var m = makeModule(scriptName)
incl(m.flags, sfMainModule)
vm.globalCtx = setupVM(m, scriptName)
compileSystemModule()
processModule(m, llStreamOpen(scriptName, fmRead), nil)
proc cleanup() =
# ensure everything can be called again:
resetAllModulesHard()
clearPasses()
msgs.gErrorMax = 1
msgs.writeLnHook = nil
vm.globalCtx = nil
initDefines()
proc readPackageInfoFromNims*(scriptName: string; result: var PackageInfo) =
## Executes the `scriptName` nimscript file. Reads the package information
## that it populates.
# Setup custom error handling.
msgs.gErrorMax = high(int)
var previousMsg = ""
msgs.writeLnHook =
proc (output: string) =
# The error counter is incremented after the writeLnHook is invoked.
if msgs.gErrorCounter > 0:
raise newException(NimbleError, previousMsg)
previousMsg = output
# Execute the nimscript file.
execScript(scriptName)
# Extract all the necessary fields populated by the nimscript file.
template trivialField(field) =
result.field = getGlobal(astToStr field)
template trivialFieldSeq(field) =
result.field.add getGlobalAsSeq(astToStr field)
# keep reasonable default:
let name = getGlobal"packageName"
if name.len > 0: result.name = name
trivialField version
trivialField author
trivialField description
trivialField license
trivialField srcdir
trivialField bindir
trivialFieldSeq skipDirs
trivialFieldSeq skipFiles
trivialFieldSeq skipExt
trivialFieldSeq installDirs
trivialFieldSeq installFiles
trivialFieldSeq installExt
extractRequires result.requires
let binSeq = getGlobalAsSeq("bin")
for i in binSeq:
result.bin.add(i.addFileExt(ExeExt))
let backend = getGlobal("backend")
if backend.len == 0:
result.backend = "c"
elif cmpIgnoreStyle(backend, "javascript") == 0:
result.backend = "js"
else:
result.backend = backend.toLower()
cleanup()
proc execTask*(scriptName, taskName: string): bool =
## Executes the specified task in the specified script.
##
## `scriptName` should be a filename pointing to the nimscript file.
result = true
options.command = internalCmd
echo("Executing task ", taskName, " in ", scriptName)
execScript(scriptName)
# Explicitly execute the task procedure, instead of relying on hack.
assert vm.globalCtx.module.kind == skModule
let prc = vm.globalCtx.module.tab.strTableGet(getIdent(taskName & "Task"))
if prc.isNil:
# Procedure not defined in the NimScript module.
return false
discard vm.globalCtx.execProc(prc, [])
cleanup()
proc getNimScriptCommand*(): string =
options.command
proc setNimScriptCommand*(command: string) =
options.command = command
proc hasTaskRequestedCommand*(): bool =
## Determines whether the last executed task used ``setCommand``
return getNimScriptCommand() != internalCmd
proc listTasks*(scriptName: string) =
setNimScriptCommand("help")
execScript(scriptName)
# TODO: Make the 'task' template generate explicit data structure containing
# all the task names + descriptions.
cleanup()

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
import version, tools, nimbletypes
import version, tools, nimbletypes, nimscriptsupport
when not declared(system.map):
from sequtils import map
@ -15,7 +15,7 @@ type
downloadMethod*: string
description*: string
tags*: seq[string] # Even if empty, always a valid non nil seq. \
# From here on, optional fields set to the emtpy string if not available.
# From here on, optional fields set to the empty string if not available.
version*: string
dvcsTag*: string
web*: string # Info url for humans.
@ -23,9 +23,12 @@ type
MetaData* = object
url*: string
proc initPackageInfo(): PackageInfo =
result.mypath = ""
result.name = ""
NimbleFile* = string
proc initPackageInfo(path: string): PackageInfo =
result.mypath = path
# reasonable default:
result.name = path.splitFile.name
result.version = ""
result.author = ""
result.description = ""
@ -42,6 +45,47 @@ proc initPackageInfo(): PackageInfo =
result.binDir = ""
result.backend = "c"
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 newException(NimbleError,
"Invalid package name: cannot beging with " & name[0])
var prevWasUnderscore = false
for c in name:
case c
of '_':
if prevWasUnderscore:
raise newException(NimbleError,
"Invalid package name: cannot contain \"__\"")
prevWasUnderscore = true
of AllChars - IdentChars:
raise newException(NimbleError,
"Invalid package name: cannot contain '$1'" % $c)
else:
prevWasUnderscore = false
proc toValidPackageName*(name: string): string =
result = ""
for c in name:
case c
of '_', '-':
if result[^1] != '_': result.add('_')
of AllChars - IdentChars - {'-'}: discard
else: result.add(c)
proc validateVersion*(ver: string) =
for c in ver:
if c notin ({'.'} + Digits):
raise newException(NimbleError,
"Version may only consist of numbers and the '.' character " &
"but found '" & c & "'.")
proc validatePackageInfo(pkgInfo: PackageInfo, path: string) =
if pkgInfo.name == "":
raise newException(NimbleError, "Incorrect .nimble file: " & path &
@ -49,40 +93,29 @@ proc validatePackageInfo(pkgInfo: PackageInfo, path: string) =
if pkgInfo.version == "":
raise newException(NimbleError, "Incorrect .nimble file: " & path &
" does not contain a version field.")
if pkgInfo.author == "":
raise newException(NimbleError, "Incorrect .nimble file: " & path &
" does not contain an author field.")
if pkgInfo.description == "":
raise newException(NimbleError, "Incorrect .nimble file: " & path &
" does not contain a description field.")
if pkgInfo.license == "":
raise newException(NimbleError, "Incorrect .nimble file: " & path &
" does not contain a license field.")
if pkgInfo.backend notin ["c", "cc", "objc", "cpp", "js"]:
raise newException(NimbleError, "'" & pkgInfo.backend &
"' is an invalid backend.")
for c in pkgInfo.version:
if c notin ({'.'} + Digits):
raise newException(NimbleError,
"Version may only consist of numbers and the '.' character " &
"but found '" & c & "'.")
proc parseRequires(req: string): PkgTuple =
try:
if ' ' in req:
var i = skipUntil(req, Whitespace)
result.name = req[0 .. i].strip
result.ver = parseVersionRange(req[i .. req.len-1])
elif '#' in req:
var i = skipUntil(req, {'#'})
result.name = req[0 .. i-1]
result.ver = parseVersionRange(req[i .. req.len-1])
else:
result.name = req.strip
result.ver = VersionRange(kind: verAny)
except ParseVersionError:
raise newException(NimbleError,
"Unable to parse dependency version range: " & getCurrentExceptionMsg())
if not pkgInfo.isMinimal:
if pkgInfo.author == "":
raise newException(NimbleError, "Incorrect .nimble file: " & path &
" does not contain an author field.")
if pkgInfo.description == "":
raise newException(NimbleError, "Incorrect .nimble file: " & path &
" does not contain a description field.")
if pkgInfo.license == "":
raise newException(NimbleError, "Incorrect .nimble file: " & path &
" does not contain a license field.")
if pkgInfo.backend notin ["c", "cc", "objc", "cpp", "js"]:
raise newException(NimbleError, "'" & pkgInfo.backend &
"' is an invalid backend.")
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.
@ -100,9 +133,7 @@ proc multiSplit(s: string): seq[string] =
if len(result) < 1:
return @[s]
proc readPackageInfo*(path: string): PackageInfo =
result = initPackageInfo()
result.mypath = path
proc readPackageInfoFromNimble(path: string; result: var PackageInfo) =
var fs = newFileStream(path, fmRead)
if fs != nil:
var p: CfgParser
@ -164,7 +195,72 @@ proc readPackageInfo*(path: string): PackageInfo =
close(p)
else:
raise newException(ValueError, "Cannot open package info: " & path)
validatePackageInfo(result, 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)``
##
## Also works for file paths like:
## ``/home/user/.nimble/pkgs/package-0.1/package.nimble``
if pkgPath.splitFile.ext == ".nimble":
return getNameVersion(pkgPath.splitPath.head)
result.name = ""
result.version = ""
let tail = pkgpath.splitPath.tail
if '-' notin tail:
result.name = tail
return
for i in countdown(tail.len-1, 0):
if tail[i] == '-':
result.name = tail[0 .. i-1]
result.version = tail[i+1 .. tail.len-1]
break
proc readPackageInfo*(nf: NimbleFile; onlyMinimalInfo=false): PackageInfo =
## Reads package info from the specifed 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)
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.
@ -241,21 +337,31 @@ proc getPackageList*(packagesPath: string): seq[Package] =
let pkg: Package = p.fromJson()
result.add(pkg)
proc findNimbleFile*(dir: string): string =
proc findNimbleFile*(dir: string; error: bool): NimbleFile =
result = ""
var hits = 0
for kind, path in walkDir(dir):
if kind == pcFile and path.splitFile.ext in [".babel", ".nimble"]:
if result != "":
raise newException(NimbleError,
"Only one .nimble file should be present in " & dir)
result = path
if kind == pcFile:
let ext = path.splitFile.ext
case ext
of ".babel", ".nimble":
result = path
inc hits
else: discard
if hits >= 2:
raise newException(NimbleError,
"Only one .nimble file should be present in " & dir)
elif hits == 0:
if error:
raise newException(NimbleError,
"Specified directory does not contain a .nimble file.")
else:
# 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)
if nimbleFile == "":
raise newException(NimbleError,
"Specified directory does not contain a .nimble file.")
let nimbleFile = findNimbleFile(dir, true)
result = readPackageInfo(nimbleFile)
proc getInstalledPkgs*(libsDir: string):
@ -266,13 +372,10 @@ proc getInstalledPkgs*(libsDir: string):
result = @[]
for kind, path in walkDir(libsDir):
if kind == pcDir:
let nimbleFile = findNimbleFile(path)
let nimbleFile = findNimbleFile(path, false)
if nimbleFile != "":
let meta = readMetaData(path)
result.add((readPackageInfo(nimbleFile), meta))
else:
# TODO: Abstract logging.
echo("WARNING: No .nimble file found for ", path)
result.add((readPackageInfo(nimbleFile, true), meta))
proc findPkg*(pkglist: seq[tuple[pkginfo: PackageInfo, meta: MetaData]],
dep: PkgTuple,
@ -284,8 +387,8 @@ proc findPkg*(pkglist: seq[tuple[pkginfo: PackageInfo, meta: MetaData]],
##
## **Note**: dep.name here could be a URL, hence the need for pkglist.meta.
for pkg in pkglist:
if pkg.pkginfo.name.normalize != dep.name.normalize and
pkg.meta.url.normalize != dep.name.normalize: continue
if cmpIgnoreStyle(pkg.pkginfo.name, dep.name) != 0 and
cmpIgnoreStyle(pkg.meta.url, dep.name) != 0: continue
if withinRange(newVersion(pkg.pkginfo.version), dep.ver):
if not result or newVersion(r.version) < newVersion(pkg.pkginfo.version):
r = pkg.pkginfo
@ -298,8 +401,8 @@ proc findAllPkgs*(pkglist: seq[tuple[pkginfo: PackageInfo, meta: MetaData]],
## packages if multiple are found.
result = @[]
for pkg in pkglist:
if pkg.pkginfo.name.normalize != dep.name.normalize and
pkg.meta.url.normalize != dep.name.normalize: continue
if cmpIgnoreStyle(pkg.pkginfo.name, dep.name) != 0 and
cmpIgnoreStyle(pkg.meta.url, dep.name) != 0: continue
if withinRange(newVersion(pkg.pkginfo.version), dep.ver):
result.add pkg.pkginfo
@ -318,22 +421,6 @@ proc getOutputDir*(pkgInfo: PackageInfo, bin: string): string =
else:
result = pkgInfo.mypath.splitFile.dir / bin
proc getNameVersion*(pkgpath: string): tuple[name, version: string] =
## Splits ``pkgpath`` in the format ``/home/user/.nimble/pkgs/package-0.1``
## into ``(packagea, 0.1)``
result.name = ""
result.version = ""
let tail = pkgpath.splitPath.tail
if '-' notin tail:
result.name = tail
return
for i in countdown(tail.len-1, 0):
if tail[i] == '-':
result.name = tail[0 .. i-1]
result.version = tail[i+1 .. tail.len-1]
break
proc echoPackage*(pkg: Package) =
echo(pkg.name & ":")
echo(" url: " & pkg.url & " (" & pkg.downloadMethod & ")")
@ -350,8 +437,26 @@ proc getDownloadDirName*(pkg: Package, verRange: VersionRange): string =
result.add "_"
result.add verSimple
proc isNimScript*(nf: NimbleFile): bool =
readPackageInfo(nf).isNimScript
when isMainModule:
doAssert getNameVersion("/home/user/.nimble/libs/packagea-0.1") ==
("packagea", "0.1")
doAssert getNameVersion("/home/user/.nimble/libs/package-a-0.1") ==
("package-a", "0.1")
doAssert getNameVersion("/home/user/.nimble/libs/package-a-0.1/package.nimble") ==
("package-a", "0.1")
validatePackageName("foo_bar")
validatePackageName("f_oo_b_a_r")
try:
validatePackageName("foo__bar")
assert false
except NimbleError:
assert true
doAssert toValidPackageName("foo__bar") == "foo_bar"
doAssert toValidPackageName("jhbasdh!£$@%#^_&*_()qwe") == "jhbasdh_qwe"
echo("All tests passed!")

View file

@ -33,6 +33,7 @@ type
PkgTuple* = tuple[name: string, ver: VersionRange]
ParseVersionError* = object of ValueError
NimbleError* = object of Exception
proc newVersion*(ver: string): Version = return Version(ver)
proc newSpecial*(spe: string): Special = return Special(spe)
@ -198,6 +199,24 @@ proc parseVersionRange*(s: string): VersionRange =
"Unexpected char in version range: " & s[i])
inc(i)
proc parseRequires*(req: string): PkgTuple =
try:
if ' ' in req:
var i = skipUntil(req, Whitespace)
result.name = req[0 .. i].strip
result.ver = parseVersionRange(req[i .. req.len-1])
elif '#' in req:
var i = skipUntil(req, {'#'})
result.name = req[0 .. i-1]
result.ver = parseVersionRange(req[i .. req.len-1])
else:
result.name = req.strip
result.ver = VersionRange(kind: verAny)
except ParseVersionError:
raise newException(NimbleError,
"Unable to parse dependency version range: " & getCurrentExceptionMsg())
proc `$`*(verRange: VersionRange): string =
case verRange.kind
of verLater: