bin/ansible-inventory: Make the script loadable into a Lisp system.
[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))
64 (print-json-simple label))
65 (princ ": ")
66 (pprint-newline :miser)
67 (let ((*print-json-comma* nil))
68 (funcall thunk))))
69
70(defmacro print-json-mapping (label &body body)
71 `(print-json-mapping* ,label (lambda () ,@body)))
72
73(defun print-json-simple (value)
74 (typecase value
75 (list (print-json-list (dolist (i value) (print-json-simple i))))
76 (hash-table (print-json-map (maphash (lambda (k v)
77 (print-json-mapping k
78 (print-json-simple v)))
79 value)))
80 (t
81 (print-json-itemstart)
82 (etypecase value
83 (integer (format t "~A" value))
84 (float (format t "~G" value))
85 (rational (format t "~G" (float value 0.0d0)))
86 (string (format t "~S" value))
87 ((eql t) (princ "true"))
88 ((eql nil) (princ "false"))
89 ((eql :undefined) (princ "undefined"))
90 (symbol (format t "~S" (string-downcase value)))))))
91
92(defun print-json* (thunk)
93 (let ((*print-json-comma* nil))
94 (pprint-logical-block (*standard-output* nil)
95 (funcall thunk))
96 (terpri)))
97
98(defmacro print-json (&body body)
99 `(print-json* (lambda () ,@body)))
100
101;;;--------------------------------------------------------------------------
102;;; Host definitions.
103
104(defvar *hosts* (make-hash-table))
105
106(export 'add-host)
107(defun addhost (name alist)
108 (setf (gethash name *hosts*) alist))
109
110(export 'defhost)
111(defmacro defhost (name &body alist)
112 `(progn (addhost ',name ',alist) ',name))
113
114(export 'host-plist)
115(defun host-plist (name)
116 (multiple-value-bind (plist foundp) (gethash name *hosts*)
117 (unless foundp (error "Host ~S not found" name))
118 plist))
119
120(export 'hostprop)
121(defun hostprop (name prop &optional default)
122 (multiple-value-bind (found-name value tail)
123 (get-properties (host-plist name) (list prop))
124 (declare (ignore found-name))
125 (if tail
126 (values value t)
127 (values default nil))))
128(defun (setf hostprop) (value name prop &optional default)
129 (declare (ignore default))
130 (let ((plist (host-plist name)))
131 (multiple-value-bind (found-name found-value tail)
132 (get-properties plist (list prop))
133 (declare (ignore found-name found-value))
134 (if tail
135 (setf (cadr tail) value)
136 (setf (gethash name *hosts*)
137 (cons prop (cons value plist))))
138 value)))
139
140(export 'hostprop-default)
141(defun hostprop-default (host prop value)
142 (multiple-value-bind (found-value foundp) (hostprop host prop)
143 (declare (ignore found-value))
144 (unless foundp (setf (hostprop host prop) value))))
145
146(export 'hostpropp)
147(defun hostpropp (host prop)
148 (multiple-value-bind (value foundp) (hostprop host prop)
149 (declare (ignore value))
150 foundp))
151
152(export 'maphosts)
153(defun maphosts (func)
154 (maphash (lambda (name plist)
155 (declare (ignore plist))
156 (funcall func name))
157 *hosts*))
158
159(export 'dohosts)
160(defmacro dohosts ((hostvar &optional valueform) &body body)
161 `(block nil (maphosts (lambda (,hostvar) ,@body)) ,valueform))
162
163;;;--------------------------------------------------------------------------
164;;; Group definitions.
165
166(defvar *groups* nil)
167
168(export 'add-group)
169(defun add-group (name type func)
170 (let* ((found (assoc name *groups*))
171 (list (ecase type
172 (:predicate
173 (let ((list nil))
174 (dohosts (host list)
175 (when (funcall func host) (push host list)))))
176 (:list
177 (funcall func)))))
178 (if found
179 (setf (cdr found) list)
180 (push (cons name list) *groups*))))
181
182(export 'defgroup)
183(defmacro defgroup (name type args &body body)
184 `(progn (add-group ',name ,type
185 ,(ecase type
186 (:predicate
187 (destructuring-bind (hostvar) args
188 `(lambda (,hostvar) ,@body)))
189 (:list
190 (destructuring-bind () args
191 `(lambda () ,@body)))))
192 ',name))
193
194;;;--------------------------------------------------------------------------
195;;; Post-processing hooks.
196
197(defstruct hook-entry
198 prio
199 func)
200
201(export '(prio-props prio-groups))
202(defconstant prio-props 10)
203(defconstant prio-groups 20)
204
205(defvar *hostproc-hooks* nil)
206
207(export 'addhook)
208(defun addhook (prio func)
209 (push (make-hook-entry :prio prio :func func) *hostproc-hooks*))
210
211(export 'defhook)
212(defmacro defhook ((prio) &body body)
213 `(addhook ,prio (lambda () ,@body)))
214
215;;;--------------------------------------------------------------------------
216;;; Read the input file and hook definitions.
217
218(defvar *progname*
219 #.(or *compile-file-pathname* *load-pathname*))
220
4ee5e366
MW
221(defvar *user-package*
222 (make-package "ANSIBLE-INVENTORY-USER"
223 :use '("CL" "ANSIBLE-INVENTORY")))
224
225(defun load-input (file)
226 (let ((*package* *user-package*))
227 (load file :verbose nil)))
228
229(defun parse-command-line (args)
3f6c5992
MW
230 (loop (let* ((arg (pop args))
231 (len (length arg)))
232 (cond ((string= arg "--") (return))
233 ((string= arg "-") (push arg args) (return))
234 ((and (plusp (length arg))
235 (char= (char arg 0) #\-))
236 (do ((i 1 (1+ i)))
237 ((>= i len))
238 (flet ((getarg ()
239 (cond ((< (1+ i) len)
240 (prog1 (subseq arg (1+ i))
241 (setf i len)))
242 (args
243 (pop args))
244 (t
d7003518
MW
245 (error "missing argument")))))
246 (case (char arg i)
247 (#\h (format t "usage: ~A FILE ...~%"
248 (pathname-name *progname*)))
249 (t (error "unknown option `-~A'" (char arg i)))))))
3f6c5992 250 (t (push arg args) (return)))))
4ee5e366 251 (mapc #'load-input args))
3f6c5992
MW
252
253;;;--------------------------------------------------------------------------
254;;; Run the hooks.
255
4ee5e366
MW
256(defun run-hooks ()
257 (setf *hostproc-hooks* (sort *hostproc-hooks* #'< :key #'hook-entry-prio))
258 (mapc (compose #'hook-entry-func #'funcall) *hostproc-hooks*))
3f6c5992
MW
259
260;;;--------------------------------------------------------------------------
261;;; Output the definitions.
262
4ee5e366
MW
263(defun output ()
264 (let ((*print-right-margin* 77))
265 (print-json
3f6c5992 266 (print-json-map
4ee5e366
MW
267 (print-json-mapping "all"
268 (print-json-list (maphosts #'print-json-simple)))
269 (dolist (assoc *groups*)
270 (print-json-mapping (car assoc)
271 (print-json-list
272 (mapc #'print-json-simple (cdr assoc)))))
273 (print-json-mapping "_meta"
3f6c5992 274 (print-json-map
4ee5e366
MW
275 (print-json-mapping "hostvars"
276 (print-json-map
277 (maphash (lambda (host plist)
278 (print-json-mapping host
279 (print-json-map
280 (do ((plist plist (cddr plist)))
281 ((endp plist))
282 (print-json-mapping (car plist)
283 (print-json-simple (cadr plist)))))))
284 *hosts*)))))))))
285
286;;;--------------------------------------------------------------------------
287;;; Main program.
288
289#+cl-launch
290(progn
291 (parse-command-line cl-launch:*arguments*)
292 (run-hooks)
293 (output))
3f6c5992
MW
294
295;;;----- That's all, folks --------------------------------------------------