feat(details): implement custom ranges

This commit is contained in:
contrapunctus 2021-06-27 20:59:17 +05:30
parent 9f8e455e01
commit c4b94be941
2 changed files with 92 additions and 9 deletions

View File

@ -869,7 +869,8 @@ unchanged."
(defun chronometrist-iso-timestamp-to-ts (timestamp)
"Convert TIMESTAMP to a TS struct. (see `ts.el')
TIMESTAMP must be the ISO-8601 format, as handled by `parse-iso8601-time-string'."
TIMESTAMP must be an ISO-8601 timestamp, as handled by
`parse-iso8601-time-string'."
(-let [(second minute hour day month year dow _dst utcoff)
(decode-time
(parse-iso8601-time-string timestamp))]
@ -2051,7 +2052,8 @@ using `chronometrist-details-schema-transformers'.")
"Return rows to be displayed in the `chronometrist-details' 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
for plist in (chronometrist-details-intervals-for-range chronometrist-details-range chronometrist-events)
collect
(-let* (((&plist :name name :tags tags :start start :stop stop) plist)
;; whether tags or key-values are actually displayed is handled later
(tags (chronometrist-details-rows-helper tags))
@ -2110,6 +2112,35 @@ ISO date or date-time strings - display intervals in this range.
Dates are inclusive.")
(make-variable-buffer-local 'chronometrist-details-range)
(defun chronometrist-iso-date-p (string)
(string-match-p
(rx (and string-start
(>= 1 num) "-" (= 2 num) "-" (= 2 num)
string-end))
string))
(defun chronometrist-details-intervals-for-range (range table)
"Return intervals for RANGE from TABLE.
RANGE must be a time range as specified by `chronometrist-details-range'.
TABLE must be a hash table similar to `chronometrist-events'."
(pcase range
('nil
(gethash (format-time-string "%F") table))
((pred stringp)
(gethash range table))
(`(,begin . ,end)
(if (and (chronometrist-iso-date-p begin) (chronometrist-iso-date-p end))
(let ((begin-ts (chronometrist-iso-timestamp-to-ts begin))
(end-ts (chronometrist-iso-timestamp-to-ts end)))
(cl-loop while (not (ts> begin-ts end-ts))
append (gethash (ts-format "%F" begin-ts) table)
do (ts-adjustf begin-ts 'day 1)))))))
;; (chronometrist-details-intervals-for-range nil chronometrist-events)
;; (chronometrist-details-intervals-for-range "2021-06-01" chronometrist-events)
;; (chronometrist-details-intervals-for-range '("2021-06-01" . "2021-06-03") chronometrist-events)
(defvar chronometrist-details-set-range ()
"Prompt user for range for current `chronometrist-details' buffer.")

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:
@ -1635,6 +1635,7 @@ If REPLACE is non-nil, replace the last event with PLIST."
*** events-subset :reader:
:PROPERTIES:
:VALUE: hash table
:CUSTOM_ID: program-data-structures-events-subset
:END:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-events-subset (start end)
@ -1764,7 +1765,8 @@ unchanged."
#+BEGIN_SRC emacs-lisp
(defun chronometrist-iso-timestamp-to-ts (timestamp)
"Convert TIMESTAMP to a TS struct. (see `ts.el')
TIMESTAMP must be the ISO-8601 format, as handled by `parse-iso8601-time-string'."
TIMESTAMP must be an ISO-8601 timestamp, as handled by
`parse-iso8601-time-string'."
(-let [(second minute hour day month year dow _dst utcoff)
(decode-time
(parse-iso8601-time-string timestamp))]
@ -1772,6 +1774,18 @@ TIMESTAMP must be the ISO-8601 format, as handled by `parse-iso8601-time-string'
(make-ts :hour hour :minute minute :second second
:day day :month month :year year
:dow dow :tz-offset utcoff))))
#+END_SRC
**** tests
#+BEGIN_SRC emacs-lisp :tangle chronometrist-tests.el :load test
(ert-deftest chronometrist-iso-timestamp-to-ts ()
(should (ts= (chronometrist-iso-timestamp-to-ts "2021-01-01")
(make-ts :year 2021 :month 1 :day 1
:hour 0 :minute 0 :second 0)))
(should (ts= (chronometrist-iso-timestamp-to-ts "2021-01-01T01:01:01")
(make-ts :year 2021 :month 1 :day 1
:hour 1 :minute 1 :second 1))))
#+END_SRC
*** iso-date-to-ts :function:
#+BEGIN_SRC emacs-lisp
@ -2099,7 +2113,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'.")
@ -3268,7 +3282,8 @@ using `chronometrist-details-schema-transformers'.")
"Return rows to be displayed in the `chronometrist-details' 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
for plist in (chronometrist-details-intervals-for-range chronometrist-details-range chronometrist-events)
collect
(-let* (((&plist :name name :tags tags :start start :stop stop) plist)
;; whether tags or key-values are actually displayed is handled later
(tags (chronometrist-details-rows-helper tags))
@ -3296,7 +3311,7 @@ Return value is a list as specified by `tabulated-list-entries'."
do (cl-incf index)))
#+END_SRC
**** chronometrist-details-mode :major:mode:
**** chronometrist-details-mode :major:mode:
#+BEGIN_SRC emacs-lisp
(define-derived-mode chronometrist-details-mode tabulated-list-mode "Details"
"Major mode for `chronometrist-details'."
@ -3310,7 +3325,7 @@ Return value is a list as specified by `tabulated-list-entries'."
(run-hooks 'chronometrist-mode-hook))
#+END_SRC
**** chronometrist-details :command:
**** chronometrist-details :command:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-details ()
(interactive)
@ -3336,6 +3351,43 @@ ISO date or date-time strings - display intervals in this range.
Dates are inclusive.")
(make-variable-buffer-local 'chronometrist-details-range)
#+END_SRC
**** iso-date-p
#+BEGIN_SRC emacs-lisp
(defun chronometrist-iso-date-p (string)
(string-match-p
(rx (and string-start
(>= 1 num) "-" (= 2 num) "-" (= 2 num)
string-end))
string))
#+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.
#+BEGIN_SRC emacs-lisp
(defun chronometrist-details-intervals-for-range (range table)
"Return intervals for RANGE from TABLE.
RANGE must be a time range as specified by `chronometrist-details-range'.
TABLE must be a hash table similar to `chronometrist-events'."
(pcase range
('nil
(gethash (format-time-string "%F") table))
((pred stringp)
(gethash range table))
(`(,begin . ,end)
(if (and (chronometrist-iso-date-p begin) (chronometrist-iso-date-p end))
(let ((begin-ts (chronometrist-iso-timestamp-to-ts begin))
(end-ts (chronometrist-iso-timestamp-to-ts end)))
(cl-loop while (not (ts> begin-ts end-ts))
append (gethash (ts-format "%F" begin-ts) table)
do (ts-adjustf begin-ts 'day 1)))))))
;; (chronometrist-details-intervals-for-range nil chronometrist-events)
;; (chronometrist-details-intervals-for-range "2021-06-01" chronometrist-events)
;; (chronometrist-details-intervals-for-range '("2021-06-01" . "2021-06-03") chronometrist-events)
#+END_SRC
**** set-range :command:
#+BEGIN_SRC emacs-lisp