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