diff --git a/src/libcmd/local.mk b/src/libcmd/local.mk index abb7459a7d09..c807e0b6b362 100644 --- a/src/libcmd/local.mk +++ b/src/libcmd/local.mk @@ -8,7 +8,7 @@ libcmd_SOURCES := $(wildcard $(d)/*.cc) libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers -libcmd_LDFLAGS = $(EDITLINE_LIBS) $(LOWDOWN_LIBS) $(THREAD_LDFLAGS) +libcmd_LDFLAGS = $(EDITLINE_LIBS) $(THREAD_LDFLAGS) libcmd_LIBS = libstore libutil libexpr libmain libfetchers diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 0659a217393b..16a756eb1e99 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -657,7 +657,7 @@ Value * EvalState::addPrimOp(PrimOp && primOp) v.mkApp(vPrimOp, vPrimOp); return addConstant(primOp.name, v, { .type = nThunk, // FIXME - .doc = primOp.doc, + .doc = primOp.doc.c_str(), }); } @@ -684,13 +684,14 @@ std::optional EvalState::getDoc(Value & v) { if (v.isPrimOp()) { auto v2 = &v; - if (auto * doc = v2->primOp->doc) + auto & doc = v2->primOp->doc; + if (doc != "") return Doc { .pos = {}, .name = v2->primOp->name, .arity = v2->primOp->arity, .args = v2->primOp->args, - .doc = doc, + .doc = doc.c_str(), }; } return {}; diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 9141156b1b0f..5e59e00c5d5b 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -65,7 +65,7 @@ struct PrimOp /** * Optional free-form documentation about the primop. */ - const char * doc = nullptr; + const std::string doc = ""; /** * Implementation of the primop. diff --git a/src/libexpr/local.mk b/src/libexpr/local.mk index 0c3e36750e8a..9e59ec44ce31 100644 --- a/src/libexpr/local.mk +++ b/src/libexpr/local.mk @@ -16,7 +16,7 @@ libexpr_CXXFLAGS += -I src/libutil -I src/libstore -I src/libfetchers -I src/lib libexpr_LIBS = libutil libstore libfetchers -libexpr_LDFLAGS += -lboost_context $(THREAD_LDFLAGS) +libexpr_LDFLAGS += -lboost_context $(THREAD_LDFLAGS) $(LOWDOWN_LIBS) ifdef HOST_LINUX libexpr_LDFLAGS += -ldl endif diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index 05cd8b3db739..7f2aa9098860 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -9,6 +9,11 @@ #include "url.hh" #include "value-to-json.hh" +#if HAVE_LOWDOWN +# include "markdown.hh" +# include "terminal.hh" +#endif + #include #include #include @@ -186,7 +191,7 @@ static void prim_fetchTree(EvalState & state, const PosIdx pos, Value * * args, static RegisterPrimOp primop_fetchTree({ .name = "fetchTree", .args = {"input"}, - .doc = R"( + .doc = stripIndentation(R"( Fetch a file system tree or a plain file using one of the supported backends and return an attribute set with: - the resulting fixed-output [store path](@docroot@/glossary.md#gloss-store-path) @@ -227,6 +232,133 @@ static RegisterPrimOp primop_fetchTree({ document) if `fetchTree` was a curried call with the first paramter for `type` or an attribute like `builtins.fetchTree.git`! --> + )") + []() -> std::string { +#if HAVE_LOWDOWN + struct lowdown_opts opts { + .type = LOWDOWN_TERM, + .maxdepth = SIZE_MAX, + .cols = SIZE_MAX, + .hmargin = 0, + .vmargin = 0, + .feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES, + //.oflags = LOWDOWN_TERM_NOLINK, + }; + + auto doc = LowdownUniquePtr { + lowdown_doc_new(&opts), + }; + assert(doc); + + auto makeNode = [](enum lowdown_rndrt type) { + LowdownUniquePtr node { + (struct lowdown_node *) calloc(1, sizeof(struct lowdown_node)) + }; + assert(node); + node->type = type; + TAILQ_INIT(&node->children); + return node; + }; + + auto makeBuf = []{ + return LowdownUniquePtr { + lowdown_buf_new(16384), + }; + }; + + auto setParent = [](struct lowdown_node & parent, struct lowdown_node & child) { + TAILQ_INSERT_TAIL(&parent.children, &child, entries); + ++parent.rndr_list.items; + child.parent = &parent; + }; + + auto createBuffer = [](std::string_view str) -> struct lowdown_buf { + auto size = str.size(); + auto * data = (char *) malloc(size); + assert(data); + memcpy(data, str.data(), size); + data[size] = '\0'; + return { + .data = data, + .size = size, + .maxsize = size + 1, + .unit = 0, + .buffer_free = (int) true, + }; + }; + + auto root = makeNode(LOWDOWN_ROOT); + auto & schemes = *makeNode(LOWDOWN_LIST).release(); + setParent(*root, schemes); + schemes.rndr_list.flags = (enum hlist_fl) 0; + + for (const auto & [schemeName, scheme] : fetchers::getAllInputSchemes()) { + auto & s = *makeNode(LOWDOWN_LISTITEM).release(); + setParent(schemes, s); + s.rndr_listitem.flags = HLIST_FL_BLOCK; + { + auto & name_p = *makeNode(LOWDOWN_PARAGRAPH).release(); + setParent(s, name_p); + auto & name = *makeNode(LOWDOWN_NORMAL_TEXT).release(); + setParent(name_p, name); + name.rndr_normal_text.text = createBuffer(schemeName); + } + { + auto & desc_p = *makeNode(LOWDOWN_PARAGRAPH).release(); + setParent(s, desc_p); + auto markdown = scheme->schemeDescription(); + size_t maxn = 0; + auto * desc = lowdown_doc_parse(&*doc, &maxn, markdown.data(), markdown.size(), nullptr); + assert(desc); + setParent(desc_p, *desc); + } + auto & attrs = *makeNode(LOWDOWN_LIST).release(); + setParent(s, attrs); + schemes.rndr_list.flags = (enum hlist_fl) 0; + for (const auto & [attrName, attribute] : scheme->allowedAttrs()) { + auto & a = *makeNode(LOWDOWN_LISTITEM).release(); + setParent(attrs, a); + a.rndr_listitem.flags = HLIST_FL_BLOCK; + { + auto & name_p = *makeNode(LOWDOWN_PARAGRAPH).release(); + setParent(a, name_p); + auto & name = *makeNode(LOWDOWN_NORMAL_TEXT).release(); + setParent(name_p, name); + std::string header = attrName + + " (" + attribute.type + + ", " + (attribute.required ? "required" : "optional") + + ")"; + name.rndr_normal_text.text = createBuffer(std::string_view(header)); + } + { + auto & doc_p = *makeNode(LOWDOWN_PARAGRAPH).release(); + setParent(s, doc_p); + auto * markdown = attribute.doc; + size_t maxn = 0; + auto * doc_s = lowdown_doc_parse(&*doc, &maxn, markdown, strlen(markdown), nullptr); + assert(doc_s); + setParent(doc_p, *doc_s); + } + } + } + + auto renderer = LowdownUniquePtr { + (struct lowdown_term *) lowdown_term_new(&opts), + }; + assert(renderer); + + auto buf = makeBuf(); + assert(buf); + + int rndr_res = lowdown_term_rndr(&*buf, &*renderer, &*root); + assert(rndr_res); + + return filterANSIEscapes(std::string { buf->data, buf->size }, true); +#else + return {}; +#endif + }() + + stripIndentation(R"( + The following input types are still subject to change: - `"path"` @@ -271,7 +403,7 @@ static RegisterPrimOp primop_fetchTree({ > ```nix > builtins.fetchTree "github:NixOS/nixpkgs/ae2e6b3958682513d28f7d633734571fb18285dd" > ``` - )", + )"), .fun = prim_fetchTree, .experimentalFeature = Xp::FetchTree, }); diff --git a/src/libutil/local.mk b/src/libutil/local.mk index 200026c1e06f..e72ee6a4fddf 100644 --- a/src/libutil/local.mk +++ b/src/libutil/local.mk @@ -8,7 +8,7 @@ libutil_SOURCES := $(wildcard $(d)/*.cc $(d)/signature/*.cc) libutil_CXXFLAGS += -I src/libutil -libutil_LDFLAGS += $(THREAD_LDFLAGS) $(LIBCURL_LIBS) $(SODIUM_LIBS) $(OPENSSL_LIBS) $(LIBBROTLI_LIBS) $(LIBARCHIVE_LIBS) $(BOOST_LDFLAGS) -lboost_context +libutil_LDFLAGS += $(THREAD_LDFLAGS) $(LOWDOWN_LIBS) $(LIBCURL_LIBS) $(SODIUM_LIBS) $(OPENSSL_LIBS) $(LIBBROTLI_LIBS) $(LIBARCHIVE_LIBS) $(BOOST_LDFLAGS) -lboost_context $(foreach i, $(wildcard $(d)/args/*.hh), \ $(eval $(call install-file-in, $(i), $(includedir)/nix/args, 0644))) diff --git a/src/libcmd/markdown.cc b/src/libutil/markdown.cc similarity index 100% rename from src/libcmd/markdown.cc rename to src/libutil/markdown.cc diff --git a/src/libcmd/markdown.hh b/src/libutil/markdown.hh similarity index 100% rename from src/libcmd/markdown.hh rename to src/libutil/markdown.hh diff --git a/src/nix/main.cc b/src/nix/main.cc index 51c3dd56f88a..df7f18de0853 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -404,7 +404,7 @@ void mainWrapped(int argc, char * * argv) auto b = nlohmann::json::object(); if (!builtin.value->isPrimOp()) continue; auto primOp = builtin.value->primOp; - if (!primOp->doc) continue; + if (primOp->doc == "") continue; b["arity"] = primOp->arity; b["args"] = primOp->args; b["doc"] = trim(stripIndentation(primOp->doc));