|
| 1 | +{ |
| 2 | + nixos, |
| 3 | + # list representing a nixos option path (e.g. ['console' 'enable']), or a |
| 4 | + # prefix of such a path (e.g. ['console']), or a string representing the same |
| 5 | + # (e.g. 'console.enable') |
| 6 | + path, |
| 7 | + # whether to recurse down the config attrset and show each set value instead |
| 8 | + recursive, |
| 9 | +}: |
| 10 | + |
| 11 | +let |
| 12 | + inherit (nixos.pkgs) lib; |
| 13 | + |
| 14 | + path' = if lib.isString path then (if path == "" then [ ] else readOption path) else path; |
| 15 | + |
| 16 | + # helper that maps `f` on subslices starting when `predStart x` and |
| 17 | + # ending when `predEnd x` (no support for nested occurrences) |
| 18 | + flatMapSlices = |
| 19 | + predStart: predEnd: f: list: |
| 20 | + let |
| 21 | + empty = { |
| 22 | + result = [ ]; |
| 23 | + active = [ ]; |
| 24 | + }; |
| 25 | + op = |
| 26 | + { result, active }: |
| 27 | + x: |
| 28 | + if predStart x && predEnd x then |
| 29 | + { |
| 30 | + result = result ++ active ++ f [ x ]; |
| 31 | + active = [ ]; |
| 32 | + } |
| 33 | + else if predStart x then |
| 34 | + { |
| 35 | + result = result ++ active; |
| 36 | + active = [ x ]; |
| 37 | + } |
| 38 | + else if predEnd x then |
| 39 | + { |
| 40 | + result = result ++ f (active ++ [ x ]); |
| 41 | + active = [ ]; |
| 42 | + } |
| 43 | + else |
| 44 | + { |
| 45 | + inherit result; |
| 46 | + active = active ++ [ x ]; |
| 47 | + }; |
| 48 | + in |
| 49 | + (x: x.result ++ x.active) (lib.foldl op empty list); |
| 50 | + |
| 51 | + # tries to invert showOption, taking a written-out option name and splitting |
| 52 | + # it into its parts |
| 53 | + readOption = |
| 54 | + str: |
| 55 | + let |
| 56 | + unescape = list: lib.replaceStrings (map (c: "\\${c}") list) list; |
| 57 | + unescapeNixString = lib.flip lib.pipe [ |
| 58 | + (lib.concatStringsSep ".") |
| 59 | + (unescape [ "$" ]) |
| 60 | + builtins.fromJSON |
| 61 | + ]; |
| 62 | + in |
| 63 | + flatMapSlices (lib.hasPrefix "\"") (lib.hasSuffix "\"") (x: [ (unescapeNixString x) ]) ( |
| 64 | + lib.splitString "." str |
| 65 | + ); |
| 66 | + |
| 67 | + # like 'mapAttrsRecursiveCond' but handling errors in the attrset tree as leaf |
| 68 | + # nodes (which means `f` is expected to handle shallow errors) |
| 69 | + safeMapAttrsRecursiveCond = |
| 70 | + cond: f: set: |
| 71 | + let |
| 72 | + recurse = |
| 73 | + path: |
| 74 | + lib.mapAttrs ( |
| 75 | + name: value: |
| 76 | + let |
| 77 | + e = builtins.tryEval value; |
| 78 | + path' = path ++ [ name ]; |
| 79 | + in |
| 80 | + if e.success && lib.isAttrs value && cond value then recurse path' value else f path' value |
| 81 | + ); |
| 82 | + in |
| 83 | + recurse [ ] set; |
| 84 | + |
| 85 | + # traverse the option tree along `path` from `root`, returning the option or |
| 86 | + # attrset at the given location |
| 87 | + optionByPath = |
| 88 | + path: root: |
| 89 | + let |
| 90 | + into = |
| 91 | + opt: part: |
| 92 | + if lib.isOption opt && opt.type.descriptionClass == "composite" then |
| 93 | + opt.type.getSubOptions [ ] |
| 94 | + else if lib.isOption opt then |
| 95 | + throw "Trying to access '${part}' inside ${opt.type.name} option while traversing option path '${lib.showOption path}'" |
| 96 | + else if lib.isAttrs opt && lib.hasAttr part opt then |
| 97 | + opt.${part} |
| 98 | + else |
| 99 | + throw "Found neither an attrset nor supported option type near '${part}' while traversing option path '${lib.showOption path}'"; |
| 100 | + in |
| 101 | + lib.foldl into root path; |
| 102 | + |
| 103 | + toPretty = lib.generators.toPretty { multiline = true; }; |
| 104 | + safeToPretty = |
| 105 | + x: |
| 106 | + let |
| 107 | + e = builtins.tryEval (toPretty x); |
| 108 | + in |
| 109 | + if e.success then e.value else "[1;31m«error»[m"; |
| 110 | + |
| 111 | + indent = str: lib.concatStringsSep "\n" (map (x: " " + x) (lib.splitString "\n" str)); |
| 112 | + |
| 113 | + optionAttrNames = attrs: lib.filter (x: x != "_module") (lib.attrNames attrs); |
| 114 | + |
| 115 | + ## full, non-recursive mode: print an option from `options` |
| 116 | + renderAttrs = |
| 117 | + attrs: "This attribute set contains:\n${lib.concatStringsSep "\n" (optionAttrNames attrs)}"; |
| 118 | + |
| 119 | + renderOption = |
| 120 | + option: value: |
| 121 | + let |
| 122 | + entry = |
| 123 | + cond: heading: value: |
| 124 | + lib.optional cond "${heading}:\n${indent value}"; |
| 125 | + in |
| 126 | + lib.concatStringsSep "\n\n" ( |
| 127 | + lib.concatLists [ |
| 128 | + (entry true "Value" (toPretty value)) |
| 129 | + (entry (option ? default) "Default" (toPretty option.default)) |
| 130 | + (entry (option ? type) "Type" (option.type.description)) |
| 131 | + (entry (option ? description) "Description" (lib.removeSuffix "\n" option.description)) |
| 132 | + (entry (option ? example) "Example" (toPretty option.example)) |
| 133 | + (entry (option ? declarations) "Declared by" (lib.concatStringsSep "\n" option.declarations)) |
| 134 | + (entry (option ? files) "Defined by" (lib.concatStringsSep "\n" option.files)) |
| 135 | + ] |
| 136 | + ); |
| 137 | + |
| 138 | + renderFull = |
| 139 | + entry: configEntry: |
| 140 | + if lib.isOption entry then |
| 141 | + renderOption entry configEntry |
| 142 | + else if lib.isAttrs entry then |
| 143 | + renderAttrs entry |
| 144 | + else |
| 145 | + throw "Found neither an attrset nor option at option path '${lib.showOption path'}'"; |
| 146 | + |
| 147 | + ## recursive mode: print paths and values from `config` |
| 148 | + renderRecursive = |
| 149 | + config: |
| 150 | + let |
| 151 | + renderShort = n: v: "${lib.showOption (path' ++ n)} = ${safeToPretty v};"; |
| 152 | + mapAttrsRecursive' = safeMapAttrsRecursiveCond (x: !lib.isDerivation x); |
| 153 | + in |
| 154 | + if lib.isAttrs config then |
| 155 | + lib.concatStringsSep "\n" (lib.collect lib.isString (mapAttrsRecursive' renderShort config)) |
| 156 | + else |
| 157 | + renderShort [ ] config; |
| 158 | + |
| 159 | +in |
| 160 | +if !lib.hasAttrByPath path' nixos.config then |
| 161 | + throw "Couldn't resolve config path '${lib.showOption path'}'" |
| 162 | +else |
| 163 | + let |
| 164 | + optionEntry = optionByPath path' nixos.options; |
| 165 | + configEntry = lib.attrByPath path' null nixos.config; |
| 166 | + in |
| 167 | + if recursive then renderRecursive configEntry else renderFull optionEntry configEntry |
0 commit comments