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