Fix typo in last patch
[planner-el.git] / planner-report.el
blobfa0c8e3b09ae53dee31f1b1a454e210aa4a8eff4
1 ;;; planner-report.el --- create a timely status report based on planner pages
3 ;; Copyright (C) 2004, 2006 Andrew J. Korty <ajk@iu.edu>
4 ;; Parts copyright (C) 2005 Free Software Foundation, Inc.
6 ;; Emacs Lisp Archive Entry
7 ;; Filename: planner-report.el
8 ;; Version: $Revision: 1.11 $
9 ;; Keywords: hypermedia
10 ;; Author: Andrew J. Korty <ajk@iu.edu>
11 ;; Maintainer: Andrew J. Korty <ajk@iu.edu>
12 ;; Description: Create a timely status report based on planner pages
13 ;; URL: http://www.plannerlove.com/
14 ;; Compatibility: Emacs21
16 ;; This file is part of Planner. It is not part of GNU Emacs.
18 ;; Planner is free software; you can redistribute it and/or modify it
19 ;; under the terms of the GNU General Public License as published by
20 ;; the Free Software Foundation; either version 2, or (at your option)
21 ;; any later version.
23 ;; Planner is distributed in the hope that it will be useful, but
24 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
25 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26 ;; General Public License for more details.
28 ;; You should have received a copy of the GNU General Public License
29 ;; along with Planner; see the file COPYING. If not, write to the
30 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
31 ;; Boston, MA 02110-1301, USA.
33 ;;; Commentary:
35 ;; This library creates a status report for a given timespan. The
36 ;; report itself is just another Planner page in your planner
37 ;; directory. Once generated, it contains tasks and notes culled from
38 ;; active project pages. Tasks are only shown if they are incomplete
39 ;; or were completed within the timespan. Notes are shown if they
40 ;; were created during the timespan. Tasks and notes are grouped
41 ;; together under a heading for their corresponding project.
43 ;; The idea is you have one of these status reports generated
44 ;; periodically (say, every couple of weeks). Perhaps you use cron to
45 ;; run them automatically and then mail you a reminder that they've
46 ;; been done. Then you can edit the page, adding verbiage where it is
47 ;; needed and removing irrelevant items. This editing process is as
48 ;; easy as editing any other Planner page. Finally, you can publish
49 ;; the page along with the rest of your planner using M-x
50 ;; muse-project-publish.
52 ;; If you use planner-authz.el, you can tell planner-report.el only to
53 ;; consult project pages that a given list of users
54 ;; (planner-report-authz) can access when generating the report. For
55 ;; example, if you're preparing a status report for your boss, add
56 ;; yourself and him to planner-report-authz. The resulting status
57 ;; report will only contain information the two of you are supposed to
58 ;; have access to, and the report itself will be similarly restricted.
60 ;; * Startup and Usage
62 ;; Once this file is installed, add the following to your .emacs file:
64 ;; (require 'planner-report)
66 ;; Then you can use the following command to generate a status report:
68 ;; M-x planner-report-generate
70 ;; You will be prompted for a beginning and ending date, and then the
71 ;; status report will be generated. You can then edit it to your
72 ;; liking and publish it just like you would the rest of your planner.
74 ;; * Customization
76 ;; All user-serviceable options can be customized with
77 ;; M-x customize-group RET planner-report RET.
79 ;; * Contributors
81 ;; Seth Falcon (sethfalcon AT gmail DOT com) helped port this to
82 ;; Planner-Muse.
84 ;; Yann Hodique helped port this to Muse.
86 ;; $Id: planner-report.el,v 1.11 2004/12/06 02:39:00 ajk Exp $
88 ;;; Code:
90 (require 'muse)
91 (require 'planner)
93 (defgroup planner-report nil
94 "A planner.el extension for generating timely status reports
95 based on planner pages."
96 :group 'planner
97 :prefix "planner-report")
99 (defcustom planner-report-authz nil
100 "List of users a status report should be restricted to.
101 When status reports are generated, only planner pages accessible
102 by these users will be consulted, and the resulting status report
103 will be similarly restricted."
104 :group 'planner-report
105 :type '(repeat string))
107 (defcustom planner-report-pretty-print-plan-pages t
108 "If non-nil, pretty print plan pages.
109 If nil, leave page names as-is.
110 This requires muse-wiki.el to work properly."
111 :group 'planner-report
112 :type 'boolean)
114 (defcustom planner-report-remove-task-numbers t
115 "Remove task numbers when generating status reports."
116 :group 'planner-report
117 :type 'boolean)
119 (defcustom planner-report-replace-note-numbers "**"
120 "If non-nil, a string with which to replace note numbers when
121 generating status reports."
122 :group 'planner-report
123 :type 'string)
125 (defcustom planner-report-unfinished-offset nil
126 "If non-nil, the offset in days from the current date of
127 unfinished tasks to include in the status report. If nil,
128 include all unfinished tasks."
129 :group 'planner-report
130 :type '(choice (integer :tag "Number of days")
131 (const :tag "Include all unifinished tasks" nil)))
133 (defvar planner-report-version "$Revision: 1.11 $"
134 "Version of of planner-report.el.")
136 ;;;###autoload
137 (defun planner-report-generate (begin end)
138 "Generate a status report spanning a period from BEGIN to END.
139 BEGIN and END are in the format YYYY.MM.DD."
140 (interactive
141 (let ((planner-expand-name-favor-future-p
142 (or planner-expand-name-favor-future-p
143 planner-task-dates-favor-future-p)))
144 (list (planner-read-date "Start date")
145 (planner-read-date "End date"))))
146 (save-some-buffers nil (lambda () (planner-derived-mode-p 'planner-mode)))
147 (cd (planner-directory))
148 (let ((filename (concat "StatusReport" end)))
149 (when (and muse-file-extension (not (string= muse-file-extension "")))
150 (setq filename (concat filename "." muse-file-extension)))
151 (with-temp-buffer
152 (when planner-report-authz
153 (require 'planner-authz)
154 (insert "#authz "
155 (mapconcat 'identity planner-report-authz " ") "\n"))
156 (insert "#title Status report for " begin " to " end "\n")
157 (let* ((unsorted-pages
158 (if planner-report-authz
159 (planner-authz-file-alist planner-report-authz)
160 (planner-file-alist)))
161 (pages
162 (sort unsorted-pages (lambda (a b) (string< (car a) (car b)))))
163 notes tasks)
164 (while pages
165 (when (caar pages)
166 ;; Add only project pages, and skip other status reports
167 (unless (or (string-match "^StatusReport" (caar pages))
168 (string-match planner-date-regexp (caar pages)))
169 (with-temp-buffer
170 (with-planner
171 (insert-file-contents-literally (cdar pages))
172 (setq tasks
173 (planner-report-find-tasks (caar pages) begin end))
174 (setq notes
175 (planner-report-find-notes (caar pages) begin end)))))
176 ;; Insert a linked heading if we found anything
177 (if (or notes tasks)
178 (insert "\n* [[" (caar pages) "]["
179 (if (and planner-report-pretty-print-plan-pages
180 (fboundp 'muse-wiki-publish-pretty-title))
181 (muse-wiki-publish-pretty-title (caar pages))
182 (caar pages))
183 "]]\n\n"))
184 (when tasks
185 (insert tasks "\n\n")
186 (setq tasks nil))
187 (when notes
188 (insert notes "\n")
189 (setq notes nil)))
190 (setq pages (cdr pages))))
191 (write-file filename t))
192 (find-file filename)))
194 (defun planner-report-find-notes (page begin end)
195 "Find notes on PAGE that were created between BEGIN and END.
196 BEGIN and END are formatted as YYYY.MM.DD."
197 (goto-char (point-min))
198 (let (result)
199 (while (re-search-forward "^\\.#[0-9]+\\s-+" nil t)
200 (let ((note
201 (buffer-substring
202 (planner-line-beginning-position)
203 (save-excursion
204 ;; Find the end of this note (maybe EOF)
205 (re-search-forward "^\\(\\.#[0-9]+\\s-+\\|\\*\\*?\\s-+\\)"
206 nil 1)
207 (goto-char (planner-line-beginning-position))
208 (point))))
209 (info (planner-current-note-info)))
210 (when info
211 (let* ((link (planner-note-link info))
212 (date (if link (planner-link-base link))))
213 ;; Snarf if note is associated with a date that is in range
214 (and date
215 (not (string< date begin))
216 (not (string< end date))
217 (progn
218 (if planner-report-replace-note-numbers
219 (setq note
220 (planner-replace-regexp-in-string
221 "^\\.#[0-9]+"
222 planner-report-replace-note-numbers
223 note t t)))
224 (setq result (if result (concat note result) note))))))))
225 result))
227 (defun planner-report-find-tasks (page begin end)
228 "Find cancelled or completed tasks on PAGE with a date between
229 BEGIN and END and any unfinished tasks with a date constrained by
230 `planner-report-unfinished-offset'. BEGIN and END are formatted
231 as YYYY.MM.DD."
232 (goto-char (point-min))
233 (let (result)
234 (while (re-search-forward "^#[A-C]" nil t)
235 (let* ((task (buffer-substring (planner-line-beginning-position)
236 (planner-line-end-position)))
237 (info (planner-task-info-from-string page task)))
238 (when info
239 (let ((date (planner-task-date info)))
240 ;; If the task isn't cancelled nor completed and has a
241 ;; date less than or equal to planner-report-unfinished
242 ;; away, snarf. If it has been cancelled or completed and
243 ;; the date is in range, snarf.
244 (and date
245 (or (and (not (or (equal (planner-task-status info) "C")
246 (equal (planner-task-status info) "X")))
247 (or (null planner-report-unfinished-offset)
248 (not (string<
249 (planner-calculate-date-from-day-offset
250 (planner-date-to-filename
251 (decode-time (current-time)))
252 planner-report-unfinished-offset)
253 date))))
254 (and (not (string< date begin))
255 (not (string< end date))))
256 (progn
257 (if planner-report-remove-task-numbers
258 (setq task (planner-replace-regexp-in-string
259 "^\\(#[A-C]\\)\\([0-9]+ +\\)"
260 "\\1 " task t nil)))
261 (setq result
262 (if result (concat result "\n" task) task))))))))
263 result))
265 (provide 'planner-report)
267 ;;; planner-report.el ends here