branch: externals/org-gnosis commit 1d082d0b41f50ac214e4035128ea60f59c397bdb Author: Thanos Apollo <pub...@thanosapollo.org> Commit: Thanos Apollo <pub...@thanosapollo.org>
[fix] mark-todo-as-done: Add last-done-date property --- org-gnosis.el | 211 +++++++++++++++++++++++++++------------------------------- 1 file changed, 99 insertions(+), 112 deletions(-) diff --git a/org-gnosis.el b/org-gnosis.el index dd6fb8302f..ee52bd0c75 100644 --- a/org-gnosis.el +++ b/org-gnosis.el @@ -308,7 +308,7 @@ Removes all contents of FILE in database, adding them anew." (when (and journal-p file) (let ((done-todos (org-gnosis-get-checked-items (org-element-parse-buffer)))) (cl-loop for done-todo in done-todos - do (org-gnosis-mark-todo-as-done done-todo file)))))) + do (org-gnosis-mark-todo-as-done done-todo)))))) (defun org-gnosis-delete-file (&optional file) "Delete FILE. @@ -535,6 +535,104 @@ If file or id are not found, use `org-open-at-point'." (t (org-open-at-point))) (org-gnosis-mode 1))) +;; Should we use `org-get'? +(defun org-gnosis-get--todos (file) + "Get TODO items for FILE." + (let ((todos)) + (with-temp-buffer + (insert-file-contents file) + (org-mode) + (org-element-map (org-element-parse-buffer) 'headline + (lambda (headline) + (when (string= (org-element-property :todo-keyword headline) "TODO") + (let* ((title (org-element-property :raw-value headline)) + (timestamp (org-element-property :raw-value + (org-element-property :scheduled headline)))) + (push `(,title ,timestamp ,file) todos)))))) + (nreverse todos))) + +(defun org-gnosis-find-file-with-heading (title files) + "Find first org file in FILES containing heading TITLE." + (catch 'found + (dolist (file files) + (with-temp-buffer + (insert-file-contents file) + (org-mode) + (goto-char (point-min)) + (when (org-find-exact-headline-in-buffer title) + (throw 'found file)))))) + +(defun org-gnosis-get-todos (&optional files) + "Get TODO items for FILES. + +If TITLE is non-nil, return the file that has a TODO TITLE." + (let ((files (or files org-gnosis-todo-files)) + todos) + (cl-loop for file in files + do (push (org-gnosis-get--todos file) todos)) + (nreverse (apply #'append todos)))) + +(defun org-gnosis-todos () + "Output todos as checkboxes in a string for current date." + (let ((todos (org-gnosis-get-todos)) + (current-date (format-time-string "%Y-%m-%d")) + todos-string) + (cl-loop for todo in todos + do + (let ((todo-title (car todo)) + (todo-timestamp (cadr todo))) + (when (or + (null todo-timestamp) + (string-match-p (regexp-quote current-date) todo-timestamp)) + (setq todos-string + (concat todos-string + (format "%s [ ] %s\n" org-gnosis-bullet-point-char + todo-title)))))) + (or todos-string ""))) + +(defun org-gnosis-get-checked-items (element) + "Get checked items for org ELEMENT. + +ELEMENT should be the output of `org-element-parse-buffer'." + (let ((checked-items)) + (org-element-map element 'item + (lambda (item) + (when (eq (org-element-property :checkbox item) 'on) + (push (car (split-string + (substring-no-properties + (string-trim + (org-element-interpret-data + (org-element-contents item)))) + "\n")) + checked-items)))) + (nreverse checked-items))) + +(defun org-gnosis-mark-todo-as-done (todo-title) + "Mark scheduled TODO with TODO-TITLE as DONE if not already done today. +ENTRY: Journal entry linked under the heading." + (let* ((file (org-gnosis-find-file-with-heading todo-title org-gnosis-todo-files)) + (today (format-time-string "%Y-%m-%d"))) + (when file + (save-current-buffer + (with-current-buffer (find-file-noselect file) + (let ((found nil)) + (save-excursion + (org-element-map (org-element-parse-buffer) 'headline + (lambda (headline) + (when (and (not found) + (string= (org-element-property :raw-value headline) + todo-title) + (string= (org-element-property :todo-keyword headline) + "TODO") + ;; Check if not done today + (not (org-entry-get (org-element-property :begin headline) + "LAST_DONE_DATE"))) + (org-with-point-at (org-element-property :begin headline) + (org-todo 'done) + (org-entry-put nil "LAST_DONE_DATE" today)) + (setq found t)))))) + (save-buffer)))))) + (defvar-keymap org-gnosis-mode-map :doc "org-gnosis keymap" "C-c C-o" #'org-gnosis-goto-id) @@ -627,117 +725,6 @@ If called with ARG do not initialize the database." (pcase-dolist (`(,table ,schema) org-gnosis-db--table-schemata) (emacsql org-gnosis-db [:create-table $i1 $S2] table schema)) (emacsql org-gnosis-db [:pragma (= user-version org-gnosis-db-version)])))) -;; should we use `org-get' -(defun org-gnosis-get--todos (file) - "Get TODO items for FILE." - (let ((todos)) - (with-temp-buffer - (insert-file-contents file) - (org-mode) - (org-element-map (org-element-parse-buffer) 'headline - (lambda (headline) - (when (string= (org-element-property :todo-keyword headline) "TODO") - (let* ((title (org-element-property :raw-value headline)) - (timestamp (org-element-property :raw-value - (org-element-property :scheduled headline)))) - (push `(,title ,timestamp ,file) todos)))))) - (nreverse todos))) - -(defun org-gnosis-find-file-with-heading (title files) - "Find first org file in FILES containing heading TITLE." - (catch 'found - (dolist (file files) - (with-temp-buffer - (insert-file-contents file) - (org-mode) - (goto-char (point-min)) - (when (org-find-exact-headline-in-buffer title) - (throw 'found file)))))) - -(defun org-gnosis-get-todos (&optional files) - "Get TODO items for FILES. - -If TITLE is non-nil, return the file that has a TODO TITLE." - (let ((files (or files org-gnosis-todo-files)) - todos) - (cl-loop for file in files - do (push (org-gnosis-get--todos file) todos)) - (nreverse (apply #'append todos)))) - -(defun org-gnosis-todos () - "Output todos as checkboxes in a string for current date." - (let ((todos (org-gnosis-get-todos)) - (current-date (format-time-string "%Y-%m-%d")) - todos-string) - (cl-loop for todo in todos - do - (let ((todo-title (car todo)) - (todo-timestamp (cadr todo))) - (when (or - (null todo-timestamp) - (string-match-p (regexp-quote current-date) todo-timestamp)) - (setq todos-string - (concat todos-string - (format "%s [ ] %s\n" org-gnosis-bullet-point-char - todo-title)))))) - (or todos-string ""))) - -(defun org-gnosis-get-checked-items (element) - "Get checked items for org ELEMENT. - -ELEMENT should be the output of `org-element-parse-buffer'." - (let ((checked-items)) - (org-element-map element 'item - (lambda (item) - (when (eq (org-element-property :checkbox item) 'on) - (push (car (split-string - (substring-no-properties - (string-trim - (org-element-interpret-data - (org-element-contents item)))) - "\n")) - checked-items)))) - (nreverse checked-items))) - -;; TODO: Break this into smaller functions -(defun org-gnosis-mark-todo-as-done (todo-title entry) - "Mark the TODO Heading with TODO-TITLE as DONE. -ENTRY: Journal entry linked under the heading." - (let* ((file (org-gnosis-find-file-with-heading todo-title org-gnosis-todo-files)) - (today (org-time-string-to-absolute (format-time-string "%Y-%m-%d")))) - (when file - (save-current-buffer - (with-current-buffer (find-file-noselect file) - (let ((found nil)) - (save-excursion - (org-element-map (org-element-parse-buffer) 'headline - (lambda (headline) - (let ((scheduled (org-element-property :scheduled headline))) - (when (and (not found) - (string= (org-element-property :raw-value headline) - todo-title) - (string= (org-element-property :todo-keyword headline) - "TODO") - (or (null scheduled) - (= (org-time-string-to-absolute - (org-element-property :raw-value scheduled)) - today))) - (org-with-point-at - (save-excursion - (goto-char (point-min)) - (let ((case-fold-search t)) - (re-search-forward (concat "^\\*+ .*" - (regexp-quote todo-title))))) - (org-todo 'done) - (org-end-of-subtree) - (insert "\n " org-gnosis-bullet-point-char " ") - (org-insert-link - nil - (format "file:%s" - (expand-file-name entry org-gnosis-journal-dir)) - "Journal File")) - (setq found t))))))) - (save-buffer)))))) (provide 'org-gnosis) ;;; org-gnosis.el ends here