1#! @shell@ -e
2
3# FIXME: rewrite this in a more suitable language.
4
5usage () {
6 exec man nixos-option
7 exit 1
8}
9
10#####################
11# Process Arguments #
12#####################
13
14xml=false
15verbose=false
16nixPath=""
17
18option=""
19exit_code=0
20
21argfun=""
22for arg; do
23 if test -z "$argfun"; then
24 case $arg in
25 -*)
26 sarg="$arg"
27 longarg=""
28 while test "$sarg" != "-"; do
29 case $sarg in
30 --*) longarg=$arg; sarg="--";;
31 -I) argfun="include_nixpath";;
32 -*) usage;;
33 esac
34 # remove the first letter option
35 sarg="-${sarg#??}"
36 done
37 ;;
38 *) longarg=$arg;;
39 esac
40 for larg in $longarg; do
41 case $larg in
42 --xml) xml=true;;
43 --verbose) verbose=true;;
44 --help) usage;;
45 -*) usage;;
46 *) if test -z "$option"; then
47 option="$larg"
48 else
49 usage
50 fi;;
51 esac
52 done
53 else
54 case $argfun in
55 set_*)
56 var=$(echo $argfun | sed 's,^set_,,')
57 eval $var=$arg
58 ;;
59 include_nixpath)
60 nixPath="-I $arg $nixPath"
61 ;;
62 esac
63 argfun=""
64 fi
65done
66
67if $verbose; then
68 set -x
69else
70 set +x
71fi
72
73#############################
74# Process the configuration #
75#############################
76
77evalNix(){
78 # disable `-e` flag, it's possible that the evaluation of `nix-instantiate` fails (e.g. due to broken pkgs)
79 set +e
80 result=$(nix-instantiate ${nixPath:+$nixPath} - --eval-only "$@" 2>&1)
81 exit_code=$?
82 set -e
83
84 if test $exit_code -eq 0; then
85 cat <<EOF
86$result
87EOF
88 return 0;
89 else
90 sed -n '
91 /^error/ { s/, at (string):[0-9]*:[0-9]*//; p; };
92 /^warning: Nix search path/ { p; };
93' <<EOF
94$result
95EOF
96 exit_code=1
97 fi
98}
99
100header="let
101 nixos = import <nixpkgs/nixos> {};
102 nixpkgs = import <nixpkgs> {};
103in with nixpkgs.lib;
104"
105
106# This function is used for converting the option definition path given by
107# the user into accessors for reaching the definition and the declaration
108# corresponding to this option.
109generateAccessors(){
110 if result=$(evalNix --strict --show-trace <<EOF
111$header
112
113let
114 path = "${option:+$option}";
115 pathList = splitString "." path;
116
117 walkOptions = attrsNames: result:
118 if attrsNames == [] then
119 result
120 else
121 let name = head attrsNames; rest = tail attrsNames; in
122 if isOption result.options then
123 walkOptions rest {
124 options = result.options.type.getSubOptions "";
125 opt = ''(\${result.opt}.type.getSubOptions "")'';
126 cfg = ''\${result.cfg}."\${name}"'';
127 }
128 else
129 walkOptions rest {
130 options = result.options.\${name};
131 opt = ''\${result.opt}."\${name}"'';
132 cfg = ''\${result.cfg}."\${name}"'';
133 }
134 ;
135
136 walkResult = (if path == "" then x: x else walkOptions pathList) {
137 options = nixos.options;
138 opt = ''nixos.options'';
139 cfg = ''nixos.config'';
140 };
141
142in
143 ''let option = \${walkResult.opt}; config = \${walkResult.cfg}; in''
144EOF
145)
146 then
147 echo $result
148 else
149 # In case of error we want to ignore the error message roduced by the
150 # script above, as it is iterating over each attribute, which does not
151 # produce a nice error message. The following code is a fallback
152 # solution which is cause a nicer error message in the next
153 # evaluation.
154 echo "\"let option = nixos.options${option:+.$option}; config = nixos.config${option:+.$option}; in\""
155 fi
156}
157
158header="$header
159$(eval echo $(generateAccessors))
160"
161
162evalAttr(){
163 local prefix="$1"
164 local strict="$2"
165 local suffix="$3"
166
167 # If strict is set, then set it to "true".
168 test -n "$strict" && strict=true
169
170 evalNix ${strict:+--strict} <<EOF
171$header
172
173let
174 value = $prefix${suffix:+.$suffix};
175 strict = ${strict:-false};
176 cleanOutput = x: with nixpkgs.lib;
177 if isDerivation x then x.outPath
178 else if isFunction x then "<CODE>"
179 else if strict then
180 if isAttrs x then mapAttrs (n: cleanOutput) x
181 else if isList x then map cleanOutput x
182 else x
183 else x;
184in
185 cleanOutput value
186EOF
187}
188
189evalOpt(){
190 evalAttr "option" "" "$@"
191}
192
193evalCfg(){
194 local strict="$1"
195 evalAttr "config" "$strict"
196}
197
198findSources(){
199 local suffix=$1
200 evalNix --strict <<EOF
201$header
202
203option.$suffix
204EOF
205}
206
207# Given a result from nix-instantiate, recover the list of attributes it
208# contains.
209attrNames() {
210 local attributeset=$1
211 # sed is used to replace un-printable subset by 0s, and to remove most of
212 # the inner-attribute set, which reduce the likelyhood to encounter badly
213 # pre-processed input.
214 echo "builtins.attrNames $attributeset" | \
215 sed 's,<[A-Z]*>,0,g; :inner; s/{[^\{\}]*};/0;/g; t inner;' | \
216 evalNix --strict
217}
218
219# map a simple list which contains strings or paths.
220nixMap() {
221 local fun="$1"
222 local list="$2"
223 local elem
224 for elem in $list; do
225 test $elem = '[' -o $elem = ']' && continue;
226 $fun $elem
227 done
228}
229
230# This duplicates the work made below, but it is useful for processing
231# the output of nixos-option with other tools such as nixos-gui.
232if $xml; then
233 evalNix --xml --no-location <<EOF
234$header
235
236let
237 sources = builtins.map (f: f.source);
238 opt = option;
239 cfg = config;
240in
241
242with nixpkgs.lib;
243
244let
245 optStrict = v:
246 let
247 traverse = x :
248 if isAttrs x then
249 if x ? outPath then true
250 else all id (mapAttrsFlatten (n: traverseNoAttrs) x)
251 else traverseNoAttrs x;
252 traverseNoAttrs = x:
253 # do not continue in attribute sets
254 if isAttrs x then true
255 else if isList x then all id (map traverse x)
256 else true;
257 in assert traverse v; v;
258in
259
260if isOption opt then
261 optStrict ({}
262 // optionalAttrs (opt ? default) { inherit (opt) default; }
263 // optionalAttrs (opt ? example) { inherit (opt) example; }
264 // optionalAttrs (opt ? description) { inherit (opt) description; }
265 // optionalAttrs (opt ? type) { typename = opt.type.description; }
266 // optionalAttrs (opt ? options) { inherit (opt) options; }
267 // {
268 # to disambiguate the xml output.
269 _isOption = true;
270 declarations = sources opt.declarations;
271 definitions = sources opt.definitions;
272 value = cfg;
273 })
274else
275 opt
276EOF
277 exit $?
278fi
279
280if test "$(evalOpt "_type" 2> /dev/null)" = '"option"'; then
281 echo "Value:"
282 evalCfg 1
283
284 echo
285
286 echo "Default:"
287 if default=$(evalOpt "default" - 2> /dev/null); then
288 echo "$default"
289 else
290 echo "<None>"
291 fi
292 echo
293 if example=$(evalOpt "example" - 2> /dev/null); then
294 echo "Example:"
295 echo "$example"
296 echo
297 fi
298 echo "Description:"
299 echo
300 echo $(evalOpt "description")
301
302 echo $desc;
303
304 printPath () { echo " $1"; }
305
306 echo "Declared by:"
307 nixMap printPath "$(findSources "declarations")"
308 echo
309 echo "Defined by:"
310 nixMap printPath "$(findSources "files")"
311 echo
312
313else
314 # echo 1>&2 "Warning: This value is not an option."
315
316 result=$(evalCfg "")
317 if names=$(attrNames "$result" 2> /dev/null); then
318 echo 1>&2 "This attribute set contains:"
319 escapeQuotes () { eval echo "$1"; }
320 nixMap escapeQuotes "$names"
321 else
322 echo 1>&2 "An error occurred while looking for attribute names."
323 echo $result
324 fi
325fi
326
327exit $exit_code