feat(details): implement custom ranges
This commit is contained in:
parent
9f8e455e01
commit
c4b94be941
|
@ -869,7 +869,8 @@ unchanged."
|
||||||
|
|
||||||
(defun chronometrist-iso-timestamp-to-ts (timestamp)
|
(defun chronometrist-iso-timestamp-to-ts (timestamp)
|
||||||
"Convert TIMESTAMP to a TS struct. (see `ts.el')
|
"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)
|
(-let [(second minute hour day month year dow _dst utcoff)
|
||||||
(decode-time
|
(decode-time
|
||||||
(parse-iso8601-time-string timestamp))]
|
(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 rows to be displayed in the `chronometrist-details' buffer.
|
||||||
Return value is a list as specified by `tabulated-list-entries'."
|
Return value is a list as specified by `tabulated-list-entries'."
|
||||||
(cl-loop with index = 1
|
(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)
|
(-let* (((&plist :name name :tags tags :start start :stop stop) plist)
|
||||||
;; whether tags or key-values are actually displayed is handled later
|
;; whether tags or key-values are actually displayed is handled later
|
||||||
(tags (chronometrist-details-rows-helper tags))
|
(tags (chronometrist-details-rows-helper tags))
|
||||||
|
@ -2110,6 +2112,35 @@ ISO date or date-time strings - display intervals in this range.
|
||||||
Dates are inclusive.")
|
Dates are inclusive.")
|
||||||
(make-variable-buffer-local 'chronometrist-details-range)
|
(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 ()
|
(defvar chronometrist-details-set-range ()
|
||||||
"Prompt user for range for current `chronometrist-details' buffer.")
|
"Prompt user for range for current `chronometrist-details' buffer.")
|
||||||
|
|
||||||
|
|
|
@ -976,7 +976,7 @@ Boilerplate for updating state between file operations in tests.
|
||||||
(list :last (chronometrist-file-hash :before-last nil)
|
(list :last (chronometrist-file-hash :before-last nil)
|
||||||
:rest (chronometrist-file-hash nil :before-last t)))))
|
:rest (chronometrist-file-hash nil :before-last t)))))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
*** chronometrist-file :custom:variable:
|
*** chronometrist-file :custom:variable:
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defcustom chronometrist-file
|
(defcustom chronometrist-file
|
||||||
(locate-user-emacs-file "chronometrist.sexp")
|
(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)
|
(setq chronometrist--file-state nil)
|
||||||
(chronometrist-refresh))
|
(chronometrist-refresh))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
*** chronometrist-events :variable:
|
*** chronometrist-events :variable:
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:VALUE: hash table
|
:VALUE: hash table
|
||||||
:END:
|
:END:
|
||||||
|
@ -1635,6 +1635,7 @@ If REPLACE is non-nil, replace the last event with PLIST."
|
||||||
*** events-subset :reader:
|
*** events-subset :reader:
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:VALUE: hash table
|
:VALUE: hash table
|
||||||
|
:CUSTOM_ID: program-data-structures-events-subset
|
||||||
:END:
|
:END:
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun chronometrist-events-subset (start end)
|
(defun chronometrist-events-subset (start end)
|
||||||
|
@ -1764,7 +1765,8 @@ unchanged."
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun chronometrist-iso-timestamp-to-ts (timestamp)
|
(defun chronometrist-iso-timestamp-to-ts (timestamp)
|
||||||
"Convert TIMESTAMP to a TS struct. (see `ts.el')
|
"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)
|
(-let [(second minute hour day month year dow _dst utcoff)
|
||||||
(decode-time
|
(decode-time
|
||||||
(parse-iso8601-time-string timestamp))]
|
(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
|
(make-ts :hour hour :minute minute :second second
|
||||||
:day day :month month :year year
|
:day day :month month :year year
|
||||||
:dow dow :tz-offset utcoff))))
|
: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
|
#+END_SRC
|
||||||
*** iso-date-to-ts :function:
|
*** iso-date-to-ts :function:
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
@ -2099,7 +2113,7 @@ Return the value returned by Fₙ."
|
||||||
See `tabulated-list-format'."
|
See `tabulated-list-format'."
|
||||||
:type '(vector))
|
:type '(vector))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
**** chronometrist-mode-hook :hook:normal:
|
**** chronometrist-mode-hook :hook:normal:
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defvar chronometrist-mode-hook nil
|
(defvar chronometrist-mode-hook nil
|
||||||
"Normal hook run at the very end of `chronometrist-mode'.")
|
"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 rows to be displayed in the `chronometrist-details' buffer.
|
||||||
Return value is a list as specified by `tabulated-list-entries'."
|
Return value is a list as specified by `tabulated-list-entries'."
|
||||||
(cl-loop with index = 1
|
(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)
|
(-let* (((&plist :name name :tags tags :start start :stop stop) plist)
|
||||||
;; whether tags or key-values are actually displayed is handled later
|
;; whether tags or key-values are actually displayed is handled later
|
||||||
(tags (chronometrist-details-rows-helper tags))
|
(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)))
|
do (cl-incf index)))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
**** chronometrist-details-mode :major:mode:
|
**** chronometrist-details-mode :major:mode:
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(define-derived-mode chronometrist-details-mode tabulated-list-mode "Details"
|
(define-derived-mode chronometrist-details-mode tabulated-list-mode "Details"
|
||||||
"Major mode for `chronometrist-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))
|
(run-hooks 'chronometrist-mode-hook))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
**** chronometrist-details :command:
|
**** chronometrist-details :command:
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun chronometrist-details ()
|
(defun chronometrist-details ()
|
||||||
(interactive)
|
(interactive)
|
||||||
|
@ -3336,6 +3351,43 @@ ISO date or date-time strings - display intervals in this range.
|
||||||
Dates are inclusive.")
|
Dates are inclusive.")
|
||||||
(make-variable-buffer-local 'chronometrist-details-range)
|
(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
|
#+END_SRC
|
||||||
**** set-range :command:
|
**** set-range :command:
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
|
Reference in New Issue