Merge branch 'dev'

This commit is contained in:
contrapunctus 2021-01-19 13:21:25 +05:30
commit 125d0ed289
10 changed files with 57 additions and 50 deletions

10
Cask
View File

@ -7,11 +7,11 @@
"A time tracker for Emacs with a nice interface")
(depends-on "cl")
(depends-on "dash" "2.16.0")
(depends-on "seq" "2.20")
(depends-on "s" "1.12.0")
(depends-on "ts" "0.2")
(depends-on "anaphora" "1.0.4")
(depends-on "dash" "2.16.0")
(depends-on "seq" "2.20")
(depends-on "s" "1.12.0")
(depends-on "ts" "0.2")
(depends-on "anaphora" "1.0.4")
(files "elisp/*.el")

View File

@ -114,7 +114,7 @@ Some options and ideas -
* Per-task history generation will create problems - e.g. values for a given key for one task won't be suggested for values for the same key in another 🤦
+ Tags and keys are already task-sensitive; just don't make values task-sensitive.
2. [X] Compare partial hashes of file to know what has changed - only update memory when necessary.
3. [ ] In-memory cache - don't store entire file into memory; instead, split midnight-spanning events just for the requested data.
3. [ ] In-memory cache - don't store entire file into memory; instead, split midnight-spanning intervals just for the requested data.
* Will increase load time for the first time =chronometrist=, =chronometrist-report=, or =chronometrist-statistics= are run (including forward/backward commands in the latter two)
+ Can try pre-emptively loading data for the latter two
* Will reduce memory used by =chronometrist-events=.
@ -128,21 +128,21 @@ Some options and ideas -
* Will greatly impede human editing of the file, too. 🤔
+ An editing UI could help - pretty sure every timestamp edit I've ever made has been for the last interval, or at most an interval in today's data.
** Cache
+ Lessons from the parsimonious-reading branch - iterating =read= over the whole file is fast; splitting the events is not.
+ Lessons from the parsimonious-reading branch - iterating =read= over the whole file is fast; splitting the intervals is not.
+ Things we need to read the whole file for - task list, tag/key/value history.
+ Fill =chronometrist-events= only as much as the buffer needing split events requires. e.g. for =chronometrist=, just a day; for =chronometrist-report=, a week; etc.
+ Anything requiring split events will first look in =chronometrist-events=, and if not found, will read from the file and update =chronometrist-events=.
+ Fill =chronometrist-events= only as much as the buffer needing split intervals requires. e.g. for =chronometrist=, just a day; for =chronometrist-report=, a week; etc.
+ Anything requiring split intervals will first look in =chronometrist-events=, and if not found, will read from the file and update =chronometrist-events=.
+ When the file changes, use the file byte length and hash strategy described below to know whether to keep the cache.
+ Save cache to a file, so that event splitting is avoided by reading from that.
** Ideas to make -refresh-file faster
1. Support multiple files, so we read and process lesser data when one of them changes.
2. Make file writing async
3. Don't refresh from file when clocking in.
4. Only write to the file when Emacs is idle or being killed, and store data in memory (in the events hash table) in the meantime
4. Only write to the file when Emacs is idle or being killed, and store data in memory (in the intervals hash table) in the meantime
5. What if commands both write to the file /and/ add to the hash table, so we don't have to re-read the file and re-populate the table for commands? The expensive reading+parsing could be avoided for commands, and only take place for the user changing the file.
* [X] jonasw - store length and hash of previous file, see if the new file has the same hash until old-length bytes.
* Rather than storing and hashing the full length, we could do it until (before) the last s-expression (or last N s-expressions?). That way, we know if the last expression (or last N expressions) have changed.
* Or even the first expression of the current date. That way, we just re-read the events for today. Because chronometrist-events uses dates as keys, it's easy to work on the basis of dates.
* Or even the first expression of the current date. That way, we just re-read the intervals for today. Because chronometrist-events uses dates as keys, it's easy to work on the basis of dates.
6. [ ] Don't generate tag/keyword/value history from the entire log, just from the last N days (where N is user-customizable).
7. [ ] Just why are we reading the whole file? ~chronometrist~ should not read more than a day; ~chronometrist-report~ should not read more than a week at a time, and so on. Make a branch which works on this logic, see if it is faster.
@ -193,7 +193,7 @@ ppp.el doesn't align plist values along the same column. It's also GPL, and I'm
:start "2020-12-22T23:01:00+0530"
:stop "2020-12-23T00:54:52+0530")
#+END_SRC
...is displayed as 1:53:52 after clocking out. :\
...is displayed as 1:53:52 (rather than 00:54:52) after clocking out. :\
** chronometrist [8%]
1. [ ] Add =:stop= time when we call =chronometrist-kv-accept=, not when we quit the key-value prompt.
@ -219,7 +219,7 @@ ppp.el doesn't align plist values along the same column. It's also GPL, and I'm
=chronometrist-kv-add= could quit nonlocally when the user enters a blank input (or hits C-g? Maybe by using =unwind-protect=?), cancelling the clock in/out, and thereby letting =chronometrist-kv-accept= resume clock in/out. (It can determine whether to clock in or out using =chronometrist-current-task=)
2. [X] Enhanced tag/key-value prompt - before asking for tags/key-values, if the last occurence of task had tags/key-values, ask if they should be reused. y - yes, n - no (continue to usual prompts).
* [X] Show what those tags/key-values are, so the user knows what will be added.
3. [ ] Expandable items - show intervals for task today
3. [ ] "Explain" command - show intervals for task today
* [ ] Switch between intervals and tag-combination breakdown
4. [ ] Magit/other VCS integration
* [ ] Add support for using key-values to point to a commit (commit hash + repo path?)
@ -236,7 +236,7 @@ ppp.el doesn't align plist values along the same column. It's also GPL, and I'm
2. [ ] Highlight column of current day
3. [ ] Command to narrow report to specific project(s)
4. [ ] Jump to beginning/end of data (keys B/F)
5. [ ] Expandable items - show tag-combination-based breakdown
5. [ ] "Explain" command - show tag-combination-based breakdown
** Code quality [25%]
1. [ ] Remove duplication between =chronometrist-toggle-task= and =chronometrist-toggle-task-button=
@ -274,7 +274,7 @@ ppp.el doesn't align plist values along the same column. It's also GPL, and I'm
: file-notify-add-watch ((describe-function 'file-notify-add-watch))
...yuck :\
* Currently using file: links with text search - =[file:../elisp/file.el::defun identifier (]=, =[file:../elisp/file.el::defvar identifier (]=, etc.
4. [ ] Fix heading link to "midnight-spanning events" - jumps to the correct heading in HTML export, but jumps to its own self in Org mode.
4. [ ] Fix heading link to "midnight-spanning intervals" - jumps to the correct heading in HTML export, but jumps to its own self in Org mode.
5. [ ] Figure out some way to hide package prefixes in identifiers in Org mode (without actually affecting the contents, a la nameless-mode)
** UX [30%]
@ -317,7 +317,10 @@ ppp.el doesn't align plist values along the same column. It's also GPL, and I'm
2. a list, with the first element being the task name as a string, followed by keyword-value pairs
Keywords can be
1. =:tag-prompt=, =:key-prompt= - values can be nil, t (the default), or a function. If nil, don't ask for tags/keys for this task. If t, ask for tags/keys for this task using =chronometrist-tags-add=/=chronometrist-key-add=. If it's a function, use that as the prompt.
1. +=:tag-prompt=, =:key-prompt= - values can be nil, t (the default), or a function. If nil, don't ask for tags/keys for this task. If t, ask for tags/keys for this task using =chronometrist-tags-add=/=chronometrist-key-add=. If it's a function, use that as the prompt.+
* Tags and key-values are optional extensions; we don't want Chronometrist to know about them.
+ Well, even with this style of configuration, Chronometrist doesn't necessarily have to...it could use the fields it knows about, ignoring the rest; the extensions could check for the fields they know about.
* Instead of setting the prompt function, set hooks (=chronometrist-before-in-functions=/=chronometrist-after-in-functions=/=chronometrist-before-out-functions=/=chronometrist-after-out-functions=) per-task. This is preferable, because if you define a custom prompt function, you probably also want to remove certain functions coming earlier in the hook, such as =chronometrist-skip-query-prompt=, for that task.
2. =:hide= - values can be nil (the default) or t - if t, hide this task from being displayed in =chronometrist=/=chronometrist-report=/=chronometrist-statistics= buffers. (effectively a non-destructive deletion of all intervals of the task)
...

View File

@ -727,8 +727,6 @@ Function - chronometrist-events-maybe-split (event)
@item
Function - chronometrist-events-populate ()
@item
Function - chronometrist-tasks-from-table ()
@item
Function - chronometrist-events-add (plist)
@item
Function - chronometrist-events-replace-last (plist)
@ -791,7 +789,7 @@ Function - chronometrist-task-time-one-day (task &optional (ts (ts-now)))
String &optional ts -> seconds
@end itemize
@item
Function - chronometrist-active-time-one-day (&optional ts)
Function - chronometrist-active-time-one-day (&optional (ts (ts-now)))
@itemize
@item
&optional ts -> seconds
@ -799,7 +797,7 @@ Function - chronometrist-active-time-one-day (&optional ts)
@item
Function - chronometrist-statistics-count-active-days (task &optional (table chronometrist-events))
@item
Function - chronometrist-task-events-in-day (task ts)
Function - chronometrist-task-events-in-day (task &optional (ts (ts-now)))
@end enumerate
@node chronometrist-report-customel

View File

@ -293,19 +293,18 @@ Each of these has a corresponding function to clear it and fill it with values -
38. Command - chronometrist (&optional arg)
** chronometrist-events.el
1. Variable - chronometrist-events
* keys - iso-date
2. Function - chronometrist-day-start (timestamp)
* iso-timestamp -> encode-time
3. Function - chronometrist-file-clean ()
* commented out, unused
4. Function - chronometrist-events-maybe-split (event)
5. Function - chronometrist-events-populate ()
6. Function - chronometrist-tasks-from-table ()
7. Function - chronometrist-events-add (plist)
8. Function - chronometrist-events-replace-last (plist)
9. Function - chronometrist-events-subset (start end)
* ts ts -> hash-table
1. Variable - chronometrist-events
* keys - iso-date
2. Function - chronometrist-day-start (timestamp)
* iso-timestamp -> encode-time
3. Function - chronometrist-file-clean ()
* commented out, unused
4. Function - chronometrist-events-maybe-split (event)
5. Function - chronometrist-events-populate ()
6. Function - chronometrist-events-add (plist)
7. Function - chronometrist-events-replace-last (plist)
8. Function - chronometrist-events-subset (start end)
* ts ts -> hash-table
** chronometrist-migrate.el
1. Variable - chronometrist-migrate-table
@ -327,10 +326,10 @@ Each of these has a corresponding function to clear it and fill it with values -
* -> plist
2. Function - chronometrist-task-time-one-day (task &optional (ts (ts-now)))
* String &optional ts -> seconds
3. Function - chronometrist-active-time-one-day (&optional ts)
3. Function - chronometrist-active-time-one-day (&optional (ts (ts-now)))
* &optional ts -> seconds
4. Function - chronometrist-statistics-count-active-days (task &optional (table chronometrist-events))
5. Function - chronometrist-task-events-in-day (task ts)
5. Function - chronometrist-task-events-in-day (task &optional (ts (ts-now)))
** chronometrist-report-custom.el
1. Custom variable - chronometrist-report-buffer-name

View File

@ -41,7 +41,7 @@ This is distinct from `chronometrist-time-re-file' (which see) -
must correspond to the output from `chronometrist-format-time'.")
(defvar chronometrist-task-list nil
"List of tasks in `chronometrist-file', as returned by `chronometrist-tasks-from-table'.")
"List of tasks in `chronometrist-file'.")
(defvar chronometrist--fs-watch nil
"Filesystem watch object.

View File

@ -127,14 +127,6 @@ were none."
(clrhash chronometrist-events)
(chronometrist-sexp-events-populate))
(defun chronometrist-tasks-from-table ()
"Return a list of task names from `chronometrist-events'."
(cl-loop for plists being the hash-values of chronometrist-events append
(cl-loop for plist in plists collect (plist-get plist :name)) into list
finally
(cl-return
(cl-remove-duplicates (sort list #'string-lessp) :test #'equal))))
(defun chronometrist-events-add (plist)
"Add new PLIST at the end of `chronometrist-events'."
(let* ((date-today (format-time-string "%Y-%m-%d"))

View File

@ -40,6 +40,14 @@ considers it an alist."
(when (listp list)
(cl-loop for elt in list thereis (chronometrist-plist-pp-pair-p elt))))
(defun chronometrist-plist-pp-plist-p (list)
(while (consp list)
(setq list (if (and (keywordp (car list))
(consp (cdr list)))
(cddr list)
'not-plist)))
(null list))
(defun chronometrist-plist-pp-longest-keyword-length ()
"Find the length of the longest keyword in a plist.
This assumes there is a single plist in the current buffer, and
@ -62,7 +70,7 @@ The list must be on a single line, as emitted by `prin1'."
(progn
(setq sexp (save-excursion (read (current-buffer))))
(cond
((json-plist-p sexp)
((chronometrist-plist-pp-plist-p sexp)
(chronometrist-plist-pp-buffer-plist inside-sublist-p)
(chronometrist-plist-pp-buffer inside-sublist-p))
((chronometrist-plist-pp-alist-p sexp)
@ -104,7 +112,7 @@ The list must be on a single line, as emitted by `prin1'."
(make-string left-indent ?\ ))
(chronometrist-plist-pp-indent-sexp sexp right-indent)))
;; not a keyword = a value
((json-plist-p sexp)
((chronometrist-plist-pp-plist-p sexp)
(chronometrist-plist-pp-buffer-plist))
((and (listp sexp)
(not (chronometrist-plist-pp-pair-p sexp)))

View File

@ -40,7 +40,7 @@ The return value is seconds, as an integer."
;; no events for this task on TS, i.e. no time spent
0)))
(defun chronometrist-active-time-one-day (&optional ts)
(cl-defun chronometrist-active-time-one-day (&optional (ts (ts-now)))
"Return the total active time on TS (if non-nil) or today.
TS must be a ts struct (see `ts.el')
@ -65,7 +65,7 @@ which span midnights. (see `chronometrist-events-clean')"
table)
count))
(defun chronometrist-task-events-in-day (task ts)
(cl-defun chronometrist-task-events-in-day (task &optional (ts (ts-now)))
"Get events for TASK on TS.
TS should be a ts struct (see `ts.el').

View File

@ -23,7 +23,9 @@ STREAM (which is the value of `current-buffer')."
(defmacro chronometrist-loop-file (for expr in file &rest loop-clauses)
"`cl-loop' LOOP-CLAUSES over s-expressions in FILE.
VAR is bound to each s-expression."
(declare (indent defun))
(declare (indent defun)
;; FIXME
(debug ("for" form "in" form &rest &or sexp form)))
`(chronometrist-sexp-in-file ,file
(goto-char (point-max))
(cl-loop with ,expr

View File

@ -1,6 +1,11 @@
;; -*- lexical-binding: t; -*-
(require 'chronometrist-plist-pp)
(ert-deftest plist-p ()
(should (eq t (chronometrist-plist-pp-plist-p '(:a 1 :b 2))))
(should (eq nil (chronometrist-plist-pp-plist-p '(0 :a 1 :b 2))))
(should (eq nil (chronometrist-plist-pp-plist-p '(:a 1 :b 2 3)))))
(ert-deftest plist-pp-buffer ()
(should
(equal
@ -96,5 +101,5 @@
" :comment (\"stretching\" (25 10 \"push-ups\")))"))))
;; Local Variables:
;; nameless-current-name: "chronometrist"
;; nameless-current-name: "chronometrist-plist-pp"
;; End: