Commit 6a117e27 authored by Naïm Favier's avatar Naïm Favier Committed by pennae
Browse files

nixos/doc: render option values using `lib.generators.toPretty`

Render un`_type`d defaults and examples as `literalExpression`s using
`lib.generators.toPretty` so that consumers don't have to reinvent Nix
pretty-printing. `renderOptionValue` is kept internal for now intentionally.

Make `toPretty` print floats as valid Nix values (without a tilde).

Get rid of the now-obsolete `substSpecial` function.

Move towards disallowing evaluation of packages in the manual by
raising a warning on `pkgs.foo.{outPath,drvPath}`; later, this should
throw an error. Instead, module authors should use `literalExpression`
and `mkPackageOption`.
parent 0b661ce3
Loading
Loading
Loading
Loading
+10 −4
Original line number Diff line number Diff line
@@ -279,6 +279,9 @@ rec {

  /* Pretty print a value, akin to `builtins.trace`.
   * Should probably be a builtin as well.
   * The pretty-printed string should be suitable for rendering default values
   * in the NixOS manual. In particular, it should be as close to a valid Nix expression
   * as possible.
   */
  toPretty = {
    /* If this option is true, attrsets like { __pretty = fn; val = …; }
@@ -294,7 +297,10 @@ rec {
            introSpace = if multiline then "\n${indent}  " else " ";
            outroSpace = if multiline then "\n${indent}" else " ";
    in if   isInt      v then toString v
    else if isFloat    v then "~${toString v}"
    # toString loses precision on floats, so we use toJSON instead. This isn't perfect
    # as the resulting string may not parse back as a float (e.g. 42, 1e-06), but for
    # pretty-printing purposes this is acceptable.
    else if isFloat    v then builtins.toJSON v
    else if isString   v then
      let
        lines = filter (v: ! isList v) (builtins.split "\n" v);
@@ -328,7 +334,7 @@ rec {
                         else "<function, args: {${showFnas}}>"
    else if isAttrs    v then
      # apply pretty values if allowed
      if attrNames v == [ "__pretty" "val" ] && allowPrettyValues
      if allowPrettyValues && v ? __pretty && v ? val
         then v.__pretty v.val
      else if v == {} then "{ }"
      else if v ? type && v.type == "derivation" then
+17 −4
Original line number Diff line number Diff line
@@ -218,7 +218,7 @@ rec {
  # the set generated with filterOptionSets.
  optionAttrSetToDocList = optionAttrSetToDocList' [];

  optionAttrSetToDocList' = prefix: options:
  optionAttrSetToDocList' = _: options:
    concatMap (opt:
      let
        docOption = rec {
@@ -234,9 +234,8 @@ rec {
          readOnly = opt.readOnly or false;
          type = opt.type.description or "unspecified";
        }
        // optionalAttrs (opt ? example) { example = scrubOptionValue opt.example; }
        // optionalAttrs (opt ? default) { default = scrubOptionValue opt.default; }
        // optionalAttrs (opt ? defaultText) { default = opt.defaultText; }
        // optionalAttrs (opt ? example) { example = renderOptionValue opt.example; }
        // optionalAttrs (opt ? default) { default = renderOptionValue (opt.defaultText or opt.default); }
        // optionalAttrs (opt ? relatedPackages && opt.relatedPackages != null) { inherit (opt) relatedPackages; };

        subOptions =
@@ -256,6 +255,9 @@ rec {
     efficient: the XML representation of derivations is very large
     (on the order of megabytes) and is not actually used by the
     manual generator.

     This function was made obsolete by renderOptionValue and is kept for
     compatibility with out-of-tree code.
  */
  scrubOptionValue = x:
    if isDerivation x then
@@ -265,6 +267,17 @@ rec {
    else x;


  /* Ensures that the given option value (default or example) is a `_type`d string
     by rendering Nix values to `literalExpression`s.
  */
  renderOptionValue = v:
    if v ? _type && v ? text then v
    else literalExpression (lib.generators.toPretty {
      multiline = true;
      allowPrettyValues = true;
    } v);


  /* For use in the `defaultText` and `example` option attributes. Causes the
     given string to be rendered verbatim in the documentation as Nix code. This
     is necessary for complex values, e.g. functions, or values that depend on
+1 −1
Original line number Diff line number Diff line
@@ -741,7 +741,7 @@ runTests {
    };
    expected = rec {
      int = "42";
      float = "~0.133700";
      float = "0.1337";
      bool = "true";
      emptystring = ''""'';
      string = ''"fn\''${o}\"r\\d"'';
+1 −18
Original line number Diff line number Diff line
@@ -26,7 +26,7 @@
  # If you include more than one option list into a document, you need to
  # provide different ids.
, variablelistId ? "configuration-variable-list"
  # Strig to prefix to the option XML/HTML id attributes.
  # String to prefix to the option XML/HTML id attributes.
, optionIdPrefix ? "opt-"
, revision ? "" # Specify revision for the options
# a set of options the docs we are generating will be merged into, as if by recursiveUpdate.
@@ -45,28 +45,11 @@
}:

let
  # Make a value safe for JSON. Functions are replaced by the string "<function>",
  # derivations are replaced with an attrset
  # { _type = "derivation"; name = <name of that derivation>; }.
  # We need to handle derivations specially because consumers want to know about them,
  # but we can't easily use the type,name subset of keys (since type is often used as
  # a module option and might cause confusion). Use _type,name instead to the same
  # effect, since _type is already used by the module system.
  substSpecial = x:
    if lib.isDerivation x then { _type = "derivation"; name = x.name; }
    else if builtins.isAttrs x then lib.mapAttrs (name: substSpecial) x
    else if builtins.isList x then map substSpecial x
    else if lib.isFunction x then "<function>"
    else x;

  rawOpts = lib.optionAttrSetToDocList options;
  transformedOpts = map transformOptions rawOpts;
  filteredOpts = lib.filter (opt: opt.visible && !opt.internal) transformedOpts;
  optionsList = lib.flip map filteredOpts
   (opt: opt
    // lib.optionalAttrs (opt ? example) { example = substSpecial opt.example; }
    // lib.optionalAttrs (opt ? default) { default = substSpecial opt.default; }
    // lib.optionalAttrs (opt ? type) { type = substSpecial opt.type; }
    // lib.optionalAttrs (opt ? relatedPackages && opt.relatedPackages != []) { relatedPackages = genRelatedPackages opt.relatedPackages opt.name; }
   );

+8 −3
Original line number Diff line number Diff line
@@ -48,10 +48,15 @@ let
        };
        scrubDerivations = namePrefix: pkgSet: mapAttrs
          (name: value:
            let wholeName = "${namePrefix}.${name}"; in
            if isAttrs value then
            let
              wholeName = "${namePrefix}.${name}";
              guard = lib.warn "Attempt to evaluate package ${wholeName} in option documentation; this is not supported and will eventually be an error. Use `mkPackageOption` or `literalExpression` instead.";
            in if isAttrs value then
              scrubDerivations wholeName value
              // (optionalAttrs (isDerivation value) { outPath = "\${${wholeName}}"; })
              // optionalAttrs (isDerivation value) {
                outPath = guard "\${${wholeName}}";
                drvPath = guard drvPath;
              }
            else value
          )
          pkgSet;