simplify skip functions

This commit is contained in:
petrucci4prez 2018-04-27 23:03:56 -04:00
parent 0ef5fe8dd1
commit 59010e234f
2 changed files with 358 additions and 299 deletions

322
conf.el
View File

@ -319,9 +319,15 @@
(not (member (nth 2 (org-heading-components)) org-done-keywords))) (not (member (nth 2 (org-heading-components)) org-done-keywords)))
(setq org-refile-target-verify-function 'nd/verify-refile-target) (setq org-refile-target-verify-function 'nd/verify-refile-target)
(setq org-agenda-files (quote ("~/Org" (use-package org-bullets
"~/Org/large_projects" :ensure t
"~/Org/reference"))) :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-dim-blocked-tasks nil)
(setq org-agenda-compact-blocks t) (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))) (and (not (nd/heading-has-parent)) (nd/is-task-p)))
(defun nd/is-project-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))) (and (nd/heading-has-parent) (nd/is-task-p)))
(defun nd/is-scheduled-heading-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" "returns parent keyword if heading is in the immediate subtree of a todoitem"
(save-excursion (and (org-up-heading-safe) (nd/is-todoitem-p)))) (save-excursion (and (org-up-heading-safe) (nd/is-todoitem-p))))
(defun nd/is-discontinuous-project-task-p () (defun nd/has-discontinuous-parent ()
"detects todoitems that are children of non-todoitems "returns t if heading has a parent which is not a
that in turn are children of todoitems (discontinous project)" todoitem which in turn has a parent which is a todoitem"
(let ((has-todoitem-parent) (let ((has-todoitem-parent)
(has-non-todoitem-parent)) (has-non-todoitem-parent))
(save-excursion (save-excursion
@ -509,149 +515,180 @@ down the list override higher items")
(org-forward-heading-same-level 1 t))) (org-forward-heading-same-level 1 t)))
project-state)) 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 "tests if a project has toplevel heading of top-keyword and
child status equal to status code and returns keyword if child status equal to status code and returns keyword if
both are true" both are true"
`(if (and (equal ,keyword ,top-keyword) `(and
(nd/compare-statuscodes ,operator (nd/descend-into-project) statuscode)) (equal ,keyword ,test-keyword)
,keyword)) (nd/compare-statuscodes ,operator (nd/descend-into-project) ,statuscode)))
(defun nd/is-project-status-p (statuscode) (defun nd/is-project-status-p (statuscode)
(let ((keyword (nd/is-project-p))) "Returns t if project matches statuscode given.
(if keyword Note that this assumes the headline being tested is a valid project"
(case statuscode (case statuscode
;; projects closed more than 30 days ago ;; projects closed more than 30 days ago
;; note CANCELLED overrides all subtasks/projects ;; note CANCELLED overrides all subtasks/projects
(:archivable (:archivable
(if (nd/is-archivable-heading-p) (if (nd/is-archivable-heading-p)
(cond ((equal keyword "CANCELLED") keyword) (or (equal keyword "CANCELLED")
(t (nd/is-project-keyword-status-p "DONE" = :archivable))))) (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))
(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))
;; 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 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 TODO but all subtasks are done
(:undone-complete
(nd/is-project-keyword-status-p "TODO" < :stuck))
;; projects with invalid todo keywords
(:invalid-todostate
(member keyword nd/project-invalid-todostates))
;; projects with scheduled heading (only subtasks should be scheduled)
(:scheduled-project
(nd/is-scheduled-heading-p))
;; projects closed less than 30 days ago ;; error if not known
;; note CANCELLED overrides all subtasks/projects (t (if (not (member statuscode nd/project-statuscodes))
(:complete (error "unknown statuscode")))))
(if (not (nd/is-archivable-heading-p))
(cond ((equal keyword "CANCELLED") keyword)
(t (nd/is-project-keyword-status-p "DONE" = :complete)))))
;; projects with no waiting, held, or active components ;; helper functions
(:stuck (defun nd/skip-item ()
(nd/is-project-keyword-status-p "TODO" = :stuck)) (save-excursion (or (outline-next-heading) (point-max))))
;; held projects (defun nd/skip-subtree ()
;; note toplevel HOLD overrides all subtasks/projects (save-excursion (or (org-end-of-subtree t) (point-max))))
(:held
(cond ((equal keyword "HOLD") keyword)
(t (nd/is-project-keyword-status-p "TODO" = :stuck))))
;; projects with at least one waiting component (defconst nd/project-skip-todostates
(:waiting '("HOLD" "CANCELLED")
(nd/is-project-keyword-status-p "TODO" = :waiting)) "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")
;; projects with at least one active component (defmacro nd/skip-heading-with (heading-fun test-fun)
(:active "Skips headings accoring to certain characteristics. heading-fun
(nd/is-project-keyword-status-p "TODO" = :active)) 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
;; projects marked DONE but still have undone subtasks the heading and may or may not use the keyword output supplied by
(:done-incomplete the heading-fun. This function will not skip if heading-fun and
(nd/is-project-keyword-status-p "DONE" > :complete)) test-fun return true"
`(save-restriction
;; projects not marked DONE but all subtasks are done (widen)
(:undone-complete (let ((keyword (,heading-fun)))
(nd/is-project-keyword-status-p "TODO" < :stuck)) (message keyword)
(if (not (and keyword ,test-fun))
;; projects with invalid todo keywords (nd/skip-item)))))
(:invalid-todostate
(if (member keyword nd/project-invalid-todostates) keyword))
;; projects with scheduled heading (only subtasks should be scheduled)
(:scheduled-project
(if (nd/is-scheduled-heading-p) keyword))))))
;; TODO we could clean this up with macros
(defun nd/skip-non-atomic-tasks ()
(save-restriction
(widen)
(if (not (nd/is-atomic-task-p))
(save-excursion (or (outline-next-heading) (point-max))))))
(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 () ;; atomic tasks
(save-restriction ;; by definition these have no parents, so
(widen) ;; we don't need to worry about skipping over projects
;; TODO skip over invalid and held ;; any todo state is valid and we only sort by done/cancelled
(if (not (equal (nd/is-project-task-p) "WAITING")) (defun nd/skip-non-unclosed-atomic-tasks ()
(save-excursion (or (outline-next-heading) (point-max)))))) (nd/skip-heading-with
nd/is-atomic-task-p
(not (member keyword org-done-keywords))))
(defun nd/skip-non-held-project-tasks () (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 (save-restriction
(widen) (widen)
;; TODO skip over invalid and held (let ((keyword (nd/is-todoitem-p)))
(if (not (equal (nd/is-project-task-p) "HOLD")) (if keyword
(save-excursion (or (outline-next-heading) (point-max)))))) (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)))))
;; slip functions ;; task-level errors
(defun nd/skip-non-discontinuous-project-tasks () (defun nd/skip-non-discontinuous-project-tasks ()
(save-restriction (nd/skip-heading-with
(widen) nd/is-todoitem-p
(if (not (nd/is-discontinuous-project-task-p)) (nd/has-discontinuous-parent)))
(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-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-undone-closed-todoitems () (defun nd/skip-non-undone-closed-todoitems ()
(save-restriction (nd/skip-heading-with
(widen) nd/is-todoitem-p
(if (not (and (not (member (nd/is-todoitem-p)) org-done-keywords) (nd/is-closed-heading-p))) (and (not (member keyword org-done-keywords))
(save-excursion (or (outline-next-heading) (point-max)))))) (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 ;; 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) (defun nd/skip-projects-without-statuscode (statuscode)
(save-restriction (save-restriction
(widen) (widen)
(if (not (nd/is-project-status-p statuscode)) (let ((keyword (nd/is-project-p)))
(save-excursion (or (outline-next-heading) (point-max)))))) ;; TODO there may be a way to skip over skippable projects
;; and save a few cycles. Not a huge deal, but would require
;; top-level projects ;; keeping the skippable line and then skipping over the others
(defun nd/skip-subprojects-without-statuscode (statuscode) ;; in one fell swoop, not easy to do efficiently
(save-restriction (if keyword
(widen) (if (not (nd/is-project-status-p statuscode))
(if (or (nd/heading-has-parent) (not (nd/is-project-status-p statuscode))) (if nd/agenda-limit-project-toplevel
(save-excursion (or (outline-next-heading) (point-max)))))) (nd/skip-subtree)
(nd/skip-item)))
(defun nd/skip-series-projects-without-statuscode (statuscode) (nd/skip-item)))))
(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))))))
(defvar nd/agenda-limit-project-toplevel t (defvar nd/agenda-limit-project-toplevel t
"used to filter projects by all levels or top-level only") "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) (defun nd/agenda-base-task-command (keyword skip-fun)
"shorter syntax to define task agenda commands" "shorter syntax to define task agenda commands"
`(tags-todo `(tags
"-NA-REFILE/!" "-NA-REFILE/"
((org-agenda-overriding-header (concat ,keyword " Tasks")) ((org-agenda-overriding-header (concat ,keyword " Tasks"))
(org-agenda-skip-function ,skip-fun) (org-agenda-skip-function ,skip-fun)
(org-agenda-todo-ignore-with-date 'all) (org-agenda-todo-ignore-with-date 'all)
@ -680,9 +717,7 @@ both are true"
(and nd/agenda-limit-project-toplevel "Toplevel ") (and nd/agenda-limit-project-toplevel "Toplevel ")
,keyword ,keyword
" Projects")) " Projects"))
(org-agenda-skip-function (if nd/agenda-limit-project-toplevel (org-agenda-skip-function '(nd/skip-projects-without-statuscode ,statuscode))
'(nd/skip-subprojects-without-statuscode ,statuscode)
'(nd/skip-projects-without-statuscode ,statuscode)))
(org-agenda-sorting-strategy '(category-keep))))) (org-agenda-sorting-strategy '(category-keep)))))
(setq org-agenda-tags-todo-honor-ignore-options t) (setq org-agenda-tags-todo-honor-ignore-options t)
@ -690,10 +725,10 @@ both are true"
`(("t" `(("t"
"Task View" "Task View"
((agenda "" nil) ((agenda "" nil)
,(nd/agenda-base-task-command "Next Project" ''nd/skip-non-next-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-waiting-project-tasks) ,(nd/agenda-base-task-command "Waiting Project" ''(nd/skip-non-keyword-project-tasks "WAITING"))
,(nd/agenda-base-task-command "Atomic" ''nd/skip-non-atomic-tasks) ,(nd/agenda-base-task-command "Atomic" ''nd/skip-non-unclosed-atomic-tasks)
,(nd/agenda-base-task-command "Held Project" ''nd/skip-non-held-project-tasks))) ,(nd/agenda-base-task-command "Held Project" ''(nd/skip-non-keyword-project-tasks "HOLD"))))
("o" ("o"
"Project Overview" "Project Overview"
(,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/!" "Stuck" :stuck) (,(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-agenda-overriding-header "Tasks to Refile"))
(org-tags-match-list-sublevels nil)) (org-tags-match-list-sublevels nil))
,(nd/agenda-base-task-command "Discontinous Project" ''nd/skip-non-discontinuous-project-tasks) ,(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-task-command "Undone Closed" ''nd/skip-non-undone-closed-todoitems)
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/" "Invalid" :invalid-todostate) ,(nd/agenda-base-task-command "Done Unclosed" ''nd/skip-non-done-unclosed-todoitems)
;; ,(nd/agenda-base-task-command "Done But Not Closed" ''nd/skip-non-done-open-todoitems) ,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/" "Undone Completed" :undone-complete)
;; ,(nd/agenda-base-task-command "Closed But Not Done" ''nd/skip-non-open-closed-todoitems) ,(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" ("s"
"Series projects" "Series projects"
(,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC+Project_Type=\"series\"/!" "Active Series" :active) (,(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) (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 (use-package calfw-org
:init :init
:ensure t :ensure t

335
conf.org
View File

@ -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))) (not (member (nth 2 (org-heading-components)) org-done-keywords)))
(setq org-refile-target-verify-function 'nd/verify-refile-target) (setq org-refile-target-verify-function 'nd/verify-refile-target)
#+END_SRC #+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 ** agenda
*** basic config *** basic config
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(setq org-agenda-files (quote ("~/Org" (setq org-agenda-files '("~/Org"
"~/Org/large_projects" "~/Org/large_projects"
"~/Org/reference"))) "~/Org/reference"))
;; (setq org-agenda-files '("~/Org/reference/agendatest.org"))
(setq org-agenda-dim-blocked-tasks nil) (setq org-agenda-dim-blocked-tasks nil)
(setq org-agenda-compact-blocks t) (setq org-agenda-compact-blocks t)
#+END_SRC #+END_SRC
@ -513,7 +522,7 @@ These are the building blocks for skip functions.
(and (not (nd/heading-has-parent)) (nd/is-task-p))) (and (not (nd/heading-has-parent)) (nd/is-task-p)))
(defun nd/is-project-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))) (and (nd/heading-has-parent) (nd/is-task-p)))
(defun nd/is-scheduled-heading-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" "returns parent keyword if heading is in the immediate subtree of a todoitem"
(save-excursion (and (org-up-heading-safe) (nd/is-todoitem-p)))) (save-excursion (and (org-up-heading-safe) (nd/is-todoitem-p))))
(defun nd/is-discontinuous-project-task-p () (defun nd/has-discontinuous-parent ()
"detects todoitems that are children of non-todoitems "returns t if heading has a parent which is not a
that in turn are children of todoitems (discontinous project)" todoitem which in turn has a parent which is a todoitem"
(let ((has-todoitem-parent) (let ((has-todoitem-parent)
(has-non-todoitem-parent)) (has-non-todoitem-parent))
(save-excursion (save-excursion
@ -677,154 +686,184 @@ These are the building blocks for skip functions.
(org-forward-heading-same-level 1 t))) (org-forward-heading-same-level 1 t)))
project-state)) 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 "tests if a project has toplevel heading of top-keyword and
child status equal to status code and returns keyword if child status equal to status code and returns keyword if
both are true" both are true"
`(if (and (equal ,keyword ,top-keyword) `(and
(nd/compare-statuscodes ,operator (nd/descend-into-project) statuscode)) (equal ,keyword ,test-keyword)
,keyword)) (nd/compare-statuscodes ,operator (nd/descend-into-project) ,statuscode)))
(defun nd/is-project-status-p (statuscode) (defun nd/is-project-status-p (statuscode)
(let ((keyword (nd/is-project-p))) "Returns t if project matches statuscode given.
(if keyword Note that this assumes the headline being tested is a valid project"
(case statuscode (case statuscode
;; projects closed more than 30 days ago ;; projects closed more than 30 days ago
;; note CANCELLED overrides all subtasks/projects ;; note CANCELLED overrides all subtasks/projects
(:archivable (:archivable
(if (nd/is-archivable-heading-p) (if (nd/is-archivable-heading-p)
(cond ((equal keyword "CANCELLED") keyword) (or (equal keyword "CANCELLED")
(t (nd/is-project-keyword-status-p "DONE" = :archivable))))) (nd/is-project-keyword-status-p "DONE" = :archivable))))
;; projects closed less than 30 days ago ;; projects closed less than 30 days ago
;; note CANCELLED overrides all subtasks/projects ;; note CANCELLED overrides all subtasks/projects
(:complete (:complete
(if (not (nd/is-archivable-heading-p)) (if (not (nd/is-archivable-heading-p))
(cond ((equal keyword "CANCELLED") keyword) (or (equal keyword "CANCELLED")
(t (nd/is-project-keyword-status-p "DONE" = :complete))))) (nd/is-project-keyword-status-p "DONE" = :complete))))
;; projects with no waiting, held, or active components ;; projects with no waiting, held, or active components
(:stuck (:stuck
(nd/is-project-keyword-status-p "TODO" = :stuck)) (nd/is-project-keyword-status-p "TODO" = :stuck))
;; held projects ;; held projects
;; note toplevel HOLD overrides all subtasks/projects ;; note toplevel HOLD overrides all subtasks/projects
(:held (:held
(cond ((equal keyword "HOLD") keyword) (or (equal keyword "HOLD")
(t (nd/is-project-keyword-status-p "TODO" = :stuck)))) (nd/is-project-keyword-status-p "TODO" = :held)))
;; projects with at least one waiting component ;; projects with at least one waiting component
(:waiting (:waiting
(nd/is-project-keyword-status-p "TODO" = :waiting)) (nd/is-project-keyword-status-p "TODO" = :waiting))
;; projects with at least one active component ;; projects with at least one active component
(:active (:active
(nd/is-project-keyword-status-p "TODO" = :active)) (nd/is-project-keyword-status-p "TODO" = :active))
;; projects marked DONE but still have undone subtasks ;; projects marked DONE but still have undone subtasks
(:done-incomplete (:done-incomplete
(nd/is-project-keyword-status-p "DONE" > :complete)) (nd/is-project-keyword-status-p "DONE" > :complete))
;; projects not marked DONE but all subtasks are done ;; projects marked TODO but all subtasks are done
(:undone-complete (:undone-complete
(nd/is-project-keyword-status-p "TODO" < :stuck)) (nd/is-project-keyword-status-p "TODO" < :stuck))
;; projects with invalid todo keywords ;; projects with invalid todo keywords
(:invalid-todostate (:invalid-todostate
(if (member keyword nd/project-invalid-todostates) keyword)) (member keyword nd/project-invalid-todostates))
;; projects with scheduled heading (only subtasks should be scheduled) ;; projects with scheduled heading (only subtasks should be scheduled)
(:scheduled-project (:scheduled-project
(if (nd/is-scheduled-heading-p) keyword)))))) (nd/is-scheduled-heading-p))
;; error if not known
(t (if (not (member statuscode nd/project-statuscodes))
(error "unknown statuscode")))))
#+END_SRC #+END_SRC
*** skip functions *** skip functions
These are the primary means we use to sort through tasks. Note that we could do this with 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. tags in the custom commands section but I find this easier to maintain and possibly faster.
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
;; TODO we could clean this up with macros ;; helper functions
(defun nd/skip-non-atomic-tasks () (defun nd/skip-item ()
(save-restriction (save-excursion (or (outline-next-heading) (point-max))))
(widen)
(if (not (nd/is-atomic-task-p))
(save-excursion (or (outline-next-heading) (point-max))))))
(defun nd/skip-non-next-project-tasks () (defun nd/skip-subtree ()
(save-restriction (save-excursion (or (org-end-of-subtree t) (point-max))))
(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 () (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 (save-restriction
(widen) (widen)
;; TODO skip over invalid and held (let ((keyword (nd/is-todoitem-p)))
(if (not (equal (nd/is-project-task-p) "HOLD")) (if keyword
(save-excursion (or (outline-next-heading) (point-max)))))) (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)))))
;; slip functions ;; task-level errors
(defun nd/skip-non-discontinuous-project-tasks () (defun nd/skip-non-discontinuous-project-tasks ()
(save-restriction (nd/skip-heading-with
(widen) nd/is-todoitem-p
(if (not (nd/is-discontinuous-project-task-p)) (nd/has-discontinuous-parent)))
(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-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-undone-closed-todoitems () (defun nd/skip-non-undone-closed-todoitems ()
(save-restriction (nd/skip-heading-with
(widen) nd/is-todoitem-p
(if (not (and (not (member (nd/is-todoitem-p)) org-done-keywords) (nd/is-closed-heading-p))) (and (not (member keyword org-done-keywords))
(save-excursion (or (outline-next-heading) (point-max)))))) (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 ;; 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) (defun nd/skip-projects-without-statuscode (statuscode)
(save-restriction (save-restriction
(widen) (widen)
(if (not (nd/is-project-status-p statuscode)) (let ((keyword (nd/is-project-p)))
(save-excursion (or (outline-next-heading) (point-max)))))) ;; TODO there may be a way to skip over skippable projects
;; and save a few cycles. Not a huge deal, but would require
;; top-level projects ;; keeping the skippable line and then skipping over the others
(defun nd/skip-subprojects-without-statuscode (statuscode) ;; in one fell swoop, not easy to do efficiently
(save-restriction (if keyword
(widen) (if (not (nd/is-project-status-p statuscode))
(if (or (nd/heading-has-parent) (not (nd/is-project-status-p statuscode))) (if nd/agenda-limit-project-toplevel
(save-excursion (or (outline-next-heading) (point-max)))))) (nd/skip-subtree)
(nd/skip-item)))
(defun nd/skip-series-projects-without-statuscode (statuscode) (nd/skip-item)))))
(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))))))
#+END_SRC #+END_SRC
*** interactive view functions *** interactive view functions
#+BEGIN_SRC emacs-lisp #+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) (defun nd/agenda-base-task-command (keyword skip-fun)
"shorter syntax to define task agenda commands" "shorter syntax to define task agenda commands"
`(tags-todo `(tags
"-NA-REFILE/!" "-NA-REFILE/"
((org-agenda-overriding-header (concat ,keyword " Tasks")) ((org-agenda-overriding-header (concat ,keyword " Tasks"))
(org-agenda-skip-function ,skip-fun) (org-agenda-skip-function ,skip-fun)
(org-agenda-todo-ignore-with-date 'all) (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 ") (and nd/agenda-limit-project-toplevel "Toplevel ")
,keyword ,keyword
" Projects")) " Projects"))
(org-agenda-skip-function (if nd/agenda-limit-project-toplevel (org-agenda-skip-function '(nd/skip-projects-without-statuscode ,statuscode))
'(nd/skip-subprojects-without-statuscode ,statuscode)
'(nd/skip-projects-without-statuscode ,statuscode)))
(org-agenda-sorting-strategy '(category-keep))))) (org-agenda-sorting-strategy '(category-keep)))))
#+END_SRC #+END_SRC
@ -868,10 +905,10 @@ tags in the custom commands section but I find this easier to maintain and possi
`(("t" `(("t"
"Task View" "Task View"
((agenda "" nil) ((agenda "" nil)
,(nd/agenda-base-task-command "Next Project" ''nd/skip-non-next-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-waiting-project-tasks) ,(nd/agenda-base-task-command "Waiting Project" ''(nd/skip-non-keyword-project-tasks "WAITING"))
,(nd/agenda-base-task-command "Atomic" ''nd/skip-non-atomic-tasks) ,(nd/agenda-base-task-command "Atomic" ''nd/skip-non-unclosed-atomic-tasks)
,(nd/agenda-base-task-command "Held Project" ''nd/skip-non-held-project-tasks))) ,(nd/agenda-base-task-command "Held Project" ''(nd/skip-non-keyword-project-tasks "HOLD"))))
("o" ("o"
"Project Overview" "Project Overview"
(,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/!" "Stuck" :stuck) (,(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-agenda-overriding-header "Tasks to Refile"))
(org-tags-match-list-sublevels nil)) (org-tags-match-list-sublevels nil))
,(nd/agenda-base-task-command "Discontinous Project" ''nd/skip-non-discontinuous-project-tasks) ,(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-task-command "Undone Closed" ''nd/skip-non-undone-closed-todoitems)
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/" "Invalid" :invalid-todostate) ,(nd/agenda-base-task-command "Done Unclosed" ''nd/skip-non-done-unclosed-todoitems)
;; ,(nd/agenda-base-task-command "Done But Not Closed" ''nd/skip-non-done-open-todoitems) ,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/" "Undone Completed" :undone-complete)
;; ,(nd/agenda-base-task-command "Closed But Not Done" ''nd/skip-non-open-closed-todoitems) ,(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" ("s"
"Series projects" "Series projects"
(,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC+Project_Type=\"series\"/!" "Active Series" :active) (,(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) (setq org-agenda-auto-exclude-function 'nd/org-auto-exclude-function)
#+END_SRC #+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 ** caldav
+BEGIN_SRC emacs-lisp +BEGIN_SRC emacs-lisp
(use-package org-caldav (use-package org-caldav