From c4b94be941e32df017b8acc640f26f4a6a615fc7 Mon Sep 17 00:00:00 2001 From: contrapunctus Date: Sun, 27 Jun 2021 20:59:17 +0530 Subject: [PATCH] feat(details): implement custom ranges --- elisp/chronometrist.el | 35 ++++++++++++++++++++-- elisp/chronometrist.org | 66 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 92 insertions(+), 9 deletions(-) diff --git a/elisp/chronometrist.el b/elisp/chronometrist.el index 40fe094..38d07be 100644 --- a/elisp/chronometrist.el +++ b/elisp/chronometrist.el @@ -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.") diff --git a/elisp/chronometrist.org b/elisp/chronometrist.org index 2ee0820..f6ff93d 100644 --- a/elisp/chronometrist.org +++ b/elisp/chronometrist.org @@ -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