From c0a7b84748d5e27e6804117b8a57ce71269c3c66 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 26 Aug 2016 18:55:55 +0200 Subject: [PATCH] nix path-info: Add --json flag Also, factor out JSON generation from value-to-json.{cc,hh}, and support producing indented JSON. --- src/libexpr/value-to-json.cc | 55 +++++------ src/libexpr/value-to-json.hh | 71 +------------- src/libexpr/value.hh | 3 +- src/libutil/json.cc | 171 ++++++++++++++++++++++++++++++++ src/libutil/json.hh | 183 +++++++++++++++++++++++++++++++++++ src/nix-env/nix-env.cc | 15 ++- src/nix/path-info.cc | 111 ++++++++++++++++----- 7 files changed, 476 insertions(+), 133 deletions(-) create mode 100644 src/libutil/json.cc create mode 100644 src/libutil/json.hh diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc index 47ee324a6..72e413e44 100644 --- a/src/libexpr/value-to-json.cc +++ b/src/libexpr/value-to-json.cc @@ -1,4 +1,5 @@ #include "value-to-json.hh" +#include "json.hh" #include "eval-inline.hh" #include "util.hh" @@ -8,24 +9,8 @@ namespace nix { - -void escapeJSON(std::ostream & str, const string & s) -{ - str << "\""; - for (auto & 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 if (i >= 0 && i < 32) - str << "\\u" << std::setfill('0') << std::setw(4) << std::hex << (uint16_t) i << std::dec; - else str << i; - str << "\""; -} - - void printValueAsJSON(EvalState & state, bool strict, - Value & v, std::ostream & str, PathSet & context) + Value & v, JSONPlaceholder & out, PathSet & context) { checkInterrupt(); @@ -34,58 +19,58 @@ void printValueAsJSON(EvalState & state, bool strict, switch (v.type) { case tInt: - str << v.integer; + out.write(v.integer); break; case tBool: - str << (v.boolean ? "true" : "false"); + out.write(v.boolean); break; case tString: copyContext(v, context); - escapeJSON(str, v.string.s); + out.write(v.string.s); break; case tPath: - escapeJSON(str, state.copyPathToStore(context, v.path)); + out.write(state.copyPathToStore(context, v.path)); break; case tNull: - str << "null"; + out.write(nullptr); break; case tAttrs: { Bindings::iterator i = v.attrs->find(state.sOutPath); if (i == v.attrs->end()) { - JSONObject json(str); + auto obj(out.object()); StringSet names; for (auto & j : *v.attrs) names.insert(j.name); for (auto & j : names) { Attr & a(*v.attrs->find(state.symbols.create(j))); - json.attr(j); - printValueAsJSON(state, strict, *a.value, str, context); + auto placeholder(obj.placeholder(j)); + printValueAsJSON(state, strict, *a.value, placeholder, context); } } else - printValueAsJSON(state, strict, *i->value, str, context); + printValueAsJSON(state, strict, *i->value, out, context); break; } case tList1: case tList2: case tListN: { - JSONList json(str); + auto list(out.list()); for (unsigned int n = 0; n < v.listSize(); ++n) { - json.elem(); - printValueAsJSON(state, strict, *v.listElems()[n], str, context); + auto placeholder(list.placeholder()); + printValueAsJSON(state, strict, *v.listElems()[n], placeholder, context); } break; } case tExternal: - v.external->printValueAsJSON(state, strict, str, context); + v.external->printValueAsJSON(state, strict, out, context); break; case tFloat: - str << v.fpoint; + out.write(v.fpoint); break; default: @@ -93,9 +78,15 @@ void printValueAsJSON(EvalState & state, bool strict, } } +void printValueAsJSON(EvalState & state, bool strict, + Value & v, std::ostream & str, PathSet & context) +{ + JSONPlaceholder out(str); + printValueAsJSON(state, strict, v, out, context); +} void ExternalValueBase::printValueAsJSON(EvalState & state, bool strict, - std::ostream & str, PathSet & context) const + JSONPlaceholder & out, PathSet & context) const { throw TypeError(format("cannot convert %1% to JSON") % showType()); } diff --git a/src/libexpr/value-to-json.hh b/src/libexpr/value-to-json.hh index c59caf564..67fed6487 100644 --- a/src/libexpr/value-to-json.hh +++ b/src/libexpr/value-to-json.hh @@ -8,73 +8,12 @@ namespace nix { +class JSONPlaceholder; + void printValueAsJSON(EvalState & state, bool strict, - Value & v, std::ostream & out, PathSet & context); + Value & v, JSONPlaceholder & out, PathSet & context); -void escapeJSON(std::ostream & str, const string & s); - -struct JSONObject -{ - std::ostream & str; - bool first; - JSONObject(std::ostream & str) : str(str), first(true) - { - str << "{"; - } - ~JSONObject() - { - str << "}"; - } - void attr(const string & s) - { - if (!first) str << ","; else first = false; - escapeJSON(str, s); - str << ":"; - } - void attr(const string & s, const string & t) - { - attr(s); - escapeJSON(str, t); - } - void attr(const string & s, const char * t) - { - attr(s); - escapeJSON(str, t); - } - void attr(const string & s, bool b) - { - attr(s); - str << (b ? "true" : "false"); - } - template - void attr(const string & s, const T & n) - { - attr(s); - str << n; - } -}; - -struct JSONList -{ - std::ostream & str; - bool first; - JSONList(std::ostream & str) : str(str), first(true) - { - str << "["; - } - ~JSONList() - { - str << "]"; - } - void elem() - { - if (!first) str << ","; else first = false; - } - void elem(const string & s) - { - elem(); - escapeJSON(str, s); - } -}; +void printValueAsJSON(EvalState & state, bool strict, + Value & v, std::ostream & str, PathSet & context); } diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index 62bdd9281..f5e485748 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -36,6 +36,7 @@ class Symbol; struct Pos; class EvalState; class XMLWriter; +class JSONPlaceholder; typedef long NixInt; @@ -73,7 +74,7 @@ class ExternalValueBase /* Print the value as JSON. Defaults to unconvertable, i.e. throws an error */ virtual void printValueAsJSON(EvalState & state, bool strict, - std::ostream & str, PathSet & context) const; + JSONPlaceholder & out, PathSet & context) const; /* Print the value as XML. Defaults to unevaluated */ virtual void printValueAsXML(EvalState & state, bool strict, bool location, diff --git a/src/libutil/json.cc b/src/libutil/json.cc new file mode 100644 index 000000000..619b1d631 --- /dev/null +++ b/src/libutil/json.cc @@ -0,0 +1,171 @@ +#include "json.hh" + +#include +#include + +namespace nix { + +void toJSON(std::ostream & str, const char * start, const char * end) +{ + str << '"'; + for (auto i = start; i != end; i++) + if (*i == '\"' || *i == '\\') str << '\\' << *i; + else if (*i == '\n') str << "\\n"; + else if (*i == '\r') str << "\\r"; + else if (*i == '\t') str << "\\t"; + else if (*i >= 0 && *i < 32) + str << "\\u" << std::setfill('0') << std::setw(4) << std::hex << (uint16_t) *i << std::dec; + else str << *i; + str << '"'; +} + +void toJSON(std::ostream & str, const std::string & s) +{ + toJSON(str, s.c_str(), s.c_str() + s.size()); +} + +void toJSON(std::ostream & str, const char * s) +{ + if (!s) str << "null"; else toJSON(str, s, s + strlen(s)); +} + +void toJSON(std::ostream & str, unsigned long n) +{ + str << n; +} + +void toJSON(std::ostream & str, long n) +{ + str << n; +} + +void toJSON(std::ostream & str, double f) +{ + str << f; +} + +void toJSON(std::ostream & str, bool b) +{ + str << (b ? "true" : "false"); +} + +JSONWriter::JSONWriter(std::ostream & str, bool indent) + : state(new JSONState(str, indent)) +{ + state->stack.push_back(this); +} + +JSONWriter::JSONWriter(JSONState * state) + : state(state) +{ + state->stack.push_back(this); +} + +JSONWriter::~JSONWriter() +{ + assertActive(); + state->stack.pop_back(); + if (state->stack.empty()) delete state; +} + +void JSONWriter::comma() +{ + assertActive(); + if (first) { + first = false; + } else { + state->str << ','; + } + if (state->indent) indent(); +} + +void JSONWriter::indent() +{ + state->str << '\n' << std::string(state->depth * 2, ' '); +} + +void JSONList::open() +{ + state->depth++; + state->str << '['; +} + +JSONList::~JSONList() +{ + state->depth--; + if (state->indent && !first) indent(); + state->str << "]"; +} + +JSONList JSONList::list() +{ + comma(); + return JSONList(state); +} + +JSONObject JSONList::object() +{ + comma(); + return JSONObject(state); +} + +JSONPlaceholder JSONList::placeholder() +{ + comma(); + return JSONPlaceholder(state); +} + +void JSONObject::open() +{ + state->depth++; + state->str << '{'; +} + +JSONObject::~JSONObject() +{ + state->depth--; + if (state->indent && !first) indent(); + state->str << "}"; +} + +void JSONObject::attr(const std::string & s) +{ + comma(); + toJSON(state->str, s); + state->str << ':'; + if (state->indent) state->str << ' '; +} + +JSONList JSONObject::list(const std::string & name) +{ + attr(name); + return JSONList(state); +} + +JSONObject JSONObject::object(const std::string & name) +{ + attr(name); + return JSONObject(state); +} + +JSONPlaceholder JSONObject::placeholder(const std::string & name) +{ + attr(name); + return JSONPlaceholder(state); +} + +JSONList JSONPlaceholder::list() +{ + assertValid(); + first = false; + return JSONList(state); +} + +JSONObject JSONPlaceholder::object() +{ + assertValid(); + first = false; + return JSONObject(state); +} + +} diff --git a/src/libutil/json.hh b/src/libutil/json.hh new file mode 100644 index 000000000..852000a51 --- /dev/null +++ b/src/libutil/json.hh @@ -0,0 +1,183 @@ +#pragma once + +#include +#include +#include + +namespace nix { + +void toJSON(std::ostream & str, const char * start, const char * end); +void toJSON(std::ostream & str, const std::string & s); +void toJSON(std::ostream & str, const char * s); +void toJSON(std::ostream & str, unsigned long n); +void toJSON(std::ostream & str, long n); +void toJSON(std::ostream & str, double f); +void toJSON(std::ostream & str, bool b); + +class JSONWriter +{ +protected: + + struct JSONState + { + std::ostream & str; + bool indent; + size_t depth = 0; + std::vector stack; + JSONState(std::ostream & str, bool indent) : str(str), indent(indent) { } + ~JSONState() + { + assert(stack.empty()); + } + }; + + JSONState * state; + + bool first = true; + + JSONWriter(std::ostream & str, bool indent); + + JSONWriter(JSONState * state); + + ~JSONWriter(); + + void assertActive() + { + assert(!state->stack.empty() && state->stack.back() == this); + } + + void comma(); + + void indent(); +}; + +class JSONObject; +class JSONPlaceholder; + +class JSONList : JSONWriter +{ +private: + + friend class JSONObject; + friend class JSONPlaceholder; + + void open(); + + JSONList(JSONState * state) + : JSONWriter(state) + { + open(); + } + +public: + + JSONList(std::ostream & str, bool indent = false) + : JSONWriter(str, indent) + { + open(); + } + + ~JSONList(); + + template + JSONList & elem(const T & v) + { + comma(); + toJSON(state->str, v); + return *this; + } + + JSONList list(); + + JSONObject object(); + + JSONPlaceholder placeholder(); +}; + +class JSONObject : JSONWriter +{ +private: + + friend class JSONList; + friend class JSONPlaceholder; + + void open(); + + JSONObject(JSONState * state) + : JSONWriter(state) + { + open(); + } + + void attr(const std::string & s); + +public: + + JSONObject(std::ostream & str, bool indent = false) + : JSONWriter(str, indent) + { + open(); + } + + ~JSONObject(); + + template + JSONObject & attr(const std::string & name, const T & v) + { + attr(name); + toJSON(state->str, v); + return *this; + } + + JSONList list(const std::string & name); + + JSONObject object(const std::string & name); + + JSONPlaceholder placeholder(const std::string & name); +}; + +class JSONPlaceholder : JSONWriter +{ + +private: + + friend class JSONList; + friend class JSONObject; + + JSONPlaceholder(JSONState * state) + : JSONWriter(state) + { + } + + void assertValid() + { + assertActive(); + assert(first); + } + +public: + + JSONPlaceholder(std::ostream & str, bool indent = false) + : JSONWriter(str, indent) + { + } + + ~JSONPlaceholder() + { + assert(!first || std::uncaught_exception()); + } + + template + void write(const T & v) + { + assertValid(); + first = false; + toJSON(state->str, v); + } + + JSONList list(); + + JSONObject object(); +}; + +} diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index 955a24661..6a557e8ac 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -10,6 +10,7 @@ #include "store-api.hh" #include "user-env.hh" #include "util.hh" +#include "json.hh" #include "value-to-json.hh" #include "xml-writer.hh" @@ -860,26 +861,24 @@ static VersionDiff compareVersionAgainstSet( static void queryJSON(Globals & globals, vector & elems) { - JSONObject topObj(cout); + JSONObject topObj(cout, true); for (auto & i : elems) { - topObj.attr(i.attrPath); - JSONObject pkgObj(cout); + JSONObject pkgObj = topObj.object(i.attrPath); pkgObj.attr("name", i.name); pkgObj.attr("system", i.system); - pkgObj.attr("meta"); - JSONObject metaObj(cout); + JSONObject metaObj = pkgObj.object("meta"); StringSet metaNames = i.queryMetaNames(); for (auto & j : metaNames) { - metaObj.attr(j); + auto placeholder = metaObj.placeholder(j); Value * v = i.queryMeta(j); if (!v) { printMsg(lvlError, format("derivation ‘%1%’ has invalid meta attribute ‘%2%’") % i.name % j); - cout << "null"; + placeholder.write(nullptr); } else { PathSet context; - printValueAsJSON(*globals.state, true, *v, cout, context); + printValueAsJSON(*globals.state, true, *v, placeholder, context); } } } diff --git a/src/nix/path-info.cc b/src/nix/path-info.cc index dca22240b..b25e60db6 100644 --- a/src/nix/path-info.cc +++ b/src/nix/path-info.cc @@ -2,6 +2,10 @@ #include "shared.hh" #include "store-api.hh" + +#include "json.hh" + + #include #include @@ -12,12 +16,14 @@ struct CmdPathInfo : StorePathsCommand bool showSize = false; bool showClosureSize = false; bool showSigs = false; + bool json = false; CmdPathInfo() { mkFlag('s', "size", "print size of the NAR dump of each path", &showSize); mkFlag('S', "closure-size", "print sum size of the NAR dumps of the closure of each path", &showClosureSize); mkFlag(0, "sigs", "show signatures", &showSigs); + mkFlag(0, "json", "produce JSON output", &json); } std::string name() override @@ -41,6 +47,10 @@ struct CmdPathInfo : StorePathsCommand "To check the existence of a path in a binary cache:", "nix path-info -r /nix/store/7qvk5c91...-geeqie-1.1 --store https://cache.nixos.org/" }, + Example{ + "To print the 10 most recently added paths (using --json and the jq(1) command):", + "nix path-info --all --json | jq -r 'sort_by(.registrationTime)[-11:-1][].path'" + }, }; } @@ -50,36 +60,85 @@ struct CmdPathInfo : StorePathsCommand for (auto & storePath : storePaths) pathLen = std::max(pathLen, storePath.size()); - for (auto storePath : storePaths) { - auto info = store->queryPathInfo(storePath); - storePath = info->path; // FIXME: screws up padding + auto getClosureSize = [&](const Path & storePath) { + size_t totalSize = 0; + PathSet closure; + store->computeFSClosure(storePath, closure, false, false); + for (auto & p : closure) + totalSize += store->queryPathInfo(p)->narSize; + return totalSize; + }; - std::cout << storePath << std::string(std::max(0, (int) pathLen - (int) storePath.size()), ' '); + if (json) { + JSONList jsonRoot(std::cout, true); - if (showSize) { - std::cout << '\t' << std::setw(11) << info->narSize; + for (auto storePath : storePaths) { + auto info = store->queryPathInfo(storePath); + storePath = info->path; + + auto jsonPath = jsonRoot.object(); + jsonPath + .attr("path", storePath) + .attr("narHash", info->narHash.to_string()) + .attr("narSize", info->narSize); + + if (showClosureSize) + jsonPath.attr("closureSize", getClosureSize(storePath)); + + if (info->deriver != "") + jsonPath.attr("deriver", info->deriver); + + { + auto jsonRefs = jsonPath.list("references"); + for (auto & ref : info->references) + jsonRefs.elem(ref); + } + + if (info->registrationTime) + jsonPath.attr("registrationTime", info->registrationTime); + + if (info->ultimate) + jsonPath.attr("ultimate", info->ultimate); + + if (info->ca != "") + jsonPath.attr("ca", info->ca); + + if (!info->sigs.empty()) { + auto jsonSigs = jsonPath.list("signatures"); + for (auto & sig : info->sigs) + jsonSigs.elem(sig); + } } - - if (showClosureSize) { - size_t totalSize = 0; - PathSet closure; - store->computeFSClosure(storePath, closure, false, false); - for (auto & p : closure) - totalSize += store->queryPathInfo(p)->narSize; - std::cout << '\t' << std::setw(11) << totalSize; - } - - if (showSigs) { - std::cout << '\t'; - Strings ss; - if (info->ultimate) ss.push_back("ultimate"); - if (info->ca != "") ss.push_back("ca:" + info->ca); - for (auto & sig : info->sigs) ss.push_back(sig); - std::cout << concatStringsSep(" ", ss); - } - - std::cout << std::endl; } + + else { + + for (auto storePath : storePaths) { + auto info = store->queryPathInfo(storePath); + storePath = info->path; // FIXME: screws up padding + + std::cout << storePath << std::string(std::max(0, (int) pathLen - (int) storePath.size()), ' '); + + if (showSize) + std::cout << '\t' << std::setw(11) << info->narSize; + + if (showClosureSize) + std::cout << '\t' << std::setw(11) << getClosureSize(storePath); + + if (showSigs) { + std::cout << '\t'; + Strings ss; + if (info->ultimate) ss.push_back("ultimate"); + if (info->ca != "") ss.push_back("ca:" + info->ca); + for (auto & sig : info->sigs) ss.push_back(sig); + std::cout << concatStringsSep(" ", ss); + } + + std::cout << std::endl; + } + + } + } };