bin/ansible-inventory: Smash `-' to `_' in dictionary key keywords.
[distorted-ansible] / bin / ansible-inventory
CommitLineData
3f6c5992
MW
1#! /usr/bin/cl-launch -X --
2;;; -*-lisp-*-
3
4(cl:defpackage #:ansible-inventory
4ee5e366 5 (:use #:common-lisp))
3f6c5992
MW
6
7(in-package #:ansible-inventory)
8
0b0ce183
MW
9(declaim (optimize debug))
10
3f6c5992
MW
11;;;--------------------------------------------------------------------------
12;;; General utilities.
13
14(defun compose (&rest funcs)
15 (reduce (lambda (f g)
16 (lambda (&rest args) (multiple-value-call g (apply f args))))
17 funcs :initial-value #'values))
18
19;;;--------------------------------------------------------------------------
20;;; JSON machinery.
21
22(defvar *print-json-comma*)
23
24(defun print-json-itemstart ()
25 (cond (*print-json-comma*
26 (princ ", ")
27 (pprint-newline :linear))
28 (t
29 (setf *print-json-comma* t))))
30
31(defun print-json-map* (thunk)
32 (print-json-itemstart)
33 (princ "{ ")
34 (pprint-indent :block 2)
35 (pprint-newline :linear)
36 (pprint-logical-block (*standard-output* nil)
37 (let ((*print-json-comma* nil)) (funcall thunk)))
38 (princ " ")
39 (pprint-indent :block 0)
40 (pprint-newline :linear)
41 (princ "}"))
42
43(defmacro print-json-map (&body body)
44 `(print-json-map* (lambda () ,@body)))
45
46(defun print-json-list* (thunk)
47 (print-json-itemstart)
48 (princ "[")
49 (pprint-indent :block 2)
50 (pprint-newline :linear)
51 (pprint-logical-block (*standard-output* nil)
52 (let ((*print-json-comma* nil)) (funcall thunk)))
53 (pprint-indent :block 0)
54 (pprint-newline :linear)
55 (princ "]"))
56
57(defmacro print-json-list (&body body)
58 `(print-json-list* (lambda () ,@body)))
59
60(defun print-json-mapping* (label thunk)
61 (print-json-itemstart)
62 (pprint-logical-block (*standard-output* nil)
63 (let ((*print-json-comma* nil))
06260a4f
MW
64 (print-json-simple (if (not (keywordp label))
65 label
66 (with-output-to-string (out)
67 (let ((labname (symbol-name label)))
68 (dotimes (i (length labname))
69 (let ((ch (char labname i)))
70 (write-char (if (char= ch #\-) #\_
71 (char-downcase ch))
72 out))))))))
3f6c5992
MW
73 (princ ": ")
74 (pprint-newline :miser)
75 (let ((*print-json-comma* nil))
76 (funcall thunk))))
77
78(defmacro print-json-mapping (label &body body)
79 `(print-json-mapping* ,label (lambda () ,@body)))
80
81(defun print-json-simple (value)
82 (typecase value
aa2cd939
MW
83 ((and vector (not string))
84 (print-json-list
85 (dotimes (i (length value)) (print-json-simple (aref value i)))))
86 (null
87 (princ "null"))
88 (list
89 (print-json-map
90 (dolist (i value)
91 (print-json-mapping (car i)
92 (print-json-simple (cdr i))))))
93 (hash-table
94 (print-json-map
95 (maphash (lambda (k v)
96 (print-json-mapping k
97 (print-json-simple v)))
98 value)))
3f6c5992
MW
99 (t
100 (print-json-itemstart)
101 (etypecase value
102 (integer (format t "~A" value))
103 (float (format t "~G" value))
104 (rational (format t "~G" (float value 0.0d0)))
105 (string (format t "~S" value))
106 ((eql t) (princ "true"))
107 ((eql nil) (princ "false"))
108 ((eql :undefined) (princ "undefined"))
109 (symbol (format t "~S" (string-downcase value)))))))
110
111(defun print-json* (thunk)
112 (let ((*print-json-comma* nil))
113 (pprint-logical-block (*standard-output* nil)
114 (funcall thunk))
115 (terpri)))
116
117(defmacro print-json (&body body)
118 `(print-json* (lambda () ,@body)))
119
120;;;--------------------------------------------------------------------------
121;;; Host definitions.
122
123(defvar *hosts* (make-hash-table))
124
125(export 'add-host)
126(defun addhost (name alist)
127 (setf (gethash name *hosts*) alist))
128
129(export 'defhost)
130(defmacro defhost (name &body alist)
131 `(progn (addhost ',name ',alist) ',name))
132
133(export 'host-plist)
134(defun host-plist (name)
135 (multiple-value-bind (plist foundp) (gethash name *hosts*)
136 (unless foundp (error "Host ~S not found" name))
137 plist))
138
139(export 'hostprop)
140(defun hostprop (name prop &optional default)
141 (multiple-value-bind (found-name value tail)
142 (get-properties (host-plist name) (list prop))
143 (declare (ignore found-name))
144 (if tail
145 (values value t)
146 (values default nil))))
147(defun (setf hostprop) (value name prop &optional default)
148 (declare (ignore default))
149 (let ((plist (host-plist name)))
150 (multiple-value-bind (found-name found-value tail)
151 (get-properties plist (list prop))
152 (declare (ignore found-name found-value))
153 (if tail
154 (setf (cadr tail) value)
155 (setf (gethash name *hosts*)
156 (cons prop (cons value plist))))
157 value)))
158
159(export 'hostprop-default)
160(defun hostprop-default (host prop value)
161 (multiple-value-bind (found-value foundp) (hostprop host prop)
162 (declare (ignore found-value))
163 (unless foundp (setf (hostprop host prop) value))))
164
165(export 'hostpropp)
166(defun hostpropp (host prop)
167 (multiple-value-bind (value foundp) (hostprop host prop)
168 (declare (ignore value))
169 foundp))
170
171(export 'maphosts)
172(defun maphosts (func)
173 (maphash (lambda (name plist)
174 (declare (ignore plist))
175 (funcall func name))
176 *hosts*))
177
178(export 'dohosts)
179(defmacro dohosts ((hostvar &optional valueform) &body body)
180 `(block nil (maphosts (lambda (,hostvar) ,@body)) ,valueform))
181
182;;;--------------------------------------------------------------------------
183;;; Group definitions.
184
185(defvar *groups* nil)
186
187(export 'add-group)
188(defun add-group (name type func)
189 (let* ((found (assoc name *groups*))
190 (list (ecase type
191 (:predicate
192 (let ((list nil))
193 (dohosts (host list)
194 (when (funcall func host) (push host list)))))
195 (:list
196 (funcall func)))))
197 (if found
198 (setf (cdr found) list)
199 (push (cons name list) *groups*))))
200
201(export 'defgroup)
202(defmacro defgroup (name type args &body body)
203 `(progn (add-group ',name ,type
204 ,(ecase type
205 (:predicate
206 (destructuring-bind (hostvar) args
207 `(lambda (,hostvar) ,@body)))
208 (:list
209 (destructuring-bind () args
210 `(lambda () ,@body)))))
211 ',name))
212
213;;;--------------------------------------------------------------------------
214;;; Post-processing hooks.
215
216(defstruct hook-entry
217 prio
218 func)
219
220(export '(prio-props prio-groups))
221(defconstant prio-props 10)
222(defconstant prio-groups 20)
223
224(defvar *hostproc-hooks* nil)
225
226(export 'addhook)
227(defun addhook (prio func)
228 (push (make-hook-entry :prio prio :func func) *hostproc-hooks*))
229
230(export 'defhook)
231(defmacro defhook ((prio) &body body)
232 `(addhook ,prio (lambda () ,@body)))
233
234;;;--------------------------------------------------------------------------
235;;; Read the input file and hook definitions.
236
237(defvar *progname*
238 #.(or *compile-file-pathname* *load-pathname*))
239
4ee5e366
MW
240(defvar *user-package*
241 (make-package "ANSIBLE-INVENTORY-USER"
242 :use '("CL" "ANSIBLE-INVENTORY")))
243
244(defun load-input (file)
245 (let ((*package* *user-package*))
246 (load file :verbose nil)))
247
248(defun parse-command-line (args)
3f6c5992
MW
249 (loop (let* ((arg (pop args))
250 (len (length arg)))
251 (cond ((string= arg "--") (return))
252 ((string= arg "-") (push arg args) (return))
253 ((and (plusp (length arg))
254 (char= (char arg 0) #\-))
255 (do ((i 1 (1+ i)))
256 ((>= i len))
257 (flet ((getarg ()
258 (cond ((< (1+ i) len)
259 (prog1 (subseq arg (1+ i))
260 (setf i len)))
261 (args
262 (pop args))
263 (t
d7003518
MW
264 (error "missing argument")))))
265 (case (char arg i)
266 (#\h (format t "usage: ~A FILE ...~%"
267 (pathname-name *progname*)))
268 (t (error "unknown option `-~A'" (char arg i)))))))
3f6c5992 269 (t (push arg args) (return)))))
4ee5e366 270 (mapc #'load-input args))
3f6c5992
MW
271
272;;;--------------------------------------------------------------------------
273;;; Run the hooks.
274
4ee5e366
MW
275(defun run-hooks ()
276 (setf *hostproc-hooks* (sort *hostproc-hooks* #'< :key #'hook-entry-prio))
277 (mapc (compose #'hook-entry-func #'funcall) *hostproc-hooks*))
3f6c5992
MW
278
279;;;--------------------------------------------------------------------------
280;;; Output the definitions.
281
4ee5e366
MW
282(defun output ()
283 (let ((*print-right-margin* 77))
284 (print-json
3f6c5992 285 (print-json-map
4ee5e366
MW
286 (print-json-mapping "all"
287 (print-json-list (maphosts #'print-json-simple)))
288 (dolist (assoc *groups*)
289 (print-json-mapping (car assoc)
290 (print-json-list
291 (mapc #'print-json-simple (cdr assoc)))))
292 (print-json-mapping "_meta"
3f6c5992 293 (print-json-map
4ee5e366
MW
294 (print-json-mapping "hostvars"
295 (print-json-map
296 (maphash (lambda (host plist)
297 (print-json-mapping host
298 (print-json-map
299 (do ((plist plist (cddr plist)))
300 ((endp plist))
301 (print-json-mapping (car plist)
302 (print-json-simple (cadr plist)))))))
303 *hosts*)))))))))
304
305;;;--------------------------------------------------------------------------
306;;; Main program.
307
308#+cl-launch
309(progn
310 (parse-command-line cl-launch:*arguments*)
311 (run-hooks)
312 (output))
3f6c5992
MW
313
314;;;----- That's all, folks --------------------------------------------------