[go: up one dir, main page]

File: utils.lisp

package info (click to toggle)
s-utils 20070312-1
  • links: PTS
  • area: main
  • in suites: lenny
  • size: 84 kB
  • ctags: 23
  • sloc: lisp: 193; makefile: 88
file content (204 lines) | stat: -rw-r--r-- 8,387 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
;;;; -*- mode: lisp -*-
;;;;
;;;; $Id: base64.lisp,v 1.3 2005/02/07 17:45:41 scaekenberghe Exp $
;;;;
;;;; This is a collection of Common Lisp utilities
;;;;
;;;; Copyright (C) 2004-2005 Sven Van Caekenberghe, Beta Nine BVBA.
;;;;
;;;; You are granted the rights to distribute and use this software
;;;; as governed by the terms of the Lisp Lesser General Public License
;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.

(in-package :s-utils)

(export
 '(make-subdirectory
   pathname-parent
   copy-stream
   tokens
   format-universal-time
   format-duration
   format-iso-gmt-time
   parse-integer-safely
   +us-day-names+
   +us-month-names+
   +us-time-format+
   +us-date-format+
   +en-duration-unit-names+))

;; pathname/directory manipulation

(defun make-subdirectory (basedir subdir)
  "Give a pathname, basedir, of a directory, create a subdirectory with name subdir"
  (make-pathname :directory (append (pathname-directory basedir)
				    (if (listp subdir) subdir (list subdir)))))

(defun pathname-parent (pathname)
  "Given a pathname, return the parent pathname"
  (if (pathname-name pathname)
      (make-pathname :directory (pathname-directory pathname))
    (make-pathname :directory (butlast (pathname-directory pathname)))))

;; stream copying

