* docs: new design for the documentation site Apply the brand to the Material for MkDocs site: Inter and JetBrains Mono served by the site (upright and real italic), brand colours, blue header band with the syntax pattern, wordmark in the header, new home page (hero, cards, animated feature tour), 404 page, polish for tabs, search, footer, focus rings, tables and code blocks, and theme features (code copy, previous/next footer, TOC follow, search suggestions). Draw the number railroad diagram, the parser callback events and the iterator illustrations as inline SVGs that follow the light and dark scheme. The generator scripts are in docs/mkdocs/scripts/figures; the feature slideshow (json-light.svg, json-dark.svg, replacing json.gif on the home page) is built by docs/mkdocs/scripts/slideshow. Decision nodes in five flowcharts become hexagons, which need far less room. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: add figures, table captions and a macro chooser - Figures with numbered captions: SAX stepper, json_view layout and edit stepper, exception map, JSON pointer and patch walkers, QA pipeline, scaling and binary size charts, roadmap lanes, release timeline, integration decision tree and namespace builder - Table captions (nj-table-caption) on 9 pages - Compile times: modules and PCH charts, filesystem section, re-measured Figure 1, links between features/modules.md and compile_times.md - Interactive macro chooser for features/arbitrary_types.md - Link check: exclude URLs that answer the plugin with 400/401/419 or are dead (customers.md) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: macro chooser generates adl_serializer for non-default-constructible types Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: cross-link concept pages and API pages in See also sections Add See also links in both directions between the concept pages (features, home, integration) and the API pages, write API links in code font, give every entry a comment after a dash, and show the callback event figure on the parser callbacks page. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: address review comments on the new design - Home: add a "Latest release" button - Sponsors: update the list and group it by tier - Features: list all feature pages on the index page - Releases: add highlights per release, link the 4.0 roadmap, add the first commit to the timeline - Exceptions: prefix all example messages with the exception name - API: no code font for format names in the See also links of the to_/from_ pages - Merge Patch: show the document before, the patch, and the result - Replace "--" with typographic dashes, mark external links with an icon, center Mermaid diagrams Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
586 lines
29 KiB
JavaScript
586 lines
29 KiB
JavaScript
/* figures: macro chooser for the NLOHMANN_DEFINE_TYPE_* / NLOHMANN_DEFINE_DERIVED_TYPE_* macros.
|
|
Usage: <div class="nj-macro-chooser" data-variant="full|compact" data-preset="base=yes,keys=custom" markdown>
|
|
with a static fallback list inside (shown without JavaScript). Links marked with { data-nj-mc="KEY" } in the
|
|
fallback are reused for the reference links, so each embedding page resolves its own relative URLs. Keys:
|
|
intrusive, non_intrusive, derived, with_names, third_party, non_default.
|
|
The rules follow include/nlohmann/detail/macro_scope.hpp: WITH_NAMES takes "key", member pairs and no empty
|
|
member list; WITH_DEFAULT and the plain variants default-construct the type in from_json/get; ONLY_SERIALIZE
|
|
generates to_json only; a type that is not default constructible and converts both ways gets no macro but an
|
|
adl_serializer specialization whose from_json returns the value; DERIVED_TYPE calls the base type's to_json/from_json first; at most 63 members (31 with
|
|
WITH_NAMES). The generated programs were compiled and run with clang++ -std=c++11 against include/. */
|
|
(function () {
|
|
"use strict";
|
|
|
|
var QUESTIONS = [
|
|
{ id: "edit", legend: "Can you edit the class?", options: [["yes", "Yes"], ["no", "No, it is third-party"]] },
|
|
{ id: "access", legend: "Member access", options: [["public", "All public"], ["private", "Some private"]] },
|
|
{ id: "base", legend: "Base class", options: [["no", "None"], ["yes", "Derived from a base"]] },
|
|
{ id: "dir", legend: "Direction", options: [["both", "To and from JSON"], ["to", "Only to JSON"]] },
|
|
{ id: "missing", legend: "Missing keys when parsing", options: [["throw", "Throw"], ["default", "Keep defaults"]] },
|
|
{ id: "keys", legend: "JSON keys", options: [["members", "Member names"], ["custom", "Custom names"]] },
|
|
{ id: "ctor", legend: "Default constructible?", options: [["yes", "Yes"], ["no", "No"]] }
|
|
];
|
|
|
|
var KEYWORDS = ("alignas alignof and asm auto bool break case catch char class const constexpr continue decltype default " +
|
|
"delete do double else enum explicit export extern false float for friend goto if inline int long mutable namespace " +
|
|
"new noexcept not nullptr operator or private protected public register return short signed sizeof static " +
|
|
"struct switch template this throw true try typedef typeid typename union unsigned using virtual void volatile " +
|
|
"while").split(" ");
|
|
var HL_TYPES = ["int", "bool", "double", "float", "char", "void", "auto", "unsigned", "long", "short", "signed"];
|
|
var HL_CONST = ["true", "false", "nullptr"];
|
|
var IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
var MEMBER_TYPE = /^[A-Za-z_][A-Za-z0-9_:<>, ]*$/;
|
|
|
|
var counter = 0;
|
|
|
|
function el(tag, attrs, text) {
|
|
var e = document.createElement(tag);
|
|
if (attrs) {
|
|
Object.keys(attrs).forEach(function (k) { e.setAttribute(k, attrs[k]); });
|
|
}
|
|
if (text !== undefined) e.textContent = text;
|
|
return e;
|
|
}
|
|
|
|
function esc(s) {
|
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
}
|
|
|
|
/* small C++ tokenizer emitting the Pygments classes the site already colours */
|
|
function highlight(code) {
|
|
var out = "";
|
|
var re = /(\/\/[^\n]*)|(#include)|(<[a-z_/.]+\.hpp>|<[a-z]+>)|("(?:[^"\\\n]|\\.)*")|(\b\d+\b)|([A-Za-z_][A-Za-z0-9_]*)|(::|<<|==|[=:<>&])|([{}()\[\];,.!])|(\s+)|(.)/g;
|
|
var m, prev = "";
|
|
while ((m = re.exec(code)) !== null) {
|
|
var t = m[0];
|
|
if (m[1]) out += '<span class="c1">' + esc(t) + "</span>";
|
|
else if (m[2]) out += '<span class="cp">' + esc(t) + "</span>";
|
|
else if (m[3] && prev === "#include") out += '<span class="cpf">' + esc(t) + "</span>";
|
|
else if (m[4]) out += '<span class="s">' + esc(t) + "</span>";
|
|
else if (m[5]) out += '<span class="mi">' + esc(t) + "</span>";
|
|
else if (m[6]) {
|
|
var cls = "n";
|
|
if (HL_CONST.indexOf(t) >= 0) cls = "kc";
|
|
else if (HL_TYPES.indexOf(t) >= 0) cls = "kt";
|
|
else if (KEYWORDS.indexOf(t) >= 0) cls = "k";
|
|
else if (prev === "namespace") cls = "nn";
|
|
else if (prev === "struct" || prev === "class") cls = "nc";
|
|
out += '<span class="' + cls + '">' + esc(t) + "</span>";
|
|
} else if (m[7]) out += '<span class="o">' + esc(t) + "</span>";
|
|
else if (m[8]) out += '<span class="p">' + esc(t) + "</span>";
|
|
else out += esc(t);
|
|
if (!m[9]) prev = (m[3] && prev === "#include") ? "" : t;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function parsePreset(s) {
|
|
var r = {};
|
|
(s || "").split(",").forEach(function (kv) {
|
|
var p = kv.split("=");
|
|
if (p.length === 2) r[p[0].trim()] = p[1].trim();
|
|
});
|
|
return r;
|
|
}
|
|
|
|
/* ---- the decision logic, independent of the DOM ---- */
|
|
function decide(st) {
|
|
var res = { errors: [], warnings: [], macro: null, reason: "", place: "", ref: null };
|
|
var n = st.members.length;
|
|
var names = st.keys === "custom";
|
|
|
|
if (st.edit === "no" && st.access === "private") {
|
|
res.errors.push({ text: "No macro can reach private members from outside the class. Write to_json/from_json by hand using the public interface, for example in a specialization of adl_serializer.", link: "third_party", linkText: "How do I convert third-party types?" });
|
|
}
|
|
var adl = st.dir === "both" && st.ctor === "no";
|
|
if (!adl && names && n === 0) {
|
|
res.errors.push({ text: "The WITH_NAMES macros need at least one key and member pair. Without members there is nothing to rename: choose Member names." });
|
|
}
|
|
var limit = names ? 31 : 63;
|
|
if (!adl && n > limit) {
|
|
res.errors.push({ text: "The macros support at most " + limit + " members" + (names ? " with custom names" : "") + ". Write to_json/from_json by hand for larger types." });
|
|
}
|
|
|
|
/* identifier checks */
|
|
if (!IDENT.test(st.type) || KEYWORDS.indexOf(st.type) >= 0) res.errors.push({ text: "The type name must be a C++ identifier." });
|
|
if (st.base === "yes" && (!IDENT.test(st.baseName) || KEYWORDS.indexOf(st.baseName) >= 0)) res.errors.push({ text: "The base class name must be a C++ identifier." });
|
|
if (st.base === "yes" && st.baseName === st.type) res.errors.push({ text: "The base class needs a different name than the type." });
|
|
var seenName = {}, seenKey = {};
|
|
st.members.forEach(function (mb, i) {
|
|
var label = "Member " + (i + 1);
|
|
if (!IDENT.test(mb.name) || KEYWORDS.indexOf(mb.name) >= 0) res.errors.push({ text: label + ": the name must be a C++ identifier." });
|
|
else if (seenName[mb.name]) res.errors.push({ text: label + ": the name " + mb.name + " is used twice." });
|
|
else if (mb.name === st.type) res.errors.push({ text: label + ": a member cannot have the name of its class." });
|
|
seenName[mb.name] = true;
|
|
if (!MEMBER_TYPE.test(mb.mtype.trim())) res.errors.push({ text: label + ": enter a type such as int, std::string or std::vector<int>." });
|
|
var key = names ? mb.key : mb.name;
|
|
if (seenKey[key]) res.warnings.push(label + ": the JSON key \"" + key + "\" is used twice; the later member overwrites the earlier one in to_json.");
|
|
seenKey[key] = true;
|
|
if (st.base === "yes" && key === "id") res.warnings.push(label + ": the key \"id\" is also written by the example base class; the derived member overwrites it.");
|
|
});
|
|
|
|
if (st.edit === "no" && st.access === "private") return res;
|
|
|
|
if (adl) {
|
|
res.adl = true;
|
|
res.intrusive = st.access === "private";
|
|
res.ref = "non_default";
|
|
res.reason = "Every macro that generates from_json default-constructs the type, so none fits. A specialization of adl_serializer returns the value from from_json instead" +
|
|
(st.access === "private" ? "; the class befriends it to reach the private members" : "") + ".";
|
|
res.place = "Write the specialization in namespace nlohmann, after the class.";
|
|
return res;
|
|
}
|
|
|
|
var intrusive = st.access === "private";
|
|
var parts = ["NLOHMANN_DEFINE", st.base === "yes" ? "DERIVED_TYPE" : "TYPE", intrusive ? "INTRUSIVE" : "NON_INTRUSIVE"];
|
|
if (st.dir === "to") parts.push("ONLY_SERIALIZE");
|
|
else if (st.missing === "default") parts.push("WITH_DEFAULT");
|
|
if (names) parts.push("WITH_NAMES");
|
|
res.macro = parts.join("_");
|
|
res.intrusive = intrusive;
|
|
res.ref = names ? "with_names" : (st.base === "yes" ? "derived" : (intrusive ? "intrusive" : "non_intrusive"));
|
|
|
|
var why = [];
|
|
if (intrusive) why.push("Private members need the macro inside the class, where its friend functions can access them");
|
|
else if (st.edit === "no") why.push("You cannot edit the class, so the macro goes next to it in its namespace, which works because all members are public");
|
|
else why.push("All members are public, so the macro can stay outside the class, in its namespace");
|
|
if (st.base === "yes") why.push("the conversion of " + (st.baseName || "the base") + " runs first");
|
|
if (st.dir === "to") why.push("only to_json is generated");
|
|
else if (st.missing === "default") why.push("missing keys keep the value of a default-constructed " + st.type);
|
|
else why.push("a missing key throws out_of_range.403");
|
|
if (names) why.push("each member is preceded by its JSON key");
|
|
res.reason = why.join("; ") + ".";
|
|
res.place = intrusive ? "Write the macro inside the class body." : "Write the macro after the class, in the same namespace.";
|
|
if (st.dir === "to" && st.ctor === "no") res.warnings.push("The type is not default constructible: fine for ONLY_SERIALIZE, but j.get<" + st.type + ">() will not compile.");
|
|
return res;
|
|
}
|
|
|
|
/* C++ string literal; control characters as three-digit octal escapes, which cannot run into the next character */
|
|
function cppString(s) {
|
|
return '"' + s.replace(/[\\"\u0000-\u001f\u007f]/g, function (c) {
|
|
if (c === "\\" || c === '"') return "\\" + c;
|
|
var o = c.charCodeAt(0).toString(8);
|
|
return "\\" + ("00" + o).slice(-3);
|
|
}) + '"';
|
|
}
|
|
|
|
function macroCall(name, head, pairs, indent) {
|
|
var one = name + "(" + head.concat(pairs).join(", ") + ")";
|
|
if (one.length + indent.length <= 88) return indent + one;
|
|
var lines = [indent + name + "(" + head.join(", ") + (pairs.length ? "," : ")")];
|
|
pairs.forEach(function (p, i) {
|
|
lines.push(indent + " " + p + (i === pairs.length - 1 ? ")" : ","));
|
|
});
|
|
return lines.join("\n");
|
|
}
|
|
|
|
function generate(st, res) {
|
|
var ns = "ns";
|
|
var T = st.type, B = st.baseName;
|
|
var priv = res.intrusive;
|
|
var names = st.keys === "custom";
|
|
var suffix = st.dir === "to" ? "_ONLY_SERIALIZE" : (st.missing === "default" ? "_WITH_DEFAULT" : "");
|
|
var adl = !!res.adl;
|
|
var noCtor = st.ctor === "no" && (st.members.length > 0 || st.base === "yes");
|
|
var L = [];
|
|
L.push("#include <iostream>");
|
|
L.push("#include <nlohmann/json.hpp>");
|
|
L.push("");
|
|
L.push("using nlohmann::json;");
|
|
L.push("");
|
|
L.push("namespace " + ns);
|
|
L.push("{");
|
|
if (st.base === "yes") {
|
|
L.push("// stand-in for your base class: it must be serializable itself");
|
|
if (priv) {
|
|
L.push("class " + B);
|
|
L.push("{");
|
|
L.push(" private:");
|
|
L.push(" int id{};");
|
|
L.push("");
|
|
L.push(" public:");
|
|
L.push(" NLOHMANN_DEFINE_TYPE_INTRUSIVE" + suffix + "(" + B + ", id)");
|
|
L.push("};");
|
|
} else {
|
|
L.push("struct " + B);
|
|
L.push("{");
|
|
L.push(" int id{};");
|
|
L.push("};");
|
|
L.push("NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE" + suffix + "(" + B + ", id)");
|
|
}
|
|
L.push("");
|
|
}
|
|
var kw = priv ? "class " : "struct ";
|
|
var inherit = st.base === "yes" ? (priv ? " : public " + B : " : " + B) : "";
|
|
L.push(kw + T + inherit);
|
|
L.push("{");
|
|
if (priv) L.push(" private:");
|
|
st.members.forEach(function (mb) {
|
|
L.push(" " + mb.mtype.trim() + " " + mb.name + (noCtor ? ";" : "{};"));
|
|
});
|
|
var head = [T];
|
|
if (st.base === "yes") head.push(B);
|
|
var pairs = st.members.map(function (mb) {
|
|
return names ? cppString(mb.key) + ", " + mb.name : mb.name;
|
|
});
|
|
if (noCtor || priv) {
|
|
if (st.members.length) L.push("");
|
|
if (priv) L.push(" public:");
|
|
}
|
|
if (noCtor) {
|
|
var plist = st.members.map(function (mb) { return mb.mtype.trim() + " " + mb.name + "_"; });
|
|
var ilist = st.members.map(function (mb) { return mb.name + "(" + mb.name + "_)"; });
|
|
if (st.base === "yes") {
|
|
plist.unshift(B + " base_");
|
|
ilist.unshift(B + "(base_)");
|
|
}
|
|
L.push(" " + T + "(" + plist.join(", ") + ")");
|
|
L.push(" : " + ilist.join(", "));
|
|
L.push(" {}");
|
|
if (priv) L.push("");
|
|
}
|
|
if (adl) {
|
|
if (priv) {
|
|
L.push(" friend struct nlohmann::adl_serializer<" + T + ">;");
|
|
}
|
|
L.push("};");
|
|
L.push("} // namespace " + ns);
|
|
L.push("");
|
|
L.push("namespace nlohmann");
|
|
L.push("{");
|
|
L.push("template <>");
|
|
L.push("struct adl_serializer<" + ns + "::" + T + ">");
|
|
L.push("{");
|
|
L.push(" // note: the return type is the value, not void, and there is no default construction");
|
|
L.push(" static " + ns + "::" + T + " from_json(const json& j)");
|
|
L.push(" {");
|
|
var args = st.members.map(function (mb) {
|
|
var key = names ? cppString(mb.key) : cppString(mb.name);
|
|
var ty = mb.mtype.trim();
|
|
return st.missing === "default"
|
|
? "j.value(" + key + ", " + ty + "{})"
|
|
: "j.at(" + key + ").get<" + ty + ">()";
|
|
});
|
|
if (st.base === "yes") args.unshift("j.get<" + ns + "::" + B + ">()");
|
|
L.push(" return " + ns + "::" + T + "{" + args.join(", ") + "};");
|
|
L.push(" }");
|
|
L.push("");
|
|
L.push(" // you must provide to_json as well: the specialization replaces the default one");
|
|
L.push(" static void to_json(json& j, const " + ns + "::" + T + "& t)");
|
|
L.push(" {");
|
|
if (st.base === "yes") L.push(" j = static_cast<const " + ns + "::" + B + "&>(t);");
|
|
else L.push(" j = json::object();");
|
|
st.members.forEach(function (mb) {
|
|
L.push(" j[" + (names ? cppString(mb.key) : cppString(mb.name)) + "] = t." + mb.name + ";");
|
|
});
|
|
L.push(" }");
|
|
L.push("};");
|
|
L.push("} // namespace nlohmann");
|
|
} else if (priv) {
|
|
L.push(macroCall(res.macro, head, pairs, " "));
|
|
L.push("};");
|
|
} else {
|
|
L.push("};");
|
|
L.push("");
|
|
L.push(macroCall(res.macro, head, pairs, ""));
|
|
}
|
|
if (!adl) L.push("} // namespace " + ns);
|
|
L.push("");
|
|
L.push("int main()");
|
|
L.push("{");
|
|
var Q = ns + "::" + T;
|
|
if (noCtor) {
|
|
var init = st.members.map(function (mb) { return mb.mtype.trim() + "{}"; });
|
|
if (st.base === "yes") init.unshift(ns + "::" + B + "{}");
|
|
L.push(" " + Q + " p{" + init.join(", ") + "};");
|
|
} else {
|
|
L.push(" " + Q + " p{};");
|
|
}
|
|
L.push(" json j = p; // to_json");
|
|
L.push(" std::cout << j.dump() << '\\n';");
|
|
if (st.dir === "both") {
|
|
L.push("");
|
|
L.push(" auto p2 = j.get<" + Q + ">(); // from_json");
|
|
L.push(" std::cout << (json(p2) == j) << '\\n'; // round trip: 1");
|
|
if (st.missing === "default") {
|
|
L.push("");
|
|
L.push(" // missing keys keep the defaults");
|
|
L.push(" std::cout << json(json::object().get<" + Q + ">()).dump() << '\\n';");
|
|
} else {
|
|
L.push(" // a missing key throws json::out_of_range (403)");
|
|
}
|
|
}
|
|
L.push("}");
|
|
return L.join("\n") + "\n";
|
|
}
|
|
|
|
function copyText(text, done) {
|
|
function fallback() {
|
|
var ta = el("textarea", { readonly: "", "aria-hidden": "true" });
|
|
ta.value = text;
|
|
ta.style.position = "fixed";
|
|
ta.style.opacity = "0";
|
|
document.body.appendChild(ta);
|
|
ta.select();
|
|
var ok = false;
|
|
try { ok = document.execCommand("copy"); } catch (e) { ok = false; }
|
|
document.body.removeChild(ta);
|
|
done(ok);
|
|
}
|
|
if (navigator.clipboard && window.isSecureContext) {
|
|
navigator.clipboard.writeText(text).then(function () { done(true); }, fallback);
|
|
} else {
|
|
fallback();
|
|
}
|
|
}
|
|
|
|
/* ---- DOM ---- */
|
|
function setup(root) {
|
|
if (root.dataset.njMcReady) return;
|
|
root.dataset.njMcReady = "1";
|
|
var uid = "nj-mc-" + (++counter);
|
|
var variant = root.getAttribute("data-variant") || "full";
|
|
var preset = parsePreset(root.getAttribute("data-preset"));
|
|
|
|
var links = {};
|
|
Array.prototype.forEach.call(root.querySelectorAll("a[data-nj-mc]"), function (a) {
|
|
links[a.getAttribute("data-nj-mc")] = a.getAttribute("href");
|
|
});
|
|
|
|
var st = { edit: "yes", access: "public", base: "no", dir: "both", missing: "throw", keys: "members", ctor: "yes",
|
|
type: "person", baseName: "entity",
|
|
members: [{ mtype: "std::string", name: "name", key: "name" }, { mtype: "int", name: "age", key: "age" }] };
|
|
QUESTIONS.forEach(function (q) {
|
|
if (preset[q.id] && q.options.some(function (o) { return o[0] === preset[q.id]; })) st[q.id] = preset[q.id];
|
|
});
|
|
|
|
var fallback = root.querySelector(".nj-mc-fallback");
|
|
if (fallback) fallback.hidden = true;
|
|
|
|
var app = el("div", { "class": "nj-mc-app" });
|
|
var form = el("form", { "class": "nj-mc-form", "aria-label": "Macro chooser options" });
|
|
form.addEventListener("submit", function (e) { e.preventDefault(); });
|
|
|
|
var qgrid = el("div", { "class": "nj-mc-questions" });
|
|
var notes = {};
|
|
var groups = {};
|
|
QUESTIONS.forEach(function (q) {
|
|
var fs = el("fieldset", { "class": "nj-mc-q" });
|
|
fs.appendChild(el("legend", null, q.legend));
|
|
var seg = el("div", { "class": "nj-mc-seg" });
|
|
q.options.forEach(function (o) {
|
|
var id = uid + "-" + q.id + "-" + o[0];
|
|
var inp = el("input", { type: "radio", id: id, name: uid + "-" + q.id, value: o[0] });
|
|
if (st[q.id] === o[0]) inp.checked = true;
|
|
inp.addEventListener("change", function () { st[q.id] = o[0]; update(); });
|
|
seg.appendChild(inp);
|
|
seg.appendChild(el("label", { "for": id }, o[1]));
|
|
});
|
|
fs.appendChild(seg);
|
|
var note = el("p", { "class": "nj-mc-qnote", id: uid + "-" + q.id + "-note" });
|
|
note.hidden = true;
|
|
fs.appendChild(note);
|
|
fs.setAttribute("aria-describedby", note.id);
|
|
notes[q.id] = note;
|
|
groups[q.id] = fs;
|
|
qgrid.appendChild(fs);
|
|
});
|
|
form.appendChild(qgrid);
|
|
|
|
/* type editor */
|
|
var editor = el("div", { "class": "nj-mc-editor" });
|
|
var names = el("div", { "class": "nj-mc-names" });
|
|
function textField(label, value, onInput, cls) {
|
|
var id = uid + "-f" + (++counter);
|
|
var w = el("div", { "class": "nj-mc-field" + (cls ? " " + cls : "") });
|
|
w.appendChild(el("label", { "for": id }, label));
|
|
var inp = el("input", { type: "text", id: id, spellcheck: "false", autocomplete: "off", autocapitalize: "off" });
|
|
inp.value = value;
|
|
inp.addEventListener("input", function () { onInput(inp.value); update(); });
|
|
w.appendChild(inp);
|
|
return w;
|
|
}
|
|
names.appendChild(textField("Type name", st.type, function (v) { st.type = v.trim(); }));
|
|
var baseField = textField("Base class", st.baseName, function (v) { st.baseName = v.trim(); }, "nj-mc-basefield");
|
|
names.appendChild(baseField);
|
|
editor.appendChild(names);
|
|
|
|
var mtable = el("div", { "class": "nj-mc-members", role: "group", "aria-label": "Member variables" });
|
|
var mhead = el("div", { "class": "nj-mc-mrow nj-mc-mhead", "aria-hidden": "true" });
|
|
mhead.appendChild(el("span", null, "Member type"));
|
|
mhead.appendChild(el("span", null, "Member name"));
|
|
var keyHead = el("span", { "class": "nj-mc-keycol" }, "JSON key");
|
|
mhead.appendChild(keyHead);
|
|
mhead.appendChild(el("span"));
|
|
mtable.appendChild(mhead);
|
|
var mbody = el("div", { "class": "nj-mc-mbody" });
|
|
mtable.appendChild(mbody);
|
|
var addBtn = el("button", { type: "button", "class": "nj-mc-add" }, "Add member");
|
|
addBtn.addEventListener("click", function () {
|
|
var k = st.members.length + 1;
|
|
var nm = "member" + k;
|
|
while (st.members.some(function (mb) { return mb.name === nm; })) nm = "member" + (++k);
|
|
st.members.push({ mtype: "int", name: nm, key: nm });
|
|
renderMembers();
|
|
update();
|
|
var rows = mbody.querySelectorAll(".nj-mc-mrow");
|
|
var last = rows[rows.length - 1];
|
|
if (last) last.querySelector("input").focus();
|
|
});
|
|
editor.appendChild(mtable);
|
|
editor.appendChild(addBtn);
|
|
if (variant === "full") form.appendChild(editor);
|
|
|
|
function renderMembers() {
|
|
mbody.textContent = "";
|
|
st.members.forEach(function (mb, i) {
|
|
var row = el("div", { "class": "nj-mc-mrow" });
|
|
function cell(prop, label, cls) {
|
|
var inp = el("input", { type: "text", spellcheck: "false", autocomplete: "off", autocapitalize: "off",
|
|
"aria-label": label + " of member " + (i + 1), placeholder: label, "class": cls || "" });
|
|
inp.value = mb[prop];
|
|
inp.addEventListener("input", function () {
|
|
var keyFollows = prop === "name" && mb.key === mb.name;
|
|
mb[prop] = prop === "key" ? inp.value : inp.value.trim();
|
|
if (keyFollows) {
|
|
mb.key = mb.name;
|
|
var k = row.querySelector(".nj-mc-keycol");
|
|
if (k) k.value = mb.key;
|
|
}
|
|
update();
|
|
});
|
|
row.appendChild(inp);
|
|
}
|
|
cell("mtype", "Type");
|
|
cell("name", "Name");
|
|
cell("key", "JSON key", "nj-mc-keycol");
|
|
var rm = el("button", { type: "button", "class": "nj-mc-rm", "aria-label": "Remove member " + (i + 1), title: "Remove member" });
|
|
rm.appendChild(el("span", { "aria-hidden": "true" }, "\u00d7"));
|
|
rm.addEventListener("click", function () {
|
|
st.members.splice(i, 1);
|
|
renderMembers();
|
|
update();
|
|
var btns = mbody.querySelectorAll(".nj-mc-rm");
|
|
(btns[Math.min(i, btns.length - 1)] || addBtn).focus();
|
|
});
|
|
row.appendChild(rm);
|
|
mbody.appendChild(row);
|
|
});
|
|
}
|
|
renderMembers();
|
|
app.appendChild(form);
|
|
|
|
/* output */
|
|
var out = el("div", { "class": "nj-mc-out" });
|
|
var result = el("div", { "class": "nj-mc-result", role: "status", "aria-live": "polite" });
|
|
out.appendChild(result);
|
|
var hints = el("ul", { "class": "nj-mc-hints" });
|
|
out.appendChild(hints);
|
|
var codeWrap = el("div", { "class": "nj-mc-code" });
|
|
var bar = el("div", { "class": "nj-mc-codebar" });
|
|
bar.appendChild(el("span", { "class": "nj-mc-codetitle" }, "Generated code (C++11)"));
|
|
var copyBtn = el("button", { type: "button", "class": "nj-mc-copy", "aria-label": "Copy generated code" }, "Copy");
|
|
var copyStatus = el("span", { "class": "nj-mc-copystatus", role: "status", "aria-live": "polite" });
|
|
bar.appendChild(copyStatus);
|
|
bar.appendChild(copyBtn);
|
|
codeWrap.appendChild(bar);
|
|
var hl = el("div", { "class": "language-cpp highlight" });
|
|
var pre = el("pre");
|
|
var code = el("code", { tabindex: "0", "aria-label": "Generated C++ code" });
|
|
pre.appendChild(code);
|
|
hl.appendChild(pre);
|
|
codeWrap.appendChild(hl);
|
|
if (variant === "full") out.appendChild(codeWrap);
|
|
app.appendChild(out);
|
|
root.appendChild(app);
|
|
|
|
var currentCode = "";
|
|
var statusTimer = null;
|
|
copyBtn.addEventListener("click", function () {
|
|
copyText(currentCode, function (ok) {
|
|
copyStatus.textContent = ok ? "Copied" : "Press Ctrl+C / Cmd+C to copy";
|
|
if (!ok) {
|
|
var r = document.createRange();
|
|
r.selectNodeContents(code);
|
|
var s = window.getSelection();
|
|
s.removeAllRanges();
|
|
s.addRange(r);
|
|
}
|
|
clearTimeout(statusTimer);
|
|
statusTimer = setTimeout(function () { copyStatus.textContent = ""; }, 2500);
|
|
});
|
|
});
|
|
|
|
function setNote(id, text) {
|
|
notes[id].hidden = !text;
|
|
notes[id].textContent = text || "";
|
|
}
|
|
|
|
function addLink(parent, key, text) {
|
|
if (!key || !links[key]) return;
|
|
parent.appendChild(document.createTextNode(" "));
|
|
parent.appendChild(el("a", { href: links[key] }, text));
|
|
}
|
|
|
|
function update() {
|
|
var dirTo = st.dir === "to";
|
|
groups.missing.disabled = dirTo;
|
|
groups.missing.classList.toggle("nj-mc-off", dirTo);
|
|
setNote("missing", dirTo ? "Not applicable: ONLY_SERIALIZE generates no from_json." : "");
|
|
setNote("access", st.edit === "no" && st.access === "private" ? "Private members of a class you cannot edit are out of reach for every macro." : "");
|
|
setNote("ctor", st.ctor === "no" && !dirTo ? "The macros default-construct the type when parsing: this gives an adl_serializer specialization instead." : "");
|
|
baseField.hidden = st.base !== "yes";
|
|
mtable.classList.toggle("nj-mc-custom", st.keys === "custom");
|
|
keyHead.hidden = st.keys !== "custom";
|
|
|
|
var res = decide(st);
|
|
result.textContent = "";
|
|
hints.textContent = "";
|
|
if ((res.macro || res.adl) && !res.errors.length) {
|
|
result.className = "nj-mc-result";
|
|
result.appendChild(el("p", { "class": "nj-mc-label" }, res.adl ? "No macro fits, write a serializer" : "Recommended macro"));
|
|
var name = el("p", { "class": "nj-mc-macro" });
|
|
name.appendChild(el("code", null, res.adl ? "adl_serializer<" + st.type + ">" : res.macro));
|
|
result.appendChild(name);
|
|
var why = el("p", { "class": "nj-mc-why" }, res.reason + " " + res.place);
|
|
addLink(why, res.ref, res.adl ? "Details" : "Reference");
|
|
result.appendChild(why);
|
|
} else {
|
|
result.className = "nj-mc-result nj-mc-bad";
|
|
result.appendChild(el("p", { "class": "nj-mc-label" }, (res.macro || res.adl) ? "Cannot generate this code yet" : "No macro fits"));
|
|
res.errors.forEach(function (e) {
|
|
var p = el("p", { "class": "nj-mc-why" }, e.text);
|
|
addLink(p, e.link, e.linkText);
|
|
result.appendChild(p);
|
|
});
|
|
}
|
|
res.warnings.forEach(function (w) { hints.appendChild(el("li", null, w)); });
|
|
hints.hidden = !res.warnings.length;
|
|
|
|
var ok = (res.macro || res.adl) && !res.errors.length;
|
|
codeWrap.hidden = !ok;
|
|
if (ok) {
|
|
currentCode = generate(st, res);
|
|
code.innerHTML = highlight(currentCode);
|
|
}
|
|
root.setAttribute("data-nj-mc-macro", ok ? (res.macro || "adl_serializer") : "");
|
|
}
|
|
update();
|
|
}
|
|
|
|
function init() {
|
|
Array.prototype.forEach.call(document.querySelectorAll(".nj-macro-chooser"), setup);
|
|
}
|
|
|
|
if (typeof document$ !== "undefined" && document$ && typeof document$.subscribe === "function") {
|
|
document$.subscribe(init);
|
|
} else if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", init);
|
|
} else {
|
|
init();
|
|
}
|
|
|
|
/* exported for the verification scripts */
|
|
window.njMacroChooser = { decide: decide, generate: generate };
|
|
})();
|