1 #! /usr/bin/cl-launch -X --
4 (cl:defpackage #:ansible-inventory
7 (in-package #:ansible-inventory)
9 (declaim (optimize debug))
11 ;;;--------------------------------------------------------------------------
12 ;;; General utilities.
14 (defun compose (&rest funcs)
16 (lambda (&rest args) (multiple-value-call g (apply f args))))
17 funcs :initial-value #'values))
19 ;;;--------------------------------------------------------------------------
22 (defvar *print-json-comma*)
24 (defun print-json-itemstart ()
25 (cond (*print-json-comma*
27 (pprint-newline :linear))
29 (setf *print-json-comma* t))))
31 (defun print-json-map* (thunk)
32 (print-json-itemstart)
34 (pprint-indent :block 2)
35 (pprint-newline :linear)
36 (pprint-logical-block (*standard-output* nil)
37 (let ((*print-json-comma* nil)) (funcall thunk)))
39 (pprint-indent :block 0)
40 (pprint-newline :linear)
43 (defmacro print-json-map (&body body)
44 `(print-json-map* (lambda () ,@body)))
46 (defun print-json-list* (thunk)
47 (print-json-itemstart)
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)
57 (defmacro print-json-list (&body body)
58 `(print-json-list* (lambda () ,@body)))
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 (if (not (keywordp 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 #\-) #\_
74 (pprint-newline :miser)
75 (let ((*print-json-comma* nil))
78 (defmacro print-json-mapping (label &body body)
79 `(print-json-mapping* ,label (lambda () ,@body)))
81 (defun print-json-simple (value)
83 ((and vector (not string))
85 (dotimes (i (length value)) (print-json-simple (aref value i)))))
91 (print-json-mapping (car i)
92 (print-json-simple (cdr i))))))
95 (maphash (lambda (k v)
97 (print-json-simple v)))
100 (print-json-itemstart)
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)))))))
111 (defun print-json* (thunk)
112 (let ((*print-json-comma* nil))
113 (pprint-logical-block (*standard-output* nil)
117 (defmacro print-json (&body body)
118 `(print-json* (lambda () ,@body)))
120 ;;;--------------------------------------------------------------------------
121 ;;; Host definitions.
123 (defvar *hosts* (make-hash-table))
126 (defun addhost (name alist)
127 (setf (gethash name *hosts*) alist))
130 (defmacro defhost (name &body alist)
131 `(progn (addhost ',name ',alist) ',name))
134 (defun host-plist (name)
135 (multiple-value-bind (plist foundp) (gethash name *hosts*)
136 (unless foundp (error "Host ~S not found" name))
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))
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))
154 (setf (cadr tail) value)
155 (setf (gethash name *hosts*)
156 (cons prop (cons value plist))))
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))))
166 (defun hostpropp (host prop)
167 (multiple-value-bind (value foundp) (hostprop host prop)
168 (declare (ignore value))
172 (defun maphosts (func)
173 (maphash (lambda (name plist)
174 (declare (ignore plist))
179 (defmacro dohosts ((hostvar &optional valueform) &body body)
180 `(block nil (maphosts (lambda (,hostvar) ,@body)) ,valueform))
182 ;;;--------------------------------------------------------------------------
183 ;;; Group definitions.
185 (defvar *groups* nil)
188 (defun add-group (name type func)
189 (let* ((found (assoc name *groups*))
194 (when (funcall func host) (push host list)))))
198 (setf (cdr found) list)
199 (push (cons name list) *groups*))))
202 (defmacro defgroup (name type args &body body)
203 `(progn (add-group ',name ,type
206 (destructuring-bind (hostvar) args
207 `(lambda (,hostvar) ,@body)))
209 (destructuring-bind () args
210 `(lambda () ,@body)))))
213 ;;;--------------------------------------------------------------------------
214 ;;; Post-processing hooks.
216 (defstruct hook-entry
220 (export '(prio-props prio-groups))
221 (defconstant prio-props 10)
222 (defconstant prio-groups 20)
224 (defvar *hostproc-hooks* nil)
227 (defun addhook (prio func)
228 (push (make-hook-entry :prio prio :func func) *hostproc-hooks*))
231 (defmacro defhook ((prio) &body body)
232 `(addhook ,prio (lambda () ,@body)))
234 ;;;--------------------------------------------------------------------------
235 ;;; Read the input file and hook definitions.
238 #.(or *compile-file-pathname* *load-pathname*))
240 (defvar *user-package*
241 (make-package "ANSIBLE-INVENTORY-USER"
242 :use '("CL" "ANSIBLE-INVENTORY")))
244 (defun load-input (file)
245 (let ((*package* *user-package*))
246 (load file :verbose nil)))
248 (defun parse-command-line (args)
249 (loop (let* ((arg (pop args))
251 (cond ((string= arg "--") (return))
252 ((string= arg "-") (push arg args) (return))
253 ((and (plusp (length arg))
254 (char= (char arg 0) #\-))
258 (cond ((< (1+ i) len)
259 (prog1 (subseq arg (1+ i))
264 (error "missing argument")))))
266 (#\h (format t "usage: ~A FILE ...~%"
267 (pathname-name *progname*)))
268 (t (error "unknown option `-~A'" (char arg i)))))))
269 (t (push arg args) (return)))))
270 (mapc #'load-input args))
272 ;;;--------------------------------------------------------------------------
276 (setf *hostproc-hooks* (sort *hostproc-hooks* #'< :key #'hook-entry-prio))
277 (mapc (compose #'hook-entry-func #'funcall) *hostproc-hooks*))
279 ;;;--------------------------------------------------------------------------
280 ;;; Output the definitions.
283 (let ((*print-right-margin* 77))
286 (print-json-mapping "all"
287 (print-json-list (maphosts #'print-json-simple)))
288 (dolist (assoc *groups*)
289 (print-json-mapping (car assoc)
291 (mapc #'print-json-simple (cdr assoc)))))
292 (print-json-mapping "_meta"
294 (print-json-mapping "hostvars"
296 (maphash (lambda (host plist)
297 (print-json-mapping host
299 (do ((plist plist (cddr plist)))
301 (print-json-mapping (car plist)
302 (print-json-simple (cadr plist)))))))
305 ;;;--------------------------------------------------------------------------
310 (parse-command-line cl-launch:*arguments*)
314 ;;;----- That's all, folks --------------------------------------------------