Merge branch 'dev'

This commit is contained in:
contrapunctus 2019-09-09 12:55:18 +05:30
commit 9fe6e2ed66
8 changed files with 149 additions and 46 deletions

View File

@ -4,6 +4,16 @@ 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]
## [0.2.2] - 2019-09-09
### Fixed
* Error resulting from incorrect variable name in chronometrist-maybe-stop-timer
* Long waiting times after saving timeclock-file due to multiple, erroneously-created file system watchers.
### Deprecated
* timeclock will be removed as a backend in the next release.
## [0.2.1] - 2019-08-14
### Fixed
* bug with wrongly named function in chronometrist-report

View File

@ -16,6 +16,8 @@ Largely modelled after the Android application, [A Time Tracker](https://github.
2. No support for concurrent tasks. (timeclock.el limitation)
3. Does not support all timeclock.el features (please make an issue or a PR)
**WARNING: with the next version (v0.3), chronometrist will no longer use timeclock as a dependency and will use its own s-expression-based backend. A command to migrate the timeclock-file will be provided.**
## Installation
You can get `chronometrist` from https://framagit.org/contrapunctus/chronometrist/

View File

@ -2,26 +2,29 @@
2. -report - highlight the current day
* Chronometrist
** Features
1. Create chronometrist-unused-projects-list + chronometrist-projects-list.
** UX
1. don't suggest nil when asking for first project on first run
2. 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
3. Mouse commands should work only on buttons.
4. Button actions should accept prefix arguments and behave exactly like their keyboard counterparts.
5. mouse-3 should clock-out without asking for reason.
6. 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.
** Code
1. use variables instead of hardcoded numbers to determine spacing
2. refactor repetitive calls to (format "%04d-%02d-%02d" (elt seq a) (elt seq b) (elt seq c))
3. Timeclock already _has_ hooks! :| Why do we re-implement them?
- I don't know of a way to know the project being clocked into using timeclock hooks.
- With v0.2.0 Chronometrist also has a before-project-stop-functions, which runs before the project is stopped, and can control whether the project actually is stopped.
4. Optimization. ~chronometrist-refresh~ is expensive in CPU, and ~chronometrist-timer~ runs it every 3 seconds by default. :\
6. 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.
7. Optimization. ~chronometrist-refresh~ is expensive in CPU, and ~chronometrist-timer~ runs it every 3 seconds by default. :\
Ideas -
* Support multiple files, so we read and process lesser data when one of them changes.
* Make file writing async
* Don't refresh from file when clocking in.
* Only write to the file when Emacs is idle or being killed, and store data in memory (in the events hash table) in the meantime
5. Use buttercup instead of ert
6. See if it is possible to store buttons in a variable, so *-print-non-tabular functions can be made shorter and less imperative. (see ~make-text-button~)
7. Merge all event-querying functions so that they always operate on an entire hash table (so no 'day' variants),
* What if commands both write to the file /and/ add to the hash table, so we don't have to re-read the file and re-populate the table for commands? The expensive reading+parsing could be avoided for commands, and only take place for the user changing the file.
** Code
1. use variables instead of hardcoded numbers to determine spacing
2. refactor repetitive calls to (format "%04d-%02d-%02d" (elt seq a) (elt seq b) (elt seq c))
3. Timeclock already _has_ hooks! :| Why do we re-implement them?
- I don't know of a way to know the project being clocked into using timeclock hooks.
- With v0.2.0 Chronometrist also has a before-project-stop-functions, which runs before the project is stopped, and can control whether the project actually is stopped.
4. Use buttercup instead of ert
5. See if it is possible to store buttons in a variable, so *-print-non-tabular functions can be made shorter and less imperative. (see ~make-text-button~)
6. Merge all event-querying functions so that they always operate on an entire hash table (so no 'day' variants),

View File

@ -81,28 +81,27 @@ This function always returns nil."
(with-current-buffer (find-file-noselect timeclock-file)
(save-excursion
(goto-char (point-min))
(let ((events))
(while (not (= (point) (point-max)))
(let* ((event-string (buffer-substring-no-properties (point-at-bol)
(point-at-eol)))
(info-re (concat ". " chronometrist-date-re " " chronometrist-time-re-file))
(project-or-comment (->> event-string
(replace-regexp-in-string (concat info-re " ?") "")
(vector)))
(the-rest (--> (concat "\\(" info-re "\\)" ".*")
(replace-regexp-in-string it "\\1" event-string)
(split-string it "[ /:]")
(append (list (car it))
(mapcar #'string-to-number (-slice it 1 7)))))
(key (-slice the-rest 1 4))
(old-value (gethash key chronometrist-events))
(new-value (vector (vconcat the-rest ;; vconcat converts lists to vectors
project-or-comment))))
(if old-value
(puthash key (vconcat old-value new-value) chronometrist-events)
(puthash key new-value chronometrist-events)))
(forward-line))
nil))))
(while (not (= (point) (point-max)))
(let* ((event-string (buffer-substring-no-properties (point-at-bol)
(point-at-eol)))
(info-re (concat ". " chronometrist-date-re " " chronometrist-time-re-file))
(project-or-comment (->> event-string
(replace-regexp-in-string (concat info-re " ?") "")
(vector)))
(the-rest (--> (concat "\\(" info-re "\\)" ".*")
(replace-regexp-in-string it "\\1" event-string)
(split-string it "[ /:]")
(append (list (car it))
(mapcar #'string-to-number (-slice it 1 7)))))
(key (-slice the-rest 1 4))
(old-value (gethash key chronometrist-events))
(new-value (vector (vconcat the-rest ;; vconcat converts lists to vectors
project-or-comment))))
(if old-value
(puthash key (vconcat old-value new-value) chronometrist-events)
(puthash key new-value chronometrist-events)))
(forward-line))
nil)))
(defun chronometrist-events-subset (start-date end-date)
"Return a subset of `chronometrist-events'.

View File

@ -32,10 +32,16 @@ is clocked in to a project."
(unless chronometrist--timer-object
(setq chronometrist--timer-object
(run-at-time t chronometrist-update-interval #'chronometrist-timer))
(when arg
(when interactive-test
(message "Timer started."))
t))
(defun chronometrist-force-restart-timer ()
(interactive)
(cancel-timer chronometrist--timer-object)
(setq chronometrist--timer-object nil)
(run-at-time t chronometrist-update-interval #'chronometrist-timer))
(defun chronometrist-change-update-interval (arg)
(interactive "NEnter new interval (in seconds): ")
(cancel-timer chronometrist--timer-object)
@ -49,6 +55,4 @@ is clocked in to a project."
(provide 'chronometrist-timer)
(provide 'chronometrist-timer)
;;; chronometrist-timer.el ends here

View File

@ -51,6 +51,7 @@
(defvar chronometrist--timer-object nil)
(defvar chronometrist--project-history nil)
(defvar chronometrist--point nil)
(defvar chronometrist--fs-watcher nil)
;; ## FUNCTIONS ##
(defun chronometrist-current-project ()
@ -379,8 +380,8 @@ ASK is used like in `timeclock-out'."
If there is no project at point, do nothing.
With numeric prefix argument PREFIX, toggle the Nth project. If there
is no corresponding project, do nothing.
With numeric prefix argument PREFIX, toggle the Nth project. If
there is no corresponding project, do nothing.
If NO-PROMPT is non-nil, don't ask for a reason."
(interactive "P")
@ -410,7 +411,7 @@ If NO-PROMPT is non-nil, don't ask for a reason."
With numeric prefix argument PREFIX, toggle the Nth project. If there
is no corresponding project, do nothing."
(interactive "P")
(funcall-interactively #'chronometrist-toggle-project prefix t))
(chronometrist-toggle-project prefix t))
(defun chronometrist-add-new-project ()
"Add a new project."
@ -461,7 +462,11 @@ If numeric argument ARG is 2, run `chronometrist-statistics'."
(if chronometrist--point
(goto-char chronometrist--point)
(chronometrist-goto-last-project))))
(file-notify-add-watch timeclock-file '(change) #'chronometrist-refresh-file))))))
(unless chronometrist--fs-watcher
(setq chronometrist--fs-watcher
(file-notify-add-watch chronometrist-file
'(change)
#'chronometrist-refresh-file))))))))
;; Local Variables:
;; nameless-current-name: "chronometrist"

54
doc/query.md Normal file
View File

@ -0,0 +1,54 @@
Merge these
* chronometrist-total-time-one-day
* chronometrist-reason-list
* chronometrist-project-time-one-day
* chronometrist-intervals-on
* chronometrist-diary-projects-reasons-on
into either
* a single 'event query' function (which only returns events), OR
* a single query function, which returns whatever fields you specify from the events returned by whatever criteria you specify
# chronometrist-query
(&key RETURN CODE DATE TIME PROJECT COMMENT)
RETURN can be
* nil - entire events are returned
* a symbol - `code`, `date`, `time`, `project`, or `comment` - for each event, return only that object.
* a list of these symbols - for each event, return only these objects
CODE ...
DATE can be
* nil - query all dates
* Any date format we accept, including (YEAR MONTH DAY) and calendrical information returned by (decode-time)
* a pair containing one of the above formats - query between these dates. (nil . DATE) means from the beginning to DATE; conversely, (DATE . nil) means from DATE to the end.
* a list containing dates and/or pairs
TIME ...
PROJECT can be
* nil - for all projects
* A string - a single project
* A list of strings - these specific projects
COMMENT ...
(Sounds an awful lot like SQL, doesn't it? 😓)
## Arbitrary key values (custom storage format)
If we end up creating a custom s-exp based format which allows for arbitrary key values apart from the ones listed above (see [doc/tags.md](doc/tags.md)), we'd probably need to go the macro route.
How do we know how to compare a given key's value to the specified value? Should we have the user define them first?
Do we really need to define values and comparators? Or can `equal` do? I'd like to do
* pattern matching on dates e.g. `(2019 _ _)` for all dates in 2019 - but that can be accomplished with `:date (2019 1 1) (2019 12 31)`. A more complex example - `(_ 1 _)` for events from January for all years. Or `(_ _ 1)` for the first date of any month.
* regexp matching for string values
## Data structure
We use a hash table with dates as keys and events as values. But - if we want to make this something other projects can use - other applications may not want to have dates as keys.
One possibility is to use integers as keys.
## Nic Ferrier's [emacs-db](https://github.com/nicferrier/emacs-db) and [emacs-kv](https://github.com/nicferrier/emacs-kv)
I tried out -db. I wanted to store plists, but it seems to support only alists. Also, it stores them as a the printed representation of a hash table...which is probably faster than `read`ing individual s-exps and assembling that into one, but probably not nice to edit by hand (which is one of our major requirements).

View File

@ -3,12 +3,12 @@
1. A task has a name and zero or more tags. The name describes the task at a high level, and the tags are used to refine the description. A comment can be used for additional details.
Example -
```
(event :name "Playing"
:tags ("guitar" "solo")
...)
(event :name "OSM"
:tags ("survey")
...)
(event :name "Playing"
:tags ("guitar" "solo")
...)
(event :name "OSM"
:tags ("survey")
...)
```
2. A task only has tags, and no name. It is identified in the interface by the tags alone.
3. A task only has tags. The first N tags (1 or 2?) form the task name.
@ -31,3 +31,29 @@
## UI
1. Something to suggest tags which are commonly used with a given tag. Probably store it as a hash table or such, with tags as keys and `((tag . score) ...)` as values, sorted in descending order of scores.
UX flow
1. User sees list of their tasks (names or first tags, depending on tagging system design)
2. They select a task to start
3. When they stop with a universal arg, ask them if they want to add any tags. If we have tag-usage history, suggest adding those tags first.
* maybe use ~~magit-popup~~ Transient to help compose an entry instead?
* tag input must suggest earlier used tags, but must also support adding new ones.
Add new field - name of field - data -
~~magit-popup~~ Transient
# Tag UI
Should enable user to
1. create new tags
2. enter previously used tags quickly
3. avoid misspellings
## Solutions
* Use read-from-minibuffer - provides editable history, but no completion
* maybe use levenshtein.el to achieve #3
* "Add previous tags? [y]es, [n]o, [e]dit"
* completing-read-multiple - completion for multiple candidates, history, can suggest the last-used set of tags via DEF/INITIAL-INPUT
* A command which calls completing-read in an infinite loop, letting the user enter one tag at a time, and provides the user with keys to accept or cancel (quitting the loop). But completing-read doesn't have a common keymap which is inherited by ido and other completing-read replacements. (or does it?)
Default tag behavior for toggle commands (i.e. when no tagging is specified) - add no tags, or use last-used tags? (if present)