Browse code
Cleanup, document, etc.
fiddlerwoaroof authored on 21/02/2016 04:12:41
Showing 5 changed files
Showing 5 changed files
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,74 @@ |
1 |
+A little utility to manage a simple time-sheet file. |
|
2 |
+ |
|
3 |
+The file-format is this: |
|
4 |
+ |
|
5 |
+``` |
|
6 |
+-- Someday YYYY-MM-DD |
|
7 |
+ start@HH:MM[:SS][+-NN(mins|hrs)][,HH:MM[:SS][+-NN(mins|hrs)]] |
|
8 |
+ Client Name: This is a memo. |
|
9 |
+``` |
|
10 |
+ |
|
11 |
+Write now the parser is fairly fragile: it reds what it can and fails silently at the first |
|
12 |
+error. Eventually there'll be better error-handling. |
|
13 |
+ |
|
14 |
+``` |
|
15 |
+% ./timesheet -h |
|
16 |
+timesheet, common-lisp version 0:1 |
|
17 |
+ -c --client boolean Sort by client |
|
18 |
+ -r --reverse boolean Reverse sort |
|
19 |
+ -s --status boolean Print a summary of the hours worked and the prices |
|
20 |
+ -h --help boolean show help |
|
21 |
+``` |
|
22 |
+ |
|
23 |
+``` |
|
24 |
+% ./timesheet -r sample-inputs/test.ts |
|
25 |
+Thu, 2016/01/04 Client #2 5.00 hrs Implement prototype and write presentation |
|
26 |
+Thu, 2016/01/04 Client #1 9.00 hrs Implement Facebook Connector |
|
27 |
+Wed, 2016/01/03 Client #2 2.50 hrs Discussed user requirements and produce specification. |
|
28 |
+Wed, 2016/01/03 Client #1 6.00 hrs Delivered prototype, reviewed prototype feedback. |
|
29 |
+Tue, 2016/01/02 Client #1 8.00 hrs Prototype for testing user experience. |
|
30 |
+Mon, 2016/01/01 Client #1 8.00 hrs Mockup of site layout |
|
31 |
+``` |
|
32 |
+ |
|
33 |
+``` |
|
34 |
+% ./timesheet -sc sample-inputs/test.ts |
|
35 |
+Mon, 2016/01/01 Client #1 8.00 hrs Mockup of site layout |
|
36 |
+Tue, 2016/01/02 Client #1 8.00 hrs Prototype for testing user experience. |
|
37 |
+Wed, 2016/01/03 Client #1 6.00 hrs Delivered prototype, reviewed prototype feedback. |
|
38 |
+Thu, 2016/01/04 Client #1 9.00 hrs Implement Facebook Connector |
|
39 |
+Wed, 2016/01/03 Client #2 2.50 hrs Discussed user requirements and produce specification. |
|
40 |
+Thu, 2016/01/04 Client #2 5.00 hrs Implement prototype and write presentation |
|
41 |
+------------------------------------------------------------------------------------------------------------------------ |
|
42 |
+ Client #1: 31.00 hours @ 40.00 $/hr = $1240.00 |
|
43 |
+ Client #2: 7.50 hours @ 40.00 $/hr = $ 300.00 |
|
44 |
+ Total: 38.50 hours @ 40.00 $/hr = $1540.00 |
|
45 |
+``` |
|
46 |
+ |
|
47 |
+``` |
|
48 |
+% ./timesheet -sr sample-inputs/test.ts |
|
49 |
+Thu, 2016/01/04 Client #2 5.00 hrs Implement prototype and write presentation |
|
50 |
+Thu, 2016/01/04 Client #1 9.00 hrs Implement Facebook Connector |
|
51 |
+Wed, 2016/01/03 Client #2 2.50 hrs Discussed user requirements and produce specification. |
|
52 |
+Wed, 2016/01/03 Client #1 6.00 hrs Delivered prototype, reviewed prototype feedback. |
|
53 |
+Tue, 2016/01/02 Client #1 8.00 hrs Prototype for testing user experience. |
|
54 |
+Mon, 2016/01/01 Client #1 8.00 hrs Mockup of site layout |
|
55 |
+------------------------------------------------------------------------------------------------------------------------ |
|
56 |
+ Client #1: 31.00 hours @ 40.00 $/hr = $1240.00 |
|
57 |
+ Client #2: 7.50 hours @ 40.00 $/hr = $ 300.00 |
|
58 |
+ Total: 38.50 hours @ 40.00 $/hr = $1540.00 |
|
59 |
+``` |
|
60 |
+ |
|
61 |
+``` |
|
62 |
+% ./timesheet -scr sample-inputs/test.ts |
|
63 |
+Thu, 2016/01/04 Client #2 5.00 hrs Implement prototype and write presentation |
|
64 |
+Wed, 2016/01/03 Client #2 2.50 hrs Discussed user requirements and produce specification. |
|
65 |
+Thu, 2016/01/04 Client #1 9.00 hrs Implement Facebook Connector |
|
66 |
+Wed, 2016/01/03 Client #1 6.00 hrs Delivered prototype, reviewed prototype feedback. |
|
67 |
+Tue, 2016/01/02 Client #1 8.00 hrs Prototype for testing user experience. |
|
68 |
+Mon, 2016/01/01 Client #1 8.00 hrs Mockup of site layout |
|
69 |
+------------------------------------------------------------------------------------------------------------------------ |
|
70 |
+ Client #1: 31.00 hours @ 40.00 $/hr = $1240.00 |
|
71 |
+ Client #2: 7.50 hours @ 40.00 $/hr = $ 300.00 |
|
72 |
+ Total: 38.50 hours @ 40.00 $/hr = $1540.00 |
|
73 |
+``` |
|
74 |
+ |
2 | 77 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,24 @@ |
1 |
+-- Monday 2016-01-01 |
|
2 |
+ start@08:00--16:00 |
|
3 |
+ Client #1: Mockup of site layout |
|
4 |
+ |
|
5 |
+-- Tuesday 2016-01-02 |
|
6 |
+ start@08:00--16:00 |
|
7 |
+ Client #1: Prototype for testing user experience. |
|
8 |
+ |
|
9 |
+-- Wednesday 2016-01-03 |
|
10 |
+ start@08:00--12:00,15:30--17:00+30min |
|
11 |
+ Client #1: Delivered prototype, reviewed prototype feedback. |
|
12 |
+ start@12:30--15:00 |
|
13 |
+ Client #2: Discussed user requirements and produce specification. |
|
14 |
+ |
|
15 |
+-- Thursday 2016-01-04 |
|
16 |
+ start@08:00--16:54 |
|
17 |
+ Client #1: Implement Facebook Connector |
|
18 |
+ start@17:00--21:54 |
|
19 |
+ Client #2: Implement prototype and write presentation |
|
20 |
+ |
|
21 |
+-- Friday 2016-01-05 |
|
22 |
+ start@11:00--16:54 |
|
23 |
+ Client #1: Implement Twitter Connector |
|
24 |
+ |
... | ... |
@@ -10,7 +10,7 @@ |
10 | 10 |
(defvar *rate*) |
11 | 11 |
|
12 | 12 |
(defun parse-file (&optional (file *default-time-sheet-file*)) |
13 |
- (with-open-file (s *default-time-sheet-file* :direction :input) |
|
13 |
+ (with-open-file (s file :direction :input) |
|
14 | 14 |
(let ((dest (make-string (file-length s)))) |
15 | 15 |
(read-sequence dest s) |
16 | 16 |
(caar (smug:run (timesheet.parser::.date-records) dest))))) |
... | ... |
@@ -19,28 +19,28 @@ |
19 | 19 |
(with-slots (year month day) date-obj |
20 | 20 |
(list day month year))) |
21 | 21 |
|
22 |
+(defun combine-date-time (time-obj day month year) |
|
23 |
+ (with-slots (second minute hour) time-obj |
|
24 |
+ (local-time:encode-timestamp 0 second minute hour |
|
25 |
+ day month year))) |
|
26 |
+ |
|
22 | 27 |
(defun calculate-ranges (ranges year month day) |
23 |
- (loop for (start-obj end-obj mod) in ranges |
|
24 |
- for start = (local-time:encode-timestamp 0 |
|
25 |
- (slot-value start-obj 'second) |
|
26 |
- (slot-value start-obj 'minute) |
|
27 |
- (slot-value start-obj 'hour) |
|
28 |
- day month year) |
|
29 |
- for end = (local-time:encode-timestamp 0 |
|
30 |
- (slot-value end-obj 'second) |
|
31 |
- (slot-value end-obj 'minute) |
|
32 |
- (slot-value end-obj 'hour) |
|
33 |
- day month year ) |
|
34 |
- for time-mod = (when time-mod |
|
35 |
- (let ((unit (make-keyword |
|
36 |
- (string-upcase |
|
37 |
- (if (string= (slot-value time-mod 'timesheet.parser::unit) "mins") |
|
38 |
- "minute" |
|
39 |
- "hour")))) |
|
40 |
- (amount (slot-value time-mod 'timesheet.parser:amount))) |
|
41 |
- (funcall #'local-time-duration:duration unit amount))) |
|
42 |
- nconc (list (local-time-duration:timestamp-difference end start) |
|
43 |
- (or time-mod (local-time-duration:duration))))) |
|
28 |
+ (flet ((time-mod-unit-keyword (time-mod) |
|
29 |
+ (make-keyword |
|
30 |
+ (string-upcase |
|
31 |
+ (if (string= (slot-value time-mod 'unit) "mins") |
|
32 |
+ "minute" |
|
33 |
+ "hour"))))) |
|
34 |
+ (loop for (start-obj end-obj mod) in ranges |
|
35 |
+ for start = (combine-date-time start-obj year month day) |
|
36 |
+ for end = (combine-date-time end-obj year month day) |
|
37 |
+ for time-mod = (when mod |
|
38 |
+ (let ((unit (time-mod-unit-keyword mod)) |
|
39 |
+ (amount (slot-value mod 'timesheet.parser:amount))) |
|
40 |
+ (funcall #'local-time-duration:duration unit amount))) |
|
41 |
+ nconc (list |
|
42 |
+ (local-time-duration:timestamp-difference end start) |
|
43 |
+ (or time-mod (local-time-duration:duration)))))) |
|
44 | 44 |
|
45 | 45 |
(defun calculate-rounded-ranges (ranges) |
46 | 46 |
(flet ((calc-duration-in-15mins (duration) |
... | ... |
@@ -62,13 +62,15 @@ |
62 | 62 |
`(,date |
63 | 63 |
,client |
64 | 64 |
,(calculate-rounded-ranges |
65 |
- (calculate-ranges ranges year month day)) |
|
65 |
+ (calculate-ranges ranges day month year)) |
|
66 | 66 |
,memo)))))))) |
67 | 67 |
|
68 | 68 |
(defparameter +pprint-log-option-spec+ |
69 |
- '((("client" #\c) :type boolean :optional t :documentation "sort by client") |
|
70 |
- (("reverse" #\r) :type boolean :optional t :documentation "reverse") |
|
71 |
- (("status" #\s) :type boolean :optional t :documentation "status"))) |
|
69 |
+ '((("client" #\c) :type boolean :optional t :documentation "Sort by client") |
|
70 |
+ (("reverse" #\r) :type boolean :optional t :documentation "Reverse sort") |
|
71 |
+ (("status" #\s) :type boolean :optional t |
|
72 |
+ :documentation "Print a summary of the hours worked and the prices") |
|
73 |
+ (("help" #\h) :type boolean :optional t :documentation "show help"))) |
|
72 | 74 |
|
73 | 75 |
(defparameter *version* "0:1") |
74 | 76 |
(defun show-version () |
... | ... |
@@ -78,32 +80,25 @@ |
78 | 80 |
(show-version) |
79 | 81 |
(command-line-arguments:show-option-help +pprint-log-option-spec+ :sort-names t)) |
80 | 82 |
|
81 |
-(defun pprint-log (args &key client reverse status help) |
|
82 |
- (when help |
|
83 |
- (show-help) |
|
84 |
- (return-from pprint-log)) |
|
83 |
+(defun sort-by-date (results) |
|
84 |
+ (stable-sort results #'local-time:timestamp< |
|
85 |
+ :key (alambda (apply #'local-time:encode-timestamp |
|
86 |
+ (append '(0 0 0 0) |
|
87 |
+ (unroll-date (car it))))))) |
|
88 |
+ |
|
89 |
+(defun pprint-results (results status) |
|
90 |
+ (let ((clients (make-hash-table)) |
|
91 |
+ (total-cost 0)) |
|
85 | 92 |
|
86 |
- (let* ((*default-time-sheet-file* (or (cadr args) *default-time-sheet-file*)) |
|
87 |
- (*print-pretty* t) |
|
88 |
- (results (get-log *default-time-sheet-file*)) |
|
89 |
- (clients (make-hash-table)) |
|
90 |
- (total-cost 0)) |
|
91 |
- (setf results (stable-sort results #'local-time:timestamp< |
|
92 |
- :key (alambda (apply #'local-time:encode-timestamp |
|
93 |
- (append '(0 0 0 0) |
|
94 |
- (unroll-date (car it))))))) |
|
95 |
- (when client |
|
96 |
- (setf results (stable-sort results #'string-lessp :key #'cadr))) |
|
97 |
- (when reverse |
|
98 |
- (setf results (nreverse results))) |
|
99 |
- (format t "~&~:{~4a ~10<~:(~a~)~> ~7,2F hrs ~a~%~}" results) |
|
100 | 93 |
(flet ((record-client (client hours) |
101 | 94 |
(let ((client (make-keyword (string-upcase client)))) |
102 | 95 |
(incf (gethash client clients 0) hours)))) |
96 |
+ (format t "~&~:{~4a ~10<~:(~a~)~> ~7,2F hrs ~a~%~}" results) |
|
103 | 97 |
(when status |
104 | 98 |
(format t "~120,1,0,'-<~>") |
105 | 99 |
(let ((total (format nil "~26<Total~>:~7,2F hours @ ~7,2F $/hr = $~7,2F" |
106 |
- (loop for (_ client time ___) in results |
|
100 |
+ (loop for (_ client time __) in results |
|
101 |
+ do (progn _ __) |
|
107 | 102 |
sum time |
108 | 103 |
do (record-client client time) |
109 | 104 |
do (incf total-cost (* time *rate*))) |
... | ... |
@@ -118,14 +113,33 @@ |
118 | 113 |
(fix-assoc (hash-table-alist clients)) |
119 | 114 |
#'string< |
120 | 115 |
:key (alambda (car it))))) |
121 |
- (format t total)))))) |
|
116 |
+ (format t total)))))) |
|
117 |
+ |
|
118 |
+(defun pprint-log (args &key client reverse status help) |
|
119 |
+ (when help |
|
120 |
+ (show-help) |
|
121 |
+ (return-from pprint-log)) |
|
122 |
+ |
|
123 |
+ (flet ((sort-results (results) |
|
124 |
+ (setf results (sort-by-date results)) |
|
125 |
+ (when client |
|
126 |
+ (setf results (stable-sort results #'string-lessp :key #'cadr))) |
|
127 |
+ (when reverse |
|
128 |
+ (setf results (nreverse results))) |
|
129 |
+ results)) |
|
130 |
+ |
|
131 |
+ (let* ((*default-time-sheet-file* (or (car args) *default-time-sheet-file*)) |
|
132 |
+ (*print-pretty* t) |
|
133 |
+ (results (sort-results (get-log *default-time-sheet-file*)))) |
|
134 |
+ (pprint-results results status)))) |
|
122 | 135 |
|
123 | 136 |
(defun pprint-log-main (argv) |
124 |
- (setf *default-time-sheet-file* (ubiquitous:defaulted-value "" :timesheet :file)) |
|
125 |
- (setf *rate* (ubiquitous:defaulted-value 40 :rate)) |
|
137 |
+ (setf *rate* (ubiquitous:defaulted-value 0 :rate) |
|
138 |
+ *default-time-sheet-file* (ubiquitous:defaulted-value #p"~/time.md" :timesheet :file)) |
|
126 | 139 |
(command-line-arguments:handle-command-line |
127 | 140 |
+pprint-log-option-spec+ |
128 | 141 |
'pprint-log |
129 | 142 |
:command-line (cdr argv) |
130 | 143 |
:name "timesheet" |
131 | 144 |
:rest-arity t)) |
145 |
+ |