Merge branch 'dev' into third-time
This commit is contained in:
commit
2cca269d33
|
@ -5,6 +5,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## unreleased
|
||||
### Added
|
||||
1. `chronometrist-third`, an extension to add support for the [Third Time](https://www.lesswrong.com/posts/RWu8eZqbwgB9zaerh/third-time-a-better-way-to-work) system.
|
||||
2. New custom variable `chronometrist-key-value-preset-alist`, to define completion suggestions in advance.
|
||||
3. New custom variable `chronometrist-key-value-use-database-history`, to control whether database history is used for key-value suggestions.
|
||||
|
||||
## [0.10.0] - 2022-02-15
|
||||
### Changed
|
||||
1. The value of `chronometrist-file` must now be a file path _without extension._ Please update your configurations.
|
||||
2. The existing file format used by Chronometrist is now called the `plist` format.
|
||||
|
@ -14,10 +20,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
1. Multiple backend support - new custom variable `chronometrist-active-backend` to determine active backend, new command `chronometrist-switch-backend` to temporarily select a backend (with completion).
|
||||
2. New `plist-group` backend, reducing time taken in startup and after changes to the file.
|
||||
3. Unified migration interface with command `chronometrist-migrate`.
|
||||
4. New custom variable `chronometrist-task-list`, to add/hide tasks without modifying the database.
|
||||
4. New custom variable `chronometrist-task-list`, to add/hide tasks without modifying the database. Setting it also disables generation of the task list from the database, speeding up many operations.
|
||||
5. New command `chronometrist-discard-active`, to discard the active interval.
|
||||
6. Debug logging messages - to view them, set `chronometrist-debug-enable`.
|
||||
7. `chronometrist-third`, an extension to add support for the [Third Time](https://www.lesswrong.com/posts/RWu8eZqbwgB9zaerh/third-time-a-better-way-to-work) system.
|
||||
|
||||
### Fixed
|
||||
1. Code to detect the type of change made to the file has been rewritten, hopefully fixing some uncommon `read` errors and `args out of range` errors.
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
manual.org
|
82
TODO.org
82
TODO.org
|
@ -799,12 +799,18 @@ protocol implementation progress
|
|||
20. [ ] [superclass] chronometrist-timer
|
||||
|
||||
* New backends :feature:
|
||||
1. Org time tracking
|
||||
2. timeclock
|
||||
3. [[https://klog.jotaen.net/][klog]]
|
||||
4. SQLite
|
||||
5. https://github.com/projecthamster/hamster
|
||||
+ https://github.com/projecthamster/hamster/wiki/Our-datamodel
|
||||
** Org time tracking
|
||||
** timeclock
|
||||
See docstring of =timeclock-log-data=
|
||||
|
||||
** [[https://klog.jotaen.net/][klog]]
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: new-backends-klog
|
||||
:END:
|
||||
** SQLite
|
||||
** Project Hamster
|
||||
https://github.com/projecthamster/hamster
|
||||
+ https://github.com/projecthamster/hamster/wiki/Our-datamodel
|
||||
|
||||
* Functions doing similar things :code:
|
||||
1. =task-time-one-day=
|
||||
|
@ -812,9 +818,10 @@ protocol implementation progress
|
|||
3. =events-to-durations=
|
||||
|
||||
* Use ISO date for functions operating on dates :time:format:
|
||||
* STARTED customizable task list :feature:
|
||||
1. Interactive, buffer-local modification of task list, with completion (=completing-read-multiple=)
|
||||
2. Adding a task? We can modify the task list, but how to persist it?
|
||||
* STARTED customizable task list [33%] :feature:
|
||||
1. [X] Make =chronometrist-task-list= customizable
|
||||
2. [ ] Interactive, buffer-local modification of task list, with completion (=completing-read-multiple=)
|
||||
3. [ ] Adding a task? We can modify the task list, but how to persist it?
|
||||
|
||||
* Extend time range prompt :feature:
|
||||
Support inputs like "today", "yesterday", "5 days ago", etc.
|
||||
|
@ -848,6 +855,9 @@ With different checks, for different levels of speed/thoroughness -
|
|||
|
||||
* format changes
|
||||
** multiple intervals per record :feature:
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: multiple-intervals-per-record
|
||||
:END:
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(:name "<name>"
|
||||
[<keyword-value pair>] ...
|
||||
|
@ -858,8 +868,9 @@ With different checks, for different levels of speed/thoroughness -
|
|||
|
||||
It doesn't do anything not already possible in the current formats. At best, it removes some duplication when the same task is "paused" and "resumed".
|
||||
+ Sometimes you pause and resume a task and don't want to split your key-values between >1 intervals (to avoid messing up completion suggestions for the future). An alternative means to the same end could be to add a key like =:deduct "<duration>"= or =:deduct ("<start time>" . "<stop time>")=.
|
||||
- This will also make it easier to support formats like [[https://klog.jotaen.net/][klog]], which support this feature.
|
||||
- This will also make it easier to support formats like [[#new-backends-klog][klog]], which support this feature.
|
||||
- It will probably complicate all data consuming code, though...think of =chronometrist-details= 🤔
|
||||
- An alternative idea could be to define a custom variable to hold the user's key values. If this variable is defined, it would be used instead of generating suggestions from past key-values. That way, such situations will not affect key-value suggestions.
|
||||
|
||||
** "event records" - records with only a timestamp :feature:
|
||||
:PROPERTIES:
|
||||
|
@ -871,7 +882,7 @@ Records not used for time tracking, but to store data associated with a date or
|
|||
[<keyword-value pair>] ...)
|
||||
#+END_SRC
|
||||
|
||||
** STARTED tagging dates with key-values :feature:
|
||||
** STARTED tagging dates with key-values [0%] :feature:
|
||||
:PROPERTIES:
|
||||
:CREATED: 2022-02-10T22:59:45+0530
|
||||
:CUSTOM_ID: date-key-values
|
||||
|
@ -896,6 +907,10 @@ Alternatively, this would be easier to work with - easier to select the key-valu
|
|||
|
||||
The plist backend could theoretically be extended to support it, but I'd rather deprecate that format (since it suffers from performance issues) and not deal with it again.
|
||||
|
||||
1. [ ] Update call sites of =chronometrist-latest-date-records= (which may now also contain key-values)
|
||||
2. [ ] Update code accessing the hash table
|
||||
3. [ ] Update =plist-pp= to handle
|
||||
|
||||
* undesired file watcher created after literate-elisp-load :bug:
|
||||
:PROPERTIES:
|
||||
:CREATED: 2021-12-18T15:13:35+0530
|
||||
|
@ -911,7 +926,7 @@ The plist backend could theoretically be extended to support it, but I'd rather
|
|||
:CREATED: 2022-01-08T23:32:37+0530
|
||||
:END:
|
||||
1. [ ] default suggested input backend should be the active backend
|
||||
2. [ ] conserve file local variables prop line
|
||||
2. [ ] conserve file local variables prop line for text backends
|
||||
|
||||
* STARTED Support for the Third Time System [57%] :extension:
|
||||
:PROPERTIES:
|
||||
|
@ -935,12 +950,6 @@ Additional rules:
|
|||
+ /Avoid taking other unearned breaks/ if possible — so try to do personal tasks during normal or big breaks, or before/after your work day.
|
||||
#+END_QUOTE
|
||||
|
||||
** Example flow
|
||||
1. work for 30m
|
||||
2. clock out - add 10m to =break-time=
|
||||
3. clock in after a 5m break - subtract 5m from =break-time=
|
||||
|
||||
** Tasks
|
||||
1. [X] =chronometrist-third-fraction=
|
||||
2. [X] =chronometrist-third-break-time=
|
||||
3. [X] on clock out, increment =break-time= and start timed notification
|
||||
|
@ -952,6 +961,11 @@ Additional rules:
|
|||
* [ ] Hook function which prompts for work hours whenever you first clock in on a date. If work hours are defined in the custom variable, ask whether to use them - on negative answer, prompt for today's work hours. If work hours are not defined, prompt for today's work hours.
|
||||
7. [ ] Displaying/recording breaks. Probably done implicitly - when work hours are defined, any time not spent working can be interpreted as break time.
|
||||
|
||||
** Example flow
|
||||
1. work for 30m
|
||||
2. clock out - add 10m to =break-time=
|
||||
3. clock in after a 5m break - subtract 5m from =break-time=
|
||||
|
||||
** Extras
|
||||
1. persist =break-time= between Emacs sessions
|
||||
2. audible alerts
|
||||
|
@ -963,13 +977,15 @@ Additional rules:
|
|||
2. edit stop time to add +20 minutes of work (30m total)
|
||||
* compare with old data in hash table - decrement break time added by old plist, increment break time added by new plist
|
||||
|
||||
* Customizable alerts
|
||||
* STARTED Customizable alerts [50%] :feature:code:
|
||||
:PROPERTIES:
|
||||
:CREATED: 2022-02-14T08:22:36+0530
|
||||
:CUSTOM_ID: customizable-alerts
|
||||
:branch: alert-macro
|
||||
:END:
|
||||
=chronometrist-third= and =chronometrist-goal= have nearly identical alert code; additionally, users cannot customize the alert style per-alert without basically rewriting the alert functions.
|
||||
|
||||
Convert =chronometrist-third-alert-functions= and =chronometrist-goal-alert-functions= to customizable alists whose entries take the form =(SYMBOL FUNCTION ALERT-FN &rest ALERT-ARGS)=, where
|
||||
Convert =chronometrist-third-alert-functions= and =chronometrist-goal-alert-functions= to customizable alists[fn:4] whose entries take the form =(SYMBOL FUNCTION ALERT-FN &rest ALERT-ARGS)=, where
|
||||
+ =SYMBOL= uniquely identifies the alert,
|
||||
+ =FUNCTION= is a function calling =chronometrist-*-run-at-time= and =ALERT-FN=, and
|
||||
+ =ALERT-ARGS= are passed to =ALERT-FN=.
|
||||
|
@ -978,3 +994,29 @@ Convert =chronometrist-third-alert-functions= and =chronometrist-goal-alert-func
|
|||
Similar to how they behave now, these packages will start/stop functions for all entries provided in these alists. (So the user can still control which alerts are run.)
|
||||
|
||||
Also define =chronometrist--timer-alist=, which associates =symbol= with a timer object.
|
||||
|
||||
1. [X] write macro
|
||||
2. [ ] use in =chronometrist-goal= and =chronometrist-third=
|
||||
|
||||
[fn:4] Actually, make a macro to define such alists, since the docstrings are likely to be near-identical.
|
||||
|
||||
* DONE Predefined key-values for tasks
|
||||
:PROPERTIES:
|
||||
:CREATED: 2022-02-17T23:34:17+0530
|
||||
:END:
|
||||
Benefits
|
||||
1. Speeds up key-value completion for very large data sets.
|
||||
2. Prevents key-value suggestions from being affected by [[#multiple-intervals-per-record][splitting of tasks across multiple intervals]].
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
'(("Task" <plist> ...)
|
||||
...)
|
||||
#+END_SRC
|
||||
|
||||
* no-goal-alert - "You have spent on <task>" [0%] :bug:
|
||||
:PROPERTIES:
|
||||
:CREATED: 2022-02-25T00:19:49+0530
|
||||
:END:
|
||||
=chronometrist-goal-no-goal-alert= uses =chronometrist-task-time-one-day=, which calls =chronometrist-task-records-for-date= with the /current/ date.
|
||||
|
||||
1. [ ] Use the data for the latest date recorded in the database, rather than the current date.
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
|
@ -192,6 +192,29 @@ used in `chronometrist-before-out-functions'."
|
|||
"Add key-values to Chronometrist time intervals."
|
||||
:group 'chronometrist)
|
||||
|
||||
(defcustom chronometrist-key-value-use-database-history t
|
||||
"If non-nil, use database to generate key-value suggestions.
|
||||
If nil, only `chronometrist-key-value-preset-alist' is used."
|
||||
:type 'boolean
|
||||
:group 'chronometrist-key-value)
|
||||
|
||||
(defcustom chronometrist-key-value-preset-alist nil
|
||||
"Alist of key-value suggestions for `chronometrist-key-value' prompts.
|
||||
Each element must be in the form (\"TASK\" <KEYWORD> <VALUE> ...)"
|
||||
:type
|
||||
'(repeat
|
||||
(cons
|
||||
(string :tag "Task name")
|
||||
(repeat :tag "Property preset"
|
||||
(plist :tag "Property"
|
||||
;; :key-type 'keyword :value-type 'sexp
|
||||
))))
|
||||
:group 'chronometrist-key-values)
|
||||
|
||||
(defun chronometrist-key-value-get-presets (task)
|
||||
"Return presets for TASK from `chronometrist-key-value-preset-alist' as a list of plists."
|
||||
(alist-get task chronometrist-key-value-preset-alist nil nil #'equal))
|
||||
|
||||
(defcustom chronometrist-kv-buffer-name "*Chronometrist-Key-Values*"
|
||||
"Name of buffer in which key-values are entered."
|
||||
:group 'chronometrist-key-values
|
||||
|
@ -404,30 +427,35 @@ used in `chronometrist-before-out-functions'."
|
|||
["Change tags and key-values for active/last interval"
|
||||
chronometrist-key-values-unified-prompt]))
|
||||
|
||||
(cl-defun chronometrist-key-values-unified-prompt (&optional (task (plist-get (chronometrist-latest-record (chronometrist-active-backend)) :name)))
|
||||
(cl-defun chronometrist-key-values-unified-prompt
|
||||
(&optional (task (plist-get (chronometrist-latest-record (chronometrist-active-backend)) :name)))
|
||||
"Query user for tags and key-values to be added for TASK.
|
||||
Return t, to permit use in `chronometrist-before-out-functions'."
|
||||
(interactive)
|
||||
(let* ((backend (chronometrist-active-backend))
|
||||
(presets (--map (format "%S" it)
|
||||
(chronometrist-key-value-get-presets task)))
|
||||
(key-values
|
||||
(cl-loop for plist in (chronometrist-to-list backend)
|
||||
when (equal (plist-get plist :name) task)
|
||||
collect
|
||||
(let ((plist (chronometrist-plist-remove plist :name :start :stop)))
|
||||
(when plist (format "%S" plist)))
|
||||
into key-value-plists
|
||||
finally return
|
||||
(--> (seq-filter #'identity key-value-plists)
|
||||
(cl-remove-duplicates it :test #'equal :from-end t))))
|
||||
(when chronometrist-key-value-use-database-history
|
||||
(cl-loop for plist in (chronometrist-to-list backend)
|
||||
when (equal (plist-get plist :name) task)
|
||||
collect
|
||||
(let ((plist (chronometrist-plist-remove plist :name :start :stop)))
|
||||
(when plist (format "%S" plist)))
|
||||
into key-value-plists
|
||||
finally return
|
||||
(--> (seq-filter #'identity key-value-plists)
|
||||
(cl-remove-duplicates it :test #'equal :from-end t)))))
|
||||
(latest (chronometrist-latest-record backend)))
|
||||
(if (null key-values)
|
||||
(if (and (null presets) (null key-values))
|
||||
(progn (chronometrist-tags-add) (chronometrist-kv-add))
|
||||
(chronometrist-replace-last
|
||||
backend
|
||||
(chronometrist-plist-update
|
||||
latest
|
||||
(read (completing-read (format "Key-values for %s: " task)
|
||||
key-values nil nil nil 'chronometrist-key-values-unified-prompt-history))))))
|
||||
(let* ((candidates (append presets key-values))
|
||||
(input (completing-read
|
||||
(format "Key-values for %s: " task)
|
||||
candidates nil nil nil 'chronometrist-key-values-unified-prompt-history)))
|
||||
(chronometrist-replace-last backend
|
||||
(chronometrist-plist-update latest
|
||||
(read input))))))
|
||||
t)
|
||||
|
||||
(provide 'chronometrist-key-values)
|
||||
|
|
|
@ -330,6 +330,39 @@ used in `chronometrist-before-out-functions'."
|
|||
"Add key-values to Chronometrist time intervals."
|
||||
:group 'chronometrist)
|
||||
#+END_SRC
|
||||
|
||||
*** use-database-history :custom:variable:
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defcustom chronometrist-key-value-use-database-history t
|
||||
"If non-nil, use database to generate key-value suggestions.
|
||||
If nil, only `chronometrist-key-value-preset-alist' is used."
|
||||
:type 'boolean
|
||||
:group 'chronometrist-key-value)
|
||||
#+END_SRC
|
||||
|
||||
*** preset-alist :custom:variable:
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defcustom chronometrist-key-value-preset-alist nil
|
||||
"Alist of key-value suggestions for `chronometrist-key-value' prompts.
|
||||
Each element must be in the form (\"TASK\" <KEYWORD> <VALUE> ...)"
|
||||
:type
|
||||
'(repeat
|
||||
(cons
|
||||
(string :tag "Task name")
|
||||
(repeat :tag "Property preset"
|
||||
(plist :tag "Property"
|
||||
;; :key-type 'keyword :value-type 'sexp
|
||||
))))
|
||||
:group 'chronometrist-key-values)
|
||||
#+END_SRC
|
||||
|
||||
**** get-presets
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun chronometrist-key-value-get-presets (task)
|
||||
"Return presets for TASK from `chronometrist-key-value-preset-alist' as a list of plists."
|
||||
(alist-get task chronometrist-key-value-preset-alist nil nil #'equal))
|
||||
#+END_SRC
|
||||
|
||||
*** kv-buffer-name :custom:variable:
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defcustom chronometrist-kv-buffer-name "*Chronometrist-Key-Values*"
|
||||
|
@ -337,6 +370,7 @@ used in `chronometrist-before-out-functions'."
|
|||
:group 'chronometrist-key-values
|
||||
:type 'string)
|
||||
#+END_SRC
|
||||
|
||||
*** key-history :variable:
|
||||
:PROPERTIES:
|
||||
:VALUE: hash table
|
||||
|
@ -707,41 +741,48 @@ Return t, to permit use in `chronometrist-before-out-functions'."
|
|||
:CUSTOM_ID: unified-prompt
|
||||
:END:
|
||||
1. [ ] Improve appearance - is there an easy way to syntax highlight the plists?
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(cl-defun chronometrist-key-values-unified-prompt (&optional (task (plist-get (chronometrist-latest-record (chronometrist-active-backend)) :name)))
|
||||
(cl-defun chronometrist-key-values-unified-prompt
|
||||
(&optional (task (plist-get (chronometrist-latest-record (chronometrist-active-backend)) :name)))
|
||||
"Query user for tags and key-values to be added for TASK.
|
||||
Return t, to permit use in `chronometrist-before-out-functions'."
|
||||
(interactive)
|
||||
(let* ((backend (chronometrist-active-backend))
|
||||
(presets (--map (format "%S" it)
|
||||
(chronometrist-key-value-get-presets task)))
|
||||
(key-values
|
||||
(cl-loop for plist in (chronometrist-to-list backend)
|
||||
when (equal (plist-get plist :name) task)
|
||||
collect
|
||||
(let ((plist (chronometrist-plist-remove plist :name :start :stop)))
|
||||
(when plist (format "%S" plist)))
|
||||
into key-value-plists
|
||||
finally return
|
||||
(--> (seq-filter #'identity key-value-plists)
|
||||
(cl-remove-duplicates it :test #'equal :from-end t))))
|
||||
(when chronometrist-key-value-use-database-history
|
||||
(cl-loop for plist in (chronometrist-to-list backend)
|
||||
when (equal (plist-get plist :name) task)
|
||||
collect
|
||||
(let ((plist (chronometrist-plist-remove plist :name :start :stop)))
|
||||
(when plist (format "%S" plist)))
|
||||
into key-value-plists
|
||||
finally return
|
||||
(--> (seq-filter #'identity key-value-plists)
|
||||
(cl-remove-duplicates it :test #'equal :from-end t)))))
|
||||
(latest (chronometrist-latest-record backend)))
|
||||
(if (null key-values)
|
||||
(if (and (null presets) (null key-values))
|
||||
(progn (chronometrist-tags-add) (chronometrist-kv-add))
|
||||
(chronometrist-replace-last
|
||||
backend
|
||||
(chronometrist-plist-update
|
||||
latest
|
||||
(read (completing-read (format "Key-values for %s: " task)
|
||||
key-values nil nil nil 'chronometrist-key-values-unified-prompt-history))))))
|
||||
(let* ((candidates (append presets key-values))
|
||||
(input (completing-read
|
||||
(format "Key-values for %s: " task)
|
||||
candidates nil nil nil 'chronometrist-key-values-unified-prompt-history)))
|
||||
(chronometrist-replace-last backend
|
||||
(chronometrist-plist-update latest
|
||||
(read input))))))
|
||||
t)
|
||||
|
||||
#+END_SRC
|
||||
|
||||
* Provide
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(provide 'chronometrist-key-values)
|
||||
;;; chronometrist-key-values.el ends here
|
||||
#+END_SRC
|
||||
|
||||
* Local variables :NOEXPORT:
|
||||
* Local variables :noexport:
|
||||
# Local Variables:
|
||||
# my-org-src-default-lang: "emacs-lisp"
|
||||
# eval: (when (package-installed-p 'literate-elisp) (require 'literate-elisp) (literate-elisp-load (buffer-file-name)))
|
||||
# End:
|
||||
|
|
|
@ -171,7 +171,7 @@ SCHEMA should be a vector as specified by `tabulated-list-format'."
|
|||
;;; chronometrist-spark.el ends here
|
||||
#+END_SRC
|
||||
|
||||
* Local variables :NOEXPORT:
|
||||
* Local variables :noexport:
|
||||
# Local Variables:
|
||||
# eval: (when (package-installed-p 'literate-elisp) (require 'literate-elisp) (literate-elisp-load (buffer-file-name)))
|
||||
# End:
|
||||
|
|
|
@ -71,7 +71,7 @@ For the possible values of DEFAULT-HOURS, see the variable
|
|||
;; divisor:1 ends here
|
||||
|
||||
;; [[file:chronometrist-third.org::*duration-format][duration-format:1]]
|
||||
(defcustom chronometrist-third-duration-format "%H, %M, and %S%z"
|
||||
(defcustom chronometrist-third-duration-format "%H, %M and %S%z"
|
||||
"Format string for durations, passed to `format-seconds'."
|
||||
:type 'string)
|
||||
;; duration-format:1 ends here
|
||||
|
@ -160,19 +160,19 @@ and stopped when they clock in."
|
|||
"Stop alert timers and update break time."
|
||||
(chronometrist-third-stop-alert-timers)
|
||||
(unless (zerop chronometrist-third-break-time)
|
||||
(-let* (((&plist :stop stop) (cl-second (chronometrist-to-list (chronometrist-active-backend))))
|
||||
(used-break-duration (ts-diff (ts-now) (chronometrist-iso-to-ts stop)))
|
||||
(new-break-time (- chronometrist-third-break-time used-break-duration))
|
||||
(old-break-time chronometrist-third-break-time)
|
||||
(used-break-duration-string (format-seconds chronometrist-third-duration-format
|
||||
used-break-duration)))
|
||||
(setq chronometrist-third-break-time (if (> new-break-time 0) new-break-time 0))
|
||||
(-let* (((&plist :stop stop) (cl-second (chronometrist-to-list (chronometrist-active-backend))))
|
||||
(used-break (ts-diff (ts-now) (chronometrist-iso-to-ts stop)))
|
||||
(used-break-string (format-seconds chronometrist-third-duration-format used-break))
|
||||
(new-break (- chronometrist-third-break-time used-break))
|
||||
(old-break chronometrist-third-break-time))
|
||||
(setq chronometrist-third-break-time (if (> new-break 0) new-break 0))
|
||||
(alert
|
||||
(if (zerop chronometrist-third-break-time)
|
||||
(format "You have used up all %s of your break time\n(%s break)"
|
||||
old-break-time used-break-duration-string)
|
||||
(format-seconds chronometrist-third-duration-format old-break)
|
||||
used-break-string)
|
||||
(format "You have used %s of your break time\n(%s left)"
|
||||
used-break-duration-string
|
||||
used-break-string
|
||||
(format-seconds chronometrist-third-duration-format chronometrist-third-break-time)))))))
|
||||
;; clock-in:1 ends here
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ For the possible values of DEFAULT-HOURS, see the variable
|
|||
|
||||
** duration-format :custom:variable:
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defcustom chronometrist-third-duration-format "%H, %M, and %S%z"
|
||||
(defcustom chronometrist-third-duration-format "%H, %M and %S%z"
|
||||
"Format string for durations, passed to `format-seconds'."
|
||||
:type 'string)
|
||||
#+END_SRC
|
||||
|
@ -188,19 +188,19 @@ and stopped when they clock in."
|
|||
"Stop alert timers and update break time."
|
||||
(chronometrist-third-stop-alert-timers)
|
||||
(unless (zerop chronometrist-third-break-time)
|
||||
(-let* (((&plist :stop stop) (cl-second (chronometrist-to-list (chronometrist-active-backend))))
|
||||
(used-break-duration (ts-diff (ts-now) (chronometrist-iso-to-ts stop)))
|
||||
(new-break-time (- chronometrist-third-break-time used-break-duration))
|
||||
(old-break-time chronometrist-third-break-time)
|
||||
(used-break-duration-string (format-seconds chronometrist-third-duration-format
|
||||
used-break-duration)))
|
||||
(setq chronometrist-third-break-time (if (> new-break-time 0) new-break-time 0))
|
||||
(-let* (((&plist :stop stop) (cl-second (chronometrist-to-list (chronometrist-active-backend))))
|
||||
(used-break (ts-diff (ts-now) (chronometrist-iso-to-ts stop)))
|
||||
(used-break-string (format-seconds chronometrist-third-duration-format used-break))
|
||||
(new-break (- chronometrist-third-break-time used-break))
|
||||
(old-break chronometrist-third-break-time))
|
||||
(setq chronometrist-third-break-time (if (> new-break 0) new-break 0))
|
||||
(alert
|
||||
(if (zerop chronometrist-third-break-time)
|
||||
(format "You have used up all %s of your break time\n(%s break)"
|
||||
old-break-time used-break-duration-string)
|
||||
(format-seconds chronometrist-third-duration-format old-break)
|
||||
used-break-string)
|
||||
(format "You have used %s of your break time\n(%s left)"
|
||||
used-break-duration-string
|
||||
used-break-string
|
||||
(format-seconds chronometrist-third-duration-format chronometrist-third-break-time)))))))
|
||||
#+END_SRC
|
||||
|
||||
|
@ -239,7 +239,7 @@ break time is up."
|
|||
;;; chronometrist-third.el ends here
|
||||
#+END_SRC
|
||||
|
||||
* Local variables :NOEXPORT:
|
||||
* Local variables :noexport:
|
||||
# Local Variables:
|
||||
# my-org-src-default-lang: "emacs-lisp"
|
||||
# eval: (when (package-installed-p 'literate-elisp) (require 'literate-elisp) (literate-elisp-load (buffer-file-name)))
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
;; Keywords: calendar
|
||||
;; Homepage: https://tildegit.org/contrapunctus/chronometrist
|
||||
;; Package-Requires: ((emacs "27.1") (dash "2.16.0") (seq "2.20") (ts "0.2"))
|
||||
;; Version: 0.9.0
|
||||
;; Version: 0.10.0
|
||||
|
||||
;; This is free and unencumbered software released into the public domain.
|
||||
;;
|
||||
|
@ -315,8 +315,8 @@ Return value is a ts struct (see `ts.el')."
|
|||
(plist-put :stop stop-2))))))))
|
||||
;; split-plist:1 ends here
|
||||
|
||||
;; [[file:chronometrist.org::*events-update][events-update:1]]
|
||||
(defun chronometrist-events-update (plist hash-table &optional replace)
|
||||
;; [[file:chronometrist.org::*ht-update][ht-update:1]]
|
||||
(defun chronometrist-ht-update (plist hash-table &optional replace)
|
||||
"Return HASH-TABLE with PLIST added as the latest interval.
|
||||
If REPLACE is non-nil, replace the last interval with PLIST."
|
||||
(let* ((date (->> (plist-get plist :start)
|
||||
|
@ -327,28 +327,29 @@ If REPLACE is non-nil, replace the last interval with PLIST."
|
|||
(append it (list plist))
|
||||
(puthash date it hash-table))
|
||||
hash-table))
|
||||
;; events-update:1 ends here
|
||||
;; ht-update:1 ends here
|
||||
|
||||
;; [[file:chronometrist.org::*last-date][last-date:1]]
|
||||
(defun chronometrist-events-last-date (hash-table)
|
||||
;; [[file:chronometrist.org::*ht-last-date][ht-last-date:1]]
|
||||
(defun chronometrist-ht-last-date (hash-table)
|
||||
"Return an ISO-8601 date string for the latest date present in `chronometrist-events'."
|
||||
(--> (hash-table-keys hash-table)
|
||||
(sort it #'string-lessp)
|
||||
(last it)
|
||||
(car it)))
|
||||
;; last-date:1 ends here
|
||||
(cl-first it)))
|
||||
;; ht-last-date:1 ends here
|
||||
|
||||
;; [[file:chronometrist.org::*events-last][events-last:1]]
|
||||
(cl-defun chronometrist-events-last (&optional (backend (chronometrist-active-backend)))
|
||||
;; [[file:chronometrist.org::*ht-last][ht-last:1]]
|
||||
(cl-defun chronometrist-ht-last (&optional (backend (chronometrist-active-backend)))
|
||||
"Return the last plist from `chronometrist-events'."
|
||||
(let* ((hash-table (chronometrist-backend-hash-table backend))
|
||||
(last-date (chronometrist-events-last-date hash-table)))
|
||||
(last-date (chronometrist-ht-last-date hash-table)))
|
||||
(--> (gethash last-date hash-table)
|
||||
(last it)
|
||||
(car it))))
|
||||
;; events-last:1 ends here
|
||||
;; ht-last:1 ends here
|
||||
|
||||
;; [[file:chronometrist.org::#program-data-structures-events-subset][events-subset:1]]
|
||||
(defun chronometrist-events-subset (start end hash-table)
|
||||
;; [[file:chronometrist.org::#program-data-structures-ht-subset][ht-subset:1]]
|
||||
(defun chronometrist-ht-subset (start end hash-table)
|
||||
"Return a subset of HASH-TABLE.
|
||||
The subset will contain values between dates START and END (both
|
||||
inclusive).
|
||||
|
@ -363,7 +364,7 @@ treated as though their time is 00:00:00."
|
|||
(puthash key value subset)))
|
||||
hash-table)
|
||||
subset))
|
||||
;; events-subset:1 ends here
|
||||
;; ht-subset:1 ends here
|
||||
|
||||
;; [[file:chronometrist.org::*task-time-one-day][task-time-one-day:1]]
|
||||
(cl-defun chronometrist-task-time-one-day (task &optional (date (chronometrist-date-ts)) (backend (chronometrist-active-backend)))
|
||||
|
@ -1388,7 +1389,7 @@ STREAM (which is the value of `current-buffer')."
|
|||
(chronometrist-backend-run-assertions backend)
|
||||
(with-slots (hash-table) backend
|
||||
(when-let*
|
||||
((latest-date (chronometrist-events-last-date hash-table))
|
||||
((latest-date (chronometrist-ht-last-date hash-table))
|
||||
(records (gethash latest-date hash-table)))
|
||||
(cons latest-date records))))
|
||||
;; latest-date-records:1 ends here
|
||||
|
@ -1497,7 +1498,7 @@ This is meant to be run in `chronometrist-file' when using an s-expression backe
|
|||
`chronometrist-plist-backend' file."
|
||||
(with-slots (hash-table) backend
|
||||
(-let [(new-plist &as &plist :name new-task) (chronometrist-latest-record backend)]
|
||||
(setf hash-table (chronometrist-events-update new-plist hash-table))
|
||||
(setf hash-table (chronometrist-ht-update new-plist hash-table))
|
||||
(chronometrist-add-to-task-list new-task backend))))
|
||||
;; on-add:1 ends here
|
||||
|
||||
|
@ -1507,8 +1508,8 @@ This is meant to be run in `chronometrist-file' when using an s-expression backe
|
|||
`chronometrist-plist-backend' file is modified."
|
||||
(with-slots (hash-table) backend
|
||||
(-let (((new-plist &as &plist :name new-task) (chronometrist-latest-record backend))
|
||||
((&plist :name old-task) (chronometrist-events-last backend)))
|
||||
(setf hash-table (chronometrist-events-update new-plist hash-table t))
|
||||
((&plist :name old-task) (chronometrist-ht-last backend)))
|
||||
(setf hash-table (chronometrist-ht-update new-plist hash-table t))
|
||||
(chronometrist-remove-from-task-list old-task backend)
|
||||
(chronometrist-add-to-task-list new-task backend))))
|
||||
;; on-modify:1 ends here
|
||||
|
@ -1518,8 +1519,8 @@ This is meant to be run in `chronometrist-file' when using an s-expression backe
|
|||
"Function run when the newest plist in a
|
||||
`chronometrist-plist-backend' file is deleted."
|
||||
(with-slots (hash-table) backend
|
||||
(-let (((&plist :name old-task) (chronometrist-events-last))
|
||||
(date (chronometrist-events-last-date hash-table)))
|
||||
(-let (((&plist :name old-task) (chronometrist-ht-last))
|
||||
(date (chronometrist-ht-last-date hash-table)))
|
||||
;; `chronometrist-remove-from-task-list' checks the hash table to determine
|
||||
;; if `chronometrist-task-list' is to be updated. Thus, the hash table must
|
||||
;; not be updated until the task list is.
|
||||
|
@ -1788,7 +1789,7 @@ Return value is either a list in the form
|
|||
`chronometrist-plist-group-backend' file is modified."
|
||||
(with-slots (hash-table) backend
|
||||
(-let* (((date . plists) (chronometrist-latest-date-records backend))
|
||||
(old-date (chronometrist-events-last-date hash-table))
|
||||
(old-date (chronometrist-ht-last-date hash-table))
|
||||
(old-plists (gethash old-date hash-table)))
|
||||
(puthash date plists hash-table)
|
||||
(cl-loop for plist in old-plists
|
||||
|
@ -1802,7 +1803,7 @@ Return value is either a list in the form
|
|||
"Function run when the newest plist-group in a
|
||||
`chronometrist-plist-group-backend' file is deleted."
|
||||
(with-slots (hash-table) backend
|
||||
(-let* ((old-date (chronometrist-events-last-date hash-table))
|
||||
(-let* ((old-date (chronometrist-ht-last-date hash-table))
|
||||
(old-plists (gethash old-date hash-table)))
|
||||
(cl-loop for plist in old-plists
|
||||
do (chronometrist-remove-from-task-list (plist-get plist :name) backend))
|
||||
|
@ -2946,7 +2947,7 @@ displayed. They must be ts structs (see `ts.el').")
|
|||
It simply operates on the entire hash table TABLE (see
|
||||
`chronometrist-to-hash-table' for table format), so ensure that TABLE is
|
||||
reduced to the desired range using
|
||||
`chronometrist-events-subset'."
|
||||
`chronometrist-ht-subset'."
|
||||
(cl-loop for task in (chronometrist-task-list) collect
|
||||
(let* ((active-days (chronometrist-statistics-count-active-days task table))
|
||||
(active-percent (cl-case (plist-get chronometrist-statistics--ui-state :mode)
|
||||
|
@ -2975,12 +2976,12 @@ reduced to the desired range using
|
|||
('week
|
||||
(let* ((start (plist-get chronometrist-statistics--ui-state :start))
|
||||
(end (plist-get chronometrist-statistics--ui-state :end))
|
||||
(ht (chronometrist-events-subset start end hash-table)))
|
||||
(ht (chronometrist-ht-subset start end hash-table)))
|
||||
(chronometrist-statistics-rows-internal ht)))
|
||||
(t ;; `chronometrist-statistics--ui-state' is nil, show current week's data
|
||||
(let* ((start (chronometrist-previous-week-start (chronometrist-date-ts)))
|
||||
(end (ts-adjust 'day 7 start))
|
||||
(ht (chronometrist-events-subset start end hash-table)))
|
||||
(ht (chronometrist-ht-subset start end hash-table)))
|
||||
(setq chronometrist-statistics--ui-state `(:mode week :start ,start :end ,end))
|
||||
(chronometrist-statistics-rows-internal ht))))))
|
||||
;; rows:1 ends here
|
||||
|
|
|
@ -412,7 +412,7 @@ But I discovered that if I do that, =package-lint= says - =error: Couldn't parse
|
|||
;; Keywords: calendar
|
||||
;; Homepage: https://tildegit.org/contrapunctus/chronometrist
|
||||
;; Package-Requires: ((emacs "27.1") (dash "2.16.0") (seq "2.20") (ts "0.2"))
|
||||
;; Version: 0.9.0
|
||||
;; Version: 0.10.0
|
||||
|
||||
;; This is free and unencumbered software released into the public domain.
|
||||
;;
|
||||
|
@ -833,9 +833,9 @@ Return value is a ts struct (see `ts.el')."
|
|||
:stop "2021-02-13T00:03:46+0530")))))
|
||||
#+END_SRC
|
||||
|
||||
*** events-update :writer:
|
||||
*** ht-update :writer:
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun chronometrist-events-update (plist hash-table &optional replace)
|
||||
(defun chronometrist-ht-update (plist hash-table &optional replace)
|
||||
"Return HASH-TABLE with PLIST added as the latest interval.
|
||||
If REPLACE is non-nil, replace the last interval with PLIST."
|
||||
(let* ((date (->> (plist-get plist :start)
|
||||
|
@ -848,33 +848,34 @@ If REPLACE is non-nil, replace the last interval with PLIST."
|
|||
hash-table))
|
||||
#+END_SRC
|
||||
|
||||
*** last-date :reader:
|
||||
*** ht-last-date :reader:
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun chronometrist-events-last-date (hash-table)
|
||||
(defun chronometrist-ht-last-date (hash-table)
|
||||
"Return an ISO-8601 date string for the latest date present in `chronometrist-events'."
|
||||
(--> (hash-table-keys hash-table)
|
||||
(sort it #'string-lessp)
|
||||
(last it)
|
||||
(car it)))
|
||||
(cl-first it)))
|
||||
#+END_SRC
|
||||
|
||||
*** events-last :reader:
|
||||
*** ht-last :reader:
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(cl-defun chronometrist-events-last (&optional (backend (chronometrist-active-backend)))
|
||||
(cl-defun chronometrist-ht-last (&optional (backend (chronometrist-active-backend)))
|
||||
"Return the last plist from `chronometrist-events'."
|
||||
(let* ((hash-table (chronometrist-backend-hash-table backend))
|
||||
(last-date (chronometrist-events-last-date hash-table)))
|
||||
(last-date (chronometrist-ht-last-date hash-table)))
|
||||
(--> (gethash last-date hash-table)
|
||||
(last it)
|
||||
(car it))))
|
||||
#+END_SRC
|
||||
|
||||
*** events-subset :reader:
|
||||
*** ht-subset :reader:
|
||||
:PROPERTIES:
|
||||
:VALUE: hash table
|
||||
:CUSTOM_ID: program-data-structures-events-subset
|
||||
:CUSTOM_ID: program-data-structures-ht-subset
|
||||
:END:
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun chronometrist-events-subset (start end hash-table)
|
||||
(defun chronometrist-ht-subset (start end hash-table)
|
||||
"Return a subset of HASH-TABLE.
|
||||
The subset will contain values between dates START and END (both
|
||||
inclusive).
|
||||
|
@ -2311,7 +2312,7 @@ In this backend, it's easier to implement this in terms of [[#program-backend-pl
|
|||
(chronometrist-backend-run-assertions backend)
|
||||
(with-slots (hash-table) backend
|
||||
(when-let*
|
||||
((latest-date (chronometrist-events-last-date hash-table))
|
||||
((latest-date (chronometrist-ht-last-date hash-table))
|
||||
(records (gethash latest-date hash-table)))
|
||||
(cons latest-date records))))
|
||||
#+END_SRC
|
||||
|
@ -2427,7 +2428,7 @@ This is meant to be run in `chronometrist-file' when using an s-expression backe
|
|||
`chronometrist-plist-backend' file."
|
||||
(with-slots (hash-table) backend
|
||||
(-let [(new-plist &as &plist :name new-task) (chronometrist-latest-record backend)]
|
||||
(setf hash-table (chronometrist-events-update new-plist hash-table))
|
||||
(setf hash-table (chronometrist-ht-update new-plist hash-table))
|
||||
(chronometrist-add-to-task-list new-task backend))))
|
||||
#+END_SRC
|
||||
|
||||
|
@ -2438,8 +2439,8 @@ This is meant to be run in `chronometrist-file' when using an s-expression backe
|
|||
`chronometrist-plist-backend' file is modified."
|
||||
(with-slots (hash-table) backend
|
||||
(-let (((new-plist &as &plist :name new-task) (chronometrist-latest-record backend))
|
||||
((&plist :name old-task) (chronometrist-events-last backend)))
|
||||
(setf hash-table (chronometrist-events-update new-plist hash-table t))
|
||||
((&plist :name old-task) (chronometrist-ht-last backend)))
|
||||
(setf hash-table (chronometrist-ht-update new-plist hash-table t))
|
||||
(chronometrist-remove-from-task-list old-task backend)
|
||||
(chronometrist-add-to-task-list new-task backend))))
|
||||
#+END_SRC
|
||||
|
@ -2450,8 +2451,8 @@ This is meant to be run in `chronometrist-file' when using an s-expression backe
|
|||
"Function run when the newest plist in a
|
||||
`chronometrist-plist-backend' file is deleted."
|
||||
(with-slots (hash-table) backend
|
||||
(-let (((&plist :name old-task) (chronometrist-events-last))
|
||||
(date (chronometrist-events-last-date hash-table)))
|
||||
(-let (((&plist :name old-task) (chronometrist-ht-last))
|
||||
(date (chronometrist-ht-last-date hash-table)))
|
||||
;; `chronometrist-remove-from-task-list' checks the hash table to determine
|
||||
;; if `chronometrist-task-list' is to be updated. Thus, the hash table must
|
||||
;; not be updated until the task list is.
|
||||
|
@ -2786,7 +2787,7 @@ Return value is either a list in the form
|
|||
`chronometrist-plist-group-backend' file is modified."
|
||||
(with-slots (hash-table) backend
|
||||
(-let* (((date . plists) (chronometrist-latest-date-records backend))
|
||||
(old-date (chronometrist-events-last-date hash-table))
|
||||
(old-date (chronometrist-ht-last-date hash-table))
|
||||
(old-plists (gethash old-date hash-table)))
|
||||
(puthash date plists hash-table)
|
||||
(cl-loop for plist in old-plists
|
||||
|
@ -2801,7 +2802,7 @@ Return value is either a list in the form
|
|||
"Function run when the newest plist-group in a
|
||||
`chronometrist-plist-group-backend' file is deleted."
|
||||
(with-slots (hash-table) backend
|
||||
(-let* ((old-date (chronometrist-events-last-date hash-table))
|
||||
(-let* ((old-date (chronometrist-ht-last-date hash-table))
|
||||
(old-plists (gethash old-date hash-table)))
|
||||
(cl-loop for plist in old-plists
|
||||
do (chronometrist-remove-from-task-list (plist-get plist :name) backend))
|
||||
|
@ -2852,7 +2853,7 @@ Return value is either a list in the form
|
|||
collect plist))
|
||||
#+END_SRC
|
||||
|
||||
***** TODO active-days :reader:method:NOEXPORT:
|
||||
***** TODO active-days :reader:method:noexport:
|
||||
#+BEGIN_SRC emacs-lisp :tangle no
|
||||
(cl-defmethod chronometrist-active-days ((backend chronometrist-plist-group-backend) task &key start end)
|
||||
(cl-check-type task string)
|
||||
|
@ -2877,7 +2878,7 @@ We apply the same hack as in the [[hack-note-plist-group-insert][insert]] method
|
|||
t))
|
||||
#+END_SRC
|
||||
|
||||
***** count-records :reader:method:NOEXPORT:
|
||||
***** count-records :reader:method:noexport:
|
||||
#+BEGIN_SRC emacs-lisp :tangle no
|
||||
(cl-defmethod chronometrist-count-records ((backend chronometrist-plist-group-backend)))
|
||||
#+END_SRC
|
||||
|
@ -4102,7 +4103,7 @@ displayed. They must be ts structs (see `ts.el').")
|
|||
It simply operates on the entire hash table TABLE (see
|
||||
`chronometrist-to-hash-table' for table format), so ensure that TABLE is
|
||||
reduced to the desired range using
|
||||
`chronometrist-events-subset'."
|
||||
`chronometrist-ht-subset'."
|
||||
(cl-loop for task in (chronometrist-task-list) collect
|
||||
(let* ((active-days (chronometrist-statistics-count-active-days task table))
|
||||
(active-percent (cl-case (plist-get chronometrist-statistics--ui-state :mode)
|
||||
|
@ -4132,12 +4133,12 @@ reduced to the desired range using
|
|||
('week
|
||||
(let* ((start (plist-get chronometrist-statistics--ui-state :start))
|
||||
(end (plist-get chronometrist-statistics--ui-state :end))
|
||||
(ht (chronometrist-events-subset start end hash-table)))
|
||||
(ht (chronometrist-ht-subset start end hash-table)))
|
||||
(chronometrist-statistics-rows-internal ht)))
|
||||
(t ;; `chronometrist-statistics--ui-state' is nil, show current week's data
|
||||
(let* ((start (chronometrist-previous-week-start (chronometrist-date-ts)))
|
||||
(end (ts-adjust 'day 7 start))
|
||||
(ht (chronometrist-events-subset start end hash-table)))
|
||||
(ht (chronometrist-ht-subset start end hash-table)))
|
||||
(setq chronometrist-statistics--ui-state `(:mode week :start ,start :end ,end))
|
||||
(chronometrist-statistics-rows-internal ht))))))
|
||||
#+END_SRC
|
||||
|
@ -4561,7 +4562,7 @@ range.")
|
|||
#+END_SRC
|
||||
|
||||
**** intervals-for-range :reader:
|
||||
This is basically like [[#program-data-structures-events-subset][chronometrist-events-subset]], but returns a list instead of a hash table. Might replace one with the other in the future.
|
||||
This is basically like [[#program-data-structures-ht-subset][chronometrist-ht-subset]], but returns a list instead of a hash table. Might replace one with the other in the future.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun chronometrist-details-intervals-for-range (range table)
|
||||
|
@ -4764,7 +4765,7 @@ Return value is a list as specified by `tabulated-list-entries'."
|
|||
;;; chronometrist.el ends here
|
||||
#+END_SRC
|
||||
|
||||
* Local variables :NOEXPORT:
|
||||
* Local variables :noexport:
|
||||
Evaluate this to be able to insert the package prefix via =nameless-insert-name=, at the cost of having all =nameless-aliases= break (= less readable code).
|
||||
|
||||
#+BEGIN_SRC emacs-lisp :load no :tangle no
|
||||
|
|
428
manual.md
428
manual.md
|
@ -1,428 +0,0 @@
|
|||
|
||||
# Table of Contents
|
||||
|
||||
1. [Benefits](#benefits)
|
||||
2. [Limitations](#limitations)
|
||||
3. [Comparisons](#comparisons)
|
||||
1. [timeclock.el](#timeclock.el)
|
||||
2. [Org time tracking](#org-time-tracking)
|
||||
4. [Installation](#installation)
|
||||
1. [from MELPA](#install-from-melpa)
|
||||
2. [from Git](#install-from-git)
|
||||
5. [Usage](#usage)
|
||||
1. [chronometrist](#usage-chronometrist)
|
||||
2. [chronometrist-report](#usage-chronometrist-report)
|
||||
3. [chronometrist-statistics](#usage-chronometrist-statistics)
|
||||
4. [chronometrist-details](#org09eaa12)
|
||||
5. [common commands](#usage-common-commands)
|
||||
6. [Time goals/targets](#time-goals)
|
||||
6. [How-to](#how-to)
|
||||
1. [How to display a prompt when exiting with an active task](#how-to-prompt-when-exiting-emacs)
|
||||
2. [How to load the program using literate-elisp](#how-to-literate-elisp)
|
||||
3. [How to attach tags to time intervals](#how-to-tags)
|
||||
4. [How to attach key-values to time intervals](#how-to-key-value-pairs)
|
||||
5. [How to skip running hooks/attaching tags and key values](#orga569ce7)
|
||||
6. [How to open certain files when you start a task](#how-to-open-files-on-task-start)
|
||||
7. [How to warn yourself about uncommitted changes](#how-to-warn-uncommitted-changes)
|
||||
8. [How to display the current time interval in the activity indicator](#how-to-activity-indicator)
|
||||
9. [How to back up your Chronometrist data](#how-to-backup)
|
||||
10. [How to configure Vertico for use with Chronometrist](#howto-vertico)
|
||||
7. [Explanation](#org2a03f3a)
|
||||
1. [Literate Program](#explanation-literate-program)
|
||||
8. [User's reference](#org2585c1f)
|
||||
9. [Contributions and contact](#contributions-contact)
|
||||
10. [License](#license)
|
||||
11. [Thanks](#thanks)
|
||||
|
||||
<a href="https://liberapay.com/contrapunctus/donate"><img alt="Donate using Liberapay" src="https://img.shields.io/liberapay/receives/contrapunctus.svg?logo=liberapay"></a>
|
||||
|
||||
<a href="https://melpa.org/#/chronometrist"><img src="https://melpa.org/packages/chronometrist-badge.svg"></a>
|
||||
|
||||
A time tracker in Emacs with a nice interface
|
||||
|
||||
Largely modelled after the Android application, [A Time Tracker](https://github.com/netmackan/ATimeTracker)
|
||||
|
||||
|
||||
<a id="benefits"></a>
|
||||
|
||||
# Benefits
|
||||
|
||||
1. Extremely simple and efficient to use
|
||||
2. Displays useful information about your time usage
|
||||
3. Support for both mouse and keyboard
|
||||
4. Human errors in tracking are easily fixed by editing a plain text file
|
||||
5. Hooks to let you perform arbitrary actions when starting/stopping tasks
|
||||
6. Fancy graphs with the `chronometrist-spark` extension
|
||||
|
||||
|
||||
<a id="limitations"></a>
|
||||
|
||||
# Limitations
|
||||
|
||||
1. No support for concurrent tasks.
|
||||
|
||||
|
||||
<a id="comparisons"></a>
|
||||
|
||||
# Comparisons
|
||||
|
||||
|
||||
<a id="timeclock.el"></a>
|
||||
|
||||
## timeclock.el
|
||||
|
||||
Compared to timeclock.el, Chronometrist
|
||||
|
||||
- stores data in an s-expression format rather than a line-based one
|
||||
- supports attaching tags and arbitrary key-values to time intervals
|
||||
- has commands to shows useful summaries
|
||||
- has more hooks
|
||||
|
||||
|
||||
<a id="org-time-tracking"></a>
|
||||
|
||||
## Org time tracking
|
||||
|
||||
Chronometrist and Org time tracking seem to be equivalent in terms of capabilities, approaching the same ends through different means.
|
||||
|
||||
- Chronometrist doesn't have a mode line indicator at the moment. (planned)
|
||||
- Chronometrist doesn't have Org's sophisticated querying facilities. (an SQLite backend is planned)
|
||||
- Org does so many things that keybindings seem to necessarily get longer. Chronometrist has far fewer commands than Org, so most of the keybindings are single keys, without modifiers.
|
||||
- Chronometrist's UI is cleaner, since the storage is separate from the display. It doesn't show tasks as trees like Org, but it uses tags and key-values to achieve that. Additionally, navigating a flat list takes fewer user operations than navigating a tree.
|
||||
- Chronometrist data is just s-expressions (plists), and may be easier to parse than a complex text format with numerous use-cases.
|
||||
|
||||
|
||||
<a id="installation"></a>
|
||||
|
||||
# Installation
|
||||
|
||||
|
||||
<a id="install-from-melpa"></a>
|
||||
|
||||
## from MELPA
|
||||
|
||||
1. Set up MELPA - <https://melpa.org/#/getting-started>
|
||||
|
||||
(Chronometrist uses Semantic Versioning and the developer is accident-prone, so using MELPA Stable is suggested 😏)
|
||||
2. `M-x package-install RET chronometrist RET`
|
||||
|
||||
|
||||
<a id="install-from-git"></a>
|
||||
|
||||
## from Git
|
||||
|
||||
You can get `chronometrist` from <https://tildegit.org/contrapunctus/chronometrist> or <https://codeberg.org/contrapunctus/chronometrist>
|
||||
|
||||
`chronometrist` requires
|
||||
|
||||
- Emacs v25 or higher
|
||||
- [dash.el](https://github.com/magnars/dash.el)
|
||||
- [ts.el](https://github.com/alphapapa/ts.el)
|
||||
|
||||
Add the "elisp/" subdirectory to your load-path, and `(require 'chronometrist)`.
|
||||
|
||||
|
||||
<a id="usage"></a>
|
||||
|
||||
# Usage
|
||||
|
||||
|
||||
<a id="usage-chronometrist"></a>
|
||||
|
||||
## chronometrist
|
||||
|
||||
Run `M-x chronometrist` to see your projects, the time you spent on them today, which one is active, and the total time clocked today.
|
||||
|
||||
Click or hit `RET` (`chronometrist-toggle-task`) on a project to start tracking time for it. If it's already clocked in, it will be clocked out.
|
||||
|
||||
You can also hit `<numeric prefix> RET` anywhere in the buffer to toggle the corresponding project, e.g. =C-1 RET= will toggle the project with index 1.
|
||||
|
||||
Press `r` to see a weekly report (see `chronometrist-report`)
|
||||
|
||||
|
||||
<a id="usage-chronometrist-report"></a>
|
||||
|
||||
## chronometrist-report
|
||||
|
||||
Run `M-x chronometrist-report` (or `chronometrist` with a prefix argument of 1, or press `r` in the `chronometrist` buffer) to see a weekly report.
|
||||
|
||||
Press `b` to look at past weeks, and `f` for future weeks.
|
||||
|
||||
|
||||
<a id="usage-chronometrist-statistics"></a>
|
||||
|
||||
## chronometrist-statistics
|
||||
|
||||
Run `M-x chronometrist-statistics` (or `chronometrist` with a prefix argument of 2) to view statistics.
|
||||
|
||||
Press `b` to look at past time ranges, and `f` for future ones.
|
||||
|
||||
|
||||
<a id="org09eaa12"></a>
|
||||
|
||||
## chronometrist-details
|
||||
|
||||
|
||||
<a id="usage-common-commands"></a>
|
||||
|
||||
## common commands
|
||||
|
||||
In the buffers created by the previous three commands, you can press `l` (`chronometrist-open-log`) to view/edit your `chronometrist-file`, which by default is `~/.emacs.d/chronometrist.sexp`.
|
||||
|
||||
All of these commands will kill their buffer when run again with the buffer visible, so the keys you bind them to behave as a toggle.
|
||||
|
||||
All buffers keep themselves updated via an idle timer - no need to frequently press `g` to update.
|
||||
|
||||
|
||||
<a id="time-goals"></a>
|
||||
|
||||
## Time goals/targets
|
||||
|
||||
If you wish you could define time goals for some tasks, and have Chronometrist notify you when you're approaching the goal, completing it, or exceeding it, check out the extension [chronometrist-goal.el](https://github.com/contrapunctus-1/chronometrist-goal/).
|
||||
|
||||
|
||||
<a id="how-to"></a>
|
||||
|
||||
# How-to
|
||||
|
||||
See the Customize groups `chronometrist` and `chronometrist-report` for variables intended to be user-customizable.
|
||||
|
||||
|
||||
<a id="how-to-prompt-when-exiting-emacs"></a>
|
||||
|
||||
## How to display a prompt when exiting with an active task
|
||||
|
||||
Evaluate or add to your init.el the following -
|
||||
`(add-hook 'kill-emacs-query-functions 'chronometrist-query-stop)`
|
||||
|
||||
|
||||
<a id="how-to-literate-elisp"></a>
|
||||
|
||||
## How to load the program using literate-elisp
|
||||
|
||||
(add-to-list 'load-path "<directory containing chronometrist.org>")
|
||||
|
||||
(require 'literate-elisp) ;; or autoload, use-package, ...
|
||||
(literate-elisp-load "chronometrist.org")
|
||||
|
||||
|
||||
<a id="how-to-tags"></a>
|
||||
|
||||
## How to attach tags to time intervals
|
||||
|
||||
1. Add `chronometrist-tags-add` to one or more of these hooks <sup><a id="fnr.1" class="footref" href="#fn.1" role="doc-backlink">1</a></sup> -
|
||||
|
||||
(add-to-list 'chronometrist-after-in-functions 'chronometrist-tags-add)
|
||||
(add-to-list 'chronometrist-before-out-functions 'chronometrist-tags-add)
|
||||
(add-to-list 'chronometrist-after-out-functions 'chronometrist-tags-add)
|
||||
2. clock in/clock out to trigger the hook.
|
||||
|
||||
The prompt suggests past combinations you used for the current task, which you can browse with `M-p=/=M-n`. You can leave it blank by pressing `RET`.
|
||||
|
||||
|
||||
<a id="how-to-key-value-pairs"></a>
|
||||
|
||||
## How to attach key-values to time intervals
|
||||
|
||||
1. Add `chronometrist-kv-add` to one or more of these hooks <sup><a id="fnr.1.100" class="footref" href="#fn.1" role="doc-backlink">1</a></sup> -
|
||||
|
||||
(add-to-list 'chronometrist-after-in-functions 'chronometrist-kv-add)
|
||||
(add-to-list 'chronometrist-before-out-functions 'chronometrist-kv-add)
|
||||
(add-to-list 'chronometrist-after-out-functions 'chronometrist-kv-add)
|
||||
|
||||
To exit the prompt, press the key it indicates for quitting - you can then edit the resulting key-values by hand if required. Press `C-c C-c` to accept the key-values, or `C-c C-k` to cancel.
|
||||
|
||||
|
||||
<a id="orga569ce7"></a>
|
||||
|
||||
## How to skip running hooks/attaching tags and key values
|
||||
|
||||
Use `M-RET` (`chronometrist-toggle-task-no-hooks`) to clock in/out.
|
||||
|
||||
|
||||
<a id="how-to-open-files-on-task-start"></a>
|
||||
|
||||
## How to open certain files when you start a task
|
||||
|
||||
An idea from the author's own init -
|
||||
|
||||
(defun my-start-project (project)
|
||||
(pcase project
|
||||
("Guitar"
|
||||
(find-file-other-window "~/repertoire.org"))
|
||||
;; ...
|
||||
))
|
||||
|
||||
(add-hook 'chronometrist-before-in-functions 'my-start-project)
|
||||
|
||||
|
||||
<a id="how-to-warn-uncommitted-changes"></a>
|
||||
|
||||
## How to warn yourself about uncommitted changes
|
||||
|
||||
Another one, prompting the user if they have uncommitted changes in a git repository (assuming they use [Magit](https://magit.vc/)) -
|
||||
|
||||
(autoload 'magit-anything-modified-p "magit")
|
||||
|
||||
(defun my-commit-prompt ()
|
||||
"Prompt user if `default-directory' is a dirty Git repository.
|
||||
Return t if the user answers yes, if the repository is clean, or
|
||||
if there is no Git repository.
|
||||
|
||||
Return nil (and run `magit-status') if the user answers no."
|
||||
(cond ((not (magit-anything-modified-p)) t)
|
||||
((yes-or-no-p
|
||||
(format "You have uncommitted changes in %S. Really clock out? "
|
||||
default-directory)) t)
|
||||
(t (magit-status) nil)))
|
||||
|
||||
(add-hook 'chronometrist-before-out-functions 'my-commit-prompt)
|
||||
|
||||
|
||||
<a id="how-to-activity-indicator"></a>
|
||||
|
||||
## How to display the current time interval in the activity indicator
|
||||
|
||||
(defun my-activity-indicator ()
|
||||
(--> (chronometrist-latest-record (chronometrist-active-backend))
|
||||
(plist-put it :stop (chronometrist-format-time-iso8601))
|
||||
(list it)
|
||||
(chronometrist-events-to-durations it)
|
||||
(-reduce #'+ it)
|
||||
(truncate it)
|
||||
(chronometrist-format-duration it)))
|
||||
|
||||
(setq chronometrist-activity-indicator #'my-activity-indicator)
|
||||
|
||||
|
||||
<a id="how-to-backup"></a>
|
||||
|
||||
## How to back up your Chronometrist data
|
||||
|
||||
I suggest backing up Chronometrist data on each save using the [async-backup](https://tildegit.org/contrapunctus/async-backup) package.<sup><a id="fnr.2" class="footref" href="#fn.2" role="doc-backlink">2</a></sup> Here's how you can do that.
|
||||
|
||||
1. Add the following to your init.
|
||||
|
||||
(use-package async-backup)
|
||||
2. Open your Chronometrist file and add `async-backup` to a buffer-local `after-save-hook`.
|
||||
|
||||
M-x chronometrist-open-log
|
||||
M-x add-file-local-variable-prop-line RET eval RET (add-hook 'after-save-hook #'async-backup nil t) RET
|
||||
3. Optionally, configure `backup-directory-alist` to set a specific directory for the backups.
|
||||
|
||||
|
||||
<a id="howto-vertico"></a>
|
||||
|
||||
## How to configure Vertico for use with Chronometrist
|
||||
|
||||
By default, [Vertico](https://github.com/minad/vertico) uses its own sorting function - for some commands (such as `chronometrist-key-values-unified-prompt`) this results in *worse* suggestions, since Chronometrist sorts suggestions in most-recent-first order.
|
||||
|
||||
You can either disable Vertico's sorting entirely -
|
||||
|
||||
(setq vertico-sort-function nil)
|
||||
|
||||
Or use `vertico-multiform` to disable sorting for only specific commands -
|
||||
|
||||
(use-package vertico-multiform
|
||||
:init (vertico-multiform-mode)
|
||||
:config
|
||||
(setq vertico-multiform-commands
|
||||
'((chronometrist-toggle-task (vertico-sort-function . nil))
|
||||
(chronometrist-toggle-task-no-hooks (vertico-sort-function . nil))
|
||||
(chronometrist-key-values-unified-prompt (vertico-sort-function . nil)))))
|
||||
|
||||
|
||||
<a id="org2a03f3a"></a>
|
||||
|
||||
# Explanation
|
||||
|
||||
|
||||
<a id="explanation-literate-program"></a>
|
||||
|
||||
## Literate Program
|
||||
|
||||
Chronometrist is a literate program, made using Org - the canonical source is the `chronometrist.org` file, which contains source blocks. These are provided to users after *tangling* (extracting the source into an Emacs Lisp file).
|
||||
|
||||
The Org file can also be loaded directly using the [literate-elisp](https://github.com/jingtaozf/literate-elisp) package, so that all source links (e.g. `xref`, `describe-function`) lead to the Org file, within the context of the concerned documentation. See [How to load the program using literate-elisp](#how-to-literate-elisp).
|
||||
|
||||
`chronometrist.org` is also included in MELPA installs, although not used directly by default, since doing so would interfere with automatic generation of autoloads.
|
||||
|
||||
|
||||
<a id="org2585c1f"></a>
|
||||
|
||||
# User's reference
|
||||
|
||||
All variables intended for user customization are listed here. They serve as the public API for this project for the purpose of semantic versioning. Any changes to these which require a user to modify their configuration are considered breaking changes.
|
||||
|
||||
1. `chronometrist-file`
|
||||
2. `chronometrist-buffer-name`
|
||||
3. `chronometrist-report-buffer-name`
|
||||
4. `chronometrist-details-buffer-name`
|
||||
5. `chronometrist-sexp-pretty-print-function`
|
||||
6. `chronometrist-hide-cursor`
|
||||
7. `chronometrist-update-interval`
|
||||
8. `chronometrist-activity-indicator`
|
||||
|
||||
Buffer schemas
|
||||
|
||||
1. `chronometrist-schema`
|
||||
2. `chronometrist-details-schema`
|
||||
|
||||
Hooks
|
||||
|
||||
1. `chronometrist-mode-hook`
|
||||
2. `chronometrist-schema-transformers`
|
||||
3. `chronometrist-row-transformers`
|
||||
4. `chronometrist-before-in-functions`
|
||||
5. `chronometrist-after-in-functions`
|
||||
6. `chronometrist-before-out-functions`
|
||||
7. `chronometrist-after-out-functions`
|
||||
8. `chronometrist-file-change-hook`
|
||||
9. `chronometrist-timer-hook`
|
||||
|
||||
|
||||
<a id="contributions-contact"></a>
|
||||
|
||||
# Contributions and contact
|
||||
|
||||
Feedback and MRs are very welcome. 🙂
|
||||
|
||||
- <TODO.md> has a long list of tasks
|
||||
- <elisp/chronometrist.md> contains all developer-oriented documentation
|
||||
|
||||
If you have tried using Chronometrist, I'd love to hear your experiences! Get in touch with the author and other Emacs users in the Emacs channel on the Jabber network - [xmpp:emacs@salas.suchat.org?join](https://conversations.im/j/emacs@salas.suchat.org) ([web chat](https://inverse.chat/#converse/room?jid=emacs@salas.suchat.org))
|
||||
|
||||
(For help in getting started with Jabber, [click here](https://xmpp.org/getting-started/))
|
||||
|
||||
|
||||
<a id="license"></a>
|
||||
|
||||
# License
|
||||
|
||||
I dream of a world where all software is liberated - transparent, trustable, and accessible for anyone to use or improve. But I don't want to make demands or threats (e.g. via legal conditions) to get there.
|
||||
|
||||
I'd rather make a request - please do everything you can to help that dream come true. Please Unlicense as much software as you can.
|
||||
|
||||
Chronometrist is released under your choice of [Unlicense](https://unlicense.org/) or the [WTFPL](http://www.wtfpl.net/).
|
||||
|
||||
(See files [UNLICENSE](UNLICENSE) and [WTFPL](WTFPL)).
|
||||
|
||||
|
||||
<a id="thanks"></a>
|
||||
|
||||
# Thanks
|
||||
|
||||
wasamasa, bpalmer, aidalgol, pjb and the rest of #emacs for their tireless help and support
|
||||
|
||||
jwiegley for `timeclock.el`, which we used as a backend in earlier versions
|
||||
|
||||
blandest for helping me with the name
|
||||
|
||||
fiete and wu-lee for testing and bug reports
|
||||
|
||||
|
||||
# Footnotes
|
||||
|
||||
<sup><a id="fn.1" href="#fnr.1">1</a></sup> but not `chronometrist-before-in-functions`
|
||||
|
||||
<sup><a id="fn.2" href="#fnr.2">2</a></sup> It is possible to use Emacs' built-in backup system to do it, but since it is synchronous, doing so will greatly slow down saving of the Chronometrist file.
|
17
manual.org
17
manual.org
|
@ -4,14 +4,19 @@
|
|||
#+HTML_HEAD: <link rel="stylesheet" type="text/css" href="style.css" />
|
||||
|
||||
#+BEGIN_EXPORT html
|
||||
<a href="https://liberapay.com/contrapunctus/donate"><img alt="Donate using Liberapay" src="https://img.shields.io/liberapay/receives/contrapunctus.svg?logo=liberapay"></a>
|
||||
<a href="https://liberapay.com/contrapunctus/donate">
|
||||
<img alt="Donate using Liberapay" src="https://img.shields.io/liberapay/receives/contrapunctus.svg?logo=liberapay">
|
||||
</a>
|
||||
|
||||
<a href="https://melpa.org/#/chronometrist"><img src="https://melpa.org/packages/chronometrist-badge.svg"></a>
|
||||
<a href="https://melpa.org/#/chronometrist">
|
||||
<img src="https://melpa.org/packages/chronometrist-badge.svg">
|
||||
</a>
|
||||
#+END_EXPORT
|
||||
|
||||
A time tracker in Emacs with a nice interface
|
||||
Chronometrist is a friendly and powerful personal time tracker and analyzer for Emacs.
|
||||
|
||||
Largely modelled after the Android application, [[https://github.com/netmackan/ATimeTracker][A Time Tracker]]
|
||||
#+CAPTION: The main Chronometrist buffer, with the enabled extensions [[#time-goals][chronometrist-goal]] ("Targets" column + alerts) and chronometrist-spark ("Graph" column displaying the activity for the past 4 weeks).
|
||||
[[file:doc/2022-02-20 13-26-53.png]]
|
||||
|
||||
* Benefits
|
||||
:PROPERTIES:
|
||||
|
@ -67,8 +72,6 @@ Chronometrist and Org time tracking seem to be equivalent in terms of capabiliti
|
|||
:END:
|
||||
|
||||
1. Set up MELPA - https://melpa.org/#/getting-started
|
||||
|
||||
(Chronometrist uses Semantic Versioning and the developer is accident-prone, so using MELPA Stable is suggested 😏)
|
||||
2. =M-x package-install RET chronometrist RET=
|
||||
|
||||
** from Git
|
||||
|
@ -371,6 +374,7 @@ Chronometrist is released under your choice of [[https://unlicense.org/][Unlicen
|
|||
:PROPERTIES:
|
||||
:CUSTOM_ID: thanks
|
||||
:END:
|
||||
The main buffer and the report buffer are copied from the Android application, [[https://github.com/netmackan/ATimeTracker][A Time Tracker]]
|
||||
|
||||
wasamasa, bpalmer, aidalgol, pjb and the rest of #emacs for their tireless help and support
|
||||
|
||||
|
@ -382,6 +386,5 @@ fiete and wu-lee for testing and bug reports
|
|||
|
||||
* Local variables :noexport:
|
||||
# Local Variables:
|
||||
# eval: (progn (require 'ox-md) (add-hook 'after-save-hook (lambda () (org-export-to-file 'md "manual.md")) nil t))
|
||||
# my-org-src-default-lang: "emacs-lisp"
|
||||
# End:
|
||||
|
|
Reference in New Issue