nimble/packageinfo.nim
2013-06-02 23:38:43 +01:00

211 lines
7.2 KiB
Nim

# 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, common
type
TPackageInfo* = object
mypath*: string ## The path of this .babel file
name*: string
version*: string
author*: string
description*: string
license*: string
skipDirs*: seq[string]
skipFiles*: seq[string]
requires*: seq[tuple[name: string, ver: PVersionRange]]
bin*: seq[string]
TPackage* = object
name*: string
version*: string
license*: string
url*: string
dvcsTag*: string
downloadMethod*: string
tags*: seq[string]
description*: string
proc initPackageInfo(): TPackageInfo =
result.mypath = ""
result.name = ""
result.version = ""
result.author = ""
result.description = ""
result.license = ""
result.skipDirs = @[]
result.skipFiles = @[]
result.requires = @[]
result.bin = @[]
proc validatePackageInfo(pkgInfo: TPackageInfo, path: string) =
if pkgInfo.name == "":
quit("Incorrect .babel file: " & path & " does not contain a name field.")
if pkgInfo.version == "":
quit("Incorrect .babel file: " & path & " does not contain a version field.")
if pkgInfo.author == "":
quit("Incorrect .babel file: " & path & " does not contain an author field.")
if pkgInfo.description == "":
quit("Incorrect .babel file: " & path & " does not contain a description field.")
if pkgInfo.license == "":
quit("Incorrect .babel file: " & path & " does not contain a license field.")
proc parseRequires(req: string): tuple[name: string, ver: PVersionRange] =
try:
if ' ' in req:
var i = skipUntil(req, whitespace)
result.name = req[0 .. i].strip
result.ver = parseVersionRange(req[i .. -1])
else:
result.name = req.strip
result.ver = PVersionRange(kind: verAny)
except EParseVersion:
quit("Unable to parse dependency version range: " & getCurrentExceptionMsg())
proc readPackageInfo*(path: string): TPackageInfo =
result = initPackageInfo()
result.mypath = path
var fs = newFileStream(path, fmRead)
if fs != nil:
var p: TCfgParser
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 "skipdirs":
result.skipDirs.add(ev.value.split(','))
of "skipfiles":
result.skipFiles.add(ev.value.split(','))
of "bin":
result.bin = ev.value.split(',')
else:
quit("Invalid field: " & ev.key, QuitFailure)
of "deps", "dependencies":
case ev.key.normalize
of "requires":
for v in ev.value.split(','):
result.requires.add(parseRequires(v.strip))
else:
quit("Invalid field: " & ev.key, QuitFailure)
else: quit("Invalid section: " & currentSection, QuitFailure)
of cfgOption: quit("Invalid package info, should not contain --" & ev.value, QuitFailure)
of cfgError:
echo(ev.msg)
close(p)
else:
raise newException(EInvalidValue, "Cannot open package info: " & path)
validatePackageInfo(result, path)
proc optionalField(obj: PJsonNode, name: string): string =
if existsKey(obj, name):
if obj[name].kind == JString:
return obj[name].str
else:
quit("Corrupted packages.json file. " & name & " field is of unexpected type.")
else: return ""
proc requiredField(obj: PJsonNode, name: string): string =
if existsKey(obj, name):
if obj[name].kind == JString:
return obj[name].str
else:
quit("Corrupted packages.json file. " & name & " field is of unexpected type.")
else:
quit("Package in packages.json file does not contain a " & name & " field.")
proc getPackage*(pkg: string, packagesPath: string, resPkg: var TPackage): bool =
let packages = parseFile(packagesPath)
for p in packages:
if p["name"].str != pkg: continue
resPkg.name = pkg
resPkg.url = p.requiredField("url")
resPkg.version = p.optionalField("version")
resPkg.downloadMethod = p.requiredField("method")
resPkg.dvcsTag = p.optionalField("dvcs-tag")
resPkg.license = p.requiredField("license")
resPkg.tags = @[]
for t in p["tags"]:
resPkg.tags.add(t.str)
resPkg.description = p.requiredField("description")
return true
return false
proc getPackageList*(packagesPath: string): seq[TPackage] =
result = @[]
let packages = parseFile(packagesPath)
for p in packages:
var pkg: TPackage
pkg.name = p.requiredField("name")
pkg.version = p.optionalField("version")
pkg.url = p.requiredField("url")
pkg.downloadMethod = p.requiredField("method")
pkg.dvcsTag = p.optionalField("dvcs-tag")
pkg.license = p.requiredField("license")
pkg.tags = @[]
for t in p["tags"]:
pkg.tags.add(t.str)
pkg.description = p.requiredField("description")
result.add(pkg)
proc findBabelFile*(dir: string): string =
result = ""
for kind, path in walkDir(dir):
if kind == pcFile and path.splitFile.ext == ".babel":
if result != "":
raise newException(EBabel, "Only one .babel file should be present in " & dir)
result = path
proc getPkgInfo*(dir: string): TPackageInfo =
## Find the .babel file in ``dir`` and parses it, returning a TPackageInfo.
let babelFile = findBabelFile(dir)
if babelFile == "":
quit("Specified directory does not contain a .babel file.", QuitFailure)
result = readPackageInfo(babelFile)
proc getInstalledPkgs*(libsDir: string): seq[TPackageInfo] =
## Gets a list of installed packages.
##
## ``libsDir`` is in most cases: ~/.babel/libs/
result = @[]
for kind, path in walkDir(libsDir):
if kind == pcDir:
let babelFile = findBabelFile(path)
if babelFile != "":
result.add(readPackageInfo(babelFile))
else:
# TODO: Abstract logging.
echo("WARNING: No .babel file found for ", path)
proc findPkg*(pkglist: seq[TPackageInfo],
dep: tuple[name: string, ver: PVersionRange],
r: var TPackageInfo): bool =
## Searches ``pkglist`` for a package of which version is withing the range
## of ``dep.ver``. ``True`` is returned if a package is found. If multiple
## packages are found the newest one is returned (the one with the highest
## version number)
for pkg in pkglist:
if pkg.name != dep.name: continue
if withinRange(newVersion(pkg.version), dep.ver):
if not result or newVersion(r.version) < newVersion(pkg.version):
r = pkg
result = true
proc echoPackage*(pkg: TPackage) =
echo(pkg.name & ":")
echo(" url: " & pkg.url & " (" & pkg.downloadMethod & ")")
echo(" tags: " & pkg.tags.join(", "))
echo(" description: " & pkg.description)
echo(" license: " & pkg.license)