From 148608ba6ddf93168e86525627bed755a474d21f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 2 Dec 2020 12:26:09 +0100 Subject: [PATCH 1/4] Add 'nix help' --- src/nix/main.cc | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/nix/main.cc b/src/nix/main.cc index 5056ceb78..a75f8ae65 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -149,6 +149,50 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs } }; +static void showHelp(std::vector subcommand) +{ + showManPage(subcommand.empty() ? "nix" : fmt("nix3-%s", concatStringsSep("-", subcommand))); +} + +struct CmdHelp : Command +{ + std::vector subcommand; + + CmdHelp() + { + expectArgs({ + .label = "subcommand", + .handler = {&subcommand}, + }); + } + + std::string description() override + { + return "show help about 'nix' or a particular subcommand"; + } + + Examples examples() override + { + return { + Example{ + "To show help about 'nix' in general:", + "nix help" + }, + Example{ + "To show help about a particular subcommand:", + "nix help run" + }, + }; + } + + void run() override + { + showHelp(subcommand); + } +}; + +static auto rCmdHelp = registerCommand("help"); + void mainWrapped(int argc, char * * argv) { /* The chroot helper needs to be run before any threads have been From df552a26452f4cdf734edcac049187b8fd806153 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 2 Dec 2020 21:25:32 +0100 Subject: [PATCH 2/4] nix eval: Add option to write a directory This is useful for generating the nix manpages, but it may have other applications (like generating configuration files without a Nix store). --- src/nix/eval.cc | 55 ++++++++++++++++++++++++++++++++++++++++++---- tests/pure-eval.sh | 8 +++++++ 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/nix/eval.cc b/src/nix/eval.cc index 8da81d667..0f02919de 100644 --- a/src/nix/eval.cc +++ b/src/nix/eval.cc @@ -3,6 +3,7 @@ #include "shared.hh" #include "store-api.hh" #include "eval.hh" +#include "eval-inline.hh" #include "json.hh" #include "value-to-json.hh" #include "progress-bar.hh" @@ -13,6 +14,7 @@ struct CmdEval : MixJSON, InstallableCommand { bool raw = false; std::optional apply; + std::optional writeTo; CmdEval() { @@ -24,6 +26,13 @@ struct CmdEval : MixJSON, InstallableCommand .labels = {"expr"}, .handler = {&apply}, }); + + addFlag({ + .longName = "write-to", + .description = "write a string or attrset of strings to 'path'", + .labels = {"path"}, + .handler = {&writeTo}, + }); } std::string description() override @@ -66,7 +75,7 @@ struct CmdEval : MixJSON, InstallableCommand auto state = getEvalState(); - auto v = installable->toValue(*state).first; + auto [v, pos] = installable->toValue(*state); PathSet context; if (apply) { @@ -77,13 +86,51 @@ struct CmdEval : MixJSON, InstallableCommand v = vRes; } - if (raw) { + if (writeTo) { + stopProgressBar(); + + if (pathExists(*writeTo)) + throw Error("path '%s' already exists", *writeTo); + + std::function recurse; + + recurse = [&](Value & v, const Pos & pos, const Path & path) + { + state->forceValue(v); + if (v.type == tString) + // FIXME: disallow strings with contexts? + writeFile(path, v.string.s); + else if (v.type == tAttrs) { + if (mkdir(path.c_str(), 0777) == -1) + throw SysError("creating directory '%s'", path); + for (auto & attr : *v.attrs) + try { + if (attr.name == "." || attr.name == "..") + throw Error("invalid file name '%s'", attr.name); + recurse(*attr.value, *attr.pos, path + "/" + std::string(attr.name)); + } catch (Error & e) { + e.addTrace(*attr.pos, hintfmt("while evaluating the attribute '%s'", attr.name)); + throw; + } + } + else + throw TypeError("value at '%s' is not a string or an attribute set", pos); + }; + + recurse(*v, pos, *writeTo); + } + + else if (raw) { stopProgressBar(); std::cout << state->coerceToString(noPos, *v, context); - } else if (json) { + } + + else if (json) { JSONPlaceholder jsonOut(std::cout); printValueAsJSON(*state, true, *v, jsonOut, context); - } else { + } + + else { state->forceValueDeep(*v); logger->cout("%s", *v); } diff --git a/tests/pure-eval.sh b/tests/pure-eval.sh index 43a765997..561ca53fb 100644 --- a/tests/pure-eval.sh +++ b/tests/pure-eval.sh @@ -16,3 +16,11 @@ nix eval --expr 'assert 1 + 2 == 3; true' [[ $(nix eval --impure --expr "(import (builtins.fetchurl { url = file://$(pwd)/pure-eval.nix; })).x") == 123 ]] (! nix eval --expr "(import (builtins.fetchurl { url = file://$(pwd)/pure-eval.nix; })).x") nix eval --expr "(import (builtins.fetchurl { url = file://$(pwd)/pure-eval.nix; sha256 = \"$(nix hash-file pure-eval.nix --type sha256)\"; })).x" + +rm -rf $TEST_ROOT/eval-out +nix eval --store dummy:// --write-to $TEST_ROOT/eval-out --expr '{ x = "foo" + "bar"; y = { z = "bla"; }; }' +[[ $(cat $TEST_ROOT/eval-out/x) = foobar ]] +[[ $(cat $TEST_ROOT/eval-out/y/z) = bla ]] + +rm -rf $TEST_ROOT/eval-out +(! nix eval --store dummy:// --write-to $TEST_ROOT/eval-out --expr '{ "." = "bla"; }') From 72428e38d904a1db746f37c8727ab7b0fbb457bc Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 2 Dec 2020 23:05:28 +0100 Subject: [PATCH 3/4] Generate separate manpages for each nix subcommand --- .gitignore | 3 +- doc/manual/generate-manpage.nix | 50 +++++++++++++------- doc/manual/local.mk | 28 ++++++++--- doc/manual/src/{SUMMARY.md => SUMMARY.md.in} | 2 +- 4 files changed, 58 insertions(+), 25 deletions(-) rename doc/manual/src/{SUMMARY.md => SUMMARY.md.in} (99%) diff --git a/.gitignore b/.gitignore index c51582cf0..37aada307 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,8 @@ perl/Makefile.config /doc/manual/nix.json /doc/manual/conf-file.json /doc/manual/builtins.json -/doc/manual/src/command-ref/nix.md +/doc/manual/src/SUMMARY.md +/doc/manual/src/command-ref/new-cli /doc/manual/src/command-ref/conf-file.md /doc/manual/src/expressions/builtins.md diff --git a/doc/manual/generate-manpage.nix b/doc/manual/generate-manpage.nix index db266750a..2801e96fa 100644 --- a/doc/manual/generate-manpage.nix +++ b/doc/manual/generate-manpage.nix @@ -1,34 +1,40 @@ +command: + with builtins; with import ./utils.nix; let showCommand = - { command, section, def }: - "${section} Name\n\n" + { command, def, filename }: + "# Name\n\n" + "`${command}` - ${def.description}\n\n" - + "${section} Synopsis\n\n" + + "# Synopsis\n\n" + showSynopsis { inherit command; args = def.args; } + + (if def.commands or {} != {} + then + "where *subcommand* is one of the following:\n\n" + # FIXME: group by category + + concatStrings (map (name: + "* [`${command} ${name}`](./${appendName filename name}.md) - ${def.commands.${name}.description}\n") + (attrNames def.commands)) + + "\n" + else "") + (if def ? doc - then "${section} Description\n\n" + def.doc + "\n\n" + then "# Description\n\n" + def.doc + "\n\n" else "") + (let s = showFlags def.flags; in if s != "" - then "${section} Flags\n\n${s}" + then "# Flags\n\n${s}" else "") + (if def.examples or [] != [] then - "${section} Examples\n\n" + "# Examples\n\n" + concatStrings (map ({ description, command }: "${description}\n\n```console\n${command}\n```\n\n") def.examples) - else "") - + (if def.commands or [] != [] - then concatStrings ( - map (name: - "# Subcommand `${command} ${name}`\n\n" - + showCommand { command = command + " " + name; section = "##"; def = def.commands.${name}; }) - (attrNames def.commands)) else ""); + appendName = filename: name: (if filename == "nix" then "nix3" else filename) + "-" + name; + showFlags = flags: concatStrings (map (longName: @@ -48,8 +54,20 @@ let "`${command}` [*flags*...] ${concatStringsSep " " (map (arg: "*${arg.label}*" + (if arg ? arity then "" else "...")) args)}\n\n"; + processCommand = { command, def, filename }: + [ { name = filename + ".md"; value = showCommand { inherit command def filename; }; inherit command; } ] + ++ concatMap + (name: processCommand { + filename = appendName filename name; + command = command + " " + name; + def = def.commands.${name}; + }) + (attrNames def.commands or {}); + in -command: - -showCommand { command = "nix"; section = "#"; def = command; } +let + manpages = processCommand { filename = "nix"; command = "nix"; def = command; }; + summary = concatStrings (map (manpage: " - [${manpage.command}](command-ref/new-cli/${manpage.name})\n") manpages); +in +(listToAttrs manpages) // { "SUMMARY.md" = summary; } diff --git a/doc/manual/local.mk b/doc/manual/local.mk index bb8b3b60a..b40fa4ed2 100644 --- a/doc/manual/local.mk +++ b/doc/manual/local.mk @@ -4,7 +4,7 @@ MANUAL_SRCS := $(call rwildcard, $(d)/src, *.md) # Generate man pages. man-pages := $(foreach n, \ - nix-env.1 nix-build.1 nix-shell.1 nix-store.1 nix-instantiate.1 nix.1 \ + nix-env.1 nix-build.1 nix-shell.1 nix-store.1 nix-instantiate.1 \ nix-collect-garbage.1 \ nix-prefetch-url.1 nix-channel.1 \ nix-hash.1 nix-copy-closure.1 \ @@ -22,7 +22,7 @@ dummy-env = env -i \ NIX_SSL_CERT_FILE=/dummy/no-ca-bundle.crt \ NIX_STATE_DIR=/dummy -nix-eval = $(dummy-env) $(bindir)/nix eval --experimental-features nix-command -I nix/corepkgs=corepkgs --store dummy:// --impure --raw --expr +nix-eval = $(dummy-env) $(bindir)/nix eval --experimental-features nix-command -I nix/corepkgs=corepkgs --store dummy:// --impure --raw $(d)/%.1: $(d)/src/command-ref/%.md @printf "Title: %s\n\n" "$$(basename $@ .1)" > $^.tmp @@ -42,13 +42,17 @@ $(d)/nix.conf.5: $(d)/src/command-ref/conf-file.md $(trace-gen) lowdown -sT man $^.tmp -o $@ @rm $^.tmp -$(d)/src/command-ref/nix.md: $(d)/nix.json $(d)/generate-manpage.nix $(bindir)/nix - $(trace-gen) $(nix-eval) 'import doc/manual/generate-manpage.nix (builtins.fromJSON (builtins.readFile $<))' > $@.tmp +$(d)/src/SUMMARY.md: $(d)/src/SUMMARY.md.in $(d)/src/command-ref/new-cli + $(trace-gen) cat doc/manual/src/SUMMARY.md.in | while IFS= read line; do if [[ $$line = @manpages@ ]]; then cat doc/manual/src/command-ref/new-cli/SUMMARY.md; else echo "$$line"; fi; done > $@.tmp @mv $@.tmp $@ +$(d)/src/command-ref/new-cli: $(d)/nix.json $(d)/generate-manpage.nix $(bindir)/nix + @rm -rf $@ + $(trace-gen) $(nix-eval) --write-to $@ --expr 'import doc/manual/generate-manpage.nix (builtins.fromJSON (builtins.readFile $<))' + $(d)/src/command-ref/conf-file.md: $(d)/conf-file.json $(d)/generate-options.nix $(d)/src/command-ref/conf-file-prefix.md $(bindir)/nix @cat doc/manual/src/command-ref/conf-file-prefix.md > $@.tmp - $(trace-gen) $(nix-eval) 'import doc/manual/generate-options.nix (builtins.fromJSON (builtins.readFile $<))' >> $@.tmp + $(trace-gen) $(nix-eval) --expr 'import doc/manual/generate-options.nix (builtins.fromJSON (builtins.readFile $<))' >> $@.tmp @mv $@.tmp $@ $(d)/nix.json: $(bindir)/nix @@ -61,7 +65,7 @@ $(d)/conf-file.json: $(bindir)/nix $(d)/src/expressions/builtins.md: $(d)/builtins.json $(d)/generate-builtins.nix $(d)/src/expressions/builtins-prefix.md $(bindir)/nix @cat doc/manual/src/expressions/builtins-prefix.md > $@.tmp - $(trace-gen) $(nix-eval) 'import doc/manual/generate-builtins.nix (builtins.fromJSON (builtins.readFile $<))' >> $@.tmp + $(trace-gen) $(nix-eval) --expr 'import doc/manual/generate-builtins.nix (builtins.fromJSON (builtins.readFile $<))' >> $@.tmp @mv $@.tmp $@ $(d)/builtins.json: $(bindir)/nix @@ -71,7 +75,17 @@ $(d)/builtins.json: $(bindir)/nix # Generate the HTML manual. install: $(docdir)/manual/index.html -$(docdir)/manual/index.html: $(MANUAL_SRCS) $(d)/book.toml $(d)/custom.css $(d)/src/command-ref/nix.md $(d)/src/command-ref/conf-file.md $(d)/src/expressions/builtins.md +# Generate 'nix' manpages. +install: $(d)/src/command-ref/new-cli + for i in doc/manual/src/command-ref/new-cli/*.md; do \ + name=$$(basename $$i .md); \ + if [[ $$name = SUMMARY ]]; then continue; fi; \ + printf "Title: %s\n\n" "$$name" > $$i.tmp; \ + cat $$i >> $$i.tmp; \ + lowdown -sT man $$i.tmp -o $(mandir)/man1/$$name.1; \ + done + +$(docdir)/manual/index.html: $(MANUAL_SRCS) $(d)/book.toml $(d)/custom.css $(d)/src/SUMMARY.md $(d)/src/command-ref/new-cli $(d)/src/command-ref/conf-file.md $(d)/src/expressions/builtins.md $(trace-gen) mdbook build doc/manual -d $(docdir)/manual @cp doc/manual/highlight.pack.js $(docdir)/manual/highlight.js diff --git a/doc/manual/src/SUMMARY.md b/doc/manual/src/SUMMARY.md.in similarity index 99% rename from doc/manual/src/SUMMARY.md rename to doc/manual/src/SUMMARY.md.in index 8281f683f..b5ae34cfa 100644 --- a/doc/manual/src/SUMMARY.md +++ b/doc/manual/src/SUMMARY.md.in @@ -62,7 +62,7 @@ - [nix-instantiate](command-ref/nix-instantiate.md) - [nix-prefetch-url](command-ref/nix-prefetch-url.md) - [Experimental Commands](command-ref/experimental-commands.md) - - [nix](command-ref/nix.md) +@manpages@ - [Files](command-ref/files.md) - [nix.conf](command-ref/conf-file.md) - [Glossary](glossary.md) From e2efc63979a5485a96accaa1e49ffec65c353078 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 2 Dec 2020 23:19:16 +0100 Subject: [PATCH 4/4] Put examples first in the manpages --- doc/manual/generate-manpage.nix | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/manual/generate-manpage.nix b/doc/manual/generate-manpage.nix index 2801e96fa..4a0b0290b 100644 --- a/doc/manual/generate-manpage.nix +++ b/doc/manual/generate-manpage.nix @@ -20,6 +20,11 @@ let (attrNames def.commands)) + "\n" else "") + + (if def.examples or [] != [] + then + "# Examples\n\n" + + concatStrings (map ({ description, command }: "${description}\n\n```console\n${command}\n```\n\n") def.examples) + else "") + (if def ? doc then "# Description\n\n" + def.doc + "\n\n" else "") @@ -27,11 +32,7 @@ let if s != "" then "# Flags\n\n${s}" else "") - + (if def.examples or [] != [] - then - "# Examples\n\n" - + concatStrings (map ({ description, command }: "${description}\n\n```console\n${command}\n```\n\n") def.examples) - else ""); + ; appendName = filename: name: (if filename == "nix" then "nix3" else filename) + "-" + name;