Add missing files to binary tarball
[phoros.git] / util.lisp
blob70a5a12c9f8598106cfb7fd7028ba690f48bd6b9
1 ;;; PHOROS -- Photogrammetric Road Survey
2 ;;; Copyright (C) 2011, 2012 Bert Burgemeister
3 ;;;
4 ;;; This program is free software; you can redistribute it and/or modify
5 ;;; it under the terms of the GNU General Public License as published by
6 ;;; the Free Software Foundation; either version 2 of the License, or
7 ;;; (at your option) any later version.
8 ;;;
9 ;;; This program is distributed in the hope that it will be useful,
10 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 ;;; GNU General Public License for more details.
13 ;;;
14 ;;; You should have received a copy of the GNU General Public License along
15 ;;; with this program; if not, write to the Free Software Foundation, Inc.,
16 ;;; 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 (in-package :phoros)
21 (defun unqualified-symbol (symbol)
22 (cond ((keywordp symbol) symbol)
23 ((atom symbol) (intern (string symbol)))
24 (t symbol)))
26 (defmacro defun* (name lambda-list &body body)
27 "Like defun, define a function, but with an additional lambda list
28 keyword &mandatory-key which goes after the &key section or in place
29 of it. &mandatory-key argument definitions are plain symbols (no
30 lists). An error is signalled on function calls where one of those
31 keyargs is missing."
32 (let ((mandatory-key-position (position '&mandatory-key lambda-list))
33 (after-key-position (or (position '&allow-other-keys lambda-list)
34 (position '&aux lambda-list))))
35 (when mandatory-key-position
36 (setf lambda-list
37 (append (subseq lambda-list 0 mandatory-key-position)
38 (unless (position '&key lambda-list)
39 '(&key))
40 (mapcar
41 #'(lambda (k)
42 `(,k (error ,(format nil "~A: argument ~A undefined"
43 name k))))
44 (subseq lambda-list
45 (1+ mandatory-key-position)
46 after-key-position))
47 (when after-key-position
48 (subseq lambda-list after-key-position))))))
49 `(defun ,name ,(delete (unqualified-symbol '&mandatory-key) lambda-list)
50 ,@body))
52 (defmacro logged-query (message-tag &rest args)
53 "Act like postmodern:query; additionally log some debug information
54 tagged by the short string message-tag."
55 (cl-utilities:with-unique-names
56 (executed-query query-milliseconds query-result)
57 `(let* (,executed-query
58 ,query-milliseconds
59 ,query-result
60 (cl-postgres:*query-callback*
61 #'(lambda (query-string clock-ticks)
62 (setf ,query-milliseconds clock-ticks)
63 (setf ,executed-query query-string))))
64 (prog1
65 (setf ,query-result
66 (etypecase (car ',args)
67 (list
68 (typecase (caar ',args)
69 (keyword ;s-sql form
70 (query (sql-compile ',(car args))
71 ,@(cdr args)))
72 (t ;function (supposedly) returning string
73 (query ,@args))))
74 (string
75 (query ,@args))
76 (symbol
77 (query ,@args))))
78 (cl-log:log-message
79 :sql
80 "[~A] Query ~S~& took ~F seconds and yielded~& ~A."
81 ,message-tag
82 ,executed-query
83 (/ ,query-milliseconds 1000)
84 ,query-result)))))
87 (in-package :cli)
89 (defmacro with-options ((&key log database aux-database tolerate-missing)
90 (&rest options)
91 &body body
92 &aux postgresql-credentials)
93 "Evaluate body with options bound to the values of the respective
94 command line arguments. Signal error if tolerate-missing is nil and a
95 command line argument doesn't have a value. Elements of options may
96 be symbols named according to the :long-name argument of the option,
97 or lists shaped like (symbol) which bind symbol to a list of values
98 collected for multiple occurence of that option. If log is t, start
99 logging first. If database or aux-database are t, evaluate body with
100 the appropriate database connection(s) and bind the following
101 additional variables to the values of the respective command line
102 arguments: host, port, database, user, password, use-ssl; and/or
103 aux-host, aux-port, aux-database, aux-user, aux-password,
104 aux-use-ssl."
105 (assert (not (and database aux-database)) ()
106 "Can't handle connection to both database and aux-database ~
107 at the same time.")
108 (when database
109 (setf options
110 (append options '(host port database user password use-ssl)))
111 (setf postgresql-credentials
112 `(list ,(unqualified-symbol 'database)
113 ,(unqualified-symbol 'user)
114 ,(unqualified-symbol 'password)
115 ,(unqualified-symbol 'host)
116 :port ,(unqualified-symbol 'port)
117 :use-ssl (s-sql:from-sql-name
118 ,(unqualified-symbol 'use-ssl)))))
119 (when aux-database
120 (setf options
121 (append options '(aux-host aux-port aux-database
122 aux-user aux-password aux-use-ssl)))
123 (setf postgresql-credentials
124 `(list ,(unqualified-symbol 'aux-database)
125 ,(unqualified-symbol 'aux-user)
126 ,(unqualified-symbol ' aux-password)
127 ,(unqualified-symbol 'aux-host)
128 :port ,(unqualified-symbol 'aux-port)
129 :use-ssl (s-sql:from-sql-name
130 ,(unqualified-symbol 'aux-use-ssl)))))
131 (when log (setf options (append options '(log-dir))))
132 (setf options (mapcar #'unqualified-symbol options))
133 (let* ((db-connected-body
134 (if (or database aux-database)
135 `((with-connection ,postgresql-credentials
136 (muffle-postgresql-warnings)
137 ,@body))
138 body))
139 (logged-body
140 (if log
141 `((launch-logger ,(unqualified-symbol 'log-dir))
142 ,@db-connected-body)
143 db-connected-body)))
144 `(with-context (make-context)
145 (let (,@(loop
146 for option in (remove-duplicates options)
147 if (symbolp option) collect
148 (list option
149 (if tolerate-missing
150 `(getopt :long-name ,(string-downcase option))
151 `(getopt-mandatory ,(string-downcase option))))
152 else collect
153 `(,(car option)
154 (loop
155 for i = ,(if tolerate-missing
156 `(getopt :long-name ,(string-downcase
157 (car option)))
158 `(getopt-mandatory ,(string-downcase
159 (car option))))
160 then (getopt :long-name ,(string-downcase
161 (car option)))
162 while i collect i))))
163 ,@logged-body))))