about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEelco Dolstra <eelco.dolstra@logicblox.com>2013-11-18T23·03+0100
committerEelco Dolstra <eelco.dolstra@logicblox.com>2013-11-18T23·04+0100
commit77c13cdf566ffedc70d8860571afae8a6d43b552 (patch)
tree80d2c35c450911d0401e8a95d28ff9d8904b53fc
parent285df765b91588e39d6f35a32e30b84c3cb5be75 (diff)
Add a toJSON primop
-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 3958297989a1..6a472291c363 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 3db083870405..37bb730f4063 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 3e7e7e856a55..7edbe77157d8 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 c2db006c1414..3db4bb66f435 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 af408cd0bd76..45ab423c18a1 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 5f2a58454627..bf913468d505 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 000000000000..671a3c119741
--- /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 000000000000..5f36a76d8a7e
--- /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 000000000000..e8164af2b66e
--- /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 000000000000..0d4e55b3d367
--- /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;
+  }