Update copyright year, Debian installation instructions, and fix broken cross reference.
[planner-el.git] / planner-timeclock-summary-proj.el
blob49d5f523a73e0e0d46bda0da9be896c0e3acae4e
1 ;;; planner-timeclock-summary-proj.el --- timeclock project for the Emacs planner
3 ;; Copyright (C) 2004, 2008 Pascal Quesseveur
4 ;; Parts copyright (C) 2005, 2008 Free Software Foundation, Inc.
5 ;; Parts copyright (C) 2005, 2008 Trent Buck
7 ;; Author: Pascal Quesseveur <quesseveur@abaksystemes.fr>
8 ;; Time-stamp: <2004-12-17 18:39>
9 ;; Description: Summary timeclock of a project
11 ;; This file is part of Planner. It is not part of GNU Emacs.
13 ;; Planner is free software; you can redistribute it and/or modify it
14 ;; under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation; either version 3, or (at your option)
16 ;; any later version.
18 ;; Planner is distributed in the hope that it will be useful, but
19 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 ;; General Public License for more details.
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with Planner; see the file COPYING. If not, write to the
25 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
26 ;; Boston, MA 02110-1301, USA.
28 ;;; Commentary:
30 ;; planner-timeclock-summary-proj.el produces timeclock reports for planner
31 ;; files. That package uses `timeclock-project-alist' and
32 ;; `timeclock-seconds-to-string' from timeclock.
34 ;; To use, call `planner-timeclock-summary-proj-current' from a
35 ;; project page. The report is inserted at the current position in the
36 ;; buffer. The function `planner-timeclock-summary-proj-section' does
37 ;; the same but the report is inserted inside a section called "*
38 ;; Report".
40 ;; You might want to add
42 ;; (planner-timeclock-summary-proj-insinuate)
44 ;; to your planner config, which will cause the timeclock summary to
45 ;; be updated every time a Planner file is written.
47 ;;; Contributors
49 ;; Trent Buck overhauled the code for Planner-Muse.
51 ;; Marko Schütz provided a bugfix.
53 ;;; CODE
55 (require 'planner-timeclock)
56 (require 'planner-timeclock-summary)
58 ;;; User variables
60 ;;; Code:
61 (defcustom planner-timeclock-workdays-per-week 5
62 "Number of work days per week."
63 :type 'integer
64 :group 'planner-timeclock-summary)
66 (defcustom planner-timeclock-workhours-per-day 8
67 "Number of work hours per day."
68 :type 'integer
69 :group 'planner-timeclock-summary)
71 (defcustom planner-timeclock-summary-proj-header "Report"
72 "Header for the timeclock summary project section in a plan page."
73 :type 'string
74 :group 'planner-timeclock-summary)
76 ;;;; User variables stop here
78 (defun planner-timeclock-summary-proj-section ()
79 "Update the secion corresponding with `planner-timeclock-summary-proj-header'
80 in the current plan page.
81 The section is updated only if it exists."
82 (interactive)
83 (save-excursion
84 (save-restriction
85 (when (planner-narrow-to-section planner-timeclock-summary-proj-header)
86 (let ((thepage (planner-page-name)))
87 (when (and thepage
88 ;; Don't edit unless we're in a task page.
89 (not (string-match planner-date-regexp thepage)))
90 (delete-region (point-min) (point-max))
91 (insert "* " planner-timeclock-summary-proj-header "\n")
92 (planner-timeclock-summary-proj-report thepage)
93 (insert "\n\n")))))))
95 ;;;###autoload
96 (defun planner-timeclock-summary-proj-all ()
97 "Insert time report for all projects in the current buffer."
98 (interactive)
99 (planner-timeclock-summary-proj-report nil))
101 ;;;###autoload
102 (defun planner-timeclock-summary-proj-current ()
103 "Insert time report for the current project in the current buffer."
104 (interactive)
105 (let ((project (planner-page-name)))
106 (planner-timeclock-summary-proj-report project)))
108 ;;;###autoload
109 (defun planner-timeclock-summary-proj-report (project)
110 "Insert time report for PROJECT in the current buffer."
111 (interactive "sProject: ")
112 (insert (planner-timeclock-proj-build-report
113 (planner-timeclock-proj-make-alist project))))
115 (defun planner-timeclock-proj-build-report (proj-alist)
116 "Return time report for PROJ-ALIST.
117 The time report is formatted as
119 Duration || Task
120 duration | TASK 0
121 duration | TASK 1
122 duration | TOTAL."
123 (let ((str "\n Duration || Task")
124 (total-duration 0))
125 (while proj-alist
126 (let* ((proj-entry (car proj-alist))
127 (duration (cdr proj-entry)))
128 (setq str
129 (concat str "\n"
130 (format "%18s" (planner-timeclock-proj-seconds-to-string
131 duration))
132 " | "
133 (format "%s" (car proj-entry))))
134 (setq total-duration (+ duration total-duration))
135 (setq proj-alist (cdr proj-alist))))
136 (concat str
137 "\n"
138 (format "%18s" (planner-timeclock-proj-seconds-to-string
139 total-duration))
140 " | "
141 "TOTAL")))
143 (defun planner-timeclock-proj-make-alist (proj-name)
144 "Return an association list for PROJ-NAME.
145 Each association is of the form (TASK . DURATION). TASK is a task
146 name defined inside PROJ-NAME and DURATION is the total time
147 computed for that task. When PROJ-NAME is nil, each TASK is a
148 project name, and DURATION is the time spent on that project."
149 (let ((projects (planner-timeclock-proj-entries proj-name))
150 (proj-alist))
151 ;; Looping on project data. The project is made of tasks, and for each
152 ;; task there can be several time intervals.
153 (while projects
154 (let* ((entry (car projects))
155 (task (car entry))
156 (task-data (cdr entry))
157 (task-time 0))
158 ;; We compute the time spent on task TASK
159 (setq task-time 0)
160 (while task-data
161 (let ((task-entry (car task-data)))
162 (progn
163 (setq task-time (+ task-time
164 (timeclock-entry-length task-entry)))
165 (setq task-data (cdr task-data)))))
166 ;; compute the name
167 (if (string-match ": *" task)
168 (if (and (< (match-end 0) (length task)) proj-name)
169 (setq task (substring task (match-end 0)))
170 (setq task (substring task 0 (match-beginning 0)))))
171 ;; record the cons (task . time)
172 (if proj-alist
173 (let ((proj-time 0)
174 (proj-data-cell (assoc task proj-alist)))
175 (if proj-data-cell
176 (progn
177 (setq proj-time (cdr proj-data-cell))
178 (setcdr proj-data-cell (+ task-time proj-time)))
179 (add-to-list 'proj-alist (cons task task-time))))
180 (setq proj-alist (list (cons task task-time))))
181 (setq projects (cdr projects))))
182 proj-alist))
184 (defun planner-timeclock-proj-entries (proj-name)
185 "Return entries from `timeclock-project-alist' for PROJ-NAME.
186 If PROJ-NAME is nil, return `timeclock-project-alist'."
187 (let ((projects)
188 (entry-list (timeclock-project-alist)))
189 ;; Looping on entries. Each entry is in the form (PROJECT (TASKS
190 ;; DATA)). We keep only entries for which PROJECT-NAME matches
191 ;; PROJECT.
192 (if (not proj-name)
193 entry-list
194 (while entry-list
195 (let* ((proj (car entry-list))
196 (proj-entry-name (car proj)))
197 (if (and proj-name proj-entry-name
198 (string-match (concat "^\\[\\[" proj-name "\\]\\]")
199 proj-entry-name))
200 (if projects
201 (add-to-list 'projects proj)
202 (setq projects (list proj))))
203 (setq entry-list (cdr entry-list))))
204 projects)))
206 (defun planner-timeclock-summary-proj-insinuate ()
207 "Insinuate planner-timeclock-summary-proj with the rest of Planner."
208 (add-hook 'planner-mode-hook
209 (lambda ()
210 (add-hook
211 (if (boundp 'write-file-functions)
212 'write-file-functions
213 'write-file-hooks)
214 'planner-timeclock-summary-proj-section nil t))))
216 (defun planner-timeclock-proj-seconds-to-string (seconds)
217 "Convert the floating point number SECONDS to a string.
218 The string is in the form [WWw] [DDd] hh:ss."
219 (let* ((workday (* planner-timeclock-workhours-per-day 3600))
220 (days (floor (/ seconds workday)))
221 (secs (floor (- seconds (* days workday)))))
222 (if (> days planner-timeclock-workdays-per-week)
223 (let ((weeks (/ days planner-timeclock-workdays-per-week))
224 (dys (% days planner-timeclock-workdays-per-week)))
225 (if (> dys 0)
226 (format "%dw %dd %s" weeks dys
227 (timeclock-seconds-to-string secs))
228 (format "%dw %s" weeks
229 (timeclock-seconds-to-string secs))))
230 (if (> days 0)
231 (format "%dd %s" days
232 (timeclock-seconds-to-string secs))
233 (format "%s" (timeclock-seconds-to-string secs))))))
235 (provide 'planner-timeclock-summary-proj)
237 ;;; planner-timeclock-summary-proj.el ends here