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…
	
	Add table
		Add a link
		
	
		Reference in a new issue