Compare commits

...
This repository has been archived on 2022-05-13. You can view files and clone it, but cannot push or open issues or pull requests.

3 Commits

Author SHA1 Message Date
contrapunctus 3819b2e0de rethink output format - use tabulated-list-mode 2021-06-27 05:52:57 +05:30
contrapunctus 2480d26f91 fix(spark/task-durations-month): pad with nils if not ending on Saturday 2021-06-24 19:16:55 +05:30
contrapunctus 8f664fd4e2 feat(spark): create task-durations-month 2021-06-24 18:46:49 +05:30
5 changed files with 244 additions and 27 deletions

View File

@ -413,7 +413,7 @@ Some options and ideas -
- untouched project with target defined - red
- target ±5 minutes - green
- target*2 and above - red
* documentation discoverability :doc:
* documentation discoverability :doc:
Ensure that the user manual is easily discoverable.
#+BEGIN_QUOTE
@ -427,9 +427,8 @@ Ensure that the user manual is easily discoverable.
[2021-06-02 13:53:37] rnkn: although you will probably need a function link instead to find the org file \\
[2021-06-02 13:54:30] contrapunctus: Although I guess the manual.org does not really need those fancy features...info export could work for it. \\
#+END_QUOTE
* macro for extensions :code:extension:
* macro for extensions :code:extension:
<2021-06-07T16:33:54+0530>
A macro to create new columns for Chronometrist.
Extension writer specifies
@ -452,7 +451,11 @@ Benefits -
+ easier creation of such extensions
+ users can easily replace the function used to generate the cells, without having to deal with how the string is inserted into the row specifier.
* unified format-duration function :code:customization:
Current uses -
1. =chronometrist-goal=
2. =chronometrist-spark=
* unified format-duration function :code:customization:
<2021-06-08T11:17:54+0530>
Currently we have at least three ways of displaying durations - ="HH:MM:SS"= , ="XhYm"= , and =X hour(s), Y minutes(s)"= . Make a single function similar to =format-time-string=, but for durations. =ts-human-format-duration= from =ts.el= is not nearly as flexible as I'd like. When completed, we can have a single custom variable accepting a format string, which can be used to customize display of durations for the entire application at once.
@ -490,7 +493,7 @@ Alternative syntax
+ to display only values, use ="%<code>"=
+ to display long units, use ="~[<separator>]<code>"=
* DONE error - =min= called with nil :spark:bug:
* DONE error - =min= called with nil :spark:bug:
<2021-06-11T03:44:17+0530>
1. clock in
2. change =:start= of active interval to another time on the same date
@ -544,7 +547,7 @@ Debugger entered--Lisp error: (wrong-number-of-arguments #<subr min> 0)
command-execute(file-notify-handle-event nil [(file-notify ((1 . 1) (modify) "chronometrist.sexp" 0) file-notify--callback-inotify)] t)
#+END_SRC
* STARTED discoverability and mouse-accessibility of commands [33%] :ux:
* STARTED discoverability and mouse-accessibility of commands [33%] :ux:
<2021-06-15T16:18:49+0530>
Goals
1. discoverability of commands
@ -560,7 +563,7 @@ Strategies
* Perhaps I needn't worry too much. =menu-bar-mode= is enabled by default, and it makes #1 and #2 easy. I think a significant amount of the userbase disables =menu-bar-mode=, but they also have things like =counsel-M-x=, =describe-=.
+ The menu does not make the behavior of the numeric argument discoverable. Doesn't make sense to put it there, either.
* query-editing the file buffer :feature:
* query-editing the file buffer :feature:
<2021-06-16T07:50:21+0530>
=chronometrist-loop-file= can be used to run queries against user data. It would be cool to be able to edit the file directly from the query results.
@ -569,7 +572,7 @@ Strategies
2. The result data may be something which corresponds to the input data, in which case we could jump to the corresponding plist.
3. The result data may be impossible to trace back to the input data (e.g. a sum of intervals from many plists), in which case we cannot provide direct editing.
* error in change type detection :core:bug:
* error in change type detection :core:bug:
<2021-06-16T18:40:18+0530>
Steps
1. Clock in
@ -577,14 +580,14 @@ Steps
3. Clock in to different task. Error.
Might have to do with there being 2 empty lines between the last-but-one plist and the new last plist.
* spark monthly view :spark:feature:
* spark monthly view :spark:feature:
:PROPERTIES:
:feature: frontend
:END:
<2021-06-17T00:08:08+0530>
Frontend to show one or more months for a specific task, calendar-style, with each day being a single sparkline block.
* Task-specific detailed view :feature:
* Task-specific detailed view :feature:
:PROPERTIES:
:feature: frontend
:END:

View File

@ -102,5 +102,89 @@ SCHEMA should be a vector as specified by `tabulated-list-format'."
;; when being enabled/disabled, `chronometrist-spark-minor-mode' will already be t/nil here
(if chronometrist-spark-minor-mode (chronometrist-spark-setup) (chronometrist-spark-teardown)))
(defun chronometrist-task-durations-month (task iso-year-month)
(let* ((start-ts (chronometrist-iso-date-to-ts (concat iso-year-month "-01")))
;; last date of month
(stop-ts (ts-adjust 'month 1 'day -1 start-ts)))
(cl-loop with current = start-ts
while (not (ts> current stop-ts))
collect (chronometrist-task-time-one-day task current)
do (setq current (ts-adjust 'day 1 current)))))
(defgroup chronometrist-task-graph nil
"Task-specific monthly/weekly graph for the `chronometrist' time tracker."
:group 'chronometrist)
(defcustom chronometrist-task-graph-buffer-name "*chronometrist-task-graph*"
"Name of buffer created by `chronometrist-task-graph'."
:type 'string)
(defcustom chronometrist-task-graph-default-unit :month
"Default unit for `chronometrist-task-graph'.
Values may be either `:month' or `:week'."
:type '(radio (const :tag "Month" :month) (const :tag "Week" :week)))
(defcustom chronometrist-task-graph-schema
[("#" 3 (lambda (row-1 row-2) (< (car row-1) (car row-2))))
("Month" 20 t)
("Graph" 31 t)]
"Vector specifying format of `chronometrist-task-graph' buffer.
See `tabulated-list-format'."
:type '(vector))
(defvar chronometrist-task-graph-schema-transformers nil
"List of functions to transform `chronometrist-task-graph-schema' (which see).
This is passed to `chronometrist-run-transformers', which see.
Extensions adding to this list to increase the number of columns
will also need to modify the value of `tabulated-list-entries' by
using `chronometrist-task-graph-row-transformers'.")
(defvar chronometrist-task-graph-row-transformers nil
"List of functions to transform each row of `chronometrist-task-graph-rows'.
This is passed to `chronometrist-run-transformers', which see.
Extensions adding to this list to increase the number of columns
will also need to modify the value of `tabulated-list-format' by
using `chronometrist-task-graph-schema-transformers'.")
(defun chronometrist-task-graph-rows ()
"Return rows to be displayed in the `chronometrist-task-graph' buffer.
Return value is a list as specified by `tabulated-list-entries'."
(cl-loop with index = 1
for plist in (gethash (chronometrist-events-last-date) chronometrist-events) collect
(-let* (((&plist :name name :tags tags :start start :stop stop) plist)
)
(--> (vconcat (vector index-string name)
(when chronometrist-task-graph-display-tags (vector tags))
(when chronometrist-task-graph-display-key-values (vector key-values))
(vector duration timespan))
(list index it)
(chronometrist-run-transformers chronometrist-task-graph-row-transformers it)))
do (cl-incf index)))
(define-derived-mode chronometrist-task-graph-mode tabulated-list-mode "Task Graph"
"Major mode for `chronometrist-task-graph'."
(make-local-variable 'tabulated-list-format)
(--> (chronometrist-run-transformers chronometrist-task-graph-schema-transformers
chronometrist-task-graph-schema)
(setq tabulated-list-format it))
(make-local-variable 'tabulated-list-entries)
(setq tabulated-list-entries #'chronometrist-task-graph-rows)
(make-local-variable 'tabulated-list-sort-key)
(tabulated-list-init-header)
(run-hooks 'chronometrist-task-graph-mode-hook))
(defun chronometrist-task-graph ()
(interactive)
(let ((buffer (get-buffer-create chronometrist-task-graph-buffer-name))
(window (save-excursion
(get-buffer-window chronometrist-task-graph-buffer-name t))))
(cond (window (kill-buffer chronometrist-task-graph-buffer-name))
(t (with-current-buffer buffer
(switch-to-buffer buffer)
(chronometrist-task-graph-mode)
(tabulated-list-print))))))
(provide 'chronometrist-spark)
;;; chronometrist-spark.el ends here

View File

@ -38,28 +38,28 @@
(require 'spark)
#+END_SRC
* Code
** custom group :custom:group:
** custom group :custom:group:
#+BEGIN_SRC emacs-lisp
(defgroup chronometrist-spark nil
"Show sparklines in `chronometrist'."
:group 'applications)
#+END_SRC
** length :custom:variable:
** length :custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-spark-length 7
"Length of each sparkline in number of days."
:type 'integer)
#+END_SRC
** show-range :custom:variable:
** show-range :custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-spark-show-range t
"If non-nil, display range of each sparkline."
:type 'boolean)
#+END_SRC
** range :function:
** range :function:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-spark-range (durations)
"Return range for DURATIONS as a string.
@ -86,7 +86,7 @@ DURATIONS must be a list of integer seconds."
(should (equal (chronometrist-spark-range '(60 0 0)) "(1m)"))
(should (equal (chronometrist-spark-range '(60 0 120)) "(1m~2m)")))
#+END_SRC
** TODO row-transformer :function:
** TODO row-transformer :function:
if larger than 7
add space after (% length 7)th element
then add space after every 7 elements
@ -121,7 +121,7 @@ ROW must be a valid element of the list specified by
#+END_SRC
** TODO schema-transformer :function:
** TODO schema-transformer :function:
calculate length while accounting for space
#+BEGIN_SRC emacs-lisp
@ -136,7 +136,7 @@ SCHEMA should be a vector as specified by `tabulated-list-format'."
t)]))
#+END_SRC
** setup :writer:
** setup :writer:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-spark-setup ()
"Add `chronometrist-sparkline' functions to `chronometrist' hooks."
@ -144,7 +144,7 @@ SCHEMA should be a vector as specified by `tabulated-list-format'."
(add-to-list 'chronometrist-schema-transformers #'chronometrist-spark-schema-transformer))
#+END_SRC
** teardown :writer:
** teardown :writer:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-spark-teardown ()
"Remove `chronometrist-sparkline' functions from `chronometrist' hooks."
@ -154,7 +154,7 @@ SCHEMA should be a vector as specified by `tabulated-list-format'."
(remove #'chronometrist-spark-schema-transformer chronometrist-schema-transformers)))
#+END_SRC
** minor-mode :minor:mode:
** minor-mode :minor:mode:
#+BEGIN_SRC emacs-lisp
(define-minor-mode chronometrist-spark-minor-mode
nil nil nil nil
@ -162,6 +162,136 @@ SCHEMA should be a vector as specified by `tabulated-list-format'."
(if chronometrist-spark-minor-mode (chronometrist-spark-setup) (chronometrist-spark-teardown)))
#+END_SRC
** task graph view
*** prompt
*** task-durations-month
#+BEGIN_SRC emacs-lisp
(defun chronometrist-task-durations-month (task iso-year-month)
(let* ((start-ts (chronometrist-iso-date-to-ts (concat iso-year-month "-01")))
;; last date of month
(stop-ts (ts-adjust 'month 1 'day -1 start-ts)))
(cl-loop with current = start-ts
while (not (ts> current stop-ts))
collect (chronometrist-task-time-one-day task current)
do (setq current (ts-adjust 'day 1 current)))))
#+END_SRC
**** tests
#+BEGIN_SRC emacs-lisp :tangle chronometrist-spark-tests.el :load test
(ert-deftest chronometrist-task-durations-month ()
(should
(--every-p #'integerp (chronometrist-task-durations-month "Anything" "2021-01"))))
#+END_SRC
*** task-graph :custom:group:
#+BEGIN_SRC emacs-lisp
(defgroup chronometrist-task-graph nil
"Task-specific monthly/weekly graph for the `chronometrist' time tracker."
:group 'chronometrist)
#+END_SRC
*** buffer-name :custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-task-graph-buffer-name "*chronometrist-task-graph*"
"Name of buffer created by `chronometrist-task-graph'."
:type 'string)
#+END_SRC
*** default-unit :custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-task-graph-default-unit :month
"Default unit for `chronometrist-task-graph'.
Values may be either `:month' or `:week'."
:type '(radio (const :tag "Month" :month) (const :tag "Week" :week)))
#+END_SRC
*** schema :custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-task-graph-schema
[("#" 3 (lambda (row-1 row-2) (< (car row-1) (car row-2))))
("Month" 20 t)
("Graph" 31 t)]
"Vector specifying format of `chronometrist-task-graph' buffer.
See `tabulated-list-format'."
:type '(vector))
#+END_SRC
*** schema-transformers :extension:variable:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-task-graph-schema-transformers nil
"List of functions to transform `chronometrist-task-graph-schema' (which see).
This is passed to `chronometrist-run-transformers', which see.
Extensions adding to this list to increase the number of columns
will also need to modify the value of `tabulated-list-entries' by
using `chronometrist-task-graph-row-transformers'.")
#+END_SRC
*** row-transformers :extension:variable:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-task-graph-row-transformers nil
"List of functions to transform each row of `chronometrist-task-graph-rows'.
This is passed to `chronometrist-run-transformers', which see.
Extensions adding to this list to increase the number of columns
will also need to modify the value of `tabulated-list-format' by
using `chronometrist-task-graph-schema-transformers'.")
#+END_SRC
*** months :variable:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-task-graph-months
'("January" "February" "March" "April" "May" "June"
"July" "August" "September" "October" "November" "December"))
#+END_SRC
*** rows :function:
1. check default unit
2.
#+BEGIN_SRC emacs-lisp
(defun chronometrist-task-graph-rows ()
"Return rows to be displayed in the `chronometrist-task-graph' buffer.
Return value is a list as specified by `tabulated-list-entries'."
(cl-loop with index = 1
for month in chronometrist-task-graph-months collect
(-let* (((&plist :name name :tags tags :start start :stop stop) plist)
)
(--> (vconcat (vector index-string name)
(when chronometrist-task-graph-display-tags (vector tags))
(when chronometrist-task-graph-display-key-values (vector key-values))
(vector duration timespan))
(list index it)
(chronometrist-run-transformers chronometrist-task-graph-row-transformers it)))
do (cl-incf index)))
#+END_SRC
*** chronometrist-task-graph-mode :major:mode:
#+BEGIN_SRC emacs-lisp
(define-derived-mode chronometrist-task-graph-mode tabulated-list-mode "Task Graph"
"Major mode for `chronometrist-task-graph'."
(make-local-variable 'tabulated-list-format)
(--> (chronometrist-run-transformers chronometrist-task-graph-schema-transformers
chronometrist-task-graph-schema)
(setq tabulated-list-format it))
(make-local-variable 'tabulated-list-entries)
(setq tabulated-list-entries #'chronometrist-task-graph-rows)
(make-local-variable 'tabulated-list-sort-key)
(tabulated-list-init-header)
(run-hooks 'chronometrist-task-graph-mode-hook))
#+END_SRC
*** chronometrist-task-graph :command:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-task-graph ()
(interactive)
(let ((buffer (get-buffer-create chronometrist-task-graph-buffer-name))
(window (save-excursion
(get-buffer-window chronometrist-task-graph-buffer-name t))))
(cond (window (kill-buffer chronometrist-task-graph-buffer-name))
(t (with-current-buffer buffer
(switch-to-buffer buffer)
(chronometrist-task-graph-mode)
(tabulated-list-print))))))
#+END_SRC
* Provide
#+BEGIN_SRC emacs-lisp
(provide 'chronometrist-spark)

View File

@ -2087,7 +2087,7 @@ Return value is a list as specified by `tabulated-list-entries'."
(setq tabulated-list-entries #'chronometrist-details-rows)
(make-local-variable 'tabulated-list-sort-key)
(tabulated-list-init-header)
(run-hooks 'chronometrist-mode-hook))
(run-hooks 'chronometrist-details-mode-hook))
(defun chronometrist-details ()
(interactive)

View File

@ -976,7 +976,7 @@ Boilerplate for updating state between file operations in tests.
(list :last (chronometrist-file-hash :before-last nil)
:rest (chronometrist-file-hash nil :before-last t)))))
#+END_SRC
*** chronometrist-file :custom:variable:
*** chronometrist-file :custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-file
(locate-user-emacs-file "chronometrist.sexp")
@ -1510,7 +1510,7 @@ The data from =chronometrist-events= is used by most (all?) interval-consuming f
(setq chronometrist--file-state nil)
(chronometrist-refresh))
#+END_SRC
*** chronometrist-events :variable:
*** chronometrist-events :variable:
:PROPERTIES:
:VALUE: hash table
:END:
@ -2099,7 +2099,7 @@ Return the value returned by Fₙ."
See `tabulated-list-format'."
:type '(vector))
#+END_SRC
**** chronometrist-mode-hook :hook:normal:
**** chronometrist-mode-hook :hook:normal:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-mode-hook nil
"Normal hook run at the very end of `chronometrist-mode'.")
@ -2314,7 +2314,7 @@ refresh the `chronometrist' buffer."
(chronometrist-out))
t))
#+END_SRC
**** chronometrist-in :command:
**** chronometrist-in :command:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-in (task &optional _prefix)
"Clock in to TASK; record current time in `chronometrist-file'.
@ -2324,7 +2324,7 @@ TASK is the name of the task, a string. PREFIX is ignored."
(chronometrist-sexp-new plist)
(chronometrist-refresh)))
#+END_SRC
**** chronometrist-out :command:
**** chronometrist-out :command:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-out (&optional _prefix)
"Record current moment as stop time to last s-exp in `chronometrist-file'.
@ -2778,7 +2778,7 @@ Argument _FS-EVENT is ignored."
'(change)
#'chronometrist-refresh-file))))
#+END_SRC
**** chronometrist-report :command:
**** chronometrist-report :command:
#+BEGIN_SRC emacs-lisp
;;;###autoload
(defun chronometrist-report (&optional keep-date)
@ -3307,7 +3307,7 @@ Return value is a list as specified by `tabulated-list-entries'."
(setq tabulated-list-entries #'chronometrist-details-rows)
(make-local-variable 'tabulated-list-sort-key)
(tabulated-list-init-header)
(run-hooks 'chronometrist-mode-hook))
(run-hooks 'chronometrist-details-mode-hook))
#+END_SRC
**** chronometrist-details :command: