Merge branch 'dev' into cl-port

This commit is contained in:
contrapunctus 2022-04-01 22:42:10 +05:30
commit 04ede947b4
5 changed files with 175 additions and 123 deletions

View File

@ -936,8 +936,20 @@ The plist backend could theoretically be extended to support it, but I'd rather
:PROPERTIES:
:CREATED: 2022-01-08T23:32:37+0530
:END:
1. [ ] default suggested input backend should be the active backend
2. [ ] conserve file local variables prop line for text backends
1. [ ] On first launch, offer list of supported backends to migrate from
+ Some backends may not be installed - "see MELPA for more formats"
2. [ ] Use active backend as the suggested input backend
3. [ ] 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:
@ -1042,3 +1054,9 @@ Similar to [[#verify-command][verify command]], perhaps, but this is really abou
: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.

View File

@ -96,7 +96,7 @@ Return the connection object from `emacsql-sqlite'."
(cl-defmethod chronometrist-to-file (hash-table (backend chronometrist-sqlite-backend) file)
(with-slots (connection) backend
(delete-file file)
(emacsql-close connection)
(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
@ -120,7 +120,10 @@ prop-id of the inserted or existing property."
(emacsql connection
[:insert-or-ignore-into properties [properties] :values [$s1]]
props)
(caar (emacsql connection [:select (funcall max prop-id) :from properties])))))
(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."
@ -156,22 +159,23 @@ s-expressions in a text column."
(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)
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
(chronometrist-sqlite-insert-properties backend plist)
(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]
:values [$s1 $s2 $s3]]
name-id start-unix stop-unix)
(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
@ -201,6 +205,6 @@ s-expressions in a text column."
(cl-defmethod chronometrist-replace-last ((backend chronometrist-sqlite-backend) plist)
(emacsql db [:delete-from events :where ]))
(provide 'chronometrist-sqlite3)
(provide 'chronometrist-sqlite)
;;; chronometrist-sqlite3.el ends here
;;; chronometrist-sqlite.el ends here

View File

@ -115,29 +115,18 @@ Return the connection object from `emacsql-sqlite'."
finally return db)))
#+END_SRC
** to-file :method:
#+BEGIN_SRC emacs-lisp :load no :tangle no
(cl-defmethod chronometrist-to-file (hash-table (backend chronometrist-sqlite-backend) file)
(cl-loop with db = (emacsql-sqlite file)
with count = 0
for records being the hash-values of hash-table do
(cl-loop for record in records do
(chronometrist-insert record db)
(cl-incf count)
(when (zerop (% count 5))
(message "chronometrist-migrate - %s records converted" count)))
finally return count do
(message "chronometrist-migrate - finished converting %s events." count)))
#+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)
(emacsql-close connection)
(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
@ -164,7 +153,10 @@ prop-id of the inserted or existing property."
(emacsql connection
[:insert-or-ignore-into properties [properties] :values [$s1]]
props)
(caar (emacsql connection [:select (funcall max prop-id) :from properties])))))
(caar (emacsql connection [:select [prop-id]
:from properties
:where (= properties $s1)]
props)))))
#+END_SRC
*** properties-to-json :function:
@ -209,22 +201,23 @@ s-expressions in a text column."
(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)
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
(chronometrist-sqlite-insert-properties backend plist)
(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]
:values [$s1 $s2 $s3]]
name-id start-unix stop-unix)
(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
@ -272,9 +265,9 @@ s-expressions in a text column."
** Provide
#+BEGIN_SRC emacs-lisp
(provide 'chronometrist-sqlite3)
(provide 'chronometrist-sqlite)
;;; chronometrist-sqlite3.el ends here
;;; chronometrist-sqlite.el ends here
#+END_SRC
* Local variables :noexport:

View File

@ -755,6 +755,12 @@ Value must be a keyword corresponding to a key in
(cl-second (alist-get chronometrist-active-backend chronometrist-backends-alist)))
;; active-backend:1 ends here
;; [[file:chronometrist.org::*get-backend][get-backend:1]]
(defun chronometrist-get-backend (keyword)
"Return the backend object associated with KEYWORD."
(cl-second (alist-get keyword chronometrist-backends-alist)))
;; get-backend:1 ends here
;; [[file:chronometrist.org::*switch-backend][switch-backend:1]]
(defun chronometrist-switch-backend ()
(interactive)
@ -788,8 +794,7 @@ be replaced."
;; register-backend:1 ends here
;; [[file:chronometrist.org::*read-backend-name][read-backend-name:1]]
(defun chronometrist-read-backend-name (prompt backend-alist
&optional predicate return-keyword)
(defun chronometrist-read-backend-name (prompt backend-alist &optional predicate return-keyword)
"Prompt user for a Chronometrist backend name.
BACKEND-ALIST should be an alist similar to `chronometrist-backends-alist'.
@ -1876,50 +1881,63 @@ Return value is either a list in the form
;; remove-prefix:1 ends here
;; [[file:chronometrist.org::*migrate][migrate:1]]
(defun chronometrist-migrate ()
"Convert from one Chronometrist backend to another."
(defun chronometrist-migrate (&optional input-backend-keyword input-file
output-backend-keyword output-file
&rest load-files)
"Convert from one Chronometrist backend to another.
INPUT-BACKEND-KEYWORD and OUTPUT-BACKEND-KEYWORD must be keywords
corresponding to entries in `chronometrist-backends-alist'.
INPUT-FILE and OUTPUT-FILE must be file names. INPUT-FILE must
already exist.
LOAD-FILES must be paths to Elisp files. These files are `load'ed
before conversion. They may be used to point to Chronometrist
source files to be used."
(interactive)
(let* ((input-backend
(chronometrist-read-backend-name "Backend to convert: "
chronometrist-backends-alist))
(input-file-suggestion (chronometrist-backend-file input-backend))
(input-file (read-file-name "File to convert: " nil
input-file-suggestion t
input-file-suggestion))
(output-backend (chronometrist-read-backend-name
"Backend to write: "
chronometrist-backends-alist
(lambda (keyword)
(not (equal (cl-second
(alist-get keyword chronometrist-backends-alist))
input-backend)))))
(output-file-suggestion (chronometrist-backend-file output-backend))
(output-file (read-file-name "File to write: " nil nil nil
output-file-suggestion))
(input-backend-name (chronometrist-remove-prefix
(symbol-name
(eieio-object-class-name input-backend))))
(output-backend-name (chronometrist-remove-prefix
(symbol-name
(eieio-object-class-name output-backend))))
(confirm (yes-or-no-p
(format "Convert %s (%s) to %s (%s)? "
input-file
input-backend-name
output-file
output-backend-name)))
(confirm-overwrite
(if (and confirm
(file-exists-p output-file)
(not (chronometrist-file-empty-p output-file)))
(yes-or-no-p
(format "Overwrite existing non-empty file %s ?"
output-file))
t)))
(let* ((input-backend-keyword (or input-backend-keyword
(chronometrist-read-backend-name "Backend to convert: " chronometrist-backends-alist nil t)))
(input-backend (chronometrist-get-backend input-backend-keyword))
(suggested-input-file (chronometrist-backend-file input-backend))
(input-file (or input-file
(read-file-name "File to convert: "
nil suggested-input-file t suggested-input-file)))
(output-backend-keyword (or output-backend-keyword
(chronometrist-read-backend-name "Backend to write: "
chronometrist-backends-alist
(lambda (keyword)
(not (equal (cl-second
(alist-get keyword chronometrist-backends-alist))
input-backend)))
t)))
(output-backend (chronometrist-get-backend output-backend-keyword))
(suggested-output-file (chronometrist-backend-file output-backend))
(output-file (or output-file
(read-file-name "File to write: " nil nil nil suggested-output-file)))
(input-backend-name (chronometrist-remove-prefix (symbol-name (eieio-object-class-name input-backend))))
(output-backend-name (chronometrist-remove-prefix (symbol-name (eieio-object-class-name output-backend))))
(confirm (yes-or-no-p (format "Convert %s (%s) to %s (%s)? "
input-file input-backend-name
output-file output-backend-name)))
(confirm-overwrite (if (and confirm
(file-exists-p output-file)
(not (chronometrist-file-empty-p output-file)))
(yes-or-no-p
(format "Overwrite existing non-empty file %s ?" output-file))
t)))
(if (and confirm confirm-overwrite)
(chronometrist-to-file (chronometrist-backend-hash-table input-backend)
output-backend
output-file)
(start-process
"chronometrist-migrate" (generate-new-buffer-name "chronometrist-migrate")
"emacs" "--batch"
"--eval=(package-initialize)"
"--eval=(cl-loop for (pkg . desc) in package-alist
when (string-match-p \"^chronometrist\" (symbol-name pkg))
do (require pkg))"
(format "--eval=(cl-loop for file in (quote %S) do (load file))" load-files)
;; the hash table slot may be empty, so we generate the hash table from scratch
(format "--eval=(chronometrist-to-file (chronometrist-to-hash-table (chronometrist-get-backend %s)) (chronometrist-get-backend %s) %S)"
input-backend-keyword output-backend-keyword output-file))
(message "Conversion aborted."))))
;; migrate:1 ends here

View File

@ -1410,6 +1410,13 @@ Value must be a keyword corresponding to a key in
(cl-second (alist-get chronometrist-active-backend chronometrist-backends-alist)))
#+END_SRC
**** get-backend :reader:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-get-backend (keyword)
"Return the backend object associated with KEYWORD."
(cl-second (alist-get keyword chronometrist-backends-alist)))
#+END_SRC
**** switch-backend :command:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-switch-backend ()
@ -1446,8 +1453,7 @@ be replaced."
**** read-backend-name :procedure:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-read-backend-name (prompt backend-alist
&optional predicate return-keyword)
(defun chronometrist-read-backend-name (prompt backend-alist &optional predicate return-keyword)
"Prompt user for a Chronometrist backend name.
BACKEND-ALIST should be an alist similar to `chronometrist-backends-alist'.
@ -2903,50 +2909,63 @@ We apply the same hack as in the [[hack-note-plist-group-insert][insert]] method
*** migrate :command:
#+BEGIN_SRC emacs-lisp
(defun chronometrist-migrate ()
"Convert from one Chronometrist backend to another."
(defun chronometrist-migrate (&optional input-backend-keyword input-file
output-backend-keyword output-file
&rest load-files)
"Convert from one Chronometrist backend to another.
INPUT-BACKEND-KEYWORD and OUTPUT-BACKEND-KEYWORD must be keywords
corresponding to entries in `chronometrist-backends-alist'.
INPUT-FILE and OUTPUT-FILE must be file names. INPUT-FILE must
already exist.
LOAD-FILES must be paths to Elisp files. These files are `load'ed
before conversion. They may be used to point to Chronometrist
source files to be used."
(interactive)
(let* ((input-backend
(chronometrist-read-backend-name "Backend to convert: "
chronometrist-backends-alist))
(input-file-suggestion (chronometrist-backend-file input-backend))
(input-file (read-file-name "File to convert: " nil
input-file-suggestion t
input-file-suggestion))
(output-backend (chronometrist-read-backend-name
"Backend to write: "
chronometrist-backends-alist
(lambda (keyword)
(not (equal (cl-second
(alist-get keyword chronometrist-backends-alist))
input-backend)))))
(output-file-suggestion (chronometrist-backend-file output-backend))
(output-file (read-file-name "File to write: " nil nil nil
output-file-suggestion))
(input-backend-name (chronometrist-remove-prefix
(symbol-name
(eieio-object-class-name input-backend))))
(output-backend-name (chronometrist-remove-prefix
(symbol-name
(eieio-object-class-name output-backend))))
(confirm (yes-or-no-p
(format "Convert %s (%s) to %s (%s)? "
input-file
input-backend-name
output-file
output-backend-name)))
(confirm-overwrite
(if (and confirm
(file-exists-p output-file)
(not (chronometrist-file-empty-p output-file)))
(yes-or-no-p
(format "Overwrite existing non-empty file %s ?"
output-file))
t)))
(let* ((input-backend-keyword (or input-backend-keyword
(chronometrist-read-backend-name "Backend to convert: " chronometrist-backends-alist nil t)))
(input-backend (chronometrist-get-backend input-backend-keyword))
(suggested-input-file (chronometrist-backend-file input-backend))
(input-file (or input-file
(read-file-name "File to convert: "
nil suggested-input-file t suggested-input-file)))
(output-backend-keyword (or output-backend-keyword
(chronometrist-read-backend-name "Backend to write: "
chronometrist-backends-alist
(lambda (keyword)
(not (equal (cl-second
(alist-get keyword chronometrist-backends-alist))
input-backend)))
t)))
(output-backend (chronometrist-get-backend output-backend-keyword))
(suggested-output-file (chronometrist-backend-file output-backend))
(output-file (or output-file
(read-file-name "File to write: " nil nil nil suggested-output-file)))
(input-backend-name (chronometrist-remove-prefix (symbol-name (eieio-object-class-name input-backend))))
(output-backend-name (chronometrist-remove-prefix (symbol-name (eieio-object-class-name output-backend))))
(confirm (yes-or-no-p (format "Convert %s (%s) to %s (%s)? "
input-file input-backend-name
output-file output-backend-name)))
(confirm-overwrite (if (and confirm
(file-exists-p output-file)
(not (chronometrist-file-empty-p output-file)))
(yes-or-no-p
(format "Overwrite existing non-empty file %s ?" output-file))
t)))
(if (and confirm confirm-overwrite)
(chronometrist-to-file (chronometrist-backend-hash-table input-backend)
output-backend
output-file)
(start-process
"chronometrist-migrate" (generate-new-buffer-name "chronometrist-migrate")
"emacs" "--batch"
"--eval=(package-initialize)"
"--eval=(cl-loop for (pkg . desc) in package-alist
when (string-match-p \"^chronometrist\" (symbol-name pkg))
do (require pkg))"
(format "--eval=(cl-loop for file in (quote %S) do (load file))" load-files)
;; the hash table slot may be empty, so we generate the hash table from scratch
(format "--eval=(chronometrist-to-file (chronometrist-to-hash-table (chronometrist-get-backend %s)) (chronometrist-get-backend %s) %S)"
input-backend-keyword output-backend-keyword output-file))
(message "Conversion aborted."))))
#+END_SRC