Merge branch 'dev'
This commit is contained in:
commit
9fe6e2ed66
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -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
|
||||
|
|
|
@ -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/
|
||||
|
||||
|
|
25
TODO.org
25
TODO.org
|
@ -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),
|
||||
|
|
|
@ -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'.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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).
|
38
doc/tags.md
38
doc/tags.md
|
@ -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)
|
||||
|
|
Reference in New Issue