From f662e5b990b18ddc52b42f79a95b5fd966a741d4 Mon Sep 17 00:00:00 2001 From: Daniel Borchmann Date: Sun, 20 Sep 2020 14:10:46 +0200 Subject: [PATCH 01/16] Make projectile a hard dependency for personal project management --- site-lisp/db-projects.el | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/site-lisp/db-projects.el b/site-lisp/db-projects.el index 53957bd..286597e 100644 --- a/site-lisp/db-projects.el +++ b/site-lisp/db-projects.el @@ -6,13 +6,11 @@ ;;; Code: -(declare-function projectile-add-known-project "projectile") -(declare-function projectile-cleanup-known-projects "projectile") - (require 'subr-x) (require 'cl-lib) (require 'dash) (require 'bookmark) +(require 'projectile) (defgroup projects nil "Simple directory-based project management" From 2a046610e91ea40881f9044fa6975937ad6c4294 Mon Sep 17 00:00:00 2001 From: Daniel Borchmann Date: Sun, 20 Sep 2020 14:11:01 +0200 Subject: [PATCH 02/16] Clean up projectile's cache explicitly when removing a project --- site-lisp/db-projects.el | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/site-lisp/db-projects.el b/site-lisp/db-projects.el index 286597e..432b23e 100644 --- a/site-lisp/db-projects.el +++ b/site-lisp/db-projects.el @@ -98,8 +98,9 @@ nil) ;; Update projectile’s cache - (when (require 'projectile nil 'no-error) - (projectile-cleanup-known-projects))) + (projectile-remove-known-project + (expand-file-name short-name + projects-main-project-directory))) (provide 'db-projects) From 644d46326027b12ae119492cdd9feb7beb0dda2a Mon Sep 17 00:00:00 2001 From: Daniel Borchmann Date: Sun, 20 Sep 2020 14:12:50 +0200 Subject: [PATCH 03/16] Update some docstrings in db-projects As suggested by flycheck. --- site-lisp/db-projects.el | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/site-lisp/db-projects.el b/site-lisp/db-projects.el index 432b23e..a791e3d 100644 --- a/site-lisp/db-projects.el +++ b/site-lisp/db-projects.el @@ -23,12 +23,12 @@ :type 'directory) (defcustom projects-archive-directory "~/Documents/projects/.archive/" - "Directory to archive projects into" + "Directory to archive projects into." :group 'projects :type 'directory) (defun projects-project-exists-p (short-name) - "Check whether a project named SHORT-NAME already exists" + "Check whether a project named SHORT-NAME already exists." (or (file-exists-p (expand-file-name (concat (file-name-as-directory short-name) ".git") @@ -38,12 +38,17 @@ projects-main-project-directory)))) (defun projects-existing-projects () - "Return list of all short-names of existing projects" + "Return list of all short-names of existing projects." (cl-remove-if-not #'projects-project-exists-p (directory-files projects-main-project-directory))) (defun projects-add-project (short-name long-name) - "Add new project." + "Add new project with SHORT-NAME and LONG-NAME. +The project directory will be located under +`projects-main-project-directory' within a directory named +SHORT-NAME. A bookmark to the project diary will be created, +using the given LONG-NAME. The project diary will be pre-filled +with some standard information like title and creation date." (interactive "sShort Name: \nsLong Name: ") (when (projects-project-exists-p short-name) (user-error "Project %s already exists, exiting" short-name)) @@ -66,7 +71,11 @@ (projectile-add-known-project project-directory)))) (defun projects-archive-project (short-name) - "Archive existing project." + "Archive existing project identified by SHORT-NAME. +This amounts to moving the project directory SHORT-NAME under +`projects-main-project-directory' to +`projects-archive-directory', deleting the bookmark to the +project diary, and updating projectile's cache." (interactive (list (completing-read "Short Name: " (projects-existing-projects) nil t))) (unless (projects-project-exists-p short-name) From 4dead43c36de1e139331e5ae734ec341fffdc11f Mon Sep 17 00:00:00 2001 From: Daniel Borchmann Date: Sun, 20 Sep 2020 14:23:52 +0200 Subject: [PATCH 04/16] Check that newly created projects have not been archived before --- site-lisp/db-projects.el | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/site-lisp/db-projects.el b/site-lisp/db-projects.el index a791e3d..86e4958 100644 --- a/site-lisp/db-projects.el +++ b/site-lisp/db-projects.el @@ -2,7 +2,7 @@ ;;; Commentary: -;; XXX: check that newly created projects aren’t name the same as archived projects +;; XXX: update org-agenda-text-search-extra-files when creating and removing projects ;;; Code: @@ -52,6 +52,9 @@ with some standard information like title and creation date." (interactive "sShort Name: \nsLong Name: ") (when (projects-project-exists-p short-name) (user-error "Project %s already exists, exiting" short-name)) + (when (file-exists-p (expand-file-name short-name + projects-archive-directory)) + (user-error "Project %s already exists as archived project, exiting" short-name)) (let* ((project-directory (expand-file-name short-name projects-main-project-directory)) (default-directory project-directory)) From 96d4f000ca5ae195170f39b9be05a2069d2c6a18 Mon Sep 17 00:00:00 2001 From: Daniel Borchmann Date: Sun, 20 Sep 2020 14:24:38 +0200 Subject: [PATCH 05/16] Add some autoload statements --- site-lisp/db-projects.el | 2 ++ 1 file changed, 2 insertions(+) diff --git a/site-lisp/db-projects.el b/site-lisp/db-projects.el index 86e4958..0ae11fd 100644 --- a/site-lisp/db-projects.el +++ b/site-lisp/db-projects.el @@ -42,6 +42,7 @@ (cl-remove-if-not #'projects-project-exists-p (directory-files projects-main-project-directory))) +;;;###autoload (defun projects-add-project (short-name long-name) "Add new project with SHORT-NAME and LONG-NAME. The project directory will be located under @@ -73,6 +74,7 @@ with some standard information like title and creation date." (when (require 'projectile nil 'no-error) (projectile-add-known-project project-directory)))) +;;;###autoload (defun projects-archive-project (short-name) "Archive existing project identified by SHORT-NAME. This amounts to moving the project directory SHORT-NAME under From bdc8a53fe3fecaa530b6fd03f743ad1a5dab4a4e Mon Sep 17 00:00:00 2001 From: Daniel Borchmann Date: Sun, 20 Sep 2020 14:24:43 +0200 Subject: [PATCH 06/16] Minor reformatting in package signature for db-projects --- init.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/init.el b/init.el index 556c327..bcdcc8e 100644 --- a/init.el +++ b/init.el @@ -2811,7 +2811,8 @@ With given ARG, display files in `db/important-document-path’." (add-hook 'cperl-mode-hook 'cperl-lazy-install))) (use-package db-projects - :commands (projects-add-project projects-archive-project)) + :commands (projects-add-project + projects-archive-project)) (use-package define-word :ensure t From d9e74f6e54690019cdfd0f50d2e6d7384533f700 Mon Sep 17 00:00:00 2001 From: Daniel Borchmann Date: Sun, 20 Sep 2020 14:48:18 +0200 Subject: [PATCH 07/16] Allow to list Org Mode files in projects not being searched by default --- site-lisp/db-projects.el | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/site-lisp/db-projects.el b/site-lisp/db-projects.el index 0ae11fd..a0a0bc1 100644 --- a/site-lisp/db-projects.el +++ b/site-lisp/db-projects.el @@ -2,7 +2,6 @@ ;;; Commentary: -;; XXX: update org-agenda-text-search-extra-files when creating and removing projects ;;; Code: @@ -116,6 +115,28 @@ project diary, and updating projectile's cache." (expand-file-name short-name projects-main-project-directory))) +(defun projects--org-files () + "Return all Org Mode files in all known projects, recursively." + (mapcan #'(lambda (dir) + (directory-files-recursively (expand-file-name dir projects-main-project-directory) + ".*\\.org" nil)) + (projects-existing-projects))) + +(defvar org-agenda-text-search-extra-files) ; to keep the byte-compiler happy + +(defun projects-find-unsearched-org-files () + "Find Org Mode files in known projects that are not searched by default. +This is done by checking all org Mode files in every project +whether it is included in `org-agenda-text-search-extra-files'." + (require 'org) + (let ((extra-files (make-hash-table :test #'equal))) + (mapc #'(lambda (entry) + (when (stringp entry) + (puthash (file-truename entry) t extra-files))) + org-agenda-text-search-extra-files) + (cl-remove-if #'(lambda (org-file) + (gethash (file-truename org-file) extra-files nil)) + (projects--org-files)))) (provide 'db-projects) From 297bb6ac6e79a4134129c87d90f6f911b02dc9b4 Mon Sep 17 00:00:00 2001 From: Daniel Borchmann Date: Sun, 20 Sep 2020 14:48:59 +0200 Subject: [PATCH 08/16] Add short description of what db-projects is all about --- site-lisp/db-projects.el | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/site-lisp/db-projects.el b/site-lisp/db-projects.el index a0a0bc1..957029e 100644 --- a/site-lisp/db-projects.el +++ b/site-lisp/db-projects.el @@ -2,6 +2,18 @@ ;;; Commentary: +;; A project is simply a directory under `projects-main-project-directory' +;; containing either .git or .projectile. This little collection of functions +;; helps to manage these project directories and also integration them +;; consistently with the projectile package. + +;; To start, first customize `projects-main-project-directory' and +;; `projects-archive-directory' as needed. Then use `projects-add-project' to +;; add new projects and `projects-archive-project' to archive them (i.e., move +;; them to `projects-archive-directory'). This package does not offer to remove +;; projects; this has to be done manually. + +;; XXX: check known projects for missing bookmarks ;;; Code: From 9c0d630703155a583dd22161a727352435e2370d Mon Sep 17 00:00:00 2001 From: Daniel Borchmann Date: Sun, 20 Sep 2020 15:03:14 +0200 Subject: [PATCH 09/16] Remove all bookmarks when archiving a project Not only the one to the project diary. --- site-lisp/db-projects.el | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/site-lisp/db-projects.el b/site-lisp/db-projects.el index 957029e..75127f7 100644 --- a/site-lisp/db-projects.el +++ b/site-lisp/db-projects.el @@ -85,35 +85,32 @@ with some standard information like title and creation date." (when (require 'projectile nil 'no-error) (projectile-add-known-project project-directory)))) +(defun projects--find-bookmarks-for-path (path) + "Find all bookmark names that point into PATH." + (unless (file-name-absolute-p path) + (user-error "Given path %s is not absolute" path)) + (let ((path (file-truename path))) + (cl-remove-if-not #'(lambda (bmk) + (let ((filename (bookmark-get-filename bmk))) + (and (not (file-remote-p filename)) + (string-prefix-p path (file-truename filename))))) + (bookmark-all-names)))) + ;;;###autoload (defun projects-archive-project (short-name) "Archive existing project identified by SHORT-NAME. This amounts to moving the project directory SHORT-NAME under `projects-main-project-directory' to -`projects-archive-directory', deleting the bookmark to the -project diary, and updating projectile's cache." +`projects-archive-directory', deleting all bookmarks into the +project, and updating projectile's cache." (interactive (list (completing-read "Short Name: " (projects-existing-projects) nil t))) (unless (projects-project-exists-p short-name) (user-error "Project %s does not exist, exiting" short-name)) - ;; Remove bookmark first - (let* ((notebook-path (expand-file-name (concat - (file-name-as-directory short-name) - "projekttagebuch.org") - projects-main-project-directory)) - (bookmark-entry (cl-find-if (lambda (entry) - (let ((filename (->> entry - cdr - (cl-assoc 'filename) - cdr))) - (and (not (file-remote-p filename)) - (file-equal-p notebook-path - filename)))) - bookmark-alist))) - (if (null bookmark-entry) - (warn "No bookmark for project notebook of %s found." short-name) - (bookmark-delete (car bookmark-entry)))) + ;; Remove bookmarks first + (mapc #'bookmark-delete (projects--find-bookmarks-for-path + (expand-file-name short-name projects-main-project-directory))) ;; Move project directory into archive (unless (file-exists-p projects-archive-directory) From 69c6a372eb37be2ec4c94fb91c9e9ce08046a7da Mon Sep 17 00:00:00 2001 From: Daniel Borchmann Date: Sun, 20 Sep 2020 15:30:10 +0200 Subject: [PATCH 10/16] Add function to check for missing project bookmarks --- site-lisp/db-projects.el | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/site-lisp/db-projects.el b/site-lisp/db-projects.el index 75127f7..da2a1d5 100644 --- a/site-lisp/db-projects.el +++ b/site-lisp/db-projects.el @@ -13,8 +13,6 @@ ;; them to `projects-archive-directory'). This package does not offer to remove ;; projects; this has to be done manually. -;; XXX: check known projects for missing bookmarks - ;;; Code: (require 'subr-x) @@ -147,6 +145,31 @@ whether it is included in `org-agenda-text-search-extra-files'." (gethash (file-truename org-file) extra-files nil)) (projects--org-files)))) +(defun projects-check-project-diary-bookmarks () + "Check that all known projects have a bookmark to their diary. +Return list of short names of projects whose project diary does +not have a corresponding bookmark." + (let ((projects (make-hash-table :test #'equal))) + + ;; Make hash table of all diary paths to all known projects; as values we + ;; keep the short names, because these are the ones we want to return in the + ;; end + (dolist (project (projects-existing-projects)) + (let ((project-diary-path (expand-file-name (concat (file-name-as-directory project) + "projekttagebuch.org") + projects-main-project-directory))) + (when (file-exists-p project-diary-path) + (puthash project-diary-path project projects)))) + + ;; Delete all those diary links that have a bookmark + (dolist (bmkn (bookmark-all-names)) + (unless (file-remote-p (bookmark-get-filename bmkn)) + (remhash (file-truename (bookmark-get-filename bmkn)) projects))) + + ;; Return remaining short names; those are the ones that do not have a + ;; bookmark yet + (hash-table-values projects))) + (provide 'db-projects) ;;; db-projects.el ends here From 9822736d8ec5ef3d19eb995c240b810951f8a578 Mon Sep 17 00:00:00 2001 From: Daniel Borchmann Date: Sun, 20 Sep 2020 16:09:40 +0200 Subject: [PATCH 11/16] Move comment to right position --- site-lisp/db-projects.el | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site-lisp/db-projects.el b/site-lisp/db-projects.el index da2a1d5..c8bc339 100644 --- a/site-lisp/db-projects.el +++ b/site-lisp/db-projects.el @@ -149,11 +149,11 @@ whether it is included in `org-agenda-text-search-extra-files'." "Check that all known projects have a bookmark to their diary. Return list of short names of projects whose project diary does not have a corresponding bookmark." + ;; Make hash table of all diary paths to all known projects; as values we + ;; keep the short names, because these are the ones we want to return in the + ;; end (let ((projects (make-hash-table :test #'equal))) - ;; Make hash table of all diary paths to all known projects; as values we - ;; keep the short names, because these are the ones we want to return in the - ;; end (dolist (project (projects-existing-projects)) (let ((project-diary-path (expand-file-name (concat (file-name-as-directory project) "projekttagebuch.org") From 7a0fbff4c5b2369fc1003d34837e3bd053abcb9f Mon Sep 17 00:00:00 2001 From: Daniel Borchmann Date: Sun, 20 Sep 2020 16:20:23 +0200 Subject: [PATCH 12/16] Do some cleanup in db-projects --- site-lisp/db-projects.el | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/site-lisp/db-projects.el b/site-lisp/db-projects.el index c8bc339..91755f8 100644 --- a/site-lisp/db-projects.el +++ b/site-lisp/db-projects.el @@ -80,8 +80,7 @@ with some standard information like title and creation date." (if-let ((git-executable (executable-find "git"))) (call-process git-executable nil nil nil "init") (write-region "" nil (expand-file-name ".projectile"))) - (when (require 'projectile nil 'no-error) - (projectile-add-known-project project-directory)))) + (projectile-add-known-project project-directory))) (defun projects--find-bookmarks-for-path (path) "Find all bookmark names that point into PATH." @@ -106,24 +105,26 @@ project, and updating projectile's cache." (unless (projects-project-exists-p short-name) (user-error "Project %s does not exist, exiting" short-name)) - ;; Remove bookmarks first - (mapc #'bookmark-delete (projects--find-bookmarks-for-path - (expand-file-name short-name projects-main-project-directory))) + (let ((project-path (expand-file-name short-name projects-main-project-directory)) + (archive-path (expand-file-name short-name projects-archive-directory))) - ;; Move project directory into archive - (unless (file-exists-p projects-archive-directory) - (make-directory projects-archive-directory)) - (rename-file (expand-file-name short-name projects-main-project-directory) - (expand-file-name short-name projects-archive-directory) - nil) + (when (file-exists-p archive-path) + (user-error "Archived project named %s already exists, aborting" short-name)) - ;; Update projectile’s cache - (projectile-remove-known-project - (expand-file-name short-name - projects-main-project-directory))) + ;; Remove bookmarks first + (mapc #'bookmark-delete (projects--find-bookmarks-for-path project-path)) + + ;; Move project directory into archive + (unless (file-exists-p projects-archive-directory) + (make-directory projects-archive-directory)) + (rename-file project-path archive-path nil) + + ;; Update projectile’s cache + (projectile-cleanup-known-projects))) (defun projects--org-files () - "Return all Org Mode files in all known projects, recursively." + "Return all Org Mode files in all known projects, recursively. +Paths returned are absolute, but may not be canonical." (mapcan #'(lambda (dir) (directory-files-recursively (expand-file-name dir projects-main-project-directory) ".*\\.org" nil)) @@ -159,7 +160,7 @@ not have a corresponding bookmark." "projekttagebuch.org") projects-main-project-directory))) (when (file-exists-p project-diary-path) - (puthash project-diary-path project projects)))) + (puthash (file-truename project-diary-path) project projects)))) ;; Delete all those diary links that have a bookmark (dolist (bmkn (bookmark-all-names)) From 66da74d63e00d0ad966cdd17875258ce796065bb Mon Sep 17 00:00:00 2001 From: Daniel Borchmann Date: Sun, 20 Sep 2020 16:22:29 +0200 Subject: [PATCH 13/16] Fix typo --- site-lisp/db-projects.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site-lisp/db-projects.el b/site-lisp/db-projects.el index 91755f8..3808e39 100644 --- a/site-lisp/db-projects.el +++ b/site-lisp/db-projects.el @@ -148,7 +148,7 @@ whether it is included in `org-agenda-text-search-extra-files'." (defun projects-check-project-diary-bookmarks () "Check that all known projects have a bookmark to their diary. -Return list of short names of projects whose project diary does +Return list of short names of projects whose project diaries do not have a corresponding bookmark." ;; Make hash table of all diary paths to all known projects; as values we ;; keep the short names, because these are the ones we want to return in the From ce9a2f51ebe3721ab97c3f7f680333db0cd6b5ae Mon Sep 17 00:00:00 2001 From: Daniel Borchmann Date: Sun, 20 Sep 2020 16:33:24 +0200 Subject: [PATCH 14/16] Add linting function for project management --- site-lisp/db-projects.el | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/site-lisp/db-projects.el b/site-lisp/db-projects.el index 3808e39..f0bd3ef 100644 --- a/site-lisp/db-projects.el +++ b/site-lisp/db-projects.el @@ -171,6 +171,28 @@ not have a corresponding bookmark." ;; bookmark yet (hash-table-values projects))) +;;;###autoload +(defun projects-lint-projects () + "Check all known projects for proper configuration. +This includes checking whether all bookmarks are in place and +whether `org-agenda-text-search-extra-files' is set up to search +through all included Org Mode files." + (interactive) + (with-current-buffer (get-buffer-create " *projects lint results*") + + (erase-buffer) + + (when-let ((unsearched-org-files (projects-find-unsearched-org-files))) + (insert "The following Org Mode files are not included in `org-agenda-text-search-extra-files'; you may want to add them.") + (dolist (file unsearched-org-files) + (insert "\n " file)) + (insert "\n\n")) + + (when-let ((missing-bookmarks (projects-check-project-diary-bookmarks))) + (insert "The following projects do not have a project diary bookmark: " (apply #'concat missing-bookmarks))) + + (display-buffer (current-buffer)))) + (provide 'db-projects) ;;; db-projects.el ends here From 2c4cada1daec9eb2e8b0b2fe6961d1fda3a4989d Mon Sep 17 00:00:00 2001 From: Daniel Borchmann Date: Sun, 20 Sep 2020 16:35:33 +0200 Subject: [PATCH 15/16] Also look into org-agenda-files when checking for missing bookmarks --- site-lisp/db-projects.el | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/site-lisp/db-projects.el b/site-lisp/db-projects.el index f0bd3ef..b968d94 100644 --- a/site-lisp/db-projects.el +++ b/site-lisp/db-projects.el @@ -130,18 +130,21 @@ Paths returned are absolute, but may not be canonical." ".*\\.org" nil)) (projects-existing-projects))) -(defvar org-agenda-text-search-extra-files) ; to keep the byte-compiler happy +;; Let's keep the byte compiler happy +(defvar org-agenda-text-search-extra-files) +(defvar org-agenda-files) (defun projects-find-unsearched-org-files () "Find Org Mode files in known projects that are not searched by default. This is done by checking all org Mode files in every project -whether it is included in `org-agenda-text-search-extra-files'." +whether it is included in `org-agenda-text-search-extra-files' or +in `org-agenda-files'." (require 'org) (let ((extra-files (make-hash-table :test #'equal))) (mapc #'(lambda (entry) (when (stringp entry) (puthash (file-truename entry) t extra-files))) - org-agenda-text-search-extra-files) + (append org-agenda-files org-agenda-text-search-extra-files)) (cl-remove-if #'(lambda (org-file) (gethash (file-truename org-file) extra-files nil)) (projects--org-files)))) From 57044376be6f4482090c1c9a5506a09aaf01d4a0 Mon Sep 17 00:00:00 2001 From: Daniel Borchmann Date: Sun, 20 Sep 2020 16:39:02 +0200 Subject: [PATCH 16/16] Do not treat dotfiles as projects --- site-lisp/db-projects.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/site-lisp/db-projects.el b/site-lisp/db-projects.el index b968d94..d46690c 100644 --- a/site-lisp/db-projects.el +++ b/site-lisp/db-projects.el @@ -49,7 +49,8 @@ (defun projects-existing-projects () "Return list of all short-names of existing projects." (cl-remove-if-not #'projects-project-exists-p - (directory-files projects-main-project-directory))) + (directory-files projects-main-project-directory + nil "^[^.]"))) ;;;###autoload (defun projects-add-project (short-name long-name)