aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/manual/builtins.xml12
-rw-r--r--doc/manual/release-notes.xml5
-rw-r--r--src/libexpr/Makefile.am8
-rw-r--r--src/libexpr/eval.cc43
-rw-r--r--src/libexpr/eval.hh2
-rw-r--r--src/libexpr/primops.cc14
-rw-r--r--src/libexpr/value-to-json.cc93
-rw-r--r--src/libexpr/value-to-json.hh14
-rw-r--r--tests/lang/eval-okay-tojson.exp1
-rw-r--r--tests/lang/eval-okay-tojson.nix11
10 files changed, 179 insertions, 24 deletions
diff --git a/doc/manual/builtins.xml b/doc/manual/builtins.xml
index 395829798..6a472291c 100644
--- a/doc/manual/builtins.xml
+++ b/doc/manual/builtins.xml
@@ -750,6 +750,18 @@ in foo</programlisting>
</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>
<listitem><para>Convert the string value
diff --git a/doc/manual/release-notes.xml b/doc/manual/release-notes.xml
index 3db083870..37bb730f4 100644
--- a/doc/manual/release-notes.xml
+++ b/doc/manual/release-notes.xml
@@ -13,6 +13,11 @@
<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
gone.</para></listitem>
diff --git a/src/libexpr/Makefile.am b/src/libexpr/Makefile.am
index 3e7e7e856..7edbe7715 100644
--- a/src/libexpr/Makefile.am
+++ b/src/libexpr/Makefile.am
@@ -2,13 +2,13 @@ pkglib_LTLIBRARIES = libexpr.la
libexpr_la_SOURCES = \
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 \
- names.cc
+ get-drvs.cc attr-path.cc value-to-xml.cc value-to-json.cc \
+ common-opts.cc names.cc
pkginclude_HEADERS = \
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 \
- names.hh symbol-table.hh value.hh
+ get-drvs.hh attr-path.hh value-to-xml.hh value-to-json.hh \
+ common-opts.hh names.hh symbol-table.hh value.hh
libexpr_la_LIBADD = ../libutil/libutil.la ../libstore/libstore.la \
../boost/format/libformat.la @BDW_GC_LIBS@
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index c2db006c1..3db4bb66f 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -1163,26 +1163,7 @@ string EvalState::coerceToString(Value & v, PathSet & context,
if (v.type == tPath) {
Path path(canonPath(v.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;
+ return copyToStore ? copyPathToStore(context, path) : path;
}
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)
{
string path = coerceToString(v, context, false, false);
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index af408cd0b..45ab423c1 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -181,6 +181,8 @@ public:
string coerceToString(Value & v, PathSet & context,
bool coerceMore = false, bool copyToStore = true);
+ string copyPathToStore(PathSet & context, const Path & path);
+
/* Path coercion. Converts strings, paths and derivations to a
path. The result is guaranteed to be a canonicalised, absolute
path. Nothing is copied to the store. */
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 5f2a58454..bf913468d 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -5,6 +5,7 @@
#include "util.hh"
#include "archive.hh"
#include "value-to-xml.hh"
+#include "value-to-json.hh"
#include "names.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
as an input by derivations. */
static void prim_toFile(EvalState & state, Value * * args, Value & v)
@@ -1259,6 +1272,7 @@ void EvalState::createBaseEnv()
// Creating files
addPrimOp("__toXML", 1, prim_toXML);
+ addPrimOp("__toJSON", 1, prim_toJSON);
addPrimOp("__toFile", 2, prim_toFile);
addPrimOp("__filterSource", 2, prim_filterSource);
diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc
new file mode 100644
index 000000000..671a3c119
--- /dev/null
+++ b/src/libexpr/value-to-json.cc
@@ -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));
+ }
+}
+
+
+}
diff --git a/src/libexpr/value-to-json.hh b/src/libexpr/value-to-json.hh
new file mode 100644
index 000000000..5f36a76d8
--- /dev/null
+++ b/src/libexpr/value-to-json.hh
@@ -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);
+
+}
diff --git a/tests/lang/eval-okay-tojson.exp b/tests/lang/eval-okay-tojson.exp
new file mode 100644
index 000000000..e8164af2b
--- /dev/null
+++ b/tests/lang/eval-okay-tojson.exp
@@ -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}"
diff --git a/tests/lang/eval-okay-tojson.nix b/tests/lang/eval-okay-tojson.nix
new file mode 100644
index 000000000..0d4e55b3d
--- /dev/null
+++ b/tests/lang/eval-okay-tojson.nix
@@ -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;
+ }