Add a toJSON primop
This commit is contained in:
parent
285df765b9
commit
77c13cdf56
10 changed files with 179 additions and 24 deletions
|
@ -750,6 +750,18 @@ in foo</programlisting>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
|
||||||
|
<varlistentry><term><function>builtins.toJSON</function> <replaceable>e</replaceable></term>
|
||||||
|
|
||||||
|
<listitem><para>Return a string containing a JSON representation
|
||||||
|
of <replaceable>e</replaceable>. Strings, integers, booleans,
|
||||||
|
nulls and lists are mapped to their JSON equivalents. Sets
|
||||||
|
(except derivations) are represented as objects. Derivations are
|
||||||
|
translated to a JSON string containing the derivation’s output
|
||||||
|
path. Paths are copied to the store and represented as a JSON
|
||||||
|
string of the resulting store path.</para></listitem>
|
||||||
|
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry><term><function>builtins.toPath</function> <replaceable>s</replaceable></term>
|
<varlistentry><term><function>builtins.toPath</function> <replaceable>s</replaceable></term>
|
||||||
|
|
||||||
<listitem><para>Convert the string value
|
<listitem><para>Convert the string value
|
||||||
|
|
|
@ -13,6 +13,11 @@
|
||||||
|
|
||||||
<itemizedlist>
|
<itemizedlist>
|
||||||
|
|
||||||
|
<listitem>
|
||||||
|
<para>New built-in function: <function>builtins.toJSON</function>,
|
||||||
|
which returns a JSON representation of a value.</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
<listitem><para><command>nix-setuid-helper</command> is
|
<listitem><para><command>nix-setuid-helper</command> is
|
||||||
gone.</para></listitem>
|
gone.</para></listitem>
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,13 @@ pkglib_LTLIBRARIES = libexpr.la
|
||||||
|
|
||||||
libexpr_la_SOURCES = \
|
libexpr_la_SOURCES = \
|
||||||
nixexpr.cc eval.cc primops.cc lexer-tab.cc parser-tab.cc \
|
nixexpr.cc eval.cc primops.cc lexer-tab.cc parser-tab.cc \
|
||||||
get-drvs.cc attr-path.cc value-to-xml.cc common-opts.cc \
|
get-drvs.cc attr-path.cc value-to-xml.cc value-to-json.cc \
|
||||||
names.cc
|
common-opts.cc names.cc
|
||||||
|
|
||||||
pkginclude_HEADERS = \
|
pkginclude_HEADERS = \
|
||||||
nixexpr.hh eval.hh eval-inline.hh lexer-tab.hh parser-tab.hh \
|
nixexpr.hh eval.hh eval-inline.hh lexer-tab.hh parser-tab.hh \
|
||||||
get-drvs.hh attr-path.hh value-to-xml.hh common-opts.hh \
|
get-drvs.hh attr-path.hh value-to-xml.hh value-to-json.hh \
|
||||||
names.hh symbol-table.hh value.hh
|
common-opts.hh names.hh symbol-table.hh value.hh
|
||||||
|
|
||||||
libexpr_la_LIBADD = ../libutil/libutil.la ../libstore/libstore.la \
|
libexpr_la_LIBADD = ../libutil/libutil.la ../libstore/libstore.la \
|
||||||
../boost/format/libformat.la @BDW_GC_LIBS@
|
../boost/format/libformat.la @BDW_GC_LIBS@
|
||||||
|
|
|
@ -1163,26 +1163,7 @@ string EvalState::coerceToString(Value & v, PathSet & context,
|
||||||
|
|
||||||
if (v.type == tPath) {
|
if (v.type == tPath) {
|
||||||
Path path(canonPath(v.path));
|
Path path(canonPath(v.path));
|
||||||
|
return copyToStore ? copyPathToStore(context, path) : path;
|
||||||
if (!copyToStore) return path;
|
|
||||||
|
|
||||||
if (nix::isDerivation(path))
|
|
||||||
throwEvalError("file names are not allowed to end in `%1%'", drvExtension);
|
|
||||||
|
|
||||||
Path dstPath;
|
|
||||||
if (srcToStore[path] != "")
|
|
||||||
dstPath = srcToStore[path];
|
|
||||||
else {
|
|
||||||
dstPath = settings.readOnlyMode
|
|
||||||
? computeStorePathForPath(path).first
|
|
||||||
: store->addToStore(path, true, htSHA256, defaultPathFilter, repair);
|
|
||||||
srcToStore[path] = dstPath;
|
|
||||||
printMsg(lvlChatty, format("copied source `%1%' -> `%2%'")
|
|
||||||
% path % dstPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.insert(dstPath);
|
|
||||||
return dstPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v.type == tAttrs) {
|
if (v.type == tAttrs) {
|
||||||
|
@ -1218,6 +1199,28 @@ string EvalState::coerceToString(Value & v, PathSet & context,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string EvalState::copyPathToStore(PathSet & context, const Path & path)
|
||||||
|
{
|
||||||
|
if (nix::isDerivation(path))
|
||||||
|
throwEvalError("file names are not allowed to end in `%1%'", drvExtension);
|
||||||
|
|
||||||
|
Path dstPath;
|
||||||
|
if (srcToStore[path] != "")
|
||||||
|
dstPath = srcToStore[path];
|
||||||
|
else {
|
||||||
|
dstPath = settings.readOnlyMode
|
||||||
|
? computeStorePathForPath(path).first
|
||||||
|
: store->addToStore(path, true, htSHA256, defaultPathFilter, repair);
|
||||||
|
srcToStore[path] = dstPath;
|
||||||
|
printMsg(lvlChatty, format("copied source `%1%' -> `%2%'")
|
||||||
|
% path % dstPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.insert(dstPath);
|
||||||
|
return dstPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Path EvalState::coerceToPath(Value & v, PathSet & context)
|
Path EvalState::coerceToPath(Value & v, PathSet & context)
|
||||||
{
|
{
|
||||||
string path = coerceToString(v, context, false, false);
|
string path = coerceToString(v, context, false, false);
|
||||||
|
|
|
@ -181,6 +181,8 @@ public:
|
||||||
string coerceToString(Value & v, PathSet & context,
|
string coerceToString(Value & v, PathSet & context,
|
||||||
bool coerceMore = false, bool copyToStore = true);
|
bool coerceMore = false, bool copyToStore = true);
|
||||||
|
|
||||||
|
string copyPathToStore(PathSet & context, const Path & path);
|
||||||
|
|
||||||
/* Path coercion. Converts strings, paths and derivations to a
|
/* Path coercion. Converts strings, paths and derivations to a
|
||||||
path. The result is guaranteed to be a canonicalised, absolute
|
path. The result is guaranteed to be a canonicalised, absolute
|
||||||
path. Nothing is copied to the store. */
|
path. Nothing is copied to the store. */
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "util.hh"
|
#include "util.hh"
|
||||||
#include "archive.hh"
|
#include "archive.hh"
|
||||||
#include "value-to-xml.hh"
|
#include "value-to-xml.hh"
|
||||||
|
#include "value-to-json.hh"
|
||||||
#include "names.hh"
|
#include "names.hh"
|
||||||
#include "eval-inline.hh"
|
#include "eval-inline.hh"
|
||||||
|
|
||||||
|
@ -647,6 +648,18 @@ static void prim_toXML(EvalState & state, Value * * args, Value & v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Convert the argument (which can be any Nix expression) to a JSON
|
||||||
|
string. Not all Nix expressions can be sensibly or completely
|
||||||
|
represented (e.g., functions). */
|
||||||
|
static void prim_toJSON(EvalState & state, Value * * args, Value & v)
|
||||||
|
{
|
||||||
|
std::ostringstream out;
|
||||||
|
PathSet context;
|
||||||
|
printValueAsJSON(state, true, *args[0], out, context);
|
||||||
|
mkString(v, out.str(), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Store a string in the Nix store as a source file that can be used
|
/* Store a string in the Nix store as a source file that can be used
|
||||||
as an input by derivations. */
|
as an input by derivations. */
|
||||||
static void prim_toFile(EvalState & state, Value * * args, Value & v)
|
static void prim_toFile(EvalState & state, Value * * args, Value & v)
|
||||||
|
@ -1259,6 +1272,7 @@ void EvalState::createBaseEnv()
|
||||||
|
|
||||||
// Creating files
|
// Creating files
|
||||||
addPrimOp("__toXML", 1, prim_toXML);
|
addPrimOp("__toXML", 1, prim_toXML);
|
||||||
|
addPrimOp("__toJSON", 1, prim_toJSON);
|
||||||
addPrimOp("__toFile", 2, prim_toFile);
|
addPrimOp("__toFile", 2, prim_toFile);
|
||||||
addPrimOp("__filterSource", 2, prim_filterSource);
|
addPrimOp("__filterSource", 2, prim_filterSource);
|
||||||
|
|
||||||
|
|
93
src/libexpr/value-to-json.cc
Normal file
93
src/libexpr/value-to-json.cc
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
#include "value-to-xml.hh"
|
||||||
|
#include "xml-writer.hh"
|
||||||
|
#include "eval-inline.hh"
|
||||||
|
#include "util.hh"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
|
static void escapeJSON(std::ostream & str, const string & s)
|
||||||
|
{
|
||||||
|
str << "\"";
|
||||||
|
foreach (string::const_iterator, i, s)
|
||||||
|
if (*i == '\"' || *i == '\\') str << "\\" << *i;
|
||||||
|
else if (*i == '\n') str << "\\n";
|
||||||
|
else if (*i == '\r') str << "\\r";
|
||||||
|
else if (*i == '\t') str << "\\t";
|
||||||
|
else str << *i;
|
||||||
|
str << "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void printValueAsJSON(EvalState & state, bool strict,
|
||||||
|
Value & v, std::ostream & str, PathSet & context)
|
||||||
|
{
|
||||||
|
checkInterrupt();
|
||||||
|
|
||||||
|
if (strict) state.forceValue(v);
|
||||||
|
|
||||||
|
switch (v.type) {
|
||||||
|
|
||||||
|
case tInt:
|
||||||
|
str << v.integer;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case tBool:
|
||||||
|
str << (v.boolean ? "true" : "false");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case tString:
|
||||||
|
copyContext(v, context);
|
||||||
|
escapeJSON(str, v.string.s);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case tPath:
|
||||||
|
escapeJSON(str, state.copyPathToStore(context, v.path));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case tNull:
|
||||||
|
str << "null";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case tAttrs: {
|
||||||
|
Bindings::iterator i = v.attrs->find(state.sOutPath);
|
||||||
|
if (i == v.attrs->end()) {
|
||||||
|
str << "{";
|
||||||
|
StringSet names;
|
||||||
|
foreach (Bindings::iterator, i, *v.attrs)
|
||||||
|
names.insert(i->name);
|
||||||
|
bool first = true;
|
||||||
|
foreach (StringSet::iterator, i, names) {
|
||||||
|
if (!first) str << ","; else first = false;
|
||||||
|
Attr & a(*v.attrs->find(state.symbols.create(*i)));
|
||||||
|
escapeJSON(str, *i);
|
||||||
|
str << ":";
|
||||||
|
printValueAsJSON(state, strict, *a.value, str, context);
|
||||||
|
}
|
||||||
|
str << "}";
|
||||||
|
} else
|
||||||
|
printValueAsJSON(state, strict, *i->value, str, context);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case tList: {
|
||||||
|
str << "[";
|
||||||
|
bool first = true;
|
||||||
|
for (unsigned int n = 0; n < v.list.length; ++n) {
|
||||||
|
if (!first) str << ","; else first = false;
|
||||||
|
printValueAsJSON(state, strict, *v.list.elems[n], str, context);
|
||||||
|
}
|
||||||
|
str << "]";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw TypeError(format("cannot convert %1% to JSON") % showType(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
14
src/libexpr/value-to-json.hh
Normal file
14
src/libexpr/value-to-json.hh
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "nixexpr.hh"
|
||||||
|
#include "eval.hh"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
void printValueAsJSON(EvalState & state, bool strict,
|
||||||
|
Value & v, std::ostream & out, PathSet & context);
|
||||||
|
|
||||||
|
}
|
1
tests/lang/eval-okay-tojson.exp
Normal file
1
tests/lang/eval-okay-tojson.exp
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"{\"a\":123,\"b\":-456,\"c\":\"foo\",\"d\":\"foo\\n\\\"bar\\\"\",\"e\":true,\"f\":false,\"g\":[1,2,3],\"h\":[\"a\",[\"b\",{\"foo\\nbar\":{}}]],\"i\":3}"
|
11
tests/lang/eval-okay-tojson.nix
Normal file
11
tests/lang/eval-okay-tojson.nix
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
builtins.toJSON
|
||||||
|
{ a = 123;
|
||||||
|
b = -456;
|
||||||
|
c = "foo";
|
||||||
|
d = "foo\n\"bar\"";
|
||||||
|
e = true;
|
||||||
|
f = false;
|
||||||
|
g = [ 1 2 3 ];
|
||||||
|
h = [ "a" [ "b" { "foo\nbar" = {}; } ] ];
|
||||||
|
i = 1 + 2;
|
||||||
|
}
|
Loading…
Reference in a new issue