Add report build function.
Remove append file functionality. Fix some bugs.
This commit is contained in:
parent
cfd8274e26
commit
52ef1c8f87
236
worktimer.scm
236
worktimer.scm
@ -21,19 +21,24 @@
|
|||||||
;; THE SOFTWARE.
|
;; THE SOFTWARE.
|
||||||
|
|
||||||
(import (rnrs io ports (6))
|
(import (rnrs io ports (6))
|
||||||
|
(srfi srfi-1)
|
||||||
(srfi srfi-19)
|
(srfi srfi-19)
|
||||||
(srfi srfi-11))
|
(srfi srfi-11))
|
||||||
|
|
||||||
;;; Use srfi-48 in other scheme implementation
|
;;; Use srfi-48 in other scheme implementation
|
||||||
(use-modules (ice-9 format))
|
(use-modules (ice-9 format))
|
||||||
|
|
||||||
|
(use-modules (ice-9 pretty-print))
|
||||||
|
|
||||||
;;; Record format:
|
;;; Record format:
|
||||||
;;; PROJECT/PATH: [START_TIME] - [STOP_TIME] - DURATION
|
;;; PROJECT/TASK/SUBTASK/ETC: [START_TIME] - [STOP_TIME] - DURATION
|
||||||
|
|
||||||
|
(define date-format "~Y-~m-~d ~H:~M:~S")
|
||||||
|
|
||||||
;;; Find substring separated by ch-start and ch-end.
|
;;; Find substring separated by ch-start and ch-end.
|
||||||
;; (substring/find str ch-start ch-end [begin end])
|
;;; (substring/find str ch-start ch-end [begin end])
|
||||||
;; If ch-start is #f, select substring from beginning of string.
|
;;; If ch-start is #f, select substring from beginning of string.
|
||||||
;; If ch-end is #f, select substring from ch-start to end string.
|
;;; If ch-end is #f, select substring from ch-start to end string.
|
||||||
(define (substring/find str ch-start ch-end . args)
|
(define (substring/find str ch-start ch-end . args)
|
||||||
(let ((start (if (null? args) 0 (car args)))
|
(let ((start (if (null? args) 0 (car args)))
|
||||||
(end (if (< (length args) 2) (string-length str) (cadr args))))
|
(end (if (< (length args) 2) (string-length str) (cadr args))))
|
||||||
@ -45,6 +50,7 @@
|
|||||||
(if (not cs) (reverse strings)
|
(if (not cs) (reverse strings)
|
||||||
(loop (cons (substring str (1+ cs) ce) strings) (1+ ce)))))))))
|
(loop (cons (substring str (1+ cs) ce) strings) (1+ ce)))))))))
|
||||||
|
|
||||||
|
;;; Convert time-difference to string "hh:mm:ss"
|
||||||
(define (time-difference->h:m:s td)
|
(define (time-difference->h:m:s td)
|
||||||
(let ((sec (time-second td)))
|
(let ((sec (time-second td)))
|
||||||
(let* ((h (quotient sec 3600))
|
(let* ((h (quotient sec 3600))
|
||||||
@ -55,6 +61,7 @@
|
|||||||
(format #f "~2,'0d" m) ":"
|
(format #f "~2,'0d" m) ":"
|
||||||
(format #f "~2,'0d" s)))))
|
(format #f "~2,'0d" s)))))
|
||||||
|
|
||||||
|
;;; Convert path to string
|
||||||
(define (path->string path)
|
(define (path->string path)
|
||||||
(let loop ((path path)
|
(let loop ((path path)
|
||||||
(str ""))
|
(str ""))
|
||||||
@ -65,7 +72,9 @@
|
|||||||
(car path)
|
(car path)
|
||||||
(if (null? (cdr path)) "" "/"))))))
|
(if (null? (cdr path)) "" "/"))))))
|
||||||
|
|
||||||
(define (parse-path str . args)
|
;;; Split path to separate elements
|
||||||
|
;;; (path-split str [start end])
|
||||||
|
(define (path-split str . args)
|
||||||
(let ((start (if (null? args) 0 (car args)))
|
(let ((start (if (null? args) 0 (car args)))
|
||||||
(end (if (or (null? args) (null? (cdr args))) (string-length str) (cadr args))))
|
(end (if (or (null? args) (null? (cdr args))) (string-length str) (cadr args))))
|
||||||
(let loop ((path '()) (path-start start))
|
(let loop ((path '()) (path-start start))
|
||||||
@ -75,11 +84,13 @@
|
|||||||
(loop
|
(loop
|
||||||
(cons (substring str path-start item-end) path) (1+ item-end)))))))
|
(cons (substring str path-start item-end) path) (1+ item-end)))))))
|
||||||
|
|
||||||
|
;;; Parse task string and return list:
|
||||||
|
;;; '((list of path elements) start-date stop-date duration)
|
||||||
(define (parse-task-string str)
|
(define (parse-task-string str)
|
||||||
(let ((path
|
(let ((path
|
||||||
(let ((path-end (string-index str #\:)))
|
(let ((path-end (string-index str #\:)))
|
||||||
(if (not path-end) '()
|
(if (not path-end) '()
|
||||||
(parse-path str 0 path-end))))
|
(path-split str 0 path-end))))
|
||||||
(dates (substring/find str #\[ #\])))
|
(dates (substring/find str #\[ #\])))
|
||||||
(if (null? dates) '()
|
(if (null? dates) '()
|
||||||
(let ((date-start (string->date (car dates) "~Y-~m-~d ~H:~M:~S")))
|
(let ((date-start (string->date (car dates) "~Y-~m-~d ~H:~M:~S")))
|
||||||
@ -93,94 +104,157 @@
|
|||||||
(values date-end duration)))))
|
(values date-end duration)))))
|
||||||
(list path date-start date-end duration))))))
|
(list path date-start date-end duration))))))
|
||||||
|
|
||||||
|
;;; Parse timesheet file and return list of tasks
|
||||||
(define (parse-timesheet filename)
|
(define (parse-timesheet filename)
|
||||||
(call-with-input-file filename
|
(call-with-input-file filename
|
||||||
(lambda (port)
|
(lambda (port)
|
||||||
(let loop ((recs '()))
|
(let loop ((recs '()))
|
||||||
(let ((line (get-line port)))
|
(let ((line (get-line port)))
|
||||||
(if (eof-object? line)
|
(if (eof-object? line)
|
||||||
recs
|
(reverse recs)
|
||||||
(loop (parse-task-string line))))))))
|
(loop (cons (parse-task-string line) recs))))))))
|
||||||
|
|
||||||
(define (timesheet-get-last filename)
|
;;; Return difference of two dates
|
||||||
(let ((last-line
|
(define (date-difference d1 d2)
|
||||||
(call-with-input-file filename
|
|
||||||
(lambda (port)
|
|
||||||
(let loop ((last-line ""))
|
|
||||||
(let ((line (get-line port)))
|
|
||||||
(if (eof-object? line) last-line
|
|
||||||
(loop line))))))))
|
|
||||||
(parse-task-string last-line)))
|
|
||||||
|
|
||||||
(define (call-with-append-file filename thunk)
|
|
||||||
(let ((port (open-file filename
|
|
||||||
(if (file-exists? filename) "a" "w"))))
|
|
||||||
(thunk port)
|
|
||||||
(close-port port)))
|
|
||||||
|
|
||||||
(define (write-stop-and-duration port start-date stop-date)
|
|
||||||
(format port " - [~a] - ~a\n"
|
|
||||||
(date->string stop-date "~Y-~m-~d ~H:~M:~S")
|
|
||||||
(time-difference->h:m:s
|
|
||||||
(time-difference
|
(time-difference
|
||||||
(date->time-utc stop-date)
|
(date->time-utc d1)
|
||||||
(date->time-utc start-date)))))
|
(date->time-utc d2)))
|
||||||
|
|
||||||
|
;;; Print timesheet
|
||||||
|
(define (print-timesheet timesheet)
|
||||||
|
(for-each
|
||||||
|
(lambda (task)
|
||||||
|
(let ((path (car task))
|
||||||
|
(sdate (cadr task))
|
||||||
|
(edate (caddr task))
|
||||||
|
(duration (cadddr task)))
|
||||||
|
(format #t "~a: [~a]"
|
||||||
|
(path->string path)
|
||||||
|
(date->string sdate date-format))
|
||||||
|
(if edate
|
||||||
|
(format #t " - [~a] - ~a\n"
|
||||||
|
(date->string edate date-format)
|
||||||
|
(time-difference->h:m:s duration))
|
||||||
|
(newline))))
|
||||||
|
timesheet))
|
||||||
|
|
||||||
|
;;; Report is a tree of projects items.
|
||||||
|
;;; Root of the tree is super-project with name "ROOT":
|
||||||
|
;;; ("ROOT" d
|
||||||
|
;;; ("PROJ1" d
|
||||||
|
;;; ("P1-TASK1" d
|
||||||
|
;;; ("P1-T1-SUBTASK1" d)
|
||||||
|
;;; ("P1-T1-SUBTASK2" d))
|
||||||
|
;;; ("P1-TASK2" d)))
|
||||||
|
(define (make-report timesheet)
|
||||||
|
;; Add task duration to project branch
|
||||||
|
(define (tree-add-duration project-tree path duration)
|
||||||
|
(let tree-walk ((tree project-tree)
|
||||||
|
(path path))
|
||||||
|
;; Add task duration to tree leaf
|
||||||
|
(set-car! (cdr tree) (add-duration (cadr tree) duration))
|
||||||
|
|
||||||
|
;; Search next leaf corresponding with path item
|
||||||
|
(if (null? path)
|
||||||
|
project-tree
|
||||||
|
(let ((item (find
|
||||||
|
(lambda (i) (string= (car i) (car path)))
|
||||||
|
(cddr tree))))
|
||||||
|
(if item
|
||||||
|
(tree-walk item (cdr path))
|
||||||
|
|
||||||
|
;; Add new branch (or leaf) to tree
|
||||||
|
(let ((add-items
|
||||||
|
(let add-item-loop ((add-items '())
|
||||||
|
(path (reverse path)))
|
||||||
|
(if (null? path)
|
||||||
|
add-items
|
||||||
|
(add-item-loop
|
||||||
|
(if (null? add-items)
|
||||||
|
(list (car path) duration)
|
||||||
|
(list (car path) duration add-items))
|
||||||
|
(cdr path))))))
|
||||||
|
(set-cdr! (cdr tree) (cons add-items (cddr tree)))
|
||||||
|
project-tree))))))
|
||||||
|
;; END tree-add-duration
|
||||||
|
|
||||||
|
(let loop ((projects (list "Overall" (make-time 'time-duration 0 0)))
|
||||||
|
(timesheet timesheet))
|
||||||
|
(if (null? timesheet)
|
||||||
|
projects ; TODO Sort projects by path
|
||||||
|
(let* ((task (car timesheet))
|
||||||
|
(path (car task))
|
||||||
|
(duration (cadddr task)))
|
||||||
|
(loop (if duration
|
||||||
|
(tree-add-duration projects path duration)
|
||||||
|
projects)
|
||||||
|
(cdr timesheet))))))
|
||||||
|
|
||||||
|
(define (print-report report)
|
||||||
|
(let walk ((tree report)
|
||||||
|
(level 0))
|
||||||
|
(when (not (null? tree))
|
||||||
|
(format #t "~v_~a: ~a\n" level (car tree)
|
||||||
|
(time-difference->h:m:s (cadr tree)))
|
||||||
|
(for-each (lambda (l) (walk l (+ level 2))) (cddr tree)))))
|
||||||
|
|
||||||
;; MAIN
|
;; MAIN
|
||||||
|
|
||||||
(define ts-file "/home/np/timesheet.txt")
|
(define ts-file "/home/np/timesheet.txt")
|
||||||
|
|
||||||
(let* ((cmdl (command-line))
|
;; (let* ((cmdl (command-line))
|
||||||
(params (cdr cmdl)))
|
;; (params (cdr cmdl)))
|
||||||
(if (null? params)
|
;; (if (null? params)
|
||||||
;; No command. Show last record and duration if running
|
;; ;; No command. Show last record and duration if running
|
||||||
(let ((last (timesheet-get-last ts-file)))
|
;; (let ((last (timesheet-get-last ts-file)))
|
||||||
(if (null? last)
|
;; (if (null? last)
|
||||||
(format #t "Not any tasks\n")
|
;; (format #t "Not any tasks\n")
|
||||||
(let ((path (car last))
|
;; (let ((path (car last))
|
||||||
(start-date (cadr last))
|
;; (start-date (cadr last))
|
||||||
(stop-date (caddr last))
|
;; (stop-date (caddr last))
|
||||||
(duration (cadddr last))
|
;; (duration (cadddr last))
|
||||||
(now (current-date)))
|
;; (now (current-date)))
|
||||||
(format #t "~a: [~a] - [~a] - ~a\n"
|
;; (format #t "~a: [~a] - [~a] - ~a\n"
|
||||||
(path->string path)
|
;; (path->string path)
|
||||||
(date->string start-date "~Y-~m-~d ~H:~M:~S")
|
;; (date->string start-date "~Y-~m-~d ~H:~M:~S")
|
||||||
(if (not stop-date) "NOW"
|
;; (if (not stop-date) "NOW"
|
||||||
(date->string stop-date "~Y-~m-~d ~H:~M:~S"))
|
;; (date->string stop-date "~Y-~m-~d ~H:~M:~S"))
|
||||||
(time-difference->h:m:s
|
;; (time-difference->h:m:s
|
||||||
(if duration duration
|
;; (if duration duration
|
||||||
(time-difference
|
;; (time-difference
|
||||||
(date->time-utc (if stop-date stop-date now))
|
;; (date->time-utc (if stop-date stop-date now))
|
||||||
(date->time-utc start-date))))))))
|
;; (date->time-utc start-date))))))))
|
||||||
|
|
||||||
;; Commands
|
;; ;; Commands
|
||||||
(let ((command (car params))
|
;; (let ((command (car params))
|
||||||
(date (current-date)))
|
;; (date (current-date)))
|
||||||
(cond
|
;; (cond
|
||||||
|
|
||||||
((equal? command "start")
|
;; ((equal? command "start")
|
||||||
;; ----------------------- Start timer ------------------------- ;;
|
;; ;; ----------------------- Start timer ------------------------- ;;
|
||||||
(call-with-append-file
|
;; (call-with-append-file
|
||||||
ts-file
|
;; ts-file
|
||||||
(lambda (port)
|
;; (lambda (port)
|
||||||
(let* ((last (timesheet-get-last ts-file))
|
;; (let* ((last (timesheet-get-last ts-file))
|
||||||
(path
|
;; (path
|
||||||
(if (null? (cdr params))
|
;; (if (null? (cdr params))
|
||||||
(if (null? last) '() (car last))
|
;; (if (null? last) '() (car last))
|
||||||
(parse-path (cadr params)))))
|
;; (path-split (cadr params)))))
|
||||||
(if (null? path)
|
;; (if (null? path)
|
||||||
(format #t "ERROR: Not specified the task path\n")
|
;; (format #t "ERROR: Not specified the task path\n")
|
||||||
(begin
|
;; (begin
|
||||||
(when (and (not (null? last)) (not (caddr last)))
|
;; (when (and (not (null? last)) (not (caddr last)))
|
||||||
(write-stop-and-duration port (cadr last) date))
|
;; (write-stop-and-duration port (cadr last) date))
|
||||||
(format port "~a: [~a]" (path->string path) (date->string date "~Y-~m-~d ~H:~M:~S"))))))))
|
;; (format port "~a: [~a]" (path->string path) (date->string date "~Y-~m-~d ~H:~M:~S"))))))))
|
||||||
|
|
||||||
((equal? command "stop")
|
;; ((equal? command "stop")
|
||||||
;; ----------------------- Stop timer ------------------------- ;;
|
;; ;; ----------------------- Stop timer ------------------------- ;;
|
||||||
(call-with-append-file
|
;; (call-with-append-file
|
||||||
ts-file
|
;; ts-file
|
||||||
(lambda (port)
|
;; (lambda (port)
|
||||||
(let ((last (timesheet-get-last ts-file)))
|
;; (let ((last (timesheet-get-last ts-file)))
|
||||||
(if (or (null? last) (caddr last))
|
;; (if (or (null? last) (caddr last))
|
||||||
(format #t "WARNING: Not running any tasks\n")
|
;; (format #t "WARNING: Not running any tasks\n")
|
||||||
(write-stop-and-duration port (cadr last) date))))))))))
|
;; (write-stop-and-duration port (cadr last) date))))))))))
|
||||||
|
|
||||||
|
(print-report (make-report (parse-timesheet ts-file)))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user