Compare commits

...

3 Commits

Author SHA1 Message Date
Ehmry - dffb461f94 Remove dead inmports 2023-10-08 14:36:34 +01:00
Ehmry - f978c53a29 Use fetchurl for web forges 2023-10-08 14:30:29 +01:00
Ehmry - 2e2129e873 Add excludes to examples in readme 2023-10-08 14:26:58 +01:00
7 changed files with 134 additions and 505 deletions

View File

@ -12,13 +12,16 @@ nim_lk > lock.json
These lock files contain Nix FOD store paths that can be converted to `nim.cfg` files.
```nix
{ pkgs ? import <nixpkgs> { }, lockPath }:
{ pkgs ? import <nixpkgs> { }, lockPath, excludes ? [ ] }:
let inherit (pkgs) lib;
in lib.pipe lockPath [
builtins.readFile
builtins.fromJSON
(builtins.getAttr "depends")
(map ({ path, srcDir, ... }: ''path:"${path}/${srcDir}"''))
in with builtins;
lib.pipe lockPath [
readFile
fromJSON
(getAttr "depends")
(filter ({ packages, ... }: !(any (pkg: elem pkg excludes) packages)))
(map ({ path, srcDir, ... }: ''path:"${storePath path}/${srcDir}"''))
lib.strings.concatLines
(pkgs.writeText "nim.cfg")
]
@ -28,15 +31,17 @@ I manage all this with [Tup](https://gittup.org/tup).
```
# Tuprules.tup above my Nim projects
!nim_lk = |> nim_lk > %o |> lock.json
!nim_lk = |> nim_lk | jq --compact-output --sort-keys > %o |> lock.json
NIXEXPRS_DIR = $(TUP_CWD)/nixexprs
!nim_cfg = |> nix build --file $(NIXEXPRS_DIR)/configure.nix --argstr lockPath `pwd`/%f --out-link %o |> nim.cfg
!nim_cfg = |> nix build --file $(NIXEXPRS_DIR)/configure.nix --out-link %o --arg lockPath `pwd`/%f --arg excludes '[$(NIM_LOCK_EXCLUDES)]' |> nim.cfg
```
```
# Tupfile in a Nim project
include_rules
# Omit the foobar library from nim.cfg.
NIM_LOCK_EXCLUDES += "foobar"
: |> !nim_lk |> | ./<lock>
: lock.json |> !nim_cfg |> | ./<lock>
```

2
Tupfile Normal file
View File

@ -0,0 +1,2 @@
include_rules
: |> !nim_lk |>

1
lock.json Normal file
View File

@ -0,0 +1 @@
{"depends":[]}

View File

@ -3,6 +3,6 @@ bin = @["nim_lk"]
description = "Tool for generating Nim lockfiles"
license = "BSD-3-Clause"
srcDir = "src"
version = "20231004"
version = "20231008"
requires "nim >= 2.0.0"

View File

@ -1,14 +1,13 @@
import private/nix
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import nimblepkg/common,
nimblepkg/download,
nimblepkg/packageinfo,
nimblepkg/options,
nimblepkg/version,
nimblepkg/packageinfo,
nimblepkg/packageparser,
nimblepkg/cli
nimblepkg/version
import std/[algorithm, deques, httpclient, json, monotimes, os, osproc, parseutils, random, sequtils, streams, strutils, tables, times, uri]
import std/[algorithm, deques, httpclient, json, os, osproc, parseutils, streams, strutils, uri]
const githubPackagesUrl =
"https://raw.githubusercontent.com/nim-lang/packages/master/packages.json"
@ -28,23 +27,44 @@ func isGitUrl(uri: Uri): bool =
uri.scheme == "git" or
uri.scheme.startsWith("git+")
proc gitLsRemote(url: string): seq[tuple[tag: string, rev: string]] =
var lines = execProcess(
"git",
args = ["ls-remote", "--tags", url],
options = {poUsePath},
)
result.setLen(lines.countLines.pred)
var off = 0
for i in 0..result.high:
off.inc parseUntil(lines, result[i].rev, {'\x09'}, off)
off.inc skipWhiteSpace(lines, off)
off.inc skipUntil(lines, '/', off).succ
off.inc skipUntil(lines, '/', off).succ
off.inc parseUntil(lines, result[i].tag, {'\x0a'}, off).succ
proc isWebGitForgeUrl(uri: Uri): bool =
result =
case uri.hostname
of "github.com", "git.sr.ht", "codeberg.org": true
else: false
if not result:
stderr.writeLine "not a web forge hostname: '", uri.hostname, "'"
proc startProcess(cmd: string; cmdArgs: varargs[string]): Process =
startProcess(cmd, args = cmdArgs, options = {poUsePath})
proc gitLsRemote(url: string; withTags: bool): seq[tuple[tag: string, rev: string]] =
var line, rev, refer: string
var process =
if withTags:
startProcess("git", "ls-remote", "--tags", url)
else:
startProcess("git", "ls-remote", url)
while process.running:
while process.outputStream.readLine(line):
var off = 0
off.inc parseUntil(line, rev, Whitespace, off)
off.inc skipWhile(line, Whitespace, off)
refer = line[off..line.high]
const
refsTags = "refs/tags/"
headsTags = "refs/heads/"
if refer.startsWith(refsTags):
refer.removePrefix(refsTags)
elif refer.startsWith(headsTags):
refer.removePrefix(headsTags)
result.add((refer, rev,))
stderr.write(process.errorStream.readAll)
close(process)
proc matchRev(url: string; wanted: VersionRange): tuple[tag: string, rev: string] =
let pairs = gitLsRemote(url)
let withTags = wanted.kind != verAny
let pairs = gitLsRemote(url, withTags)
var resultVersion: Version
for (tag, rev) in pairs:
var tagVer = Version(tag)
@ -53,23 +73,25 @@ proc matchRev(url: string; wanted: VersionRange): tuple[tag: string, rev: string
result = (tag, rev)
if result.rev == "" and pairs.len > 0:
result = pairs[pairs.high]
doAssert result.rev != ""
proc collectMetadata(data: JsonNode; options: Options) =
proc collectMetadata(data: JsonNode) =
let storePath = data["path"].getStr
var packageNames = newJArray()
var packageNames = newSeq[string]()
for (kind, path) in walkDir(storePath):
if kind in {pcFile, pcLinkToFile} and path.endsWith(".nimble"):
var (dir, name, ext) = splitFile(path)
packageNames.add %name
var (_, name, _) = splitFile(path)
packageNames.add name
if packageNames.len == 0:
quit("no .nimble files found in " & storePath)
data["packages"] = packageNames
sort(packageNames)
data["packages"] = %packageNames
var
nimbleFilePath = findNimbleFile(storePath, true)
pkg = readPackageInfo(nimbleFilePath, options)
pkg = readPackageInfo(nimbleFilePath, parseCmdLine())
data["srcDir"] = %pkg.srcDir
proc prefetchGit(uri: Uri; version: VersionRange; options: Options): JsonNode =
proc prefetchGit(uri: Uri; version: VersionRange): JsonNode =
var
uri = uri
subdir = ""
@ -80,10 +102,8 @@ proc prefetchGit(uri: Uri; version: VersionRange; options: Options): JsonNode =
uri.query = ""
let url = $uri
let (tag, rev) = matchRev(url, version)
var args = @["--quiet", "--fetch-submodules", "--url", url]
if rev != "":
args.add "--rev"
args.add rev
var args = @["--quiet", "--fetch-submodules", "--url", url, "--rev", rev]
stderr.writeLine "prefetch ", url
let dump = execProcess(
"nix-prefetch-git",
args = args,
@ -97,7 +117,50 @@ proc prefetchGit(uri: Uri; version: VersionRange; options: Options): JsonNode =
result["method"] = %"git"
if tag != "":
result["ref"] = %tag
collectMetadata(result, options)
collectMetadata(result)
proc prefetchGitForge(uri: Uri; version: VersionRange): JsonNode =
result = newJObject()
var
uri = uri
subdir = ""
uri.scheme.removePrefix("git+")
if uri.query != "":
if uri.query.startsWith("subdir="):
subdir = uri.query[7 .. ^1]
uri.query = ""
var url = $uri
let (tag, rev) = matchRev(url, version)
uri.scheme = "https"
uri.path.removeSuffix(".git")
uri.path.add("/archive/")
uri.path.add(rev)
uri.path.add(".tar.gz")
url = $uri
stderr.writeLine "prefetch ", url
var lines = execProcess(
"nix-prefetch-url",
args = @[url, "--type", "sha256", "--print-path", "--unpack", "--name", "source"],
options = {poUsePath})
var
hash, storePath: string
off: int
off.inc parseUntil(lines, hash, {'\n'}, off).succ
off.inc parseUntil(lines, storePath, {'\n'}, off).succ
doAssert off == lines.len, "unrecognized nix-prefetch-url output:\n" & lines
result["method"] = %"fetchurl"
result["path"] = %storePath
result["rev"] = %rev
result["sha256"] = %hash
result["url"] = %url
if subdir != "":
result["subdir"] = %* subdir
if tag != "":
result["ref"] = %tag
collectMetadata(result)
proc containsPackageUri(lockAttrs: JsonNode; pkgUri: string): bool =
for e in lockAttrs.items:
@ -110,10 +173,10 @@ proc containsPackage(lockAttrs: JsonNode; pkgName: string): bool =
if pkgName == other.getStr:
return true
proc collectRequires(pending: var Deque[PkgTuple]; options: Options; pkgPath: string) =
proc collectRequires(pending: var Deque[PkgTuple]; pkgPath: string) =
var
nimbleFilePath = findNimbleFile(pkgPath, true)
pkg = readPackageInfo(nimbleFilePath, options)
pkg = readPackageInfo(nimbleFilePath, parseCmdLine())
for pair in pkg.requires:
if pair.name != "nim" and pair.name != "compiler":
pending.addLast(pair)
@ -149,12 +212,12 @@ proc getPackgeUri(name: string): tuple[uri: string, meth: string] =
quit("Failed to parse shit JSON " & $e)
inc i
proc generateLockfile(options: Options): JsonNode =
proc generateLockfile(): JsonNode =
result = newJObject()
var
deps = newJArray()
pending: Deque[PkgTuple]
collectRequires(pending, options, getCurrentDir())
collectRequires(pending, getCurrentDir())
while pending.len > 0:
let batchLen = pending.len
for i in 1..batchLen:
@ -167,34 +230,36 @@ proc generateLockfile(options: Options): JsonNode =
if not deps.containsPackage(pkg.name):
pending.addLast(pkg)
elif not deps.containsPackageUri(pkg.name):
if uri.isGitUrl:
pkgData = prefetchGit(uri, pkg.ver, options)
if uri.isWebGitForgeUrl:
pkgData = prefetchGitForge(uri, pkg.ver)
elif uri.isGitUrl:
pkgData = prefetchGit(uri, pkg.ver)
else:
quit("unhandled URI " & $uri)
collectRequires(pending, options, pkgData["path"].getStr)
collectRequires(pending, pkgData["path"].getStr)
deps.add pkgData
if batchLen == pending.len:
var
pkgData: JsonNode
pkg = pending.popFirst()
info = getPackgeUri(pkg.name)
case info.meth
of "git":
stderr.writeLine "prefetch ", info.uri
var pkgData = prefetchGit(parseUri info.uri, pkg.ver, options)
collectRequires(pending, options, pkgData["path"].getStr)
deps.add pkgData
uri = parseUri info.uri
if uri.isWebGitForgeUrl:
pkgData = prefetchGitForge(uri, pkg.ver)
else:
quit("unhandled fetch method " & $info.meth & " for " & info.uri)
case info.meth
of "git":
pkgData = prefetchGit(uri, pkg.ver)
else:
quit("unhandled fetch method " & $info.meth & " for " & info.uri)
collectRequires(pending, pkgData["path"].getStr)
deps.add pkgData
sort(deps.elems)
result["depends"] = deps
proc main =
var options = parseCmdLine()
# parse nimble options, not recommended
if options.action.typ != actionCustom:
options.action = Action(typ: actionCustom)
var lockInfo = generateLockfile(options)
var lockInfo = generateLockfile()
stdout.writeLine lockInfo
main()

View File

@ -1,324 +0,0 @@
# Copyright (C) Dominik Picheta. All rights reserved.
# BSD License. Look at license.txt for more info.
import parseutils, os, osproc, strutils, tables, pegs, uri, json
import packageinfo, packageparser, version, tools, common, options, cli
from algorithm import SortOrder, sorted
from sequtils import toSeq, filterIt, map
type
DownloadMethod* {.pure.} = enum
git = "git", hg = "hg"
proc getSpecificDir(meth: DownloadMethod): string {.used.} =
case meth
of DownloadMethod.git:
".git"
of DownloadMethod.hg:
".hg"
proc doCheckout(meth: DownloadMethod, downloadDir, branch: string) =
case meth
of DownloadMethod.git:
cd downloadDir:
# Force is used here because local changes may appear straight after a
# clone has happened. Like in the case of git on Windows where it
# messes up the damn line endings.
doCmd("git checkout --force " & branch)
doCmd("git submodule update --recursive")
of DownloadMethod.hg:
cd downloadDir:
doCmd("hg checkout " & branch)
proc doPull(meth: DownloadMethod, downloadDir: string) {.used.} =
case meth
of DownloadMethod.git:
doCheckout(meth, downloadDir, "")
cd downloadDir:
doCmd("git pull")
if existsFile(".gitmodules"):
doCmd("git submodule update")
of DownloadMethod.hg:
doCheckout(meth, downloadDir, "default")
cd downloadDir:
doCmd("hg pull")
proc doClone(meth: DownloadMethod, url, downloadDir: string, branch = "",
onlyTip = true) =
case meth
of DownloadMethod.git:
let
depthArg = if onlyTip: "--depth 1 " else: ""
branchArg = if branch == "": "" else: "-b " & branch & " "
doCmd("git clone --recursive " & depthArg & branchArg & url &
" " & downloadDir)
of DownloadMethod.hg:
let
tipArg = if onlyTip: "-r tip " else: ""
branchArg = if branch == "": "" else: "-b " & branch & " "
doCmd("hg clone " & tipArg & branchArg & url & " " & downloadDir)
proc getTagsList(dir: string, meth: DownloadMethod): seq[string] =
cd dir:
var output = execProcess("git tag")
case meth
of DownloadMethod.git:
output = execProcess("git tag")
of DownloadMethod.hg:
output = execProcess("hg tags")
if output.len > 0:
case meth
of DownloadMethod.git:
result = @[]
for i in output.splitLines():
if i == "": continue
result.add(i)
of DownloadMethod.hg:
result = @[]
for i in output.splitLines():
if i == "": continue
var tag = ""
discard parseUntil(i, tag, ' ')
if tag != "tip":
result.add(tag)
else:
result = @[]
proc getTagsListRemote*(url: string, meth: DownloadMethod): seq[string] =
var
url = url
uri = parseUri url
if uri.query != "":
uri.query = ""
url = $uri
result = @[]
case meth
of DownloadMethod.git:
var (output, exitCode) = doCmdEx("git ls-remote --tags " & url)
if exitCode != QuitSuccess:
raise newException(OSError, "Unable to query remote tags for " & url &
". Git returned: " & output)
for i in output.splitLines():
let refStart = i.find("refs/tags/")
# git outputs warnings, empty lines, etc
if refStart == -1: continue
let start = refStart+"refs/tags/".len
let tag = i[start .. i.len-1]
if not tag.endswith("^{}"): result.add(tag)
of DownloadMethod.hg:
# http://stackoverflow.com/questions/2039150/show-tags-for-remote-hg-repository
raise newException(ValueError, "Hg doesn't support remote tag querying.")
proc getVersionList*(tags: seq[string]): OrderedTable[Version, string] =
## Return an ordered table of Version -> git tag label. Ordering is
## in descending order with the most recent version first.
let taggedVers: seq[tuple[ver: Version, tag: string]] =
tags
.filterIt(it != "")
.map(proc(s: string): tuple[ver: Version, tag: string] =
# skip any chars before the version
let i = skipUntil(s, Digits)
# TODO: Better checking, tags can have any
# names. Add warnings and such.
result = (newVersion(s[i .. s.len-1]), s))
.sorted(proc(a, b: (Version, string)): int = cmp(a[0], b[0]),
SortOrder.Descending)
result = toOrderedTable[Version, string](taggedVers)
proc getDownloadMethod*(meth: string): DownloadMethod =
case meth
of "git": return DownloadMethod.git
of "hg", "mercurial": return DownloadMethod.hg
else:
raise newException(NimbleError, "Invalid download method: " & meth)
proc getHeadName*(meth: DownloadMethod): Version =
## Returns the name of the download method specific head. i.e. for git
## it's ``head`` for hg it's ``tip``.
case meth
of DownloadMethod.git: newVersion("#head")
of DownloadMethod.hg: newVersion("#tip")
proc checkUrlType*(url: string): DownloadMethod =
## Determines the download method based on the URL.
if doCmdEx("git ls-remote " & url).exitCode == QuitSuccess:
return DownloadMethod.git
elif doCmdEx("hg identify " & url).exitCode == QuitSuccess:
return DownloadMethod.hg
else:
raise newException(NimbleError, "Unable to identify url: " & url)
proc getUrlData*(url: string): (string, Table[string, string]) =
var uri = parseUri(url)
# TODO: use uri.parseQuery once it lands... this code is quick and dirty.
var subdir = ""
if uri.query.startsWith("subdir="):
subdir = uri.query[7 .. ^1]
uri.query = ""
return ($uri, {"subdir": subdir}.toTable())
proc isURL*(name: string): bool =
name.startsWith(peg" @'://' ")
proc doDownload(url: string, downloadDir: string, verRange: VersionRange,
downMethod: DownloadMethod,
options: Options): Version =
## Downloads the repository specified by ``url`` using the specified download
## method.
##
## Returns the version of the repository which has been downloaded.
template getLatestByTag(meth: untyped) {.dirty.} =
# 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 no tagged versions satisfy our range latest.tag will be "".
# We still clone in that scenario because we want to try HEAD in that case.
# https://github.com/nim-lang/nimble/issues/22
meth
if $latest.ver != "":
result = latest.ver
removeDir(downloadDir)
if verRange.kind == verSpecial:
# We want a specific commit/branch/tag here.
if verRange.spe == getHeadName(downMethod):
# Grab HEAD.
doClone(downMethod, url, downloadDir, onlyTip = not options.forceFullClone)
else:
# Grab the full repo.
doClone(downMethod, url, downloadDir, onlyTip = false)
# Then perform a checkout operation to get the specified branch/commit.
# `spe` starts with '#', trim it.
doAssert(($verRange.spe)[0] == '#')
doCheckout(downMethod, downloadDir, substr($verRange.spe, 1))
result = verRange.spe
else:
case downMethod
of DownloadMethod.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.
result = getHeadName(downMethod)
let versions = getTagsListRemote(url, downMethod).getVersionList()
if versions.len > 0:
getLatestByTag:
display("Cloning", "latest tagged version: " & latest.tag,
priority = MediumPriority)
doClone(downMethod, url, downloadDir, latest.tag,
onlyTip = not options.forceFullClone)
else:
# If no commits have been tagged on the repo we just clone HEAD.
doClone(downMethod, url, downloadDir) # Grab HEAD.
of DownloadMethod.hg:
doClone(downMethod, url, downloadDir, onlyTip = not options.forceFullClone)
result = getHeadName(downMethod)
let versions = getTagsList(downloadDir, downMethod).getVersionList()
if versions.len > 0:
getLatestByTag:
display("Switching", "to latest tagged version: " & latest.tag,
priority = MediumPriority)
doCheckout(downMethod, downloadDir, latest.tag)
proc downloadPkg*(url: string, verRange: VersionRange,
downMethod: DownloadMethod,
subdir: string,
options: Options,
downloadPath = ""): (string, Version) =
## Downloads the repository as specified by ``url`` and ``verRange`` using
## the download method specified.
##
## If `downloadPath` isn't specified a location in /tmp/ will be used.
##
## Returns the directory where it was downloaded (subdir is appended) and
## the concrete version which was downloaded.
let downloadDir =
if downloadPath == "":
(getNimbleTempDir() / getDownloadDirName(url, verRange))
else:
downloadPath
createDir(downloadDir)
var modUrl =
if url.startsWith("git://") and options.config.cloneUsingHttps:
"https://" & url[6 .. ^1]
else: url
# Fixes issue #204
# github + https + trailing url slash causes a
# checkout/ls-remote to fail with Repository not found
if modUrl.contains("github.com") and modUrl.endswith("/"):
modUrl = modUrl[0 .. ^2]
if subdir.len > 0:
display("Downloading", "$1 using $2 (subdir is '$3')" %
[modUrl, $downMethod, subdir],
priority = HighPriority)
else:
display("Downloading", "$1 using $2" % [modUrl, $downMethod],
priority = HighPriority)
result = (
downloadDir / subdir,
doDownload(modUrl, downloadDir, verRange, downMethod, options)
)
if verRange.kind != verSpecial:
## Makes sure that the downloaded package's version satisfies the requested
## version range.
let pkginfo = getPkgInfo(result[0], options)
if pkginfo.version.newVersion notin verRange:
raise newException(NimbleError,
"Downloaded package's version does not satisfy requested version " &
"range: wanted $1 got $2." %
[$verRange, $pkginfo.version])
proc echoPackageVersions*(pkg: Package) =
let downMethod = pkg.downloadMethod.getDownloadMethod()
case downMethod
of DownloadMethod.git:
try:
let versions = getTagsListRemote(pkg.url, downMethod).getVersionList()
if versions.len > 0:
let sortedVersions = toSeq(values(versions))
echo(" versions: " & join(sortedVersions, ", "))
else:
echo(" versions: (No versions tagged in the remote repository)")
except OSError:
echo(getCurrentExceptionMsg())
of DownloadMethod.hg:
echo(" versions: (Remote tag retrieval not supported by " &
pkg.downloadMethod & ")")
proc packageVersionsJson*(pkg: Package): JsonNode =
result = newJArray()
let downMethod = pkg.downloadMethod.getDownloadMethod()
try:
case downMethod
of DownloadMethod.git:
let versions = getTagsListRemote(pkg.url, downMethod).getVersionList()
for v in values(versions):
result.add %v
of DownloadMethod.hg:
discard
except:
result = %* { "exception": getCurrentExceptionMsg() }
when isMainModule:
# Test version sorting
block:
let data = @["v9.0.0-taeyeon", "v9.0.1-jessica", "v9.2.0-sunny",
"v9.4.0-tiffany", "v9.4.2-hyoyeon"]
let expected = toOrderedTable[Version, string]({
newVersion("9.4.2-hyoyeon"): "v9.4.2-hyoyeon",
newVersion("9.4.0-tiffany"): "v9.4.0-tiffany",
newVersion("9.2.0-sunny"): "v9.2.0-sunny",
newVersion("9.0.1-jessica"): "v9.0.1-jessica",
newVersion("9.0.0-taeyeon"): "v9.0.0-taeyeon"
})
doAssert expected == getVersionList(data)
echo("Everything works!")

View File

@ -1,120 +0,0 @@
import std/sequtils, std/strutils, std/tables
type
ValueKind* = enum
tInt,
tBool,
tString,
tPath,
tNull,
tAttrs,
tList,
tThunk,
tApp,
tLambda,
tBlackhole,
tPrimOp,
tPrimOpApp,
tExternal,
tFloat
Value* = ref object
case kind: ValueKind
of tInt:
num*: BiggestInt
of tBool:
boolean*: bool
of tString:
str*: string
of tPath:
path*: string
of tNull:
discard
of tAttrs:
attrs*: Table[string, Value]
of tList:
list*: seq[Value]
of tThunk: discard
of tApp: discard
of tLambda: discard
of tBlackhole: discard
of tPrimOp: discard
of tPrimOpApp: discard
of tExternal: discard
of tFloat:
fnum*: float
func `$`*(v: Value): string =
case v.kind:
of tInt:
result = $v.num
of tBool:
result = if v.boolean: "True" else: "False"
of tString:
result = "\"$1\"" % (v.str.replace("\"", "\\\""))
of tPath:
result = v.path
of tNull:
result = "null"
of tAttrs:
result = "{"
for key, val in v.attrs.pairs:
let key = if key.validIdentifier: key else: key.escape
result.add("$1=$2;" % [key, $val])
result.add "}"
of tList:
result = "[ "
for e in v.list:
result.add $e
result.add " "
result.add "]"
of tFloat:
result = $v.fnum
else:
result = $v.kind
func toNix*(x: Value): Value = x
func toNix*(x: SomeInteger): Value =
Value(kind: tInt, num: x)
func toNix*(x: bool): Value =
Value(kind: tBool, boolean: x)
func toNix*(x: string): Value =
Value(kind: tString, str: x)
func toPath*(x: string): Value =
Value(kind: tPath, path: x)
template toNix*(pairs: openArray[(string, Value)]): Value =
Value(kind: tAttrs, attrs: toTable pairs)
func toNix*(x: seq[Value]): Value =
Value(kind: tList, list: x)
template toNix*(x: seq[untyped]): Value =
map(x, toNix).toNix
func toNix*(x: openarray[Value]): Value =
Value(kind: tList, list: x[x.low .. x.high])
func toNix*(x: float): Value =
Value(kind: tFloat, fnum: x)
template `[]=`*(result: Value; key: string; val: untyped) =
result.attrs[key] = val.toNix
proc `[]`*(attrs: Value; key: string): Value =
attrs.attrs[key]
proc newNull*(): Value =
Value(kind: tNull)
proc newAttrs*(): Value =
Value(kind: tAttrs, attrs: initTable[string, Value]())
func newList*(): Value =
Value(kind: tList)
proc add*(x, y: Value) =
x.list.add y