simplify skip functions
This commit is contained in:
parent
0ef5fe8dd1
commit
59010e234f
314
conf.el
314
conf.el
|
@ -319,9 +319,15 @@
|
|||
(not (member (nth 2 (org-heading-components)) org-done-keywords)))
|
||||
(setq org-refile-target-verify-function 'nd/verify-refile-target)
|
||||
|
||||
(setq org-agenda-files (quote ("~/Org"
|
||||
"~/Org/large_projects"
|
||||
"~/Org/reference")))
|
||||
(use-package org-bullets
|
||||
:ensure t
|
||||
:config
|
||||
(add-hook 'org-mode-hook (lambda () (org-bullets-mode))))
|
||||
|
||||
(setq org-agenda-files '("~/Org"
|
||||
"~/Org/large_projects"
|
||||
"~/Org/reference"))
|
||||
;; (setq org-agenda-files '("~/Org/reference/agendatest.org"))
|
||||
(setq org-agenda-dim-blocked-tasks nil)
|
||||
(setq org-agenda-compact-blocks t)
|
||||
|
||||
|
@ -345,7 +351,7 @@ this is used to both test if a heading is a todoitem and retrieving the keyword"
|
|||
(and (not (nd/heading-has-parent)) (nd/is-task-p)))
|
||||
|
||||
(defun nd/is-project-task-p ()
|
||||
"return todo keyword if heading is task with no parents"
|
||||
"return todo keyword if heading is task with parents"
|
||||
(and (nd/heading-has-parent) (nd/is-task-p)))
|
||||
|
||||
(defun nd/is-scheduled-heading-p ()
|
||||
|
@ -410,9 +416,9 @@ this is used to both test if a heading is a todoitem and retrieving the keyword"
|
|||
"returns parent keyword if heading is in the immediate subtree of a todoitem"
|
||||
(save-excursion (and (org-up-heading-safe) (nd/is-todoitem-p))))
|
||||
|
||||
(defun nd/is-discontinuous-project-task-p ()
|
||||
"detects todoitems that are children of non-todoitems
|
||||
that in turn are children of todoitems (discontinous project)"
|
||||
(defun nd/has-discontinuous-parent ()
|
||||
"returns t if heading has a parent which is not a
|
||||
todoitem which in turn has a parent which is a todoitem"
|
||||
(let ((has-todoitem-parent)
|
||||
(has-non-todoitem-parent))
|
||||
(save-excursion
|
||||
|
@ -509,149 +515,180 @@ down the list override higher items")
|
|||
(org-forward-heading-same-level 1 t)))
|
||||
project-state))
|
||||
|
||||
(defmacro nd/is-project-keyword-status-p (top-keyword operator statuscode)
|
||||
(defmacro nd/is-project-keyword-status-p (test-keyword operator statuscode)
|
||||
"tests if a project has toplevel heading of top-keyword and
|
||||
child status equal to status code and returns keyword if
|
||||
both are true"
|
||||
`(if (and (equal ,keyword ,top-keyword)
|
||||
(nd/compare-statuscodes ,operator (nd/descend-into-project) statuscode))
|
||||
,keyword))
|
||||
`(and
|
||||
(equal ,keyword ,test-keyword)
|
||||
(nd/compare-statuscodes ,operator (nd/descend-into-project) ,statuscode)))
|
||||
|
||||
(defun nd/is-project-status-p (statuscode)
|
||||
(let ((keyword (nd/is-project-p)))
|
||||
(if keyword
|
||||
(case statuscode
|
||||
;; projects closed more than 30 days ago
|
||||
;; note CANCELLED overrides all subtasks/projects
|
||||
(:archivable
|
||||
(if (nd/is-archivable-heading-p)
|
||||
(cond ((equal keyword "CANCELLED") keyword)
|
||||
(t (nd/is-project-keyword-status-p "DONE" = :archivable)))))
|
||||
"Returns t if project matches statuscode given.
|
||||
Note that this assumes the headline being tested is a valid project"
|
||||
(case statuscode
|
||||
;; projects closed more than 30 days ago
|
||||
;; note CANCELLED overrides all subtasks/projects
|
||||
(:archivable
|
||||
(if (nd/is-archivable-heading-p)
|
||||
(or (equal keyword "CANCELLED")
|
||||
(nd/is-project-keyword-status-p "DONE" = :archivable))))
|
||||
|
||||
;; projects closed less than 30 days ago
|
||||
;; note CANCELLED overrides all subtasks/projects
|
||||
(:complete
|
||||
(if (not (nd/is-archivable-heading-p))
|
||||
(cond ((equal keyword "CANCELLED") keyword)
|
||||
(t (nd/is-project-keyword-status-p "DONE" = :complete)))))
|
||||
;; projects closed less than 30 days ago
|
||||
;; note CANCELLED overrides all subtasks/projects
|
||||
(:complete
|
||||
(if (not (nd/is-archivable-heading-p))
|
||||
(or (equal keyword "CANCELLED")
|
||||
(nd/is-project-keyword-status-p "DONE" = :complete))))
|
||||
|
||||
;; projects with no waiting, held, or active components
|
||||
(:stuck
|
||||
(nd/is-project-keyword-status-p "TODO" = :stuck))
|
||||
;; projects with no waiting, held, or active components
|
||||
(:stuck
|
||||
(nd/is-project-keyword-status-p "TODO" = :stuck))
|
||||
|
||||
;; held projects
|
||||
;; note toplevel HOLD overrides all subtasks/projects
|
||||
(:held
|
||||
(cond ((equal keyword "HOLD") keyword)
|
||||
(t (nd/is-project-keyword-status-p "TODO" = :stuck))))
|
||||
;; held projects
|
||||
;; note toplevel HOLD overrides all subtasks/projects
|
||||
(:held
|
||||
(or (equal keyword "HOLD")
|
||||
(nd/is-project-keyword-status-p "TODO" = :held)))
|
||||
|
||||
;; projects with at least one waiting component
|
||||
(:waiting
|
||||
(nd/is-project-keyword-status-p "TODO" = :waiting))
|
||||
;; projects with at least one waiting component
|
||||
(:waiting
|
||||
(nd/is-project-keyword-status-p "TODO" = :waiting))
|
||||
|
||||
;; projects with at least one active component
|
||||
(:active
|
||||
(nd/is-project-keyword-status-p "TODO" = :active))
|
||||
;; projects with at least one active component
|
||||
(:active
|
||||
(nd/is-project-keyword-status-p "TODO" = :active))
|
||||
|
||||
;; projects marked DONE but still have undone subtasks
|
||||
(:done-incomplete
|
||||
(nd/is-project-keyword-status-p "DONE" > :complete))
|
||||
;; projects marked DONE but still have undone subtasks
|
||||
(:done-incomplete
|
||||
(nd/is-project-keyword-status-p "DONE" > :complete))
|
||||
|
||||
;; projects not marked DONE but all subtasks are done
|
||||
(:undone-complete
|
||||
(nd/is-project-keyword-status-p "TODO" < :stuck))
|
||||
;; projects marked TODO but all subtasks are done
|
||||
(:undone-complete
|
||||
(nd/is-project-keyword-status-p "TODO" < :stuck))
|
||||
|
||||
;; projects with invalid todo keywords
|
||||
(:invalid-todostate
|
||||
(if (member keyword nd/project-invalid-todostates) keyword))
|
||||
;; projects with invalid todo keywords
|
||||
(:invalid-todostate
|
||||
(member keyword nd/project-invalid-todostates))
|
||||
|
||||
;; projects with scheduled heading (only subtasks should be scheduled)
|
||||
(:scheduled-project
|
||||
(if (nd/is-scheduled-heading-p) keyword))))))
|
||||
;; projects with scheduled heading (only subtasks should be scheduled)
|
||||
(:scheduled-project
|
||||
(nd/is-scheduled-heading-p))
|
||||
|
||||
;; TODO we could clean this up with macros
|
||||
(defun nd/skip-non-atomic-tasks ()
|
||||
;; error if not known
|
||||
(t (if (not (member statuscode nd/project-statuscodes))
|
||||
(error "unknown statuscode")))))
|
||||
|
||||
;; helper functions
|
||||
(defun nd/skip-item ()
|
||||
(save-excursion (or (outline-next-heading) (point-max))))
|
||||
|
||||
(defun nd/skip-subtree ()
|
||||
(save-excursion (or (org-end-of-subtree t) (point-max))))
|
||||
|
||||
(defconst nd/project-skip-todostates
|
||||
'("HOLD" "CANCELLED")
|
||||
"These keywords override all contents within their subtrees.
|
||||
Currently used to tell skip functions when they can hop over
|
||||
entire subtrees to save time and ignore tasks")
|
||||
|
||||
(defmacro nd/skip-heading-with (heading-fun test-fun)
|
||||
"Skips headings accoring to certain characteristics. heading-fun
|
||||
is a function that tests the heading and returns the todoitem keyword
|
||||
on success. Test-fun is a function that further tests the identity of
|
||||
the heading and may or may not use the keyword output supplied by
|
||||
the heading-fun. This function will not skip if heading-fun and
|
||||
test-fun return true"
|
||||
`(save-restriction
|
||||
(widen)
|
||||
(let ((keyword (,heading-fun)))
|
||||
(message keyword)
|
||||
(if (not (and keyword ,test-fun))
|
||||
(nd/skip-item)))))
|
||||
|
||||
;; atomic tasks
|
||||
;; by definition these have no parents, so
|
||||
;; we don't need to worry about skipping over projects
|
||||
;; any todo state is valid and we only sort by done/cancelled
|
||||
(defun nd/skip-non-unclosed-atomic-tasks ()
|
||||
(nd/skip-heading-with
|
||||
nd/is-atomic-task-p
|
||||
(not (member keyword org-done-keywords))))
|
||||
|
||||
(defun nd/skip-non-closed-atomic-tasks ()
|
||||
(nd/skip-heading-with
|
||||
nd/is-atomic-task-p
|
||||
(and (member keyword org-done-keywords)
|
||||
(not (nd/is-archivable-heading)))))
|
||||
|
||||
(defun nd/skip-non-archivable-atomic-tasks ()
|
||||
(nd/skip-heading-with
|
||||
nd/is-atomic-task-p
|
||||
(and (member keyword org-done-keywords)
|
||||
(nd/is-archivable-heading))))
|
||||
|
||||
;; project tasks
|
||||
;; since these are part of projects I need to assess
|
||||
;; if the parent project is skippable, in which case
|
||||
;; I jump to the next subtree
|
||||
;; Note that I only care about the keyword in these
|
||||
;; cases because I don't archive these, I archive
|
||||
;; their parent projects. The keywords I care about
|
||||
;; are NEXT, WAITING, and HOLD because these are
|
||||
;; definitive project tasks that require/inhibit
|
||||
;; futher action
|
||||
(defun nd/skip-non-keyword-project-tasks (skip-keyword)
|
||||
(save-restriction
|
||||
(widen)
|
||||
(if (not (nd/is-atomic-task-p))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
(let ((keyword (nd/is-todoitem-p)))
|
||||
(if keyword
|
||||
(if (nd/heading-has-children)
|
||||
(if (member keyword nd/project-skip-todostates)
|
||||
(nd/skip-subtree)
|
||||
(nd/skip-item))
|
||||
(if (not (and (nd/heading-has-parent)
|
||||
(equal keyword skip-keyword)))
|
||||
(nd/skip-item)))
|
||||
(nd/skip-item)))))
|
||||
|
||||
(defun nd/skip-non-next-project-tasks ()
|
||||
(save-restriction
|
||||
(widen)
|
||||
;; TODO skip over invalid and held
|
||||
(if (not (equal (nd/is-project-task-p) "NEXT"))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
|
||||
(defun nd/skip-non-waiting-project-tasks ()
|
||||
(save-restriction
|
||||
(widen)
|
||||
;; TODO skip over invalid and held
|
||||
(if (not (equal (nd/is-project-task-p) "WAITING"))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
|
||||
(defun nd/skip-non-held-project-tasks ()
|
||||
(save-restriction
|
||||
(widen)
|
||||
;; TODO skip over invalid and held
|
||||
(if (not (equal (nd/is-project-task-p) "HOLD"))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
|
||||
;; slip functions
|
||||
;; task-level errors
|
||||
(defun nd/skip-non-discontinuous-project-tasks ()
|
||||
(save-restriction
|
||||
(widen)
|
||||
(if (not (nd/is-discontinuous-project-task-p))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
(nd/skip-heading-with
|
||||
nd/is-todoitem-p
|
||||
(nd/has-discontinuous-parent)))
|
||||
|
||||
(defun nd/skip-non-done-open-todoitems ()
|
||||
(save-restriction
|
||||
(widen)
|
||||
(if (not (and (member (nd/is-todoitem-p) org-done-keywords) (not (nd/is-closed-heading-p))))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
(defun nd/skip-non-done-unclosed-todoitems ()
|
||||
(nd/skip-heading-with
|
||||
nd/is-todoitem-p
|
||||
(and (member keyword org-done-keywords)
|
||||
(not (nd/is-closed-heading-p)))))
|
||||
|
||||
(defun nd/skip-non-undone-closed-todoitems ()
|
||||
(save-restriction
|
||||
(widen)
|
||||
(if (not (and (not (member (nd/is-todoitem-p)) org-done-keywords) (nd/is-closed-heading-p)))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
(nd/skip-heading-with
|
||||
nd/is-todoitem-p
|
||||
(and (not (member keyword org-done-keywords))
|
||||
(nd/is-closed-heading-p))))
|
||||
|
||||
(defun nd/skip-non-series-atomic-tasks ()
|
||||
(nd/skip-heading-with
|
||||
nd/is-atomic-task-p
|
||||
(nd/is-series-heading-p)))
|
||||
|
||||
;; projects
|
||||
;; TODO skip entire subtree if we don't need to evaluate anything inside
|
||||
;; otherwise (for example) a held project will still have it's subtasks show up
|
||||
(defun nd/skip-projects-without-statuscode (statuscode)
|
||||
(save-restriction
|
||||
(widen)
|
||||
(if (not (nd/is-project-status-p statuscode))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
|
||||
;; top-level projects
|
||||
(defun nd/skip-subprojects-without-statuscode (statuscode)
|
||||
(save-restriction
|
||||
(widen)
|
||||
(if (or (nd/heading-has-parent) (not (nd/is-project-status-p statuscode)))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
|
||||
(defun nd/skip-series-projects-without-statuscode (statuscode)
|
||||
(save-restriction
|
||||
(widen)
|
||||
(if (not (and (nd/is-series-heading-p) (nd/is-project-status-p statuscode)))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
;; series projects
|
||||
;; defined as project with property Project_type=series
|
||||
;; must have:
|
||||
;; - one level of subtasks
|
||||
;; - all subtasks either TODO/scheduled, NEXT, DONE, CANCELLED
|
||||
;; - at least one TODO/scheduled or NEXT (active) ..else empty
|
||||
;; invalid if:
|
||||
;; - project header is invalid project header (typical rules apply)
|
||||
|
||||
;; archiving
|
||||
(defun nd/skip-non-archivable-atomic-tasks ()
|
||||
(save-restriction
|
||||
(widen)
|
||||
(if (not (nd/is-archivable-atomic-task-p))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
(let ((keyword (nd/is-project-p)))
|
||||
;; TODO there may be a way to skip over skippable projects
|
||||
;; and save a few cycles. Not a huge deal, but would require
|
||||
;; keeping the skippable line and then skipping over the others
|
||||
;; in one fell swoop, not easy to do efficiently
|
||||
(if keyword
|
||||
(if (not (nd/is-project-status-p statuscode))
|
||||
(if nd/agenda-limit-project-toplevel
|
||||
(nd/skip-subtree)
|
||||
(nd/skip-item)))
|
||||
(nd/skip-item)))))
|
||||
|
||||
(defvar nd/agenda-limit-project-toplevel t
|
||||
"used to filter projects by all levels or top-level only")
|
||||
|
@ -665,8 +702,8 @@ both are true"
|
|||
|
||||
(defun nd/agenda-base-task-command (keyword skip-fun)
|
||||
"shorter syntax to define task agenda commands"
|
||||
`(tags-todo
|
||||
"-NA-REFILE/!"
|
||||
`(tags
|
||||
"-NA-REFILE/"
|
||||
((org-agenda-overriding-header (concat ,keyword " Tasks"))
|
||||
(org-agenda-skip-function ,skip-fun)
|
||||
(org-agenda-todo-ignore-with-date 'all)
|
||||
|
@ -680,9 +717,7 @@ both are true"
|
|||
(and nd/agenda-limit-project-toplevel "Toplevel ")
|
||||
,keyword
|
||||
" Projects"))
|
||||
(org-agenda-skip-function (if nd/agenda-limit-project-toplevel
|
||||
'(nd/skip-subprojects-without-statuscode ,statuscode)
|
||||
'(nd/skip-projects-without-statuscode ,statuscode)))
|
||||
(org-agenda-skip-function '(nd/skip-projects-without-statuscode ,statuscode))
|
||||
(org-agenda-sorting-strategy '(category-keep)))))
|
||||
|
||||
(setq org-agenda-tags-todo-honor-ignore-options t)
|
||||
|
@ -690,10 +725,10 @@ both are true"
|
|||
`(("t"
|
||||
"Task View"
|
||||
((agenda "" nil)
|
||||
,(nd/agenda-base-task-command "Next Project" ''nd/skip-non-next-project-tasks)
|
||||
,(nd/agenda-base-task-command "Waiting Project" ''nd/skip-non-waiting-project-tasks)
|
||||
,(nd/agenda-base-task-command "Atomic" ''nd/skip-non-atomic-tasks)
|
||||
,(nd/agenda-base-task-command "Held Project" ''nd/skip-non-held-project-tasks)))
|
||||
,(nd/agenda-base-task-command "Next Project" ''(nd/skip-non-keyword-project-tasks "NEXT"))
|
||||
,(nd/agenda-base-task-command "Waiting Project" ''(nd/skip-non-keyword-project-tasks "WAITING"))
|
||||
,(nd/agenda-base-task-command "Atomic" ''nd/skip-non-unclosed-atomic-tasks)
|
||||
,(nd/agenda-base-task-command "Held Project" ''(nd/skip-non-keyword-project-tasks "HOLD"))))
|
||||
("o"
|
||||
"Project Overview"
|
||||
(,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/!" "Stuck" :stuck)
|
||||
|
@ -706,11 +741,11 @@ both are true"
|
|||
((org-agenda-overriding-header "Tasks to Refile"))
|
||||
(org-tags-match-list-sublevels nil))
|
||||
,(nd/agenda-base-task-command "Discontinous Project" ''nd/skip-non-discontinuous-project-tasks)
|
||||
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/!" "Unmarked Completed" :complete)
|
||||
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/" "Invalid" :invalid-todostate)
|
||||
;; ,(nd/agenda-base-task-command "Done But Not Closed" ''nd/skip-non-done-open-todoitems)
|
||||
;; ,(nd/agenda-base-task-command "Closed But Not Done" ''nd/skip-non-open-closed-todoitems)
|
||||
))
|
||||
,(nd/agenda-base-task-command "Undone Closed" ''nd/skip-non-undone-closed-todoitems)
|
||||
,(nd/agenda-base-task-command "Done Unclosed" ''nd/skip-non-done-unclosed-todoitems)
|
||||
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/" "Undone Completed" :undone-complete)
|
||||
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/" "Done Incompleted" :done-incomplete)
|
||||
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/" "Invalid Todostate" :invalid-todostate)))
|
||||
("s"
|
||||
"Series projects"
|
||||
(,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC+Project_Type=\"series\"/!" "Active Series" :active)
|
||||
|
@ -746,11 +781,6 @@ both are true"
|
|||
|
||||
(setq org-agenda-auto-exclude-function 'nd/org-auto-exclude-function)
|
||||
|
||||
(use-package org-bullets
|
||||
:ensure t
|
||||
:config
|
||||
(add-hook 'org-mode-hook (lambda () (org-bullets-mode))))
|
||||
|
||||
(use-package calfw-org
|
||||
:init
|
||||
:ensure t
|
||||
|
|
319
conf.org
319
conf.org
|
@ -481,12 +481,21 @@ I use tags for contexts (mostly). The "@" represents location contexts and a mut
|
|||
(not (member (nth 2 (org-heading-components)) org-done-keywords)))
|
||||
(setq org-refile-target-verify-function 'nd/verify-refile-target)
|
||||
#+END_SRC
|
||||
** ui
|
||||
*** bullets
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(use-package org-bullets
|
||||
:ensure t
|
||||
:config
|
||||
(add-hook 'org-mode-hook (lambda () (org-bullets-mode))))
|
||||
#+END_SRC
|
||||
** agenda
|
||||
*** basic config
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(setq org-agenda-files (quote ("~/Org"
|
||||
"~/Org/large_projects"
|
||||
"~/Org/reference")))
|
||||
(setq org-agenda-files '("~/Org"
|
||||
"~/Org/large_projects"
|
||||
"~/Org/reference"))
|
||||
;; (setq org-agenda-files '("~/Org/reference/agendatest.org"))
|
||||
(setq org-agenda-dim-blocked-tasks nil)
|
||||
(setq org-agenda-compact-blocks t)
|
||||
#+END_SRC
|
||||
|
@ -513,7 +522,7 @@ These are the building blocks for skip functions.
|
|||
(and (not (nd/heading-has-parent)) (nd/is-task-p)))
|
||||
|
||||
(defun nd/is-project-task-p ()
|
||||
"return todo keyword if heading is task with no parents"
|
||||
"return todo keyword if heading is task with parents"
|
||||
(and (nd/heading-has-parent) (nd/is-task-p)))
|
||||
|
||||
(defun nd/is-scheduled-heading-p ()
|
||||
|
@ -578,9 +587,9 @@ These are the building blocks for skip functions.
|
|||
"returns parent keyword if heading is in the immediate subtree of a todoitem"
|
||||
(save-excursion (and (org-up-heading-safe) (nd/is-todoitem-p))))
|
||||
|
||||
(defun nd/is-discontinuous-project-task-p ()
|
||||
"detects todoitems that are children of non-todoitems
|
||||
that in turn are children of todoitems (discontinous project)"
|
||||
(defun nd/has-discontinuous-parent ()
|
||||
"returns t if heading has a parent which is not a
|
||||
todoitem which in turn has a parent which is a todoitem"
|
||||
(let ((has-todoitem-parent)
|
||||
(has-non-todoitem-parent))
|
||||
(save-excursion
|
||||
|
@ -677,154 +686,184 @@ These are the building blocks for skip functions.
|
|||
(org-forward-heading-same-level 1 t)))
|
||||
project-state))
|
||||
|
||||
(defmacro nd/is-project-keyword-status-p (top-keyword operator statuscode)
|
||||
(defmacro nd/is-project-keyword-status-p (test-keyword operator statuscode)
|
||||
"tests if a project has toplevel heading of top-keyword and
|
||||
child status equal to status code and returns keyword if
|
||||
both are true"
|
||||
`(if (and (equal ,keyword ,top-keyword)
|
||||
(nd/compare-statuscodes ,operator (nd/descend-into-project) statuscode))
|
||||
,keyword))
|
||||
`(and
|
||||
(equal ,keyword ,test-keyword)
|
||||
(nd/compare-statuscodes ,operator (nd/descend-into-project) ,statuscode)))
|
||||
|
||||
(defun nd/is-project-status-p (statuscode)
|
||||
(let ((keyword (nd/is-project-p)))
|
||||
(if keyword
|
||||
(case statuscode
|
||||
;; projects closed more than 30 days ago
|
||||
;; note CANCELLED overrides all subtasks/projects
|
||||
(:archivable
|
||||
(if (nd/is-archivable-heading-p)
|
||||
(cond ((equal keyword "CANCELLED") keyword)
|
||||
(t (nd/is-project-keyword-status-p "DONE" = :archivable)))))
|
||||
"Returns t if project matches statuscode given.
|
||||
Note that this assumes the headline being tested is a valid project"
|
||||
(case statuscode
|
||||
;; projects closed more than 30 days ago
|
||||
;; note CANCELLED overrides all subtasks/projects
|
||||
(:archivable
|
||||
(if (nd/is-archivable-heading-p)
|
||||
(or (equal keyword "CANCELLED")
|
||||
(nd/is-project-keyword-status-p "DONE" = :archivable))))
|
||||
|
||||
;; projects closed less than 30 days ago
|
||||
;; note CANCELLED overrides all subtasks/projects
|
||||
(:complete
|
||||
(if (not (nd/is-archivable-heading-p))
|
||||
(cond ((equal keyword "CANCELLED") keyword)
|
||||
(t (nd/is-project-keyword-status-p "DONE" = :complete)))))
|
||||
;; projects closed less than 30 days ago
|
||||
;; note CANCELLED overrides all subtasks/projects
|
||||
(:complete
|
||||
(if (not (nd/is-archivable-heading-p))
|
||||
(or (equal keyword "CANCELLED")
|
||||
(nd/is-project-keyword-status-p "DONE" = :complete))))
|
||||
|
||||
;; projects with no waiting, held, or active components
|
||||
(:stuck
|
||||
(nd/is-project-keyword-status-p "TODO" = :stuck))
|
||||
;; projects with no waiting, held, or active components
|
||||
(:stuck
|
||||
(nd/is-project-keyword-status-p "TODO" = :stuck))
|
||||
|
||||
;; held projects
|
||||
;; note toplevel HOLD overrides all subtasks/projects
|
||||
(:held
|
||||
(cond ((equal keyword "HOLD") keyword)
|
||||
(t (nd/is-project-keyword-status-p "TODO" = :stuck))))
|
||||
;; held projects
|
||||
;; note toplevel HOLD overrides all subtasks/projects
|
||||
(:held
|
||||
(or (equal keyword "HOLD")
|
||||
(nd/is-project-keyword-status-p "TODO" = :held)))
|
||||
|
||||
;; projects with at least one waiting component
|
||||
(:waiting
|
||||
(nd/is-project-keyword-status-p "TODO" = :waiting))
|
||||
;; projects with at least one waiting component
|
||||
(:waiting
|
||||
(nd/is-project-keyword-status-p "TODO" = :waiting))
|
||||
|
||||
;; projects with at least one active component
|
||||
(:active
|
||||
(nd/is-project-keyword-status-p "TODO" = :active))
|
||||
;; projects with at least one active component
|
||||
(:active
|
||||
(nd/is-project-keyword-status-p "TODO" = :active))
|
||||
|
||||
;; projects marked DONE but still have undone subtasks
|
||||
(:done-incomplete
|
||||
(nd/is-project-keyword-status-p "DONE" > :complete))
|
||||
;; projects marked DONE but still have undone subtasks
|
||||
(:done-incomplete
|
||||
(nd/is-project-keyword-status-p "DONE" > :complete))
|
||||
|
||||
;; projects not marked DONE but all subtasks are done
|
||||
(:undone-complete
|
||||
(nd/is-project-keyword-status-p "TODO" < :stuck))
|
||||
;; projects marked TODO but all subtasks are done
|
||||
(:undone-complete
|
||||
(nd/is-project-keyword-status-p "TODO" < :stuck))
|
||||
|
||||
;; projects with invalid todo keywords
|
||||
(:invalid-todostate
|
||||
(if (member keyword nd/project-invalid-todostates) keyword))
|
||||
;; projects with invalid todo keywords
|
||||
(:invalid-todostate
|
||||
(member keyword nd/project-invalid-todostates))
|
||||
|
||||
;; projects with scheduled heading (only subtasks should be scheduled)
|
||||
(:scheduled-project
|
||||
(if (nd/is-scheduled-heading-p) keyword))))))
|
||||
;; projects with scheduled heading (only subtasks should be scheduled)
|
||||
(:scheduled-project
|
||||
(nd/is-scheduled-heading-p))
|
||||
|
||||
;; error if not known
|
||||
(t (if (not (member statuscode nd/project-statuscodes))
|
||||
(error "unknown statuscode")))))
|
||||
#+END_SRC
|
||||
*** skip functions
|
||||
These are the primary means we use to sort through tasks. Note that we could do this with
|
||||
tags in the custom commands section but I find this easier to maintain and possibly faster.
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
;; TODO we could clean this up with macros
|
||||
(defun nd/skip-non-atomic-tasks ()
|
||||
;; helper functions
|
||||
(defun nd/skip-item ()
|
||||
(save-excursion (or (outline-next-heading) (point-max))))
|
||||
|
||||
(defun nd/skip-subtree ()
|
||||
(save-excursion (or (org-end-of-subtree t) (point-max))))
|
||||
|
||||
(defconst nd/project-skip-todostates
|
||||
'("HOLD" "CANCELLED")
|
||||
"These keywords override all contents within their subtrees.
|
||||
Currently used to tell skip functions when they can hop over
|
||||
entire subtrees to save time and ignore tasks")
|
||||
|
||||
(defmacro nd/skip-heading-with (heading-fun test-fun)
|
||||
"Skips headings accoring to certain characteristics. heading-fun
|
||||
is a function that tests the heading and returns the todoitem keyword
|
||||
on success. Test-fun is a function that further tests the identity of
|
||||
the heading and may or may not use the keyword output supplied by
|
||||
the heading-fun. This function will not skip if heading-fun and
|
||||
test-fun return true"
|
||||
`(save-restriction
|
||||
(widen)
|
||||
(let ((keyword (,heading-fun)))
|
||||
(message keyword)
|
||||
(if (not (and keyword ,test-fun))
|
||||
(nd/skip-item)))))
|
||||
|
||||
;; atomic tasks
|
||||
;; by definition these have no parents, so
|
||||
;; we don't need to worry about skipping over projects
|
||||
;; any todo state is valid and we only sort by done/cancelled
|
||||
(defun nd/skip-non-unclosed-atomic-tasks ()
|
||||
(nd/skip-heading-with
|
||||
nd/is-atomic-task-p
|
||||
(not (member keyword org-done-keywords))))
|
||||
|
||||
(defun nd/skip-non-closed-atomic-tasks ()
|
||||
(nd/skip-heading-with
|
||||
nd/is-atomic-task-p
|
||||
(and (member keyword org-done-keywords)
|
||||
(not (nd/is-archivable-heading)))))
|
||||
|
||||
(defun nd/skip-non-archivable-atomic-tasks ()
|
||||
(nd/skip-heading-with
|
||||
nd/is-atomic-task-p
|
||||
(and (member keyword org-done-keywords)
|
||||
(nd/is-archivable-heading))))
|
||||
|
||||
;; project tasks
|
||||
;; since these are part of projects I need to assess
|
||||
;; if the parent project is skippable, in which case
|
||||
;; I jump to the next subtree
|
||||
;; Note that I only care about the keyword in these
|
||||
;; cases because I don't archive these, I archive
|
||||
;; their parent projects. The keywords I care about
|
||||
;; are NEXT, WAITING, and HOLD because these are
|
||||
;; definitive project tasks that require/inhibit
|
||||
;; futher action
|
||||
(defun nd/skip-non-keyword-project-tasks (skip-keyword)
|
||||
(save-restriction
|
||||
(widen)
|
||||
(if (not (nd/is-atomic-task-p))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
(let ((keyword (nd/is-todoitem-p)))
|
||||
(if keyword
|
||||
(if (nd/heading-has-children)
|
||||
(if (member keyword nd/project-skip-todostates)
|
||||
(nd/skip-subtree)
|
||||
(nd/skip-item))
|
||||
(if (not (and (nd/heading-has-parent)
|
||||
(equal keyword skip-keyword)))
|
||||
(nd/skip-item)))
|
||||
(nd/skip-item)))))
|
||||
|
||||
(defun nd/skip-non-next-project-tasks ()
|
||||
(save-restriction
|
||||
(widen)
|
||||
;; TODO skip over invalid and held
|
||||
(if (not (equal (nd/is-project-task-p) "NEXT"))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
|
||||
(defun nd/skip-non-waiting-project-tasks ()
|
||||
(save-restriction
|
||||
(widen)
|
||||
;; TODO skip over invalid and held
|
||||
(if (not (equal (nd/is-project-task-p) "WAITING"))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
|
||||
(defun nd/skip-non-held-project-tasks ()
|
||||
(save-restriction
|
||||
(widen)
|
||||
;; TODO skip over invalid and held
|
||||
(if (not (equal (nd/is-project-task-p) "HOLD"))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
|
||||
;; slip functions
|
||||
;; task-level errors
|
||||
(defun nd/skip-non-discontinuous-project-tasks ()
|
||||
(save-restriction
|
||||
(widen)
|
||||
(if (not (nd/is-discontinuous-project-task-p))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
(nd/skip-heading-with
|
||||
nd/is-todoitem-p
|
||||
(nd/has-discontinuous-parent)))
|
||||
|
||||
(defun nd/skip-non-done-open-todoitems ()
|
||||
(save-restriction
|
||||
(widen)
|
||||
(if (not (and (member (nd/is-todoitem-p) org-done-keywords) (not (nd/is-closed-heading-p))))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
(defun nd/skip-non-done-unclosed-todoitems ()
|
||||
(nd/skip-heading-with
|
||||
nd/is-todoitem-p
|
||||
(and (member keyword org-done-keywords)
|
||||
(not (nd/is-closed-heading-p)))))
|
||||
|
||||
(defun nd/skip-non-undone-closed-todoitems ()
|
||||
(save-restriction
|
||||
(widen)
|
||||
(if (not (and (not (member (nd/is-todoitem-p)) org-done-keywords) (nd/is-closed-heading-p)))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
(nd/skip-heading-with
|
||||
nd/is-todoitem-p
|
||||
(and (not (member keyword org-done-keywords))
|
||||
(nd/is-closed-heading-p))))
|
||||
|
||||
(defun nd/skip-non-series-atomic-tasks ()
|
||||
(nd/skip-heading-with
|
||||
nd/is-atomic-task-p
|
||||
(nd/is-series-heading-p)))
|
||||
|
||||
;; projects
|
||||
;; TODO skip entire subtree if we don't need to evaluate anything inside
|
||||
;; otherwise (for example) a held project will still have it's subtasks show up
|
||||
(defun nd/skip-projects-without-statuscode (statuscode)
|
||||
(save-restriction
|
||||
(widen)
|
||||
(if (not (nd/is-project-status-p statuscode))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
|
||||
;; top-level projects
|
||||
(defun nd/skip-subprojects-without-statuscode (statuscode)
|
||||
(save-restriction
|
||||
(widen)
|
||||
(if (or (nd/heading-has-parent) (not (nd/is-project-status-p statuscode)))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
|
||||
(defun nd/skip-series-projects-without-statuscode (statuscode)
|
||||
(save-restriction
|
||||
(widen)
|
||||
(if (not (and (nd/is-series-heading-p) (nd/is-project-status-p statuscode)))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
;; series projects
|
||||
;; defined as project with property Project_type=series
|
||||
;; must have:
|
||||
;; - one level of subtasks
|
||||
;; - all subtasks either TODO/scheduled, NEXT, DONE, CANCELLED
|
||||
;; - at least one TODO/scheduled or NEXT (active) ..else empty
|
||||
;; invalid if:
|
||||
;; - project header is invalid project header (typical rules apply)
|
||||
|
||||
;; archiving
|
||||
(defun nd/skip-non-archivable-atomic-tasks ()
|
||||
(save-restriction
|
||||
(widen)
|
||||
(if (not (nd/is-archivable-atomic-task-p))
|
||||
(save-excursion (or (outline-next-heading) (point-max))))))
|
||||
(let ((keyword (nd/is-project-p)))
|
||||
;; TODO there may be a way to skip over skippable projects
|
||||
;; and save a few cycles. Not a huge deal, but would require
|
||||
;; keeping the skippable line and then skipping over the others
|
||||
;; in one fell swoop, not easy to do efficiently
|
||||
(if keyword
|
||||
(if (not (nd/is-project-status-p statuscode))
|
||||
(if nd/agenda-limit-project-toplevel
|
||||
(nd/skip-subtree)
|
||||
(nd/skip-item)))
|
||||
(nd/skip-item)))))
|
||||
#+END_SRC
|
||||
*** interactive view functions
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
|
@ -840,8 +879,8 @@ tags in the custom commands section but I find this easier to maintain and possi
|
|||
|
||||
(defun nd/agenda-base-task-command (keyword skip-fun)
|
||||
"shorter syntax to define task agenda commands"
|
||||
`(tags-todo
|
||||
"-NA-REFILE/!"
|
||||
`(tags
|
||||
"-NA-REFILE/"
|
||||
((org-agenda-overriding-header (concat ,keyword " Tasks"))
|
||||
(org-agenda-skip-function ,skip-fun)
|
||||
(org-agenda-todo-ignore-with-date 'all)
|
||||
|
@ -855,9 +894,7 @@ tags in the custom commands section but I find this easier to maintain and possi
|
|||
(and nd/agenda-limit-project-toplevel "Toplevel ")
|
||||
,keyword
|
||||
" Projects"))
|
||||
(org-agenda-skip-function (if nd/agenda-limit-project-toplevel
|
||||
'(nd/skip-subprojects-without-statuscode ,statuscode)
|
||||
'(nd/skip-projects-without-statuscode ,statuscode)))
|
||||
(org-agenda-skip-function '(nd/skip-projects-without-statuscode ,statuscode))
|
||||
(org-agenda-sorting-strategy '(category-keep)))))
|
||||
|
||||
#+END_SRC
|
||||
|
@ -868,10 +905,10 @@ tags in the custom commands section but I find this easier to maintain and possi
|
|||
`(("t"
|
||||
"Task View"
|
||||
((agenda "" nil)
|
||||
,(nd/agenda-base-task-command "Next Project" ''nd/skip-non-next-project-tasks)
|
||||
,(nd/agenda-base-task-command "Waiting Project" ''nd/skip-non-waiting-project-tasks)
|
||||
,(nd/agenda-base-task-command "Atomic" ''nd/skip-non-atomic-tasks)
|
||||
,(nd/agenda-base-task-command "Held Project" ''nd/skip-non-held-project-tasks)))
|
||||
,(nd/agenda-base-task-command "Next Project" ''(nd/skip-non-keyword-project-tasks "NEXT"))
|
||||
,(nd/agenda-base-task-command "Waiting Project" ''(nd/skip-non-keyword-project-tasks "WAITING"))
|
||||
,(nd/agenda-base-task-command "Atomic" ''nd/skip-non-unclosed-atomic-tasks)
|
||||
,(nd/agenda-base-task-command "Held Project" ''(nd/skip-non-keyword-project-tasks "HOLD"))))
|
||||
("o"
|
||||
"Project Overview"
|
||||
(,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/!" "Stuck" :stuck)
|
||||
|
@ -884,11 +921,11 @@ tags in the custom commands section but I find this easier to maintain and possi
|
|||
((org-agenda-overriding-header "Tasks to Refile"))
|
||||
(org-tags-match-list-sublevels nil))
|
||||
,(nd/agenda-base-task-command "Discontinous Project" ''nd/skip-non-discontinuous-project-tasks)
|
||||
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/!" "Unmarked Completed" :complete)
|
||||
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/" "Invalid" :invalid-todostate)
|
||||
;; ,(nd/agenda-base-task-command "Done But Not Closed" ''nd/skip-non-done-open-todoitems)
|
||||
;; ,(nd/agenda-base-task-command "Closed But Not Done" ''nd/skip-non-open-closed-todoitems)
|
||||
))
|
||||
,(nd/agenda-base-task-command "Undone Closed" ''nd/skip-non-undone-closed-todoitems)
|
||||
,(nd/agenda-base-task-command "Done Unclosed" ''nd/skip-non-done-unclosed-todoitems)
|
||||
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/" "Undone Completed" :undone-complete)
|
||||
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/" "Done Incompleted" :done-incomplete)
|
||||
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/" "Invalid Todostate" :invalid-todostate)))
|
||||
("s"
|
||||
"Series projects"
|
||||
(,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC+Project_Type=\"series\"/!" "Active Series" :active)
|
||||
|
@ -935,14 +972,6 @@ the agenda does not do this by default...it's annoying
|
|||
|
||||
(setq org-agenda-auto-exclude-function 'nd/org-auto-exclude-function)
|
||||
#+END_SRC
|
||||
** ui
|
||||
*** bullets
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(use-package org-bullets
|
||||
:ensure t
|
||||
:config
|
||||
(add-hook 'org-mode-hook (lambda () (org-bullets-mode))))
|
||||
#+END_SRC
|
||||
** caldav
|
||||
+BEGIN_SRC emacs-lisp
|
||||
(use-package org-caldav
|
||||
|
|
Loading…
Reference in New Issue