diff --git a/babel.nim b/babel.nim index 24f90c6..07da2b4 100644 --- a/babel.nim +++ b/babel.nim @@ -136,7 +136,6 @@ proc prompt(options: TOptions, question: string): bool = let babelDir = getHomeDir() / ".babel" let pkgsDir = babelDir / "pkgs" let binDir = babelDir / "bin" -let nimVer = getNimrodVersion() proc update(url: string = defaultPackageURL) = ## Downloads the package list from the specified URL. @@ -227,8 +226,8 @@ proc copyFilesRec(origDir, currentDir, dest: string, pkgInfo: TPackageInfo) = copyFileD(pkgInfo.mypath, changeRoot(pkgInfo.mypath.splitFile.dir, dest, pkgInfo.mypath)) -proc install(packages: seq[String], verRange: PVersionRange, options: TOptions, - doPrompt = true): string {.discardable.} +proc install(packages: seq[tuple[name: string, verRange: PVersionRange]], + options: TOptions, doPrompt = true): string {.discardable.} proc processDeps(pkginfo: TPackageInfo, options: TOptions): seq[string] = ## Verifies and installs dependencies. ## @@ -237,6 +236,7 @@ proc processDeps(pkginfo: TPackageInfo, options: TOptions): seq[string] = let pkglist = getInstalledPkgs(pkgsDir) for dep in pkginfo.requires: if dep.name == "nimrod": + let nimVer = getNimrodVersion() if not withinRange(nimVer, dep.ver): quit("Unsatisfied dependency: " & dep.name & " (" & $dep.ver & ")") else: @@ -244,7 +244,7 @@ proc processDeps(pkginfo: TPackageInfo, options: TOptions): seq[string] = var pkg: TPackageInfo if not findPkg(pkglist, dep, pkg): echo("None found, installing...") - let dest = install(@[dep.name], dep.ver, options) + let dest = install(@[(dep.name, dep.ver)], options) result.add(dest) else: echo("Dependency already satisfied.") @@ -328,8 +328,8 @@ proc downloadPkg(pkg: TPackage, verRange: PVersionRange): string = doDownload(pkg, downloadDir, verRange) result = downloadDir -proc install(packages: seq[String], verRange: PVersionRange, options: TOptions, - doPrompt = true): string = +proc install(packages: seq[tuple[name: string, verRange: PVersionRange]], + options: TOptions, doPrompt = true): string = if packages == @[]: result = installFromDir(getCurrentDir(), false, options) else: @@ -337,19 +337,20 @@ proc install(packages: seq[String], verRange: PVersionRange, options: TOptions, if doPrompt and options.prompt("Local packages.json not found, download it from internet?"): update() - install(packages, verRange, options, false) + install(packages, options, false) else: quit("Please run babel update.", QuitFailure) - for p in packages: + + for pv in packages: var pkg: TPackage - if getPackage(p, babelDir / "packages.json", pkg): - let downloadDir = downloadPkg(pkg, verRange) + if getPackage(pv.name, babelDir / "packages.json", pkg): + let downloadDir = downloadPkg(pkg, pv.verRange) result = installFromDir(downloadDir, false, options) else: if doPrompt and - options.prompt(p & " not found in local packages.json, check internet for updated packages?"): + options.prompt(pv.name & " not found in local packages.json, check internet for updated packages?"): update() - install(@[p], verRange, options, false) + install(@[pv], options, false) else: raise newException(EBabel, "Package not found.") @@ -437,8 +438,15 @@ proc doAction(options: TOptions) = else: update() of ActionInstall: - # TODO: Allow user to specify version. - install(options.action.optionalName, PVersionRange(kind: verAny), options) + var installList: seq[tuple[name: string, verRange: PVersionRange]] = @[] + for name in options.action.optionalName: + if '#' in name: + let i = find(name, '#') + installList.add((name[0 .. i-1], name[i .. -1].parseVersionRange())) + else: + installList.add((name, PVersionRange(kind: verAny))) + + install(installList, options) of ActionSearch: search(options) of ActionList: diff --git a/download.nim b/download.nim index 8a7567b..2f83a38 100644 --- a/download.nim +++ b/download.nim @@ -36,14 +36,16 @@ proc doPull(meth: TDownloadMethod, downloadDir: string) = cd downloadDir: doCmd("hg pull") -proc doClone(meth: TDownloadMethod, url, downloadDir: string, branch = "") = +proc doClone(meth: TDownloadMethod, url, downloadDir: string, branch = "", tip = true) = let branchArg = if branch == "": "" else: "-b " & branch & " " case meth of TDownloadMethod.Git: + let depthArg = if tip: "--depth 1 " else: "" # TODO: Get rid of the annoying 'detached HEAD' message somehow? - doCmd("git clone --depth 1 " & branchArg & url & " " & downloadDir) + doCmd("git clone " & depthArg & branchArg & url & " " & downloadDir) of TDownloadMethod.Hg: - doCmd("hg clone -r tip " & branchArg & url & " " & downloadDir) + let tipArg = if tip: "-r tip " else: "" + doCmd("hg clone " & tipArg & branchArg & url & " " & downloadDir) proc getTagsList(dir: string, meth: TDownloadMethod): seq[string] = cd dir: @@ -100,57 +102,70 @@ proc getDownloadMethod*(meth: string): TDownloadMethod = else: raise newException(EBabel, "Invalid download method: " & meth) +proc getHeadName*(meth: TDownloadMethod): string = + ## Returns the name of the download method specific head. i.e. for git + ## it's ``head`` for hg it's ``tip``. + case meth + of TDownloadMethod.Git: "head" + of TDownloadMethod.Hg: "tip" + proc doDownload*(pkg: TPackage, downloadDir: string, verRange: PVersionRange) = + template getLatestByTag(meth: stmt): stmt {.dirty, immediate.} = + echo("Found tags...") + # Find latest version that fits our ``verRange``. + var latest = findLatest(verRange, versions) + ## Note: HEAD is not used when verRange.kind is verAny. This is + ## intended behaviour, the latest tagged version will be used in this case. + if latest.tag != "": + meth + + proc verifyHead() = + ## Makes sure that HEAD satisfies the requested version range. + 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) + let downMethod = pkg.downloadMethod.getDownloadMethod() echo "Downloading ", pkg.name, " using ", downMethod, "..." - - case downMethod - of TDownloadMethod.Git: - # For Git we have to query the repo remotely for its tags. This is - # necessary as cloning with a --depth of 1 removes all tag info. - let versions = getTagsListRemote(pkg.url, downMethod).getVersionList() - if versions.len > 0: - echo("Found tags...") - var latest = findLatest(verRange, versions) - ## Note: HEAD is not used when verRange.kind is verAny. This is - ## intended behaviour, the latest tagged version will be used in this case. - if latest.tag != "": - echo("Cloning latest tagged version: ", latest.tag) - removeDir(downloadDir) - doClone(downMethod, pkg.url, downloadDir, latest.tag) - else: - # If no commits have been tagged on the repo we just clone HEAD. - removeDir(downloadDir) + removeDir(downloadDir) + if verRange.kind == verSpecial: + # We want a specific commit/branch/tag here. + if verRange.spe == newSpecial(getHeadName(downMethod)): doClone(downMethod, pkg.url, downloadDir) # Grab HEAD. - if verRange.kind != verAny: - # Make sure that HEAD satisfies the requested version range. - 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) - of TDownloadMethod.Hg: - removeDir(downloadDir) - doClone(downMethod, pkg.url, downloadDir) - let versions = getTagsList(downloadDir, downMethod).getVersionList() - - if versions.len > 0: - echo("Found tags...") - var latest = findLatest(verRange, versions) - ## Note: HEAD is not used when verRange.kind is verAny. This is - ## intended behaviour, the latest tagged version will be used in this case. - if latest.tag != "": - echo("Switching to latest tagged version: ", latest.tag) - doCheckout(downMethod, downloadDir, 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: + # We don't know if we got a commit hash or a branch here, and + # we can't clone a specific commit (with depth 1) according to: + # http://stackoverflow.com/a/7198956/492186 + doClone(downMethod, pkg.url, downloadDir, tip = false) + doCheckout(downMethod, downloadDir, $verRange.spe) + else: + case downMethod + of TDownloadMethod.Git: + # For Git we have to query the repo remotely for its tags. This is + # necessary as cloning with a --depth of 1 removes all tag info. + let versions = getTagsListRemote(pkg.url, downMethod).getVersionList() + if versions.len > 0: + getLatestByTag: + echo("Cloning latest tagged version: ", latest.tag) + doClone(downMethod, pkg.url, downloadDir, latest.tag) + else: + # If no commits have been tagged on the repo we just clone HEAD. + doClone(downMethod, pkg.url, downloadDir) # Grab HEAD. + if verRange.kind != verAny: + verifyHead() + of TDownloadMethod.Hg: + doClone(downMethod, pkg.url, downloadDir) + let versions = getTagsList(downloadDir, downMethod).getVersionList() + + if versions.len > 0: + getLatestByTag: + echo("Switching to latest tagged version: ", latest.tag) + doCheckout(downMethod, downloadDir, latest.tag) + elif verRange.kind != verAny: + verifyHead() proc echoPackageVersions*(pkg: TPackage) = let downMethod = pkg.downloadMethod.getDownloadMethod() diff --git a/packageinfo.nim b/packageinfo.nim index 265ca8b..a42a673 100644 --- a/packageinfo.nim +++ b/packageinfo.nim @@ -72,6 +72,10 @@ proc parseRequires(req: string): tuple[name: string, ver: PVersionRange] = var i = skipUntil(req, whitespace) result.name = req[0 .. i].strip result.ver = parseVersionRange(req[i .. -1]) + elif '#' in req: + var i = skipUntil(req, {'#'}) + result.name = req[0 .. i-1] + result.ver = parseVersionRange(req[i .. -1]) else: result.name = req.strip result.ver = PVersionRange(kind: verAny) diff --git a/version.nim b/version.nim index a8c629f..ffecae5 100644 --- a/version.nim +++ b/version.nim @@ -5,6 +5,7 @@ import strutils, tables, hashes, parseutils type TVersion* = distinct string + TSpecial* = distinct string TVersionRangeEnum* = enum verLater, # > V @@ -13,13 +14,16 @@ type verEqEarlier, # <= V -- Equal or earlier verIntersect, # > V & < V verEq, # V - verAny # * + verAny, # * + verSpecial # #head PVersionRange* = ref TVersionRange TVersionRange* = object case kind*: TVersionRangeEnum of verLater, verEarlier, verEqLater, verEqEarlier, verEq: ver*: TVersion + of verSpecial: + spe*: TSpecial of verIntersect: verILeft, verIRight: PVersionRange of verAny: @@ -28,11 +32,16 @@ type EParseVersion* = object of EInvalidValue proc newVersion*(ver: string): TVersion = return TVersion(ver) +proc newSpecial*(spe: string): TSpecial = return TSpecial(spe) proc `$`*(ver: TVersion): String {.borrow.} proc hash*(ver: TVersion): THash {.borrow.} +proc `$`*(ver: TSpecial): String {.borrow.} + +proc hash*(ver: TSpecial): THash {.borrow.} + proc `<`*(ver: TVersion, ver2: TVersion): Bool = var sVer = string(ver).split('.') var sVer2 = string(ver2).split('.') @@ -65,6 +74,9 @@ proc `==`*(ver: TVersion, ver2: TVersion): Bool = else: return False +proc `==`*(spe: TSpecial, spe2: TSpecial): bool = + return ($spe).toLower() == ($spe2).toLower() + proc `<=`*(ver: TVersion, ver2: TVersion): Bool = return (ver == ver2) or (ver < ver2) @@ -80,14 +92,28 @@ proc withinRange*(ver: TVersion, ran: PVersionRange): Bool = return ver <= ran.ver of verEq: return ver == ran.ver + of verSpecial: + return False of verIntersect: return withinRange(ver, ran.verILeft) and withinRange(ver, ran.verIRight) of verAny: return True +proc withinRange*(spe: TSpecial, ran: PVersionRange): Bool = + case ran.kind + of verLater, verEarlier, verEqLater, verEqEarlier, verEq, verIntersect: + return False + of verSpecial: + return spe == ran.spe + of verAny: + return True + proc contains*(ran: PVersionRange, ver: TVersion): bool = return withinRange(ver, ran) +proc contains*(ran: PVersionRange, spe: TSpecial): bool = + return withinRange(spe, ran) + proc makeRange*(version: string, op: string): PVersionRange = new(result) if version == "": @@ -110,6 +136,10 @@ proc makeRange*(version: string, op: string): PVersionRange = proc parseVersionRange*(s: string): PVersionRange = # >= 1.5 & <= 1.8 new(result) + if s[0] == '#': + result.kind = verSpecial + result.spe = s[1 .. -1].TSpecial + return var i = 0 var op = "" @@ -163,6 +193,8 @@ proc `$`*(verRange: PVersionRange): String = result = "<= " of verEq: result = "" + of verSpecial: + return "#" & $verRange.spe of verIntersect: return $verRange.verILeft & " & " & $verRange.verIRight of verAny: @@ -212,6 +244,7 @@ when isMainModule: doAssert(newVersion("1") == newVersion("1")) doAssert(newVersion("1.0.2.4.6.1.2.123") == newVersion("1.0.2.4.6.1.2.123")) doAssert(newVersion("1.0.2") != newVersion("1.0.2.4.6.1.2.123")) + doAssert(newVersion("1.0.3") != newVersion("1.0.2")) doAssert(not (newVersion("") < newVersion("0.0.0"))) doAssert(newVersion("") < newVersion("1.0.0")) @@ -220,8 +253,19 @@ when isMainModule: var versions = toTable[TVersion, string]({newVersion("0.1.1"): "v0.1.1", newVersion("0.2.3"): "v0.2.3", newVersion("0.5"): "v0.5"}) doAssert findLatest(parseVersionRange(">= 0.1 & <= 0.4"), versions) == (newVersion("0.2.3"), "v0.2.3") + # TODO: These fail. + #doAssert newVersion("0.1-rc1") < newVersion("0.2") + #doAssert newVersion("0.1-rc1") < newVersion("0.1") + + # Special tests + doAssert newSpecial("ab26sgdt362") != newSpecial("ab26saggdt362") + doAssert newSpecial("ab26saggdt362") == newSpecial("ab26saggdt362") + doAssert newSpecial("head") == newSpecial("HEAD") + doAssert newSpecial("head") == newSpecial("head") + + var sp = parseVersionRange("#ab26sgdt362") + doAssert newSpecial("ab26sgdt362") in sp + doAssert newSpecial("ab26saggdt362") notin sp - doAssert newVersion("0.1-rc1") < newVersion("0.2") - doAssert newVersion("0.1-rc1") < newVersion("0.1") echo("Everything works!") \ No newline at end of file