diff --git a/doc/manual/builtins.xml b/doc/manual/builtins.xml index 3958297989..6a472291c3 100644 --- a/doc/manual/builtins.xml +++ b/doc/manual/builtins.xml @@ -750,6 +750,18 @@ in foo + builtins.toJSON e + + Return a string containing a JSON representation + of e. 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. + + + builtins.toPath s Convert the string value diff --git a/doc/manual/release-notes.xml b/doc/manual/release-notes.xml index 3db0838704..37bb730f40 100644 --- a/doc/manual/release-notes.xml +++ b/doc/manual/release-notes.xml @@ -13,6 +13,11 @@ + + New built-in function: builtins.toJSON, + which returns a JSON representation of a value. + + nix-setuid-helper is gone. diff --git a/src/libexpr/Makefile.am b/src/libexpr/Makefile.am index 3e7e7e856a..7edbe77157 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 c2db006c14..3db4bb66f4 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 af408cd0bd..45ab423c18 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 5f2a584546..bf913468d5 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 0000000000..671a3c1197 --- /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 + + +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 0000000000..5f36a76d8a --- /dev/null +++ b/src/libexpr/value-to-json.hh @@ -0,0 +1,14 @@ +#pragma once + +#include "nixexpr.hh" +#include "eval.hh" + +#include +#include + +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 0000000000..e8164af2b6 --- /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 0000000000..0d4e55b3d3 --- /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; + }