#! @shell@ -e # FIXME: rewrite this in a more suitable language. usage () { exec man nixos-option exit 1 } ##################### # Process Arguments # ##################### xml=false verbose=false nixPath="" option="" argfun="" for arg; do if test -z "$argfun"; then case $arg in -*) sarg="$arg" longarg="" while test "$sarg" != "-"; do case $sarg in --*) longarg=$arg; sarg="--";; -I) argfun="include_nixpath";; -*) usage;; esac # remove the first letter option sarg="-${sarg#??}" done ;; *) longarg=$arg;; esac for larg in $longarg; do case $larg in --xml) xml=true;; --verbose) verbose=true;; --help) usage;; -*) usage;; *) if test -z "$option"; then option="$larg" else usage fi;; esac done else case $argfun in set_*) var=$(echo $argfun | sed 's,^set_,,') eval $var=$arg ;; include_nixpath) nixPath="-I $arg $nixPath" ;; esac argfun="" fi done if $verbose; then set -x else set +x fi ############################# # Process the configuration # ############################# evalNix(){ result=$(nix-instantiate ${nixPath:+$nixPath} - --eval-only "$@" 2>&1) if test $? -eq 0; then cat <<EOF $result EOF return 0; else sed -n ' /^error/ { s/, at (string):[0-9]*:[0-9]*//; p; }; /^warning: Nix search path/ { p; }; ' <<EOF $result EOF return 1; fi } header="let nixos = import <nixpkgs/nixos> {}; nixpkgs = import <nixpkgs> {}; in with nixpkgs.lib; " # This function is used for converting the option definition path given by # the user into accessors for reaching the definition and the declaration # corresponding to this option. generateAccessors(){ if result=$(evalNix --strict --show-trace <<EOF $header let path = "${option:+$option}"; pathList = splitString "." path; walkOptions = attrsNames: result: if attrsNames == [] then result else let name = head attrsNames; rest = tail attrsNames; in if isOption result.options then walkOptions rest { options = result.options.type.getSubOptions ""; opt = ''(\${result.opt}.type.getSubOptions "")''; cfg = ''\${result.cfg}."\${name}"''; } else walkOptions rest { options = result.options.\${name}; opt = ''\${result.opt}."\${name}"''; cfg = ''\${result.cfg}."\${name}"''; } ; walkResult = (if path == "" then x: x else walkOptions pathList) { options = nixos.options; opt = ''nixos.options''; cfg = ''nixos.config''; }; in ''let option = \${walkResult.opt}; config = \${walkResult.cfg}; in'' EOF ) then echo $result else # In case of error we want to ignore the error message roduced by the # script above, as it is iterating over each attribute, which does not # produce a nice error message. The following code is a fallback # solution which is cause a nicer error message in the next # evaluation. echo "\"let option = nixos.options${option:+.$option}; config = nixos.config${option:+.$option}; in\"" fi } header="$header $(eval echo $(generateAccessors)) " evalAttr(){ local prefix="$1" local strict="$2" local suffix="$3" # If strict is set, then set it to "true". test -n "$strict" && strict=true evalNix ${strict:+--strict} <<EOF $header let value = $prefix${suffix:+.$suffix}; strict = ${strict:-false}; cleanOutput = x: with nixpkgs.lib; if isDerivation x then x.outPath else if isFunction x then "<CODE>" else if strict then if isAttrs x then mapAttrs (n: cleanOutput) x else if isList x then map cleanOutput x else x else x; in cleanOutput value EOF } evalOpt(){ evalAttr "option" "" "$@" } evalCfg(){ local strict="$1" evalAttr "config" "$strict" } findSources(){ local suffix=$1 evalNix --strict <<EOF $header option.$suffix EOF } # Given a result from nix-instantiate, recover the list of attributes it # contains. attrNames() { local attributeset=$1 # sed is used to replace un-printable subset by 0s, and to remove most of # the inner-attribute set, which reduce the likelyhood to encounter badly # pre-processed input. echo "builtins.attrNames $attributeset" | \ sed 's,<[A-Z]*>,0,g; :inner; s/{[^\{\}]*};/0;/g; t inner;' | \ evalNix --strict } # map a simple list which contains strings or paths. nixMap() { local fun="$1" local list="$2" local elem for elem in $list; do test $elem = '[' -o $elem = ']' && continue; $fun $elem done } # This duplicates the work made below, but it is useful for processing # the output of nixos-option with other tools such as nixos-gui. if $xml; then evalNix --xml --no-location <<EOF $header let sources = builtins.map (f: f.source); opt = option; cfg = config; in with nixpkgs.lib; let optStrict = v: let traverse = x : if isAttrs x then if x ? outPath then true else all id (mapAttrsFlatten (n: traverseNoAttrs) x) else traverseNoAttrs x; traverseNoAttrs = x: # do not continue in attribute sets if isAttrs x then true else if isList x then all id (map traverse x) else true; in assert traverse v; v; in if isOption opt then optStrict ({} // optionalAttrs (opt ? default) { inherit (opt) default; } // optionalAttrs (opt ? example) { inherit (opt) example; } // optionalAttrs (opt ? description) { inherit (opt) description; } // optionalAttrs (opt ? type) { typename = opt.type.description; } // optionalAttrs (opt ? options) { inherit (opt) options; } // { # to disambiguate the xml output. _isOption = true; declarations = sources opt.declarations; definitions = sources opt.definitions; value = cfg; }) else opt EOF exit $? fi if test "$(evalOpt "_type" 2> /dev/null)" = '"option"'; then echo "Value:" evalCfg 1 echo echo "Default:" if default=$(evalOpt "default" - 2> /dev/null); then echo "$default" else echo "<None>" fi echo if example=$(evalOpt "example" - 2> /dev/null); then echo "Example:" echo "$example" echo fi echo "Description:" echo echo $(evalOpt "description") echo $desc; printPath () { echo " $1"; } echo "Declared by:" nixMap printPath "$(findSources "declarations")" echo echo "Defined by:" nixMap printPath "$(findSources "files")" echo else # echo 1>&2 "Warning: This value is not an option." result=$(evalCfg "") if names=$(attrNames "$result" 2> /dev/null); then echo 1>&2 "This attribute set contains:" escapeQuotes () { eval echo "$1"; } nixMap escapeQuotes "$names" else echo 1>&2 "An error occurred while looking for attribute names." echo $result fi fi