198 lines
6.2 KiB
C++
198 lines
6.2 KiB
C++
// dump_json <kv|rows|manifest|csv> <file>
|
|
// Emits the parse result as JSON in the same shape as tests/mars_text/oracle/dump.py
|
|
// so the two can be compared structurally. Bytes >= 0x80 are written as
|
|
// \u00XX (one escape per byte) on both sides.
|
|
#include <cstdio>
|
|
#include <fstream>
|
|
#include <iterator>
|
|
#include <map>
|
|
#include <string>
|
|
|
|
#include "mars/text/csv.h"
|
|
#include "mars/text/flat_kv.h"
|
|
#include "mars/text/manifest.h"
|
|
|
|
using namespace mars::text;
|
|
|
|
namespace {
|
|
|
|
void json_string(std::string_view s, std::string& out) {
|
|
out.push_back('"');
|
|
for (unsigned char c : s) {
|
|
switch (c) {
|
|
case '"': out += "\\\""; break;
|
|
case '\\': out += "\\\\"; break;
|
|
case '\n': out += "\\n"; break;
|
|
case '\r': out += "\\r"; break;
|
|
case '\t': out += "\\t"; break;
|
|
default:
|
|
if (c < 0x20 || c >= 0x7f) {
|
|
char buf[8];
|
|
std::snprintf(buf, sizeof buf, "\\u%04x", c);
|
|
out += buf;
|
|
} else {
|
|
out.push_back(static_cast<char>(c));
|
|
}
|
|
}
|
|
}
|
|
out.push_back('"');
|
|
}
|
|
|
|
void json_token(const Token& t, std::string& out) {
|
|
switch (t.kind()) {
|
|
case ValueKind::Int: out += std::to_string(*t.as_int()); break;
|
|
case ValueKind::Float: {
|
|
char buf[64];
|
|
std::snprintf(buf, sizeof buf, "%.17g", *t.as_float());
|
|
std::string s = buf;
|
|
if (s.find_first_of(".eEn") == std::string::npos) s += ".0"; // keep it a JSON float
|
|
out += s;
|
|
break;
|
|
}
|
|
case ValueKind::Bool: out += *t.as_bool() ? "true" : "false"; break;
|
|
case ValueKind::Bareword:
|
|
case ValueKind::String: json_string(t.text, out); break;
|
|
}
|
|
}
|
|
|
|
void json_tokens(const std::vector<Token>& toks, std::string& out) {
|
|
out.push_back('[');
|
|
for (std::size_t i = 0; i < toks.size(); ++i) {
|
|
if (i) out.push_back(',');
|
|
json_token(toks[i], out);
|
|
}
|
|
out.push_back(']');
|
|
}
|
|
|
|
// kv value: null / scalar / list, matching the reference reader's shape
|
|
void json_kv_value(const std::vector<Token>& toks, std::string& out) {
|
|
if (toks.empty()) out += "null";
|
|
else if (toks.size() == 1) json_token(toks[0], out);
|
|
else json_tokens(toks, out);
|
|
}
|
|
|
|
std::string dump_kv(const FlatKV& kv) {
|
|
std::map<std::string, const KvEntry*> last; // exact key, last wins
|
|
for (const KvEntry& e : kv.entries()) last[e.key] = &e;
|
|
std::string out = "{\"values\":{";
|
|
bool first = true;
|
|
for (const auto& [key, e] : last) {
|
|
if (!first) out.push_back(',');
|
|
first = false;
|
|
json_string(key, out);
|
|
out.push_back(':');
|
|
json_kv_value(e->values, out);
|
|
}
|
|
out += "},\"duplicates\":{";
|
|
first = true;
|
|
for (const auto& [key, lines] : kv.duplicates()) {
|
|
if (!first) out.push_back(',');
|
|
first = false;
|
|
json_string(key, out);
|
|
out += ":[";
|
|
for (std::size_t i = 0; i < lines.size(); ++i) out += (i ? "," : "") + std::to_string(lines[i]);
|
|
out.push_back(']');
|
|
}
|
|
out += "}}";
|
|
return out;
|
|
}
|
|
|
|
std::string dump_rows(const Rows& rows) {
|
|
std::string out = "{\"rows\":[";
|
|
for (std::size_t i = 0; i < rows.size(); ++i) {
|
|
if (i) out.push_back(',');
|
|
json_tokens(rows[i].tokens, out);
|
|
}
|
|
out += "]}";
|
|
return out;
|
|
}
|
|
|
|
const char* problem_kind(Problem::Kind k) {
|
|
switch (k) {
|
|
case Problem::Kind::Unrecognised: return "unrecognised";
|
|
case Problem::Kind::DuplicateId: return "duplicate_id";
|
|
case Problem::Kind::DeletedAndAssigned: return "deleted_and_assigned";
|
|
case Problem::Kind::UnbalancedQuote: return "unbalanced_quote";
|
|
case Problem::Kind::UnterminatedQuotedCell: return "unterminated_quoted_cell";
|
|
}
|
|
return "?";
|
|
}
|
|
|
|
std::string dump_manifest(const Result<Manifest>& r) {
|
|
std::string out = "{\"entries\":[";
|
|
bool first = true;
|
|
for (const ManifestEntry& e : r.value.entries()) {
|
|
if (!first) out.push_back(',');
|
|
first = false;
|
|
out += "[" + std::to_string(e.id) + ",";
|
|
json_string(e.name, out);
|
|
out.push_back(']');
|
|
}
|
|
out += "],\"deleted\":[";
|
|
const auto& del = r.value.deleted();
|
|
for (std::size_t i = 0; i < del.size(); ++i) out += (i ? "," : "") + std::to_string(del[i]);
|
|
out += "],\"problems\":[";
|
|
first = true;
|
|
for (const Problem& p : r.problems) {
|
|
if (!first) out.push_back(',');
|
|
first = false;
|
|
out += "{\"line\":" + (p.line ? std::to_string(p.line) : std::string("null")) + ",\"kind\":\"" + problem_kind(p.kind) +
|
|
"\",\"id\":" + (p.id >= 0 ? std::to_string(p.id) : std::string("null")) + "}";
|
|
}
|
|
out += "]}";
|
|
return out;
|
|
}
|
|
|
|
std::string dump_csv(const Csv& csv) {
|
|
std::string out = "{\"header\":";
|
|
if (csv.header) {
|
|
out.push_back('[');
|
|
for (std::size_t i = 0; i < csv.header->size(); ++i) {
|
|
if (i) out.push_back(',');
|
|
json_string((*csv.header)[i], out);
|
|
}
|
|
out.push_back(']');
|
|
} else {
|
|
out += "null";
|
|
}
|
|
out += ",\"rows\":[";
|
|
for (std::size_t i = 0; i < csv.rows.size(); ++i) {
|
|
if (i) out.push_back(',');
|
|
out.push_back('[');
|
|
for (std::size_t j = 0; j < csv.rows[i].cells.size(); ++j) {
|
|
if (j) out.push_back(',');
|
|
json_string(csv.rows[i].cells[j], out);
|
|
}
|
|
out.push_back(']');
|
|
}
|
|
out += "]}";
|
|
return out;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char** argv) {
|
|
if (argc != 3) {
|
|
std::fprintf(stderr, "usage: dump_json <kv|rows|manifest|csv> <file>\n");
|
|
return 2;
|
|
}
|
|
std::ifstream in(argv[2], std::ios::binary);
|
|
if (!in) {
|
|
std::fprintf(stderr, "cannot read %s\n", argv[2]);
|
|
return 2;
|
|
}
|
|
std::string text((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
|
|
std::string kind = argv[1];
|
|
std::string out;
|
|
if (kind == "kv") out = dump_kv(parse_flat_kv(text).value);
|
|
else if (kind == "rows") out = dump_rows(parse_rows(text).value);
|
|
else if (kind == "manifest") out = dump_manifest(parse_manifest(text));
|
|
else if (kind == "csv") out = dump_csv(parse_csv(text).value);
|
|
else {
|
|
std::fprintf(stderr, "unknown kind %s\n", argv[1]);
|
|
return 2;
|
|
}
|
|
std::fwrite(out.data(), 1, out.size(), stdout);
|
|
std::fputc('\n', stdout);
|
|
return 0;
|
|
}
|