Merged from mwolson@gnu.org--2006 (patch 41)
[planner-el.git] / planner-timeclock-summary-proj.el
blobfbe473a0d4a887d0263a3e5ef97f65de91d9474c
1 ;;; planner-timeclock-summary-proj.el --- timeclock project for the Emacs planner
3 ;; Copyright (C) 2004 Pascal Quesseveur
4 ;; Parts copyright (C) 2005 Free Software Foundation, Inc.
5 ;; Parts copyright (C) 2005 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 2, 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 ;;; CODE
53 (require 'planner-timeclock)
54 (require 'planner-timeclock-summary)
56 ;;; User variables
58 ;;; Code:
59 (defcustom planner-timeclock-workdays-per-week 5
60 "Number of work days per week."
61 :type 'integer
62 :group 'planner-timeclock-summary)
64 (defcustom planner-timeclock-workhours-per-day 8
65 "Number of work hours per day."
66 :type 'integer
67 :group 'planner-timeclock-summary)
69 (defcustom planner-timeclock-summary-proj-header "Report"
70 "Header for the timeclock summary project section in a plan page."
71 :type 'string
72 :group 'planner-timeclock-summary)
74 ;;;; User variables stop here
76 (defun planner-timeclock-summary-proj-section ()
77 "Update the secion corresponding with `planner-timeclock-summary-proj-header'
78 in the current plan page.
79 The section is updated only if it exists."
80 (interactive)
81 (save-excursion
82 (save-restriction
83 (when (planner-narrow-to-section planner-timeclock-summary-proj-header)
84 (let ((thepage (planner-page-name)))
85 (when (and thepage
86 ;; Don't edit unless we're in a task page.
87 (not (string-match planner-date-regexp thepage)))
88 (delete-region (point-min) (point-max))
89 (insert "* " planner-timeclock-summary-proj-header "\n")
90 (planner-timeclock-summary-proj-report thepage)
91 (insert "\n\n")))))))
93 ;;;###autoload
94 (defun planner-timeclock-summary-proj-all ()
95 "Insert time report for all projects in the current buffer."
96 (interactive)
97 (planner-timeclock-summary-proj-report nil))
99 ;;;###autoload
100 (defun planner-timeclock-summary-proj-current ()
101 "Insert time report for the current project in the current buffer."
102 (interactive)
103 (let ((project (planner-page-name)))
104 (planner-timeclock-summary-proj-report project)))
106 ;;;###autoload
107 (defun planner-timeclock-summary-proj-report (project)
108 "Insert time report for PROJECT in the current buffer."
109 (interactive "sProject: ")
110 (insert (planner-timeclock-proj-build-report
111 (planner-timeclock-proj-make-alist project))))
113 (defun planner-timeclock-proj-build-report (proj-alist)
114 "Return time report for PROJ-ALIST.
115 The time report is formatted as
117 Duration || Task
118 duration | TASK 0
119 duration | TASK 1
120 duration | TOTAL."
121 (let ((str "\n Duration || Task")
122 (total-duration 0))
123 (while proj-alist
124 (let* ((proj-entry (car proj-alist))
125 (duration (cdr proj-entry)))
126 (setq str
127 (concat str "\n"
128 (format "%18s" (planner-timeclock-proj-seconds-to-string
129 duration))
130 " | "
131 (format "%s" (car proj-entry))))
132 (setq total-duration (+ duration total-duration))
133 (setq proj-alist (cdr proj-alist))))
134 (concat str
135 "\n"
136 (format "%18s" (planner-timeclock-proj-seconds-to-string
137 total-duration))
138 " | "
139 "TOTAL")))
141 (defun planner-timeclock-proj-make-alist (proj-name)
142 "Return an association list for PROJ-NAME.
143 Each association is of the form (TASK . DURATION). TASK is a task
144 name defined inside PROJ-NAME and DURATION is the total time
145 computed for that task. When PROJ-NAME is nil, each TASK is a
146 project name, and DURATION is the time spent on that project."
147 (let ((projects (planner-timeclock-proj-entries proj-name))
148 (proj-alist))
149 ;; Looping on project data. The project is made of tasks, and for each
150 ;; task there can be several time intervals.
151 (while projects
152 (let* ((entry (car projects))
153 (task (car entry))
154 (task-data (cdr entry))
155 (task-time 0))
156 ;; We compute the time spent on task TASK
157 (setq task-time 0)
158 (while task-data
159 (let ((task-entry (car task-data)))
160 (progn
161 (setq task-time (+ task-time
162 (timeclock-entry-length task-entry)))
163 (setq task-data (cdr task-data)))))
164 ;; compute the name
165 (if (string-match ": *" task)
166 (if (and (< (match-end 0) (length task)) proj-name)
167 (setq task (substring task (match-end 0)))
168 (setq task (substring task 0 (match-beginning 0)))))
169 ;; record the cons (task . time)
170 (if proj-alist
171 (let ((proj-time 0)
172 (proj-data-cell (assoc task proj-alist)))
173 (if proj-data-cell
174 (progn
175 (setq proj-time (cdr proj-data-cell))
176 (setcdr proj-data-cell (+ task-time proj-time)))
177 (add-to-list 'proj-alist (cons task task-time))))
178 (setq proj-alist (list (cons task task-time))))
179 (setq projects (cdr projects))))
180 proj-alist))
182 (defun planner-timeclock-proj-entries (proj-name)
183 "Return entries from `timeclock-project-alist' for PROJ-NAME.
184 If PROJ-NAME is nil, return `timeclock-project-alist'."
185 (let ((projects)
186 (entry-list (timeclock-project-alist)))
187 ;; Looping on entries. Each entry is in the form (PROJECT (TASKS
188 ;; DATA)). We keep only entries for witch PROJECT-NAME matches
189 ;; PROJECT.
190 (if (not proj-name)
191 entry-list
192 (while entry-list
193 (let* ((proj (car entry-list))
194 (proj-entry-name (car proj)))
195 (if (and proj-name
196 (string-match (concat "^\\[\\[" proj-name "\\]\\]")
197 proj-entry-name))
198 (if projects
199 (add-to-list 'projects proj)
200 (setq projects (list proj))))
201 (setq entry-list (cdr entry-list))))
202 projects)))
204 (defun planner-timeclock-summary-proj-insinuate ()
205 "Insinuate planner-timeclock-summary-proj with the rest of Planner."
206 (add-hook 'planner-mode-hook
207 (lambda ()
208 (add-hook
209 (if (boundp 'write-file-functions)
210 'write-file-functions
211 'write-file-hooks)
212 'planner-timeclock-summary-proj-section nil t))))
214 (defun planner-timeclock-proj-seconds-to-string (seconds)
215 "Convert the floating point number SECONDS to a string.
216 The string is in the form [WWw] [DDd] hh:ss."
217 (let* ((workday (* planner-timeclock-workhours-per-day 3600))
218 (days (floor (/ seconds workday)))
219 (secs (floor (- seconds (* days workday)))))
220 (if (> days planner-timeclock-workdays-per-week)
221 (let ((weeks (/ days planner-timeclock-workdays-per-week))
222 (dys (% days planner-timeclock-workdays-per-week)))
223 (if (> dys 0)
224 (format "%dw %dd %s" weeks dys
225 (timeclock-seconds-to-string secs))
226 (format "%dw %s" weeks
227 (timeclock-seconds-to-string secs))))
228 (if (> days 0)
229 (format "%dd %s" days
230 (timeclock-seconds-to-string secs))
231 (format "%s" (timeclock-seconds-to-string secs))))))
233 (provide 'planner-timeclock-summary-proj)
235 ;;; planner-timeclock-summary-proj.el ends here