Merge branch 'dev' into cl-port
This commit is contained in:
commit
04ede947b4
22
TODO.org
22
TODO.org
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Reference in New Issue