git.fiddlerwoaroof.com
Browse code

Cleanup

- Rewrote parts of the timesheet printer to use CLOS more effectively
- Renamed a condition to fix package locks
- Updated the binary to use SBCL's --compress-core option

fiddlerwoaroof authored on 27/02/2016 18:54:10
Showing 3 changed files
... ...
@@ -80,9 +80,9 @@
80 80
       (setf amount amnt unit unt)
81 81
       it)))
82 82
 
83
-(define-condition parse-error () ())
83
+(define-condition parsing-error (parse-error) ())
84 84
 
85
-(define-condition invalid-day-of-week (parse-error)
85
+(define-condition invalid-day-of-week (parsing-error)
86 86
   ((day-of-week :initarg :day-of-week :reader day-of-week))
87 87
   (:report (lambda (condition stream)
88 88
              (format stream "~s is not a valid day of the week"
89 89
Binary files a/precompiled/timesheet and b/precompiled/timesheet differ
... ...
@@ -9,6 +9,15 @@
9 9
 (defvar *default-time-sheet-file*)
10 10
 (defvar *rate*)
11 11
 
12
+(defclass status-calculator ()
13
+  ((rate :initarg :rate :accessor rate)
14
+   (total-hours :initform 0 :initarg :total-hour :accessor total-hours)
15
+   (client-totals :initarg :client-totals :accessor client-totals)))
16
+
17
+(defclass status-line ()
18
+  ((client :initarg :client :accessor client)
19
+   (duration :initarg :duration :accessor duration :initform 0)))
20
+
12 21
 (defclass parsed-entry ()
13 22
   ((date :initarg :date :accessor date)
14 23
    (client :initarg :client :accessor client)
... ...
@@ -34,20 +43,7 @@
34 43
                  :memo memo
35 44
                  :start-times start-times))
36 45
 
37
-(defmacro /. (&rest body)
38
-  (let ((args '())
39
-        forms)
40
-    (loop for (head . tail) on body
41
-          until (eql head '->)
42
-          do (push head args)
43
-          finally (setf args (nreverse args))
44
-          finally (setf forms tail))
45
-    `(macrolet
46
-       ((>< (&rest form)
47
-          (list* (cadr form) (car form) (cddr form))))
48
-       (lambda ,args ,@forms))))
49
-
50
-(define-condition parse-error ()
46
+(define-condition parsing-error ()
51 47
   ((leftovers :initarg :leftovers :accessor leftovers))
52 48
   (:report (lambda (condition stream)
53 49
                (format stream "Parse error: ~20s leftover" (leftovers condition)))))
... ...
@@ -58,7 +54,7 @@
58 54
       (read-sequence dest s)
59 55
       (multiple-value-bind (parsed leftovers) (smug:parse (timesheet.parser::.date-records) dest)
60 56
         (unless (string= leftovers "")
61
-          (cerror "Continue?" 'parse-error :leftovers leftovers))
57
+          (cerror "Continue?" 'parsing-error :leftovers leftovers))
62 58
         parsed))))
63 59
 
64 60
 (defun unroll-date (date-obj)
... ...
@@ -147,23 +143,21 @@
147 143
     (hash-table-alist results)))
148 144
 
149 145
 (defgeneric print-entries (entries)
150
-  (:method ((incompletes list))
151
-   (format t "~&~120,1,0,'-<~>~%Partial Entries:~%")
152
-   (loop for (client . entries) in (group-by-client incompletes)
153
-         do  (loop for entry in entries
154
-                   do (format t "~&~4<~>~a, ~a:~%~{~12<~>one starting at ~a~%~}"
155
-                              (client entry)
156
-                              (memo entry)
157
-                              (mapcar
158
-                                (alambda (local-time:format-timestring
159
-                                           nil it
160
-                                           :format '(:year #\/ (:month 2) #\/ (:day 2) #\Space
161
-                                                     (:hour 2) #\: (:min 2) #\: (:sec 2))))
162
-                                (start-times entry)))))))
163
-
164
-(defclass status-calculator ()
165
-  ((clients :initform (make-hash-table) :accessor clients)
166
-   (total-cost :initform 0 :accessor total-cost)))
146
+  (:method ((entries list))
147
+   (mapcar #'print-entries entries))
148
+  (:method ((entry partial-entry))
149
+   (format t "~&~4<~>~a, ~a:~%~{~12<~>one starting at ~a~%~}"
150
+           (client entry)
151
+           (memo entry)
152
+           (mapcar
153
+             (alambda (local-time:format-timestring
154
+                        nil it
155
+                        :format '(:year #\/ (:month 2) #\/ (:day 2) #\Space
156
+                                  (:hour 2) #\: (:min 2) #\: (:sec 2))))
157
+             (start-times entry))))
158
+  (:method ((it complete-entry))
159
+   (format t "~&~4a ~10<~:(~a~)~> ~7,2F hrs ~a~%"
160
+           (date it) (client it) (duration it) (memo it))))
167 161
 
168 162
 (defgeneric record-client (calc client hours)
169 163
   (:method ((calc status-calculator) client hours)
... ...
@@ -171,53 +165,74 @@
171 165
       (incf (gethash client (clients calc) 0)
172 166
             hours))))
173 167
 
174
-(defgeneric total-line (calc results)
175
-  (:method ((calc status-calculator) results)
176
-   (with-accessors ((total-cost total-cost)) calc
168
+(defgeneric update (calculator entry)
169
+  (:method ((calculator status-calculator) entry)
170
+   (incf (total-hours calculator) (duration entry)))
171
+  (:method ((calculator status-line) entry)
172
+   (incf (duration calculator) (duration entry))))
173
+
174
+(defun update-clients (clients-hash-table entry)
175
+  (with-accessors ((client client)) entry
176
+    (update (ensure-gethash client clients-hash-table
177
+                            (make-instance 'status-line :client client)) 
178
+            entry)))
179
+
180
+(defun calculate-results (results &optional (rate *rate*))
181
+  (let ((status-calculator
182
+          (make-instance 'status-calculator
183
+                         :rate rate
184
+                         :client-totals (make-hash-table :test 'equalp))))
185
+    (prog1 status-calculator
186
+      (loop for entry in results
187
+            do (update-clients (client-totals status-calculator) entry)
188
+            do (update status-calculator entry)))))
189
+
190
+(defgeneric total-line (calc rate)
191
+  (:method ((calc status-calculator) rate)
192
+   (with-accessors ((total-hours total-hours)) calc
177 193
      (format nil "~26<Total~>:~7,2F hours @ ~7,2F $/hr = $~7,2F"
178
-             (loop for result in results
179
-                   do (record-client calc (client result) (duration result))
180
-                   do (incf total-cost (* (duration result) *rate*))
181
-                   sum (duration result))
182
-             *rate*
183
-             total-cost))))
194
+             total-hours rate (* rate total-hours)))))
195
+
196
+(defgeneric calculate-cost (calc time)
197
+  (:method ((calc status-calculator) (status-line status-line))
198
+   (* (rate calc) (duration status-line))))
184 199
 
185 200
 (defun print-status (results)
186
-  (let ((status-calculator (make-instance 'status-calculator)))
187
-    (flet ((fix-assoc (alist)
188
-             (mapcar (destructuring-lambda ((client . time))
189
-                       (list client
190
-                             time
191
-                             *rate*
192
-                             (* time *rate*)))
193
-                     alist)))
194
-      (let ((total (total-line status-calculator results)))
195
-        (format t "~&~120,1,0,'-<~>~%~:{~:(~26<~a~>~):~7,2F hours @ ~7,2F $/hr = $~7,2F~%~}"
196
-          (stable-sort (fix-assoc (hash-table-alist (clients status-calculator)))
197
-                       #'string<
198
-                       :key (alambda (car it))))
199
-        (format t total)))))
201
+  (let* ((status-calculator (calculate-results results)))
202
+    (flet ((print-status-line (status-line)
203
+             (format t "~&~:(~26<~a~>~):~7,2F hours @ ~7,2F $/hr = $~7,2F~%"
204
+                     (client status-line)
205
+                     (duration status-line)
206
+                     (rate status-calculator)
207
+                     (calculate-cost status-calculator status-line))))
208
+      (format t "~&~120,1,0,'-<~>~%")
209
+      (loop with client-totals = (client-totals status-calculator)
210
+            for  client in (sort (hash-table-keys client-totals) #'string-lessp)
211
+            for  status-line = (gethash client client-totals)
212
+            do (print-status-line status-line))
213
+      (format t (total-line status-calculator *rate*)))))
200 214
 
201 215
 (defun pprint-results (results incompletes status)
202
-  (format t "~&~:{~4a ~10<~:(~a~)~> ~7,2F hrs ~a~%~}"
203
-          (mapcar (alambda (list (date it) (client it) (duration it) (memo it)))
204
-                  results))
216
+  (print-entries results)
205 217
 
206 218
   (when incompletes
219
+    (format t "~&~120,1,0,'-<~>~%Partial Entries:~%")
207 220
     (print-entries incompletes))
208 221
 
209 222
   (when status
210 223
     (print-status results)))
211 224
 
212
-(defun group-by-class (list)
213
-  (loop with completes = '()
214
-        with partials  = '()
215
-        with complete-class = (find-class 'complete-entry)
216
-        with partial-class = (find-class 'partial-entry)
217
-        for el in list
218
-        when (eq (class-of el) complete-class) do (push el completes)
219
-        when (eq (class-of el) partial-class)  do (push el partials)
220
-        finally (return (values completes partials))))
225
+(defun group-by-class (list &optional accum1 accum2)
226
+  (tagbody ; Let's do some TCO ...
227
+    start
228
+    (if (null list)
229
+      (return-from group-by-class (list accum1 accum2))
230
+      (destructuring-bind (head . tail) list
231
+        (etypecase head
232
+          (complete-entry (setf accum1 (cons head accum1))) ; Here we set the accumulators
233
+          (partial-entry (setf accum2 (cons head accum2)))) ;  to the appropriate values.
234
+        (setf list tail) ; Here we step towards the terminating condition
235
+        (go start))))) ; Recurse
221 236
 
222 237
 (defun pprint-log (args &key client reverse status help)
223 238
   (when help
... ...
@@ -234,7 +249,7 @@
234 249
 
235 250
     (let ((*default-time-sheet-file* (or (car args) *default-time-sheet-file*))
236 251
           (*print-pretty* t))
237
-      (multiple-value-bind (complete-ranges incomplete-ranges) (group-by-class (get-log *default-time-sheet-file*))
252
+      (destructuring-bind (complete-ranges incomplete-ranges) (group-by-class (get-log *default-time-sheet-file*))
238 253
         (let ((complete-results (sort-results complete-ranges))
239 254
               (incomplete-results (sort-results incomplete-ranges t)))
240 255
           (pprint-results complete-results incomplete-results status))))))