From dc2f278c95ce4a73749cbb8221a568201535d46a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 20 Aug 2020 12:21:46 +0200 Subject: [PATCH] Allow 'nix' subcommands to provide docs in Markdown format --- doc/manual/generate-manpage.jq | 6 ++++- src/libutil/args.cc | 4 +++- src/libutil/args.hh | 4 ++++ src/libutil/config.cc | 37 ------------------------------ src/libutil/util.cc | 41 ++++++++++++++++++++++++++++++++++ src/libutil/util.hh | 6 +++++ src/nix/add-to-store.cc | 8 +++++++ src/nix/repl.cc | 2 +- 8 files changed, 68 insertions(+), 40 deletions(-) diff --git a/doc/manual/generate-manpage.jq b/doc/manual/generate-manpage.jq index d3cf1c601..dd632f162 100644 --- a/doc/manual/generate-manpage.jq +++ b/doc/manual/generate-manpage.jq @@ -12,7 +12,7 @@ def show_flags: ; def show_synopsis: - "`" + .command + "` " + (.args | map("*" + .label + "*" + (if has("arity") then "" else "..." end)) | join(" ")) + "\n\n" + "`" + .command + "` [*flags*...] " + (.args | map("*" + .label + "*" + (if has("arity") then "" else "..." end)) | join(" ")) + "\n\n" ; def show_command: @@ -21,6 +21,10 @@ def show_command: + "`" + .command + "` - " + .def.description + "\n\n" + .section + " Synopsis\n\n" + ({"command": .command, "args": .def.args} | show_synopsis) + + (if .def | has("doc") + then .section + " Description\n\n" + .def.doc + "\n\n" + else "" + end) + (if (.def.flags | length) > 0 then .section + " Flags\n\n" + (.def | show_flags) diff --git a/src/libutil/args.cc b/src/libutil/args.cc index ad83a2414..147602415 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -359,12 +359,14 @@ nlohmann::json Command::toJSON() for (auto & example : examples()) { auto ex = nlohmann::json::object(); ex["description"] = example.description; - ex["command"] = example.command; + ex["command"] = chomp(stripIndentation(example.command)); exs.push_back(std::move(ex)); } auto res = Args::toJSON(); res["examples"] = std::move(exs); + auto s = doc(); + if (s != "") res.emplace("doc", stripIndentation(s)); return res; } diff --git a/src/libutil/args.hh b/src/libutil/args.hh index c56044f1d..3c1f87f7e 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -22,6 +22,7 @@ public: virtual void printHelp(const string & programName, std::ostream & out); + /* Return a short one-line description of the command. */ virtual std::string description() { return ""; } protected: @@ -221,6 +222,9 @@ struct Command : virtual Args virtual void prepare() { }; virtual void run() = 0; + /* Return documentation about this command, in Markdown format. */ + virtual std::string doc() { return ""; } + struct Example { std::string description; diff --git a/src/libutil/config.cc b/src/libutil/config.cc index e5297c653..3cf720bce 100644 --- a/src/libutil/config.cc +++ b/src/libutil/config.cc @@ -153,43 +153,6 @@ void Config::convertToArgs(Args & args, const std::string & category) s.second.setting->convertToArg(args, category); } -static std::string stripIndentation(std::string_view s) -{ - size_t minIndent = 10000; - size_t curIndent = 0; - bool atStartOfLine = true; - - for (auto & c : s) { - if (atStartOfLine && c == ' ') - curIndent++; - else if (c == '\n') { - if (atStartOfLine) - minIndent = std::max(minIndent, curIndent); - curIndent = 0; - atStartOfLine = true; - } else { - if (atStartOfLine) { - minIndent = std::min(minIndent, curIndent); - atStartOfLine = false; - } - } - } - - std::string res; - - size_t pos = 0; - while (pos < s.size()) { - auto eol = s.find('\n', pos); - if (eol == s.npos) eol = s.size(); - if (eol - pos > minIndent) - res.append(s.substr(pos + minIndent, eol - pos - minIndent)); - res.push_back('\n'); - pos = eol + 1; - } - - return res; -} - AbstractSetting::AbstractSetting( const std::string & name, const std::string & description, diff --git a/src/libutil/util.cc b/src/libutil/util.cc index c0b9698ee..9e7142e01 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -1464,6 +1464,47 @@ string base64Decode(std::string_view s) } +std::string stripIndentation(std::string_view s) +{ + size_t minIndent = 10000; + size_t curIndent = 0; + bool atStartOfLine = true; + + for (auto & c : s) { + if (atStartOfLine && c == ' ') + curIndent++; + else if (c == '\n') { + if (atStartOfLine) + minIndent = std::max(minIndent, curIndent); + curIndent = 0; + atStartOfLine = true; + } else { + if (atStartOfLine) { + minIndent = std::min(minIndent, curIndent); + atStartOfLine = false; + } + } + } + + std::string res; + + size_t pos = 0; + while (pos < s.size()) { + auto eol = s.find('\n', pos); + if (eol == s.npos) eol = s.size(); + if (eol - pos > minIndent) + res.append(s.substr(pos + minIndent, eol - pos - minIndent)); + res.push_back('\n'); + pos = eol + 1; + } + + return res; +} + + +////////////////////////////////////////////////////////////////////// + + static Sync> windowSize{{0, 0}}; diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 3a20679a8..082e26375 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -464,6 +464,12 @@ string base64Encode(std::string_view s); string base64Decode(std::string_view s); +/* Remove common leading whitespace from the lines in the string + 's'. For example, if every line is indented by at least 3 spaces, + then we remove 3 spaces from the start of every line. */ +std::string stripIndentation(std::string_view s); + + /* Get a value for the specified key from an associate container. */ template std::optional get(const T & map, const typename T::key_type & key) diff --git a/src/nix/add-to-store.cc b/src/nix/add-to-store.cc index 713155840..023ffa4ed 100644 --- a/src/nix/add-to-store.cc +++ b/src/nix/add-to-store.cc @@ -36,6 +36,14 @@ struct CmdAddToStore : MixDryRun, StoreCommand return "add a path to the Nix store"; } + std::string doc() override + { + return R"( + Copy the file or directory *path* to the Nix store, and + print the resulting store path on standard output. + )"; + } + Examples examples() override { return { diff --git a/src/nix/repl.cc b/src/nix/repl.cc index a74655200..0cbe9643c 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -782,7 +782,7 @@ struct CmdRepl : StoreCommand, MixEvalArgs return { Example{ "Display all special commands within the REPL:", - "nix repl\n nix-repl> :?" + "nix repl\nnix-repl> :?" } }; }