emacs-wiki-discuss
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[emacs-wiki-discuss] timeclock report by project


From: Pascal Quesseveur
Subject: [emacs-wiki-discuss] timeclock report by project
Date: Mon, 20 Dec 2004 09:52:22 +0100

Hello,

I have written the following to be able to support timeclock summary
in plan pages. It have written as a separate module from
planner-timeclock-summary, but perhaps it would be best to merge it
with planner-timeclock-summary.

It is my first attempt to write an elisp pacakage, so feel free to
comment. Sorry for my english.

===================================================================
;;; planner-timeclock-summary-proj.el --- timeclock project for the
;;; Emacs planner

;; Author: Pascal Quesseveur <address@hidden>
;; Time-stamp: <2004-12-17 18:39>
;; Description: Summary timeclock of a project

;;; Commentary:
;;
;; planner-timeclock-summary-proj.el produces timeclock reports for planner
;; files.  That package uses `timeclock-project-alist' and
;; `timeclock-seconds-to-string' from timeclock.

;;; To use, call `planner-timeclock-summary-proj-current' from a
;;; project page. The report is inserted at the current position in
;;; the buffer. The function
;;; `planner-timeclock-summary-proj-section' does the same but the
;;; report is inserted inside a section called "* Report".

;;; CODE

(require 'planner-timeclock)
(require 'planner-timeclock-summary)

;;; User variables

(defcustom planner-timeclock-workdays-per-week 5
  "Number of working days per week."
  :type 'integer
  :group 'planner-timeclock-summary)

(defcustom planner-timeclock-workhours-per-day 8
  "Number of working hours per day."
  :type 'integer
  :group 'planner-timeclock-summary)

;;;; User variables stop here

(defun planner-timeclock-summary-proj-section ()
  "Inserts the time report for the current project in the current
buffer inside a Report section."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (if (re-search-forward "^[*] Report" nil t)
        ;; A report section has been found in the buffer. We delete it.
        (progn
          (forward-line)
          ;; Delete lines until the next section or to end of buffer.
          ;; End of buffer is managed by the (catch throw)
          ;; construct to avoid problems when calling kill-line with
          ;; point at end of buffer ...
          (catch 'loop
            (while (not (looking-at "^[*] "))
              (if (eobp)
                  (throw 'loop nil)
                (kill-line)))))
      ;; section creation when it doesn't exist
      (end-of-buffer)
      (insert "\n\n* Report\n"))
    ;; insert the report in the buffer
    (planner-timeclock-summary-proj-current)
    (insert "\n\n")))

(defun planner-timeclock-summary-proj-all ()
  "Inserts the time report for the all the projects in the current
buffer."
  (interactive)
  (planner-timeclock-summary-proj-report nil))

(defun planner-timeclock-summary-proj-current ()
  "Inserts the time report for the current project in the current
buffer."
  (interactive)
  (let ((project (file-name-nondirectory buffer-file-name)))
    (planner-timeclock-summary-proj-report project)))

(defun planner-timeclock-summary-proj-report (project)
  "Inserts the time report for a project in the current buffer."
  (interactive "sProject: ")
  (insert (planner-timeclock-proj-build-report
           (planner-timeclock-proj-make-alist project))))

;;;

(defun planner-timeclock-proj-build-report (proj-alist)
  "Returns a string containning the time report formated as:

  TASK 0 | duration
  TASK 1 | duration
   TOTAL | duration."
  (let ((str)
        (total-duration 0))
    (while proj-alist
      (let* ((proj-entry (car proj-alist))
             (duration (cdr proj-entry)))
        (setq str (concat str "\n" (format "%8s" (car proj-entry)) " | "
                          (seconds-to-weekdays-string duration)))
        (setq total-duration (+ duration total-duration))
        (setq proj-alist (cdr proj-alist))))
    (concat str "\n" (format "%8s" "TOTAL") " | "
                      (seconds-to-weekdays-string total-duration) "\n")))

(defun planner-timeclock-proj-make-alist (proj-name)
  "Returns an association list where each association is a cons cell
TASK . DURATION. TASK is a task name defined inside PROJ-NAME and
DURATION is the total time computed for that task.  When PROJ-NAME
is nil, each TASK is a project name, and DURATION is the time spent on
that project."
  (let ((projects (planner-timeclock-proj-entries proj-name))
        (proj-alist))
    ;; Looping on project data. The project is made of tasks, and for each
    ;; task there can be several time intervals.
    (while projects
      (let* ((entry (car projects))
             (task (car entry))
             (task-data (cdr entry))
             (task-time 0))
        ;; We compute the time spent on task TASK
        (setq task-time 0)
        (while task-data
          (let ((task-entry (car task-data)))
            (progn
              (setq task-time (+ task-time
                                 (timeclock-entry-length task-entry)))
              (setq task-data (cdr task-data)))))
        ;; compute the name
        (if (string-match ": *" task)
            (if (and (< (match-end 0) (length task)) proj-name)
                (setq task (substring task (match-end 0)))
              (setq task (substring task 0 (match-beginning 0)))))
        
        ;; record the cons (task . time)
        (if proj-alist
            (let ((proj-time 0)
                  (proj-data-cell (assoc task proj-alist)))
              (if proj-data-cell
                  (progn
                    (setq proj-time (cdr proj-data-cell))
                    (setcdr proj-data-cell (+ task-time proj-time)))
              (add-to-list 'proj-alist (cons task task-time))))
          (setq proj-alist (list (cons task task-time))))
      (setq projects (cdr projects))))
    proj-alist))

(defun planner-timeclock-proj-entries (proj-name)
  "Returns a list containing entries from timeclock-project-alist
matching the project name PROJECT-NAME. If proj-name is nil returns
timeclock-project-alist."
  (let ((projects)
        (entry-list (timeclock-project-alist)))
    ;; Looping on entries. Each entry is in the form (PROJECT (TASKS
    ;; DATA)). We keep only entries for witch PROJECT-NAME matches
    ;; PROJECT.
    (if (not proj-name)
        entry-list
      (while entry-list
        (let* ((proj (car entry-list))
               (proj-entry-name (car proj)))
          (if (and proj-name
                   (string-match (concat "^" proj-name) proj-entry-name))
              (if projects
                  (add-to-list 'projects proj)
                (setq projects (list proj))))
          (setq entry-list (cdr entry-list))))
      projects)))

(defun seconds-to-weekdays-string (seconds)
  "Convert the floating point number SECONDS to a string.
The string is in the form [WWw] [DDd] hh:ss."
  (let* ((workday (* planner-timeclock-workhours-per-day 3600))
         (days (floor (/ seconds workday)))
         (secs (floor (- seconds (* days workday)))))
    (if (> days planner-timeclock-workdays-per-week)
        (let ((weeks (/ days planner-timeclock-workdays-per-week))
              (dys (% days planner-timeclock-workdays-per-week)))
          (if (> dys 0)
              (format "%dw %dd %s" weeks dys
                      (timeclock-seconds-to-string secs))
            (format "%dw %s" weeks
                    (timeclock-seconds-to-string secs))))
      (if (> days 0)
          (format "%dd %s" days
                  (timeclock-seconds-to-string secs))
        (format "%s" (timeclock-seconds-to-string secs))))))

;;; planner-timeclock-summary-proj.el ends here


-- 
Pascal Quesseveur, address@hidden





reply via email to

[Prev in Thread] Current Thread [Next in Thread]