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.

539 Commits

Author SHA1 Message Date
contrapunctus f7b2defcee Update CL port manual 2022-05-03 10:45:18 +05:30
contrapunctus be32efd73b Update custom IDs 2022-05-03 10:45:10 +05:30
contrapunctus 339d5da1e7 Display durations using format-seconds 2022-05-03 10:43:50 +05:30
contrapunctus db965525b0 Extend CLIM frontend documentation 2022-04-27 21:12:27 +05:30
contrapunctus e2d32232df CLIM frontend: Write some documentation 2022-04-27 16:43:22 +05:30
contrapunctus 4f2d986fe4 Add CUSTOM_IDs 2022-04-27 16:26:50 +05:30
contrapunctus e641139497 TODO: Add commands, rephrase effect of setting 2022-04-25 19:29:10 +05:30
contrapunctus c5d5ba419e CLIM: Specify table columns using classes 2022-04-23 08:19:56 +05:30
contrapunctus 5d93dd48bc CLIM: Display durations 2022-04-22 22:30:13 +05:30
contrapunctus e57f88a935 Make task-time-one-day work with interval objects 2022-04-21 11:03:06 +05:30
contrapunctus 3f8253daa4 Define default method for task-records-for-date 2022-04-21 09:52:53 +05:30
contrapunctus ecaf35f842 Correct type declarations 2022-04-21 09:38:27 +05:30
contrapunctus 900dbd9742 Have task-records-for-date return intervals 2022-04-21 09:37:22 +05:30
contrapunctus 07498b39c5 (WIP) Implement task-records-for-date (SQLite) 2022-04-20 12:08:09 +05:30
contrapunctus 44c29317ec Add task for concurrent intervals 2022-04-20 10:38:51 +05:30
contrapunctus c74d83908c Finish implementing get-day (SQLite) 2022-04-19 09:48:11 +05:30
contrapunctus 07609d4813 Rename slot activity -> name 2022-04-18 23:15:16 +05:30
contrapunctus cf50c5beaf Refactor get-day (SQLite) 2022-04-18 22:45:08 +05:30
contrapunctus 0021f92e29 Create interval objects from query 2022-04-18 20:09:13 +05:30
contrapunctus c9ab8b4f95 get-day (SQLite): write query for intervals 2022-04-18 19:19:25 +05:30
contrapunctus 94e7a78404 Rename `get` to `get-day`, implement SQLite method (WIP) 2022-04-18 03:54:51 +05:30
contrapunctus 998ea0f94e Define new classes, begin implementing new protocol 2022-04-17 13:45:31 +05:30
contrapunctus 8f14fbb546 Add cl-naive-store to backends roadmap 2022-04-16 21:38:26 +05:30
contrapunctus bb4b2121c9 Move Overview from LP to manual 2022-04-15 17:43:34 +05:30
contrapunctus dbc7be41d2 manual: Mention plan for Emacs Lisp codebase 2022-04-15 12:02:55 +05:30
contrapunctus a1c79d8faa manual: Mention LP benefits 2022-04-15 10:35:22 +05:30
contrapunctus d63bc11c8a manual: Reword Benefits section 2022-04-15 10:00:34 +05:30
contrapunctus dcca2632fb manual: Update backup location step 2022-04-15 09:54:51 +05:30
contrapunctus bbf5a2e048 manual: Move Contributions to Explanation 2022-04-15 09:46:35 +05:30
contrapunctus 73be9670b7 Mention literate-elisp-load on file open 2022-04-14 22:22:17 +05:30
contrapunctus 1bf960b6e0 TODO: Add tasks for tutorial/how-to revamp 2022-04-14 22:21:50 +05:30
contrapunctus ca20b3edda TODO: Add documentation restructuring tasks 2022-04-14 18:12:56 +05:30
contrapunctus 88aa54c082 Restore subtitle 2022-04-14 14:13:33 +05:30
contrapunctus f25c586232 Explain Common Lisp port 2022-04-14 14:08:35 +05:30
contrapunctus 30bde518a0 Update license explanation 2022-04-14 13:25:15 +05:30
contrapunctus ea704f8c00 Reorganize documentation, mention CLIM frontend 2022-04-14 13:15:19 +05:30
contrapunctus 8dbba77d12 Merge branch 'dev' into doc 2022-04-14 12:56:16 +05:30
contrapunctus 3a68a65156 TODO: add command, configuration 2022-04-14 12:40:26 +05:30
contrapunctus 1465f5da33 Fix errors about unknown types 2022-04-14 12:34:28 +05:30
contrapunctus 38e4997269 Add chronometrist: to method names 2022-04-13 10:48:24 +05:30
contrapunctus 0831ae82d5 Display tasks as presentation types 2022-04-11 22:42:47 +05:30
contrapunctus b9417d6a25 Rename views, list more commands 2022-04-11 17:54:01 +05:30
contrapunctus f3f96a8066 doc(TODO): Note more commands 2022-04-11 12:54:26 +05:30
contrapunctus b9ba7fe9b2 Make background black and foreground white 2022-04-10 23:29:20 +05:30
contrapunctus 90b68d6671 List commands available in views 2022-04-10 21:47:00 +05:30
contrapunctus 80f08aabdf Combine tabular views into more general graph view 2022-04-10 18:54:26 +05:30
contrapunctus 1a7ebecb2e Add missing views; rethink default view 2022-04-10 08:30:12 +05:30
contrapunctus 8d65dd8a13 Document the CL port 2022-04-08 21:34:10 +05:30
contrapunctus b70b5eac5c Make CLIM UI design notes 2022-04-08 20:05:25 +05:30
contrapunctus 0f0dab74bc Display index column 2022-04-08 11:41:50 +05:30
contrapunctus 162f005507 Do not :use the chronometrist package 2022-04-08 09:13:02 +05:30
contrapunctus 71d116480d CLIM UI - display task list 2022-04-08 09:13:02 +05:30
contrapunctus 53f650678b Implement list-tasks differently for SQLite backend 2022-04-07 18:01:34 +05:30
contrapunctus 9df00858f4 Use SxQL for table creation 2022-04-07 16:52:59 +05:30
contrapunctus 33ac4243f4 Replace clsql with cl-sqlite 2022-04-06 07:24:26 +05:30
contrapunctus e21c85e543 Try to fix empty prop_id 2022-04-05 10:33:05 +05:30
contrapunctus aec8f82c60 Fix most remaining errors in import-export 2022-04-05 07:48:00 +05:30
contrapunctus 98f8bcd7b8 Attempt to fix quoting of plists 2022-04-04 21:33:02 +05:30
contrapunctus 88c6e11b9f Update plist-remove and emacsql calls, fix let-match patterns 2022-04-04 20:03:08 +05:30
contrapunctus 031f3b9640 Convert emacsql to CLSQL, reinstate some helpers 2022-04-04 07:14:43 +05:30
contrapunctus 63f60ccde5 Address still more errors 2022-04-04 06:50:50 +05:30
contrapunctus a04447f745 Replace iso-to-ts -> parse-timestring, ts-adjust -> adjust-timestamp
And split-string with uiop:split-string
2022-04-04 01:10:35 +05:30
contrapunctus e4ff298ba4 Update some call sites for timestamp, plist, hash table, and database APIs 2022-04-03 23:00:07 +05:30
contrapunctus a0107ccb91 Correct class option syntax 2022-04-03 20:17:10 +05:30
contrapunctus 5a64f7ce93 Fix make-hash-table-1 error, backend-file bug
backend-file uncovered an issue with redefining slots - we now use
:default-initargs instead.
2022-04-03 13:52:57 +05:30
contrapunctus 5d0910e4a0 Remove `path` slot 2022-04-03 10:20:40 +05:30
contrapunctus 11ba9d14c9 Remove on-file-path-change 2022-04-03 09:53:10 +05:30
contrapunctus f18c71453b Disable verify method 2022-04-03 09:30:36 +05:30
contrapunctus ebce220bd5 Create plist-group package 2022-04-03 09:26:36 +05:30
contrapunctus 7a1cf0d66e Create XDG directories on startup 2022-04-02 22:36:45 +05:30
contrapunctus e45064be44 Export external symbols 2022-04-02 22:13:47 +05:30
contrapunctus 131e157239 Replace -let with let-match 2022-04-02 22:13:26 +05:30
contrapunctus fdc2b1a964 Fix method arity issues 2022-04-02 22:13:06 +05:30
contrapunctus 3f50c1ba55 Remove old to-file method 2022-04-02 22:12:46 +05:30
contrapunctus 0ea1dee847 Update some references to old chronometrist-file variable 2022-04-02 21:49:54 +05:30
contrapunctus ad6cf91a53 Reinstate data structures and time functions 2022-04-02 21:45:15 +05:30
contrapunctus 8fc14e6686 Convert Elisp alist functions to CL 2022-04-02 21:43:59 +05:30
contrapunctus 6e38c56f0e doc: Add first-run behaviour tasks 2022-04-02 17:30:45 +05:30
contrapunctus 493d7d34ba Address load errors
Remove frontend command `switch-backend` and function `read-backend-name`
Replace `alist-get` with `assoc`
Add earmuffs to *backends-alist*
2022-04-02 17:23:54 +05:30
contrapunctus c4666ee770 Remove `provide` form 2022-04-02 16:58:21 +05:30
contrapunctus b5d71bd4d5 Address load errors 2022-04-02 16:58:11 +05:30
contrapunctus c0c6c20e2a Correct view statement 2022-04-02 16:37:35 +05:30
contrapunctus 48ea058ce3 Define file slot accessor and update some call sites
Since we don't have Elisp's add-variable-watcher in Common Lisp, this
seemed like the next best way (or an even better way?) to
automatically update the database path as soon as the user changes its
location.
2022-04-02 14:57:26 +05:30
contrapunctus a77eaac963 Use XDG directories for configuration and storage locations 2022-04-02 12:39:47 +05:30
contrapunctus 0e52e04491 Address compiler errors, reinstate make-hash-table-1 2022-04-02 12:00:02 +05:30
contrapunctus 1a7d70b8b7 Write SXQL alternative 2022-04-02 11:06:04 +05:30
contrapunctus 35d9e81c78 Create view 2022-04-02 10:58:35 +05:30
contrapunctus 10b8328474 Try using queries in CLSQL symbolic syntax 2022-04-02 10:58:09 +05:30
contrapunctus cf80e12ce6 Migrate create-file from emacsql to CLSQL 2022-04-02 09:12:12 +05:30
contrapunctus 125cba8fc3 Replace emacsql-sqlite with clsql:connect 2022-04-02 08:13:43 +05:30
contrapunctus 1e00f8e930 Add tags 2022-04-02 08:13:36 +05:30
contrapunctus 0722b10a32 Define package chronometrist.sqlite 2022-04-02 08:10:39 +05:30
contrapunctus cfbe5a6209 Remove plist backend code 2022-04-02 08:07:21 +05:30
contrapunctus 01deba0c45 Convert or remove Elisp-isms 2022-04-02 08:07:16 +05:30
contrapunctus 987e82cbb9 Replace -let with let-match, comment out square bracket syntax
We also remove code which is not likely to be necessary for some time,
in a bid to be able to load the file without errors.
2022-04-02 00:41:47 +05:30
contrapunctus 605fc63b49 Remove migration and timeclock code 2022-04-02 00:12:37 +05:30
contrapunctus 04ede947b4 Merge branch 'dev' into cl-port 2022-04-01 22:42:10 +05:30
contrapunctus c47906f0bf Remove unused code 2022-04-01 21:31:12 +05:30
contrapunctus 059579e841 TODO: Remove async.el discussion, add tasks 2022-04-01 20:26:07 +05:30
contrapunctus 98650443c7 Merge branch 'doc' into async-migrate 2022-04-01 20:23:51 +05:30
contrapunctus cbf302a7d1 Fix empty output and emacsql-close error 2022-04-01 20:23:47 +05:30
contrapunctus 8c02c3c397 Accept optional arguments, make code async (WIP)
The optional arguments enable partially-non-interactive use and ease
testing.
2022-04-01 19:47:45 +05:30
contrapunctus 8f11adb3bc doc: Add async migration task and attempted implementation 2022-04-01 18:11:15 +05:30
contrapunctus 25dd20f170 Add task for first-launch migration 2022-04-01 12:20:23 +05:30
contrapunctus dbbc25d127 Merge branch 'doc' into dev 2022-04-01 12:02:59 +05:30
contrapunctus ea61ed6d2a Insert prop-id when inserting intervals 2022-04-01 12:02:21 +05:30
contrapunctus f7285c2ddc doc: Change 'custom fields' idea to 'categories' 2022-03-31 22:54:52 +05:30
contrapunctus ad07f401e2 doc: Add customizable total duration fields idea 2022-03-31 17:32:45 +05:30
contrapunctus 8ede73dbf5 Define backend and frontend systems 2022-03-30 20:26:50 +05:30
contrapunctus 769495ca5e Write incomplete to-list method for SQLite backend 2022-03-29 20:52:39 +05:30
contrapunctus b5501afc9d Change source block language 2022-03-29 20:29:59 +05:30
contrapunctus 6de9e473fd Use earmuffs and defvar for custom variables 2022-03-29 20:29:59 +05:30
contrapunctus 985969f867 Remove cl- and chronometrist- prefixes 2022-03-29 20:29:59 +05:30
contrapunctus b902da07ee Copy SQLite backend code 2022-03-29 20:05:33 +05:30
contrapunctus 691c8c4089 Export symbols for custom variables 2022-03-28 03:11:05 +05:30
contrapunctus e4a3cf4cfd Create CLIM GUI file from Elisp frontend codebase 2022-03-28 02:52:06 +05:30
contrapunctus fc7ed02df7 Remove report- prefix 2022-03-28 02:38:41 +05:30
contrapunctus 236dbfda8f Remove remaining cl- prefixes 2022-03-27 20:17:13 +05:30
contrapunctus e50b2813b3 Remove pretty-printer 2022-03-27 20:15:38 +05:30
contrapunctus 9ff6dd7edf Address package lock errors 2022-03-26 23:33:02 +05:30
contrapunctus 0cc451fa03 Change source block language 2022-03-26 23:24:42 +05:30
contrapunctus cd657af021 Remove frontend and timer code 2022-03-26 23:09:32 +05:30
contrapunctus d7da24b7cb Remove chronometrist- prefix 2022-03-26 23:02:39 +05:30
contrapunctus 7c85867aa7 Remove cl- prefixes 2022-03-26 23:00:58 +05:30
contrapunctus 6b30afb90e Change cl-defun to defun; remove custom groups 2022-03-26 22:58:17 +05:30
contrapunctus 0147bc3650 Add system definition, copy Elisp LP 2022-03-26 20:57:33 +05:30
contrapunctus 3f52469f31 Disable JSON pretty-printing 2022-03-26 20:09:49 +05:30
contrapunctus 1eaf2c099a Fix emacsql setup form; setup automatic literate-elisp-load 2022-03-25 23:00:20 +05:30
contrapunctus c22be59b51 Reinstate encoding of properties as JSON (now customizable) 2022-03-25 22:39:12 +05:30
contrapunctus 0d4dde2a3f Use INSERT OR IGNORE INTO; move tasks to TODO 2022-03-25 20:27:27 +05:30
contrapunctus 8c23ac65f0 Remove JSON encoding of user key-values 2022-03-25 20:13:16 +05:30
contrapunctus ab1dc18795 Fix "Too many open files" error 2022-03-25 19:53:58 +05:30
contrapunctus fc47683184 Fix json-encode error with cons pairs 2022-03-25 09:14:06 +05:30
contrapunctus a2c483f1ee Correct create-file return value 2022-03-25 08:38:42 +05:30
contrapunctus 98dda890d4 Add optional file argument to create-file 2022-03-25 06:25:11 +05:30
contrapunctus 319e13140a Create database connection for insert 2022-03-25 06:24:11 +05:30
contrapunctus c541224f73 Do nothing if inserting duplicates into intervals 2022-03-25 06:23:28 +05:30
contrapunctus f60526e42c Handle insertion of day-crossing intervals 2022-03-24 23:42:34 +05:30
contrapunctus 818d130066 Move `create-file` near old definition
Move `create-file` before `to-file`
2022-03-24 20:24:03 +05:30
contrapunctus a3aec17488 Reindent emacsql queries 2022-03-24 20:07:50 +05:30
contrapunctus 72c9a355b5 (WIP) Implement to-file and insert operations 2022-03-24 19:51:07 +05:30
contrapunctus 640c3e75ee Extend schema to support date properties and events 2022-03-23 21:19:24 +05:30
contrapunctus 4c06cc1ab6 Tag command; rename local variables 2022-03-23 21:17:46 +05:30
contrapunctus f9e68771c8 Remove tags column; use JSON column for user properties 2022-03-22 12:12:23 +05:30
contrapunctus b71c534693 Merge branch 'doc' into sqlite 2022-03-22 11:21:16 +05:30
contrapunctus a8a51accbf Add notes on SQLite backend 2022-03-21 09:27:25 +05:30
contrapunctus 239f733dd8 Merge branch 'dev' into sqlite 2022-03-21 09:19:17 +05:30
contrapunctus 23d94400cc Merge branch 'doc' into dev 2022-03-03 13:11:39 +05:30
contrapunctus e6c8ed785d Add task for better error reporting 2022-03-03 13:11:20 +05:30
contrapunctus 7cf2c86afd Convert user presets to strings 2022-02-25 15:20:21 +05:30
contrapunctus 3962c63f93 Mark task done; document chronometrist-goal bug 2022-02-25 00:27:50 +05:30
contrapunctus 9ef3337c5a Tweak default duration format 2022-02-24 15:47:06 +05:30
contrapunctus 7334be5743 Add key-value presets
Inspired by the Android application "My Expenses" by Michael
Totschnig.
2022-02-23 20:47:46 +05:30
contrapunctus 3d2bc8149e Format old-break duration like other durations 2022-02-23 14:52:53 +05:30
contrapunctus c51f4a4560 Remove MELPA Stable recommendation 2022-02-20 16:46:48 +05:30
contrapunctus e6df8138ee Merge branch 'doc' into dev 2022-02-20 16:01:23 +05:30
contrapunctus ba560d6857 Use manual.org as readme 2022-02-20 15:54:53 +05:30
contrapunctus 6556ee0bde Add screenshot 2022-02-20 15:54:53 +05:30
contrapunctus 08e53cfea3 Add predefined key-values idea 2022-02-17 23:44:08 +05:30
contrapunctus 6b56be318d Move tasks into main heading 2022-02-17 23:33:38 +05:30
contrapunctus dd8660ee73 Add tasks for implementing date properties 2022-02-17 23:33:07 +05:30
contrapunctus 2b2bfa2778 Merge branch 'doc' into dev 2022-02-16 00:34:00 +05:30
contrapunctus 2c12741474 Update versions for release 2022-02-15 21:03:25 +05:30
contrapunctus 12e11945a2 Rename "events" -> "ht" 2022-02-15 20:48:59 +05:30
contrapunctus 9cdc5e5cbb Tweak task 2022-02-15 18:17:14 +05:30
contrapunctus b222ecc35f Update section "customizable task list" and CHANGELOG 2022-02-15 12:49:02 +05:30
contrapunctus 4638b040ff Use headings for new backend items 2022-02-15 12:35:31 +05:30
contrapunctus 456fe451ab Add heading tags 2022-02-15 12:32:07 +05:30
contrapunctus 27ebc824fa Correct :nexport: tag 2022-02-15 01:26:15 +05:30
contrapunctus d88619b15f Update TODO for alert macro 2022-02-14 22:13:00 +05:30
contrapunctus 2eed71ad85 Add 'canned key-values' idea 2022-02-14 20:47:12 +05:30
contrapunctus a5c709253f Add CUSTOM_ID, macro idea 2022-02-14 13:05:02 +05:30
contrapunctus e7fd4df27e Split Third Time section into headings 2022-02-14 09:00:54 +05:30
contrapunctus fe9ed8c7f0 Add customizable alerts idea 2022-02-14 09:00:31 +05:30
contrapunctus cdcc059b26 Add notes on multiple intervals/time deduction 2022-02-13 17:16:56 +05:30
contrapunctus 21c442069e Merge branch 'doc' into dev 2022-02-13 16:04:54 +05:30
contrapunctus d69944b81c Correct tags, add docstring 2022-02-13 16:03:48 +05:30
contrapunctus 38f2b6239c Remove duplicate footnote 2022-02-13 02:59:12 +05:30
contrapunctus 005e54f79b Note branches; mark work on date properties as STARTED 2022-02-13 02:53:07 +05:30
contrapunctus 2c3b9eaa97 Add work hour implementation idea 2022-02-13 02:52:25 +05:30
contrapunctus 7f4e5e9c7c Remove leftover SO reference 2022-02-13 02:39:22 +05:30
contrapunctus 90d3f73cdf Remove :stop key for active split plists 2022-02-13 01:05:38 +05:30
contrapunctus a7eecc96ab Change latest-record to always return a unified record
This fixes -
* time displayed by activity indicator
* calculation of break time for chronometrist-third
2022-02-13 01:02:11 +05:30
contrapunctus 28a0f69132 Show different message when all break time is used up 2022-02-13 00:02:49 +05:30
contrapunctus bedbf6a8c5 Pass time as argument to callbacks 2022-02-12 23:50:34 +05:30
contrapunctus a78e1420d7 Merge branch 'doc' into dev 2022-02-12 23:42:32 +05:30
contrapunctus 29dee2792c Fix unintentionally exported heading 2022-02-12 23:41:39 +05:30
contrapunctus 1b6c21d529 Fix `alert` errors, blank duration in notification 2022-02-12 21:41:15 +05:30
contrapunctus 6ba6c0996c Update explanation 2022-02-12 07:39:01 +05:30
contrapunctus 66d747d27c Add autoload cookie 2022-02-11 22:01:24 +05:30
contrapunctus c13241b0a9 Update TODO 2022-02-11 12:36:24 +05:30
contrapunctus 09203bf5db Fix incorrect duration bug 2022-02-11 12:35:56 +05:30
contrapunctus 27e2b17214 Add docstrings 2022-02-11 12:29:49 +05:30
contrapunctus 16248f35b3 Add alternative date key-value form 2022-02-11 02:36:46 +05:30
contrapunctus 863cbcbf52 Update CHANGELOG 2022-02-11 00:22:22 +05:30
contrapunctus 3ad9c3b2aa Merge branch 'dev' into third-time 2022-02-11 00:19:28 +05:30
contrapunctus 2b57dc4807 Reword point, add deprecation notice 2022-02-11 00:19:18 +05:30
contrapunctus 64c14137af Add tasks for big break command, work hours, and date key-values 2022-02-10 23:19:18 +05:30
contrapunctus 811b79eb95 Add TODO items for Third Time 2022-02-10 22:14:25 +05:30
contrapunctus 0a1382abd7 Address lint issues 2022-02-10 22:05:31 +05:30
contrapunctus 276bf64cf7 Add library headers, define alerts 2022-02-10 21:57:17 +05:30
contrapunctus 8381dc36c5 Define minor mode 2022-02-10 19:17:04 +05:30
contrapunctus 1297f2faa1 Auto-load with literate-elisp 2022-02-10 18:53:06 +05:30
contrapunctus daf2d2895f Merge branch 'dev' into third-time 2022-02-10 18:51:20 +05:30
contrapunctus 75d0130df4 Implement clock-in and clock-out functions 2022-02-10 18:47:35 +05:30
contrapunctus bbb9986edf Rename function, update chronometrist-interval to add :stop times 2022-02-10 18:27:01 +05:30
contrapunctus a83173d0a9 Merge branch 'doc' into dev 2022-02-10 18:18:39 +05:30
contrapunctus bbe54ca3bf Fix undesired empty lines between plist groups 2022-02-10 17:27:19 +05:30
contrapunctus f94ab9643b Add scratch code 2022-02-10 17:03:08 +05:30
contrapunctus 409aa0337e Add newlines between headings 2022-02-10 16:47:28 +05:30
contrapunctus a04e9ebfba Add TODO entry for the Third Time system 2022-02-10 16:46:24 +05:30
contrapunctus cd7eb9c726 Merge branch 'dev' into sqlite 2022-01-31 22:52:58 +05:30
contrapunctus 406c23275c Correct example configuration for vertico-multiform 2022-01-24 19:59:33 +05:30
contrapunctus 5629b06f33 Tweak vertico-multiform example code 2022-01-24 02:27:09 +05:30
contrapunctus e603178655 Link to Vertico 2022-01-24 02:15:41 +05:30
contrapunctus 024bab2376 Add how-to for Vertico 2022-01-24 02:11:56 +05:30
contrapunctus 0880c3c966 Remove extra spaces 2022-01-24 01:49:37 +05:30
contrapunctus 28820db0d3 Update activity indicator code example 2022-01-24 01:48:42 +05:30
contrapunctus 54023fac6a Update auto-backup instructions 2022-01-24 01:45:02 +05:30
contrapunctus 88ee7710d8 Add history variable for unified key-value prompt 2022-01-24 01:36:15 +05:30
contrapunctus f0073b583b Fix relint action 2022-01-23 13:11:30 +05:30
contrapunctus 4a83282931 Address package-lint issues 2022-01-23 13:09:57 +05:30
contrapunctus ddb5729d21 Merge branch 'backend-tests' into dev 2022-01-23 13:08:52 +05:30
contrapunctus 7008da32fd Correct build actions 2022-01-23 13:05:17 +05:30
contrapunctus 66b92580d3 Fix code block export 2022-01-23 12:09:57 +05:30
contrapunctus 182c98ae7b Link to other repository 2022-01-23 11:23:48 +05:30
contrapunctus fd1dd3a5e8 Set up automatic literate-elisp-load 2022-01-23 11:22:30 +05:30
contrapunctus 0a09dc74e6 Add generated file warning 2022-01-23 11:06:02 +05:30
contrapunctus d22bd4f7e3 Tweak short descriptions 2022-01-23 11:02:55 +05:30
contrapunctus 4ddc06c084 Update README/manual 2022-01-23 10:56:08 +05:30
contrapunctus 07322c1052 Remove Cask file 2022-01-23 10:43:04 +05:30
contrapunctus 42cf241587 Make description consistent 2022-01-23 10:42:54 +05:30
contrapunctus bcff5690fc Add more test cases for insert 2022-01-14 21:48:32 +05:30
contrapunctus 86c7fa6d5b Use with-slots, new schema, and register-backend 2022-01-14 01:02:51 +05:30
contrapunctus fe9f2b494f Add links to tests 2022-01-13 22:48:42 +05:30
contrapunctus 84069f0b87 Remove old tests 2022-01-13 21:54:51 +05:30
contrapunctus 64dced972a Add debugging messages 2022-01-13 21:54:06 +05:30
contrapunctus b308fc06d3 Remove superfluous package prefix 2022-01-13 21:53:13 +05:30
contrapunctus 40a5eb60bd Clear hash table after each test 2022-01-13 21:50:28 +05:30
contrapunctus 240c0f5956 Change debug message formats 2022-01-13 17:09:26 +05:30
contrapunctus a909f323bd Fix insertion in debug buffer 2022-01-13 17:08:57 +05:30
contrapunctus c33eb1dac5 Merge branch 'dev' into backend-tests 2022-01-13 17:08:28 +05:30
contrapunctus 21285da0d9 Remove sqlite.org from Makefile 2022-01-13 17:07:48 +05:30
contrapunctus f558a3a469 Merge branch 'dev' into backend-tests 2022-01-13 17:06:28 +05:30
contrapunctus 5900a85491 Add Makefile 2022-01-13 14:44:51 +05:30
contrapunctus c2a223718e Correct dependencies 2022-01-13 14:40:51 +05:30
contrapunctus 77480b2e9f Create WIP sqlite backend
Mostly copied from the earlier attempt
2022-01-13 03:13:39 +05:30
contrapunctus e3590575d0 Check if file-watch is actually non-nil
A preemptive check against possible error situations
2022-01-13 01:40:44 +05:30
contrapunctus b9cc5b7a49 Add tests for create-file, latest-date-records, and insert 2022-01-13 01:37:25 +05:30
contrapunctus c06a5740fc Inhibit query when killing buffer in post-test cleanup 2022-01-12 20:05:05 +05:30
contrapunctus 4ef9890f67 Add type checks to replace-last and task-records-for-date 2022-01-12 14:56:42 +05:30
contrapunctus 69da87faf0 Move common testing definitions to Setup 2022-01-12 14:18:27 +05:30
contrapunctus 58ec3aa09a Update current-task test 2022-01-12 14:18:27 +05:30
contrapunctus 52a2aeae14 Extend plist type; add :before method for type-checking 2022-01-12 14:18:27 +05:30
contrapunctus 04bc6191c4 Change ert-deftest wrapper
Earlier, it was designed only for generating tests for all backend
operations. Now, with the addition of the NAME argument, it is made to
generate test groups (one per backend) for a specific operation.
2022-01-12 11:46:55 +05:30
contrapunctus 23d8831916 Merge branch 'dev' into backend-tests 2022-01-12 11:09:35 +05:30
contrapunctus 78b7a760a0 Add debug message to insert method 2022-01-12 11:06:11 +05:30
contrapunctus 766f22b6c5 Update changelog for changed debug mechanism 2022-01-12 11:05:40 +05:30
contrapunctus 4d1d0c53a9 Fix active record getting inserted in both old and new groups 2022-01-12 03:26:33 +05:30
contrapunctus 8e94af2f32 Add a basic verify method for plist-group backends 2022-01-11 05:04:54 +05:30
contrapunctus a4617f7066 Merge branch 'doc' into dev 2022-01-11 04:15:45 +05:30
contrapunctus 3aca6b2e70 Write own debug logging code to replace display-warning
The constant popping up of the warning buffer window was annoying
2022-01-11 00:42:49 +05:30
contrapunctus b05a1e5f13 Fix incorrect file change type logic and -let pattern
new-last-hash is supposed to be between rest-end and file-length,
rather than new-file-length
2022-01-10 22:09:43 +05:30
contrapunctus fcc34aa4ff Move cleanup code to macro 2022-01-10 15:56:15 +05:30
contrapunctus 278481b854 Use macro instead of looping inside deftest 2022-01-10 15:40:37 +05:30
contrapunctus bb5f2a9e41 Return non-nil on successful plist-backend insert method 2022-01-10 15:36:12 +05:30
contrapunctus 59c23aef13 Make backend test cleanup code generic; tweak class hierarchy 2022-01-10 14:47:28 +05:30
contrapunctus 94f538a6b0 Raise error if remove-last has nothing to remove 2022-01-10 14:45:58 +05:30
contrapunctus cbe62977b3 Refactor backend test cleanup forms 2022-01-10 10:54:32 +05:30
contrapunctus 79ca0242de Change create-file to return the file name on success 2022-01-10 10:53:32 +05:30
contrapunctus d144ccf62b Add newlines between headings 2022-01-09 20:54:24 +05:30
contrapunctus 09a045121b Add bug note, UX improvement 2022-01-09 18:46:20 +05:30
contrapunctus 52eefcd86f Prevent args out of bound errors 2022-01-09 18:32:19 +05:30
contrapunctus 6e00907e28 Change debug from a macro to a function 2022-01-09 18:31:44 +05:30
contrapunctus cadb28f030 Add menu entry for migrate command 2022-01-09 17:35:36 +05:30
contrapunctus aa40bbe291 Add debug messages for commands 2022-01-09 17:34:40 +05:30
contrapunctus 57ba75f376 Add debug logging and messages 2022-01-09 16:01:06 +05:30
contrapunctus 43b924ca03 Fix error stemming from incorrect pattern 2022-01-09 11:03:15 +05:30
contrapunctus 1525df5fc2 Fix insertion of day-spanning plists
Prevent insertion of undesired newlines
2022-01-09 03:35:16 +05:30
contrapunctus eb4fc565e0 Update TODO 2022-01-09 02:09:40 +05:30
contrapunctus 0938841b26 Update CHANGELOG 2022-01-08 23:31:17 +05:30
contrapunctus 0c4e3ef47c Rewrite file-change-type, update on-change 2022-01-08 23:26:03 +05:30
contrapunctus 41728d511c Convert file-state into discrete slots and update most references 2022-01-08 21:33:09 +05:30
contrapunctus 5b272da659 Make timer monomorphic, create backend-modified-p 2022-01-08 14:27:45 +05:30
contrapunctus cdcb031e31 Merge branch 'doc' into dev 2022-01-08 14:25:21 +05:30
contrapunctus ccc759d737 Fix error when buffer being edited 2022-01-08 13:43:06 +05:30
contrapunctus c92eaa924b Clarify optimization idea 2022-01-07 23:28:26 +05:30
contrapunctus b45cce3e34 Add implemented optimization idea 2022-01-07 23:26:59 +05:30
contrapunctus ea336afe09 Do not generate task list if it exists 2022-01-07 16:16:28 +05:30
contrapunctus e8137651d0 Update file-change-type tests 2022-01-06 12:00:31 +05:30
contrapunctus 0d00ac6fe2 Fix buffer not displayed on first start 2022-01-05 23:31:35 +05:30
contrapunctus 2721215ce7 Document backend protocol 2022-01-05 19:26:57 +05:30
contrapunctus 74cc00b1e5 Address byte compiler warnings 2022-01-05 15:20:26 +05:30
contrapunctus 8b0e3def68 Edit docstring, comment out debugging message 2022-01-05 14:32:46 +05:30
contrapunctus eaf09dea10 Add docstrings 2022-01-05 14:26:35 +05:30
contrapunctus 71eded0620 Update TODO 2022-01-05 14:26:25 +05:30
contrapunctus d07f99ff3a Change protocol signatures; implement plist-group methods 2022-01-05 13:55:19 +05:30
contrapunctus d5ce69ff92 Update plist backend docstrings and parameters 2022-01-05 13:21:31 +05:30
contrapunctus 2cefb73052 Add details to docstrings, remove fs-event parameter from GF 2022-01-05 13:13:04 +05:30
contrapunctus 4298af549d Refresh buffer after backend reset 2022-01-05 11:50:47 +05:30
contrapunctus 18f8c77025 Move task-list procedures 2022-01-05 11:07:19 +05:30
contrapunctus e02acdd3cc Refactor on-change, add 3 new generic functions
on-add/on-modify/on-remove are not completely implemented yet
2022-01-05 11:05:08 +05:30
contrapunctus 9a8b359cd9 Update active backend when chronometrist-file is changed
Users' custom chronometrist-file value was being ignored unless it was
set before the package was loaded.
2022-01-04 01:30:23 +05:30
contrapunctus b42a651d21 Address errors 2022-01-04 01:29:26 +05:30
contrapunctus eaa1692d3a Use backend-empty-p instead of file-empty-p 2022-01-04 00:56:56 +05:30
contrapunctus 36336367ef Use cl- for other list accessors 2022-01-04 00:30:47 +05:30
contrapunctus 46e28b022b Replace second with cl-second 2022-01-03 19:08:43 +05:30
contrapunctus 06f2ceb44a Address byte compiler warnings 2022-01-03 19:00:41 +05:30
contrapunctus 4206ffe615 Merge branch 'grouped-plist-format' into dev 2022-01-03 18:45:32 +05:30
contrapunctus b2cb16ff10 Address byte compiler warnings 2022-01-03 18:45:16 +05:30
contrapunctus 1aab30d399 Reorganize protocol 2022-01-03 18:02:06 +05:30
contrapunctus 0874b9697a Remove update-properties 2022-01-03 17:50:34 +05:30
contrapunctus 2756239e68 Move file-state to class slot 2022-01-03 17:47:15 +05:30
contrapunctus 0657777e8c Convert timestamps to properties 2022-01-03 16:18:07 +05:30
contrapunctus 3cf063b07b Add two optimization ideas 2022-01-03 16:14:47 +05:30
contrapunctus 1bd3c16929 Rename reset-internal -> reset-backend 2022-01-03 14:07:45 +05:30
contrapunctus df00031285 Remove references to files from protocol 2022-01-03 03:20:24 +05:30
contrapunctus 5d5b423c59 Implement multi-backend tests
Reorganize plist backend methods
Address some failing tests for plist backend methods
2022-01-03 02:11:49 +05:30
contrapunctus 909be83e3a Suppress load-time error about nonexistent file 2021-12-27 14:06:42 +05:30
contrapunctus c87b8a0a4d Add tests for case of file without records 2021-12-27 14:06:09 +05:30
contrapunctus 7ce9f8bf2d Move create-file earlier in the protocol 2021-12-27 14:03:14 +05:30
contrapunctus e031d43f24 Add error-checking and return values 2021-12-27 14:02:53 +05:30
contrapunctus 28f120f94b Reorder tests 2021-12-26 19:36:44 +05:30
contrapunctus 0fb0c6d0d6 Specify return value in protocol and update behavior 2021-12-26 19:36:44 +05:30
contrapunctus 283efd5a03 Reinstate test data as list of plists 2021-12-26 14:29:51 +05:30
contrapunctus f29c6c5157 Merge branch 'doc' into backend-tests 2021-12-24 21:55:25 +05:30
contrapunctus c58726ca7c doc(TODO): Add new task for verify command 2021-12-24 21:54:46 +05:30
contrapunctus e3acecfec9 doc(TODO): Cross off task 2021-12-24 21:54:12 +05:30
contrapunctus 5eee8f8df6 doc(TODO): Add Hamster as potential backend 2021-12-24 21:45:03 +05:30
contrapunctus bd4680fc04 Return non-nil if create-file succeeds 2021-12-23 20:33:52 +05:30
contrapunctus 01d11ba5d2 Add tests for backend protocol 2021-12-23 20:33:22 +05:30
contrapunctus 0dd4e775f5 Remove old test data file 2021-12-23 20:29:08 +05:30
contrapunctus 43e4d212f8 Merge branch 'doc' into backend-tests 2021-12-18 15:45:20 +05:30
contrapunctus 708b9fafa8 doc(TODO): Document bug 2021-12-18 15:44:35 +05:30
contrapunctus a41662236f Replace `cl-assert` with `error`
A failed `cl-assert` does not pass a `should-error` test.
2021-12-18 15:02:06 +05:30
contrapunctus c2f1590fe7 doc(TODO): Add ideas requiring changes to the format 2021-12-18 11:59:28 +05:30
contrapunctus cbcb10fdd4 Rename check-preconditions to run-assertions 2021-12-13 11:51:17 +05:30
contrapunctus a123e59611 Add type checks and assertions for plist group methods 2021-12-13 11:19:05 +05:30
contrapunctus 34ff8f9038 Segregate extended protocol for plist-group backend 2021-12-13 10:21:20 +05:30
contrapunctus 74414c313b Segregate extended protocol, add check-preconditions
Add details to protocol docstrings
2021-12-12 20:01:54 +05:30
contrapunctus be53039f00 doc(TODO): Add klog and SQLite 2021-12-07 16:24:41 +05:30
contrapunctus 7ca6413907 doc: explain plist-group specific concerns 2021-12-06 14:58:17 +05:30
contrapunctus 66853efd12 doc: clarify docstring for latest-record 2021-12-06 14:57:37 +05:30
contrapunctus 9d68ac8b53 Merge branch 'dev' into plist-group-handle-split-plists 2021-12-05 22:18:57 +05:30
contrapunctus 975b67af1f fix: order of records returned by plist-group to-list 2021-12-05 22:16:30 +05:30
contrapunctus 2ac54f418a Merge branch 'dev' into plist-group-handle-split-plists 2021-12-04 22:45:12 +05:30
contrapunctus 3fd8efea73 Create generic update-properties 2021-12-04 18:41:17 +05:30
contrapunctus 0e8e9f4373 Fix error messages to match Elisp guidelines 2021-12-04 18:39:46 +05:30
contrapunctus 7ddb9c653b Join line if only deleting a plist 2021-12-04 16:58:45 +05:30
contrapunctus c70a7791b0 code: use local variables for clarification 2021-12-04 15:15:33 +05:30
contrapunctus a947cca7c7 Throw error if remove-last is called on backend with no records 2021-12-04 15:13:40 +05:30
contrapunctus b469defcd4 Throw error if `insert` called with empty plist 2021-12-04 14:25:43 +05:30
contrapunctus 10b592a04b fix: make replace-last handle split plists 2021-12-04 14:19:31 +05:30
contrapunctus a669f2b765 fix: prevent leftover empty plist-groups 2021-12-04 12:58:54 +05:30
contrapunctus 07aaa0e78c fix: make remove-last take split plists into account
last-two-split-p - remove redundant conditions (we are checking for a
situation in which the newer-plist-group cannot, by definition, have
>1 plists), change return value.
2021-12-03 22:12:04 +05:30
contrapunctus 6ad88b19d3 Merge branch 'doc' into plist-group-handle-split-plists 2021-12-03 10:49:49 +05:30
contrapunctus 6aa4ef15f8 doc(TODO): move input frontends idea to its own heading 2021-12-03 10:49:28 +05:30
contrapunctus 9e8c3659d5 Create last-two-split-p 2021-12-03 01:01:27 +05:30
contrapunctus d108a4fdcb style: tweak plists-split-p 2021-12-03 00:16:53 +05:30
contrapunctus 83f19d70db Merge branch 'doc' into plist-group-handle-split-plists 2021-12-02 17:52:49 +05:30
contrapunctus 0fa2b270b4 doc(TODO): mention in-progress branch 2021-12-02 17:52:16 +05:30
contrapunctus 17b19d79f5 Create plists-split-p 2021-12-02 17:42:45 +05:30
contrapunctus 2725b742cd doc(TODO): auto-insert timestamp for new headings 2021-12-02 17:00:45 +05:30
contrapunctus f9dae2e2c8 doc(TODO): add new tasks 2021-12-01 18:59:50 +05:30
contrapunctus 311c3b0a1b Add test case 2021-12-01 17:09:08 +05:30
contrapunctus 5e5697b326 fix: dispatch on correct backend object
(This didn't have any incorrect behaviour that I noticed...I wonder why.)
2021-12-01 15:46:29 +05:30
contrapunctus 1234a65af5 doc(TODO): add implementation progress for plist-group backend 2021-12-01 15:44:44 +05:30
contrapunctus 6be3849136 doc: add TODO items and failing test case 2021-11-30 21:33:05 +05:30
contrapunctus 37bc29d923 fix: pretty printer behaviour for nested list values 2021-11-30 18:10:34 +05:30
contrapunctus e8eadc7048 fix: message user when discard-active is not applicable 2021-11-29 16:42:42 +05:30
contrapunctus 4eb4f30873 feat: new command chronometrist-discard-active 2021-11-28 17:26:12 +05:30
contrapunctus 14f02ff402 feat: implement backend operation remove-last 2021-11-28 12:36:31 +05:30
contrapunctus 8b82d68822 fix: timer running even when file buffer modified 2021-11-28 00:53:36 +05:30
contrapunctus 6768acd662 Reinstate chronometrist alias 2021-11-28 00:51:25 +05:30
contrapunctus 510a9400e1 Adjust nameless configuration
(To get aliases to work again, at the cost of nameless-insert-name
breaking. Opened an issue in the nameless repository -
https://github.com/Malabarba/Nameless/issues/32 )
2021-11-27 18:21:50 +05:30
contrapunctus 55f875ce1f Change "plist-pp" to "pp" 2021-11-27 18:08:50 +05:30
contrapunctus 40926e9d54 Merge branch 'dev' into doc 2021-11-27 17:32:49 +05:30
contrapunctus da45400b23 doc: add debugging suggestion 2021-11-25 19:12:36 +05:30
contrapunctus e29041e91d doc(TODO): add task 2021-11-25 17:37:04 +05:30
contrapunctus ac41b2ffde doc(TODO): update completed task 2021-11-25 09:55:59 +05:30
contrapunctus c9389d4404 doc(manual): add/update custom IDs, change manual.org link to chronometrist.org 2021-11-22 18:22:13 +05:30
contrapunctus 99f31e0381 doc(manual): make backup-per-save guide 2021-11-22 18:13:41 +05:30
contrapunctus d13362f6bf Move tests into chronometrist-tests.org 2021-11-22 17:01:57 +05:30
contrapunctus 87671c5a7f Merge branch 'doc' into dev 2021-11-22 16:43:59 +05:30
contrapunctus 2d28f896ff Remove unused local variable 2021-11-22 16:40:27 +05:30
contrapunctus a2a506f0f3 doc: clarify docstring 2021-11-22 16:39:43 +05:30
contrapunctus 702c1db887 Address byte compiler errors 2021-11-22 01:53:48 +05:30
contrapunctus 7e552e32fd Enable comment links to ease jumping from compilation errors to LP 2021-11-21 00:45:57 +05:30
contrapunctus bda42a5b90 doc(TODO): remove reset day operation, add sparkline task 2021-11-19 15:15:23 +05:30
contrapunctus 43a0645acd Add command to menu 2021-11-19 15:15:06 +05:30
contrapunctus e844e9f4b2 Create command to visit Org literate source 2021-11-19 15:10:17 +05:30
contrapunctus 0cc1a1174b Mention current name of macro 2021-11-19 15:05:54 +05:30
contrapunctus 4f91eb72e7 Merge branch 'dev' into doc 2021-11-18 18:05:13 +05:30
contrapunctus b0bbfb9ac5 Mirror changes in Org LP
(Forgot that this file was now an Org LP too.)
2021-11-18 12:00:28 +05:30
contrapunctus 0311640883 doc(TODO): add tasks for documentation discoverability 2021-11-18 09:50:13 +05:30
contrapunctus 133554d1b8 doc(changelog): note changes affecting user configuration 2021-11-18 08:51:14 +05:30
contrapunctus 6eb0549fb1 Update changelog 2021-11-18 02:36:58 +05:30
contrapunctus 1d278e4efc Merge branch 'grouped-plist-format' into dev 2021-11-18 02:28:56 +05:30
contrapunctus 7a3c5681ce Write currently-failing plist-pp-to-string test 2021-11-18 02:18:58 +05:30
contrapunctus b92f8b9c25 Write tests for plist-p, plist-group-p 2021-11-18 02:16:45 +05:30
contrapunctus 4477832673 doc: update TODO 2021-11-18 02:15:08 +05:30
contrapunctus 7b0df9a2d1 doc: mark hack 2021-11-18 02:14:11 +05:30
contrapunctus e15da603c7 [spark] write docstring 2021-11-09 23:13:14 +05:30
contrapunctus 866776d028 [plist-pp] handle plist groups 2021-11-08 22:44:01 +05:30
contrapunctus 552c64b967 [plist-pp] use shorter parameter name, add comments, use ?\s 2021-11-01 11:27:41 +05:30
contrapunctus 13c6cd184a [plist-group] temporary hack - reset data structures on file change
I intend to adapt the plist backend's change type detection code so it
can be used for any kind of s-expression file backends.
2021-11-01 11:04:47 +05:30
contrapunctus 38ba060de3 Fix incorrect backend being migrated 2021-11-01 11:03:54 +05:30
contrapunctus 8924e38401 Add :comments no in preperation for future top-level :comments link 2021-11-01 11:03:54 +05:30
contrapunctus a10e7f6e3a Fix chronometrist-migrate bugs 2021-11-01 11:03:54 +05:30
contrapunctus 62856f515d Move fs-watch to backend object slot 2021-11-01 11:03:54 +05:30
contrapunctus ae8c422d70 Run reset-internal instead of on-change after switching backends 2021-10-31 12:55:11 +05:30
contrapunctus 5892e9737f Write read-backend-name docstring; move to backend section 2021-10-31 12:53:37 +05:30
contrapunctus 186475d41e Create switch-backend command and register-backend function 2021-10-31 11:19:09 +05:30
contrapunctus 574ffd996c Split interval if necessary when clocking out 2021-10-31 09:55:58 +05:30
contrapunctus 7e19560d2a [plist-group] if required, create new tagged list before record insert 2021-10-30 09:51:21 +05:30
contrapunctus f583e5b3cc doc: update TODO 2021-10-30 08:36:21 +05:30
contrapunctus b70f304eb5 [plist-group] use hash table for task-records-for-date
* Move file-backend-mixin and related definitions to "common
  definitions for s-expression backends"
* Use loop-sexp-file and reinstate sexp-pre-read-check to reduce
  duplication
* Change some generics to dispatch on file-backend-mixin to make them
  common to the plist and plist-group backends
2021-10-30 01:17:23 +05:30
contrapunctus 7a17ed14a7 Rename functions; implement to-hash-table for plist-group
task-records -> task-records-for-date
active-time-one-day -> active-time-on

New generic - latest-date-records
Removed generic active-time
2021-10-30 00:00:35 +05:30
contrapunctus 06b29e3ce0 Update all references to chronometrist-events (except tests) 2021-10-29 09:54:28 +05:30
contrapunctus 45a2eeec06 Have details frontend use hash-table slot 2021-10-29 01:17:44 +05:30
contrapunctus 436aba7547 Remove list-tasks from backend protocol 2021-10-29 01:06:26 +05:30
contrapunctus d60d29c126 [key-values] remove key-value history limit 2021-10-29 00:40:54 +05:30
contrapunctus 4f0b0c0171 Use to-list + cl-loop instead of cl-loop wrapper macros 2021-10-29 00:40:54 +05:30
contrapunctus b0482699cd fix: use unwind-protect to always reset iterator state 2021-10-23 17:06:46 +05:30
contrapunctus 1d300249cc feat: customizable task list 2021-10-23 16:06:08 +05:30
contrapunctus 71ead83538 Rename test file 2021-10-21 14:57:33 +05:30
contrapunctus d10684f817 Add task-records test; restructure tests 2021-10-21 14:56:04 +05:30
contrapunctus 9eb9a05dd7 Make current-task a regular function 2021-10-21 14:33:50 +05:30
contrapunctus 8fd4d19c82 convert tests to Org LP; move hash table into backend object
* create generic functions reset-internal, on-change, backend-empty-p, memory-layer-empty-p
* move most of refresh-file to on-change method
2021-10-20 19:31:11 +05:30
contrapunctus 15b9bb832e fix(key-values): let -> let* 2021-10-17 17:34:05 +05:30
contrapunctus 1aa1a30291 Write tests for loop-records; move generic function 2021-10-15 07:39:59 +05:30
contrapunctus 31db47f46f Remove local variables from test file 2021-10-14 10:53:22 +05:30
contrapunctus 6424ef662c Remove docstrings from methods 2021-10-14 09:22:35 +05:30
contrapunctus 052bc23b17 Write backend tests 2021-10-14 09:21:28 +05:30
contrapunctus d6c980043b Implement insert method 2021-10-14 08:08:48 +05:30
contrapunctus 1f85435d56 Change slot name and values 2021-10-13 07:05:45 +05:30
contrapunctus 6bdb38f171 Implement replace-last 2021-10-13 06:59:34 +05:30
contrapunctus 786b6937ad Update task-records for changed loop-records interface 2021-10-13 06:56:45 +05:30
contrapunctus 177eb62d34 Update method for changed loop-records interface 2021-10-12 14:03:08 +05:30
contrapunctus fac18d2e08 optimize: speed up key/value history generation 2021-10-11 07:12:41 +05:30
contrapunctus 49d005be82 fix: remove :end from iterator states 2021-10-11 06:05:20 +05:30
contrapunctus b86825dded Use keywords for iterator-state; wrap cl-loop in save-excursion 2021-10-11 05:38:58 +05:30
contrapunctus 68d6b88bb0 doc: clarify plist time format 2021-10-10 22:02:13 +05:30
contrapunctus b76624c8e7 Convert plist-group-backend iterator to method;
Initialize iterator-state to t, so point is moved to (point-max) on
the first run of the iterator, too.

Our :after method for initialize-instance only applies to file-related
backends, and is thus changed to only dispatch on file-backend-mixin.
2021-10-10 21:58:27 +05:30
contrapunctus 5402130b27 Make iterator a generic function; store iterator-state as slot
Remove file-related details from `loop-records`
2021-10-10 17:52:52 +05:30
contrapunctus 1efc5ec7ac Move file-related slots into file-backend-mixin 2021-09-19 20:09:35 +05:30
contrapunctus a8cad8ccac fix file change detection 2021-09-13 19:34:20 +05:30
contrapunctus b310aa9298 migrate - fix insertion of file mode prop line 2021-09-12 19:19:41 +05:30
contrapunctus de2cabc4a6 update TODO 2021-09-12 17:22:08 +05:30
contrapunctus dfad505901 plist backend - fix test code 2021-09-12 17:22:08 +05:30
contrapunctus 4637f38408 optimize plist-group-read-record; extend backend documentation 2021-09-12 17:22:08 +05:30
contrapunctus 03b71132dc loop-records - fix infinite loop; list-tasks - fix test 2021-09-12 17:22:08 +05:30
contrapunctus e4c33d7097 create backend-generic `cl-loop` interface 2021-09-12 17:22:08 +05:30
contrapunctus 3b9e151bac migrate - prompt to overwrite if output file exists 2021-09-12 17:21:50 +05:30
contrapunctus 793491eb9b Remove list-records from protocol (there's already `to-hash-table`) 2021-09-10 19:41:55 +05:30
contrapunctus 69cda68f20 Convert references to chronometrist-file to chronometrist-backend-file 2021-09-09 21:25:40 +05:30
contrapunctus 7d7e801484 Update file watch setup for multiple backends 2021-09-09 18:30:37 +05:30
contrapunctus 34aeec3bb7 Revert changes to date format
Refactor spark-durations from spark-row-transformer for ease of
testing
2021-09-09 02:14:04 +05:30
contrapunctus 126b5710fb Merge branch 'doc' into grouped-plist-format 2021-09-07 07:29:39 +05:30
contrapunctus 4c7645bf55 TODO - add backend and code cleanup items 2021-09-07 07:29:16 +05:30
contrapunctus c14fea723c (WIP) implement task-records from task-events-in-day
Use ISO date instead of TS struct when dates required
2021-09-07 07:21:05 +05:30
contrapunctus b1192b1fdf Protocol - move list-records; remove task-time
task-time does not need to be polymorphic
2021-09-07 07:20:23 +05:30
contrapunctus 648253172a Implement task-records 2021-09-07 06:24:13 +05:30
contrapunctus 9e34fb8058 correct to-hash method, class definition 2021-09-06 22:31:27 +05:30
contrapunctus e0a6ff13a9 Clarify section name 2021-09-06 19:33:33 +05:30
contrapunctus 7f90191a40 rename loop-file; implement list-tasks and current-task methods 2021-09-06 19:32:21 +05:30
contrapunctus 63df54aa2e chronometrist-migrate - prompt for backends and files 2021-09-06 15:42:02 +05:30
contrapunctus 092f3e8f07 Hard wrap and copy hash table docstring 2021-09-06 01:18:26 +05:30
contrapunctus 8d73749c3f Associate create-file with both plist and plist-group backends
Move definitions common to sexp backends together
2021-09-06 01:18:26 +05:30
contrapunctus d673f00e5a Fix failing tests 2021-09-06 01:12:00 +05:30
contrapunctus 5a0d522e6f Describe plist group backend 2021-09-06 00:46:20 +05:30
contrapunctus dcf219aab4 Correct headings 2021-09-05 20:20:03 +05:30
contrapunctus b8b0967e18 Merge branch 'doc' into grouped-plist-format 2021-09-05 19:35:21 +05:30
contrapunctus a8eb47b184 TODO: note plist-group backend progress 2021-09-05 19:34:22 +05:30
contrapunctus 6893299427 Create methods stubs, create basic to-file method and migrate command 2021-09-05 19:10:37 +05:30
contrapunctus 43deef28af Use backend passed to method rather than active-backend 2021-09-05 17:39:43 +05:30
contrapunctus 3e7c41c9f7 Create on-file-change generic 2021-09-04 20:25:18 +05:30
contrapunctus 083a2f4d8e Adapt sexp-events-populate to to-hash-table 2021-09-04 20:20:59 +05:30
contrapunctus 78048824e5 Add package prefix to accessors 2021-09-04 19:45:20 +05:30
contrapunctus da1f2ed329 Fix more errors 2021-09-04 19:29:24 +05:30
contrapunctus 080e75a4c4 Move plist format description 2021-09-04 18:26:05 +05:30
contrapunctus a948fb4632 backend protocol - create count-record generic 2021-09-04 18:23:06 +05:30
contrapunctus 88d06da043 backend protocol - create migration generics 2021-09-04 18:08:06 +05:30
contrapunctus b218a835f7 plist backend - correct errors 2021-09-04 18:01:56 +05:30
contrapunctus 297f288130 Correct docstring 2021-09-04 17:29:34 +05:30
contrapunctus 9989cbf3d0 Update/clarify headings 2021-09-04 17:26:36 +05:30
contrapunctus 3f30eadbb0 Create method for list-tasks 2021-09-04 17:26:10 +05:30
contrapunctus af286f2d5d Create method for replace-last 2021-09-04 14:49:18 +05:30
contrapunctus dee921244b Create method for insert 2021-09-04 14:21:44 +05:30
contrapunctus 4e6243b3e3 Update call sites for create-file 2021-09-04 14:14:47 +05:30
contrapunctus 2b6ee13b96 Create method for create-file 2021-09-04 14:10:25 +05:30
contrapunctus 622914397e Create method for latest-record 2021-09-04 14:01:22 +05:30
contrapunctus 445614e9aa backend - add file slot 2021-09-04 13:55:26 +05:30
contrapunctus 260aafaebb Make edit-file method for plist backend 2021-09-04 08:45:56 +05:30
contrapunctus 0d612f28b5 Update current-task call sites 2021-09-04 08:43:32 +05:30
contrapunctus 4f50600e69 Correct generic function tags 2021-09-04 08:15:40 +05:30
contrapunctus 7bb023caf5 Rethink custom interface 2021-09-04 08:11:57 +05:30
contrapunctus 7c55a19e1f feat: create generic backend protocol 2021-09-04 00:28:33 +05:30
contrapunctus 2f218165b9 doc(TODO): file format ideas - list common pros and cons 2021-09-03 13:01:20 +05:30
contrapunctus 24a11688e2 doc(TODO): convert optimization ideas to headings 2021-09-03 01:26:52 +05:30
contrapunctus 6b00abca83 Merge branch 'dev' into doc 2021-09-03 01:22:10 +05:30
contrapunctus ba9e8dfa9c fix: correct docstring; indent code 2021-09-02 21:05:30 +05:30
contrapunctus 1b549a3c4c doc(TODO): note progress on format-seconds branch 2021-09-02 21:04:34 +05:30
contrapunctus a172e2cbb7 doc(TODO): add bug 2021-09-02 20:48:48 +05:30
contrapunctus 524ba9592f doc(README): add Liberapay widget, graph USP 2021-07-13 23:39:48 +05:30
contrapunctus 651aaa4ca1 doc(TODO): extend task-graph description 2021-07-09 20:19:40 +05:30
contrapunctus f2cd709242 doc(README): update installation instructions 2021-07-09 13:57:45 +05:30
contrapunctus 3ce8df0ba2 doc(TODO): add task for chronometrist-details 2021-07-09 08:20:31 +05:30
contrapunctus ecafd48a09 Merge branch 'dev' into doc 2021-07-09 07:52:54 +05:30
contrapunctus 73e6d98612 doc: bump versions 2021-07-08 03:17:55 +05:30
contrapunctus 58f0fdf0ba feat: add menu entry and keybinding for force-restart-timer 2021-07-08 03:15:47 +05:30
contrapunctus 103521be88 fix: remove auto-alignment of tags, tangle prompt
* I was almost always answering no to the prompt. Better to do it
  manually, then.
* The auto-alignment was resulting in garbage changes in each commit,
  probably because of an interaction with nameless-mode
2021-07-06 23:38:06 +05:30
contrapunctus 5620d87e31 doc(TODO): add note for interval editing UI 2021-07-06 23:38:00 +05:30
contrapunctus 5d42648c2a doc(TODO): add optimization idea - persist the hash table 2021-07-06 23:38:00 +05:30
contrapunctus 61eb311d0a feat(details): create new buffer for each range 2021-07-06 23:38:00 +05:30
contrapunctus d3ac1d4c18 doc(TODO): more thought on persisting hash table 2021-07-06 23:16:29 +05:30
contrapunctus 27b2c82635 doc(TODO): add note for interval editing UI 2021-07-06 03:46:18 +05:30
contrapunctus 7d51b46eac doc(TODO): add optimization idea - persist the hash table 2021-07-05 23:32:40 +05:30
contrapunctus 224a7fec3f cleanup: remove iso-date-to-ts, rename iso-timestamp-to-ts -> iso-to-ts 2021-07-03 10:59:22 +05:30
contrapunctus 26e3dd5c5d feat(details): implement timestamp ranges 2021-07-03 10:51:36 +05:30
contrapunctus 7976b53d91 fix(details): flatten key-values, remove keywords 2021-07-03 06:20:10 +05:30
contrapunctus 1931e88c85 feat(details): support begin/end in ranges 2021-07-03 00:23:56 +05:30
contrapunctus 883f813615 Merge branch 'doc' into details-view 2021-07-01 07:29:13 +05:30
contrapunctus ffa02ad3e9 doc(TODO): unify frontend ideas and add new ones 2021-07-01 07:28:06 +05:30
contrapunctus 0f68ab4f38 feat(details): add more menu items 2021-07-01 07:24:58 +05:30
contrapunctus f7f2349004 fix(details): set history variables 2021-07-01 07:24:26 +05:30
contrapunctus c5d4c33057 doc(details): update CHANGELOG 2021-06-28 07:51:10 +05:30
contrapunctus a04ca1fd28 fix(details): correct error 2021-06-28 07:50:19 +05:30
contrapunctus cc77b18797 fix(details): use read-from-minibuffer for filter 2021-06-28 06:53:35 +05:30
contrapunctus fd468a9ed5 feat(details): implement filter; refactor `rows` 2021-06-28 06:08:45 +05:30
contrapunctus 5adf3f3901 doc: update CHANGELOG 2021-06-27 22:28:11 +05:30
contrapunctus b774700450 feat(details): create prompt, menu 2021-06-27 22:24:37 +05:30
contrapunctus c4b94be941 feat(details): implement custom ranges 2021-06-27 22:24:28 +05:30
contrapunctus 9f8e455e01 feat(details): create state variables for custom ranges and filters 2021-06-27 20:57:38 +05:30
contrapunctus df8b7adf80 doc(TODO): add enhanced sparklines idea 2021-06-27 05:43:05 +05:30
contrapunctus 3f3567903b Merge branch 'dev' into doc 2021-06-27 03:38:47 +05:30
contrapunctus 3c9fc9b13a doc(TODO): add frontend macro idea 2021-06-27 03:38:04 +05:30
contrapunctus 2f8d136bf6 doc: shorten docstring 2021-06-26 09:09:11 +05:30
contrapunctus af9c463980 doc(TODO): add frontend idea 2021-06-22 13:18:07 +05:30
25 changed files with 12601 additions and 2458 deletions

View File

@ -4,7 +4,7 @@
;; for some reason, setting `nameless-current-name' to "chronometrist"
;; makes all aliases not take effect - probably specific to Org
;; literate programs
((nil . ((nameless-aliases . (("c" . "chronometrist")
((nil . ((nameless-aliases . (("cb" . "chronometrist-backend")
("cc" . "chronometrist-common")
("cd" . "chronometrist-details")
("ce" . "chronometrist-events")
@ -13,7 +13,9 @@
("cp" . "chronometrist-plist-pp")
("cr" . "chronometrist-report")
("cs" . "chronometrist-statistics")
("cx" . "chronometrist-sexp")))))
("cx" . "chronometrist-sexp")
("c" . "chronometrist")))
(sentence-end-double-space . t)))
(org-mode
. ((org-html-self-link-headlines . t)
(eval . (org-indent-mode))
@ -21,16 +23,4 @@
. (concat "<link rel=\"stylesheet\" "
"type=\"text/css\" "
"href=\"../org-doom-molokai.css\" />"))
(eval
. (add-hook
'after-save-hook
(lambda ()
(let ((fn (buffer-file-name)))
(when (y-or-n-p (format "Tangle file %s?" fn))
(compile
(mapconcat #'shell-quote-argument
`("emacs" "-q" "-Q" "--batch" "--eval=(require 'ob-tangle)"
,(format "--eval=(org-babel-tangle-file \"%s\")" fn))
" ")))))
nil t))
(eval . (add-hook 'before-save-hook (lambda nil (org-align-all-tags)) nil t)))))
(org-babel-tangle-use-relative-file-links . t))))