(defun copy-stream (in out &optional (buffer (make-string 4096)) second-buffer (convertor #'identity))
  "Copy all data from input stream in to output stream out using buffer (and read/write-sequence)"
  ;; optionally use a second-buffer of a different type matching the destination stream
  ;; and use the convertor function to convert elements from buffer to second-buffer
  (if second-buffer
      (labels ((convert-buffer (limit)
                 (loop :for i :below limit 
                       :do (setf (elt second-buffer i) (funcall convertor (elt buffer i)))))
               (copy-chunks ()
                 (let ((size (read-sequence buffer in)))
                   (cond ((< size (length buffer))
                          (convert-buffer size)
                          (write-sequence second-buffer out :end size))
                         (t
                          (convert-buffer (length buffer))
                          (write-sequence second-buffer out)
                          (copy-chunks))))))
        (assert (= (length buffer) (length second-buffer)))
        (copy-chunks))
    (labels ((copy-chunks ()
               (let ((size (read-sequence buffer in)))
                 (cond ((< size (length buffer))
                        (write-sequence buffer out :end size))
                       (t
                        (write-sequence buffer out)
                        (copy-chunks))))))
      (copy-chunks))))

;; elementary parsing

(defun tokens (string &key (start 0) end (separators (list #\space #\return #\linefeed #\tab)))
  "Split string in a list of tokens using separators, a list of characters"
  (if (= start (length string))
      '()
    (let ((p (position-if #'(lambda (char) (find char separators :test #'char=)) 
                          string 
                          :start start :end end)))
      (if p
	  (if (= p start)
	      (tokens string :start (1+ start) :end end :separators separators)
	    (cons (subseq string start p)
		  (tokens string :start (1+ p) :end end :separators separators)))
	(list (subseq string start end))))))

;; time/date formatting

(defparameter +us-day-names+ 
  '("Mon" "Tue" "Wed" "Thu" "Fri" "Sat" "Sun")
  "US English short day name constant strings")

(defparameter +us-month-names+ 
  '("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")
  "US English short month name constant strings")

(defparameter +us-time-format+ 
  '(:day-name #\Space :month-name #\Space :date #\Space :year #\Space :hour #\: :minute #\: :second)
  "US English style date-time format")

(defparameter +us-date-format+ 
  '(:day-name #\Space :month-name #\Space :date #\Space :year)
  "US English style date-only format")

(defun format-universal-time (universal-time 
                              &key 
                              (format +us-time-format+) 
                              (day-names +us-day-names+)  
                              (month-names +us-month-names+)
                              decode-in-timezone
                              stream)
  "Format universal time using format, day-names and month-names, if stream is not nil use it, else return a string"
  (multiple-value-bind (second minute hour date month year day daylight-p timezone)
      (if decode-in-timezone 
          (decode-universal-time universal-time decode-in-timezone)
        (decode-universal-time universal-time))
    (declare (ignore daylight-p))
    (flet ((two-digit (n) (format nil "~2,'0d" n)))
      (let* ((month-name (elt month-names (1- month)))
             (day-name (elt day-names day))
             (bindings `((:second . ,(two-digit second)) 
                         (:minute . ,(two-digit minute)) 
                         (:hour . ,(two-digit hour)) 
                         (:date . ,date) 
                         (:month . ,month) 
                         (:year . ,year) 
                         (:day . ,day)
                         (:date2 . ,(two-digit date))
                         (:day-name . ,day-name) 
                         (:month-name . ,month-name)
                         (:timezone . ,(format nil "~c~2,'0d" (if (plusp timezone) #\+ #\-) (abs timezone)))))
             (out (or stream (make-string-output-stream))))
        (dolist (x format)
          (format out "~a" (if (keywordp x) (cdr (assoc x bindings)) x)))
        (unless stream
          (get-output-stream-string out))))))

;; duration formatting

(defparameter +en-duration-unit-names+
  #("year" "day" "hour" "minute" "second")
  "English time duration unit name constant strings")

(defun format-duration (seconds &key (unit-names +en-duration-unit-names+) stream)
  "Format seconds as duration using unit-names, if stream is not nil use it, else return a string"
  (let ((out (or stream (make-string-output-stream)))
        years days hours minutes did-wrote-output)
    (setf years (floor seconds (* 60 60 24 365)))
    (setf seconds (rem seconds (* 60 60 24 365)))
    (setf days (floor seconds (* 60 60 24)))
    (setf seconds (rem seconds (* 60 60 24)))
    (setf hours (floor seconds (* 60 60)))
    (setf seconds (rem seconds (* 60 60)))
    (setf minutes (floor seconds 60))
    (setf seconds (rem seconds 60))
    (flet ((fmt-unit (n unit)
             (unless (zerop n)
               (when did-wrote-output (write-char #\space out))
               (format out "~d ~a~p" n unit n)
               (setf did-wrote-output t))))
      (fmt-unit years (aref unit-names 0))
      (fmt-unit days (aref unit-names 1))
      (fmt-unit hours (aref unit-names 2))
      (fmt-unit minutes (aref unit-names 3))
      (fmt-unit seconds (aref unit-names 4)))
    (unless stream
      (get-output-stream-string out))))

;; simplified ISO date/time formatting

(defun format-iso-gmt-time (universal-time &key stream)
  "Format universal time using a simple and fast 'ISO GMT' style, if stream is not nil use it, else return a string"
  (let ((out (or stream (make-string-output-stream))))
    (multiple-value-bind (second minute hour date month year)
        (decode-universal-time universal-time 0)
      (flet ((two-digit (n s) (if (< n 10) 
                                  (progn (write-char #\0 s) (write n :stream s)) 
                                (write n :stream s))))
        (write year :stream out)
        (two-digit month out)
        (two-digit date out)
        (write-char #\T out)
        (two-digit hour out)
        (two-digit minute out)
        (two-digit second out)))
    (unless stream
      (get-output-stream-string out))))
    
;; extended integer parsing

(defun parse-integer-safely (string &key (start 0) end (radix 10) default)
  "Like parse-integer, but will return default on error, accepts nil as argument"
  (if (and (stringp string) 
           (not (zerop (- (length string) start))))
      (multiple-value-bind (value terminating-position)
          (parse-integer string :start start :end end :radix radix :junk-allowed t)
        (if (= terminating-position (or end (length string)))
            value
          default))
    default))

;;;; eof