Refactored a lot of code -- split many functions into separate module.

This commit is contained in:
Dominik Picheta 2013-06-02 22:38:13 +01:00
commit f46fcaf6d3
4 changed files with 134 additions and 114 deletions

164
babel.nim
View file

@ -3,7 +3,7 @@
import httpclient, parseopt, os, strutils, osproc, pegs, tables, parseutils import httpclient, parseopt, os, strutils, osproc, pegs, tables, parseutils
import packageinfo, version import packageinfo, version, common, tools
type type
TActionType = enum TActionType = enum
@ -18,19 +18,17 @@ type
optionalName: seq[string] # When this is @[], installs package from current dir. optionalName: seq[string] # When this is @[], installs package from current dir.
of ActionSearch: of ActionSearch:
search: seq[string] # Search string. search: seq[string] # Search string.
EBabel = object of EBase
const const
help = """ help = """
Usage: babel COMMAND [opts] Usage: babel COMMAND [opts]
Commands: Commands:
install Installs a list of packages. install [pkgname, ...] Installs a list of packages.
build Builds a package. build [pkgname] Builds a package.
update Updates package list. A package list URL can be optionally specificed. update [url] Updates package list. A package list URL can be optionally specified.
search Searches for a specified package. search pkg/tag Searches for a specified package. Search is performed by tag and by name.
list Lists all packages. list Lists all packages.
""" """
babelVersion = "0.1.0" babelVersion = "0.1.0"
defaultPackageURL = "https://github.com/nimrod-code/packages/raw/master/packages.json" defaultPackageURL = "https://github.com/nimrod-code/packages/raw/master/packages.json"
@ -95,13 +93,6 @@ proc prompt(question: string): bool =
else: else:
return false return false
proc getNimrodVersion: TVersion =
let vOutput = execProcess("nimrod -v")
var matches: array[0..MaxSubpatterns, string]
if vOutput.find(peg"'Version'\s{(\d\.)+\d}", matches) == -1:
quit("Couldn't find Nimrod version.", QuitFailure)
newVersion(matches[0])
let babelDir = getHomeDir() / ".babel" let babelDir = getHomeDir() / ".babel"
let libsDir = babelDir / "libs" let libsDir = babelDir / "libs"
let binDir = babelDir / "bin" let binDir = babelDir / "bin"
@ -112,40 +103,18 @@ proc update(url: string = defaultPackageURL) =
downloadFile(url, babelDir / "packages.json") downloadFile(url, babelDir / "packages.json")
echo("Done.") echo("Done.")
proc findBabelFile(dir: string): string =
result = ""
for kind, path in walkDir(dir):
if kind == pcFile and path.splitFile.ext == ".babel":
if result != "": quit("Only one .babel file should be present in " & dir)
result = path
proc copyFileD(fro, to: string) = proc copyFileD(fro, to: string) =
echo(fro, " -> ", to) echo(fro, " -> ", to)
copyFile(fro, to) copyFile(fro, to)
proc samePaths(p1, p2: string): bool =
## Normalizes path (by adding a trailing slash) and compares.
let cp1 = if not p1.endsWith("/"): p1 & "/" else: p1
let cp2 = if not p2.endsWith("/"): p2 & "/" else: p2
return cmpPaths(cp1, cp2) == 0
proc changeRoot(origRoot, newRoot, path: string): string =
## origRoot: /home/dom/
## newRoot: /home/test/
## path: /home/dom/bar/blah/2/foo.txt
## Return value -> /home/test/bar/blah/2/foo.txt
if path.startsWith(origRoot):
return newRoot / path[origRoot.len .. -1]
else:
raise newException(EInvalidValue,
"Cannot change root of path: Path does not begin with original root.")
proc doCmd(cmd: string) = proc doCmd(cmd: string) =
let exitCode = execCmd(cmd) let exitCode = execCmd(cmd)
if exitCode != QuitSuccess: if exitCode != QuitSuccess:
quit("Execution failed with exit code " & $exitCode, QuitFailure) quit("Execution failed with exit code " & $exitCode, QuitFailure)
proc copyFilesRec(origDir, currentDir, dest: string, pkgInfo: TPackageInfo) = proc copyFilesRec(origDir, currentDir, dest: string, pkgInfo: TPackageInfo) =
## Used for library-only packages. Copies all the required files, skips
## files specified in the .babel file.
for kind, file in walkDir(currentDir): for kind, file in walkDir(currentDir):
if kind == pcDir: if kind == pcDir:
var skip = false var skip = false
@ -168,7 +137,7 @@ proc copyFilesRec(origDir, currentDir, dest: string, pkgInfo: TPackageInfo) =
if file.splitFile().name[0] == '.': skip = true if file.splitFile().name[0] == '.': skip = true
for ignoreFile in pkgInfo.skipFiles: for ignoreFile in pkgInfo.skipFiles:
if ignoreFile.endswith("babel"): if ignoreFile.endswith("babel"):
quit(ignoreFile & " must be installed.") raise newException(EBabel, ignoreFile & " must be installed.")
if samePaths(file, origDir / ignoreFile): if samePaths(file, origDir / ignoreFile):
skip = true skip = true
break break
@ -176,59 +145,30 @@ proc copyFilesRec(origDir, currentDir, dest: string, pkgInfo: TPackageInfo) =
if not skip: if not skip:
copyFileD(file, changeRoot(origDir, dest, file)) copyFileD(file, changeRoot(origDir, dest, file))
proc getPkgInfo(dir: string): TPackageInfo =
let babelFile = findBabelFile(dir)
if babelFile == "":
quit("Specified directory does not contain a .babel file.", QuitFailure)
result = readPackageInfo(babelFile)
# TODO: Move to packageinfo.nim
proc getInstalledPkgs(): seq[tuple[path: string, info: TPackageInfo]] =
## Gets a list of installed packages
result = @[]
for kind, path in walkDir(libsDir):
if kind == pcDir:
let babelFile = findBabelFile(path)
if babelFile != "":
result.add((path, readPackageInfo(babelFile)))
else:
# TODO: Abstract logging.
echo("WARNING: No .babel file found for ", path)
proc findPkg(pkglist: seq[tuple[path: string, info: TPackageInfo]],
dep: tuple[name: string, ver: PVersionRange],
r: var tuple[path: string, info: TPackageInfo]): bool =
for pkg in pkglist:
if pkg.info.name != dep.name: continue
if withinRange(newVersion(pkg.info.version), dep.ver):
if not result or newVersion(r.info.version) < newVersion(pkg.info.version):
r = pkg
result = true
proc install(packages: seq[String], verRange: PVersionRange): string {.discardable.} proc install(packages: seq[String], verRange: PVersionRange): string {.discardable.}
proc processDeps(pkginfo: TPackageInfo): seq[string] = proc processDeps(pkginfo: TPackageInfo): seq[string] =
## Verifies and installs dependencies. ## Verifies and installs dependencies.
## ##
## Returns the list of paths to pass to the compiler during build phase. ## Returns the list of paths to pass to the compiler during build phase.
result = @[] result = @[]
let pkglist = getInstalledPkgs() let pkglist = getInstalledPkgs(libsDir)
for dep in pkginfo.requires: for dep in pkginfo.requires:
if dep.name == "nimrod": if dep.name == "nimrod":
if not withinRange(nimVer, dep.ver): if not withinRange(nimVer, dep.ver):
quit("Unsatisfied dependency: " & dep.name & " (" & $dep.ver & ")") quit("Unsatisfied dependency: " & dep.name & " (" & $dep.ver & ")")
else: else:
echo("Looking for ", dep.name, " (", $dep.ver, ")...") echo("Looking for ", dep.name, " (", $dep.ver, ")...")
var pkg: tuple[path: string, info: TPackageInfo] var pkg: TPackageInfo
if not findPkg(pkglist, dep, pkg): if not findPkg(pkglist, dep, pkg):
echo("None found, installing...")
let dest = install(@[dep.name], dep.ver) let dest = install(@[dep.name], dep.ver)
if dest != "": if dest != "":
# only add if not a binary package # only add if not a binary package
result.add(dest) result.add(dest)
else: else:
echo("Dependency already satisfied.") echo("Dependency already satisfied.")
if pkg.info.bin.len == 0: if pkg.bin.len == 0:
result.add(pkg.path) result.add(pkg.mypath.splitFile.dir)
proc buildFromDir(dir: string, paths: seq[string]) = proc buildFromDir(dir: string, paths: seq[string]) =
## Builds a package which resides in ``dir`` ## Builds a package which resides in ``dir``
@ -237,7 +177,6 @@ proc buildFromDir(dir: string, paths: seq[string]) =
for path in paths: args.add("--path:" & path & " ") for path in paths: args.add("--path:" & path & " ")
for bin in pkgInfo.bin: for bin in pkgInfo.bin:
echo("Building ", pkginfo.name, "/", bin, "...") echo("Building ", pkginfo.name, "/", bin, "...")
echo(args)
doCmd("nimrod c -d:release " & args & dir / bin) doCmd("nimrod c -d:release " & args & dir / bin)
proc installFromDir(dir: string, latest: bool): string = proc installFromDir(dir: string, latest: bool): string =
@ -273,11 +212,6 @@ proc installFromDir(dir: string, latest: bool): string =
echo(pkgInfo.name & " installed successfully.") echo(pkgInfo.name & " installed successfully.")
result = pkgDestDir result = pkgDestDir
proc getDVCSTag(pkg: TPackage): string =
result = pkg.dvcsTag
if result == "":
result = pkg.version
proc getTagsList(dir: string): seq[string] = proc getTagsList(dir: string): seq[string] =
let output = execProcess("cd \"" & dir & "\" && git tag") let output = execProcess("cd \"" & dir & "\" && git tag")
if output.len > 0: if output.len > 0:
@ -294,6 +228,42 @@ proc getVersionList(dir: string): TTable[TVersion, string] =
# TODO: Better checking, tags can have any names. Add warnings and such. # TODO: Better checking, tags can have any names. Add warnings and such.
result[newVersion(tag[i .. -1])] = tag result[newVersion(tag[i .. -1])] = tag
proc downloadPkg(pkg: TPackage, verRange: PVersionRange): string =
let downloadDir = (getTempDir() / "babel" / pkg.name)
echo("Downloading ", pkg.name, " into ", downloadDir, "...")
case pkg.downloadMethod
of "git":
echo("Executing git...")
if existsDir(downloadDir / ".git"):
doCmd("cd " & downloadDir & " && git pull")
else:
removeDir(downloadDir)
doCmd("git clone --depth 1 " & pkg.url & " " & downloadDir)
# TODO: Determine if version is a commit hash, if it is. Move the
# git repo to ``babelDir/libs``, then babel can simply checkout
# the correct hash instead of constantly cloning and copying.
# N.B. This may still partly be requires, as one lib may require hash A
# whereas another lib requires hash B and they are both required by the
# project you want to build.
let versions = getVersionList(downloadDir)
if versions.len > 0:
let latest = findLatest(verRange, versions)
if latest.tag != "":
doCmd("cd \"" & downloadDir & "\" && git checkout " & latest.tag)
elif verRange.kind != verAny:
let pkginfo = getPkgInfo(downloadDir)
if pkginfo.version.newVersion notin verRange:
raise newException(EBabel,
"No versions of " & pkg.name &
" exist (this usually means that `git tag` returned nothing)." &
"Git HEAD also does not satisfy version range: " & $verRange)
# We use GIT HEAD if it satisfies our ver range
else: raise newException(EBabel, "Unknown download method: " & pkg.downloadMethod)
result = downloadDir
proc install(packages: seq[String], verRange: PVersionRange): string = proc install(packages: seq[String], verRange: PVersionRange): string =
if packages == @[]: if packages == @[]:
result = installFromDir(getCurrentDir(), false) result = installFromDir(getCurrentDir(), false)
@ -303,37 +273,7 @@ proc install(packages: seq[String], verRange: PVersionRange): string =
for p in packages: for p in packages:
var pkg: TPackage var pkg: TPackage
if getPackage(p, babelDir / "packages.json", pkg): if getPackage(p, babelDir / "packages.json", pkg):
let downloadDir = (getTempDir() / "babel" / pkg.name) let downloadDir = downloadPkg(pkg, verRange)
#let dvcsTag = getDVCSTag(pkg)
case pkg.downloadMethod
of "git":
echo("Executing git...")
if existsDir(downloadDir / ".git"):
doCmd("cd " & downloadDir & " && git pull")
else:
removeDir(downloadDir)
doCmd("git clone --depth 1 " & pkg.url & " " & downloadDir)
# TODO: Determine if version is a commit hash, if it is. Move the
# git repo to ``babelDir/libs``, then babel can simply checkout
# the correct hash instead of constantly cloning and copying.
let versions = getVersionList(downloadDir)
if versions.len > 0:
let latest = findLatest(verRange, versions)
if latest.tag != "":
doCmd("cd \"" & downloadDir & "\" && git checkout " & latest.tag)
elif verRange.kind != verAny:
let pkginfo = getPkgInfo(downloadDir)
if pkginfo.version.newVersion notin verRange:
raise newException(EBabel,
"No versions of " & pkg.name &
" exist (this usually means that `git tag` returned nothing)." &
"Git HEAD also does not satisfy version range: " & $verRange)
# We use GIT HEAD if it satisfies our ver range
else: quit("Unknown download method: " & pkg.downloadMethod, QuitFailure)
result = installFromDir(downloadDir, false) result = installFromDir(downloadDir, false)
else: else:
raise newException(EBabel, "Package not found.") raise newException(EBabel, "Package not found.")
@ -359,7 +299,7 @@ proc search(action: TAction) =
notFound = false notFound = false
break break
if notFound: if notFound:
# Search by tag. # Search by name.
for pkg in pkgList: for pkg in pkgList:
if pkg.name in action.search: if pkg.name in action.search:
echoPackage(pkg) echoPackage(pkg)

