From 12603493848be0e22369ade385cf0ba3567673f8 Mon Sep 17 00:00:00 2001 From: Robert Helgesson Date: Sun, 6 May 2018 22:14:50 +0200 Subject: [PATCH] doc: make documentation independent from NixOS Unfortunately this duplicates some code from NixOS but it does allow much more flexibility and, hopefully, stability in the Home Manager documentation. Fixes #254. --- doc/default.nix | 329 ++++++++++++++++++++++++++++++++++++++ doc/man-configuration.xml | 34 ++++ doc/man-home-manager.xml | 62 +++++++ doc/man-pages.xml | 16 ++ doc/manual.xml | 40 +++++ modules/manual.nix | 26 +-- 6 files changed, 485 insertions(+), 22 deletions(-) create mode 100644 doc/default.nix create mode 100644 doc/man-configuration.xml create mode 100644 doc/man-home-manager.xml create mode 100644 doc/man-pages.xml create mode 100644 doc/manual.xml diff --git a/doc/default.nix b/doc/default.nix new file mode 100644 index 00000000..8b995c5a --- /dev/null +++ b/doc/default.nix @@ -0,0 +1,329 @@ +{ pkgs, options, config, version, revision, extraSources ? [] }: + +with pkgs; + +let + lib = pkgs.lib; + + # Remove invisible and internal options. + optionsListVisible = lib.filter (opt: opt.visible && !opt.internal) (lib.optionAttrSetToDocList options); + + # Replace functions by the string + substFunction = x: + if builtins.isAttrs x then lib.mapAttrs (name: substFunction) x + else if builtins.isList x then map substFunction x + else if lib.isFunction x then "" + else x; + + # Generate DocBook documentation for a list of packages. This is + # what `relatedPackages` option of `mkOption` from + # ../../../lib/options.nix influences. + # + # Each element of `relatedPackages` can be either + # - a string: that will be interpreted as an attribute name from `pkgs`, + # - a list: that will be interpreted as an attribute path from `pkgs`, + # - an attrset: that can specify `name`, `path`, `package`, `comment` + # (either of `name`, `path` is required, the rest are optional). + genRelatedPackages = packages: + let + unpack = p: if lib.isString p then { name = p; } + else if lib.isList p then { path = p; } + else p; + describe = args: + let + name = args.name or (lib.concatStringsSep "." args.path); + path = args.path or [ args.name ]; + package = args.package or (lib.attrByPath path (throw "Invalid package attribute path `${toString path}'") pkgs); + in "" + + "pkgs.${name} (${package.meta.name})" + + lib.optionalString (!package.meta.available) " [UNAVAILABLE]" + + ": ${package.meta.description or "???"}." + + lib.optionalString (args ? comment) "\n${args.comment}" + # Lots of `longDescription's break DocBook, so we just wrap them into + + lib.optionalString (package.meta ? longDescription) "\n${package.meta.longDescription}" + + ""; + in "${lib.concatStringsSep "\n" (map (p: describe (unpack p)) packages)}"; + + optionsListDesc = lib.flip map optionsListVisible (opt: opt // { + # Clean up declaration sites to not refer to the NixOS source tree. + declarations = map stripAnyPrefixes opt.declarations; + } + // lib.optionalAttrs (opt ? example) { example = substFunction opt.example; } + // lib.optionalAttrs (opt ? default) { default = substFunction opt.default; } + // lib.optionalAttrs (opt ? type) { type = substFunction opt.type; } + // lib.optionalAttrs (opt ? relatedPackages) { relatedPackages = genRelatedPackages opt.relatedPackages; }); + + # We need to strip references to /nix/store/* from options, + # including any `extraSources` if some modules came from elsewhere, + # or else the build will fail. + # + # E.g. if some `options` came from modules in ${pkgs.customModules}/nix, + # you'd need to include `extraSources = [ pkgs.customModules ]` + prefixesToStrip = map (p: "${toString p}/") ([ ../../.. ] ++ extraSources); + stripAnyPrefixes = lib.flip (lib.fold lib.removePrefix) prefixesToStrip; + + # Custom "less" that pushes up all the things ending in ".enable*" + # and ".package*" + optionLess = a: b: + let + ise = lib.hasPrefix "enable"; + isp = lib.hasPrefix "package"; + cmp = lib.splitByAndCompare ise lib.compare + (lib.splitByAndCompare isp lib.compare lib.compare); + in lib.compareLists cmp a.loc b.loc < 0; + + # Customly sort option list for the man page. + optionsList = lib.sort optionLess optionsListDesc; + + # Convert the list of options into an XML file. + optionsXML = builtins.toFile "options.xml" (builtins.toXML optionsList); + + optionsDocBook = runCommand "options-db.xml" {} '' + optionsXML=${optionsXML} + if grep /home--manager/modules $optionsXML; then + echo "The manual appears to depend on the location of Home Manager, which is bad" + echo "since this prevents sharing via the NixOS channel. This is typically" + echo "caused by an option default that refers to a relative path (see above" + echo "for hints about the offending path)." + exit 1 + fi + ${buildPackages.libxslt.bin}/bin/xsltproc \ + --stringparam revision '${revision}' \ + -o $out ${} $optionsXML + ''; + + sources = lib.sourceFilesBySuffices ./. [".xml"]; + + modulesDoc = builtins.toFile "modules.xml" '' +
+ ${(lib.concatMapStrings (path: '' + + '') (lib.catAttrs "value" config.meta.doc))} +
+ ''; + + generatedSources = runCommand "generated-docbook" {} '' + mkdir $out + ln -s ${modulesDoc} $out/modules.xml + ln -s ${optionsDocBook} $out/options-db.xml + printf "%s" "${version}" > $out/version + ''; + + copySources = + '' + cp -prd $sources/* . # */ + ln -s ${generatedSources} ./generated + chmod -R u+w . + ''; + + toc = builtins.toFile "toc.xml" + '' + + + + + + ''; + + manualXsltprocOptions = toString [ + "--param section.autolabel 1" + "--param section.label.includes.component.label 1" + "--stringparam html.stylesheet 'style.css overrides.css highlightjs/mono-blue.css'" + "--stringparam html.script './highlightjs/highlight.pack.js ./highlightjs/loader.js'" + "--param xref.with.number.and.title 1" + "--param toc.section.depth 3" + "--stringparam admon.style ''" + "--stringparam callout.graphics.extension .svg" + "--stringparam current.docid manual" + "--param chunk.section.depth 0" + "--param chunk.first.sections 1" + "--param use.id.as.filename 1" + "--stringparam generate.toc 'book toc appendix toc'" + "--stringparam chunk.toc ${toc}" + ]; + + manual-combined = runCommand "home-manager-manual-combined" + { inherit sources; + nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin ]; + meta.description = "The Home Manager manual as plain docbook XML"; + } + '' + ${copySources} + + xmllint --xinclude --output ./manual-combined.xml ./manual.xml + xmllint --xinclude --noxincludenode \ + --output ./man-pages-combined.xml ./man-pages.xml + + # outputs the context of an xmllint error output + # LEN lines around the failing line are printed + function context { + # length of context + local LEN=6 + # lines to print before error line + local BEFORE=4 + + # xmllint output lines are: + # file.xml:1234: there was an error on line 1234 + while IFS=':' read -r file line rest; do + echo + if [[ -n "$rest" ]]; then + echo "$file:$line:$rest" + local FROM=$(($line>$BEFORE ? $line - $BEFORE : 1)) + # number lines & filter context + nl --body-numbering=a "$file" | sed -n "$FROM,+$LEN p" + else + if [[ -n "$line" ]]; then + echo "$file:$line" + else + echo "$file" + fi + fi + done + } + + function lintrng { + xmllint --debug --noout --nonet \ + --relaxng ${docbook5}/xml/rng/docbook/docbook.rng \ + "$1" \ + 2>&1 | context 1>&2 + # ^ redirect assumes xmllint doesn’t print to stdout + } + + lintrng manual-combined.xml + lintrng man-pages-combined.xml + + mkdir $out + cp manual-combined.xml $out/ + cp man-pages-combined.xml $out/ + ''; + + olinkDB = runCommand "manual-olinkdb" + { inherit sources; + nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin ]; + } + '' + xsltproc \ + ${manualXsltprocOptions} \ + --stringparam collect.xref.targets only \ + --stringparam targets.filename "$out/manual.db" \ + --nonet \ + ${docbook5_xsl}/xml/xsl/docbook/xhtml/chunktoc.xsl \ + ${manual-combined}/manual-combined.xml + + cat > "$out/olinkdb.xml" < + + ]> + + + Allows for cross-referencing olinks between the manpages + and manual. + + + &manualtargets; + + EOF + ''; + +in rec { + inherit generatedSources; + + # The Home Manager options in JSON format. + optionsJSON = runCommand "options-json" + { meta.description = "List of Home Manager options in JSON format"; + } + '' + # Export list of options in different format. + dst=$out/share/doc/home-manager + mkdir -p $dst + + cp ${builtins.toFile "options.json" (builtins.unsafeDiscardStringContext (builtins.toJSON + (builtins.listToAttrs (map (o: { name = o.name; value = removeAttrs o ["name" "visible" "internal"]; }) optionsList)))) + } $dst/options.json + + mkdir -p $out/nix-support + echo "file json $dst/options.json" >> $out/nix-support/hydra-build-products + ''; # */ + + # Generate the Home Manager manual. + manual = runCommand "home-manager-manual" + { inherit sources; + nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin ]; + meta.description = "The Home Manager manual in HTML format"; + allowedReferences = ["out"]; + } + '' + # Generate the HTML manual. + dst=$out/share/doc/home-manager + mkdir -p $dst + xsltproc \ + ${manualXsltprocOptions} \ + --stringparam target.database.document "${olinkDB}/olinkdb.xml" \ + --nonet --output $dst/ \ + ${docbook5_xsl}/xml/xsl/docbook/xhtml/chunktoc.xsl \ + ${manual-combined}/manual-combined.xml + + mkdir -p $dst/images/callouts + cp ${docbook5_xsl}/xml/xsl/docbook/images/callouts/*.svg $dst/images/callouts/ + + cp ${../../../doc/style.css} $dst/style.css + cp ${../../../doc/overrides.css} $dst/overrides.css + cp -r ${pkgs.documentation-highlighter} $dst/highlightjs + + mkdir -p $out/nix-support + echo "nix-build out $out" >> $out/nix-support/hydra-build-products + echo "doc manual $dst" >> $out/nix-support/hydra-build-products + ''; # */ + + + manualEpub = runCommand "home-manager-manual-epub" + { inherit sources; + buildInputs = [ libxml2.bin libxslt.bin zip ]; + } + '' + # Generate the epub manual. + dst=$out/share/doc/home-manager + + xsltproc \ + ${manualXsltprocOptions} \ + --stringparam target.database.document "${olinkDB}/olinkdb.xml" \ + --nonet --xinclude --output $dst/epub/ \ + ${docbook5_xsl}/xml/xsl/docbook/epub/docbook.xsl \ + ${manual-combined}/manual-combined.xml + + mkdir -p $dst/epub/OEBPS/images/callouts + cp -r ${docbook5_xsl}/xml/xsl/docbook/images/callouts/*.svg $dst/epub/OEBPS/images/callouts # */ + echo "application/epub+zip" > mimetype + manual="$dst/home-manager-manual.epub" + zip -0Xq "$manual" mimetype + cd $dst/epub && zip -Xr9D "$manual" * + + rm -rf $dst/epub + + mkdir -p $out/nix-support + echo "doc-epub manual $manual" >> $out/nix-support/hydra-build-products + ''; + + + # Generate the Home Manager manpages. + manpages = runCommand "home-manager-manpages" + { inherit sources; + nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin ]; + allowedReferences = ["out"]; + } + '' + # Generate manpages. + mkdir -p $out/share/man + xsltproc --nonet \ + --param man.output.in.separate.dir 1 \ + --param man.output.base.dir "'$out/share/man/'" \ + --param man.endnotes.are.numbered 0 \ + --param man.break.after.slash 1 \ + --stringparam target.database.document "${olinkDB}/olinkdb.xml" \ + ${docbook5_xsl}/xml/xsl/docbook/manpages/docbook.xsl \ + ${manual-combined}/man-pages-combined.xml + ''; + +} diff --git a/doc/man-configuration.xml b/doc/man-configuration.xml new file mode 100644 index 00000000..c36cf619 --- /dev/null +++ b/doc/man-configuration.xml @@ -0,0 +1,34 @@ + + + home-configuration.nix + 5 + Home Manager + + + + + home-configuration.nix + Home Manager configuration specification + + + + Description + + The file ~/.config/nixpkgs/home.nix contains + the declarative specification of your Home Manager configuration. + The command home-manager takes this file and + realises the user environment configuration specified therein. + + + + + Options + + You can use the following options in + home-configuration.nix: + + + + diff --git a/doc/man-home-manager.xml b/doc/man-home-manager.xml new file mode 100644 index 00000000..79ae5b45 --- /dev/null +++ b/doc/man-home-manager.xml @@ -0,0 +1,62 @@ + + + home-manager + 8 + Home Manager + + + + + home-manager + reconfigure a user environment + + + + + home-manager + + + + + + + + + + + + + + Description + + This command updates the user environment so that it corresponds to the configuration + specified in ~/.config/nixpkgs/home.nix. + + + + + Files + + + ~/.local/share/home-manager/news-read-ids + + + Identifiers of news items that have been shown. Can be deleted + to reset the read news indicator. + + + + + + + + Bugs + + Please report any bugs on the project + issue tracker. + + + diff --git a/doc/man-pages.xml b/doc/man-pages.xml new file mode 100644 index 00000000..b94b0141 --- /dev/null +++ b/doc/man-pages.xml @@ -0,0 +1,16 @@ + + Home Manager Reference Pages + + + Home Manager contributors + Author + + + 2017-2018Home Manager contributors + + + + + diff --git a/doc/manual.xml b/doc/manual.xml new file mode 100644 index 00000000..99987636 --- /dev/null +++ b/doc/manual.xml @@ -0,0 +1,40 @@ + + + Home Manager Manual + + + Preface + + This manual will eventually describes how to install, use and + extend Home Manager. + + + If you encounter problems, please report them on the + nix-devel + mailing list or on the + #nixos channel on Freenode. Bugs should be + reported in + NixOS’ + GitHub issue tracker. + + + + Commands prefixed with # have to be run as root, either + requiring to login as root user or temporarily switching to it using + sudo for example. + + + + + Configuration Options + + + diff --git a/modules/manual.nix b/modules/manual.nix index 636b184c..6ab61296 100644 --- a/modules/manual.nix +++ b/modules/manual.nix @@ -9,14 +9,14 @@ let It isn't perfect, but it seems to cover a vast majority of use cases. Caveat: even if the package is reached by a different means, the path above will be shown and not e.g. `${config.services.foo.package}`. */ - nixosManual = import { + homeManagerManual = import { inherit pkgs config; version = "0.1"; revision = "release-0.1"; options = let scrubbedEval = evalModules { - modules = [ { nixpkgs.system = pkgs.stdenv.system; } ] ++ baseModules; + modules = [ { nixpkgs.localSystem = config.nixpkgs.localSystem; } ] ++ baseModules; args = (config._module.args) // { modules = [ ]; }; specialArgs = { pkgs = scrubDerivations "pkgs" pkgs; }; }; @@ -32,14 +32,6 @@ let in scrubbedEval.options; }; - homeEnvironmentManPages = pkgs.runCommand "home-environment-manpages" { - allowedReferences = [ "out" ]; - } '' - install -v -D -m444 \ - ${nixosManual.manpages}/share/man/man5/configuration.nix.5 \ - $out/share/man/man5/home-configuration.nix.5 - ''; - in { @@ -60,22 +52,12 @@ in }; config = mkIf config.manual.manpages.enable { - home.packages = [ homeEnvironmentManPages ]; + home.packages = [ homeManagerManual.manpages ]; }; # To fix error during manpage build. meta = { maintainers = [ maintainers.rycee ]; - doc = builtins.toFile "nothingness" '' - - this is just to make the docs compile - - - - ''; + doc = builtins.toFile "nothingness" ""; }; }