nix path-info: Add --json flag

Also, factor out JSON generation from value-to-json.{cc,hh}, and
support producing indented JSON.
This commit is contained in:
Eelco Dolstra 2016-08-26 18:55:55 +02:00
parent 9fa21765e7
commit c0a7b84748
7 changed files with 476 additions and 133 deletions

View file

@ -1,4 +1,5 @@
#include "value-to-json.hh" #include "value-to-json.hh"
#include "json.hh"
#include "eval-inline.hh" #include "eval-inline.hh"
#include "util.hh" #include "util.hh"
@ -8,24 +9,8 @@
namespace nix { 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, void printValueAsJSON(EvalState & state, bool strict,
Value & v, std::ostream & str, PathSet & context) Value & v, JSONPlaceholder & out, PathSet & context)
{ {
checkInterrupt(); checkInterrupt();
@ -34,58 +19,58 @@ void printValueAsJSON(EvalState & state, bool strict,
switch (v.type) { switch (v.type) {
case tInt: case tInt:
str << v.integer; out.write(v.integer);
break; break;
case tBool: case tBool:
str << (v.boolean ? "true" : "false"); out.write(v.boolean);
break; break;
case tString: case tString:
copyContext(v, context); copyContext(v, context);
escapeJSON(str, v.string.s); out.write(v.string.s);
break; break;
case tPath: case tPath:
escapeJSON(str, state.copyPathToStore(context, v.path)); out.write(state.copyPathToStore(context, v.path));
break; break;
case tNull: case tNull:
str << "null"; out.write(nullptr);
break; break;
case tAttrs: { case tAttrs: {
Bindings::iterator i = v.attrs->find(state.sOutPath); Bindings::iterator i = v.attrs->find(state.sOutPath);
if (i == v.attrs->end()) { if (i == v.attrs->end()) {
JSONObject json(str); auto obj(out.object());
StringSet names; StringSet names;
for (auto & j : *v.attrs) for (auto & j : *v.attrs)
names.insert(j.name); names.insert(j.name);
for (auto & j : names) { for (auto & j : names) {
Attr & a(*v.attrs->find(state.symbols.create(j))); Attr & a(*v.attrs->find(state.symbols.create(j)));
json.attr(j); auto placeholder(obj.placeholder(j));
printValueAsJSON(state, strict, *a.value, str, context); printValueAsJSON(state, strict, *a.value, placeholder, context);
} }
} else } else
printValueAsJSON(state, strict, *i->value, str, context); printValueAsJSON(state, strict, *i->value, out, context);
break; break;
} }
case tList1: case tList2: case tListN: { case tList1: case tList2: case tListN: {
JSONList json(str); auto list(out.list());
for (unsigned int n = 0; n < v.listSize(); ++n) { for (unsigned int n = 0; n < v.listSize(); ++n) {
json.elem(); auto placeholder(list.placeholder());
printValueAsJSON(state, strict, *v.listElems()[n], str, context); printValueAsJSON(state, strict, *v.listElems()[n], placeholder, context);
} }
break; break;
} }
case tExternal: case tExternal:
v.external->printValueAsJSON(state, strict, str, context); v.external->printValueAsJSON(state, strict, out, context);
break; break;
case tFloat: case tFloat:
str << v.fpoint; out.write(v.fpoint);
break; break;
default: 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, 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()); throw TypeError(format("cannot convert %1% to JSON") % showType());
} }

View file

@ -8,73 +8,12 @@
namespace nix { namespace nix {
class JSONPlaceholder;
void printValueAsJSON(EvalState & state, bool strict, 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); void printValueAsJSON(EvalState & state, bool strict,
Value & v, std::ostream & str, PathSet & context);
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<typename T>
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);
}
};
} }

View file

@ -36,6 +36,7 @@ class Symbol;
struct Pos; struct Pos;
class EvalState; class EvalState;
class XMLWriter; class XMLWriter;
class JSONPlaceholder;
typedef long NixInt; typedef long NixInt;
@ -73,7 +74,7 @@ class ExternalValueBase
/* Print the value as JSON. Defaults to unconvertable, i.e. throws an error */ /* Print the value as JSON. Defaults to unconvertable, i.e. throws an error */
virtual void printValueAsJSON(EvalState & state, bool strict, 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 */ /* Print the value as XML. Defaults to unevaluated */
virtual void printValueAsXML(EvalState & state, bool strict, bool location, virtual void printValueAsXML(EvalState & state, bool strict, bool location,

171
src/libutil/json.cc Normal file
View file

@ -0,0 +1,171 @@
#include "json.hh"
#include <iomanip>
#include <cstring>
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);
}
}