4
common.nim Normal file
View file

@ -0,0 +1,4 @@
# Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info.
type
EBabel* = object of EBase

View file

@ -1,9 +1,10 @@
# Copyright (C) Dominik Picheta. All rights reserved. # Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info. # BSD License. Look at license.txt for more info.
import parsecfg, json, streams, strutils, parseutils import parsecfg, json, streams, strutils, parseutils, os
import version import version, common
type type
TPackageInfo* = object TPackageInfo* = object
mypath*: string ## The path of this .babel file
name*: string name*: string
version*: string version*: string
author*: string author*: string
@ -25,6 +26,7 @@ type
description*: string description*: string
proc initPackageInfo(): TPackageInfo = proc initPackageInfo(): TPackageInfo =
result.mypath = ""
result.name = "" result.name = ""
result.version = "" result.version = ""
result.author = "" result.author = ""
@ -61,6 +63,7 @@ proc parseRequires(req: string): tuple[name: string, ver: PVersionRange] =
proc readPackageInfo*(path: string): TPackageInfo = proc readPackageInfo*(path: string): TPackageInfo =
result = initPackageInfo() result = initPackageInfo()
result.mypath = path
var fs = newFileStream(path, fmRead) var fs = newFileStream(path, fmRead)
if fs != nil: if fs != nil:
var p: TCfgParser var p: TCfgParser
@ -157,6 +160,49 @@ proc getPackageList*(packagesPath: string): seq[TPackage] =
pkg.description = p.requiredField("description") pkg.description = p.requiredField("description")
result.add(pkg) 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) = proc echoPackage*(pkg: TPackage) =
echo(pkg.name & ":") echo(pkg.name & ":")
if pkg.version != "": if pkg.version != "":

30
tools.nim Normal file
View file

@ -0,0 +1,30 @@
# Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info.
#
# Various miscellaneous utility functions reside here.
import osproc, pegs, strutils, os
import version
proc getNimrodVersion*: TVersion =
let vOutput = execProcess("nimrod -v")
var matches: array[0..MaxSubpatterns, string]
if vOutput.find(peg"'Version'\s{(\d\.)+\d}", matches) == -1:
quit("Couldn't find Nimrod version.", QuitFailure)
newVersion(matches[0])
proc samePaths*(p1, p2: string): bool =
## Normalizes path (by adding a trailing slash) and compares.
let cp1 = if not p1.endsWith("/"): p1 & "/" else: p1
let cp2 = if not p2.endsWith("/"): p2 & "/" else: p2
return cmpPaths(cp1, cp2) == 0
proc changeRoot*(origRoot, newRoot, path: string): string =
## origRoot: /home/dom/
## newRoot: /home/test/
## path: /home/dom/bar/blah/2/foo.txt
## Return value -> /home/test/bar/blah/2/foo.txt
if path.startsWith(origRoot):
return newRoot / path[origRoot.len .. -1]
else:
raise newException(EInvalidValue,
"Cannot change root of path: Path does not begin with original root.")