diff --git a/default.nix b/default.nix index 5f56a6b1..e9078d2d 100644 --- a/default.nix +++ b/default.nix @@ -5,8 +5,11 @@ rec { # CLI cli = pkgs.callPackage ./devshell { }; + # Get the modules documentation from an empty evaluation + modules-docs = (eval { configuration = { }; }).config.modules-docs; + # Docs - docs = pkgs.callPackage ./docs { }; + docs = pkgs.callPackage ./docs { inherit modules-docs; }; # Evaluate the devshell module eval = import ./modules pkgs; diff --git a/devshell.toml b/devshell.toml index 057bccd3..c82d6a83 100644 --- a/devshell.toml +++ b/devshell.toml @@ -17,7 +17,7 @@ packages = [ "go", "goreleaser", "mdbook", - "mdsh", + "webfs", ] # Use this section to set environment variables to have in the environment. diff --git a/docs/default.nix b/docs/default.nix index 86db48d3..4b2db565 100644 --- a/docs/default.nix +++ b/docs/default.nix @@ -1,4 +1,5 @@ { mdbook +, modules-docs , stdenv }: let @@ -16,6 +17,7 @@ stdenv.mkDerivation { }; buildPhase = '' + cp ${modules-docs.markdown} devshell.toml.md mdbook build ''; diff --git a/docs/devshell.toml b/docs/devshell.toml deleted file mode 100644 index 2056cc1c..00000000 --- a/docs/devshell.toml +++ /dev/null @@ -1,55 +0,0 @@ -[devshell] -# This is the name of your environment. It should usually map to the project -# name. -name = "my-project" - -# Add packages from nixpkgs here. Use `nix search nixpkgs ` to find the -# package that you need. -packages = [ - "go", - "mdsh", -] - -# Message Of The Day (MOTD) is displayed when entering the environment with an -# interactive shell. By default it will show the project name. -# -# motd = "" - -# Use this section to set environment variables to have in the environment. -# -# NOTE: all the values are escaped -[env] -FOO = 1 - -# These are bash-specific configurations. The idea is to maybe support other -# shell environments in the future, although it is not the case right now. -[bash] - -# Loaded after the environment setup. Useful to set dynamic environment -# variables. -extra = """ -export BAR=$FOO -""" - -# Only loaded in interactive shells. NOTE: `nix-shell -c "ls"` is interactive -interactive = """ - -""" - -# Declare commands that are available in the environment. -[[commands]] -help = "prints hello" -name = "hello" -command = "echo hello" - -[[commands]] -help = "used to format Nix code" -name = "nixpkgs-fmt" -package = "nixpkgs-fmt" -category = "formatters" - -[[commands]] -help = "github utility" -name = "hub" -package = "gitAndTools.hub" -category = "utilites" diff --git a/docs/devshell.toml.md b/docs/devshell.toml.md deleted file mode 100644 index 8330103f..00000000 --- a/docs/devshell.toml.md +++ /dev/null @@ -1,99 +0,0 @@ -# `devshell.toml` documentation - -All the arguments below are optional. - -## Example - -[$ ./devshell.toml](./devshell.toml) as toml -```toml -[devshell] -# This is the name of your environment. It should usually map to the project -# name. -name = "my-project" - -# Add packages from nixpkgs here. Use `nix search nixpkgs ` to find the -# package that you need. -packages = [ - "go", - "mdsh", -] - -# Message Of The Day (MOTD) is displayed when entering the environment with an -# interactive shell. By default it will show the project name. -# -# motd = "" - -# Use this section to set environment variables to have in the environment. -# -# NOTE: all the values are escaped -[env] -FOO = 1 - -# These are bash-specific configurations. The idea is to maybe support other -# shell environments in the future, although it is not the case right now. -[bash] - -# Loaded after the environment setup. Useful to set dynamic environment -# variables. -extra = """ -export BAR=$FOO -""" - -# Only loaded in interactive shells. NOTE: `nix-shell -c "ls"` is interactive -interactive = """ - -""" - -# Declare commands that are available in the environment. -[[commands]] -help = "prints hello" -name = "hello" -command = "echo hello" - -[[commands]] -help = "used to format Nix code" -name = "nixpkgs-fmt" -package = "nixpkgs-fmt" -category = "formatters" - -[[commands]] -help = "github utility" -name = "hub" -package = "gitAndTools.hub" -category = "utilites" -``` - -## Schema - -WIP - -### The `name` field - -The name of the package/project is determined by the name field, for example: - -```toml -name = "Example" -``` - -The name can contain word characters `[a-zA-Z0-9_]`. For packages it is -recommended to follow the package naming guidelines. - -The name field is optional and defaults to `devshell`. - -### The `packages` field - -### The `motd` field - -### The `env` section - -### The `bash.extra` field - -### The `bash.internal` field - -### The `commands` entries - -* `command`: -* `help`: -* `name`: -* `package`: - diff --git a/docs/serve.sh b/docs/serve.sh new file mode 100755 index 00000000..29d63a73 --- /dev/null +++ b/docs/serve.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +# Build and serve the docs for local development +set -euo pipefail +webfsd -d -r "$(nix-build "$(dirname "$0")/.." -A docs)" diff --git a/modules/default.nix b/modules/default.nix index 330e33fa..0f8c10ed 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -21,7 +21,5 @@ in activationPackage = module.config.devshell.activationPackage; - docs = module.config.devshell.docs; - shell = module.config.devshell.shell; } diff --git a/modules/devshell.nix b/modules/devshell.nix index 74fbfe6f..4869be4f 100644 --- a/modules/devshell.nix +++ b/modules/devshell.nix @@ -134,8 +134,10 @@ in { options.devshell = { bashPackage = mkOption { + internal = true; type = strOrPackage; default = pkgs.bashInteractive; + defaultText = "pkgs.bashInteractive"; description = "Version of bash to use in the project"; }; diff --git a/modules/modules-docs.nix b/modules/modules-docs.nix new file mode 100644 index 00000000..17174efd --- /dev/null +++ b/modules/modules-docs.nix @@ -0,0 +1,205 @@ +# MIT - Copyright (c) 2017-2019 Robert Helgesson and Home Manager contributors. +# +# This is an adapted version of the original https://gitlab.com/rycee/nmd/ +{ lib, pkgs, options, config, modulesPath, ... }: +with lib; +let + cfg = config.modules-docs; + + # Generate some meta data for a list of packages. This is what + # `relatedPackages` option of `mkOption` 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). + mkRelatedPackages = + let + unpack = p: + if isString p then + { name = p; } + else if isList p then + { path = p; } + else + p; + + repack = args: + let + name = args.name or (concatStringsSep "." args.path); + path = args.path or [ args.name ]; + pkg = args.package or ( + let + bail = throw "Invalid package attribute path '${toString path}'"; + in + attrByPath path bail pkgs + ); + in + { + attrName = name; + packageName = pkg.meta.name; + available = pkg.meta.available; + } // optionalAttrs (pkg.meta ? description) { + inherit (pkg.meta) description; + } // optionalAttrs (pkg.meta ? longDescription) { + inherit (pkg.meta) longDescription; + } // optionalAttrs (args ? comment) { inherit (args) comment; }; + in + map (p: repack (unpack p)); + + # Transforms a module path into a (path, url) tuple where path is relative + # to the repo root, and URL points to an online view of the module. + mkDeclaration = + let + rootsWithPrefixes = map + (p: p // { prefix = "${toString p.path}/"; }) + cfg.roots; + in + decl: + let + root = lib.findFirst + (x: lib.hasPrefix x.prefix decl) + null + rootsWithPrefixes; + in + if root == null then + # We need to strip references to /nix/store/* from the options or + # else the build will fail. + { path = removePrefix "${builtins.storePath}/" decl; url = ""; } + else + rec { + path = removePrefix root.prefix decl; + url = "${root.url}/tree/${root.branch}/${path}"; + }; + + # Sort modules and put "enable" and "package" declarations first. + moduleDocCompare = a: b: + let + isEnable = lib.hasPrefix "enable"; + isPackage = lib.hasPrefix "package"; + compareWithPrio = pred: cmp: splitByAndCompare pred compare cmp; + moduleCmp = compareWithPrio isEnable (compareWithPrio isPackage compare); + in + compareLists moduleCmp a.loc b.loc < 0; + + # Replace functions by the string + substFunction = x: + if builtins.isAttrs x then + mapAttrs (name: substFunction) x + else if builtins.isList x then + map substFunction x + else if isFunction x then + "" + else + x; + + cleanUpOption = opt: + let + applyOnAttr = n: f: optionalAttrs (hasAttr n opt) { ${n} = f opt.${n}; }; + in + opt + // applyOnAttr "declarations" (map mkDeclaration) + // applyOnAttr "example" substFunction + // applyOnAttr "default" substFunction + // applyOnAttr "type" substFunction + // applyOnAttr "relatedPackages" mkRelatedPackages; + + optionsDocs = map cleanUpOption ( + sort + moduleDocCompare + ( + filter + (opt: opt.visible && !opt.internal) + (optionAttrSetToDocList options) + ) + ); + + # TODO: display values like TOML instead. + toMarkdown = optionsDocs: + let + # TODO: handle opt.relatedPackages. What is it for? + optToMd = opt: + '' + ## ${opt.name} + + '' + + (lib.optionalString opt.internal "\n**internal**\n") + + opt.description + "\n" + + (lib.optionalString (opt ? default && opt.default != null) '' + **Default value**: + ```nix + ${ + # When defaultText is set on the module, we only get back a + # string here and defaultText has disappeared. Re-hydrate that + # knowledge by looking at the type. + if builtins.isString opt.default && !(lib.hasPrefix "string" opt.type) then + # If it's a defaultText, assume it's already formatted as nix + # code. + opt.default + else + builtins.toJSON opt.default + } + ``` + + '') + + '' + **Type**: ${opt.type} + + '' + + (lib.optionalString (opt ? example) '' + **Example value**: + ```nix + ${builtins.toJSON opt.example} + ``` + + '') + + '' + Declared in: + '' + + ( + lib.concatStringsSep + "\n" + (map + (decl: "* [./modules/${decl.path}](${decl.url})") + opt.declarations + ) + ) + + "\n" + ; + in + concatStringsSep "\n" (map optToMd optionsDocs); +in +{ + options.modules-docs = { + roots = mkOption { + internal = true; + type = types.listOf types.attrs; + description = '' + Add to this list for each new module root. The attr should have path, + url and branch attributes (TODO: convert to submodule). + ''; + }; + + data = mkOption { + visible = false; + type = types.listOf types.attrs; + description = '' + Contains a list of each module option, nicely split out for + consumption. + ''; + }; + + markdown = mkOption { + visible = false; + type = types.package; + description = '' + Modules documentation rendered to markdown. + ''; + }; + }; + + config.modules-docs = { + data = optionsDocs; + markdown = pkgs.writeText "modules-docs.md" (toMarkdown optionsDocs); + }; +} diff --git a/modules/modules.nix b/modules/modules.nix index 66ed621e..88682510 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -6,6 +6,15 @@ let ./back-compat.nix ./commands.nix ./devshell.nix + ./modules-docs.nix + { + # Configure modules-docs + config.modules-docs.roots = [{ + url = "https://github.com/numtide/devshell"; + path = ../.; + branch = "master"; + }]; + } ]; pkgsModule = { config, ... }: { diff --git a/tests/modules-docs.nix b/tests/modules-docs.nix new file mode 100644 index 00000000..9e1a27e5 --- /dev/null +++ b/tests/modules-docs.nix @@ -0,0 +1,29 @@ +{ system ? builtins.currentSystem }: +let + nixpkgs = import ../nix/nixpkgs.nix; + + pkgs = import nixpkgs { + config = { }; + overlays = [ ]; + }; + + configuration = with pkgs.lib; { + config.modules-docs.baseURL = "https://example.com"; + + options.test = mkOption { + type = types.str; + default = "XXX"; + description = '' + This is a description. + ''; + }; + }; + + module = pkgs.lib.evalModules { + modules = [ ../modules/modules-docs.nix configuration ]; + specialArgs = { + modulesPath = builtins.toString ../modules; + }; + }; +in +module