183
src/libutil/json.hh Normal file
View file

@ -0,0 +1,183 @@
#pragma once
#include <iostream>
#include <vector>
#include <cassert>
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<JSONWriter *> 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<typename T>
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<typename T>
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<typename T>
void write(const T & v)
{
assertValid();
first = false;
toJSON(state->str, v);
}
JSONList list();
JSONObject object();
};
}

View file

@ -10,6 +10,7 @@
#include "store-api.hh" #include "store-api.hh"
#include "user-env.hh" #include "user-env.hh"
#include "util.hh" #include "util.hh"
#include "json.hh"
#include "value-to-json.hh" #include "value-to-json.hh"
#include "xml-writer.hh" #include "xml-writer.hh"
@ -860,26 +861,24 @@ static VersionDiff compareVersionAgainstSet(
static void queryJSON(Globals & globals, vector<DrvInfo> & elems) static void queryJSON(Globals & globals, vector<DrvInfo> & elems)
{ {
JSONObject topObj(cout); JSONObject topObj(cout, true);
for (auto & i : elems) { for (auto & i : elems) {
topObj.attr(i.attrPath); JSONObject pkgObj = topObj.object(i.attrPath);
JSONObject pkgObj(cout);
pkgObj.attr("name", i.name); pkgObj.attr("name", i.name);
pkgObj.attr("system", i.system); pkgObj.attr("system", i.system);
pkgObj.attr("meta"); JSONObject metaObj = pkgObj.object("meta");
JSONObject metaObj(cout);
StringSet metaNames = i.queryMetaNames(); StringSet metaNames = i.queryMetaNames();
for (auto & j : metaNames) { for (auto & j : metaNames) {
metaObj.attr(j); auto placeholder = metaObj.placeholder(j);
Value * v = i.queryMeta(j); Value * v = i.queryMeta(j);
if (!v) { if (!v) {
printMsg(lvlError, format("derivation %1% has invalid meta attribute %2%") % i.name % j); printMsg(lvlError, format("derivation %1% has invalid meta attribute %2%") % i.name % j);
cout << "null"; placeholder.write(nullptr);
} else { } else {
PathSet context; PathSet context;
printValueAsJSON(*globals.state, true, *v, cout, context); printValueAsJSON(*globals.state, true, *v, placeholder, context);
} }
} }
} }

View file

@ -2,6 +2,10 @@
#include "shared.hh" #include "shared.hh"
#include "store-api.hh" #include "store-api.hh"
#include "json.hh"
#include <iomanip> #include <iomanip>
#include <algorithm> #include <algorithm>
@ -12,12 +16,14 @@ struct CmdPathInfo : StorePathsCommand
bool showSize = false; bool showSize = false;
bool showClosureSize = false; bool showClosureSize = false;
bool showSigs = false; bool showSigs = false;
bool json = false;
CmdPathInfo() CmdPathInfo()
{ {
mkFlag('s', "size", "print size of the NAR dump of each path", &showSize); 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('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, "sigs", "show signatures", &showSigs);
mkFlag(0, "json", "produce JSON output", &json);
} }
std::string name() override std::string name() override
@ -41,6 +47,10 @@ struct CmdPathInfo : StorePathsCommand
"To check the existence of a path in a binary cache:", "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/" "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) for (auto & storePath : storePaths)
pathLen = std::max(pathLen, storePath.size()); pathLen = std::max(pathLen, storePath.size());
for (auto storePath : storePaths) { auto getClosureSize = [&](const Path & storePath) {
auto info = store->queryPathInfo(storePath); size_t totalSize = 0;
storePath = info->path; // FIXME: screws up padding 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) { for (auto storePath : storePaths) {
std::cout << '\t' << std::setw(11) << info->narSize; 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;
}
}
} }
}; };