Re-write, mostly works.
This commit is contained in:
parent
ee0fd7148f
commit
c6396a3bc3
7 changed files with 285 additions and 612 deletions
13
babel.babel
13
babel.babel
|
|
@ -1,17 +1,10 @@
|
|||
; Babel library
|
||||
; Example babel file
|
||||
[Package]
|
||||
name = "babel"
|
||||
version = "0.1.0"
|
||||
author = "Dominik Picheta"
|
||||
category = "Distribution"
|
||||
description = """Babel framework: Specifies a common interface for programmers
|
||||
to more easily build their applications in portable way."""
|
||||
description = """Jester is a web framework inspired by Sinatra."""
|
||||
|
||||
[Library]
|
||||
Depends = "nimrod >= 0.8.10"
|
||||
ExposedModules = "parser, installer, version" ; No need for .nim
|
||||
;Files = "babel.babel"
|
||||
SkipDirs = "tests"
|
||||
|
||||
;[Exe]
|
||||
;Depends = "nimrod >= 0.8.11"
|
||||
;Exe = "babel"
|
||||
|
|
|
|||
201
babel.nim
Normal file
201
babel.nim
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
import httpclient, parseopt, os, strutils, osproc
|
||||
|
||||
import packageinfo
|
||||
|
||||
type
|
||||
TActionType = enum
|
||||
ActionNil, ActionUpdate, ActionInstall
|
||||
|
||||
TAction = object
|
||||
case typ: TActionType
|
||||
of ActionNil: nil
|
||||
of ActionUpdate:
|
||||
optionalURL: string # Overrides default package list.
|
||||
of ActionInstall:
|
||||
optionalName: seq[string] # When this is @[], installs package from current dir.
|
||||
|
||||
const
|
||||
help = """
|
||||
Usage: babel COMMAND
|
||||
|
||||
Commands:
|
||||
install Installs a list of packages.
|
||||
update Updates package list. A package list URL can be optionally specificed.
|
||||
"""
|
||||
babelVersion = "0.1.0"
|
||||
defaultPackageURL = "https://github.com/nimrod-code/packages/raw/master/packages.json"
|
||||
|
||||
proc writeHelp() =
|
||||
echo(help)
|
||||
quit(QuitSuccess)
|
||||
|
||||
proc writeVersion() =
|
||||
echo(babelVersion)
|
||||
quit(QuitSuccess)
|
||||
|
||||
proc parseCmdLine(): TAction =
|
||||
result.typ = ActionNil
|
||||
for kind, key, val in getOpt():
|
||||
case kind
|
||||
of cmdArgument:
|
||||
if result.typ == ActionNil:
|
||||
case key
|
||||
of "install":
|
||||
result.typ = ActionInstall
|
||||
result.optionalName = @[]
|
||||
of "update":
|
||||
result.typ = ActionUpdate
|
||||
result.optionalURL = ""
|
||||
else: writeHelp()
|
||||
else:
|
||||
case result.typ
|
||||
of ActionNil:
|
||||
assert false
|
||||
of ActionInstall:
|
||||
result.optionalName.add(key)
|
||||
of ActionUpdate:
|
||||
result.optionalURL = key
|
||||
of cmdLongOption, cmdShortOption:
|
||||
case key
|
||||
of "help", "h": writeHelp()
|
||||
of "version", "v": writeVersion()
|
||||
of cmdEnd: assert(false) # cannot happen
|
||||
if result.typ == ActionNil:
|
||||
writeHelp()
|
||||
|
||||
proc update(url: string = defaultPackageURL) =
|
||||
echo("Downloading package list from " & url)
|
||||
downloadFile(url, getHomeDir() / ".babel" / "packages.json")
|
||||
echo("Done.")
|
||||
|
||||
proc findBabelFile(dir: string): string =
|
||||
for kind, path in walkDir(dir):
|
||||
if kind == pcFile and path.splitFile.ext == ".babel":
|
||||
return path
|
||||
return ""
|
||||
|
||||
proc copyFileD(fro, to: string) =
|
||||
echo(fro, " -> ", to)
|
||||
copyFile(fro, to)
|
||||
|
||||
proc getBabelDir: string = return getHomeDir() / ".babel"
|
||||
|
||||
proc getLibsDir: string = return getBabelDir() / "libs"
|
||||
|
||||
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 copyFilesRec(origDir, currentDir: string, pkgInfo: TPackageInfo) =
|
||||
for kind, file in walkDir(currentDir):
|
||||
if kind == pcDir:
|
||||
var skip = false
|
||||
for ignoreDir in pkgInfo.skipDirs:
|
||||
if samePaths(file, origDir / ignoreDir):
|
||||
skip = true
|
||||
break
|
||||
let thisDir = splitPath(file).tail
|
||||
assert thisDir != ""
|
||||
if thisDir[0] == '.': skip = true
|
||||
if thisDir == "nimcache": skip = true
|
||||
|
||||
if skip: continue
|
||||
# Create the dir.
|
||||
createDir(changeRoot(origDir, getLibsDir() / pkgInfo.name, file))
|
||||
|
||||
copyFilesRec(origDir, file, pkgInfo)
|
||||
else:
|
||||
var skip = false
|
||||
if file.splitFile().name[0] == '.': skip = true
|
||||
if file.splitFile().ext == "": skip = true
|
||||
for ignoreFile in pkgInfo.skipFiles:
|
||||
if samePaths(file, origDir / ignoreFile):
|
||||
skip = true
|
||||
break
|
||||
|
||||
if not skip:
|
||||
copyFileD(file, changeRoot(origDir, getLibsDir() / pkgInfo.name, file))
|
||||
|
||||
proc installFromDir(dir: string) =
|
||||
let babelFile = findBabelFile(dir)
|
||||
if babelFile == "":
|
||||
quit("Specified directory does not contain a .babel file.", QuitFailure)
|
||||
var pkgInfo = readPackageInfo(babelFile)
|
||||
if not existsDir(getLibsDir() / pkgInfo.name):
|
||||
createDir(getLibsDir() / pkgInfo.name)
|
||||
else: echo("Warning: Package already exists.")
|
||||
|
||||
# Find main project file.
|
||||
let nimFile = dir / pkgInfo.name.addFileExt("nim")
|
||||
let nimrodFile = dir / pkgInfo.name.addFileExt("nimrod")
|
||||
if existsFile(nimFile) or existsFile(nimrodFile):
|
||||
if existsFile(nimFile):
|
||||
copyFileD(nimFile, changeRoot(dir, getLibsDir(), nimFile))
|
||||
pkgInfo.skipFiles.add(changeRoot(dir, "", nimFile))
|
||||
elif existsFile(nimrodFile):
|
||||
copyFileD(nimrodFile, changeRoot(dir, getLibsDir(), nimrodFile))
|
||||
pkgInfo.skipFiles.add(changeRoot(dir, "", nimrodFile))
|
||||
else:
|
||||
quit("Could not find main package file.", QuitFailure)
|
||||
|
||||
copyFilesRec(dir, dir, pkgInfo)
|
||||
|
||||
proc install(packages: seq[String]) =
|
||||
if packages == @[]:
|
||||
installFromDir(getCurrentDir())
|
||||
else:
|
||||
if not existsFile(getBabelDir() / "packages.json"):
|
||||
quit("Please run babel update.", QuitFailure)
|
||||
for p in packages:
|
||||
var pkg: TPackage
|
||||
if getPackage(p, getBabelDir() / "packages.json", pkg):
|
||||
let downloadDir = (getTempDir() / "babel" / pkg.name)
|
||||
case pkg.downloadMethod
|
||||
of "git":
|
||||
echo("Executing git...")
|
||||
removeDir(downloadDir)
|
||||
let exitCode = execCmd("git clone " & pkg.url & " " & downloadDir)
|
||||
if exitCode != QuitSuccess:
|
||||
quit("Execution of git failed.", QuitFailure)
|
||||
else: quit("Unknown download method: " & pkg.downloadMethod, QuitFailure)
|
||||
installFromDir(downloadDir)
|
||||
else:
|
||||
quit("Package not found.", QuitFailure)
|
||||
|
||||
proc doAction(action: TAction) =
|
||||
case action.typ
|
||||
of ActionUpdate:
|
||||
if action.optionalURL != "":
|
||||
update(action.optionalURL)
|
||||
else:
|
||||
update()
|
||||
of ActionInstall:
|
||||
install(action.optionalName)
|
||||
of ActionNil:
|
||||
assert false
|
||||
|
||||
when isMainModule:
|
||||
if not existsDir(getHomeDir() / ".babel"):
|
||||
createDir(getHomeDir() / ".babel")
|
||||
if not existsDir(getHomeDir() / ".babel" / "libs"):
|
||||
createDir(getHomeDir() / ".babel" / "libs")
|
||||
|
||||
parseCmdLine().doAction()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
230
installer.nim
230
installer.nim
|
|
@ -1,230 +0,0 @@
|
|||
import parser, version, osproc, strutils, re, os, parseutils, streams
|
||||
|
||||
type
|
||||
EInstall = object of EBase
|
||||
|
||||
TDepend = tuple[name: String, verRange: PVersionRange]
|
||||
|
||||
var debug* = 1 ## 0 = no messages, 1 - 3 = less to more verbose respectively.
|
||||
|
||||
proc echoD*(s: string, verbosity: int = 1, nl: bool = True) =
|
||||
if debug >= verbosity:
|
||||
stdout.write(s & (if nl: "\n" else: ""))
|
||||
|
||||
proc getBabelDir(): string =
|
||||
when defined(windows):
|
||||
result = getHomeDir() / "babel"
|
||||
else:
|
||||
result = getHomeDir() / ".babel"
|
||||
|
||||
proc getNimVersion(cmd: string = "nimrod"): String =
|
||||
var output = execProcess(cmd & " -v")
|
||||
var line = splitLines(output)[0]
|
||||
|
||||
# Thanks Araq :)
|
||||
var i = 0
|
||||
var nimrodVersion = ""
|
||||
i = skipIgnoreCase(line, "Nimrod Compiler Version ")
|
||||
if i <= 0: raise newException(EInstall, "Cannot detect Nimrod's version")
|
||||
i = parseToken(line, nimrodVersion, {'.', '0'..'9'}, i)
|
||||
if nimrodVersion.len == 0:
|
||||
raise newException(EInstall, "Cannot detect Nimrod's version")
|
||||
|
||||
return nimrodVersion
|
||||
|
||||
proc compile*(file: string, flags: string = "") =
|
||||
var args: string = flags & "c " & file
|
||||
echoD("Compiling " & file & "...")
|
||||
var code = execShellCmd(findExe("nimrod") & " " & args)
|
||||
if code != quitSuccess:
|
||||
raise newException(EInstall, "Compilation failed: Nimrod returned exit code " &
|
||||
$code)
|
||||
|
||||
proc dependExists(name: string, verRange: PVersionRange): Bool =
|
||||
if name == "nimrod":
|
||||
var nimVer = getNimVersion()
|
||||
if not withinRange(newVersion(nimVer), verRange):
|
||||
raise newException(EInstall, "Nimrod version doesn't satisfy dependency: " &
|
||||
nimVer & " " & $verRange)
|
||||
else: return True
|
||||
else:
|
||||
for kind, path in walkDir(getBabelDir() / "packages"):
|
||||
if kind == pcFile:
|
||||
var file = path.extractFilename()
|
||||
if file.startswith(name & "-"):
|
||||
if verRange.kind != verAny:
|
||||
var ver = ""
|
||||
# This will have a dot at the end, it doesn't cause trouble though.
|
||||
var ret = file.parseToken(ver, digits + {'.'}, name.len() + 1)
|
||||
if ret != 0:
|
||||
if withinRange(newVersion(ver), verRange):
|
||||
return True
|
||||
else:
|
||||
raise newException(EInstall, "Invalid .babel file: " & file)
|
||||
else: return True
|
||||
|
||||
return False
|
||||
|
||||
proc verifyDepends(proj: TProject): seq[TDepend] =
|
||||
result = @[]
|
||||
for nameStr, verStr in items(proj.depends):
|
||||
echoD(" " & nameStr & " " & verStr & "...", 2, False)
|
||||
var verRange: PVersionRange
|
||||
if verStr == "":
|
||||
new(verRange)
|
||||
verRange.kind = verAny
|
||||
else:
|
||||
verRange = parseVersionRange(verStr)
|
||||
|
||||
if not dependExists(nameStr, verRange):
|
||||
result.add((nameStr, verRange))
|
||||
echoD(" FAIL", 2)
|
||||
else:
|
||||
echoD(" OK", 2)
|
||||
|
||||
proc createDirDebug(dir: string) =
|
||||
# Have to check, so that it doesn't echo...
|
||||
if not existsDir(dir):
|
||||
echoD("Creating directory " & dir & "...", 3, False)
|
||||
createDir(dir)
|
||||
echoD(" Done!", 3)
|
||||
|
||||
proc createDirs(dirs: seq[string]) =
|
||||
for i in items(dirs):
|
||||
createDirDebug(i)
|
||||
|
||||
proc copyFileDebug(file, copyTo: string) =
|
||||
echoD("Copying " & file & " to " & copyTo & "...", 3, False)
|
||||
copyFile(file, copyTo)
|
||||
echoD(" Done!", 3)
|
||||
|
||||
proc moveFileDebug(file, moveTo: string) =
|
||||
echoD("Moving " & file & " to " & moveTo & "...", 3, False)
|
||||
moveFile(file, moveTo)
|
||||
echoD(" Done!", 3)
|
||||
|
||||
proc copyFiles(proj: TProject) =
|
||||
# This will create a $home/.babel and lib/ or bin/. It will also copy all the
|
||||
# files listed in proj.modules and proj.files and the .babel file.
|
||||
var babelDir = getBabelDir()
|
||||
|
||||
var dirs = @[babelDir, babelDir / "lib", babelDir / "bin", babelDir / "packages"]
|
||||
createDirs(dirs)
|
||||
if proj.library:
|
||||
# TODO: How will we handle multiple versions?
|
||||
var projDir = babelDir / "lib" / proj.name # $babel/lib/name
|
||||
createDirDebug(projDir)
|
||||
# Copy the files
|
||||
for i in items(proj.modules):
|
||||
var file = proj.confDir / i.addFileExt("nim")
|
||||
var copyTo = projDir / i.addFileExt("nim")
|
||||
copyFileDebug(file, copyTo)
|
||||
if proj.files.len > 0:
|
||||
for i in items(proj.files):
|
||||
var file = proj.confDir / i
|
||||
var copyTo = projDir / i
|
||||
copyFileDebug(file, copyTo)
|
||||
|
||||
elif proj.executable:
|
||||
var exeSrcFile = proj.confDir / proj.exeFile.addFileExt("nim")
|
||||
# Compile
|
||||
compile(exeSrcFile)
|
||||
|
||||
var exeCmpFile = exeSrcFile.changeFileExt(ExeExt)
|
||||
var copyTo = babelDir / "bin" / proj.exeFile.addFileExt(ExeExt)
|
||||
copyFileDebug(exeCmpFile, copyTo)
|
||||
|
||||
# Copy the .babel file into the packages folder.
|
||||
var babelFile = proj.confDir / proj.name.addFileExt("babel")
|
||||
# addFileExt fails in this situation. Although it's fine without .babel
|
||||
var copyTo = babelDir / "packages" /
|
||||
(proj.name & "-" & proj.version)
|
||||
copyFileDebug(babelFile, copyTo)
|
||||
|
||||
|
||||
|
||||
proc upgrade(proj: TProject) =
|
||||
## This proc moves the files in lib/package into lib/package/lastVersion
|
||||
## It then copies the new version into lib/package.
|
||||
|
||||
var babelDir = getBabelDir()
|
||||
# Find the config for the latest (currently installed) package version in
|
||||
# packages/
|
||||
var latestVersion = "0"
|
||||
var path = ""
|
||||
for kind, confPath in walkDir(babelDir / "packages"):
|
||||
if kind == pcFile:
|
||||
var file = confPath.extractFilename()
|
||||
if file.startsWith(proj.name & "-"):
|
||||
var ver = ""
|
||||
# This will have a dot at the end, it doesn't cause trouble though.
|
||||
discard file.parseToken(ver, digits + {'.'}, proj.name.len() + 1)
|
||||
if ver > latestVersion:
|
||||
latestVersion = ver
|
||||
path = confPath
|
||||
|
||||
assert(path != "")
|
||||
|
||||
if proj.library:
|
||||
echoD("Reading " & path & "...", 3)
|
||||
var latestConf = parseBabel(path)
|
||||
var newVerDir = babelDir / "lib" / latestConf.name / latestConf.version
|
||||
createDirDebug(newVerDir)
|
||||
# Move the files
|
||||
for kind, path in walkDir(babelDir / "lib" / latestConf.name):
|
||||
if kind == pcFile:
|
||||
moveFileDebug(path, newVerDir / path.extractFilename())
|
||||
|
||||
# Install the new version
|
||||
copyFiles(proj)
|
||||
|
||||
|
||||
proc install*(name: string, filename: string = "") =
|
||||
## Install package by the name of ``name``, filename specifies where to look for it
|
||||
## if left as "", the current working directory will be assumed.
|
||||
# TODO: Add a `debug` variable? If true the status messages get echo-ed,
|
||||
# vice-versa if false?
|
||||
var babelFile: TProject
|
||||
var path = ""
|
||||
if filename == "":
|
||||
path = name.addFileExt("babel")
|
||||
else:
|
||||
path = filename / name.addFileExt("babel")
|
||||
|
||||
echoD("Reading " & path & "...", 3)
|
||||
babelFile = parseBabel(path)
|
||||
|
||||
var ret = babelFile.verify()
|
||||
if ret != "":
|
||||
raise newException(EInstall, "Verifying the .babel file failed: " & ret)
|
||||
|
||||
var upgrade = False # Specifies whether to upgrade
|
||||
# Check whether this package is already installed
|
||||
if dependExists(babelFile.name, newVREq(babelFile.version)):
|
||||
raise newException(EInstall,
|
||||
"This package is already installed!")
|
||||
elif dependExists(babelFile.name, newVREarlier(babelFile.version)):
|
||||
upgrade = True
|
||||
|
||||
if babelFile.depends.len == 1:
|
||||
echoD("Verifying 1 dependency...")
|
||||
else:
|
||||
echoD("Verifying " & $babelFile.depends.len() & " dependencies...")
|
||||
var dependsNeeded = babelFile.verifyDepends()
|
||||
if dependsNeeded.len() > 0:
|
||||
raise newException(EInstall, "TODO: Download & Install dependencies.")
|
||||
else:
|
||||
echoD("All dependencies verified!")
|
||||
|
||||
if not upgrade:
|
||||
echoD("Installing " & babelFile.name & "-" & babelFile.version & "...")
|
||||
babelFile.copyFiles()
|
||||
else:
|
||||
echoD("Upgrading " & babelFile.name & " to version " &
|
||||
babelFile.version & "...")
|
||||
babelFile.upgrade()
|
||||
|
||||
echoD("Package " & babelFile.name & " successfully installed.")
|
||||
|
||||
when isMainModule:
|
||||
install(paramStr(1))
|
||||
71
packageinfo.nim
Normal file
71
packageinfo.nim
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import parsecfg, json, streams, strutils
|
||||
type
|
||||
TPackageInfo* = object
|
||||
name*: string
|
||||
version*: string
|
||||
author*: string
|
||||
description*: string
|
||||
library*: bool
|
||||
skipDirs*: seq[string]
|
||||
skipFiles*: seq[string]
|
||||
|
||||
TPackage* = object
|
||||
name*: string
|
||||
url*: string
|
||||
downloadMethod*: string
|
||||
tags*: seq[string]
|
||||
description*: string
|
||||
|
||||
proc readPackageInfo*(path: string): TPackageInfo =
|
||||
result.skipDirs = @[]
|
||||
result.skipFiles = @[]
|
||||
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 "library":
|
||||
case ev.key.normalize
|
||||
of "skipdirs":
|
||||
result.skipDirs.add(ev.value.split(','))
|
||||
of "skipfiles":
|
||||
result.skipFiles.add(ev.value.split(','))
|
||||
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:
|
||||
quit("Cannot open package info: " & path, QuitFailure)
|
||||
|
||||
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["url"].str
|
||||
resPkg.downloadMethod = p["method"].str
|
||||
resPkg.tags = @[]
|
||||
for t in p["tags"]:
|
||||
resPkg.tags.add(t.str)
|
||||
resPkg.description = p["description"].str
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
|
||||
193
parser.nim
193
parser.nim
|
|
@ -1,193 +0,0 @@
|
|||
import parsecfg, streams, strutils, os
|
||||
|
||||
type
|
||||
TProject* = object
|
||||
name*: String # Req
|
||||
version*: String # Req
|
||||
author*: String # Req
|
||||
category*: String # Req
|
||||
desc*: String # Req
|
||||
license*: String
|
||||
homepage*: String
|
||||
|
||||
library*: bool
|
||||
depends*: seq[tuple[name, vRange: string]] # Dependencies
|
||||
modules*: seq[string] # ExtraModules
|
||||
files*: seq[string] # files
|
||||
|
||||
executable*: bool
|
||||
exeFile*: string
|
||||
|
||||
unknownFields*: seq[string] # TODO:
|
||||
|
||||
confDir*: string # Directory of the babel file, "" if current work dir.
|
||||
|
||||
EParseErr* = object of EInvalidValue
|
||||
|
||||
proc initProj(): TProject =
|
||||
result.name = ""
|
||||
result.version = ""
|
||||
result.author = ""
|
||||
result.category = ""
|
||||
result.desc = ""
|
||||
result.license = ""
|
||||
result.homepage = ""
|
||||
|
||||
result.library = False
|
||||
result.executable = False
|
||||
result.depends = @[]
|
||||
result.modules = @[]
|
||||
result.files = @[]
|
||||
result.exeFile = ""
|
||||
|
||||
result.unknownFields = @[]
|
||||
|
||||
result.confDir = ""
|
||||
|
||||
proc parseList(s: string): seq[string] =
|
||||
result = @[]
|
||||
var many = s.split({',', ';'})
|
||||
for i in items(many):
|
||||
result.add(i.strip())
|
||||
|
||||
proc skipUntil(s, token: string, o: var string): int =
|
||||
## Captures each char until `token` is found, sets `o` to the captured string,
|
||||
## and returns the number of characters captured.
|
||||
var i = 0
|
||||
while True:
|
||||
case s[i]
|
||||
of '\0':
|
||||
break
|
||||
else:
|
||||
var found = False
|
||||
for t in 0..len(token)-1:
|
||||
if s[i+t] == token[t]:
|
||||
found = True
|
||||
else:
|
||||
found = False
|
||||
break
|
||||
if found:
|
||||
break
|
||||
inc(i)
|
||||
o = copy(s, 0, i-1)
|
||||
return i
|
||||
|
||||
proc parseDepends(s: string): seq[tuple[name, vRange: string]] =
|
||||
result = @[]
|
||||
var many = s.split({',', ';'})
|
||||
for i in items(many):
|
||||
var stripped = i.strip()
|
||||
var name = ""
|
||||
var index = skipUntil(stripped, " ", name)
|
||||
var vRange = stripped.copy(index)
|
||||
result.add((name.strip(), vRange.strip()))
|
||||
|
||||
proc parseErr(p: TCfgParser, msg: string) =
|
||||
raise newException(EParseErr, "(" & $p.getLine() & ", " &
|
||||
$p.getColumn() & ") " & msg)
|
||||
|
||||
proc parseBabel*(file: string): TProject =
|
||||
result = initProj()
|
||||
result.confDir = splitFile(file).dir
|
||||
|
||||
var f = newFileStream(file, fmRead)
|
||||
if f != nil:
|
||||
var p: TCfgParser
|
||||
open(p, f, file)
|
||||
|
||||
var section: String = ""
|
||||
while true:
|
||||
var e = next(p)
|
||||
case e.kind
|
||||
of cfgEof:
|
||||
break
|
||||
of cfgKeyValuePair:
|
||||
case section
|
||||
of "package":
|
||||
case normalize(e.key):
|
||||
of "name":
|
||||
result.name = e.value
|
||||
of "version":
|
||||
result.version = e.value
|
||||
of "author":
|
||||
result.author = e.value
|
||||
of "category":
|
||||
result.category = e.value
|
||||
of "description":
|
||||
result.desc = e.value
|
||||
of "homepage":
|
||||
result.homepage = e.value
|
||||
of "license":
|
||||
result.license = e.value
|
||||
else:
|
||||
p.parseErr("Unknown key: " & e.key)
|
||||
of "library":
|
||||
case normalize(e.key)
|
||||
of "depends":
|
||||
result.depends = e.value.parseDepends()
|
||||
of "files":
|
||||
result.files = e.value.parseList()
|
||||
of "exposedmodules":
|
||||
result.modules = e.value.parseList()
|
||||
else:
|
||||
p.parseErr("Unknown key: " & e.key)
|
||||
of "exe":
|
||||
case normalize(e.key)
|
||||
of "depends":
|
||||
result.depends = e.value.parseDepends()
|
||||
of "files":
|
||||
result.files = e.value.parseList()
|
||||
of "exe":
|
||||
result.exeFile = e.value
|
||||
else:
|
||||
p.parseErr("Unknown key: " & e.key)
|
||||
|
||||
else:
|
||||
p.parseErr("Unknown section: " & section)
|
||||
|
||||
of cfgSectionStart:
|
||||
section = normalize(e.section)
|
||||
case normalize(e.section):
|
||||
of "library":
|
||||
result.library = True
|
||||
of "exe":
|
||||
result.executable = True
|
||||
of "package":
|
||||
nil
|
||||
else:
|
||||
p.parseErr("Unknown section: " & section)
|
||||
|
||||
of cfgError:
|
||||
p.parseErr(e.msg)
|
||||
|
||||
of cfgOption:
|
||||
p.parseErr("Unknown option: " & e.key)
|
||||
|
||||
close(p)
|
||||
else:
|
||||
raise newException(EIO, "Cannot open " & file)
|
||||
|
||||
proc isEmpty(s: string): Bool = return s == ""
|
||||
|
||||
proc verify*(proj: TProject): string =
|
||||
## Checks whether the required fields have been specified.
|
||||
if isEmpty(proj.name) or isEmpty(proj.version) or isEmpty(proj.author) or
|
||||
isEmpty(proj.category) or isEmpty(proj.desc):
|
||||
return "Missing required fields."
|
||||
elif proj.library == false and proj.executable == false:
|
||||
return "Either a valid Library needs to be specified or a valid Bin."
|
||||
elif proj.library == true and proj.modules.len() == 0:
|
||||
return "A valid library needs at least one ExposedModule listed."
|
||||
# TODO: Rules for Bin.
|
||||
|
||||
return ""
|
||||
|
||||
when isMainModule:
|
||||
for i in items(parseList("test, asdasd >sda; jsj, kk >>, sd")):
|
||||
echo(i)
|
||||
var project = parseBabel("babel.babel")
|
||||
echo project.library
|
||||
echo()
|
||||
var o = ""
|
||||
echo skipUntil("nimrod >= 0.8.10", " ", o)
|
||||
echo(o)
|
||||
|
|
@ -3,15 +3,18 @@ Babel is a work in progress package manager for Nimrod.
|
|||
|
||||
## Babel's folder structure
|
||||
Babel stores everything that has been installed in ~/.babel on Unix systems and
|
||||
in your $home/babel on Windows. Libraries are installed in lib/ in folders
|
||||
which names contain the name of the package, the folders
|
||||
contain the modules and any other files that the package wished to install.
|
||||
Applications are installed into bin/. There is also a packages/ directory which
|
||||
contains all the packages' .babel files.
|
||||
in your $home/babel on Windows. Libraries are stored in ~/.babel/libs.
|
||||
|
||||
## Libraries
|
||||
Libraries should contain a ``ProjectName.nim`` file, this file will be copied
|
||||
to ~/.babel/libs/ProjectName.nim allowing anyone to import it by doing
|
||||
``import ProjectName``. Any private files should be placed, by convention, in
|
||||
a ``private`` folder, these are files which the user of your library should not
|
||||
be using. Every other file and folder will be copied to ~/.babel/libs/ProjectName/.
|
||||
|
||||
## Contribution
|
||||
If you would like to help, feel free to fork and make any additions you see
|
||||
fit and then send a pull request.
|
||||
If you have any questions about the project you can ask me directly on github,
|
||||
ask on the nimrod [forum](http://force7.de/heimdall), or ask on Freenode in
|
||||
the #nimrod channel.
|
||||
ask on the nimrod [forum](http://forum.nimrod-code.org), or ask on Freenode in
|
||||
the #nimrod channel.
|
||||
172
version.nim
172
version.nim
|
|
@ -1,172 +0,0 @@
|
|||
## Module for handling versions and version ranges such as ``>= 1.0 & <= 1.5``
|
||||
import strutils
|
||||
type
|
||||
TVersion* = distinct string
|
||||
|
||||
TVersionRangeEnum* = enum
|
||||
verLater, # > V
|
||||
verEarlier, # < V
|
||||
verEqLater, # >= V -- Equal or later
|
||||
verEqEarlier, # <= V -- Equal or earlier
|
||||
verIntersect, # > V & < V
|
||||
verEq, # V
|
||||
verAny # *
|
||||
|
||||
PVersionRange* = ref TVersionRange
|
||||
TVersionRange* = object
|
||||
case kind*: TVersionRangeEnum
|
||||
of verLater, verEarlier, verEqLater, verEqEarlier, verEq:
|
||||
ver*: TVersion
|
||||
of verIntersect:
|
||||
verILeft, verIRight: PVersionRange
|
||||
of verAny:
|
||||
nil
|
||||
|
||||
EParseVersion = object of EInvalidValue
|
||||
|
||||
proc newVersion*(ver: string): TVersion = return TVersion(ver)
|
||||
|
||||
proc `$`*(ver: TVersion): String {.borrow.}
|
||||
|
||||
proc `<`*(ver: TVersion, ver2: TVersion): Bool =
|
||||
var sVer = string(ver).split('.')
|
||||
var sVer2 = string(ver2).split('.')
|
||||
for i in 0..max(sVer.len, sVer2.len)-1:
|
||||
if i > sVer.len-1:
|
||||
return True
|
||||
elif i > sVer2.len-1:
|
||||
return False
|
||||
|
||||
var sVerI = parseInt(sVer[i])
|
||||
var sVerI2 = parseInt(sVer2[i])
|
||||
if sVerI < sVerI2:
|
||||
return True
|
||||
elif sVerI == sVerI2:
|
||||
nil
|
||||
else:
|
||||
return False
|
||||
|
||||
proc `==`*(ver: TVersion, ver2: TVersion): Bool {.borrow.}
|
||||
|
||||
proc `<=`*(ver: TVersion, ver2: TVersion): Bool =
|
||||
return (ver == ver2) or (ver < ver2)
|
||||
|
||||
proc withinRange*(ver: TVersion, ran: PVersionRange): Bool =
|
||||
case ran.kind
|
||||
of verLater:
|
||||
return ver > ran.ver
|
||||
of verEarlier:
|
||||
return ver < ran.ver
|
||||
of verEqLater:
|
||||
return ver >= ran.ver
|
||||
of verEqEarlier:
|
||||
return ver <= ran.ver
|
||||
of verEq:
|
||||
return ver == ran.ver
|
||||
of verIntersect:
|
||||
return withinRange(ver, ran.verILeft) and withinRange(ver, ran.verIRight)
|
||||
of verAny:
|
||||
return True
|
||||
|
||||
proc makeRange*(version: string, op: string): PVersionRange =
|
||||
new(result)
|
||||
case op
|
||||
of ">":
|
||||
result.kind = verLater
|
||||
of "<":
|
||||
result.kind = verEarlier
|
||||
of ">=":
|
||||
result.kind = verEqLater
|
||||
of "<=":
|
||||
result.kind = verEqEarlier
|
||||
else:
|
||||
raise newException(EParseVersion, "Invalid operator: " & op)
|
||||
result.ver = TVersion(version)
|
||||
|
||||
proc parseVersionRange*(s: string): PVersionRange =
|
||||
# >= 1.5 & <= 1.8
|
||||
new(result)
|
||||
|
||||
var i = 0
|
||||
var op = ""
|
||||
var version = ""
|
||||
while True:
|
||||
case s[i]
|
||||
of '>', '<', '=':
|
||||
op.add(s[i])
|
||||
of '&':
|
||||
result.kind = verIntersect
|
||||
result.verILeft = makeRange(version, op)
|
||||
|
||||
# Parse everything after &
|
||||
# Recursion <3
|
||||
result.verIRight = parseVersionRange(copy(s, i + 1))
|
||||
|
||||
# Disallow more than one verIntersect. It's pointless and could lead to
|
||||
# major unknown mistakes.
|
||||
if result.verIRight.kind == verIntersect:
|
||||
raise newException(EParseVersion,
|
||||
"Having more than one `&` in a version range is pointless")
|
||||
|
||||
break
|
||||
|
||||
of '0'..'9', '.':
|
||||
version.add(s[i])
|
||||
|
||||
of '\0':
|
||||
result = makeRange(version, op)
|
||||
break
|
||||
|
||||
of ' ':
|
||||
nil # Ignore whitespace
|
||||
|
||||
else:
|
||||
raise newException(EParseVersion, "Unexpected char in version range: " & s[i])
|
||||
inc(i)
|
||||
|
||||
proc `$`*(verRange: PVersionRange): String =
|
||||
case verRange.kind
|
||||
of verLater:
|
||||
result = "> "
|
||||
of verEarlier:
|
||||
result = "< "
|
||||
of verEqLater:
|
||||
result = ">= "
|
||||
of verEqEarlier:
|
||||
result = "<= "
|
||||
of verEq:
|
||||
result = ""
|
||||
of verIntersect:
|
||||
result = $verRange.verILeft & " & " & $verRange.verIRight
|
||||
of verAny:
|
||||
return "Any"
|
||||
|
||||
result.add(string(verRange.ver))
|
||||
|
||||
proc newVRAny*(): PVersionRange =
|
||||
new(result)
|
||||
result.kind = verAny
|
||||
|
||||
proc newVREarlier*(ver: String): PVersionRange =
|
||||
new(result)
|
||||
result.kind = verEarlier
|
||||
result.ver = newVersion(ver)
|
||||
|
||||
proc newVREq*(ver: string): PVersionRange =
|
||||
new(result)
|
||||
result.kind = verEq
|
||||
result.ver = newVersion(ver)
|
||||
|
||||
when isMainModule:
|
||||
assert(newVersion("1.0") < newVersion("1.4"))
|
||||
assert(newVersion("1.0.1") > newVersion("1.0"))
|
||||
assert(newVersion("1.0.6") <= newVersion("1.0.6"))
|
||||
|
||||
var inter1 = parseVersionRange(">= 1.0 & <= 1.5 & > 1.1 ")
|
||||
|
||||
assert(not withinRange(newVersion("1.5.1"), inter1))
|
||||
assert(withinRange(newVersion("1.0.2.3.4.5.6.7.8.9.10.11.12"), inter1))
|
||||
|
||||
assert(newVersion("1") == newVersion("1"))
|
||||
|
||||
echo("Everything works! Assuming that you didn't compile without assertions...")
|
||||
Loading…
Add table
Add a link
Reference in a new issue