diff options
| author | Stefan Monnier | 2018-10-08 22:33:22 -0400 |
|---|---|---|
| committer | Stefan Monnier | 2018-10-08 22:33:22 -0400 |
| commit | 333f0bfe766185c66952c6fbd4796c6bb97c868d (patch) | |
| tree | 0fc63cae1bac3c940ba388ea73522c96d8093c8c | |
| parent | cf1ebfa055fcd0749aa4ed2fc4c399470b9eb3de (diff) | |
| download | emacs-333f0bfe766185c66952c6fbd4796c6bb97c868d.tar.gz emacs-333f0bfe766185c66952c6fbd4796c6bb97c868d.zip | |
* lisp/calendar/timeclock.el: Use lexical-binding
Require cl-lib. Remove redundant :group args.
(timeclock-status-string): Avoid 'setq'.
(timeclock-ask-for-project, timeclock-ask-for-reason):
Completionu tables can be simple lists of strings.
(timeclock-read-moment): Doesn't deserve to be defsubst (most of the
others don't either, admittedly).
(timeclock-entry): New type.
(timeclock-entry-begin, timeclock-entry-end, timeclock-entry-project)
(timeclock-entry-comment): Define via 'cl-defstruct'.
(timeclock-entry-list-projects, timeclock-day-list-projects):
Avoid add-to-list on lexical vars.
(timeclock-day-list): Use 'push'.
(timeclock-log-data): Use 'pcase'.
(timeclock-mean): Simplify.
(timeclock-generate-report): Use dotimes.
| -rw-r--r-- | lisp/calendar/timeclock.el | 344 |
1 files changed, 153 insertions, 191 deletions
diff --git a/lisp/calendar/timeclock.el b/lisp/calendar/timeclock.el index ddc297604ec..646f5298fe4 100644 --- a/lisp/calendar/timeclock.el +++ b/lisp/calendar/timeclock.el | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | ;;; timeclock.el --- mode for keeping track of how much you work | 1 | ;;; timeclock.el --- mode for keeping track of how much you work -*- lexical-binding:t -*- |
| 2 | 2 | ||
| 3 | ;; Copyright (C) 1999-2018 Free Software Foundation, Inc. | 3 | ;; Copyright (C) 1999-2018 Free Software Foundation, Inc. |
| 4 | 4 | ||
| @@ -62,7 +62,7 @@ | |||
| 62 | ;; `timeclock-ask-before-exiting' to t using M-x customize (this is | 62 | ;; `timeclock-ask-before-exiting' to t using M-x customize (this is |
| 63 | ;; the default), or by adding the following to your init file: | 63 | ;; the default), or by adding the following to your init file: |
| 64 | ;; | 64 | ;; |
| 65 | ;; (add-hook 'kill-emacs-query-functions 'timeclock-query-out) | 65 | ;; (add-hook 'kill-emacs-query-functions #'timeclock-query-out) |
| 66 | 66 | ||
| 67 | ;; NOTE: If you change your timelog file without using timeclock's | 67 | ;; NOTE: If you change your timelog file without using timeclock's |
| 68 | ;; functions, or if you change the value of any of timeclock's | 68 | ;; functions, or if you change the value of any of timeclock's |
| @@ -75,6 +75,8 @@ | |||
| 75 | 75 | ||
| 76 | ;;; Code: | 76 | ;;; Code: |
| 77 | 77 | ||
| 78 | (require 'cl-lib) | ||
| 79 | |||
| 78 | (defgroup timeclock nil | 80 | (defgroup timeclock nil |
| 79 | "Keeping track of the time that gets spent." | 81 | "Keeping track of the time that gets spent." |
| 80 | :group 'data) | 82 | :group 'data) |
| @@ -84,13 +86,11 @@ | |||
| 84 | (defcustom timeclock-file (locate-user-emacs-file "timelog" ".timelog") | 86 | (defcustom timeclock-file (locate-user-emacs-file "timelog" ".timelog") |
| 85 | "The file used to store timeclock data in." | 87 | "The file used to store timeclock data in." |
| 86 | :version "24.4" ; added locate-user-emacs-file | 88 | :version "24.4" ; added locate-user-emacs-file |
| 87 | :type 'file | 89 | :type 'file) |
| 88 | :group 'timeclock) | ||
| 89 | 90 | ||
| 90 | (defcustom timeclock-workday (* 8 60 60) | 91 | (defcustom timeclock-workday (* 8 60 60) |
| 91 | "The length of a work period in seconds." | 92 | "The length of a work period in seconds." |
| 92 | :type 'integer | 93 | :type 'integer) |
| 93 | :group 'timeclock) | ||
| 94 | 94 | ||
| 95 | (defcustom timeclock-relative t | 95 | (defcustom timeclock-relative t |
| 96 | "Whether to make reported time relative to `timeclock-workday'. | 96 | "Whether to make reported time relative to `timeclock-workday'. |
| @@ -100,24 +100,21 @@ Tuesday is twelve hours -- relative to an averaged work period of | |||
| 100 | eight hours -- or eight hours, non-relative. So relative time takes | 100 | eight hours -- or eight hours, non-relative. So relative time takes |
| 101 | into account any discrepancy of time under-worked or over-worked on | 101 | into account any discrepancy of time under-worked or over-worked on |
| 102 | previous days. This only affects the timeclock mode line display." | 102 | previous days. This only affects the timeclock mode line display." |
| 103 | :type 'boolean | 103 | :type 'boolean) |
| 104 | :group 'timeclock) | ||
| 105 | 104 | ||
| 106 | (defcustom timeclock-get-project-function 'timeclock-ask-for-project | 105 | (defcustom timeclock-get-project-function 'timeclock-ask-for-project |
| 107 | "The function used to determine the name of the current project. | 106 | "The function used to determine the name of the current project. |
| 108 | When clocking in, and no project is specified, this function will be | 107 | When clocking in, and no project is specified, this function will be |
| 109 | called to determine what is the current project to be worked on. | 108 | called to determine what is the current project to be worked on. |
| 110 | If this variable is nil, no questions will be asked." | 109 | If this variable is nil, no questions will be asked." |
| 111 | :type 'function | 110 | :type 'function) |
| 112 | :group 'timeclock) | ||
| 113 | 111 | ||
| 114 | (defcustom timeclock-get-reason-function 'timeclock-ask-for-reason | 112 | (defcustom timeclock-get-reason-function 'timeclock-ask-for-reason |
| 115 | "A function used to determine the reason for clocking out. | 113 | "A function used to determine the reason for clocking out. |
| 116 | When clocking out, and no reason is specified, this function will be | 114 | When clocking out, and no reason is specified, this function will be |
| 117 | called to determine what is the reason. | 115 | called to determine what is the reason. |
| 118 | If this variable is nil, no questions will be asked." | 116 | If this variable is nil, no questions will be asked." |
| 119 | :type 'function | 117 | :type 'function) |
| 120 | :group 'timeclock) | ||
| 121 | 118 | ||
| 122 | (defcustom timeclock-get-workday-function nil | 119 | (defcustom timeclock-get-workday-function nil |
| 123 | "A function used to determine the length of today's workday. | 120 | "A function used to determine the length of today's workday. |
| @@ -127,19 +124,17 @@ the return value is nil, or equal to `timeclock-workday', nothing special | |||
| 127 | will be done. If it is a quantity different from `timeclock-workday', | 124 | will be done. If it is a quantity different from `timeclock-workday', |
| 128 | however, a record will be output to the timelog file to note the fact that | 125 | however, a record will be output to the timelog file to note the fact that |
| 129 | that day has a length that is different from the norm." | 126 | that day has a length that is different from the norm." |
| 130 | :type '(choice (const nil) function) | 127 | :type '(choice (const nil) function)) |
| 131 | :group 'timeclock) | ||
| 132 | 128 | ||
| 133 | (defcustom timeclock-ask-before-exiting t | 129 | (defcustom timeclock-ask-before-exiting t |
| 134 | "If non-nil, ask if the user wants to clock out before exiting Emacs. | 130 | "If non-nil, ask if the user wants to clock out before exiting Emacs. |
| 135 | This variable only has effect if set with \\[customize]." | 131 | This variable only has effect if set with \\[customize]." |
| 136 | :set (lambda (symbol value) | 132 | :set (lambda (symbol value) |
| 137 | (if value | 133 | (if value |
| 138 | (add-hook 'kill-emacs-query-functions 'timeclock-query-out) | 134 | (add-hook 'kill-emacs-query-functions #'timeclock-query-out) |
| 139 | (remove-hook 'kill-emacs-query-functions 'timeclock-query-out)) | 135 | (remove-hook 'kill-emacs-query-functions #'timeclock-query-out)) |
| 140 | (set symbol value)) | 136 | (set symbol value)) |
| 141 | :type 'boolean | 137 | :type 'boolean) |
| 142 | :group 'timeclock) | ||
| 143 | 138 | ||
| 144 | (defvar timeclock-update-timer nil | 139 | (defvar timeclock-update-timer nil |
| 145 | "The timer used to update `timeclock-mode-string'.") | 140 | "The timer used to update `timeclock-mode-string'.") |
| @@ -172,7 +167,7 @@ a positive argument to force an update." | |||
| 172 | (if (and currently-displaying | 167 | (if (and currently-displaying |
| 173 | (or (and value | 168 | (or (and value |
| 174 | (boundp 'display-time-hook) | 169 | (boundp 'display-time-hook) |
| 175 | (memq 'timeclock-update-mode-line | 170 | (memq #'timeclock-update-mode-line |
| 176 | display-time-hook)) | 171 | display-time-hook)) |
| 177 | (and (not value) | 172 | (and (not value) |
| 178 | timeclock-update-timer))) | 173 | timeclock-update-timer))) |
| @@ -185,7 +180,6 @@ a positive argument to force an update." | |||
| 185 | ;; FIXME: The return value isn't used, AFAIK! | 180 | ;; FIXME: The return value isn't used, AFAIK! |
| 186 | value)) | 181 | value)) |
| 187 | :type 'boolean | 182 | :type 'boolean |
| 188 | :group 'timeclock | ||
| 189 | :require 'time) | 183 | :require 'time) |
| 190 | 184 | ||
| 191 | (defcustom timeclock-first-in-hook nil | 185 | (defcustom timeclock-first-in-hook nil |
| @@ -194,40 +188,33 @@ Note that this hook is run before recording any events. Thus the | |||
| 194 | value of `timeclock-hours-today', `timeclock-last-event' and the | 188 | value of `timeclock-hours-today', `timeclock-last-event' and the |
| 195 | return value of function `timeclock-last-period' are relative previous | 189 | return value of function `timeclock-last-period' are relative previous |
| 196 | to today." | 190 | to today." |
| 197 | :type 'hook | 191 | :type 'hook) |
| 198 | :group 'timeclock) | ||
| 199 | 192 | ||
| 200 | (defcustom timeclock-load-hook nil | 193 | (defcustom timeclock-load-hook nil |
| 201 | "Hook that gets run after timeclock has been loaded." | 194 | "Hook that gets run after timeclock has been loaded." |
| 202 | :type 'hook | 195 | :type 'hook) |
| 203 | :group 'timeclock) | ||
| 204 | 196 | ||
| 205 | (defcustom timeclock-in-hook nil | 197 | (defcustom timeclock-in-hook nil |
| 206 | "A hook run every time an \"in\" event is recorded." | 198 | "A hook run every time an \"in\" event is recorded." |
| 207 | :type 'hook | 199 | :type 'hook) |
| 208 | :group 'timeclock) | ||
| 209 | 200 | ||
| 210 | (defcustom timeclock-day-over-hook nil | 201 | (defcustom timeclock-day-over-hook nil |
| 211 | "A hook that is run when the workday has been completed. | 202 | "A hook that is run when the workday has been completed. |
| 212 | This hook is only run if the current time remaining is being displayed | 203 | This hook is only run if the current time remaining is being displayed |
| 213 | in the mode line. See the variable `timeclock-mode-line-display'." | 204 | in the mode line. See the variable `timeclock-mode-line-display'." |
| 214 | :type 'hook | 205 | :type 'hook) |
| 215 | :group 'timeclock) | ||
| 216 | 206 | ||
| 217 | (defcustom timeclock-out-hook nil | 207 | (defcustom timeclock-out-hook nil |
| 218 | "A hook run every time an \"out\" event is recorded." | 208 | "A hook run every time an \"out\" event is recorded." |
| 219 | :type 'hook | 209 | :type 'hook) |
| 220 | :group 'timeclock) | ||
| 221 | 210 | ||
| 222 | (defcustom timeclock-done-hook nil | 211 | (defcustom timeclock-done-hook nil |
| 223 | "A hook run every time a project is marked as completed." | 212 | "A hook run every time a project is marked as completed." |
| 224 | :type 'hook | 213 | :type 'hook) |
| 225 | :group 'timeclock) | ||
| 226 | 214 | ||
| 227 | (defcustom timeclock-event-hook nil | 215 | (defcustom timeclock-event-hook nil |
| 228 | "A hook run every time any event is recorded." | 216 | "A hook run every time any event is recorded." |
| 229 | :type 'hook | 217 | :type 'hook) |
| 230 | :group 'timeclock) | ||
| 231 | 218 | ||
| 232 | (defvar timeclock-last-event nil | 219 | (defvar timeclock-last-event nil |
| 233 | "A list containing the last event that was recorded. | 220 | "A list containing the last event that was recorded. |
| @@ -294,12 +281,12 @@ display (non-nil means on)." | |||
| 294 | (or (memq 'timeclock-mode-string global-mode-string) | 281 | (or (memq 'timeclock-mode-string global-mode-string) |
| 295 | (setq global-mode-string | 282 | (setq global-mode-string |
| 296 | (append global-mode-string '(timeclock-mode-string)))) | 283 | (append global-mode-string '(timeclock-mode-string)))) |
| 297 | (add-hook 'timeclock-event-hook 'timeclock-update-mode-line) | 284 | (add-hook 'timeclock-event-hook #'timeclock-update-mode-line) |
| 298 | (when timeclock-update-timer | 285 | (when timeclock-update-timer |
| 299 | (cancel-timer timeclock-update-timer) | 286 | (cancel-timer timeclock-update-timer) |
| 300 | (setq timeclock-update-timer nil)) | 287 | (setq timeclock-update-timer nil)) |
| 301 | (if (boundp 'display-time-hook) | 288 | (if (boundp 'display-time-hook) |
| 302 | (remove-hook 'display-time-hook 'timeclock-update-mode-line)) | 289 | (remove-hook 'display-time-hook #'timeclock-update-mode-line)) |
| 303 | (if timeclock-use-display-time | 290 | (if timeclock-use-display-time |
| 304 | (progn | 291 | (progn |
| 305 | ;; Update immediately so there is a visible change | 292 | ;; Update immediately so there is a visible change |
| @@ -308,15 +295,15 @@ display (non-nil means on)." | |||
| 308 | (timeclock-update-mode-line) | 295 | (timeclock-update-mode-line) |
| 309 | (message "Activate `display-time-mode' or turn off \ | 296 | (message "Activate `display-time-mode' or turn off \ |
| 310 | `timeclock-use-display-time' to see timeclock information")) | 297 | `timeclock-use-display-time' to see timeclock information")) |
| 311 | (add-hook 'display-time-hook 'timeclock-update-mode-line)) | 298 | (add-hook 'display-time-hook #'timeclock-update-mode-line)) |
| 312 | (setq timeclock-update-timer | 299 | (setq timeclock-update-timer |
| 313 | (run-at-time nil 60 'timeclock-update-mode-line)))) | 300 | (run-at-time nil 60 'timeclock-update-mode-line)))) |
| 314 | (setq global-mode-string | 301 | (setq global-mode-string |
| 315 | (delq 'timeclock-mode-string global-mode-string)) | 302 | (delq 'timeclock-mode-string global-mode-string)) |
| 316 | (remove-hook 'timeclock-event-hook 'timeclock-update-mode-line) | 303 | (remove-hook 'timeclock-event-hook #'timeclock-update-mode-line) |
| 317 | (if (boundp 'display-time-hook) | 304 | (if (boundp 'display-time-hook) |
| 318 | (remove-hook 'display-time-hook | 305 | (remove-hook 'display-time-hook |
| 319 | 'timeclock-update-mode-line)) | 306 | #'timeclock-update-mode-line)) |
| 320 | (when timeclock-update-timer | 307 | (when timeclock-update-timer |
| 321 | (cancel-timer timeclock-update-timer) | 308 | (cancel-timer timeclock-update-timer) |
| 322 | (setq timeclock-update-timer nil)))) | 309 | (setq timeclock-update-timer nil)))) |
| @@ -365,7 +352,8 @@ discover the name of the project." | |||
| 365 | (if (not (= workday timeclock-workday)) | 352 | (if (not (= workday timeclock-workday)) |
| 366 | (timeclock-log "h" (number-to-string | 353 | (timeclock-log "h" (number-to-string |
| 367 | (/ workday (if (zerop (% workday (* 60 60))) | 354 | (/ workday (if (zerop (% workday (* 60 60))) |
| 368 | 60 60.0) 60)))))) | 355 | 60 60.0) |
| 356 | 60)))))) | ||
| 369 | (timeclock-log "i" (or project | 357 | (timeclock-log "i" (or project |
| 370 | (and timeclock-get-project-function | 358 | (and timeclock-get-project-function |
| 371 | (or find-project | 359 | (or find-project |
| @@ -417,12 +405,11 @@ If SHOW-SECONDS is non-nil, display second resolution. | |||
| 417 | If TODAY-ONLY is non-nil, the display will be relative only to time | 405 | If TODAY-ONLY is non-nil, the display will be relative only to time |
| 418 | worked today, ignoring the time worked on previous days." | 406 | worked today, ignoring the time worked on previous days." |
| 419 | (interactive "P") | 407 | (interactive "P") |
| 420 | (let ((remainder (timeclock-workday-remaining | 408 | (let* ((remainder (timeclock-workday-remaining |
| 421 | (or today-only | 409 | (or today-only |
| 422 | (not timeclock-relative)))) | 410 | (not timeclock-relative)))) |
| 423 | (last-in (equal (car timeclock-last-event) "i")) | 411 | (last-in (equal (car timeclock-last-event) "i")) |
| 424 | status) | 412 | (status |
| 425 | (setq status | ||
| 426 | (format "Currently %s since %s (%s), %s %s, leave at %s" | 413 | (format "Currently %s since %s (%s), %s %s, leave at %s" |
| 427 | (if last-in "IN" "OUT") | 414 | (if last-in "IN" "OUT") |
| 428 | (if show-seconds | 415 | (if show-seconds |
| @@ -435,7 +422,7 @@ worked today, ignoring the time worked on previous days." | |||
| 435 | (timeclock-seconds-to-string remainder show-seconds t) | 422 | (timeclock-seconds-to-string remainder show-seconds t) |
| 436 | (if (> remainder 0) | 423 | (if (> remainder 0) |
| 437 | "remaining" "over") | 424 | "remaining" "over") |
| 438 | (timeclock-when-to-leave-string show-seconds today-only))) | 425 | (timeclock-when-to-leave-string show-seconds today-only)))) |
| 439 | (if (called-interactively-p 'interactive) | 426 | (if (called-interactively-p 'interactive) |
| 440 | (message "%s" status) | 427 | (message "%s" status) |
| 441 | status))) | 428 | status))) |
| @@ -623,7 +610,7 @@ arguments of `completing-read'." | |||
| 623 | (format "Clock into which project (default %s): " | 610 | (format "Clock into which project (default %s): " |
| 624 | (or timeclock-last-project | 611 | (or timeclock-last-project |
| 625 | (car timeclock-project-list))) | 612 | (car timeclock-project-list))) |
| 626 | (mapcar 'list timeclock-project-list) | 613 | timeclock-project-list |
| 627 | (or timeclock-last-project | 614 | (or timeclock-last-project |
| 628 | (car timeclock-project-list)))) | 615 | (car timeclock-project-list)))) |
| 629 | 616 | ||
| @@ -632,7 +619,7 @@ arguments of `completing-read'." | |||
| 632 | (defun timeclock-ask-for-reason () | 619 | (defun timeclock-ask-for-reason () |
| 633 | "Ask the user for the reason they are clocking out." | 620 | "Ask the user for the reason they are clocking out." |
| 634 | (timeclock-completing-read "Reason for clocking out: " | 621 | (timeclock-completing-read "Reason for clocking out: " |
| 635 | (mapcar 'list timeclock-reason-list))) | 622 | timeclock-reason-list)) |
| 636 | 623 | ||
| 637 | (define-obsolete-function-alias 'timeclock-update-modeline | 624 | (define-obsolete-function-alias 'timeclock-update-modeline |
| 638 | 'timeclock-update-mode-line "24.3") | 625 | 'timeclock-update-mode-line "24.3") |
| @@ -700,7 +687,7 @@ being logged for. Normally only \"in\" events specify a project." | |||
| 700 | "\\([0-9]+\\)/\\([0-9]+\\)/\\([0-9]+\\)\\s-+" | 687 | "\\([0-9]+\\)/\\([0-9]+\\)/\\([0-9]+\\)\\s-+" |
| 701 | "\\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\)[ \t]*" "\\([^\n]*\\)")) | 688 | "\\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\)[ \t]*" "\\([^\n]*\\)")) |
| 702 | 689 | ||
| 703 | (defsubst timeclock-read-moment () | 690 | (defun timeclock-read-moment () |
| 704 | "Read the moment under point from the timelog." | 691 | "Read the moment under point from the timelog." |
| 705 | (if (looking-at timeclock-moment-regexp) | 692 | (if (looking-at timeclock-moment-regexp) |
| 706 | (let ((code (match-string 1)) | 693 | (let ((code (match-string 1)) |
| @@ -725,27 +712,19 @@ This is only provided for coherency when used by | |||
| 725 | (float-time (cadr timeclock-last-event))) | 712 | (float-time (cadr timeclock-last-event))) |
| 726 | timeclock-last-period)) | 713 | timeclock-last-period)) |
| 727 | 714 | ||
| 715 | (cl-defstruct (timeclock-entry | ||
| 716 | (:constructor nil) (:copier nil) | ||
| 717 | (:type list)) | ||
| 718 | begin end project comment | ||
| 719 | ;; FIXME: Documented in docstring of timeclock-log-data, but I can't see | ||
| 720 | ;; where it's used in the code. | ||
| 721 | final-p) | ||
| 722 | |||
| 728 | (defsubst timeclock-entry-length (entry) | 723 | (defsubst timeclock-entry-length (entry) |
| 729 | "Return the length of ENTRY in seconds." | 724 | "Return the length of ENTRY in seconds." |
| 730 | (- (float-time (cadr entry)) | 725 | (- (float-time (cadr entry)) |
| 731 | (float-time (car entry)))) | 726 | (float-time (car entry)))) |
| 732 | 727 | ||
| 733 | (defsubst timeclock-entry-begin (entry) | ||
| 734 | "Return the start time of ENTRY." | ||
| 735 | (car entry)) | ||
| 736 | |||
| 737 | (defsubst timeclock-entry-end (entry) | ||
| 738 | "Return the end time of ENTRY." | ||
| 739 | (cadr entry)) | ||
| 740 | |||
| 741 | (defsubst timeclock-entry-project (entry) | ||
| 742 | "Return the project of ENTRY." | ||
| 743 | (nth 2 entry)) | ||
| 744 | |||
| 745 | (defsubst timeclock-entry-comment (entry) | ||
| 746 | "Return the comment of ENTRY." | ||
| 747 | (nth 3 entry)) | ||
| 748 | |||
| 749 | (defsubst timeclock-entry-list-length (entry-list) | 728 | (defsubst timeclock-entry-list-length (entry-list) |
| 750 | "Return the total length of ENTRY-LIST in seconds." | 729 | "Return the total length of ENTRY-LIST in seconds." |
| 751 | (let ((length 0)) | 730 | (let ((length 0)) |
| @@ -771,14 +750,11 @@ This is only provided for coherency when used by | |||
| 771 | (- (timeclock-entry-list-span entry-list) | 750 | (- (timeclock-entry-list-span entry-list) |
| 772 | (timeclock-entry-list-length entry-list))) | 751 | (timeclock-entry-list-length entry-list))) |
| 773 | 752 | ||
| 774 | (defsubst timeclock-entry-list-projects (entry-list) | 753 | (defun timeclock-entry-list-projects (entry-list) |
| 775 | "Return a list of all the projects in ENTRY-LIST." | 754 | "Return a list of all the projects in ENTRY-LIST." |
| 776 | (let (projects proj) | 755 | (let (projects) |
| 777 | (dolist (entry entry-list) | 756 | (dolist (entry entry-list) |
| 778 | (setq proj (timeclock-entry-project entry)) | 757 | (cl-pushnew (timeclock-entry-project entry) projects :test #'equal)) |
| 779 | (if projects | ||
| 780 | (add-to-list 'projects proj) | ||
| 781 | (setq projects (list proj)))) | ||
| 782 | projects)) | 758 | projects)) |
| 783 | 759 | ||
| 784 | (defsubst timeclock-day-required (day) | 760 | (defsubst timeclock-day-required (day) |
| @@ -854,9 +830,7 @@ This is only provided for coherency when used by | |||
| 854 | (let (projects) | 830 | (let (projects) |
| 855 | (dolist (day day-list) | 831 | (dolist (day day-list) |
| 856 | (dolist (proj (timeclock-day-projects day)) | 832 | (dolist (proj (timeclock-day-projects day)) |
| 857 | (if projects | 833 | (cl-pushnew proj projects :test #'equal))) |
| 858 | (add-to-list 'projects proj) | ||
| 859 | (setq projects (list proj))))) | ||
| 860 | projects)) | 834 | projects)) |
| 861 | 835 | ||
| 862 | (defsubst timeclock-current-debt (&optional log-data) | 836 | (defsubst timeclock-current-debt (&optional log-data) |
| @@ -871,7 +845,7 @@ This is only provided for coherency when used by | |||
| 871 | "Return a list of the cdrs of the date alist from LOG-DATA." | 845 | "Return a list of the cdrs of the date alist from LOG-DATA." |
| 872 | (let (day-list) | 846 | (let (day-list) |
| 873 | (dolist (date-list (timeclock-day-alist log-data)) | 847 | (dolist (date-list (timeclock-day-alist log-data)) |
| 874 | (setq day-list (cons (cdr date-list) day-list))) | 848 | (push (cdr date-list) day-list)) |
| 875 | day-list)) | 849 | day-list)) |
| 876 | 850 | ||
| 877 | (defsubst timeclock-project-alist (&optional log-data) | 851 | (defsubst timeclock-project-alist (&optional log-data) |
| @@ -1022,54 +996,55 @@ See the documentation for the given function if more info is needed." | |||
| 1022 | (and beg (not last) | 996 | (and beg (not last) |
| 1023 | (setq last t event (list "o" now)))) | 997 | (setq last t event (list "o" now)))) |
| 1024 | (setq line (1+ line)) | 998 | (setq line (1+ line)) |
| 1025 | (cond ((equal (car event) "b") | 999 | (pcase (car event) |
| 1026 | (setcar log-data (string-to-number (nth 2 event)))) | 1000 | ("b" |
| 1027 | ((equal (car event) "h") | 1001 | (setcar log-data (string-to-number (nth 2 event)))) |
| 1028 | (setq last-date-limited (timeclock-time-to-date (cadr event)) | 1002 | ("h" |
| 1029 | last-date-seconds (* (string-to-number (nth 2 event)) | 1003 | (setq last-date-limited (timeclock-time-to-date (cadr event)) |
| 1030 | 3600.0))) | 1004 | last-date-seconds (* (string-to-number (nth 2 event)) |
| 1031 | ((equal (car event) "i") | 1005 | 3600.0))) |
| 1032 | (if beg | 1006 | ("i" |
| 1033 | (error "Error in format of timelog file, line %d" line) | 1007 | (if beg |
| 1034 | (setq beg t)) | 1008 | (error "Error in format of timelog file, line %d" line) |
| 1035 | (setq entry (list (cadr event) nil | 1009 | (setq beg t)) |
| 1036 | (and (> (length (nth 2 event)) 0) | 1010 | (setq entry (list (cadr event) nil |
| 1037 | (nth 2 event)))) | 1011 | (and (> (length (nth 2 event)) 0) |
| 1038 | (let ((date (timeclock-time-to-date (cadr event)))) | 1012 | (nth 2 event)))) |
| 1039 | (if (and last-date | 1013 | (let ((date (timeclock-time-to-date (cadr event)))) |
| 1040 | (not (equal date last-date))) | 1014 | (if (and last-date |
| 1041 | (progn | 1015 | (not (equal date last-date))) |
| 1042 | (setcar (cdr log-data) | 1016 | (progn |
| 1043 | (cons (cons last-date day) | 1017 | (setcar (cdr log-data) |
| 1044 | (cadr log-data))) | 1018 | (cons (cons last-date day) |
| 1045 | (setq day (list (and last-date-limited | 1019 | (cadr log-data))) |
| 1046 | last-date-seconds)))) | 1020 | (setq day (list (and last-date-limited |
| 1047 | (unless day | 1021 | last-date-seconds)))) |
| 1048 | (setq day (list (and last-date-limited | 1022 | (unless day |
| 1049 | last-date-seconds))))) | 1023 | (setq day (list (and last-date-limited |
| 1050 | (setq last-date date | 1024 | last-date-seconds))))) |
| 1051 | last-date-limited nil))) | 1025 | (setq last-date date |
| 1052 | ((equal (downcase (car event)) "o") | 1026 | last-date-limited nil))) |
| 1053 | (if (not beg) | 1027 | ((or "o" "O") |
| 1054 | (error "Error in format of timelog file, line %d" line) | 1028 | (if (not beg) |
| 1055 | (setq beg nil)) | 1029 | (error "Error in format of timelog file, line %d" line) |
| 1056 | (setcar (cdr entry) (cadr event)) | 1030 | (setq beg nil)) |
| 1057 | (let ((desc (and (> (length (nth 2 event)) 0) | 1031 | (setcar (cdr entry) (cadr event)) |
| 1058 | (nth 2 event)))) | 1032 | (let ((desc (and (> (length (nth 2 event)) 0) |
| 1059 | (if desc | 1033 | (nth 2 event)))) |
| 1060 | (nconc entry (list (nth 2 event)))) | 1034 | (if desc |
| 1061 | (if (equal (car event) "O") | 1035 | (nconc entry (list (nth 2 event)))) |
| 1062 | (nconc entry (if desc | 1036 | (if (equal (car event) "O") |
| 1063 | (list t) | 1037 | (nconc entry (if desc |
| 1064 | (list nil t)))) | 1038 | (list t) |
| 1065 | (nconc day (list entry)) | 1039 | (list nil t)))) |
| 1066 | (setq desc (nth 2 entry)) | 1040 | (nconc day (list entry)) |
| 1067 | (let ((proj (assoc desc (nth 2 log-data)))) | 1041 | (setq desc (nth 2 entry)) |
| 1068 | (if (null proj) | 1042 | (let ((proj (assoc desc (nth 2 log-data)))) |
| 1069 | (setcar (cddr log-data) | 1043 | (if (null proj) |
| 1070 | (cons (cons desc (list entry)) | 1044 | (setcar (cddr log-data) |
| 1071 | (nth 2 log-data))) | 1045 | (cons (cons desc (list entry)) |
| 1072 | (nconc (cdr proj) (list entry))))))) | 1046 | (nth 2 log-data))) |
| 1047 | (nconc (cdr proj) (list entry))))))) | ||
| 1073 | (forward-line)) | 1048 | (forward-line)) |
| 1074 | (if day | 1049 | (if day |
| 1075 | (setcar (cdr log-data) | 1050 | (setcar (cdr log-data) |
| @@ -1185,14 +1160,12 @@ If optional argument TIME is non-nil, use that instead of the current time." | |||
| 1185 | 1160 | ||
| 1186 | (defun timeclock-mean (l) | 1161 | (defun timeclock-mean (l) |
| 1187 | "Compute the arithmetic mean of the values in the list L." | 1162 | "Compute the arithmetic mean of the values in the list L." |
| 1188 | (let ((total 0) | 1163 | (if (not (consp l)) |
| 1189 | (count 0)) | 1164 | 0 |
| 1190 | (dolist (thisl l) | 1165 | (let ((total 0)) |
| 1191 | (setq total (+ total thisl) | 1166 | (dolist (thisl l) |
| 1192 | count (1+ count))) | 1167 | (setq total (+ total thisl))) |
| 1193 | (if (zerop count) | 1168 | (/ total (length l))))) |
| 1194 | 0 | ||
| 1195 | (/ total count)))) | ||
| 1196 | 1169 | ||
| 1197 | (defun timeclock-generate-report (&optional html-p) | 1170 | (defun timeclock-generate-report (&optional html-p) |
| 1198 | "Generate a summary report based on the current timelog file. | 1171 | "Generate a summary report based on the current timelog file. |
| @@ -1296,81 +1269,69 @@ HTML-P is non-nil, HTML markup is added." | |||
| 1296 | six-months-ago one-year-ago))) | 1269 | six-months-ago one-year-ago))) |
| 1297 | ;; collect statistics from complete timelog | 1270 | ;; collect statistics from complete timelog |
| 1298 | (dolist (day day-list) | 1271 | (dolist (day day-list) |
| 1299 | (let ((i 0) (l 5)) | 1272 | (dotimes (i 5) |
| 1300 | (while (< i l) | 1273 | (unless (time-less-p |
| 1301 | (unless (time-less-p | 1274 | (timeclock-day-begin day) |
| 1302 | (timeclock-day-begin day) | 1275 | (aref lengths i)) |
| 1303 | (aref lengths i)) | 1276 | (let ((base (float-time |
| 1304 | (let ((base (float-time | 1277 | (timeclock-day-base |
| 1305 | (timeclock-day-base | 1278 | (timeclock-day-begin day))))) |
| 1306 | (timeclock-day-begin day))))) | 1279 | (nconc (aref time-in i) |
| 1307 | (nconc (aref time-in i) | 1280 | (list (- (float-time (timeclock-day-begin day)) |
| 1308 | (list (- (float-time (timeclock-day-begin day)) | 1281 | base))) |
| 1309 | base))) | 1282 | (let ((span (timeclock-day-span day)) |
| 1310 | (let ((span (timeclock-day-span day)) | 1283 | (len (timeclock-day-length day)) |
| 1311 | (len (timeclock-day-length day)) | 1284 | (req (timeclock-day-required day))) |
| 1312 | (req (timeclock-day-required day))) | 1285 | ;; If the day's actual work length is less than |
| 1313 | ;; If the day's actual work length is less than | 1286 | ;; 70% of its span, then likely the exit time |
| 1314 | ;; 70% of its span, then likely the exit time | 1287 | ;; and break amount are not worthwhile adding to |
| 1315 | ;; and break amount are not worthwhile adding to | 1288 | ;; the statistic |
| 1316 | ;; the statistic | 1289 | (when (and (> span 0) |
| 1317 | (when (and (> span 0) | 1290 | (> (/ (float len) (float span)) 0.70)) |
| 1318 | (> (/ (float len) (float span)) 0.70)) | 1291 | (nconc (aref time-out i) |
| 1319 | (nconc (aref time-out i) | 1292 | (list (- (float-time (timeclock-day-end day)) |
| 1320 | (list (- (float-time (timeclock-day-end day)) | 1293 | base))) |
| 1321 | base))) | 1294 | (nconc (aref breaks i) (list (- span len)))) |
| 1322 | (nconc (aref breaks i) (list (- span len)))) | 1295 | (if req |
| 1323 | (if req | 1296 | (setq len (+ len (- timeclock-workday req)))) |
| 1324 | (setq len (+ len (- timeclock-workday req)))) | 1297 | (nconc (aref workday i) (list len))))))) |
| 1325 | (nconc (aref workday i) (list len))))) | ||
| 1326 | (setq i (1+ i))))) | ||
| 1327 | ;; average statistics | 1298 | ;; average statistics |
| 1328 | (let ((i 0) (l 5)) | 1299 | (dotimes (i 5) |
| 1329 | (while (< i l) | 1300 | (aset time-in i (timeclock-mean (cdr (aref time-in i)))) |
| 1330 | (aset time-in i (timeclock-mean (cdr (aref time-in i)))) | 1301 | (aset time-out i (timeclock-mean (cdr (aref time-out i)))) |
| 1331 | (aset time-out i (timeclock-mean (cdr (aref time-out i)))) | 1302 | (aset breaks i (timeclock-mean (cdr (aref breaks i)))) |
| 1332 | (aset breaks i (timeclock-mean (cdr (aref breaks i)))) | 1303 | (aset workday i (timeclock-mean (cdr (aref workday i))))) |
| 1333 | (aset workday i (timeclock-mean (cdr (aref workday i)))) | ||
| 1334 | (setq i (1+ i)))) | ||
| 1335 | ;; Output the HTML table | 1304 | ;; Output the HTML table |
| 1336 | (insert "<tr>\n") | 1305 | (insert "<tr>\n") |
| 1337 | (insert "<td align=\"center\">Time in</td>\n") | 1306 | (insert "<td align=\"center\">Time in</td>\n") |
| 1338 | (let ((i 0) (l 5)) | 1307 | (dotimes (i 5) |
| 1339 | (while (< i l) | 1308 | (insert "<td align=\"right\">" |
| 1340 | (insert "<td align=\"right\">" | 1309 | (timeclock-seconds-to-string (aref time-in i)) |
| 1341 | (timeclock-seconds-to-string (aref time-in i)) | 1310 | "</td>\n")) |
| 1342 | "</td>\n") | ||
| 1343 | (setq i (1+ i)))) | ||
| 1344 | (insert "</tr>\n") | 1311 | (insert "</tr>\n") |
| 1345 | 1312 | ||
| 1346 | (insert "<tr>\n") | 1313 | (insert "<tr>\n") |
| 1347 | (insert "<td align=\"center\">Time out</td>\n") | 1314 | (insert "<td align=\"center\">Time out</td>\n") |
| 1348 | (let ((i 0) (l 5)) | 1315 | (dotimes (i 5) |
| 1349 | (while (< i l) | 1316 | (insert "<td align=\"right\">" |
| 1350 | (insert "<td align=\"right\">" | 1317 | (timeclock-seconds-to-string (aref time-out i)) |
| 1351 | (timeclock-seconds-to-string (aref time-out i)) | 1318 | "</td>\n")) |
| 1352 | "</td>\n") | ||
| 1353 | (setq i (1+ i)))) | ||
| 1354 | (insert "</tr>\n") | 1319 | (insert "</tr>\n") |
| 1355 | 1320 | ||
| 1356 | (insert "<tr>\n") | 1321 | (insert "<tr>\n") |
| 1357 | (insert "<td align=\"center\">Break</td>\n") | 1322 | (insert "<td align=\"center\">Break</td>\n") |
| 1358 | (let ((i 0) (l 5)) | 1323 | (dotimes (i 5) |
| 1359 | (while (< i l) | 1324 | (insert "<td align=\"right\">" |
| 1360 | (insert "<td align=\"right\">" | 1325 | (timeclock-seconds-to-string (aref breaks i)) |
| 1361 | (timeclock-seconds-to-string (aref breaks i)) | 1326 | "</td>\n")) |
| 1362 | "</td>\n") | ||
| 1363 | (setq i (1+ i)))) | ||
| 1364 | (insert "</tr>\n") | 1327 | (insert "</tr>\n") |
| 1365 | 1328 | ||
| 1366 | (insert "<tr>\n") | 1329 | (insert "<tr>\n") |
| 1367 | (insert "<td align=\"center\">Workday</td>\n") | 1330 | (insert "<td align=\"center\">Workday</td>\n") |
| 1368 | (let ((i 0) (l 5)) | 1331 | (dotimes (i 5) |
| 1369 | (while (< i l) | 1332 | (insert "<td align=\"right\">" |
| 1370 | (insert "<td align=\"right\">" | 1333 | (timeclock-seconds-to-string (aref workday i)) |
| 1371 | (timeclock-seconds-to-string (aref workday i)) | 1334 | "</td>\n")) |
| 1372 | "</td>\n") | ||
| 1373 | (setq i (1+ i)))) | ||
| 1374 | (insert "</tr>\n")) | 1335 | (insert "</tr>\n")) |
| 1375 | (insert "<tfoot> | 1336 | (insert "<tfoot> |
| 1376 | <td colspan=\"6\" align=\"center\"> | 1337 | <td colspan=\"6\" align=\"center\"> |
| @@ -1393,6 +1354,7 @@ HTML-P is non-nil, HTML markup is added." | |||
| 1393 | ;; make sure we know the list of reasons, projects, and have computed | 1354 | ;; make sure we know the list of reasons, projects, and have computed |
| 1394 | ;; the last event and current discrepancy. | 1355 | ;; the last event and current discrepancy. |
| 1395 | (if (file-readable-p timeclock-file) | 1356 | (if (file-readable-p timeclock-file) |
| 1357 | ;; FIXME: Loading a file should not have these kinds of side-effects. | ||
| 1396 | (timeclock-reread-log)) | 1358 | (timeclock-reread-log)) |
| 1397 | 1359 | ||
| 1398 | ;;; timeclock.el ends here | 1360 | ;;; timeclock.el ends here |