View File

@ -4,13 +4,40 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [unreleased]
## unreleased
### Added
1. `chronometrist-third`, an extension to add support for the [Third Time](https://www.lesswrong.com/posts/RWu8eZqbwgB9zaerh/third-time-a-better-way-to-work) system.
2. New custom variable `chronometrist-key-value-preset-alist`, to define completion suggestions in advance.
3. New custom variable `chronometrist-key-value-use-database-history`, to control whether database history is used for key-value suggestions.
## [0.10.0] - 2022-02-15
### Changed
1. The value of `chronometrist-file` must now be a file path _without extension._ Please update your configurations.
2. The existing file format used by Chronometrist is now called the `plist` format.
3. The extension for files in the `plist` format is now `.plist`. Update the extension of your file to use it with the `plist` backend.
### Added
1. Multiple backend support - new custom variable `chronometrist-active-backend` to determine active backend, new command `chronometrist-switch-backend` to temporarily select a backend (with completion).
2. New `plist-group` backend, reducing time taken in startup and after changes to the file.
3. Unified migration interface with command `chronometrist-migrate`.
4. New custom variable `chronometrist-task-list`, to add/hide tasks without modifying the database. Setting it also disables generation of the task list from the database, speeding up many operations.
5. New command `chronometrist-discard-active`, to discard the active interval.
6. Debug logging messages - to view them, set `chronometrist-debug-enable`.
### Fixed
1. Code to detect the type of change made to the file has been rewritten, hopefully fixing some uncommon `read` errors and `args out of range` errors.
### Deprecated
1. The plist backend is deprecated and may be removed in a future release. The `plist-group` backend is more performant and extensible - please use `chronometrist-migrate` to convert your data to the `plist-group` backend.
## [0.9.0] - 2021-07-08
### Added
1. New commands `chronometrist-restart-task`, `chronometrist-extend-task`
2. Menus for `chronometrist` and `chronometrist-key-values`
2. Menus for `chronometrist`, `chronometrist-key-values`, and `chronometrist-details`
3. Custom ranges and filters for `chronometrist-details`. See command `chronometrist-details-set-range` and `chronometrist-details-set-filter`.
### Changed
3. Display graph ranges in `chronometrist-spark` column
4. `chronometrist-tags-add` and `chronometrist-key-values-unified-prompt` now also work interactively.
4. Display graph ranges in `chronometrist-spark` column
5. `chronometrist-tags-add` and `chronometrist-key-values-unified-prompt` now also work interactively.
## [0.8.1] - 2021-06-01
### Changed

23
Cask
View File

@ -1,23 +0,0 @@
(source gnu)
(source melpa)
(package
"Chronometrist"
"0.7.2"
"A time tracker for Emacs with a nice interface")
(depends-on "cl-lib")
(depends-on "literate-elisp" "0.1")
(depends-on "dash" "2.16.0")
(depends-on "seq" "2.20")
(depends-on "ts" "0.2")
(files "elisp/*.el")
(development
(depends-on "f")
(depends-on "ecukes")
(depends-on "ert-runner")
(depends-on "el-mock")
(depends-on "elsa")
(depends-on "buttercup"))

82
Makefile Normal file
View File

@ -0,0 +1,82 @@
.phony: all setup tangle compile lint
all: clean-elc manual.md setup tangle compile lint
setup:
emacs --batch --eval="(package-initialize)" \
--eval="(mapcar #'package-install '(indent-lint package-lint relint))"
manual.md:
emacs -q -Q --batch --eval="(require 'ox-md)" \
"manual.org" -f 'org-md-export-to-markdown'
# No -q or -Q without ORG_PATH - if the user has a newer version of
# Org, we want to use it.
tangle:
cd elisp/ && \
emacs --batch \
--eval="(progn (package-initialize) (require 'ob-tangle))" \
--eval='(org-babel-tangle-file "chronometrist.org")' \
--eval='(org-babel-tangle-file "chronometrist-key-values.org")' \
--eval='(org-babel-tangle-file "chronometrist-spark.org")' \
--eval='(org-babel-tangle-file "chronometrist-third.org")' \
--eval='(org-babel-tangle-file "chronometrist-sqlite.org")' ; \
cd ..
compile: tangle
cd elisp/ && \
emacs --batch \
--eval="(progn (package-initialize) (require 'dash) (require 'ts))" \
--eval='(byte-compile-file "chronometrist.el")' \
--eval='(byte-compile-file "chronometrist-key-values.el")' \
--eval='(byte-compile-file "chronometrist-spark.el")' \
--eval='(byte-compile-file "chronometrist-third.el")' \
--eval='(byte-compile-file "chronometrist-sqlite.el")' ; \
cd ..
lint-check-declare: tangle
cd elisp/ && \
emacs -q --batch \
--eval='(check-declare-file "chronometrist.el")' \
--eval='(check-declare-file "chronometrist-key-values.el")' \
--eval='(check-declare-file "chronometrist-spark.el")' \
--eval='(check-declare-file "chronometrist-third.el")' \
--eval='(check-declare-file "chronometrist-sqlite.el")' ; \
cd ..
lint-checkdoc: tangle
cd elisp/ && \
emacs -q -Q --batch \
--eval='(checkdoc-file "chronometrist.el")' \
--eval='(checkdoc-file "chronometrist-key-values.el")' \
--eval='(checkdoc-file "chronometrist-spark.el")' \
--eval='(checkdoc-file "chronometrist-third.el")' \
--eval='(checkdoc-file "chronometrist-sqlite.el")' ; \
cd ..
lint-package-lint: setup tangle
cd elisp/ && \
emacs --batch \
--eval="(progn (package-initialize) (require 'dash) (require 'ts) (require 'package-lint))" \
-f 'package-lint-batch-and-exit' chronometrist.el \
-f 'package-lint-batch-and-exit' chronometrist-key-values.el \
-f 'package-lint-batch-and-exit' chronometrist-spark.el \
-f 'package-lint-batch-and-exit' chronometrist-third.el \
-f 'package-lint-batch-and-exit' chronometrist-sqlite.el ; \
cd ..
lint-relint: setup tangle
cd elisp/ && \
emacs --batch \
--eval="(progn (package-initialize) (require 'relint))" \
--eval='(relint-file "chronometrist.el")' \
--eval='(relint-file "chronometrist-key-values.el")' \
--eval='(relint-file "chronometrist-spark.el")' \
--eval='(relint-file "chronometrist-third.el")' \
--eval='(relint-file "chronometrist-sqlite.el")' ; \
cd ..
lint: lint-check-declare lint-checkdoc lint-package-lint lint-relint
clean-elc:
rm elisp/*.elc

840
TODO.org
View File

@ -1,3 +1,4 @@
# -*- eval: (add-hook 'org-insert-heading-hook #'(lambda nil (save-excursion (org-set-property "CREATED" (format-time-string "%FT%T%z")))) nil t); -*-
#+TITLE: The Chronometrist TODO List
#+AUTHOR: contrapunctus
@ -7,10 +8,12 @@
* v0.3.0 [100%]
1. [X] +Release or+ bundle plist-pp.el
2. [X] -report - error opening when clocked in
* MELPA, hosting [0%]
1. [ ] merge =chronometrist-goal= into this repository
2. [X] move project to codeberg.org or tildegit.org
3. [X] ...and then, update the MELPA recipes.
* Chronometrist
1. implement point restore for =chronometrist=/=chronometrist-report=/=chronometrist-statistics= in a kill-buffer-hook, so it works whenever the buffer is killed, not just when we call the command.
@ -33,18 +36,13 @@
5. Delete =:stop= time and save
6. Clock out again
7. Accept the already-inserted key-value. Duplicate!
** Features [33%]
1. [ ] New commands
1. [ ] *Kill/discard* (bind to 'k') - which will delete the interval currently being recorded.
- Most conservative option - it will only operate on the project at point, and will only kill for a clocked-in project.
- Somewhat less conservative option - it will operate on the currently clocked-in project, no matter where point is.
- It _should_ ask for confirmation.
- Alternatively, or as a complement - an *undo command*, which will undo your last action (clock in or clock out).
- Undo and redo seem like the best bets.
2. [ ] Hook enhancement - can we supply the whole plist (including tags and key-values) to the task-start hooks, so users can have even smarter hook functions?
** Features [50%]
1. [ ] Hook enhancement - can we supply the whole plist (including tags and key-values) to the task-start hooks, so users can have even smarter hook functions?
* That would mean ensuring that -kv-read runs before other hooks.
* Actually, it should be trivial to access the last expression in the file, so maybe this is unnecessary.
3. [X] Revisit 'no reason' commands - maybe we should ask for tags and key-values with the regular commands, and skip them with the 'no reason' variants?
2. [X] Revisit 'no reason' commands - maybe we should ask for tags and key-values with the regular commands, and skip them with the 'no reason' variants?
*** Key-values [66%]
1. [X] bug - value-history appears in chronological rather than reverse chronological order
2. [X] generate history hash table from chronometrist-file.
@ -76,6 +74,7 @@
15. [X] bug - tag history starts at the beginning (wtf?), value history is empty (wtf?)
* Does not occur on master, only on dev
* Does not occur if you disable lexical binding (introduced in 4e89836)
**** Values
What forms can they take?
1. Integers, floating point numbers - easy to identify via regexp
@ -115,31 +114,62 @@
** Maybe
1. Add a new kind of plist - =(:name "NAME" :time "TIME" ...)=
To record events for which the time interval is not relevant. These won't be shown in =chronometrist= - perhaps in a different buffer.
* Optimization [33%]
Some options and ideas -
1. [X] Defer (tag/key/value) history generation from file-change-time to prompt-time, and make it per-task instead of all tasks at once
* The biggest resource hog is splitting of midnight-spanning intervals, however.
* Reduce memory use by allowing user to restrict number of s-expressions read.
* 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 intervals just for the requested data.
* Will increase load time for each forward/backward command in =chronometrist-report= and =chronometrist-statistics=
* Will reduce memory used by =chronometrist-events=.
+ Further reductions can take place, if we automatically discard cache entries past a certain limit. (perhaps excluding data for the current day, week, or month)
4. [ ] Mix of 2 and 3 - in-memory cache with partial updates
5. [ ] Split and save midnight-spanning intervals to disk - remove the need for an in-memory version of data with split midnight-spanning intervals.
* Least memory use?
* Might make the file harder for a user to edit.
6. [ ] Save timestamps as UNIX epoch time.
* Will (probably) greatly speed up time parsing and interval splitting.
* 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.
7. [ ] Use an SQL database instead of a text file. Assuming SQL can
1. find the difference between ISO-8601 timestamps
2. compare ISO-8601 timestamps, and
3. do 1 and 2 faster than Elisp.
8. [ ] Change data structure - instead of storing each plist as-is, split each into two, one with the =:start= and one with the =:end=. Now we have the elegance of the one-plist-is-a-complete-interval schema in the file, and the ease and speed of detection of midnight spanning intervals in memory.
* Optimization
** DONE Don't update task list from data when user has set their own
:PROPERTIES:
:CREATED: 2022-01-06T22:53:20+0530
:END:
** When clocking in/out, do not save file until hooks are run
:PROPERTIES:
:CREATED: 2022-01-03T16:09:36+0530
:END:
Currently, the file is probably being saved twice - once when we insert/update the record, and once again when a hook function modifies and saves the file. If this is the case, the file-notify callback is probably called twice.
See branch =reduce-saves=
** Don't save file until exit and/or Emacs idle
:PROPERTIES:
:CREATED: 2022-01-03T16:09:04+0530
:END:
A risky strategy - if there is unsaved data during an unclean exit, it will be lost.
** DONE Deferred (tag/key/value) history generation
Defer (tag/key/value) history generation from file-change-time to prompt-time, and make it per-task instead of all tasks at once
+ The biggest resource hog is splitting of midnight-spanning intervals, however.
+ Reduce memory use by allowing user to restrict number of s-expressions read.
+ 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.
** DONE Hash file contents to optimize for common changes
Compare partial hashes of file to know what has changed - only update memory when necessary.
** 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 each forward/backward command in =chronometrist-report= and =chronometrist-statistics=
+ Will reduce memory used by =chronometrist-events=.
* Further reductions can take place, if we automatically discard cache entries past a certain limit. (perhaps excluding data for the current day, week, or month)
** Split and save midnight-spanning intervals to disk
It will remove the need for an in-memory version of data with split midnight-spanning intervals.
+ Least memory use?
+ Might make the file harder for a user to edit.
** Save timestamps as UNIX epoch time
+ Will (probably) greatly speed up time parsing and interval splitting.
+ 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.
- The editing UI could have commands for next/previous interval; one could also have a command which, in the file, opens the plist at point for editing.
** Use an SQL database instead of a text file
That is, assuming SQL can
1. find the difference between ISO-8601 timestamps
2. compare ISO-8601 timestamps, and
3. do 1 and 2 faster than Elisp.
** Change data structure
Instead of storing each plist as-is, split each into two, one with the =:start= and one with the =:end=. Now we have the elegance of the one-plist-is-a-complete-interval schema in the file, and the ease and speed of detection of midnight spanning intervals in memory.
So this
: (:name "Task" ... :start "<timestamp>" :stop "<timestamp>")
@ -149,17 +179,57 @@ Some options and ideas -
(:stop "<timestamp>")
...)
#+END_SRC
9. [ ] Change file timestamp format to =("<iso-date>" "<iso-time>")=
10. [ ] Change file schema to be date-aware -
#+BEGIN_SRC emacs-lisp
** Change file timestamp format to =("<iso-date>" "<iso-time>")=
** Change file schema
Pros
1. Easy to read data for a single date - Chronometrist's most common operation.
2. No more expensive operations on each startup (date checking, splitting of midnight spanning intervals).
3. May be easier to check for midnight-spanning intervals - plists are already grouped by date, just check the first and the last plist per date. Which means, if a user neglects to split a midnight-spanning interval, we can do it for them.
* Make this checking - and also checking for ordering of keys and plists - a command which the user can run at will, rather than something being run automatically.
4. May reduce memory and disk space used - hash table keys are already dates, so =:start= and =:stop= can just be ISO times rather than date-times.
Cons
1. Opting to remove dates from intervals might make it difficult for users to recover from erroneous edits.
*** Store plists grouped by date
#+BEGIN_SRC emacs-lisp
(:date "YYYY-MM-DD"
(:name "task 1"
...
:start "HH:MM:SSZ"
:stop "HH:MM:SSZ")
...)
#+END_SRC
1. Users will have to be disciplined while editing, and ensure that events are split on day boundaries. If not, we will have to check each time the file changes, defeating the entire optimization.
;; or
("YYYY-MM-DD"
(:name "task 1"
...
:start "HH:MM:SSZ"
:stop "HH:MM:SSZ")
...)
#+END_SRC
Cons
1. Users will have to be disciplined while editing, and ensure that events are split on day boundaries. If not, we will have to check each time the file changes, defeating the entire optimization.
* Not quite - it's less work to test that, when the intervals are grouped by date.
*** Persist hash table to file
Instead of storing plists in the file, persist the hash table itself.
Pros
+ When editing manually, the whole file can be reindented with a single =sp-indent-defun=.
+ Stored data can be modified by any Emacs Lisp functions rather than functions specific to our file format.
- This may also be possible with [[*Store plists grouped by date][plists grouped by date]], if we serialize the hash table as plists rather than - as we do now - directly adding individual plists to the file.
Cons
1. Read syntax not portable to other dialects or languages, e.g. if one wants to make an Android frontend in LambdaNative (Gambit Scheme)
* A language with reader macros, such as Common Lisp, might work.
* Or, use [[*Store plists grouped by date][plists grouped by date]].
2. Slightly noisier syntax than plain plists.
3. Users cannot create comments within the hash table expression.
* Couldn't they use plist keys for comments instead? 🤔
4. May increase memory consumption - the entire hash table will be loaded into memory.
** Cache
+ 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.
@ -167,7 +237,8 @@ Some options and ideas -
+ 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
** (old) 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.
@ -178,7 +249,6 @@ Some options and ideas -
* 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.
** Clocking in/out might be too slow for my liking
* Certain
1. [ ] statistics UI for arbitrary queries
@ -186,6 +256,7 @@ Some options and ideas -
* we show buffer with
+ matched unique tag groups, and sparklines for time spent on each
+ matched key-values, and sparklines for time spent on each
** plist-pp [66%]
1. [X] plist-pp - work recursively for plist/alist values
2. [ ] Fix alignment of alist dots
@ -317,7 +388,7 @@ Some options and ideas -
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%]
** UX [33%]
1. [X] Optimization - (jonasw) store length and hash of previous file, see if the new file has the same hash until old-length bytes.
* [X] Check for type of change to file
+ [X] Handle last expression being removed
@ -325,30 +396,18 @@ Some options and ideas -
* [X] BUG - if something was removed from the last expression (thereby decreasing the length of the file), =chronometrist-file-change-type= returns =t= instead of =:last=
* [X] BUG - args out of range error when last plist is removed
2. [X] Optimization - generate history before querying, not when the file changes.
3. [ ] Don't suggest nil when asking for first project on first run
4. [ ] When starting a project with time of "-" (i.e. not worked on today until now), immediately set time to 0 instead of waiting for the first timer refresh
5. [ ] Mouse commands should work only on buttons.
6. [X] Button actions should accept prefix arguments and behave exactly like their keyboard counterparts.
7. [ ] mouse-3 should clock-out without asking for reason.
8. [ ] Some way to ask for the reason just before starting a project. Even when clocking out, the reason is asked /before/ clocking out, which adds time to the project.
9. [ ] Allow calling chronometrist-in/out from anywhere-within-Emacs (a la timeclock) as well as from the chronometrist buffer.
10. [ ] =chronometrist-timer= - if =chronometrist-file= is being edited (buffer exists and modified), don't refresh - this will (hopefully) prevent Emacs from going crazy with errors in trying to parse malformed data.
3. [ ] When starting a project with time of "-" (i.e. not worked on today until now), immediately set time to 0 instead of waiting for the first timer refresh
4. [ ] Mouse commands should work only on buttons.
5. [X] Button actions should accept prefix arguments and behave exactly like their keyboard counterparts.
6. [ ] mouse-3 should clock-out without asking for reason.
7. [ ] Some way to ask for the reason just before starting a project. Even when clocking out, the reason is asked /before/ clocking out, which adds time to the project.
8. [ ] Allow calling chronometrist-in/out from anywhere-within-Emacs (a la timeclock) as well as from the chronometrist buffer.
9. [ ] =chronometrist-timer= - if =chronometrist-file= is being edited (buffer exists and modified), don't refresh - this will (hopefully) prevent Emacs from going crazy with errors in trying to parse malformed data.
* Maybe
** New features [14%]
** New features
1. [ ] Interact with Chronometrist from phone, tablet, or smart watches. (Help needed, I'm a total strange to mobile development and don't own any wearables.)
2. [ ] Some way to use markup (Markdown, Org, etc) for certain plist values.
Implementation ideas -
* A list of keys whose values are to be edited in a user-specified major mode.
+ Multiple windows - instead of a single key-value buffer, we'll have multiple buffers in multiple visible windows, somewhat like =ediff=. The =accept= command will use the data from all involved buffers.
- The buffer and window will be created when a keyword associated with that mode is selected at the prompt.
+ Alternatively, the whole plist goes into a single buffer of the markup's major mode - the markup bits as markup, the rest of the plist in a code block 🤷‍
+ poly-mode to mix different modes
+ see [[info:elisp#Swapping Text][swapping text]]
* "Input frontends" - a way to represent s-expressions as Markdown, Org, etc, so the entire plist can be edited in that mode. As a side-effect, this will permit use of Markdown, Org, etc in keyword-values - e.g. to use markup in comments or notes.
* A binding in the key-value buffer, which will insert the string at point in a buffer of a certain mode.
3. [ ] /Task List/ - a custom variable containing a list of tasks
2. [ ] /Task List/ - a custom variable containing a list of tasks
#+BEGIN_SRC emacs-lisp
("A Task Name"
("Another Task Name" :key-prompt nil)
@ -373,11 +432,11 @@ Some options and ideas -
1. Adding tasks without clocking into them (the list is stored in a separate file)
2. Not asking for tags and/or key-values for a particular task, or having a special behaviour for a task. (e.g. some tasks I use follow certain patterns, which I'd like to automate away)
3. defining goals (subsume =chronometrist-goal-list=), priorities, etc
4. [ ] Completion for sub-plists - if the value of a user keyword-value pair is itself a plist, can we reuse the keyword-value prompt for it? 🤔
3. [ ] Completion for sub-plists - if the value of a user keyword-value pair is itself a plist, can we reuse the keyword-value prompt for it? 🤔
* Maybe generate the completion hash table when the plist is created, since this is likely to be less-used.
5. [ ] Create a debug mode
6. [ ] Create a verification command to test =chronometrist-file= for errors.
7. [X] Display task's weekly progress using ASCII/sparklines
4. [ ] Create a debug mode
5. [ ] Create a verification command to test =chronometrist-file= for errors.
6. [X] Display task's weekly progress using ASCII/sparklines
* Extension to add new column in =chronometrist= buffer
* without goals - show dashes for days on which task was not touched, x for days on which it was done. e.g.
+ =- x x x - - *= means today is a Saturday, that I did the task on three days (Mo, Tu, We), and that I'm doing it right now.
@ -413,7 +472,27 @@ Some options and ideas -
- untouched project with target defined - red
- target ±5 minutes - green
- target*2 and above - red
* documentation discoverability :doc:
* viewing/editing frontends
:PROPERTIES:
:CUSTOM_ID: viewing-editing-frontends
:END:
1. Some way to use markup (Markdown, Org, etc) for certain plist values
2. Some way to enter/edit key-values and other data, for people who don't know Lisp
** Implementation ideas
*** "Input frontends"
A way to represent s-expressions as Markdown, Org, etc, so the entire plist can be edited in that mode. As a side-effect, this will permit use of Markdown, Org, etc in keyword-values - e.g. to use markup in comments or notes.
*** A list of keys whose values are to be edited in a user-specified major mode.
* Multiple windows - instead of a single key-value buffer, we'll have multiple buffers in multiple visible windows, somewhat like =ediff=. The =accept= command will use the data from all involved buffers.
+ The buffer and window will be created when a keyword associated with that mode is selected at the prompt.
* Alternatively, the whole plist goes into a single buffer of the markup's major mode - the markup bits as markup, the rest of the plist in a code block 🤷‍
* poly-mode to mix different modes
* see [[info:elisp#Swapping Text][swapping text]]
*** A binding in the key-value buffer, which will insert the string at point in a buffer of a certain mode.
* documentation discoverability :doc:
Ensure that the user manual is easily discoverable.
#+BEGIN_QUOTE
@ -427,9 +506,15 @@ 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:
<2021-06-07T16:33:54+0530>
1. [X] command (and menu item) to launch Org LP
2. provide =:link= argument to =defgroup=
3. open changelog on update
* macro for extensions :code:extension:
:PROPERTIES:
:CREATED: 2021-06-07T16:33:54+0530
:END:
A macro to create new columns for Chronometrist.
Extension writer specifies
@ -452,9 +537,32 @@ 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:
<2021-06-08T11:17:54+0530>
Current uses -
1. =chronometrist-goal=
2. =chronometrist-spark=
* macro for frontends :code:
:PROPERTIES:
:CREATED: 2021-06-26T08:49:25+0530
:END:
A macro to create Chronometrist frontends (based on =tabulated-list-mode=). If implemented, would shorten code for all four existing and two planned frontends.
Programmer specifies -
1. frontend command name (a symbol)
2. name (string, passed to =define-derived-mode=)
3. rows function
4. schema value
5. name of buffer created by command
Macro creates -
1. =frontend= interactive command, which behaves like a toggle
2. function =frontend-rows=
3. custom variables =frontend-schema=, =frontend-row-transformers=, =frontend-schema-transformers=, =frontend-buffer-name=
* unified format-duration function :code:customization:
:PROPERTIES:
:CREATED: 2021-06-08T11:17:54+0530
:END:
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.
+ user provides a duration (in seconds), a format string, and an optional separator string
@ -490,8 +598,10 @@ Alternative syntax
+ to display only values, use ="%<code>"=
+ to display long units, use ="~[<separator>]<code>"=
* DONE error - =min= called with nil :spark:bug:
<2021-06-11T03:44:17+0530>
* DONE error - =min= called with nil :spark:bug:
:PROPERTIES:
:CREATED: 2021-06-11T03:44:17+0530
:END:
1. clock in
2. change =:start= of active interval to another time on the same date
3. error
@ -544,8 +654,10 @@ 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:
<2021-06-15T16:18:49+0530>
* STARTED discoverability and mouse-accessibility of commands [33%] :ux:
:PROPERTIES:
:CREATED: 2021-06-15T16:18:49+0530
:END:
Goals
1. discoverability of commands
2. discoverability of default keybindings
@ -560,33 +672,573 @@ 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:
<2021-06-16T07:50:21+0530>
* querying the file buffer and editing the results :feature:
:PROPERTIES:
:CREATED: 2021-06-16T07:50:21+0530
:END:
=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.
1. The result data may just be plists, which could be displayed/edited directly from the search results ([[info:elisp#Invisible Text][invisible text]] or [[info:elisp#Selective Display][selective display]] +
[[info:elisp#Narrowing][narrowing]]?)
1. The result data may just be plists, which could be displayed/edited directly from the search results ([[info:elisp#Invisible Text][invisible text]] or [[info:elisp#Selective Display][selective display]] + [[info:elisp#Narrowing][narrowing]]?)
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:
<2021-06-16T18:40:18+0530>
* error in change type detection :core:bug:plist_backend:
:PROPERTIES:
:CREATED: 2021-06-16T18:40:18+0530
:END:
Steps
1. Clock in
2. Delete active task plist, but don't save
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:
: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:
* STARTED New frontends and enhancements :feature:
** Existing frontends
*** chronometrist (overview for a day)
list of tasks, one day, durations and graphs
+ commands [0%]
1. [ ] previous/next day
2. [ ] set day (blank input to reset to today's date)
3. [ ] set duration format
4. [ ] display sparkline for total time
*** report
list of tasks, one week, durations only
*** statistics
list of tasks, one week/month/year [fn:1]
*** details (intervals for a day) [16%]
list of intervals, one day [fn:1]
+ [-] commands [50%]
1. [X] set [task/key-value] filter [fn:2]
2. [X] set range
1. implement timestamp ranges (e.g. =2021-06-01T12:00+05:30= to =2021-07-03T00:10:29+0530=)
3. [ ] previous/next day
4. [ ] set duration format
+ [ ] with =spark= - vertical sparkline for each interval
+ [ ] non-tabular text [fn:3]
+ [X] when range is a pair with the =car= or the =cdr= being blank, set the start/stop date to the earliest/latest date available.
* not possible with =completing-read-multiple=, which removes blank strings; the simplest solution was to allow "begin"/"end" as inputs.
+ [ ] when range is bigger than a day, you also want to see the date of each interval
Bugs
1. [ ] day-crossing events have the wrong duration and time. e.g. ~1 Exercise walking 30 minutes from 23:37 to 00:07~
** New frontends I want
*** task-key-values
list of unique key-values for a task, one day [fn:1], durations and graphs
+ commands [0%]
1. [ ] set task
2. [ ] set range
*** task-graph [0%]
list of days, weeks, or months [fn:1], one task (with optional key-value filter [fn:2]), horizontal graph (and durations/stats?)
+ columns
1. range (ISO date, week, or year-month)
2. per-range graph (vertical/interals if days, horizontal/days if weeks or months)
+ [ ] commands [0%]
1. [ ] toggle week/month
2. [ ] set [task/key-value] filter
3. [ ] set range
+ [ ] non-tabular text [fn:3]
[fn:1] variable range
[fn:2] keys - show intervals with those keys; key-values - intervals with those values; or predicate
[fn:3]
#+BEGIN_SRC
"(Showing intervals|No intervals to show)
[(with (<keyword>*|<keyword-value>*)|
matching <predicate>)]
(for <date>|
between (<date>|<datetime>) and (<date>|<datetime>)|
for <predicate>)"
#+END_SRC
* STARTED customizable duration output :feature:
:PROPERTIES:
:feature: frontend
:branch: format-seconds
:END:
<2021-06-22T04:57:06+0530>
Frontend similar to =chronometrist=, but specific to one task - displays its unique tag/key-value combinations and their sparklines. Time period (day, week, etc) could be customizable.
1. [X] define =chronometrist-duration-formats= to hold duration formats for different use cases in Chronometrist.
2. [ ] define customization type for =chronometrist-duration-formats= to create user-friendly Custom interface
3. [ ] make non-tabular parts of Chronometrist buffers adapt to column widths, to accomodate changes in duration formats
* Incorrect time displayed when midnight crossed with a task active :bug:
Midnight-spanning intervals are split in the hash table when the file is read, but not when a task is started and not stopped before midnight. Run a function to check at midnight?
* Duplication - accessing the latest-record :code:
Many functions use the latest record. A =(chronometrist-with-latest-record var &rest body)= might help...
* STARTED Plist-group backend [71%] :feature:optimization:
See branch =grouped-plist-format=
1. [X] Migrate backend code to use CLOS
2. [-] Implement plist-group backend methods
3. [ ] <<dates-in-timestamps>> Omit dates from plists
4. +[ ] Mark split intervals with =:split t=+
* Not required, since we can detect them with enough certainty for my liking. (See =chronometrist-plists-split-p=)
5. [X] make generic =cl-loop= backend interface - =chronometrist-loop-sexp-file=
6. [X] =chronometrist-loop-sexp-file= - optimize grouped-plist iterator
7. [X] key-values code is not yet generic
8. [ ] update plist-pp to indent tagged lists differently
9. [X] optimize program startup and file change callback
10. [X] remove all references to =chronometrist-events= (hash table)
11. [X] create new tagged list if required when clocking in
12. [X] split midnight-spanning intervals when clocking out
13. [X] interactive backend-switching command with completion; reset backend state after switching to account for changes made to the file in the meantime
14. [ ] (maybe) handle existing (in-file) split tasks. Currently, if an interval is split, commands such as =chronometrist-key-values-unified-prompt= operate on the second split interval, but not the first. Instead, plist-group methods for =chronometrist-replace-last= and =chronometrist-remove-last= could check if the last interval is split, and operate on both of them. (=chronometrist-insert= already splits plists when inserting.)
* Don't modify =chronometrist-latest-record=; code using it generally only wants the latter half of a split plist.
* See branch =plist-group-handle-split-plists=
15. [X] use file-change-detection optimization
* [X] fix bugs in change-type detection
16. [ ] fix bug - extra newlines in file
Optional
1. define generic backend protocol for key-values package
protocol implementation progress
1. [X] chronometrist-latest-date-records
2. [X] chronometrist-latest-record
3. [X] chronometrist-task-records-for-date
4. [X] chronometrist-active-days
5. [X] chronometrist-insert
6. [X] chronometrist-remove-last
7. [X] chronometrist-replace-last
8. [X] [superclass] chronometrist-create-file
9. [ ] chronometrist-view-file
10. [X] [superclass] chronometrist-edit-file
11. [ ] chronometrist-count-records
12. [X] chronometrist-to-list
13. [X] chronometrist-to-hash-table
14. [X] chronometrist-to-file
15. [X] chronometrist-on-change
16. [X] [superclass] chronometrist-reset-internal
17. [ ] chronometrist-backend-empty-p
18. [X] [superclass] chronometrist-memory-layer-empty-p
19. [ ] chronometrist-verify
20. [ ] [superclass] chronometrist-timer
* New backends :feature:
** Org time tracking
** timeclock
See docstring of =timeclock-log-data=
** [[https://klog.jotaen.net/][klog]]
:PROPERTIES:
:CUSTOM_ID: new-backends-klog
:END:
** SQLite [0%]
Chronometrist allows the user to add arbitrary key-values. There are a few ways to do this in SQL, but [[https://mariadb.com/kb/en/entity-attribute-value-implementation/][this solution]] seems well-suited to Chronometrist - put commonly-queried keys (name, start, stop) into SQL columns, and put user key-values in a JSON/s-exp TEXT column. The latter can be queried separately. SQLite has support for [[https://www.sqlite.org/json1.html][JSON columns]].
1. [ ] Use =string-remove-prefix= rather than =s-chop-prefix=
2. [-] =to-file=
* [X] insert intervals
* [ ] insert date properties
* [ ] insert events
** Project Hamster
https://github.com/projecthamster/hamster
+ https://github.com/projecthamster/hamster/wiki/Our-datamodel
** [[https://gitlab.com/Harag/cl-naive-store][cl-naive-store]]
:PROPERTIES:
:CREATED: 2022-04-16T21:11:07+0530
:END:
Reimplementing the plist/plist-group backends in Common Lisp, without the buffer visualisation and buffer-movement commands provided by Emacs/Elisp, is not something I'm very enthusiastic about. Even if those turn out to not be major factors, implementing these backends in Elisp taught me that making your own database can be a lot of work. Especially if you just care about your application, and writing a database was not what you set out to do (I severely underestimated the work involved).
Yet, some may prefer the plist-group backend to an SQLite database. I trust that a cl-naive-store backend could have the same capabilities as the plist-group backend, but without the maintenance burden of an /ad hoc/ s-expression store.
* Functions doing similar things :code:
1. =task-time-one-day=
2. =interval= (unused)
3. =events-to-durations=
* Use ISO date for functions operating on dates :time:format:
* STARTED customizable task list [33%] :feature:
1. [X] Make =chronometrist-task-list= customizable
2. [ ] Interactive, buffer-local modification of task list, with completion (=completing-read-multiple=)
3. [ ] Adding a task? We can modify the task list, but how to persist it?
* Extend time range prompt :feature:
Support inputs like "today", "yesterday", "5 days ago", etc.
* =date(1)= does something like this, right? Maybe we could shell out to it.
A general library for this could convert between (both to and from) such strings and TS structs.
* today, yesterday, day before yesterday, tomorrow, day after tomorrow
* N <seconds/minutes/hours/days/weeks/months/years> (ago|from now/today)
+ plus multiples of those units e.g. "1 year, 5 months, ... from now"
* DONE Kill/discard command :feature:
Command to delete the interval currently being recorded. (bind to 'k')
+ Most conservative option - it will only operate on the project at point, and will only kill for a clocked-in project.
+ Somewhat less conservative option - it will operate on the currently clocked-in project, no matter where point is.
+ It _should_ ask for confirmation.
+ Alternatively, or as a complement - an *undo command*, which will undo your last action (clock in or clock out).
- Undo and redo seem like the best bets.
* pretty printer
** fix handling of tagged alist group values :bug:
** put each list element on its own line if length of list exceeds fill-column :feature:
* verify command [20%] :feature:
:PROPERTIES:
:CUSTOM_ID: verify-command
:END:
With different checks, for different levels of speed/thoroughness -
1. [X] (plist group) Sequence of plist group dates
2. [ ] Check that every record (except the last) has a =:stop=
3. [ ] Intersecting timestamps
4. [ ] Sequence of records
5. [ ] (plist group) Midnight spanning interval check (first and last intervals)
6. [ ] (plist group) Check that plist timestamps have the correct date. Only applicable [[dates-in-timestamps][as long as they have a date.]]
* format changes
** multiple intervals per record :feature:
:PROPERTIES:
:CUSTOM_ID: multiple-intervals-per-record
:END:
#+BEGIN_SRC emacs-lisp
(:name "<name>"
[<keyword-value pair>] ...
:intervals
(("<start>" . "<stop>")
...))
#+END_SRC
It doesn't do anything not already possible in the current formats. At best, it removes some duplication when the same task is "paused" and "resumed".
+ Sometimes you pause and resume a task and don't want to split your key-values between >1 intervals (to avoid messing up completion suggestions for the future). An alternative means to the same end could be to add a key like =:deduct "<duration>"= or =:deduct ("<start time>" . "<stop time>")=.
- This will also make it easier to support formats like [[#new-backends-klog][klog]], which support this feature.
- It will probably complicate all data consuming code, though...think of =chronometrist-details= 🤔
- An alternative idea could be to define a custom variable to hold the user's key values. If this variable is defined, it would be used instead of generating suggestions from past key-values. That way, such situations will not affect key-value suggestions.
** "event records" - records with only a timestamp :feature:
:PROPERTIES:
:CREATED: 2021-12-18T11:48:53+0530
:END:
Records not used for time tracking, but to store data associated with a date or timestamp. More than one record may exist for the same date.
#+BEGIN_SRC emacs-lisp
(event "<date or timestamp>"
[<keyword-value pair>] ...)
#+END_SRC
** STARTED tagging dates with key-values [0%] :feature:
:PROPERTIES:
:CREATED: 2022-02-10T22:59:45+0530
:CUSTOM_ID: date-key-values
:branch: date-properties
:END:
Straightforward enough for the plist group backend -
#+BEGIN_SRC elisp
("<date>"
[<keyword> <value>]*
<plist>+)
#+END_SRC
Alternatively, this would be easier to work with - easier to select the key-values or the plists, as required -
#+BEGIN_SRC elisp
("<date>"
(key-values
[<keyword> <value>]+)
<plist>+)
#+END_SRC
=to-list= could omit the date key-values, since it has no concept of dates. =to-hash-table= could have them at the start of the lists of plists, i.e. hash values would still be =(cdr <plist group>)=.
The plist backend could theoretically be extended to support it, but I'd rather deprecate that format (since it suffers from performance issues) and not deal with it again.
1. [ ] Update call sites of =chronometrist-latest-date-records= (which may now also contain key-values)
2. [ ] Update code accessing the hash table
3. [ ] Update =plist-pp= to handle
* undesired file watcher created after literate-elisp-load :bug:
:PROPERTIES:
:CREATED: 2021-12-18T15:13:35+0530
:END:
1. Type =M-x chronometrist=; =(chronometrist-backend-file-watch (chronometrist-active-backend))= probably returns a descriptor
2. Visit literate source and type =M-x literate-elisp-load=; =(chronometrist-backend-file-watch (chronometrist-active-backend))= returns nil
3. Type =M-x chronometrist=; =file-notify-descriptors= likely contains two descriptors for the same file and the same function (=chronometrist-file-refresh=)
=chronometrist-register-backend= removes the old backend object and replaces it with a new one.
* backend migration [0%]
:PROPERTIES:
:CREATED: 2022-01-08T23:32:37+0530
:END:
1. [ ] Use active backend as the suggested input backend when migrating
2. [ ] Conserve file local variables prop line for text backends
** make migrate command async [33%]
:PROPERTIES:
:CREATED: 2022-04-01T18:02:03+0530
:END:
=chronometrist-migrate= / =chronometrist-to-file= can take a long time to run, freezing Emacs until completion if run synchronously.
1. [X] basic async export
2. [ ] display message on start and completion
3. [ ] test the INPUT-FILE argument
* STARTED Support for the Third Time System [57%] :extension:
:PROPERTIES:
:CREATED: 2022-02-10T14:12:12+0530
:branch: third-time
:END:
[[https://www.lesswrong.com/posts/RWu8eZqbwgB9zaerh/third-time-a-better-way-to-work][Third Time: a better way to work]]
#+BEGIN_QUOTE
Lastly, here in one place are all the steps for using Third Time:
1. Note the time, or start a stopwatch
2. /Work/ until you want or need to break
3. Divide how long youve just worked by 3 (or use your chosen fraction), and add any minutes left over from previous breaks
4. Set an alarm for that long
5. /Break/ until the alarm goes off, or you decide to resume work
6. If you resume early, note how much time was left, to add to your next break
7. Go back to step 1.
Additional rules:
+ If you have to stop work for a /non-work-related interruption/, start a break immediately.
+ You can (optionally) take a /big break/ for lunch and/or dinner, lasting as long as you like. Set an alarm at the start for when youll resume work. A big break uses up any saved break minutes, so you cant carry them over to the afternoon/evening.
+ /Avoid taking other unearned breaks/ if possibleso try to do personal tasks during normal or big breaks, or before/after your work day.
#+END_QUOTE
1. [X] =chronometrist-third-fraction=
2. [X] =chronometrist-third-break-time=
3. [X] on clock out, increment =break-time= and start timed notification
4. [X] on clock in, calculate duration of latest break (duration between last =:stop= and now), and subtract it from =break-time=; if the resulting time is negative, set it to zero.
5. [ ] "big break" command - user enters a time, for which we set an alarm. User must resume working around that time.
6. [ ] Define work hours
* [ ] Custom variable for default work hours.
* [ ] Hook function which inserts default work hours.
* [ ] Hook function which prompts for work hours whenever you first clock in on a date. If work hours are defined in the custom variable, ask whether to use them - on negative answer, prompt for today's work hours. If work hours are not defined, prompt for today's work hours.
7. [ ] Displaying/recording breaks. Probably done implicitly - when work hours are defined, any time not spent working can be interpreted as break time.
** Example flow
1. work for 30m
2. clock out - add 10m to =break-time=
3. clock in after a 5m break - subtract 5m from =break-time=
** Extras
1. persist =break-time= between Emacs sessions
2. audible alerts
3. handle user edits to the database
* last record modified - ?
* remove last record - ?
* example - interval extended
1. work 10 minutes and clock out - +3m break time
2. edit stop time to add +20 minutes of work (30m total)
* compare with old data in hash table - decrement break time added by old plist, increment break time added by new plist
* STARTED Customizable alerts [50%] :feature:code:
:PROPERTIES:
:CREATED: 2022-02-14T08:22:36+0530
:CUSTOM_ID: customizable-alerts
:branch: alert-macro
:END:
=chronometrist-third= and =chronometrist-goal= have nearly identical alert code; additionally, users cannot customize the alert style per-alert without basically rewriting the alert functions.
Convert =chronometrist-third-alert-functions= and =chronometrist-goal-alert-functions= to customizable alists[fn:4] whose entries take the form =(SYMBOL FUNCTION ALERT-FN &rest ALERT-ARGS)=, where
+ =SYMBOL= uniquely identifies the alert,
+ =FUNCTION= is a function calling =chronometrist-*-run-at-time= and =ALERT-FN=, and
+ =ALERT-ARGS= are passed to =ALERT-FN=.
=ALERT-FN= will usually be =alert=, and =ALERT-ARGS= will usually be keyword arguments passed to =alert=.
Similar to how they behave now, these packages will start/stop functions for all entries provided in these alists. (So the user can still control which alerts are run.)
Also define =chronometrist--timer-alist=, which associates =symbol= with a timer object.
1. [X] write macro
2. [ ] use in =chronometrist-goal= and =chronometrist-third=
[fn:4] Actually, make a macro to define such alists, since the docstrings are likely to be near-identical.
* DONE Predefined key-values for tasks
:PROPERTIES:
:CREATED: 2022-02-17T23:34:17+0530
:END:
Benefits
1. Speeds up key-value completion for very large data sets.
2. Prevents key-value suggestions from being affected by [[#multiple-intervals-per-record][splitting of tasks across multiple intervals]].
#+BEGIN_SRC emacs-lisp
'(("Task" <plist> ...)
...)
#+END_SRC
* no-goal-alert - "You have spent on <task>" [0%] :bug:
:PROPERTIES:
:CREATED: 2022-02-25T00:19:49+0530
:END:
=chronometrist-goal-no-goal-alert= uses =chronometrist-task-time-one-day=, which calls =chronometrist-task-records-for-date= with the /current/ date.
1. [ ] Use the data for the latest date recorded in the database, rather than the current date.
* better error reporting
:PROPERTIES:
:CREATED: 2022-02-28T11:35:37+0530
:END:
Similar to [[#verify-command][verify command]], perhaps, but this is really about making error messages thrown by the program more meaningful to the user.
** record does not contain intervals
:PROPERTIES:
:CREATED: 2022-02-28T11:35:47+0530
:END:
* User-defined categories
:PROPERTIES:
:CREATED: 2022-03-31T22:44:34+0530
:END:
A task can be part of any number of categories. A category may also contain other categories. The categories, and the total time spent on each one, could be displayed in the main view, separate from the tasks.
* First run behaviour :ux:
:PROPERTIES:
:CREATED: 2022-04-02T17:28:33+0530
:END:
1. [ ] Look for any existing Chronometrist database in the default location. If one is found, use it to determine the default active backend. Otherwise, offer to import from one of the supported backends.
+ Some backends may not be installed - "see MELPA for more formats"
2. [ ] Don't suggest nil when asking for first project on first run
* STARTED Common Lisp port
:PROPERTIES:
:CREATED: 2022-04-03T09:36:53+0530
:END:
** DONE Replace =add-variable-watcher=
Common Lisp does not have Elisp's =add-variable-watcher=. The Elisp code used that to automatically update the =file= slot of the active backend object when the user changes the value of =chronometrist-file=. The new solution is to remove direct accesses to the =file= slot, define a =backend-file= method which returns the value of =file= if non-nil (it's usually nil), or derives a file path from =*user-data-file*= and the backend's extension.
It seems that the =file= slot is now unnecessary - =backend-file= can check =path= instead of =file=, and use either =path= or =*user-data-file*= to derive the full path.
1. [X] Remove the =file= slot
** DONE Use pathnames instead of namestrings
:PROPERTIES:
:CREATED: 2022-04-03T12:30:24+0530
:END:
** CLIM UI design
*** One-day task-duration table
:PROPERTIES:
:CREATED: 2022-04-10T08:09:18+0530
:END:
columns
1. index (for numeric argument)
2. task name
3. task duration today
4. task activity indicator
* click to clock in/out (hover to show icon)
5. graph of daily task durations
* Click on a point in the graph to view that day in the Details View.
click on row to show taller graph and inline display of either key-value breakdown, or interval breakdown.
commands
1. clock in, clock out, etc
2. toggle details for row
3. toggle details for all rows
4. next/previous day
5. set displayed day
6. cycle graph heights (self/comparative)
* the height of the graphs can be based on the range of each individual graph's own data ('self') or based on the range of all data ('comparative').
settings
1. default graph height
*** One-day category-duration table
:PROPERTIES:
:CREATED: 2022-04-10T08:11:20+0530
:END:
1. category
2. time duration today
commands
1. toggle details for row (show tasks in each category)
2. toggle details for all rows
*** One-day event-occurrence table
:PROPERTIES:
:CREATED: 2022-04-10T08:12:58+0530
:END:
("Useful for recording things like weight, medication, waist circumference, etc.")
1. index (prefix argument)
2. event name
3. time of occurrence
4. graph of past occurrences
commands
1. add event
2. record event
*** 1+ day editable log
:PROPERTIES:
:CREATED: 2022-04-08T18:23:42+0530
:END:
Displays tables of day properties, intervals, and events, grouped by day, in date/week/month/year range (default - today)
can select a day property/interval/event to edit its data
buttons to add new day properties, intervals, and events
When there are no intervals/events/day properties, show placeholder text - "No intervals recorded on this day." / "No events recorded on this day." / "No properties for this day."
Commands
1. filter contents
2. next/previous day (or range)
3. set displayed day (or range)
*** Line chart views
:PROPERTIES:
:CREATED: [2022-04-10 Sun 14:35]
:END:
Line chart with one or more (adjustable) metrics -
* duration for task
* task-property combinations
* numeric property value/function with numeric output
...over a time range (adjustable), with points for days/weeks/months/years (adjustable)
User can switch between single graph vs separate graphs, different types of graphs, etc.
Commands
1. new/open/save/delete view
2. add/remove metric (a line on the graph)
3. increase/decrease/set range, next/previous range
4. increase/decrease/set granularity
*** Default view (overview for a day)
Default view is a tiled composition of the one-day task-duration table, one-day grouped log, and the one-day category-duration table.
*** view/edit database as
:PROPERTIES:
:CREATED: 2022-04-08T19:56:38+0530
:END:
some way to view/edit the database in a different format, e.g. Lisp plists
* Documentation restructuring :doc:
:PROPERTIES:
:CREATED: 2022-04-14T18:04:21+0530
:END:
1. Move documentation from LPs to manual.org
2. Revamp tutorials - https://diataxis.fr/tutorials/#the-language-of-tutorials
3. Make how-to guides more general?
4. Move some how-to guide code into a contrib directory?
** tutorial and how-to topic revamp
tutorials
1. installation (MELPA) -> first run
2. adding the first task
3. clocking in, clocking out
4. customization - open a file when starting a task (move from how-to)
5. customization - warn when exiting with an active task
6. clocking in/out without running hooks
7. restarting or discarding an activity
8. key-value extension - installation -> customization -> testing/results
9. graph extension - installation -> customization -> testing/results
how-to guides
1. how to install (Git, MELPA, Quelpa, etc)
2. how to attach tags and properties to time intervals (cover different commands)
3. how to configure Emacs for Chronometrist development
* Concurrent tasks
:PROPERTIES:
:CREATED: 2022-04-19T22:12:54+0530
:END:
I'm not interested in using this myself, so it must be possible to enable/disable it.
+ Enabling/disabling it would just affect how the clock-in command works. Perhaps data verification, too.
How might it affect the data consumer?
1. Calculation of total time tracked (in any range, e.g. a day) will definitely require a more complex algorithm.

9
cl/backend-sqlite.asd Normal file
View File

@ -0,0 +1,9 @@
(defsystem chronometrist-sqlite
:version "0.0.1"
:serial t
:license "Unlicense"
:author "contrapunctus <contrapunctus at disroot dot org>"
:description "SQLite backend for Chronometrist"
:defsystem-depends-on ("literate-lisp")
:depends-on (:trivia :clsql-sqlite3)
:components ((:org "chronometrist")))

3321
cl/chronometrist.org Normal file

File diff suppressed because it is too large Load Diff

1605
cl/clim.org Normal file

File diff suppressed because it is too large Load Diff

9
cl/lib.asd Normal file
View File

@ -0,0 +1,9 @@
(defsystem chronometrist
:version "0.0.1"
:serial t
:license "Unlicense"
:author "contrapunctus <contrapunctus at disroot dot org>"
:description "Friendly and extensible personal time tracker - common library"
:defsystem-depends-on ("literate-lisp")
:depends-on (:trivia)
:components ((:org "chronometrist")))

32
cl/manual.org Normal file
View File

@ -0,0 +1,32 @@
* Explanation
:PROPERTIES:
:CUSTOM_ID: explanation
:END:
This is a port of Chronometrist to Common Lisp.
Currently, it contains
1. a read-only plist-group backend
2. an incomplete SQLite backend
3. an incomplete CLIM frontend
** Source code overview
:PROPERTIES:
:CUSTOM_ID: source-code-overview
:END:
*** CLIM frontend
:PROPERTIES:
:CUSTOM_ID: clim-frontend
:END:
The CLIM frontend uses CLIM panes for each view of data Chronometrist provides. Each pane has a [[file:chronometrist.org::#display-pane][=display-pane=]] method to display its contents.
Currently, only one CLIM pane has been implemented, called the [[file:chronometrist.org::#task-duration-table-pane][task-duration table]]. By default, it displays a list of tasks, and each of their durations for today.
**** Tables
:PROPERTIES:
:CUSTOM_ID: tables
:END:
The tables in the CLIM frontend are designed with ease of extensibility in mind. Thus -
1. The columns displayed in this table are controlled by [[file:chronometrist.org::#*task-duration-table-spec*][=*task-duration-table-spec*=]], which contains a list of [[file:chronometrist.org::#column-specifier][=column-specifier=]] objects.
2. Each [[file:chronometrist.org::#column-specifier][=column-specifier=]] has two methods specializing on it, a [[file:chronometrist.org::#cell-data][=cell-data=]] method and a [[file:chronometrist.org::#cell-print][=cell-print=]] method. These determine the data contained by the cells of the column, and how that data is printed in the CLIM pane.
The function [[file:chronometrist.org::#task-duration-table-function][=task-duration-table-function=]] uses the [[file:chronometrist.org::#cell-data][=cell-data=]] methods to return the data of the table as a list of lists. This data is used by [[file:chronometrist.org::#display-pane][=display-pane=]] in conjunction with [[file:chronometrist.org::#cell-print][=cell-print=]] methods to display the data in the pane.

9
cl/ui-clim.asd Normal file
View File

@ -0,0 +1,9 @@
(defsystem chronometrist-clim
:version "0.0.1"
:serial t
:license "Unlicense"
:author "contrapunctus <contrapunctus at disroot dot org>"
:description "Friendly and extensible personal time tracker - CLIM GUI"
:defsystem-depends-on ("literate-lisp")
:depends-on (:trivia :mcclim)
:components ((:org "chronometrist")))

BIN
doc/2022-02-20 13-26-53.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -90,13 +90,13 @@ Each value is a list of tag combinations, in reverse
chronological order. Each combination is a list containing tags
as symbol and/or strings.")
(defun chronometrist-tags-history-populate (task history-table file)
(defun chronometrist-tags-history-populate (task history-table backend)
"Store tag history for TASK in HISTORY-TABLE from FILE.
Return the new value inserted into HISTORY-TABLE.
HISTORY-TABLE must be a hash table. (see `chronometrist-tags-history')"
(puthash task nil history-table)
(chronometrist-loop-file for plist in file do
(cl-loop for plist in (chronometrist-to-list backend) do
(let ((new-tag-list (plist-get plist :tags))
(old-tag-lists (gethash task history-table)))
(and (equal task (plist-get plist :name))
@ -167,10 +167,10 @@ INITIAL-INPUT is as used in `completing-read'."
_ARGS are ignored. This function always returns t, so it can be
used in `chronometrist-before-out-functions'."
(interactive)
(let* ((last-expr (chronometrist-last))
(let* ((backend (chronometrist-active-backend))
(last-expr (chronometrist-latest-record backend))
(last-name (plist-get last-expr :name))
(_history (chronometrist-tags-history-populate last-name
chronometrist-tags-history chronometrist-file))
(_history (chronometrist-tags-history-populate last-name chronometrist-tags-history backend))
(last-tags (plist-get last-expr :tags))
(input (->> (chronometrist-maybe-symbol-to-string last-tags)
(-interpose ",")
@ -179,18 +179,42 @@ used in `chronometrist-before-out-functions'."
(chronometrist-maybe-string-to-symbol))))
(when input
(--> (append last-tags input)
(reverse it)
(cl-remove-duplicates it :test #'equal)
(reverse it)
(list :tags it)
(chronometrist-plist-update (chronometrist-sexp-last) it)
(chronometrist-sexp-replace-last it)))
(reverse it)
(cl-remove-duplicates it :test #'equal)
(reverse it)
(list :tags it)
(chronometrist-plist-update
(chronometrist-latest-record backend) it)
(chronometrist-replace-last backend it)))
t))
(defgroup chronometrist-key-values nil
"Add key-values to Chronometrist time intervals."
:group 'chronometrist)
(defcustom chronometrist-key-value-use-database-history t
"If non-nil, use database to generate key-value suggestions.
If nil, only `chronometrist-key-value-preset-alist' is used."
:type 'boolean
:group 'chronometrist-key-value)
(defcustom chronometrist-key-value-preset-alist nil
"Alist of key-value suggestions for `chronometrist-key-value' prompts.
Each element must be in the form (\"TASK\" <KEYWORD> <VALUE> ...)"
:type
'(repeat
(cons
(string :tag "Task name")
(repeat :tag "Property preset"
(plist :tag "Property"
;; :key-type 'keyword :value-type 'sexp
))))
:group 'chronometrist-key-values)
(defun chronometrist-key-value-get-presets (task)
"Return presets for TASK from `chronometrist-key-value-preset-alist' as a list of plists."
(alist-get task chronometrist-key-value-preset-alist nil nil #'equal))
(defcustom chronometrist-kv-buffer-name "*Chronometrist-Key-Values*"
"Name of buffer in which key-values are entered."
:group 'chronometrist-key-values
@ -204,20 +228,20 @@ containing keywords used with that task, in reverse chronological
order. The keywords are stored as strings and their leading \":\"
is removed.")
(defun chronometrist-key-history-populate (task history-table file)
(defun chronometrist-key-history-populate (task history-table backend)
"Store key history for TASK in HISTORY-TABLE from FILE.
Return the new value inserted into HISTORY-TABLE.
HISTORY-TABLE must be a hash table (see `chronometrist-key-history')."
(puthash task nil history-table)
(chronometrist-loop-file for plist in file do
(cl-loop for plist in backend do
(catch 'quit
(let* ((name (plist-get plist :name))
(_check (unless (equal name task) (throw 'quit nil)))
(keys (--> (chronometrist-plist-key-values plist)
(seq-filter #'keywordp it)
(cl-loop for key in it collect
(chronometrist-keyword-to-string key))))
(seq-filter #'keywordp it)
(cl-loop for key in it collect
(chronometrist-keyword-to-string key))))
(_check (unless keys (throw 'quit nil)))
(old-keys (gethash name history-table)))
(puthash name
@ -231,13 +255,13 @@ HISTORY-TABLE must be a hash table (see `chronometrist-key-history')."
The hash table keys are user-key names (as strings), and the
values are lists containing values (as strings).")
(defun chronometrist-value-history-populate (history-table file)
(defun chronometrist-value-history-populate (history-table backend)
"Store value history in HISTORY-TABLE from FILE.
HISTORY-TABLE must be a hash table. (see `chronometrist-value-history')"
(clrhash history-table)
;; Note - while keys are Lisp keywords, values may be any Lisp
;; object, including lists
(chronometrist-loop-file for plist in file do
(cl-loop for plist in (chronometrist-to-list backend) do
;; We call them user-key-values because we filter out Chronometrist's
;; reserved key-values
(let ((user-key-values (chronometrist-plist-key-values plist)))
@ -288,7 +312,7 @@ It currently supports ido, ido-ubiquitous, ivy, and helm."
"Prompt the user to enter keys.
USED-KEYS are keys they have already added since the invocation
of `chronometrist-kv-add'."
(let ((key-suggestions (--> (chronometrist-last)
(let ((key-suggestions (--> (chronometrist-latest-record (chronometrist-active-backend))
(plist-get it :name)
(gethash it chronometrist-key-history))))
(completing-read (format "Key (%s to quit): "
@ -335,18 +359,19 @@ used in `chronometrist-before-out-functions'."
(interactive)
(let* ((buffer (get-buffer-create chronometrist-kv-buffer-name))
(first-key-p t)
(last-sexp (chronometrist-last))
(backend (chronometrist-active-backend))
(last-sexp (chronometrist-latest-record backend))
(last-name (plist-get last-sexp :name))
(last-kvs (chronometrist-plist-key-values last-sexp))
(used-keys (--map (chronometrist-keyword-to-string it)
(seq-filter #'keywordp last-kvs))))
(chronometrist-key-history-populate last-name chronometrist-key-history chronometrist-file)
(chronometrist-value-history-populate chronometrist-value-history chronometrist-file)
(chronometrist-key-history-populate last-name chronometrist-key-history backend)
(chronometrist-value-history-populate chronometrist-value-history backend)
(switch-to-buffer buffer)
(with-current-buffer buffer
(erase-buffer)
(chronometrist-kv-read-mode)
(if (and (chronometrist-current-task) last-kvs)
(if (and (chronometrist-current-task (chronometrist-active-backend)) last-kvs)
(progn
(funcall chronometrist-sexp-pretty-print-function last-kvs buffer)
(down-list -1)
@ -377,14 +402,15 @@ used in `chronometrist-before-out-functions'."
(defun chronometrist-kv-accept ()
"Accept the plist in `chronometrist-kv-buffer-name' and add it to `chronometrist-file'."
(interactive)
(let (user-kv-expr)
(let* ((backend (chronometrist-active-backend))
(latest (chronometrist-latest-record backend))
user-kv-expr)
(with-current-buffer (get-buffer chronometrist-kv-buffer-name)
(goto-char (point-min))
(setq user-kv-expr (ignore-errors (read (current-buffer))))
(kill-buffer chronometrist-kv-buffer-name))
(if user-kv-expr
(chronometrist-sexp-replace-last
(chronometrist-plist-update (chronometrist-sexp-last) user-kv-expr))
(chronometrist-replace-last backend (chronometrist-plist-update latest user-kv-expr))
(chronometrist-refresh))))
(defun chronometrist-kv-reject ()
@ -401,25 +427,35 @@ used in `chronometrist-before-out-functions'."
["Change tags and key-values for active/last interval"
chronometrist-key-values-unified-prompt]))
(cl-defun chronometrist-key-values-unified-prompt (&optional (task (plist-get (chronometrist-sexp-last) :name)))
(cl-defun chronometrist-key-values-unified-prompt
(&optional (task (plist-get (chronometrist-latest-record (chronometrist-active-backend)) :name)))
"Query user for tags and key-values to be added for TASK.
Return t, to permit use in `chronometrist-before-out-functions'."
(interactive)
(let ((key-values (chronometrist-loop-file for plist in chronometrist-file
when (equal (plist-get plist :name) task)
collect
(let ((plist (chronometrist-plist-remove plist :name :start :stop)))
(when plist (format "%S" plist)))
into key-value-plists
finally return
(--> (seq-filter #'identity key-value-plists)
(cl-remove-duplicates it :test #'equal :from-end t)))))
(if (null key-values)
(let* ((backend (chronometrist-active-backend))
(presets (--map (format "%S" it)
(chronometrist-key-value-get-presets task)))
(key-values
(when chronometrist-key-value-use-database-history
(cl-loop for plist in (chronometrist-to-list backend)
when (equal (plist-get plist :name) task)
collect
(let ((plist (chronometrist-plist-remove plist :name :start :stop)))
(when plist (format "%S" plist)))
into key-value-plists
finally return
(--> (seq-filter #'identity key-value-plists)
(cl-remove-duplicates it :test #'equal :from-end t)))))
(latest (chronometrist-latest-record backend)))
(if (and (null presets) (null key-values))
(progn (chronometrist-tags-add) (chronometrist-kv-add))
(chronometrist-sexp-replace-last
(chronometrist-plist-update (chronometrist-sexp-last)
(read (completing-read (format "Key-values for %s: " task)
key-values))))))
(let* ((candidates (append presets key-values))
(input (completing-read
(format "Key-values for %s: " task)
candidates nil nil nil 'chronometrist-key-values-unified-prompt-history)))
(chronometrist-replace-last backend
(chronometrist-plist-update latest
(read input))))))
t)
(provide 'chronometrist-key-values)

View File

@ -191,13 +191,13 @@ as symbol and/or strings.")
#+END_SRC
*** tags-history-populate :writer:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-tags-history-populate (task history-table file)
(defun chronometrist-tags-history-populate (task history-table backend)
"Store tag history for TASK in HISTORY-TABLE from FILE.
Return the new value inserted into HISTORY-TABLE.
HISTORY-TABLE must be a hash table. (see `chronometrist-tags-history')"
(puthash task nil history-table)
(chronometrist-loop-file for plist in file do
(cl-loop for plist in (chronometrist-to-list backend) do
(let ((new-tag-list (plist-get plist :tags))
(old-tag-lists (gethash task history-table)))
(and (equal task (plist-get plist :name))
@ -209,6 +209,7 @@ HISTORY-TABLE must be a hash table. (see `chronometrist-tags-history')"
history-table))))
(chronometrist-history-prep task history-table))
#+END_SRC
**** tests
#+BEGIN_SRC emacs-lisp :tangle chronometrist-key-values-tests.el :load test
(ert-deftest chronometrist-tags-history ()
@ -301,10 +302,10 @@ INITIAL-INPUT is as used in `completing-read'."
_ARGS are ignored. This function always returns t, so it can be
used in `chronometrist-before-out-functions'."
(interactive)
(let* ((last-expr (chronometrist-last))
(let* ((backend (chronometrist-active-backend))
(last-expr (chronometrist-latest-record backend))
(last-name (plist-get last-expr :name))
(_history (chronometrist-tags-history-populate last-name
chronometrist-tags-history chronometrist-file))
(_history (chronometrist-tags-history-populate last-name chronometrist-tags-history backend))
(last-tags (plist-get last-expr :tags))
(input (->> (chronometrist-maybe-symbol-to-string last-tags)
(-interpose ",")
@ -313,12 +314,13 @@ used in `chronometrist-before-out-functions'."
(chronometrist-maybe-string-to-symbol))))
(when input
(--> (append last-tags input)
(reverse it)
(cl-remove-duplicates it :test #'equal)
(reverse it)
(list :tags it)
(chronometrist-plist-update (chronometrist-sexp-last) it)
(chronometrist-sexp-replace-last it)))
(reverse it)
(cl-remove-duplicates it :test #'equal)
(reverse it)
(list :tags it)
(chronometrist-plist-update
(chronometrist-latest-record backend) it)
(chronometrist-replace-last backend it)))
t))
#+END_SRC
** Key-Values
@ -328,6 +330,39 @@ used in `chronometrist-before-out-functions'."
"Add key-values to Chronometrist time intervals."
:group 'chronometrist)
#+END_SRC
*** use-database-history :custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-key-value-use-database-history t
"If non-nil, use database to generate key-value suggestions.
If nil, only `chronometrist-key-value-preset-alist' is used."
:type 'boolean
:group 'chronometrist-key-value)
#+END_SRC
*** preset-alist :custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-key-value-preset-alist nil
"Alist of key-value suggestions for `chronometrist-key-value' prompts.
Each element must be in the form (\"TASK\" <KEYWORD> <VALUE> ...)"
:type
'(repeat
(cons
(string :tag "Task name")
(repeat :tag "Property preset"
(plist :tag "Property"
;; :key-type 'keyword :value-type 'sexp
))))
:group 'chronometrist-key-values)
#+END_SRC
**** get-presets
#+BEGIN_SRC emacs-lisp
(defun chronometrist-key-value-get-presets (task)
"Return presets for TASK from `chronometrist-key-value-preset-alist' as a list of plists."
(alist-get task chronometrist-key-value-preset-alist nil nil #'equal))
#+END_SRC
*** kv-buffer-name :custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-kv-buffer-name "*Chronometrist-Key-Values*"
@ -335,6 +370,7 @@ used in `chronometrist-before-out-functions'."
:group 'chronometrist-key-values
:type 'string)
#+END_SRC
*** key-history :variable:
:PROPERTIES:
:VALUE: hash table
@ -350,20 +386,20 @@ is removed.")
#+END_SRC
*** key-history-populate :writer:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-key-history-populate (task history-table file)
(defun chronometrist-key-history-populate (task history-table backend)
"Store key history for TASK in HISTORY-TABLE from FILE.
Return the new value inserted into HISTORY-TABLE.
HISTORY-TABLE must be a hash table (see `chronometrist-key-history')."
(puthash task nil history-table)
(chronometrist-loop-file for plist in file do
(cl-loop for plist in backend do
(catch 'quit
(let* ((name (plist-get plist :name))
(_check (unless (equal name task) (throw 'quit nil)))
(keys (--> (chronometrist-plist-key-values plist)
(seq-filter #'keywordp it)
(cl-loop for key in it collect
(chronometrist-keyword-to-string key))))
(seq-filter #'keywordp it)
(cl-loop for key in it collect
(chronometrist-keyword-to-string key))))
(_check (unless keys (throw 'quit nil)))
(old-keys (gethash name history-table)))
(puthash name
@ -396,13 +432,13 @@ values are lists containing values (as strings).")
*** value-history-populate :writer:
We don't want values to be task-sensitive, so this does not have a KEY parameter similar to TASK for =chronometrist-tags-history-populate= or =chronometrist-key-history-populate=.
#+BEGIN_SRC emacs-lisp
(defun chronometrist-value-history-populate (history-table file)
(defun chronometrist-value-history-populate (history-table backend)
"Store value history in HISTORY-TABLE from FILE.
HISTORY-TABLE must be a hash table. (see `chronometrist-value-history')"
(clrhash history-table)
;; Note - while keys are Lisp keywords, values may be any Lisp
;; object, including lists
(chronometrist-loop-file for plist in file do
(cl-loop for plist in (chronometrist-to-list backend) do
;; We call them user-key-values because we filter out Chronometrist's
;; reserved key-values
(let ((user-key-values (chronometrist-plist-key-values plist)))
@ -419,6 +455,7 @@ HISTORY-TABLE must be a hash table. (see `chronometrist-value-history')"
(chronometrist-history-prep key history-table))
history-table))
#+END_SRC
**** tests
#+BEGIN_SRC emacs-lisp :tangle chronometrist-key-values-tests.el :load test
(ert-deftest chronometrist-value-history ()
@ -475,7 +512,7 @@ It currently supports ido, ido-ubiquitous, ivy, and helm."
"Prompt the user to enter keys.
USED-KEYS are keys they have already added since the invocation
of `chronometrist-kv-add'."
(let ((key-suggestions (--> (chronometrist-last)
(let ((key-suggestions (--> (chronometrist-latest-record (chronometrist-active-backend))
(plist-get it :name)
(gethash it chronometrist-key-history))))
(completing-read (format "Key (%s to quit): "
@ -528,18 +565,19 @@ used in `chronometrist-before-out-functions'."
(interactive)
(let* ((buffer (get-buffer-create chronometrist-kv-buffer-name))
(first-key-p t)
(last-sexp (chronometrist-last))
(backend (chronometrist-active-backend))
(last-sexp (chronometrist-latest-record backend))
(last-name (plist-get last-sexp :name))
(last-kvs (chronometrist-plist-key-values last-sexp))
(used-keys (--map (chronometrist-keyword-to-string it)
(seq-filter #'keywordp last-kvs))))
(chronometrist-key-history-populate last-name chronometrist-key-history chronometrist-file)
(chronometrist-value-history-populate chronometrist-value-history chronometrist-file)
(chronometrist-key-history-populate last-name chronometrist-key-history backend)
(chronometrist-value-history-populate chronometrist-value-history backend)
(switch-to-buffer buffer)
(with-current-buffer buffer
(erase-buffer)
(chronometrist-kv-read-mode)
(if (and (chronometrist-current-task) last-kvs)
(if (and (chronometrist-current-task (chronometrist-active-backend)) last-kvs)
(progn
(funcall chronometrist-sexp-pretty-print-function last-kvs buffer)
(down-list -1)
@ -572,14 +610,15 @@ used in `chronometrist-before-out-functions'."
(defun chronometrist-kv-accept ()
"Accept the plist in `chronometrist-kv-buffer-name' and add it to `chronometrist-file'."
(interactive)
(let (user-kv-expr)
(let* ((backend (chronometrist-active-backend))
(latest (chronometrist-latest-record backend))
user-kv-expr)
(with-current-buffer (get-buffer chronometrist-kv-buffer-name)
(goto-char (point-min))
(setq user-kv-expr (ignore-errors (read (current-buffer))))
(kill-buffer chronometrist-kv-buffer-name))
(if user-kv-expr
(chronometrist-sexp-replace-last
(chronometrist-plist-update (chronometrist-sexp-last) user-kv-expr))
(chronometrist-replace-last backend (chronometrist-plist-update latest user-kv-expr))
(chronometrist-refresh))))
#+END_SRC
*** kv-reject :command:
@ -642,14 +681,17 @@ Types of prompts planned (#1 and #2 are meant to be mixed and matched)
LIST should be a list, with all elements being either a plists,
or lists of symbols."
(cl-loop with num = 0
(cl-loop with backend = (chronometrist-active-backend)
with num = 0
with last = (chronometrist-latest-record backend)
for elt in (-take 7 list)
do (incf num)
if (= num 10) do (setq num 0)
collect
(list (format "%s" num)
`(chronometrist-sexp-replace-last
(chronometrist-plist-update (chronometrist-sexp-last)
`(chronometrist-replace-last
backend
(chronometrist-plist-update last
',(cl-case type
(:tags (list :tags elt))
(:key-values elt))))
@ -665,7 +707,7 @@ Types of prompts planned (#1 and #2 are meant to be mixed and matched)
"Query user for tags to be added to TASK.
Return t, to permit use in `chronometrist-before-out-functions'."
(let ((table chronometrist-tags-history))
(chronometrist-tags-history-populate task table chronometrist-file)
(chronometrist-tags-history-populate task table (chronometrist-active-backend))
(if (hash-table-empty-p table)
(chronometrist-tags-add)
(chronometrist-defchoice "tag" :tag (gethash task table))
@ -678,7 +720,7 @@ Types of prompts planned (#1 and #2 are meant to be mixed and matched)
"Query user for keys to be added to TASK.
Return t, to permit use in `chronometrist-before-out-functions'."
(let ((table chronometrist-key-history))
(chronometrist-key-history-populate task table chronometrist-file)
(chronometrist-key-history-populate task table (chronometrist-active-backend))
(if (hash-table-empty-p table)
(chronometrist-kv-add)
(chronometrist-defchoice :key task table)
@ -695,32 +737,52 @@ Return t, to permit use in `chronometrist-before-out-functions'."
())))
#+END_SRC
*** WIP unified-prompt :hook:writer:
:PROPERTIES:
:CUSTOM_ID: unified-prompt
:END:
1. [ ] Improve appearance - is there an easy way to syntax highlight the plists?
#+BEGIN_SRC emacs-lisp
(cl-defun chronometrist-key-values-unified-prompt (&optional (task (plist-get (chronometrist-sexp-last) :name)))
(cl-defun chronometrist-key-values-unified-prompt
(&optional (task (plist-get (chronometrist-latest-record (chronometrist-active-backend)) :name)))
"Query user for tags and key-values to be added for TASK.
Return t, to permit use in `chronometrist-before-out-functions'."
(interactive)
(let ((key-values (chronometrist-loop-file for plist in chronometrist-file
when (equal (plist-get plist :name) task)
collect
(let ((plist (chronometrist-plist-remove plist :name :start :stop)))
(when plist (format "%S" plist)))
into key-value-plists
finally return
(--> (seq-filter #'identity key-value-plists)
(cl-remove-duplicates it :test #'equal :from-end t)))))
(if (null key-values)
(let* ((backend (chronometrist-active-backend))
(presets (--map (format "%S" it)
(chronometrist-key-value-get-presets task)))
(key-values
(when chronometrist-key-value-use-database-history
(cl-loop for plist in (chronometrist-to-list backend)
when (equal (plist-get plist :name) task)
collect
(let ((plist (chronometrist-plist-remove plist :name :start :stop)))
(when plist (format "%S" plist)))
into key-value-plists
finally return
(--> (seq-filter #'identity key-value-plists)
(cl-remove-duplicates it :test #'equal :from-end t)))))
(latest (chronometrist-latest-record backend)))
(if (and (null presets) (null key-values))
(progn (chronometrist-tags-add) (chronometrist-kv-add))
(chronometrist-sexp-replace-last
(chronometrist-plist-update (chronometrist-sexp-last)
(read (completing-read (format "Key-values for %s: " task)
key-values))))))
(let* ((candidates (append presets key-values))
(input (completing-read
(format "Key-values for %s: " task)
candidates nil nil nil 'chronometrist-key-values-unified-prompt-history)))
(chronometrist-replace-last backend
(chronometrist-plist-update latest
(read input))))))
t)
#+END_SRC
* Provide
#+BEGIN_SRC emacs-lisp
(provide 'chronometrist-key-values)
;;; chronometrist-key-values.el ends here
#+END_SRC
* Local variables :noexport:
# Local Variables:
# my-org-src-default-lang: "emacs-lisp"
# eval: (when (package-installed-p 'literate-elisp) (require 'literate-elisp) (literate-elisp-load (buffer-file-name)))
# End:

View File

@ -1,10 +1,10 @@
;;; chronometrist-spark.el --- Show sparklines in Chronometrist -*- lexical-binding: t; -*-
;;; chronometrist-spark.el --- Show sparklines in Chronometrist buffers -*- lexical-binding: t; -*-
;; Author: contrapunctus <xmpp:contrapunctus@jabjab.de>
;; Maintainer: contrapunctus <xmpp:contrapunctus@jabjab.de>
;; Keywords: calendar
;; Homepage: https://tildegit.org/contrapunctus/chronometrist
;; Package-Requires: ((emacs "24.3") (chronometrist "0.7.0") (spark "0.1"))
;; Package-Requires: ((emacs "25.1") (chronometrist "0.7.0") (spark "0.1"))
;; Version: 0.1.0
;; This is free and unencumbered software released into the public domain.
@ -21,6 +21,7 @@
;; This package adds a column to Chronometrist displaying sparklines for each task.
;;; Code:
;; This file was automatically generated from chronometrist-spark.org.
(require 'chronometrist)
(require 'spark)
@ -52,6 +53,12 @@ DURATIONS must be a list of integer seconds."
;; have a minimum and maximum range.
(format "(%sm)" (apply #'max duration-minutes))))))
(defun chronometrist-spark-durations (task length stop-ts)
"Return a list of durations for time tracked for TASK in the last LENGTH days before STOP-TS."
(cl-loop for day from (- (- length 1)) to 0
collect
(chronometrist-task-time-one-day task (ts-adjust 'day day stop-ts))))
(defun chronometrist-spark-row-transformer (row)
"Add a sparkline cell to ROW.
Used to add a sparkline column to `chronometrist-rows'.
@ -59,20 +66,11 @@ Used to add a sparkline column to `chronometrist-rows'.
ROW must be a valid element of the list specified by
`tabulated-list-entries'."
(-let* (((task vector) row)
(sparkline
(cl-loop with today = (ts-now)
with duration with active-p
for day from (- (- chronometrist-spark-length 1)) to 0
collect
(setq duration
(chronometrist-task-time-one-day task
(ts-adjust 'day day today)))
into durations
unless (zerop duration) do (setq active-p t)
finally return
(if (and active-p chronometrist-spark-show-range)
(format "%s %s" (spark durations) (chronometrist-spark-range durations))
(format "%s" (spark durations))))))
(durations (chronometrist-spark-durations task chronometrist-spark-length (ts-now)))
(sparkline (if (and (not (seq-every-p #'zerop durations))
chronometrist-spark-show-range)
(format "%s %s" (spark durations) (chronometrist-spark-range durations))
(format "%s" (spark durations)))))
(list task (vconcat vector `[,sparkline]))))
(defun chronometrist-spark-schema-transformer (schema)

View File

@ -5,13 +5,13 @@
* Library headers and commentary
#+BEGIN_SRC emacs-lisp
;;; chronometrist-spark.el --- Show sparklines in Chronometrist -*- lexical-binding: t; -*-
;;; chronometrist-spark.el --- Show sparklines in Chronometrist buffers -*- lexical-binding: t; -*-
;; Author: contrapunctus <xmpp:contrapunctus@jabjab.de>
;; Maintainer: contrapunctus <xmpp:contrapunctus@jabjab.de>
;; Keywords: calendar
;; Homepage: https://tildegit.org/contrapunctus/chronometrist
;; Package-Requires: ((emacs "24.3") (chronometrist "0.7.0") (spark "0.1"))
;; Package-Requires: ((emacs "25.1") (chronometrist "0.7.0") (spark "0.1"))
;; Version: 0.1.0
;; This is free and unencumbered software released into the public domain.
@ -31,12 +31,15 @@
;;
;; This package adds a column to Chronometrist displaying sparklines for each task.
#+END_SRC
* Dependencies
#+BEGIN_SRC emacs-lisp
;;; Code:
;; This file was automatically generated from chronometrist-spark.org.
(require 'chronometrist)
(require 'spark)
#+END_SRC
* Code
** custom group :custom:group:
#+BEGIN_SRC emacs-lisp
@ -86,6 +89,15 @@ 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
** durations :function:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-spark-durations (task length stop-ts)
"Return a list of durations for time tracked for TASK in the last LENGTH days before STOP-TS."
(cl-loop for day from (- (- length 1)) to 0
collect
(chronometrist-task-time-one-day task (ts-adjust 'day day stop-ts))))
#+END_SRC
** TODO row-transformer :function:
if larger than 7
add space after (% length 7)th element
@ -103,20 +115,11 @@ Used to add a sparkline column to `chronometrist-rows'.
ROW must be a valid element of the list specified by
`tabulated-list-entries'."
(-let* (((task vector) row)
(sparkline
(cl-loop with today = (ts-now)
with duration with active-p
for day from (- (- chronometrist-spark-length 1)) to 0
collect
(setq duration
(chronometrist-task-time-one-day task
(ts-adjust 'day day today)))
into durations
unless (zerop duration) do (setq active-p t)
finally return
(if (and active-p chronometrist-spark-show-range)
(format "%s %s" (spark durations) (chronometrist-spark-range durations))
(format "%s" (spark durations))))))
(durations (chronometrist-spark-durations task chronometrist-spark-length (ts-now)))
(sparkline (if (and (not (seq-every-p #'zerop durations))
chronometrist-spark-show-range)
(format "%s %s" (spark durations) (chronometrist-spark-range durations))
(format "%s" (spark durations)))))
(list task (vconcat vector `[,sparkline]))))
#+END_SRC
@ -167,3 +170,8 @@ SCHEMA should be a vector as specified by `tabulated-list-format'."
(provide 'chronometrist-spark)
;;; chronometrist-spark.el ends here
#+END_SRC
* Local variables :noexport:
# Local Variables:
# eval: (when (package-installed-p 'literate-elisp) (require 'literate-elisp) (literate-elisp-load (buffer-file-name)))
# End:

View File

@ -0,0 +1,210 @@
;;; chronometrist-sqlite.el --- SQLite backend for Chronometrist -*- lexical-binding: t; -*-
;; Author: contrapunctus <xmpp:contrapunctus@jabjab.de>
;; Maintainer: contrapunctus <xmpp:contrapunctus@jabjab.de>
;; Keywords: calendar
;; Homepage: https://tildegit.org/contrapunctus/chronometrist
;; Package-Requires: ((emacs "24.3") (chronometrist "0.9.0") (emacsql-sqlite "1.0.0"))
;; Version: 0.1.0
;; This is free and unencumbered software released into the public domain.
;;
;; Anyone is free to copy, modify, publish, use, compile, sell, or
;; distribute this software, either in source code form or as a compiled
;; binary, for any purpose, commercial or non-commercial, and by any
;; means.
;;
;; For more information, please refer to <https://unlicense.org>
;;; Commentary:
;;
;; This package provides an SQLite 3 backend for Chronometrist.
;;; Code:
(require 'chronometrist)
(require 'emacsql-sqlite)
(defclass chronometrist-sqlite-backend (chronometrist-backend chronometrist-file-backend-mixin)
((extension :initform "sqlite"
:accessor chronometrist-backend-ext
:custom 'string)
(connection :initform nil
:initarg :connection
:accessor chronometrist-backend-connection)))
(chronometrist-register-backend
:sqlite "Store records in SQLite database."
(make-instance 'chronometrist-sqlite-backend :path chronometrist-file))
(cl-defmethod initialize-instance :after ((backend chronometrist-sqlite-backend)
&rest _initargs)
"Initialize connection for BACKEND based on its file."
(with-slots (file connection) backend
(when (and file (not connection))
(setf connection (emacsql-sqlite file)))))
(cl-defmethod chronometrist-create-file ((backend chronometrist-sqlite-backend) &optional file)
"Create file for BACKEND if it does not already exist.
Return the connection object from `emacsql-sqlite'."
(let* ((file (or file (chronometrist-backend-file backend)))
(db (or (chronometrist-backend-connection backend)
(setf (chronometrist-backend-connection backend)
(emacsql-sqlite file)))))
(cl-loop
for query in
'(;; Properties are user-defined key-values stored as JSON.
[:create-table properties
([(prop-id integer :primary-key)
(properties text :unique :not-null)])]
;; An event is a timestamp with a name and optional properties.
[:create-table event-names
([(name-id integer :primary-key)
(name text :unique :not-null)])]
[:create-table events
([(event-id integer :primary-key)
(name-id integer :not-null :references event-names [name-id])])]
;; An interval is a time range with a name and optional properties.
[:create-table interval-names
([(name-id integer :primary-key)
(name text :unique :not-null)])]
[:create-table intervals
([(interval-id integer :primary-key)
(name-id integer :not-null :references interval-names [name-id])
(start-time integer :not-null)
;; The latest interval may be ongoing, so the stop time may be NULL.
(stop-time integer)
(prop-id integer :references properties [prop-id])]
(:unique [name-id start-time stop-time]))]
;; A date contains one or more events and intervals. It may
;; also contain properties.
[:create-table dates
([(date-id integer :primary-key)
(date integer :unique :not-null)
(prop-id integer :references properties [prop-id])])]
[:create-table date-events
([(date-id integer :not-null :references dates [date-id])
(event-id integer :not-null :references events [event-id])])]
[:create-table date-intervals
([(date-id integer :not-null :references dates [date-id])
(interval-id integer :not-null :references intervals [interval-id])])])
do (emacsql db query)
finally return db)))
(defun chronometrist-iso-to-unix (timestamp)
(truncate (float-time (parse-iso8601-time-string timestamp))))
(cl-defmethod chronometrist-to-file (hash-table (backend chronometrist-sqlite-backend) file)
(with-slots (connection) backend
(delete-file file)
(when connection (emacsql-close connection))
(setf connection nil)
(chronometrist-create-file backend file)
(cl-loop for date in (sort (hash-table-keys hash-table) #'string-lessp) do
;; insert date if it does not exist
(emacsql connection [:insert-or-ignore-into dates [date] :values [$s1]]
(chronometrist-iso-to-unix date))
(cl-loop for plist in (gethash date hash-table) do
(chronometrist-insert backend plist)))))
(defun chronometrist-sqlite-insert-properties (backend plist)
"Insert properties from PLIST to (SQLite) BACKEND.
Properties are key-values excluding :name, :start, and :stop.
Insert nothing if the properties already exist. Return the
prop-id of the inserted or existing property."
(with-slots (connection) backend
(let* ((plist (chronometrist-plist-key-values plist))
(props (if (functionp chronometrist-sqlite-properties-function)
(funcall chronometrist-sqlite-properties-function plist)
plist)))
(emacsql connection
[:insert-or-ignore-into properties [properties] :values [$s1]]
props)
(caar (emacsql connection [:select [prop-id]
:from properties
:where (= properties $s1)]
props)))))
(defun chronometrist-sqlite-properties-to-json (plist)
"Return PLIST as a JSON string."
(json-encode
;; `json-encode' throws an error when it thinks
;; it sees "alists" which have numbers as
;; "keys", so we convert any cons cells and any
;; lists starting with a number to vectors
(-tree-map (lambda (elt)
(cond ((chronometrist-pp-pair-p elt)
(vector (car elt) (cdr elt)))
((consp elt)
(vconcat elt))
(t elt)))
plist)))
(defcustom chronometrist-sqlite-properties-function nil
"Function used to control the encoding of user key-values.
The function must accept a single argument, the plist of key-values.
Any non-function value results in key-values being inserted as
s-expressions in a text column."
:type '(choice function (sexp :tag "Insert as s-expressions")))
(cl-defmethod chronometrist-insert ((backend chronometrist-sqlite-backend) plist)
(-let (((plist-1 plist-2) (chronometrist-split-plist plist))
(db (chronometrist-backend-connection backend)))
(cl-loop for plist in (if (and plist-1 plist-2)
(list plist-1 plist-2)
(list plist))
do
(-let* (((&plist :name name :start start :stop stop) plist)
(date-unix (chronometrist-iso-to-unix (chronometrist-iso-to-date start)))
(start-unix (chronometrist-iso-to-unix start))
(stop-unix (and stop (chronometrist-iso-to-unix stop)))
name-id interval-id prop-id)
;; insert name if it does not exist
(emacsql db [:insert-or-ignore-into interval-names [name]
:values [$s1]]
name)
;; insert interval properties if they do not exist
(setq prop-id (chronometrist-sqlite-insert-properties backend plist))
;; insert interval and associate it with the date
(setq name-id
(caar (emacsql db [:select [name-id]
:from interval-names
:where (= name $s1)]
name)))
(emacsql db [:insert-or-ignore-into intervals
[name-id start-time stop-time prop-id]
:values [$s1 $s2 $s3 $s4]]
name-id start-unix stop-unix prop-id)
(emacsql db [:insert-or-ignore-into dates [date]
:values [$s1]] date-unix)
(setq date-id
(caar (emacsql db [:select [date-id] :from dates
:where (= date $s1)]
date-unix))
interval-id
(caar (emacsql db [:select (funcall max interval-id) :from intervals])))
(emacsql db [:insert-into date-intervals [date-id interval-id]
:values [$s1 $s2]]
date-id interval-id)))))
(cl-defmethod chronometrist-edit-backend ((backend chronometrist-sqlite-backend))
(require 'sql)
(switch-to-buffer
(sql-comint-sqlite 'sqlite (list file))))
;; SELECT * FROM TABLE WHERE ID = (SELECT MAX(ID) FROM TABLE);
;; SELECT * FROM tablename ORDER BY column DESC LIMIT 1;
(cl-defmethod chronometrist-latest-record ((backend chronometrist-sqlite-backend) db)
(emacsql db [:select * :from events :order-by rowid :desc :limit 1]))
(cl-defmethod chronometrist-task-records-for-date ((backend chronometrist-sqlite-backend) task date-ts))
(cl-defmethod chronometrist-active-days ((backend chronometrist-sqlite-backend) task))
(cl-defmethod chronometrist-replace-last ((backend chronometrist-sqlite-backend) plist)
(emacsql db [:delete-from events :where ]))
(provide 'chronometrist-sqlite)
;;; chronometrist-sqlite.el ends here

View File

@ -0,0 +1,277 @@
#+TITLE: chronometrist-sqlite
#+AUTHOR: contrapunctus
#+SUBTITLE: SQLite backend for Chronometrist
#+PROPERTY: header-args :tangle yes :load yes
* Library headers and commentary
#+BEGIN_SRC emacs-lisp
;;; chronometrist-sqlite.el --- SQLite backend for Chronometrist -*- lexical-binding: t; -*-
;; Author: contrapunctus <xmpp:contrapunctus@jabjab.de>
;; Maintainer: contrapunctus <xmpp:contrapunctus@jabjab.de>
;; Keywords: calendar
;; Homepage: https://tildegit.org/contrapunctus/chronometrist
;; Package-Requires: ((emacs "24.3") (chronometrist "0.9.0") (emacsql-sqlite "1.0.0"))
;; Version: 0.1.0
;; This is free and unencumbered software released into the public domain.
;;
;; Anyone is free to copy, modify, publish, use, compile, sell, or
;; distribute this software, either in source code form or as a compiled
;; binary, for any purpose, commercial or non-commercial, and by any
;; means.
;;
;; For more information, please refer to <https://unlicense.org>
#+END_SRC
"Commentary" is displayed when the user clicks on the package's entry in =M-x list-packages=.
#+BEGIN_SRC emacs-lisp
;;; Commentary:
;;
;; This package provides an SQLite 3 backend for Chronometrist.
#+END_SRC
* Dependencies
#+BEGIN_SRC emacs-lisp
;;; Code:
(require 'chronometrist)
(require 'emacsql-sqlite)
#+END_SRC
* Code
** class
#+BEGIN_SRC emacs-lisp
(defclass chronometrist-sqlite-backend (chronometrist-backend chronometrist-file-backend-mixin)
((extension :initform "sqlite"
:accessor chronometrist-backend-ext
:custom 'string)
(connection :initform nil
:initarg :connection
:accessor chronometrist-backend-connection)))
(chronometrist-register-backend
:sqlite "Store records in SQLite database."
(make-instance 'chronometrist-sqlite-backend :path chronometrist-file))
#+END_SRC
** initialize-instance :method:
#+BEGIN_SRC emacs-lisp
(cl-defmethod initialize-instance :after ((backend chronometrist-sqlite-backend)
&rest _initargs)
"Initialize connection for BACKEND based on its file."
(with-slots (file connection) backend
(when (and file (not connection))
(setf connection (emacsql-sqlite file)))))
#+END_SRC
** create-file
#+BEGIN_SRC emacs-lisp
(cl-defmethod chronometrist-create-file ((backend chronometrist-sqlite-backend) &optional file)
"Create file for BACKEND if it does not already exist.
Return the connection object from `emacsql-sqlite'."
(let* ((file (or file (chronometrist-backend-file backend)))
(db (or (chronometrist-backend-connection backend)
(setf (chronometrist-backend-connection backend)
(emacsql-sqlite file)))))
(cl-loop
for query in
'(;; Properties are user-defined key-values stored as JSON.
[:create-table properties
([(prop-id integer :primary-key)
(properties text :unique :not-null)])]
;; An event is a timestamp with a name and optional properties.
[:create-table event-names
([(name-id integer :primary-key)
(name text :unique :not-null)])]
[:create-table events
([(event-id integer :primary-key)
(name-id integer :not-null :references event-names [name-id])])]
;; An interval is a time range with a name and optional properties.
[:create-table interval-names
([(name-id integer :primary-key)
(name text :unique :not-null)])]
[:create-table intervals
([(interval-id integer :primary-key)
(name-id integer :not-null :references interval-names [name-id])
(start-time integer :not-null)
;; The latest interval may be ongoing, so the stop time may be NULL.
(stop-time integer)
(prop-id integer :references properties [prop-id])]
(:unique [name-id start-time stop-time]))]
;; A date contains one or more events and intervals. It may
;; also contain properties.
[:create-table dates
([(date-id integer :primary-key)
(date integer :unique :not-null)
(prop-id integer :references properties [prop-id])])]
[:create-table date-events
([(date-id integer :not-null :references dates [date-id])
(event-id integer :not-null :references events [event-id])])]
[:create-table date-intervals
([(date-id integer :not-null :references dates [date-id])
(interval-id integer :not-null :references intervals [interval-id])])])
do (emacsql db query)
finally return db)))
#+END_SRC
** iso-to-unix :function:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-iso-to-unix (timestamp)
(truncate (float-time (parse-iso8601-time-string timestamp))))
#+END_SRC
** to-file :method:
#+BEGIN_SRC emacs-lisp
(cl-defmethod chronometrist-to-file (hash-table (backend chronometrist-sqlite-backend) file)
(with-slots (connection) backend
(delete-file file)
(when connection (emacsql-close connection))
(setf connection nil)
(chronometrist-create-file backend file)
(cl-loop for date in (sort (hash-table-keys hash-table) #'string-lessp) do
;; insert date if it does not exist
(emacsql connection [:insert-or-ignore-into dates [date] :values [$s1]]
(chronometrist-iso-to-unix date))
(cl-loop for plist in (gethash date hash-table) do
(chronometrist-insert backend plist)))))
#+END_SRC
** insert-properties :writer:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-sqlite-insert-properties (backend plist)
"Insert properties from PLIST to (SQLite) BACKEND.
Properties are key-values excluding :name, :start, and :stop.
Insert nothing if the properties already exist. Return the
prop-id of the inserted or existing property."
(with-slots (connection) backend
(let* ((plist (chronometrist-plist-key-values plist))
(props (if (functionp chronometrist-sqlite-properties-function)
(funcall chronometrist-sqlite-properties-function plist)
plist)))
(emacsql connection
[:insert-or-ignore-into properties [properties] :values [$s1]]
props)
(caar (emacsql connection [:select [prop-id]
:from properties
:where (= properties $s1)]
props)))))
#+END_SRC
*** properties-to-json :function:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-sqlite-properties-to-json (plist)
"Return PLIST as a JSON string."
(json-encode
;; `json-encode' throws an error when it thinks
;; it sees "alists" which have numbers as
;; "keys", so we convert any cons cells and any
;; lists starting with a number to vectors
(-tree-map (lambda (elt)
(cond ((chronometrist-pp-pair-p elt)
(vector (car elt) (cdr elt)))
((consp elt)
(vconcat elt))
(t elt)))
plist)))
#+END_SRC
*** properties-function :custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-sqlite-properties-function nil
"Function used to control the encoding of user key-values.
The function must accept a single argument, the plist of key-values.
Any non-function value results in key-values being inserted as
s-expressions in a text column."
:type '(choice function (sexp :tag "Insert as s-expressions")))
#+END_SRC
** insert
#+BEGIN_SRC emacs-lisp
(cl-defmethod chronometrist-insert ((backend chronometrist-sqlite-backend) plist)
(-let (((plist-1 plist-2) (chronometrist-split-plist plist))
(db (chronometrist-backend-connection backend)))
(cl-loop for plist in (if (and plist-1 plist-2)
(list plist-1 plist-2)
(list plist))
do
(-let* (((&plist :name name :start start :stop stop) plist)
(date-unix (chronometrist-iso-to-unix (chronometrist-iso-to-date start)))
(start-unix (chronometrist-iso-to-unix start))
(stop-unix (and stop (chronometrist-iso-to-unix stop)))
name-id interval-id prop-id)
;; insert name if it does not exist
(emacsql db [:insert-or-ignore-into interval-names [name]
:values [$s1]]
name)
;; insert interval properties if they do not exist
(setq prop-id (chronometrist-sqlite-insert-properties backend plist))
;; insert interval and associate it with the date
(setq name-id
(caar (emacsql db [:select [name-id]
:from interval-names
:where (= name $s1)]
name)))
(emacsql db [:insert-or-ignore-into intervals
[name-id start-time stop-time prop-id]
:values [$s1 $s2 $s3 $s4]]
name-id start-unix stop-unix prop-id)
(emacsql db [:insert-or-ignore-into dates [date]
:values [$s1]] date-unix)
(setq date-id
(caar (emacsql db [:select [date-id] :from dates
:where (= date $s1)]
date-unix))
interval-id
(caar (emacsql db [:select (funcall max interval-id) :from intervals])))
(emacsql db [:insert-into date-intervals [date-id interval-id]
:values [$s1 $s2]]
date-id interval-id)))))
#+END_SRC
** open-file
#+BEGIN_SRC emacs-lisp
(cl-defmethod chronometrist-edit-backend ((backend chronometrist-sqlite-backend))
(require 'sql)
(switch-to-buffer
(sql-comint-sqlite 'sqlite (list file))))
#+END_SRC
** latest-record
#+BEGIN_SRC emacs-lisp
;; SELECT * FROM TABLE WHERE ID = (SELECT MAX(ID) FROM TABLE);
;; SELECT * FROM tablename ORDER BY column DESC LIMIT 1;
(cl-defmethod chronometrist-latest-record ((backend chronometrist-sqlite-backend) db)
(emacsql db [:select * :from events :order-by rowid :desc :limit 1]))
#+END_SRC
** task-records-for-date
#+BEGIN_SRC emacs-lisp
(cl-defmethod chronometrist-task-records-for-date ((backend chronometrist-sqlite-backend) task date-ts))
#+END_SRC
** active-days
#+BEGIN_SRC emacs-lisp
(cl-defmethod chronometrist-active-days ((backend chronometrist-sqlite-backend) task))
#+END_SRC
** replace-last
#+BEGIN_SRC emacs-lisp
(cl-defmethod chronometrist-replace-last ((backend chronometrist-sqlite-backend) plist)
(emacsql db [:delete-from events :where ]))
#+END_SRC
** Provide
#+BEGIN_SRC emacs-lisp
(provide 'chronometrist-sqlite)
;;; chronometrist-sqlite.el ends here
#+END_SRC
* Local variables :noexport:
# Local Variables:
# eval: (when (or (package-installed-p 'emacsql) (featurep 'emacsql)) (require 'emacsql) (emacsql-fix-vector-indentation))
# eval: (when (or (package-installed-p 'literate-elisp) (featurep 'literate-elisp)) (require 'literate-elisp) (literate-elisp-load (buffer-file-name)))
# End:

View File

@ -0,0 +1,178 @@
;;; chronometrist-third.el --- Third Time support for Chronometrist -*- lexical-binding: t; -*-
;; Author: contrapunctus <xmpp:contrapunctus@jabjab.de>
;; Maintainer: contrapunctus <xmpp:contrapunctus@jabjab.de>
;; Keywords: calendar
;; Homepage: https://tildegit.org/contrapunctus/chronometrist
;; Package-Requires: ((emacs "25.1") (alert "1.2") (chronometrist "0.6.0"))
;; Version: 0.0.1
;; This is free and unencumbered software released into the public domain.
;;
;; Anyone is free to copy, modify, publish, use, compile, sell, or
;; distribute this software, either in source code form or as a compiled
;; binary, for any purpose, commercial or non-commercial, and by any
;; means.
;;
;; For more information, please refer to <https://unlicense.org>
;;; Commentary:
;; Add support for the Third Time system to Chronometrist. In Third
;; Time, you work for any length of time you like, and "earn" a third
;; of the work time as break time. For a more detailed explanation,
;; see
;; https://www.lesswrong.com/posts/RWu8eZqbwgB9zaerh/third-time-a-better-way-to-work
;; For information on usage and customization, see https://tildegit.org/contrapunctus/chronometrist-goal/src/branch/production/README.md
;;; Code:
(require 'chronometrist)
(require 'alert)
;; [[file:chronometrist-third.org::*group][group:1]]
(defgroup chronometrist-third nil
"Third Time support for Chronometrist."
:group 'chronometrist)
;; group:1 ends here
;; [[file:chronometrist-third.org::*divisor][divisor:1]]
(defcustom chronometrist-third-divisor 3
"Number to determine accumulation of break time relative to work time."
:type 'number)
;; divisor:1 ends here
;; [[file:chronometrist-third.org::*duration-format][duration-format:1]]
(defcustom chronometrist-third-duration-format "%H, %M and %S%z"
"Format string for durations, passed to `format-seconds'."
:type 'string)
;; duration-format:1 ends here
;; [[file:chronometrist-third.org::*break-time][break-time:1]]
(defvar chronometrist-third-break-time 0
"Accumulated break time in seconds.")
;; break-time:1 ends here
;; [[file:chronometrist-third.org::*alert-functions][alert-functions:1]]
(defcustom chronometrist-third-alert-functions '(chronometrist-third-half-alert chronometrist-third-quarter-alert chronometrist-third-break-over-alert)
"List of timed alerts for the Third Time system.
Typically, each function in this list should call
`chronometrist-third-run-at-time' to run another function, which
in turn should call `alert' to notify the user.
All functions in this list are started when the user clocks out,
and stopped when they clock in."
:group 'chronometrist-third
:type 'hook)
;; alert-functions:1 ends here
;; [[file:chronometrist-third.org::*timer-list][timer-list:1]]
(defvar chronometrist-third-timer-list nil)
;; timer-list:1 ends here
;; [[file:chronometrist-third.org::*run-at-time][run-at-time:1]]
(defun chronometrist-third-run-at-time (time repeat function &rest args)
"Like `run-at-time', but store timer objects in `chronometrist-third-timer-list'."
(cl-pushnew (apply #'run-at-time time repeat function args) chronometrist-third-timer-list))
;; run-at-time:1 ends here
;; [[file:chronometrist-third.org::*half-alert][half-alert:1]]
(defun chronometrist-third-half-alert ()
"Display an alert when half the break time is consumed."
(let ((half-time (/ chronometrist-third-break-time 2.0)))
(and (not (zerop chronometrist-third-break-time))
(chronometrist-third-run-at-time
half-time nil
(lambda (half-time)
(alert
(format "%s left on your break."
(format-seconds chronometrist-third-duration-format half-time))))
half-time))))
;; half-alert:1 ends here
;; [[file:chronometrist-third.org::*quarter-alert][quarter-alert:1]]
(defun chronometrist-third-quarter-alert ()
"Display an alert when 3/4ths of the break time is consumed."
(let ((three-fourths (* chronometrist-third-break-time 7.5)))
(and (not (zerop chronometrist-third-break-time))
(chronometrist-third-run-at-time
three-fourths nil
(lambda (three-fourths)
(alert
(format "%s left on your break."
(format-seconds chronometrist-third-duration-format
(- chronometrist-third-break-time three-fourths)))))
three-fourths))))
;; quarter-alert:1 ends here
;; [[file:chronometrist-third.org::*break-over-alert][break-over-alert:1]]
(defun chronometrist-third-break-over-alert ()
"Display an alert when break time is over."
(and (not (zerop chronometrist-third-break-time))
(chronometrist-third-run-at-time
chronometrist-third-break-time nil
(lambda () (alert (format "Break time is over!"))))))
;; break-over-alert:1 ends here
;; [[file:chronometrist-third.org::*start-alert-timers][start-alert-timers:1]]
(defun chronometrist-third-start-alert-timers ()
"Run functions in `chronometrist-third-alert-functions'."
(mapc #'funcall chronometrist-third-alert-functions))
;; start-alert-timers:1 ends here
;; [[file:chronometrist-third.org::*stop-alert-timers][stop-alert-timers:1]]
(defun chronometrist-third-stop-alert-timers ()
"Stop timers in `chronometrist-third-timer-list'."
(mapc (lambda (timer) (cancel-timer timer)) chronometrist-third-timer-list))
;; stop-alert-timers:1 ends here
;; [[file:chronometrist-third.org::*clock-in][clock-in:1]]
(defun chronometrist-third-clock-in (&optional _arg)
"Stop alert timers and update break time."
(chronometrist-third-stop-alert-timers)
(unless (zerop chronometrist-third-break-time)
(-let* (((&plist :stop stop) (cl-second (chronometrist-to-list (chronometrist-active-backend))))
(used-break (ts-diff (ts-now) (chronometrist-iso-to-ts stop)))
(used-break-string (format-seconds chronometrist-third-duration-format used-break))
(new-break (- chronometrist-third-break-time used-break))
(old-break chronometrist-third-break-time))
(setq chronometrist-third-break-time (if (> new-break 0) new-break 0))
(alert
(if (zerop chronometrist-third-break-time)
(format "You have used up all %s of your break time (%s break)"
(format-seconds chronometrist-third-duration-format old-break)
used-break-string)
(format "You have used %s of your break time (%s left)"
used-break-string
(format-seconds chronometrist-third-duration-format chronometrist-third-break-time)))))))
;; clock-in:1 ends here
;; [[file:chronometrist-third.org::*clock-out][clock-out:1]]
(defun chronometrist-third-clock-out (&optional _arg)
"Update break time based on the latest work interval.
Run `chronometrist-third-alert-functions' to alert user when
break time is up."
(let* ((latest-work-duration (chronometrist-interval (chronometrist-latest-record (chronometrist-active-backend))))
(break-time-increment (/ latest-work-duration chronometrist-third-divisor)))
(cl-incf chronometrist-third-break-time break-time-increment)
(alert (format "You have gained %s of break time (%s total)"
(format-seconds chronometrist-third-duration-format break-time-increment)
(format-seconds chronometrist-third-duration-format chronometrist-third-break-time)))
;; start alert timer(s)
(chronometrist-third-start-alert-timers)))
;; clock-out:1 ends here
;; [[file:chronometrist-third.org::*third-minor-mode][third-minor-mode:1]]
;;;###autoload
(define-minor-mode chronometrist-third-minor-mode
nil nil nil nil
(cond (chronometrist-third-minor-mode
(add-hook 'chronometrist-after-in-functions #'chronometrist-third-clock-in)
(add-hook 'chronometrist-after-out-functions #'chronometrist-third-clock-out))
(t (remove-hook 'chronometrist-after-in-functions #'chronometrist-third-clock-in)
(remove-hook 'chronometrist-after-out-functions #'chronometrist-third-clock-out))))
;; third-minor-mode:1 ends here
(provide 'chronometrist-third)
;;; chronometrist-third.el ends here

View File

@ -0,0 +1,213 @@
#+TITLE: chronometrist-third
#+SUBTITLE: Third Time System extension for Chronometrist
#+PROPERTY: header-args :tangle yes :load yes :comments link
* Program source
** Library headers and commentary
#+BEGIN_SRC emacs-lisp :comments no
;;; chronometrist-third.el --- Third Time support for Chronometrist -*- lexical-binding: t; -*-
;; Author: contrapunctus <xmpp:contrapunctus@jabjab.de>
;; Maintainer: contrapunctus <xmpp:contrapunctus@jabjab.de>
;; Keywords: calendar
;; Homepage: https://tildegit.org/contrapunctus/chronometrist
;; Package-Requires: ((emacs "25.1") (alert "1.2") (chronometrist "0.6.0"))
;; Version: 0.0.1
;; This is free and unencumbered software released into the public domain.
;;
;; Anyone is free to copy, modify, publish, use, compile, sell, or
;; distribute this software, either in source code form or as a compiled
;; binary, for any purpose, commercial or non-commercial, and by any
;; means.
;;
;; For more information, please refer to <https://unlicense.org>
;;; Commentary:
;; Add support for the Third Time system to Chronometrist. In Third
;; Time, you work for any length of time you like, and "earn" a third
;; of the work time as break time. For a more detailed explanation,
;; see
;; https://www.lesswrong.com/posts/RWu8eZqbwgB9zaerh/third-time-a-better-way-to-work
;; For information on usage and customization, see https://tildegit.org/contrapunctus/chronometrist-goal/src/branch/production/README.md
#+END_SRC
** Dependencies
#+BEGIN_SRC emacs-lisp :comments no
;;; Code:
(require 'chronometrist)
(require 'alert)
#+END_SRC
** group :custom:group:
#+BEGIN_SRC emacs-lisp
(defgroup chronometrist-third nil
"Third Time support for Chronometrist."
:group 'chronometrist)
#+END_SRC
** divisor :custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-third-divisor 3
"Number to determine accumulation of break time relative to work time."
:type 'number)
#+END_SRC
** duration-format :custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-third-duration-format "%H, %M and %S%z"
"Format string for durations, passed to `format-seconds'."
:type 'string)
#+END_SRC
** break-time :variable:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-third-break-time 0
"Accumulated break time in seconds.")
#+END_SRC
** alert-functions :custom:variable:
#+BEGIN_SRC emacs-lisp
(defcustom chronometrist-third-alert-functions '(chronometrist-third-half-alert chronometrist-third-quarter-alert chronometrist-third-break-over-alert)
"List of timed alerts for the Third Time system.
Typically, each function in this list should call
`chronometrist-third-run-at-time' to run another function, which
in turn should call `alert' to notify the user.
All functions in this list are started when the user clocks out,
and stopped when they clock in."
:group 'chronometrist-third
:type 'hook)
#+END_SRC
** timer-list :variable:
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-third-timer-list nil)
#+END_SRC
** run-at-time :procedure:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-third-run-at-time (time repeat function &rest args)
"Like `run-at-time', but store timer objects in `chronometrist-third-timer-list'."
(cl-pushnew (apply #'run-at-time time repeat function args) chronometrist-third-timer-list))
#+END_SRC
** half-alert :procedure:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-third-half-alert ()
"Display an alert when half the break time is consumed."
(let ((half-time (/ chronometrist-third-break-time 2.0)))
(and (not (zerop chronometrist-third-break-time))
(chronometrist-third-run-at-time
half-time nil
(lambda (half-time)
(alert
(format "%s left on your break."
(format-seconds chronometrist-third-duration-format half-time))))
half-time))))
#+END_SRC
** quarter-alert :procedure:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-third-quarter-alert ()
"Display an alert when 3/4ths of the break time is consumed."
(let ((three-fourths (* chronometrist-third-break-time 7.5)))
(and (not (zerop chronometrist-third-break-time))
(chronometrist-third-run-at-time
three-fourths nil
(lambda (three-fourths)
(alert
(format "%s left on your break."
(format-seconds chronometrist-third-duration-format
(- chronometrist-third-break-time three-fourths)))))
three-fourths))))
#+END_SRC
** break-over-alert :procedure:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-third-break-over-alert ()
"Display an alert when break time is over."
(and (not (zerop chronometrist-third-break-time))
(chronometrist-third-run-at-time
chronometrist-third-break-time nil
(lambda () (alert (format "Break time is over!"))))))
#+END_SRC
** start-alert-timers :procedure:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-third-start-alert-timers ()
"Run functions in `chronometrist-third-alert-functions'."
(mapc #'funcall chronometrist-third-alert-functions))
#+END_SRC
** stop-alert-timers :procedure:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-third-stop-alert-timers ()
"Stop timers in `chronometrist-third-timer-list'."
(mapc (lambda (timer) (cancel-timer timer)) chronometrist-third-timer-list))
#+END_SRC
** clock-in :hook:procedure:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-third-clock-in (&optional _arg)
"Stop alert timers and update break time."
(chronometrist-third-stop-alert-timers)
(unless (zerop chronometrist-third-break-time)
(-let* (((&plist :stop stop) (cl-second (chronometrist-to-list (chronometrist-active-backend))))
(used-break (ts-diff (ts-now) (chronometrist-iso-to-ts stop)))
(used-break-string (format-seconds chronometrist-third-duration-format used-break))
(new-break (- chronometrist-third-break-time used-break))
(old-break chronometrist-third-break-time))
(setq chronometrist-third-break-time (if (> new-break 0) new-break 0))
(alert
(if (zerop chronometrist-third-break-time)
(format "You have used up all %s of your break time (%s break)"
(format-seconds chronometrist-third-duration-format old-break)
used-break-string)
(format "You have used %s of your break time (%s left)"
used-break-string
(format-seconds chronometrist-third-duration-format chronometrist-third-break-time)))))))
#+END_SRC
** clock-out :hook:procedure:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-third-clock-out (&optional _arg)
"Update break time based on the latest work interval.
Run `chronometrist-third-alert-functions' to alert user when
break time is up."
(let* ((latest-work-duration (chronometrist-interval (chronometrist-latest-record (chronometrist-active-backend))))
(break-time-increment (/ latest-work-duration chronometrist-third-divisor)))
(cl-incf chronometrist-third-break-time break-time-increment)
(alert (format "You have gained %s of break time (%s total)"
(format-seconds chronometrist-third-duration-format break-time-increment)
(format-seconds chronometrist-third-duration-format chronometrist-third-break-time)))
;; start alert timer(s)
(chronometrist-third-start-alert-timers)))
#+END_SRC
** third-minor-mode :minor:mode:
#+BEGIN_SRC emacs-lisp
;;;###autoload
(define-minor-mode chronometrist-third-minor-mode
nil nil nil nil
(cond (chronometrist-third-minor-mode
(add-hook 'chronometrist-after-in-functions #'chronometrist-third-clock-in)
(add-hook 'chronometrist-after-out-functions #'chronometrist-third-clock-out))
(t (remove-hook 'chronometrist-after-in-functions #'chronometrist-third-clock-in)
(remove-hook 'chronometrist-after-out-functions #'chronometrist-third-clock-out))))
#+END_SRC
** Provide
#+BEGIN_SRC emacs-lisp :comments no
(provide 'chronometrist-third)
;;; chronometrist-third.el ends here
#+END_SRC
* Local variables :noexport:
# Local Variables:
# my-org-src-default-lang: "emacs-lisp"
# eval: (when (package-installed-p 'literate-elisp) (require 'literate-elisp) (literate-elisp-load (buffer-file-name)))
# End:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,92 +1,201 @@
#+TITLE: Chronometrist - an extensible time tracker for Emacs
#+SUBTITLE: User Manual
#+TITLE: Chronometrist
#+SUBTITLE: Friendly and powerful personal time tracker/analyzer with Emacs and CLIM frontends
#+DESCRIPTION: User Manual
#+HTML_HEAD: <link rel="stylesheet" type="text/css" href="style.css" />
[[https://melpa.org/#/chronometrist][file:https://melpa.org/packages/chronometrist-badge.svg]]
#+BEGIN_EXPORT html
<a href="https://liberapay.com/contrapunctus/donate">
<img alt="Donate using Liberapay" src="https://img.shields.io/liberapay/receives/contrapunctus.svg?logo=liberapay">
</a>
A time tracker in Emacs with a nice interface
<a href="https://melpa.org/#/chronometrist">
<img src="https://melpa.org/packages/chronometrist-badge.svg">
</a>
#+END_EXPORT
Largely modelled after the Android application, [[https://github.com/netmackan/ATimeTracker][A Time Tracker]]
* Explanation
:PROPERTIES:
:CUSTOM_ID: explanation
:END:
Chronometrist is a friendly and powerful personal time tracker and analyzer. It has frontends for Emacs and [[https://mcclim.common-lisp.dev/][CLIM]].
* Benefits
#+CAPTION: The main Chronometrist buffer, with the enabled extensions [[#time-goals][chronometrist-goal]] ("Targets" column + alerts) and chronometrist-spark ("Graph" column displaying the activity for the past 4 weeks).
[[file:doc/2022-02-20 13-26-53.png]]
** Benefits
:PROPERTIES:
:CUSTOM_ID: benefits
:END:
1. Extremely simple and efficient to use
2. Displays useful information about your time usage
2. Displays useful information about your time usage (including fancy graphs with the =chronometrist-spark= extension)
3. Support for both mouse and keyboard
4. Human errors in tracking are easily fixed by editing a plain text file
5. Hooks to let you perform arbitrary actions when starting/stopping tasks
4. Human errors in tracking can be easily fixed by editing a plain text file
5. Hooks to integrate time tracking into your workflow
* Limitations
1. No support (yet) for adding a task without clocking into it.
2. No support for concurrent tasks.
** Limitations
:PROPERTIES:
:CUSTOM_ID: limitations
:END:
1. No support for concurrent tasks.
* Comparisons
** Comparisons
:PROPERTIES:
:CUSTOM_ID: comparisons
:END:
** timeclock.el
*** timeclock.el (Emacs built-in)
:PROPERTIES:
:CUSTOM_ID: timeclock.el
:END:
Compared to timeclock.el, Chronometrist
+ stores data in an s-expression format rather than a line-based one
+ supports attaching tags and arbitrary key-values to time intervals
+ has commands to shows useful summaries
+ has more hooks
** Org time tracking
*** Org time tracking
:PROPERTIES:
:CUSTOM_ID: org-time-tracking
:END:
Chronometrist and Org time tracking seem to be equivalent in terms of capabilities, approaching the same ends through different means.
+ Chronometrist doesn't have a mode line indicator at the moment. (planned)
+ Chronometrist doesn't have Org's sophisticated querying facilities. (an SQLite backend is planned)
+ Org does so many things that keybindings seem to necessarily get longer. Chronometrist has far fewer commands than Org, so most of the keybindings are single keys, without modifiers.
+ Chronometrist's UI makes keybindings discoverable - they are displayed in the buffers themselves.
+ Chronometrist's UI is cleaner, since the storage is separate from the display. It doesn't show tasks as trees like Org, but it uses tags and key-values to achieve that. Additionally, navigating a flat list takes fewer user operations than navigating a tree.
+ Chronometrist data is just s-expressions (plists), and may be easier to parse than a complex text format with numerous use-cases.
* Installation
** Common Lisp port
:PROPERTIES:
:CUSTOM_ID: common-lisp-port
:END:
In March 2022, work began on the long-awaited Common Lisp port of Chronometrist, which aims to create -
1. a greater variety of backends (e.g. SQLite)
2. a common reusable library for frontends to use,
3. a greater variety of frontends, such as -
* a command line interface (CLI), for UNIX scripting;
* a terminal user inteface (TUI), for those so inclined;
* a CLIM (Common Lisp Interface Manager) GUI [fn:1],
* Qt and Android interfaces using [[https://gitlab.com/eql/lqml][LQML]],
* web frontends (possibly via [[https://common-lisp.net/project/parenscript/][Parenscript]] or [[https://github.com/rabbibotton/clog][CLOG]]),
* and perhaps even an interface for wearable devices!
The port was also driven by the desire to have access to Common Lisp's better performance, and features such as namespaces, a /de facto/ standard build system, multithreading, SQLite bindings, a more fully-featured implementation of CLOS and MOP, and type annotations, checking, and inference.
The literate sources for the Common Lisp port may be found in [[file:cl/chronometrist.org][cl/chronometrist.org]]. Currently, this port can -
1. import from a plist-group file and export to an SQLite database
#+BEGIN_SRC lisp
(chronometrist:to-file (chronometrist:to-hash-table
(make-instance 'chronometrist.plist-group:plist-group-backend
:file "/path/to/file.plg"))
(make-instance 'chronometrist.sqlite:sqlite-backend)
"/path/to/file.sqlite")
#+END_SRC
2. display a (WIP) CLIM GUI - =(chronometrist.clim:run-chronometrist)=
The Emacs Lisp codebase will probably become an Emacs frontend to a future Common Lisp CLI client.
[fn:1] McCLIM also has an incomplete ncurses backend - when completed, a CLIM frontend could provide a TUI "for free".
** Literate program
:PROPERTIES:
:CUSTOM_ID: explanation-literate-program
:END:
Chronometrist is written as an Org literate program, which makes it easy to obtain different views of the program source, thanks to tree- and source-block folding, tags, properties, and the =org-match= command.
The canonical source file is [[file:elisp/chronometrist.org][elisp/chronometrist.org]], which contains source blocks. These are provided to users after /tangling/ (extracting the source into an Emacs Lisp file). [fn:2]
The Org literate program can also be loaded directly using the [[https://github.com/jingtaozf/literate-elisp][literate-elisp]] package, so that all source links (e.g. =xref=, =describe-function=) lead to the Org file. See [[#how-to-literate-elisp][How to load the program using literate-elisp]].
[fn:2] the literate source is also included in MELPA installs, although not loaded through =literate-elisp-load= by default, since doing so would interfere with automatic generation of autoloads.
** Source code overview
:PROPERTIES:
:CUSTOM_ID: source-code-overview
:END:
At its most basic, we read data from a [[file:elisp/chronometrist.org::#program-backend][backend]] and [[file:elisp/chronometrist.org::#program-frontend-chronometrist][display it]] as a [[elisp:(find-library "tabulated-list")][=tabulated-list-mode=]] buffer.
The plist and plist-group backends (collectively known as the s-expression backends) =read= a text file containing s-expressions into a [[file:elisp/chronometrist.org::#program-data-structures][hash table]], and query that. When the file is changed—whether by the program or the user—they [[file:elisp/chronometrist.org::refresh-file][update the hash table]] and the [[file:elisp/chronometrist.org::#program-frontend-chronometrist-refresh][buffer]]. The s-expression backends also make use of a [[file:elisp/chronometrist.org::#program-pretty-printer][plist pretty-printer]] of their own.
There are also some [[file:elisp/chronometrist.org::#program-migration][migration commands]].
Extensions exist for -
1. [[file:elisp/chronometrist-key-values.org][attaching arbitrary metadata]] to time intervals,
2. [[https://tildegit.org/contrapunctus/chronometrist-goal][time goals and alerts]], and
3. support for the [[file:elisp/chronometrist-third.org][Third Time system]]
** Contributions and contact
:PROPERTIES:
:CUSTOM_ID: contributions-contact
:END:
Feedback and MRs are very welcome. 🙂
+ [[file:TODO.org]] has a long list of tasks
+ [[file:elisp/chronometrist.org]] contains all developer-oriented documentation
If you have tried using Chronometrist, I'd love to hear your experiences! Get in touch with the author and other Emacs users in the Emacs channel on the Jabber network - [[https://conversations.im/j/emacs@salas.suchat.org][xmpp:emacs@salas.suchat.org?join]] ([[https://inverse.chat/#converse/room?jid=emacs@salas.suchat.org][web chat]])
(For help in getting started with Jabber, [[https://xmpp.org/getting-started/][click here]])
** License
:PROPERTIES:
:CUSTOM_ID: license
:END:
I'd /like/ for all software to be liberated - transparent, trustable, and accessible for anyone to use, study, or improve.
I'd /like/ anyone using my software to credit me for the work.
I'd /like/ to receive financial support for my efforts, so I can spend all my time doing what I find meaningful.
But I don't want to make demands or threats (e.g. via legal conditions) to accomplish all that, nor restrict my services to only those who can pay.
Thus, Chronometrist is released under your choice of [[https://unlicense.org/][Unlicense]] or the [[http://www.wtfpl.net/][WTFPL]].
(See files [[file:UNLICENSE][UNLICENSE]] and [[file:WTFPL][WTFPL]]).
** Thanks
:PROPERTIES:
:CUSTOM_ID: thanks
:END:
The main buffer and the report buffer are copied from the Android application, [[https://github.com/netmackan/ATimeTracker][A Time Tracker]]
wasamasa, bpalmer, aidalgol, pjb and the rest of #emacs for their tireless help and support
jwiegley for =timeclock.el=, which we used as a backend in earlier versions
blandest for helping me with the name
fiete and wu-lee for testing and bug reports
* Tutorials
:PROPERTIES:
:CUSTOM_ID: usage
:END:
** Installation
:PROPERTIES:
:CUSTOM_ID: installation
:END:
** from MELPA
*** from MELPA
:PROPERTIES:
:CUSTOM_ID: install-from-melpa
:END:
1. Set up MELPA - https://melpa.org/#/getting-started
(Chronometrist uses semantic versioning and only releases are pushed to the master branch, so using MELPA Stable is recommended and has no effect on frequency of updates.)
2. =M-x package-install RET chronometrist RET=
** from Git
*** from Git
:PROPERTIES:
:CUSTOM_ID: install-from-git
:END:
You can get =chronometrist= from https://tildegit.org/contrapunctus/chronometrist
You can get =chronometrist= from https://tildegit.org/contrapunctus/chronometrist or https://codeberg.org/contrapunctus/chronometrist
=chronometrist= requires
+ Emacs v25 or higher
+ [[https://github.com/magnars/dash.el][dash.el]]
+ [[https://github.com/alphapapa/ts.el][ts.el]]
The optional extension =chronometrist-key-values= requires =choice.el=, apart from =chronometrist= itself.
Add the "elisp/" subdirectory to your load-path, and =(require 'chronometrist)=.
* Usage
:PROPERTIES:
:CUSTOM_ID: usage
:END:
Add the ="elisp/"= subdirectory to your load-path, and =(require 'chronometrist)=.
** chronometrist
:PROPERTIES:
:CUSTOM_ID: chronometrist-1
:CUSTOM_ID: usage-chronometrist
:END:
Run =M-x chronometrist= to see your projects, the time you spent on them today, which one is active, and the total time clocked today.
Click or hit =RET= (=chronometrist-toggle-task=) on a project to start tracking time for it. If it's already clocked in, it will be clocked out.
@ -97,25 +206,29 @@ Press =r= to see a weekly report (see =chronometrist-report=)
** chronometrist-report
:PROPERTIES:
:CUSTOM_ID: chronometrist-report
:CUSTOM_ID: usage-chronometrist-report
:END:
Run =M-x chronometrist-report= (or =chronometrist= with a prefix argument of 1, or press =r= in the =chronometrist= buffer) to see a weekly report.
Press =b= to look at past weeks, and =f= for future weeks.
** chronometrist-statistics
:PROPERTIES:
:CUSTOM_ID: chronometrist-statistics
:CUSTOM_ID: usage-chronometrist-statistics
:END:
Run =M-x chronometrist-statistics= (or =chronometrist= with a prefix argument of 2) to view statistics.
Press =b= to look at past time ranges, and =f= for future ones.
** chronometrist-details
:PROPERTIES:
:CUSTOM_ID: chronometrist-details
:END:
** common commands
:PROPERTIES:
:CUSTOM_ID: usage-common-commands
:END:
In the buffers created by the previous three commands, you can press =l= (=chronometrist-open-log=) to view/edit your =chronometrist-file=, which by default is =~/.emacs.d/chronometrist.sexp=.
All of these commands will kill their buffer when run again with the buffer visible, so the keys you bind them to behave as a toggle.
@ -124,23 +237,21 @@ All buffers keep themselves updated via an idle timer - no need to frequently pr
** Time goals/targets
:PROPERTIES:
:CUSTOM_ID: time-goalstargets
:CUSTOM_ID: time-goals
:END:
If you wish you could define time goals for some tasks, and have Chronometrist notify you when you're approaching the goal, completing it, or exceeding it, check out the extension [[https://github.com/contrapunctus-1/chronometrist-goal/][chronometrist-goal.el]].
* How-to
* How-to Guides
:PROPERTIES:
:CUSTOM_ID: customization
:CUSTOM_ID: how-to
:END:
See the Customize groups =chronometrist= and =chronometrist-report= for variables intended to be user-customizable.
** How to display a prompt when exiting with an active task
:PROPERTIES:
:CUSTOM_ID: prompt-when-exiting-emacs
:CUSTOM_ID: how-to-prompt-when-exiting-emacs
:END:
Evaluate or add to your init.el the following -
=(add-hook 'kill-emacs-query-functions 'chronometrist-query-stop)=
@ -148,7 +259,9 @@ Evaluate or add to your init.el the following -
:PROPERTIES:
:CUSTOM_ID: how-to-literate-elisp
:END:
The literate Org document will automatically =literate-elisp-load= itself when opened, if =literate-elisp= is installed via =package.el=.
If you want it to be loaded with =literate-elisp-load= on Emacs startup, add the following to your init.el -
#+BEGIN_SRC emacs-lisp
(add-to-list 'load-path "<directory containing chronometrist.org>")
@ -160,8 +273,7 @@ Evaluate or add to your init.el the following -
:PROPERTIES:
:CUSTOM_ID: how-to-tags
:END:
1. Add =chronometrist-tags-add= to one or more of these hooks [fn:1] -
1. Add =chronometrist-tags-add= to one or more of these hooks [fn:3] -
#+BEGIN_SRC emacs-lisp
(add-to-list 'chronometrist-after-in-functions 'chronometrist-tags-add)
@ -172,33 +284,32 @@ Evaluate or add to your init.el the following -
The prompt suggests past combinations you used for the current task, which you can browse with =M-p=/=M-n=. You can leave it blank by pressing =RET=.
[fn:1] but not =chronometrist-before-in-functions=
[fn:3] but not =chronometrist-before-in-functions=
** How to attach key-values to time intervals
:PROPERTIES:
:CUSTOM_ID: how-to-key-value-pairs
:END:
1. Add =chronometrist-kv-add= to one or more of these hooks [fn:3] -
1. Add =chronometrist-kv-add= to one or more of these hooks [fn:2] -
#+BEGIN_SRC emacs-lisp
#+BEGIN_SRC emacs-lisp
(add-to-list 'chronometrist-after-in-functions 'chronometrist-kv-add)
(add-to-list 'chronometrist-before-out-functions 'chronometrist-kv-add)
(add-to-list 'chronometrist-after-out-functions 'chronometrist-kv-add)
#+END_SRC
#+END_SRC
To exit the prompt, press the key it indicates for quitting - you can then edit the resulting key-values by hand if required. Press =C-c C-c= to accept the key-values, or =C-c C-k= to cancel.
[fn:2] but not =chronometrist-before-in-functions=
** How to skip running hooks/attaching tags and key values
:PROPERTIES:
:CUSTOM_ID: how-to-skip-running-hooks/attaching-tags-and-key-values
:END:
Use =M-RET= (=chronometrist-toggle-task-no-hooks=) to clock in/out.
** How to open certain files when you start a task
:PROPERTIES:
:CUSTOM_ID: open-certain-files-when-you-start-a-task
:CUSTOM_ID: how-to-open-files-on-task-start
:END:
An idea from the author's own init -
#+BEGIN_SRC emacs-lisp
@ -214,9 +325,8 @@ An idea from the author's own init -
** How to warn yourself about uncommitted changes
:PROPERTIES:
:CUSTOM_ID: uncommitted-changes
:CUSTOM_ID: how-to-warn-uncommitted-changes
:END:
Another one, prompting the user if they have uncommitted changes in a git repository (assuming they use [[https://magit.vc/][Magit]]) -
#+BEGIN_SRC emacs-lisp
@ -239,34 +349,65 @@ Return nil (and run `magit-status') if the user answers no."
** How to display the current time interval in the activity indicator
:PROPERTIES:
:CUSTOM_ID: current-time-interval-in-activity-indicator
:CUSTOM_ID: how-to-activity-indicator
:END:
#+BEGIN_SRC emacs-lisp
(defun my-activity-indicator ()
(thread-last (plist-put (chronometrist-last)
:stop (chronometrist-format-time-iso8601))
list
chronometrist-events-to-durations
(-reduce #'+)
truncate
chronometrist-format-time))
(--> (chronometrist-latest-record (chronometrist-active-backend))
(plist-put it :stop (chronometrist-format-time-iso8601))
(list it)
(chronometrist-events-to-durations it)
(-reduce #'+ it)
(truncate it)
(chronometrist-format-duration it)))
(setq chronometrist-activity-indicator #'my-activity-indicator)
#+END_SRC
* Explanation
** Literate Program
** How to back up your Chronometrist data
:PROPERTIES:
:CUSTOM_ID: explanation-literate-program
:CUSTOM_ID: how-to-backup
:END:
Chronometrist is a literate program, made using Org - the canonical source is the =chronometrist.org= file, which contains source blocks. These are provided to users after /tangling/ (extracting the source into an Emacs Lisp file).
I suggest backing up Chronometrist data on each save using the [[https://tildegit.org/contrapunctus/async-backup][async-backup]] package.[fn:4] Here's how you can do that.
The Org file can also be loaded directly using the [[https://github.com/jingtaozf/literate-elisp][literate-elisp]] package, so that all source links (e.g. =xref=, =describe-function=) lead to the Org file, within the context of the concerned documentation. See [[#how-to-literate-elisp][How to load the program using literate-elisp]].
1. Add the following to your init.
#+BEGIN_SRC emacs-lisp
(use-package async-backup)
#+END_SRC
2. Open your Chronometrist file and add =async-backup= to a buffer-local =after-save-hook=.
: M-x chronometrist-open-log
: M-x add-file-local-variable-prop-line RET eval RET (add-hook 'after-save-hook #'async-backup nil t) RET
3. Optionally, configure =async-backup-location= to set a specific directory for the backups -
: (setq async-backup-location "/path/to/backup/dir/")
=chronometrist.org= is also included in MELPA installs, although not used directly by default, since doing so would interfere with automatic generation of autoloads.
[fn:4] It is possible to use Emacs' built-in backup system to do it, but since it is synchronous, doing so will greatly slow down saving of the Chronometrist file.
** How to configure Vertico for use with Chronometrist
:PROPERTIES:
:CUSTOM_ID: howto-vertico
:END:
By default, [[https://github.com/minad/vertico][Vertico]] uses its own sorting function - for some commands (such as =chronometrist-key-values-unified-prompt=) this results in /worse/ suggestions, since Chronometrist sorts suggestions in most-recent-first order.
You can either disable Vertico's sorting entirely -
#+BEGIN_SRC emacs-lisp
(setq vertico-sort-function nil)
#+END_SRC
Or use =vertico-multiform= to disable sorting for only specific commands -
#+BEGIN_SRC emacs-lisp
(use-package vertico-multiform
:init (vertico-multiform-mode)
:config
(setq vertico-multiform-commands
'((chronometrist-toggle-task (vertico-sort-function . nil))
(chronometrist-toggle-task-no-hooks (vertico-sort-function . nil))
(chronometrist-key-values-unified-prompt (vertico-sort-function . nil)))))
#+END_SRC
* User's reference
:PROPERTIES:
:CUSTOM_ID: users-reference
:END:
All variables intended for user customization are listed here. They serve as the public API for this project for the purpose of semantic versioning. Any changes to these which require a user to modify their configuration are considered breaking changes.
1. =chronometrist-file=
@ -293,41 +434,10 @@ Hooks
8. =chronometrist-file-change-hook=
9. =chronometrist-timer-hook=
* Contributions and contact
* Local variables :noexport:
:PROPERTIES:
:CUSTOM_ID: contributions-and-contact
:CUSTOM_ID: local-variables
:END:
Feedback and MRs are very welcome. 🙂
+ [[file:TODO.org]] has a long list of tasks
+ [[file:doc/manual.org]] contains an overview of the codebase, explains various mechanisms and decisions, and has a reference of definitions.
If you have tried using Chronometrist, I'd love to hear your experiences! Get in touch with the author and other Emacs users in the Emacs channel on the Jabber network - [[https://conversations.im/j/emacs@salas.suchat.org][xmpp:emacs@salas.suchat.org?join]] ([[https://inverse.chat/#converse/room?jid=emacs@salas.suchat.org][web chat]])
(For help in getting started with Jabber, [[https://xmpp.org/getting-started/][click here]])
* License
:PROPERTIES:
:CUSTOM_ID: license
:END:
I dream of a world where all software is liberated - transparent, trustable, and accessible for anyone to use or improve. But I don't want to make demands or threats (e.g. via legal conditions) to get there.
I'd rather make a request - please do everything you can to help that dream come true. Please Unlicense as much software as you can.
Chronometrist is released under your choice of [[https://unlicense.org/][Unlicense]] or the [[http://www.wtfpl.net/][WTFPL]].
(See files [[file:UNLICENSE]] and [[file:WTFPL]]).
* Thanks
:PROPERTIES:
:CUSTOM_ID: thanks
:END:
wasamasa, bpalmer, aidalgol, pjb and the rest of #emacs for their tireless help and support
jwiegley for timeclock.el, which we used as a backend in earlier versions
blandest for helping me with the name
fiete and wu-lee for testing and bug reports
# Local Variables:
# my-org-src-default-lang: "emacs-lisp"
# End:

View File

@ -0,0 +1,488 @@
#+PROPERTY: header-args :tangle yes :load yes :comments link
#+BEGIN_SRC emacs-lisp :load no :tangle no
(setq nameless-current-name "chronometrist")
#+END_SRC
* Setup
** test-file-path-stem
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-test-file-path-stem
(format "%stest" (file-name-directory (or load-file-name default-directory))))
#+END_SRC
** test-backends
=chronometrist-test-backend= is just here till I finish writing the tests for a single backend. Will tackle how to apply the same tests to different backends afterwards - possibly with a [[#chronometrist-ert-deftest][wrapper macro]] around =ert-deftest=.
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-test-backend
(make-instance 'chronometrist-plist-group-backend :path chronometrist-test-file-path-stem))
(defun chronometrist-make-test-backends ()
(cl-loop for backend in '(chronometrist-plist-backend chronometrist-plist-group-backend)
collect (make-instance backend :path chronometrist-test-file-path-stem)))
(defvar chronometrist-test-backends (chronometrist-make-test-backends))
#+END_SRC
** test-first-record
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-test-first-record
'(:name "Programming"
:start "2018-01-01T00:00:00+0530"
:stop "2018-01-01T01:00:00+0530"))
#+END_SRC
** test-latest-record
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-test-latest-record
'(:name "Programming"
:tags (reading)
:book "Smalltalk-80: The Language and Its Implementation"
:start "2020-05-10T16:33:17+0530"
:stop "2020-05-10T17:10:48+0530"))
#+END_SRC
** test-records
#+BEGIN_SRC emacs-lisp
(defvar chronometrist-test-records
'((:name "Programming"
:start "2018-01-01T00:00:00+0530"
:stop "2018-01-01T01:00:00+0530")
(:name "Swimming"
:start "2018-01-01T02:00:00+0530"
:stop "2018-01-01T03:00:00+0530")
(:name "Cooking"
:start "2018-01-01T04:00:00+0530"
:stop "2018-01-01T05:00:00+0530")
(:name "Guitar"
:start "2018-01-01T06:00:00+0530"
:stop "2018-01-01T07:00:00+0530")
(:name "Cycling"
:start "2018-01-01T08:00:00+0530"
:stop "2018-01-01T09:00:00+0530")
(:name "Programming"
:start "2018-01-02T23:00:00+0530"
:stop "2018-01-03T01:00:00+0530")
(:name "Cooking"
:start "2018-01-03T23:00:00+0530"
:stop "2018-01-04T01:00:00+0530")
(:name "Programming"
:tags (bug-hunting)
:project "Chronometrist"
:component "goals"
:start "2020-05-09T20:03:25+0530"
:stop "2020-05-09T20:05:55+0530")
(:name "Arrangement/new edition"
:tags (new edition)
:song "Songs of Travel"
:composer "Vaughan Williams, Ralph"
:start "2020-05-10T00:04:14+0530"
:stop "2020-05-10T00:25:48+0530")
(:name "Guitar"
:tags (classical warm-up)
:start "2020-05-10T15:41:14+0530"
:stop "2020-05-10T15:55:42+0530")
(:name "Guitar"
:tags (classical solo)
:start "2020-05-10T16:00:00+0530"
:stop "2020-05-10T16:30:00+0530")
(:name "Programming"
:tags (reading)
:book "Smalltalk-80: The Language and Its Implementation"
:start "2020-05-10T16:33:17+0530"
:stop "2020-05-10T17:10:48+0530")))
#+END_SRC
** cleanup
#+BEGIN_SRC emacs-lisp
(cl-defgeneric chronometrist-backend-test-cleanup (backend)
"Delete any files created by BACKEND during testing.")
(cl-defmethod chronometrist-backend-test-cleanup ((backend chronometrist-elisp-sexp-backend))
(with-slots (file hash-table) backend
(when file
(when (file-exists-p file)
(delete-file file))
(with-current-buffer (get-file-buffer file)
;; (erase-buffer)
;; Unsuccessful attempt to inhibit "Delete excess backup versions of <file>?" prompts
(defvar delete-old-versions)
(let ((delete-old-versions t))
(set-buffer-modified-p nil)
(kill-buffer))))
(setf hash-table (clrhash hash-table))))
(cl-defmethod chronometrist-backend-test-cleanup :after ((backend t))
(setf chronometrist-test-backends (chronometrist-make-test-backends)))
#+END_SRC
** ert-deftest :macro:
:PROPERTIES:
:CUSTOM_ID: chronometrist-ert-deftest
:END:
#+BEGIN_SRC emacs-lisp
(defmacro chronometrist-ert-deftest (name backend-var &rest test-forms)
"Generate test groups containing TEST-FORMS for each backend.
BACKEND-VAR is bound to each backend in
`chronometrist-test-backends'. TEST-FORMS are passed to
`ert-deftest'."
(declare (indent defun) (debug t))
(cl-loop for backend in chronometrist-test-backends collect
(let* ((backend-name (string-remove-suffix
"-backend"
(string-remove-prefix "chronometrist"
(symbol-name
(eieio-object-class-name backend)))))
(test-name (concat "chronometrist-" (symbol-name name) backend-name)))
`(ert-deftest ,(intern test-name) ()
(let ((,backend-var ,backend))
(unwind-protect
(progn ,@test-forms)
;; cleanup - remove test backend file
(chronometrist-backend-test-cleanup ,backend)))))
into test-groups
finally return (cons 'progn test-groups)))
#+END_SRC
* Tests
** common
*** current-task
:PROPERTIES:
:CUSTOM_ID: tests-common-current-task
:END:
#+BEGIN_SRC emacs-lisp
(chronometrist-ert-deftest current-task b
;; (message "current-task test - hash-table-count %s" (hash-table-count (chronometrist-backend-hash-table b)))
(chronometrist-create-file b)
(should (not (chronometrist-current-task b)))
(chronometrist-insert b (list :name "Test" :start (chronometrist-format-time-iso8601)))
(should (equal "Test" (chronometrist-current-task b)))
(chronometrist-remove-last b)
(should (not (chronometrist-current-task b))))
#+END_SRC
*** plist-p
:PROPERTIES:
:CUSTOM_ID: tests-common-plist-p
:END:
#+BEGIN_SRC emacs-lisp
(ert-deftest chronometrist-plist-p ()
(should (eq t (chronometrist-plist-p '(:a 1 :b 2))))
(should (eq nil (chronometrist-plist-p '(0 :a 1 :b 2))))
(should (eq nil (chronometrist-plist-p '(:a 1 :b 2 3))))
(should (not (chronometrist-plist-p nil))))
#+END_SRC
*** plists-split-p
:PROPERTIES:
:CUSTOM_ID: tests-common-plists-split-p
:END:
#+BEGIN_SRC emacs-lisp
(ert-deftest chronometrist-plists-split-p ()
(should
(chronometrist-plists-split-p
'(:name "Cooking"
:recipe "whole wheat penne rigate in arrabbiata sauce"
:start "2021-11-30T23:01:10+0530"
:stop "2021-12-01T00:00:00+0530")
'(:name "Cooking"
:recipe "whole wheat penne rigate in arrabbiata sauce"
:start "2021-12-01T00:00:00+0530"
:stop "2021-12-01T00:06:22+0530")))
;; without :stop
(should
(chronometrist-plists-split-p
'(:name "Cooking"
:recipe "whole wheat penne rigate in arrabbiata sauce"
:start "2021-11-30T23:01:10+0530"
:stop "2021-12-01T00:00:00+0530")
'(:name "Cooking"
:recipe "whole wheat penne rigate in arrabbiata sauce"
:start "2021-12-01T00:00:00+0530")))
;; difference in time
(should
(not (chronometrist-plists-split-p
'(:name "Cooking"
:recipe "whole wheat penne rigate in arrabbiata sauce"
:start "2021-11-30T23:01:10+0530"
:stop "2021-12-01T00:00:00+0530")
'(:name "Cooking"
:recipe "whole wheat penne rigate in arrabbiata sauce"
:start "2021-12-01T00:00:01+0530"
:stop "2021-12-01T00:06:22+0530"))))
;; difference in key-values
(should
(not (chronometrist-plists-split-p
'(:name "Cooking"
:recipe "whole wheat penne rigate in arrabbiata sauce"
:start "2021-11-30T23:01:10+0530"
:stop "2021-12-01T00:00:00+0530")
'(:name "Cooking"
:start "2021-12-01T00:00:00+0530"
:stop "2021-12-01T00:06:22+0530")))))
#+END_SRC
** data structures
*** list-tasks
#+BEGIN_SRC emacs-lisp
(chronometrist-ert-deftest list-tasks b
;; (message "list-tasks test - hash-table-count %s" (hash-table-count (chronometrist-backend-hash-table b)))
(chronometrist-create-file b)
(let ((task-list (chronometrist-list-tasks b)))
(should (listp task-list))
(should (seq-every-p #'stringp task-list))))
#+END_SRC
** time functions
*** format-duration-long :pure:
#+BEGIN_SRC emacs-lisp
(ert-deftest chronometrist-format-duration-long ()
(should (equal (chronometrist-format-duration-long 5) ""))
(should (equal (chronometrist-format-duration-long 65) "1 minute"))
(should (equal (chronometrist-format-duration-long 125) "2 minutes"))
(should (equal (chronometrist-format-duration-long 3605) "1 hour"))
(should (equal (chronometrist-format-duration-long 3660) "1 hour, 1 minute"))
(should (equal (chronometrist-format-duration-long 3725) "1 hour, 2 minutes"))
(should (equal (chronometrist-format-duration-long 7200) "2 hours"))
(should (equal (chronometrist-format-duration-long 7260) "2 hours, 1 minute"))
(should (equal (chronometrist-format-duration-long 7320) "2 hours, 2 minutes")))
#+END_SRC
** plist pretty-printing
[[file:../elisp/chronometrist.org::#program-pretty-printer][source]]
*** plist-group-p
#+BEGIN_SRC emacs-lisp
(ert-deftest chronometrist-plist-group-p ()
(should (eq t (chronometrist-plist-group-p '(symbol (:a 1 :b 2)))))
(should (eq t (chronometrist-plist-group-p '("string" (:a 1 :b 2)))))
(should (not (chronometrist-plist-group-p nil)))
(should (not (chronometrist-plist-group-p '("string")))))
#+END_SRC
*** plist-pp-to-string
#+BEGIN_SRC emacs-lisp
(ert-deftest chronometrist-pp-to-string ()
(should
(equal
(chronometrist-pp-to-string
'(:name "Task"
:tags (foo bar)
:comment ((70 . "baz")
"zot"
(16 . "frob")
(20 20 "quux"))
:start "2020-06-25T19:27:57+0530"
:stop "2020-06-25T19:43:30+0530"))
(concat
"(:name \"Task\"\n"
" :tags (foo bar)\n"
" :comment ((70 . \"baz\")\n"
" \"zot\"\n"
" (16 . \"frob\")\n"
" (20 20 \"quux\"))\n"
" :start \"2020-06-25T19:27:57+0530\"\n"
" :stop \"2020-06-25T19:43:30+0530\")")))
(should
(equal
(chronometrist-pp-to-string
'(:name "Singing"
:tags (classical solo)
:piece ((:composer "Gioachino Rossini"
:name "Il barbiere di Siviglia"
:aria ("All'idea di quel metallo" "Dunque io son"))
(:composer "Ralph Vaughan Williams"
:name "Songs of Travel"
:movement ((4 . "Youth and Love")
(5 . "In Dreams")
(7 . "Wither Must I Wander?")))
(:composer "Ralph Vaughan Williams"
:name "Merciless Beauty"
:movement 1)
(:composer "Franz Schubert"
:name "Winterreise"
:movement ((1 . "Gute Nacht")
(2 . "Die Wetterfahne")
(4 . "Erstarrung"))))
:start "2020-11-01T12:01:20+0530"
:stop "2020-11-01T13:08:32+0530"))
(concat
"(:name \"Singing\"\n"
" :tags (classical solo)\n"
" :piece ((:composer \"Gioachino Rossini\"\n"
" :name \"Il barbiere di Siviglia\"\n"
" :aria (\"All'idea di quel metallo\" \"Dunque io son\"))\n"
" (:composer \"Ralph Vaughan Williams\"\n"
" :name \"Songs of Travel\"\n"
" :movement ((4 . \"Youth and Love\")\n"
" (5 . \"In Dreams\")\n"
" (7 . \"Wither Must I Wander?\")))\n"
" (:composer \"Ralph Vaughan Williams\"\n"
" :name \"Merciless Beauty\"\n"
" :movement 1)\n"
" (:composer \"Franz Schubert\"\n"
" :name \"Winterreise\"\n"
" :movement ((1 . \"Gute Nacht\")\n"
" (2 . \"Die Wetterfahne\")\n"
" (4 . \"Erstarrung\"))))\n"
" :start \"2020-11-01T12:01:20+0530\"\n"
" :stop \"2020-11-01T13:08:32+0530\")")))
(should (equal
(chronometrist-pp-to-string
'(:name "Cooking"
:tags (lunch)
:recipe (:name "moong-masoor ki dal"
:url "https://www.mirchitales.com/moong-masoor-dal-red-and-yellow-lentil-curry/")
:start "2020-09-23T15:22:39+0530"
:stop "2020-09-23T16:29:49+0530"))
(concat
"(:name \"Cooking\"\n"
" :tags (lunch)\n"
" :recipe (:name \"moong-masoor ki dal\"\n"
" :url \"https://www.mirchitales.com/moong-masoor-dal-red-and-yellow-lentil-curry/\")\n"
" :start \"2020-09-23T15:22:39+0530\"\n"
" :stop \"2020-09-23T16:29:49+0530\")")))
(should (equal
(chronometrist-pp-to-string
'(:name "Exercise"
:tags (warm-up)
:start "2018-11-21T15:35:04+0530"
:stop "2018-11-21T15:38:41+0530"
:comment ("stretching" (25 10 "push-ups"))))
(concat
"(:name \"Exercise\"\n"
" :tags (warm-up)\n"
" :start \"2018-11-21T15:35:04+0530\"\n"
" :stop \"2018-11-21T15:38:41+0530\"\n"
" :comment (\"stretching\" (25 10 \"push-ups\")))")))
(should (equal
(chronometrist-pp-to-string
'(:name "Guitar"
:tags (classical)
:warm-up ((right-hand-patterns "pima" "piam" "pmia" "pmai" "pami" "paim"))
:start "2021-09-28T17:49:18+0530"
:stop "2021-09-28T17:53:49+0530"))
(concat
"(:name \"Guitar\"\n"
" :tags (classical)\n"
" :warm-up ((right-hand-patterns \"pima\" \"piam\" \"pmia\" \"pmai\" \"pami\" \"paim\"))\n"
" :start \"2021-09-28T17:49:18+0530\"\n"
" :stop \"2021-09-28T17:53:49+0530\")")))
(should (equal
(chronometrist-pp-to-string
'(:name "Cooking"
:tags (lunch)
:recipe ("urad dhuli"
(:name "brown rice"
:brand "Dawat quick-cooking"
:quantity "40% of steel measuring glass"
:water "2× dry rice"))
:start "2021-11-07T14:40:45+0530"
:stop "2021-11-07T15:28:13+0530"))
(concat
"(:name \"Cooking\"\n"
" :tags (lunch)\n"
" :recipe (\"urad dhuli\"\n"
" (:name \"brown rice\"\n"
" :brand \"Dawat quick-cooking\"\n"
" :quantity \"40% of steel measuring glass\"\n"
" :water \"2× dry rice\"))\n"
" :start \"2021-11-07T14:40:45+0530\"\n"
" :stop \"2021-11-07T15:28:13+0530\")"))))
#+END_SRC
** backend
Situations
1. no file
2. empty file
3. non-empty file with no records
4. single record
* active
* inactive
* active, day-crossing
* inactive, day-crossing
5. multiple records
6. +[plist-group] latest plist is split+ (covered in #4)
Tests to be added -
1. to-hash-table
2. to-file
The order of these tests is important - the last test for each case is one which moves into the next case.
*** create-file
:PROPERTIES:
:CUSTOM_ID: tests-backend-create-file
:END:
#+BEGIN_SRC emacs-lisp
(chronometrist-ert-deftest create-file b
;; (message "create-file test - hash-table-count %s" (hash-table-count (chronometrist-backend-hash-table b)))
;; * file does not exist *
(should (chronometrist-create-file b))
;; * file exists but has no records *
(should (not (chronometrist-create-file b))))
#+END_SRC
*** latest-date-records
#+BEGIN_SRC emacs-lisp
(chronometrist-ert-deftest latest-date-records b
;; (message "latest-date-records test - hash-table-count %s" (hash-table-count (chronometrist-backend-hash-table b)))
(let ((plist-1 (cl-first chronometrist-test-records))
(plist-2 (cl-second chronometrist-test-records))
(today-ts (chronometrist-date-ts)))
;; * file does not exist *
(should-error (chronometrist-latest-date-records b))
(should (chronometrist-create-file b))
;; (message "latest-date-records: %S" (chronometrist-latest-date-records b))
;; * file exists but has no records *
(should (not (chronometrist-latest-date-records b)))
(should (chronometrist-insert b plist-1))
;; * backend has a single active record *
;; * backend has a single inactive record *
;; * backend has a single active day-crossing record *
;; * backend has a single inactive day-crossing record *
;; * backend has two records and one is active *
;; * backend has two records and both are inactive *
;; * backend has two day-crossing records and one is active *
;; * backend has two day-crossing records and both are inactive *
))
#+END_SRC
*** insert
#+BEGIN_SRC emacs-lisp
(chronometrist-ert-deftest insert b
;; (message "insert test - hash-table-count %s" (hash-table-count (chronometrist-backend-hash-table b)))
(let* ((plist1 (list :name "Test" :start (chronometrist-format-time-iso8601)))
(plist2 (append plist1 (list :stop (chronometrist-format-time-iso8601)))))
;; * file does not exist *
(should-error (chronometrist-insert b plist1))
(should (chronometrist-create-file b))
;; * file exists but has no records *
(should (chronometrist-insert b plist1))
(should (equal (progn (chronometrist-reset-backend b)
(chronometrist-latest-date-records b))
(list (chronometrist-date-iso) plist1)))
;; * backend has a single active record *
(should (chronometrist-replace-last b plist2))
(should (equal (progn (chronometrist-reset-backend b)
(chronometrist-latest-date-records b))
(list (chronometrist-date-iso) plist2)))
;; * backend has a single inactive record *
;; * backend has a single active day-crossing record *
;; * backend has a single inactive day-crossing record *
;; * backend has two records and one is active *
;; * backend has two records and both are inactive *
;; * backend has two day-crossing records and one is active *
;; * backend has two day-crossing records and both are inactive *
))
#+END_SRC
* Local Variables
# Local Variables:
# delete-old-versions: t
# End:

View File

@ -1,57 +0,0 @@
(:name "Programming"
:start "2018-01-01T00:00:00+0530"
:stop "2018-01-01T01:00:00+0530")
(:name "Swimming"
:start "2018-01-01T02:00:00+0530"
:stop "2018-01-01T03:00:00+0530")
(:name "Cooking"
:start "2018-01-01T04:00:00+0530"
:stop "2018-01-01T05:00:00+0530")
(:name "Guitar"
:start "2018-01-01T06:00:00+0530"
:stop "2018-01-01T07:00:00+0530")
(:name "Cycling"
:start "2018-01-01T08:00:00+0530"
:stop "2018-01-01T09:00:00+0530")
(:name "Programming"
:start "2018-01-02T23:00:00+0530"
:stop "2018-01-03T01:00:00+0530")
(:name "Cooking"
:start "2018-01-03T23:00:00+0530"
:stop "2018-01-04T01:00:00+0530")
(:name "Programming"
:tags (bug-hunting)
:project "Chronometrist"
:component "goals"
:start "2020-05-09T20:03:25+0530"
:stop "2020-05-09T20:05:55+0530")
(:name "Arrangement/new edition"
:tags (new edition)
:song "Songs of Travel"
:composer "Vaughan Williams, Ralph"
:start "2020-05-10T00:04:14+0530"
:stop "2020-05-10T00:25:48+0530")
(:name "Guitar"
:tags (classical warm-up)
:start "2020-05-10T15:41:14+0530"
:stop "2020-05-10T15:55:42+0530")
(:name "Guitar"
:tags (classical solo)
:start "2020-05-10T16:00:00+0530"
:stop "2020-05-10T16:30:00+0530")
(:name "Programming"
:tags (reading)
:book "Smalltalk-80: The Language and Its Implementation"
:start "2020-05-10T16:33:17+0530"
:stop "2020-05-10T17:10:48+0530")