root/trunk/bknr/datastore/src/utils/utils.lisp

Revision 4026, 17.1 kB (checked in by hans, 2 months ago)

resolve conflicts with alexandria

  • Property svn:eol-style set to native
  • Property svn:keywords set to author date id revision
Line 
1 (in-package :bknr.utils)
2
3 (defmacro define-constant (name value &optional doc)
4   "Macro for use in place of defconstant in order to  make SBCL compiler happy"
5   `(defconstant ,name (if (boundp ',name) (symbol-value ',name) ,value)
6                            ,@(when doc (list doc))))
7 ;;; date format
8
9 ;; Zeitzone fÃŒr Mail-Zeitstempel
10 (defparameter *mail-timezone* "+0100")
11
12 (defun format-date-time (&optional universal-time &key stream
13                          (show-year t) (show-month t)
14                          (show-date t) (show-time t) (show-weekday nil)
15                          (show-seconds t)
16                          vms-style mail-style xml-style js-style)
17   (or show-date show-time
18       (warn "format-date-time: show-date and show-time are nil, nothing printed"))
19   (multiple-value-bind (sec min hour day month year weekday)
20       (decode-universal-time (or universal-time (get-universal-time)))
21     (when (equal show-year :short)
22       (setq year (mod year 100)))
23     (when show-weekday
24       (setf weekday (nth weekday '("MON" "TUE" "WED" "THU" "FRI" "SA" "SO"))))
25     (let ((s (if stream stream (make-string-output-stream))))
26       (cond
27         (mail-style
28          (format s "~A, ~2,'0D ~A ~4D ~2,'0D:~2,'0D:~2,'0D ~A"
29                  (elt #("Mon" "Tue" "Wed" "Thu" "Fri" "Sat" "Sun") weekday)
30                  day (elt #("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec") (1- month)) year
31                  hour min sec *mail-timezone*))
32         (vms-style
33          (when show-date
34            (setf month (nth (- month 1) '("JAN" "FEB" "MAR" "APR" "MAY" "JUN" "JUL" "AUG" "SEP" "OCT" "NOV" "DEC")))
35            (format s "~2,' d-~a-~d" day month year))
36          (when (and show-date show-time)
37            (princ #\Space s))
38          (when show-time
39            (format s "~2,' d:~2,'0d:~2,'0d" hour min sec)))
40         (xml-style
41          (format s "~4,'0d~2,'0d~2,'0dT~2,'0d~2,'0d~2,'0d"
42                  year month day hour min sec))
43         (js-style
44          (format s "new Date(~D, ~D, ~D, ~D, ~D, ~D)"
45                  year (1- month) day hour min sec))
46         (t
47          (when show-weekday
48            (format s "~a " weekday))
49          (when show-date
50            (format s "~2,'0d." day)
51            (when show-month
52              (format s "~2,'0d." month))
53            (when show-year
54              (format s "~4,'0d" year))
55            (when (and show-date show-time)
56              (princ #\Space s)))
57          (when show-time
58            (format s "~2,'0d:~2,'0d" hour min))
59          (when (and show-seconds show-time)
60            (format s ":~2,'0d" sec))))
61       (unless stream
62         (get-output-stream-string s)))))
63
64 (defun format-time-interval (seconds)
65   (format nil "~d:~2,'0d" (floor (/ seconds 60)) (mod seconds 60)))
66
67 (defun format-duration (duration)
68   (cond
69     ((> duration (* 24 3600)) (format nil "~ad" (round (/ duration (* 24 3600)))))
70     ((> duration 3600)        (format nil "~dh" (round (/ duration 3600))))
71     ((> duration 60)          (format nil "~am" (round (/ duration 60))))
72     (t                        (format nil "~as" duration))))
73
74 (defun month-interval (month year)
75   "Returns two values, the first and last second of the given month"
76   (values
77    (encode-universal-time 0 0 0 1 month year)
78    (- (if (= 12 month)
79           (encode-universal-time 0 0 0 1 1 (+ 1 year))
80           (encode-universal-time 0 0 0 1 (+ 1 month) year))
81       1)))
82
83 (defun day-interval (day month year)
84   "Returns two values, the first and last second of the given day"
85   (values
86    (encode-universal-time 0 0 0 day month year)
87    (encode-universal-time 59 59 23 day month year)))
88
89 (defun year-interval (year)
90   (values
91    (encode-universal-time 0 0 0 1 1 year)
92    (encode-universal-time 59 59 23 31 12 year)))
93
94 (defun get-hourtime (time)
95   (multiple-value-bind (second minute hour)
96       (decode-universal-time time)
97     (declare (ignore second minute))
98     hour))
99
100 (defun get-daytime (time)
101   (multiple-value-bind (second minute hour date month year day)
102       (decode-universal-time time)
103     (declare (ignore second minute hour day))
104     (nth-value 0 (day-interval date month year))))
105
106 (defun get-monthtime (time)
107   (multiple-value-bind (second minute hour date month year day)
108       (decode-universal-time time)
109     (declare (ignore second minute hour date day))
110     (nth-value 0 (month-interval month year))))
111
112 (defun previous-day (count &key (start (get-universal-time)))
113   (- start (* count (* 24 3600))))
114
115 (defun next-day (count &key (start (get-universal-time)))
116   (+ start (* count (* 24 3600))))
117
118 (defun month-num-days (month year)
119   (multiple-value-bind (start end) (month-interval month year)
120     (nth-value 0 (round (/ (- end start) (* 24 3600))))))
121
122 (defun timetag ()
123   (multiple-value-bind (second minute hour date month year)
124       (decode-universal-time (get-universal-time) 0)
125     (format nil
126             "~d~2,'0d~2,'0dT~2,'0d~2,'0d~2,'0d"
127             year month date hour minute second)))
128
129 (defun daytag ()
130   (multiple-value-bind (second minute hour date month year)
131       (decode-universal-time (get-universal-time) 0)
132     (declare (ignore second minute hour))
133     (format nil
134             "~d~2,'0d~2,'0d"
135             year month date)))
136
137
138 ;;; local hostname
139
140 (defun hostname (&key (strip-domain t))
141   (let ((hostname
142          #+allegro (sys:getenv "HOST")
143          #+cmu (cdr (assoc :host ext:*environment-list*))
144          #+openmcl (ccl::getenv "HOST")
145          #+sbcl (sb-ext:posix-getenv "HOST")))
146     (unless hostname
147       (error "HOST environment variable not set, can't continue"))
148     (if strip-domain
149         (regex-replace "\\..*$" hostname "")
150         hostname)))
151
152
153 ;;; filesystem functions
154
155 (defun directory-empty-p (pathname)
156   (zerop (length (directory pathname))))
157
158 (defun subdir-p (subdir dir)
159   (let ((subdir (probe-file subdir))
160         (dir (probe-file dir)))
161     (when (and subdir dir)
162       (equal (subseq (pathname-directory subdir)
163                      0 (length (pathname-directory dir)))
164              (pathname-directory dir)))))
165
166 (defun move-file (file1 file2)
167   #+(or allegro openmcl)
168   (rename-file file1 file2)
169   #+cmu
170   (unix:unix-rename (namestring file1)
171                     (namestring file2))
172   #+sbcl
173   (sb-unix:unix-rename (namestring file1)
174                        (namestring file2)))
175
176 (defun make-temporary-pathname (&key (defaults nil) (name "tmp"))
177   (loop for file = (make-pathname :name (format nil "~A-~A-~A"
178                                                 name
179                                                 (get-universal-time)
180                                                 (random most-positive-fixnum))
181                                   :defaults defaults)
182         while (probe-file file)
183         finally (return file)))
184
185 (defmacro with-temporary-file ((var &rest args) &body body)
186   `(let ((,var (make-temporary-pathname ,@args)))
187      (unwind-protect
188           (progn ,@body)
189        (when (probe-file ,var)
190          (delete-file ,var)))))     
191
192 (defun parent-directory (pathname)
193   (make-pathname :directory (butlast (pathname-directory pathname))
194                  :defaults pathname))
195
196 ;;; list functions
197
198 (defun delete-first (obj list &key (test #'eql))
199   (if (funcall test (first list) obj)
200       (cdr list)
201       (do ((l list (cdr l))
202            (last nil l))
203           ((null l) list)
204         (when (funcall test (car l) obj)
205           (rplacd last (cdr l))
206           (return list)))))
207
208 (defun make-keyword-from-string (string)
209   (if (keywordp string)
210       string
211       (nth-value 0 (intern (string-upcase (regex-replace-all "\\s+" string "-")) 'keyword))))
212
213 (defun assoc-values (item alist &key (test #'equal))
214   (mapcan #'(lambda (x) (and (funcall test item (car x))
215                              (list (cdr x))))
216           alist))
217
218 (defun insert-at-index (idx l elt)
219   (cond ((= idx 0)
220          (cons elt l))
221         ((= idx (1- (length l)))
222          (append l (list elt)))
223         (t (append (subseq l 0 idx)
224                    (list elt)
225                    (subseq l idx)))))
226
227 (defun find-neighbourhood (elt list depth &key (test #'eql))
228   (loop for rest on list
229         with seen = list and i = 0
230         when (funcall test elt (car rest))
231         do (return (subseq seen 0 (+ 1 depth i)))
232         do (if (>= i depth) (setf seen (cdr seen)) (incf i))))
233        
234 (defun assoc-to-keywords (args)
235   (loop for (key . value) in args
236         nconc (list (make-keyword-from-string key) value)))
237
238 (defun group-by (list num)
239   (loop for group on list by #'(lambda (seq) (subseq seq num))
240         collect (subseq group 0 num)))
241
242 (defun group-on (list &key (test #'eql) (key #'identity) (include-key t))
243   (let ((hash (make-hash-table :test test))
244         keys)
245     (dolist (el list)
246       (let ((key (funcall key el)))
247         (unless (nth-value 1 (gethash key hash))
248           (push key keys))
249         (push el (gethash key hash))))   
250     (mapcar (lambda (key) (let ((keys (nreverse (gethash key hash))))
251                             (if include-key
252                                 (cons key keys)
253                                 keys)))
254             (nreverse keys))))
255
256 (defun count-multiple (objects &rest keys)
257   (let ((hash-tables (loop for i from 1 to (length keys)
258                            collect (make-hash-table :test #'equal)))
259         (sum 0))
260     (dolist (object objects)
261       (incf sum)
262       (loop for key in keys
263             for i from 0
264             do (incf-hash (funcall key object) (nth i hash-tables))))
265     (apply #'values sum hash-tables)))
266
267 #+no-alexandria
268 (defun rotate (list)
269   (when list
270     (append (cdr list) (list (car list)))))
271
272 (defun nrotate (list)
273   (when list
274     (let ((first (pop list)))
275       (rplacd (last list) (list first))
276       list)))
277
278 (defun genlist (from to)
279   (loop for i from from to to
280         collect i))
281
282 (defun shift-until (list num &key (test #'>=))
283   (do* ((l list (cdr l))
284         (smaller nil (cons i smaller))
285         (i (car l) (car l)))
286        ((funcall test i num)
287         (append l (nreverse smaller)))))
288
289 ;;; from norvig
290 (defun find-all (item sequence &rest keyword-args
291                  &key (test #'eql) test-not &allow-other-keys)
292   "Find all those elements of sequence that match item,
293   according to the keywords.  Doesn't alter sequence."
294   (if test-not
295       (apply #'remove item sequence
296              :test-not (complement test-not) keyword-args)
297       (apply #'remove item sequence
298              :test (complement test) keyword-args)))
299
300 ;;; hash table
301 (defun hash-to-list (hash &key (key #'cdr) (compare #'>) num)
302   (let ((results (sort (loop for key being the hash-key of hash using (hash-value val)
303                              collect (cons key val))
304                        compare :key key)))
305     (if num
306         (subseq results 0 num)
307         results)))
308
309 (defun hash-values (hash)
310   (loop for value being the hash-values of hash
311         collect value))
312
313 (defun hash-keys (hash)
314   (loop for key being the hash-keys of hash
315         collect key))
316
317 (defun incf-hash (key hash &optional (delta 1))
318   (if (gethash key hash)
319       (incf (gethash key hash) delta)
320       (setf (gethash key hash) delta)))
321
322
323 ;;; randomize
324
325 (defun randomize-list (l)
326   (let ((len (length l)))
327     (flet ((randomize (l)
328              (let ((x (random len))
329                    (mov (pop l)))
330                (insert-at-index x l mov))))
331       (dotimes (x len)
332         (setf l (randomize l)))))
333   l)
334
335 (defun random-elts (choices num)
336   (subseq (randomize-list choices) 0 num))
337
338 ;;; hashes
339 (defun hash-to-hex (vector)
340   (format nil "~{~2,'0X~}" (coerce vector 'list)))
341
342 (defun md5-string (input-string)
343   (apply #'concatenate 'string (mapcar #'(lambda (c)
344                                            (format nil "~2,'0X" c))
345                                        (coerce (#+cmu md5sum-sequence #+sbcl md5sum-string input-string) 'list))))
346
347 #+(or)
348 (defun md5-string (string)
349   (hash-to-hex (digest-string :md5 string)))
350
351 ;;; content-types
352
353 (defvar *image-type<->content-type* '((:jpg . "image/jpeg")
354                                       (:png . "image/png")
355                                       (:gif . "image/gif")))
356
357 (defun pathname-type-symbol (pathname)
358   (intern (string-upcase (pathname-type pathname)) 'keyword))
359
360 (defun image-content-type (type-symbol)
361   "Return the MIME type of the image - If the type-symbol is a string,
362 it is assumed that the string specifies the MIME type."
363   (if (keywordp type-symbol)
364       (cdr (find type-symbol *image-type<->content-type* :test #'equal :key #'car))
365       type-symbol))
366
367 (defun image-type-symbol (content-type)
368   (car (find content-type *image-type<->content-type* :test #'equal :key #'cdr)))
369
370 (defun pathname-content-type (pathname)
371   (image-content-type (pathname-type-symbol pathname)))
372
373 ;;; utf08
374 (defun convert-utf8-to-latin1 (string &key (ignore-errors t))
375   (declare (string string) (optimize (speed 3)))
376   (with-output-to-string (stream)
377     (let ((length (length string))
378           (index 0))
379       (declare (fixnum length index))
380       (loop
381        (unless (< index length) (return nil))
382            (let* ((char (char string index))
383                   (code (char-code char)))
384              (restart-case
385                  (handler-bind
386                      ((error #'(lambda (c)
387                                  (if ignore-errors
388                                      (invoke-restart 'ignore-byte)
389                                      (error c)))))
390                    (cond
391                      ((< code #x80) ; ASCII
392                       (write-char char stream)
393                       (incf index 1))
394                      ((< code #xC0)
395                      
396                       ;; We are in the middle of a multi-byte sequence!
397                       ;; This should never happen, so we raise an error.
398                       (error "Encountered illegal multi-byte sequence."))
399                      ((< code #xC4)
400                       ;; Two byte sequence in Latin-1 range
401                       (unless (< (1+ index) length)
402                         (error "Encountered incomplete two-byte sequence."))
403                       (let* ((char2 (char string (1+ index)))
404                              (code2 (char-code char2)))
405                         (unless (and (logbitp 7 code2) (not (logbitp 6 code2)))
406                           (error "Second byte in sequence is not a continuation."))
407                         (let* ((upper-bits (ldb (byte 2 0) code))
408                                (lower-bits (ldb (byte 6 0) code2))
409                                (new-code (dpb upper-bits (byte 2 6) lower-bits)))
410                           (write-char (code-char new-code) stream)))
411                       (incf index 2))
412                      ((>= code #xFE)
413                       ;; Ignore stray byte-order markers
414                       (incf index 1))
415                      (t
416                       (error (format nil "Multi-byte sequence ~d (~d) outside Latin-1 range."
417                                      code char)))))
418                (ignore-byte ()
419                  :report "Ignore byte"
420                  (incf index 1))
421                (ignore-n-bytes (n)
422                  :report "Ignore some bytes"
423                  :interactive (lambda () (format t "Enter a new value: ")
424                                       (multiple-value-list (eval (read))))
425                  (incf index n))
426                (write-another-char (b)
427                  :report "Write a new char"
428                  :interactive (lambda () (format t "Enter a new char: ")
429                                       (multiple-value-list (eval (read))))
430                  (write-char b stream)
431                  (incf index 1))
432                (write-char ()
433                  :report "Write byte to latin-1 string"
434                  (write-char char stream)
435                  (incf index 1))))))))
436
437 ;;; stirng functions
438 (defun find-matching-strings (regexp strings &key case-sensitive)
439   (let ((scanner (create-scanner regexp :case-insensitive-mode (not case-sensitive))))
440     (remove-if-not #'(lambda (str)
441                        (scan scanner str)) strings)))
442
443 ;;; stream functions
444 ;;; from macho (by Miles Egan)
445 (defun make-extendable-string ()
446   "Creates a resizable string."
447   (make-array 0 :fill-pointer t :adjustable t :element-type 'base-char))
448
449 (defun read-delimited (stream token)
450   "Reads stream up to delimiter."
451   (let ((string (make-extendable-string)))
452     (handler-case
453         (loop with tok-length = (length token)
454               with state = 0
455               initially (vector-push-extend (read-char stream) string) ;; skip first char
456               for c = (read-char stream)
457               while (< state tok-length)
458               do (let ((match (char= c (aref token state))))
459                    (cond
460                      ((and (> state 0) (not match))
461                       (unread-char c stream)
462                       (setf state 0))
463                      (t
464                       (if match (incf state))
465                       (vector-push-extend c string))))
466               finally (progn
467                         (file-position stream (- (file-position stream) tok-length))
468                         (adjust-array string (- (length string) tok-length) :fill-pointer t)
469                         (return (values string t))))
470       (end-of-file () (values (if (> (length string) 0) string nil)
471                               nil)))))
472
473 (defun read-file (stream)
474   "Reads entire contents of stream into a string."
475   (loop with result = (make-extendable-string)
476         for c = (read-char stream nil)
477         while c
478         do (vector-push-extend c result)
479         finally (return result)))
480
481 (defun remove-keys (keys args)
482   (loop for (name val) on args by #'cddr
483         unless (member name keys)
484         append (list name val)))
485
486 (defun eval-initargs (initargs)
487   (loop for (key value) on initargs by #'cddr
488         nconc (list key (eval value))))
489
490 #-allegro
491 (defun file-contents (pathname &key (element-type '(unsigned-byte 8)))
492   (with-open-file (s pathname :element-type element-type)
493     (let ((result
494            (make-array (file-length s) :element-type element-type)))
495       (read-sequence result s)
496       result)))
497
498 (defun class-subclasses (class)
499   "Return a list of the names of all subclasses of a given class."
500   (labels ((collect-subclasses (class)
501              (let ((subclasses
502                     #+allegro
503                      (aclmop:class-direct-subclasses class)
504                      #+cmu
505                      (pcl:class-direct-subclasses class)
506                      #+openmcl
507                      (openmcl-mop:class-direct-subclasses class)
508                      #+sbcl
509                      (sb-mop:class-direct-subclasses class)))
510                (apply #'append subclasses
511                       (mapcar #'collect-subclasses subclasses)))))
512     (mapcar #'class-name (remove-duplicates (collect-subclasses (if (symbolp class) (find-class class) class))))))
513
514 (defun scale-bytes (byte-count)
515   (cond
516     ((> byte-count (* 1024 1024 1024 1024))
517      (format nil "~3,1F TB" (/ byte-count (* 1024 1024 1024 1024))))
518     ((> byte-count (* 1024 1024 1024))
519      (format nil "~3,1F GB" (/ byte-count (* 1024 1024 1024))))
520     ((> byte-count (* 1024 1024))
521      (format nil "~3,1F MB" (/ byte-count (* 1024 1024))))
522     ((> byte-count 1024)
523      (format nil "~3,1F KB" (/ byte-count 1024)))
524     (t
525      (format nil "~A" byte-count))))
526
527 (defun subseq* (sequence start &optional end)
528   "Like SUBSEQ, but limit END to the length of SEQUENCE"
529   (subseq sequence start (when end
530                            (min end (length sequence)))))
Note: See TracBrowser for help on using the browser.