planner-appt: fix highlighting in plan pages
[planner-el.git] / planner-report.el
blob3826da25272da2e63c93d10e8aa0b1b0bc1c975c
1 ;;; planner-report.el --- create a timely status report based on planner pages
3 ;; Copyright (C) 2004 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 not part of GNU Emacs.
18 ;; This is free software; you can redistribute it and/or modify it under
19 ;; the terms of the GNU General Public License as published by the Free
20 ;; Software Foundation; either version 2, or (at your option) any later
21 ;; version.
23 ;; This is distributed in the hope that it will be useful, but WITHOUT
24 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
25 ;; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
26 ;; for more details.
28 ;; You should have received a copy of the GNU General Public License
29 ;; along with GNU Emacs; 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 ((pages (if planner-report-authz
158 (planner-authz-file-alist planner-report-authz)
159 (planner-file-alist)))
160 notes tasks)
161 (while pages
162 (when (caar pages)
163 ;; Add only project pages, and skip other status reports
164 (unless (or (string-match "^StatusReport" (caar pages))
165 (string-match planner-date-regexp (caar pages)))
166 (with-temp-buffer
167 (with-planner
168 (insert-file-contents-literally (cdar pages))
169 (setq tasks
170 (planner-report-find-tasks (caar pages) begin end))
171 (setq notes
172 (planner-report-find-notes (caar pages) begin end)))))
173 ;; Insert a linked heading if we found anything
174 (if (or notes tasks)
175 (insert "\n* [[" (caar pages) "]["
176 (if (and planner-report-pretty-print-plan-pages
177 (fboundp 'muse-wiki-publish-pretty-title))
178 (muse-wiki-publish-pretty-title (caar pages))
179 (caar pages))
180 "]]\n\n"))
181 (when tasks
182 (insert tasks "\n\n")
183 (setq tasks nil))
184 (when notes
185 (insert notes "\n")
186 (setq notes nil)))
187 (setq pages (cdr pages))))
188 (write-file filename t))
189 (find-file filename)))
191 (defun planner-report-find-notes (page begin end)
192 "Find notes on PAGE that were created between BEGIN and END.
193 BEGIN and END are formatted as YYYY.MM.DD."
194 (goto-char (point-min))
195 (let (result)
196 (while (re-search-forward "^\\.#[0-9]+\\s-+" nil t)
197 (let ((note
198 (buffer-substring
199 (planner-line-beginning-position)
200 (save-excursion
201 ;; Find the end of this note (maybe EOF)
202 (re-search-forward "^\\(\\.#[0-9]+\\s-+\\|\\*\\*?\\s-+\\)"
203 nil 1)
204 (goto-char (planner-line-beginning-position))
205 (point))))
206 (info (planner-current-note-info)))
207 (when info
208 (let* ((link (planner-note-link info))
209 (date (if link (planner-link-base link))))
210 ;; Snarf if note is associated with a date that is in range
211 (and date
212 (not (string< date begin))
213 (not (string< end date))
214 (progn
215 (if planner-report-replace-note-numbers
216 (setq note
217 (planner-replace-regexp-in-string
218 "^\\.#[0-9]+"
219 planner-report-replace-note-numbers
220 note t t)))
221 (setq result (if result (concat note result) note))))))))
222 result))
224 (defun planner-report-find-tasks (page begin end)
225 "Find cancelled or completed tasks on PAGE with a date between
226 BEGIN and END and any unfinished tasks with a date constrained by
227 `planner-report-unfinished-offset'. BEGIN and END are formatted
228 as YYYY.MM.DD."
229 (goto-char (point-min))
230 (let (result)
231 (while (re-search-forward "^#[A-C]" nil t)
232 (let* ((task (buffer-substring (planner-line-beginning-position)
233 (planner-line-end-position)))
234 (info (planner-task-info-from-string page task)))
235 (when info
236 (let ((date (planner-task-date info)))
237 ;; If the task isn't cancelled nor completed and has a
238 ;; date less than or equal to planner-report-unfinished
239 ;; away, snarf. If it has been cancelled or completed and
240 ;; the date is in range, snarf.
241 (and date
242 (or (and (not (or (equal (planner-task-status info) "C")
243 (equal (planner-task-status info) "X")))
244 (or (null planner-report-unfinished-offset)
245 (not (string<
246 (planner-calculate-date-from-day-offset
247 (planner-date-to-filename
248 (decode-time (current-time)))
249 planner-report-unfinished-offset)
250 date))))
251 (and (not (string< date begin))
252 (not (string< end date))))
253 (progn
254 (if planner-report-remove-task-numbers
255 (setq task (planner-replace-regexp-in-string
256 "^\\(#[A-C]\\)\\([0-9]+ +\\)"
257 "\\1 " task t nil)))
258 (setq result
259 (if result (concat result "\n" task) task))))))))
260 result))
262 (provide 'planner-report)
264 ;;; planner-report.el ends here