From mboxrd@z Thu Jan 1 00:00:00 1970 From: Alan Schmitt Subject: org-review-schedule Date: Fri, 18 Apr 2014 22:27:02 +0200 Message-ID: Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Return-path: Received: from eggs.gnu.org ([2001:4830:134:3::10]:55924) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1WbFNT-0003cr-U8 for emacs-orgmode@gnu.org; Fri, 18 Apr 2014 16:27:23 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1WbFNM-0000z0-Ei for emacs-orgmode@gnu.org; Fri, 18 Apr 2014 16:27:15 -0400 Received: from mail2-relais-roc.national.inria.fr ([192.134.164.83]:35836) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1WbFNL-0000ym-UO for emacs-orgmode@gnu.org; Fri, 18 Apr 2014 16:27:08 -0400 List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org Sender: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org To: emacs-orgmode --=-=-= Content-Type: text/plain Hello, I've just finished writing a little bit of code that allows the scheduling of reviews. The basic idea is that every task that is supposed to be reviewed has a LAST_REVIEW property (a date when the task / project was last reviewed), and optionally a REVIEW_DELAY property (with a configurable default value). If the current date is after LAST_REVIEW + REVIEW_DELAY, then the task is considered up to review. I've written a small function to show these tasks in the agenda, and a sorting function to allow them to be sorted from "this had to be review so long ago" to "this has just been available to review". Another function allows to set the LAST_REVIEW property to the current date (or a chosen date if called with C-u). I attach the code. It's the first time I'm contributing something like this, so I don't really know how to do it. The header of the file is basically the same one as from `contrib/org-expiry.el', and I tried to keep a similar structure. Please don't hesitate to comment & criticize the code, I'm still learning my way around emacs-lisp and org-mode. Thanks, Alan --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=org-review-schedule.el Content-Transfer-Encoding: quoted-printable ;;; org-review-schedule.el --- schedule reviews for Org entries ;; ;; Copyright 2007-2014 Free Software Foundation, Inc. ;; ;; Author: Alan Schmitt ;; Version: 0.1 ;; Keywords: org review ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 3, or (at your option) ;; any later version. ;; ;; This program is distributed in the hope that it will be useful, but ;; WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;; General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs. If not, see . ;; ;;; Commentary: ;; ;; This allows to schedule reviews of org entries. ;; ;; Entries will be scheduled for review only if their LAST_REVIEW ;; property is set. The next review date is computed from the ;; LAST_REVIEW property and the REVIEW_DELAY period, such as "+1m". If ;; REVIEW_DELAY is absent, a default period is used. Note that the ;; LAST_REVIEW property is not considered as inherited, but REVIEW_DELAY ;; is, allowing to set it for whole subtrees. ;; ;; Checking of review dates is done through an agenda view, using the ;; `org-review-schedule-skip' skipping function. This function is based ;; on `org-review-schedule-toreview-p', that returns `nil' if no review ;; is necessary (no review planned or it happened recently), otherwise ;; it returns the date the review was first necessary (LAST_REVIEW + ;; REVIEW_DELAY if it is in the past). ;; ;; When the entry is marked as reviewed, the LAST_REVIEW date is set to ;; the current date. The function ;; `org-review-schedule-insert-last-review' may be used for this. ;; ;; Example use. ;;=20 ;; 1 - To display the things to review. ;;=20 ;; (setq org-agenda-custom-commands (quote ( ... ("R" "Review ;; projects" tags-todo "-CANCELLED/" ;; ((org-agenda-overriding-header "Reviews Scheduled") ;; (org-agenda-skip-function 'org-review-schedule-skip) ;; (org-agenda-cmp-user-defined 'org-review-schedule-compare) ;; (org-agenda-sorting-strategy '(user-defined-down)))) ... ))) ;; ;; 2 - To set a key binding to review from the agenda ;; ;; (add-hook 'org-agenda-mode-hook (lambda () (local-set-key (kbd "C-c ;; C-r") 'org-review-schedule-insert-last-review))) ;; ;; 3 - To find projects or single tasks with no last review date. ;;=20 ;; To find the projects (tasks with sub tasks) or single tasks that have ;; no review, you can use the following: (some is taken from ;; http://doc.norang.ca/org-mode.html#Projects) ;;=20 ;; (tags-todo "-CANCELLED/" ((org-agenda-overriding-header "Missing ;;Last Review") (org-agenda-skip-function ;;'as/skip-non-project-non-single-no-review))) ;;=20 ;; (defun bh/is-project-p () "Any task with a todo keyword subtask" ;;(save-restriction (widen) (let ((has-subtask) (subtree-end ;;(save-excursion (org-end-of-subtree t))) (is-a-task (member (nth ;;2 (org-heading-components)) org-todo-keywords-1))) (save-excursion ;;(forward-line 1) (while (and (not has-subtask) (< (point) subtree-end) ;;(re-search-forward "^\*+ " subtree-end t)) (when (member ;;(org-get-todo-state) org-todo-keywords-1) (setq has-subtask t)))) (and ;;is-a-task has-subtask)))) ;;=20 ;; (defun bh/is-project-subtree-p () "Any task with a todo keyword that ;; is in a project subtree. Callers of this function already widen ;; the buffer view." (let ((task (save-excursion ;; (org-back-to-heading 'invisible-ok) (point)))) (save-excursion ;; (bh/find-project-task) (if (equal (point) task) nil t)))) ;;=20 ;; (defun bh/is-task-p () "Any task with a todo keyword and no ;;subtask" (save-restriction (widen) (let ((has-subtask) (subtree-end ;;(save-excursion (org-end-of-subtree t))) (is-a-task (member (nth ;;2 (org-heading-components)) org-todo-keywords-1))) (save-excursion ;;(forward-line 1) (while (and (not has-subtask) (< (point) subtree-end) ;;(re-search-forward "^\*+ " subtree-end t)) (when (member ;;(org-get-todo-state) org-todo-keywords-1) (setq has-subtask t)))) (and ;;is-a-task (not has-subtask))))) ;;=20 ;; (defun as/skip-non-project-non-single-no-review () "Skip non ;; projects, non single tasks, and those without last review ;; marker." (save-restriction (widen) (let ((next-headline ;; (save-excursion (or (outline-next-heading) (point-max))))) (cond ;; ((bh/is-project-p) (when (org-review-schedule-last-review-prop) ;; next-headline)) ((and (bh/is-task-p) (not ;; (bh/is-project-subtree-p))) (when ;; (org-review-schedule-last-review-prop) next-headline)) (t ;; next-headline))))) ;;=20 ;;; Code: ;;; User variables: (defgroup org-review-schedule nil "Org review scheduling." :tag "Org Review Schedule" :group 'org) (defcustom org-review-schedule-inactive-timestamps t "Insert inactive timestamps for last review properties." :type 'boolean :group 'org-review-schedule) (defcustom org-review-schedule-last-property-name "LAST_REVIEW" "The name of the property for the date of the last review." :type 'string :group 'org-review-schedule) (defcustom org-review-schedule-delay-property-name "REVIEW_DELAY" "The name of the property for setting the delay before the next review." :type 'string :group 'org-review-schedule) (defcustom org-review-schedule-delay "+1m" "Time span between the date of last review and the next one. The default value for this variable (\"+1m\") means that entries will be marked for review one month after their last review. If the review delay cannot be retrieved from the entry or the subtree above, this delay is used." :type 'string :group 'org-review-schedule) ;;; Functions: (defun org-review-schedule-last-planned (last delay) "Computes the next planned review, given the LAST review date (in string format) and the review DELAY (in string format)." (let* ((lt (org-read-date nil t last)) (ct (current-time))) (time-add lt (time-subtract (org-read-date nil t delay) ct)))) (defun org-review-schedule-last-review-prop () "Return the value of the last review property of the current headline." (let ((lr-prop org-review-schedule-last-property-name)) (org-entry-get (point) lr-prop))) (defun org-review-schedule-toreview-p () "Check if the entry at point should be marked for review. Return nil if the entry does not need to be reviewed. Otherwise return the number of days between the past planned review date and today. If there is no last review date, return nil. If there is no review delay period, use `org-review-schedule-delay'." (let* ((lr-prop org-review-schedule-last-property-name) (lp (org-entry-get (point) lr-prop))) (when lp=20 (let* ((dr-prop org-review-schedule-delay-property-name) (dr (or (org-entry-get (point) dr-prop t)=20 org-review-schedule-delay)) (nt (org-review-schedule-last-planned lp dr)) ) (if (time-less-p nt (current-time)) nt))))) (defun org-review-schedule-insert-last-review (&optional prompt) "Insert the current date as last review. If prefix argument: prompt the user for the date." (interactive "P") (let* ((ts (if prompt (concat "<" (org-read-date) ">") (format-time-string (car org-time-stamp-formats))))) (save-excursion (org-entry-put (if (equal (buffer-name) org-agenda-buffer-name) (or (org-get-at-bol 'org-marker) (org-agenda-error)) (point)) org-review-schedule-last-property-name (if org-review-schedule-inactive-timestamps (concat "[" (substring ts 1 -1) "]") ts))))) (defun org-review-schedule-skip () "Skip entries that are not scheduled to be reviewed." (save-restriction (widen) (let ((next-headline (save-excursion (or (outline-next-heading) (point-max))))) (cond ((org-review-schedule-toreview-p) nil) (t next-headline))))) (defun org-review-schedule-compare (a b) "Compares the date of scheduled review for the two agenda entries, to be used with `org-agenda-cmp-user-defined'. Returns +1 if A has been scheduled for longer and -1 otherwise." (let* ((ma (or (get-text-property 1 'org-marker a) (get-text-property 1 'org-hd-marker a))) (mb (or (get-text-property 1 'org-marker b) (get-text-property 1 'org-hd-marker b))) (pal (org-entry-get ma org-review-schedule-last-property-name)) (pad (or (org-entry-get ma org-review-schedule-delay-property-name= t) org-review-schedule-delay)) (pbl (org-entry-get mb org-review-schedule-last-property-name)) (pbd (or (org-entry-get mb org-review-schedule-delay-property-name= t) org-review-schedule-delay)) (sa (org-review-schedule-last-planned pal pad)) (sb (org-review-schedule-last-planned pbl pbd))) (if (time-less-p sa sb) 1 -1))) (provide 'org-review-schedule) ;;; org-review-schedule.el ends here --=-=-=--