emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
* [PATCH] New clocktable-feature: Structure clocktable by tags rather than by hierarchy
@ 2010-06-20 10:24 B Grobauer
  2010-06-20 10:52 ` Carsten Dominik
  0 siblings, 1 reply; 5+ messages in thread
From: B Grobauer @ 2010-06-20 10:24 UTC (permalink / raw)
  To: Emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 7128 bytes --]

Hi,

at work I am required to specify what I have spent my time on by assigning
clocked time to one of several projects/accounts. To do so, the
clocktable-feature
of org-mode already is a tremendous help, but I found that I still had to
do quite a bit of manual work, because the structure of my org-files
does not correspond 1:1 with my projects.

The attached patch allows me to tag headings with project/account names
and then have clocktable generate a table that structures the clocked time
by these project names rather than by the hierarchical layout of the
org-files.

Here is what a clock-table for the attached clocktags_test.org
looks like without the new feature:


| L | Headline                            |        Time |        |      |
|---+-------------------------------------+-------------+--------+------|
|   | *Total time*                        |     *17:30* |        |      |
|---+-------------------------------------+-------------+--------+------|
|   |                                     | *FILE time* | *0:00* |      |
| 1 | Todos                               |        7:00 |        |      |
| 2 | DONE Unallocated Task               |             |   1:00 |      |
| 2 | DONE Some Task  for Project A       |             |   1:00 |      |
| 2 | DONE Some Task for Project B        |             |   1:00 |      |
| 2 | DONE Another Task for Project A     |             |   1:00 |      |
| 2 | DONE Antother Task for Project B    |             |   1:00 |      |
| 2 | DONE Yet another Task for Project A |             |   1:00 |      |
| 2 | DONE Yet antoher Task for Project B |             |   1:00 |      |
| 1 | Project A                           |        7:30 |        |      |
| 2 | Subproject A1                       |             |   4:30 |      |
| 3 | Task 1 for Subproject A1            |             |        | 2:00 |
| 3 | Task 2 for Subproject A1            |             |        | 1:00 |
| 3 | Task 3 for Subproject A1            |             |        | 1:00 |
| 2 | Subproject A2                       |             |   3:00 |      |
| 3 | Task 1 for Subproject A2            |             |        | 1:00 |
| 3 | Task 2 for Subproject A2            |             |        | 1:00 |
| 3 | Task 3 for Subproject A2            |             |        | 1:00 |
| 1 | Project B                           |        3:00 |        |      |
| 2 | Task 1 for Project B                |             |   1:00 |      |
| 2 | Task 2 for Project B                |             |   1:00 |      |
| 2 | Task 3 for Project B                |             |   1:00 |      |

Below is what it looks like with the new "clocktags" feature,
assuming that

- all tasks that pertain to project A have been tagged with
  "00Project_A" (possibly using tag inheritance)
- all tasks that pertain to project B have been tagged with
  "01Project_B" (possibly using tag inheritance)
- the clocktable is called with parameter ':clocktable "[0-9][0-9]"'
  where the parameter value specifies a regular expression which
  a tag has to match in order to be used for structuring
  the clocktable

| ClockTag    | L | Headline                              |    Time |
|-------------+---+---------------------------------------+---------|
|             |   | *Total time*                          | *17:30* |
|-------------+---+---------------------------------------+---------|
|-------------+---+---------------------------------------+---------|
| UNALLOCATED |   | *Total*                               |  *1:00* |
|-------------+---+---------------------------------------+---------|
|             | 2 | DONE Unallocated Task                 |    1:00 |
|-------------+---+---------------------------------------+---------|
| 00Project_A |   | *Total*                               | *10:30* |
|-------------+---+---------------------------------------+---------|
|             | 2 | DONE Some Task  for Project A         |    1:00 |
|             | 2 | DONE Another Task for Project A       |    1:00 |
|             | 2 | DONE Yet another Task for Project A   |    1:00 |
|             | 2 | Subproject A1                         |    0:30 |
|             | 3 | Task 1 for Subproject A1              |    1:00 |
|             | 4 | Subtask 1 of Task 1 for Subproject A1 |    1:00 |
|             | 3 | Task 2 for Subproject A1              |    1:00 |
|             | 3 | Task 3 for Subproject A1              |    1:00 |
|             | 3 | Task 1 for Subproject A2              |    1:00 |
|             | 3 | Task 2 for Subproject A2              |    1:00 |
|             | 3 | Task 3 for Subproject A2              |    1:00 |
|-------------+---+---------------------------------------+---------|
| 01Project_B |   | *Total*                               |  *6:00* |
|-------------+---+---------------------------------------+---------|
|             | 2 | DONE Some Task for Project B          |    1:00 |
|             | 2 | DONE Antother Task for Project B      |    1:00 |
|             | 2 | DONE Yet antoher Task for Project B   |    1:00 |
|             | 2 | Task 1 for Project B                  |    1:00 |
|             | 2 | Task 2 for Project B                  |    1:00 |
|             | 2 | Task 3 for Project B                  |    1:00 |

Note that the hierarchical structure of the table is gone --
it does not make sense here, because the times of
children are not necessarily added to the time of the parent:
the hierarchical structure does not have meaning anymore
as far as clocking is concerned. Having said that, of course
in many instances one will use the hierarchy by tagging
a top-level name with a project/account tag and then
use inheritance to have all children attributed to that project.

The feature also works for scope=agenda and pulls
together clocked times from all files for each "clocktag".

The patch is not a small one, because I had to factor
out the printing of the table into a function of its
own and construct the table information as an association
list rather than directly as a string. As a result, you now
also have the possibility to receive the collected information
of the clocktable as list structure when setting the parameter
"to_alist" to a non-nil value. This may be interesting if you
want to use the function org-dblock-write:clocktable from
another function rather than through the inteface for
dynamic blocks.

I have tried to extensively comment the changes I made
to org-clock.el in order to facilitate evaluation whether the
patch is fit for inclusion into the distribution.

The attached clocktags_test.org contains also examles
for using the step parameter and the output of clocktable
results as list structure for further use in other functions.

Let me close with expressing my deep gratitude to Carsten Dominik
and all contributors to orgmode for the fantastic orgmode
project: I have been using org-mode for two years now and
don't quite know how I managed without it before that...

Best regards,

Bernd

[-- Attachment #2: clocktags_test.org --]
[-- Type: application/vnd.lotus-organizer, Size: 31742 bytes --]

[-- Attachment #3: clocktags.patch --]
[-- Type: application/octet-stream, Size: 19242 bytes --]

diff --git a/lisp/org-clock.el b/lisp/org-clock.el
index 8477b2c..9b6953e 100644
--- a/lisp/org-clock.el
+++ b/lisp/org-clock.el
@@ -1374,6 +1374,7 @@ nil are excluded from the clock summation."
     (if (consp tend) (setq tend (org-float-time tend)))
     (remove-text-properties (point-min) (point-max)
                             '(:org-clock-minutes t
+                              :org-clock-h-minutes t ; bg: property for storing per-heading minutes
                               :org-clock-force-headline-inclusion t))
     (save-excursion
       (goto-char (point-max))
@@ -1417,6 +1418,8 @@ nil are excluded from the clock summation."
 	    (setq level (- (match-end 1) (match-beginning 1)))
 	    (when (or (> t1 0) (> (aref ltimes level) 0))
 	      (when (or headline-included headline-forced)
+                ;; bg: store clocked time of heading (without subheadings
+                (put-text-property (point) (point-at-eol) :org-clock-h-minutes t1)
                 (if headline-included
                     (loop for l from 0 to level do
                           (aset ltimes l (+ (aref ltimes l) t1))))
@@ -1736,14 +1739,60 @@ the currently selected interval size."
 	  (org-update-dblock)
 	  t)))))
 
+
+
+
+;; bg: helper function; used for maintaining assoc list mapping tags to clocked time
+(defun assoc-delete-all (key alist)
+  "Delete from ALIST all elements whose car is `eq' to KEY.
+   Return the modified alist.
+   Elements of ALIST that are not conses are ignored."
+  (while (and (consp (car alist))
+	      (equal (car (car alist)) key))
+    (setq alist (cdr alist)))
+  (let ((tail alist) tail-cdr)
+    (while (setq tail-cdr (cdr tail))
+      (if (and (consp (car tail-cdr))
+	       (equal (car (car tail-cdr)) key))
+	  (setcdr tail (cdr tail-cdr))
+	(setq tail tail-cdr))))
+  alist)
+
+
+
 (defun org-dblock-write:clocktable (params)
-  "Write the standard clocktable."
+  "Write the standard clocktable. If the parameter
+   to_alist is set to a non-nil value, instead of
+   printing the table, the following result is
+   returned:
+    (list tbl tag_times)
+   where
+   - tbl is a list of association lists of the following form:
+       (file . (Filename . Accumulated_Filetime)) (only if scope = agenda
+       (level . Level)
+       (tsp . Timestamp)
+       (title . Title)
+       (hlc . Emphasis character or "")
+       (tag . Clocktag or "")
+       (time . Clocked time 
+           cumulative adding up lower levels if clocktags is not set)
+   - tag_times is an association list mapping clocktags
+     to clocked time for each tag (if parameter clock_tags is non-nil)
+
+  If additionally the 'step' parameter is used, then
+  the returned association list maps each date string denoting the
+  start time of each step to the list structure as explained above. 
+  "
   (catch 'exit
     (let* ((hlchars '((1 . "*") (2 . "/")))
 	   (ins (make-marker))
 	   (total-time nil)
 	   (scope (plist-get params :scope))
 	   (tostring (plist-get  params :tostring))
+           ;; bg: parameter to_alist required for recursive call
+	   (to_alist (plist-get  params :to_alist))
+           ;; bg: Parameter for clock tags
+           (clocktags (plist-get params :clocktags))
 	   (multifile (plist-get  params :multifile))
 	   (header (plist-get  params :header))
 	   (maxlevel (or (plist-get params :maxlevel) 3))
@@ -1754,16 +1803,113 @@ the currently selected interval size."
 	   (te (plist-get params :tend))
 	   (block (plist-get params :block))
 	   (link (plist-get params :link))
+           ;; bg: added parameter tag_times to pass collected tag_times between recursive calls
+	   (tag_times (plist-get params :tag_times))
 	   (tags (plist-get params :tags))
 	   (matcher (if tags (cdr (org-make-tags-matcher tags))))
 	   ipos time p level hlc hdl tsp props content recalc formula pcol
-	   cc beg end pos tbl tbl1 range-text rm-file-column scope-is-list st)
+	   cc beg end pos tbl tbl1 range-text rm-file-column scope-is-list st
+           time_found)
       (setq org-clock-file-total-minutes nil)
+      (when clocktags 
+        ;; bg:
+        ;; if the clocktags feature is set, we have to look at all
+        ;; levels, because the time from the lower level is not
+        ;; summed-up for higher levels
+        (setq maxlevel 1000))
+
+      (defun print_table (tbl tag_times)
+        "Function to print the clocktable. It takes two argumens:
+         tbl is a list with elements that are an association
+         list of the following form:
+               (file . (Filename . Accumulated_Filetime))  (only, if scope = agenda)
+               (level . Level)
+               (tsp . Timestamp)
+               (title . Title)
+               (hlc . Emphasis character or "")
+               (tag . Clocktag or "")
+               (time . Clocked time 
+                        cumulative adding up lower levels if clocktags is not set)
+
+         tag_times is an association list mapping clocktags
+         to added time for the clocktag.
+
+         If tag_times is nil, the function uses performs the normal
+         way of printing the table; it is not nil, it structures the
+         table by clocktags.
+        "
+
+        (let (current_file 
+              (file_time 0) 
+              (alltags_time 0)
+              result 
+              hlc
+                           )
+          (if (not tag_times) 
+              ;; bg: we use original code from org-clock, wrapped
+              ;; into a loop operating on the table "tbl"
+              (progn
+                (loop for line in tbl do
+                      (setq hlc (cdr (assoc 'hlc line)))
+                      (when (not (equal (cadr (assoc 'file line)) current_file))
+                        (when current_file
+                          (push (concat "| " (file-name-nondirectory (or current_file "")) (if timestamp "|" "") "| |*FILE time*|*"
+                                        (org-minutes-to-hh:mm-string
+                                         file_time)
+                                        "*|")
+                                result)
+                          (push "|-" result))
+                        (setq current_file    (cadr (assoc 'file line))
+                              file_time (caddr (assoc 'file line))))
+                      
+                      (push (concat 
+                             (if (assoc 'file line) (format "| %s" (file-name-nondirectory current_file)) "")
+                             "| " (int-to-string (cdr (assoc 'level line)))
+                             "|" (if timestamp (concat (cdr (assoc 'tsp line)) "|") "")
+                             hlc (cdr  (assoc 'title line)) hlc " |"
+                             (make-string (1- (cdr (assoc 'level line))) ?|)
+                             (concat hlc (org-minutes-to-hh:mm-string (cdr (assoc 'time line))) hlc)
+                             " |" ) result)
+                    
+                      
+                      )
+                (push (concat "| " (file-name-nondirectory (or current_file "")) (if timestamp "|" "") "| |*FILE time*|*"
+                              (org-minutes-to-hh:mm-string
+                               file_time)
+                              "*|")
+                      result))
+            ;; bg: we sort the entries by tags
+            (setq tag_times (sort tag_times (lambda (y x) (string-lessp (car x) (car y)))))
+            (loop for tag_time in tag_times do
+                  (setq alltags_time (+ alltags_time (cdr tag_time)))
+                  (loop for line in tbl do
+                      (when (and (string-equal (cdr (assoc 'tag line)) (car tag_time)) (not (equal (cdr (assoc 'time line)) 0)))
+
+                        (push (concat 
+                               (if (assoc 'file line) (format "| %s" (file-name-nondirectory (cadr (assoc 'file line)))) "| ")
+                               "| " (int-to-string (cdr (assoc 'level line))) 
+                               "|" (if timestamp (concat (cdr (assoc 'tsp line)) "|") "")
+                               hlc (cdr  (assoc 'title line)) hlc " |"
+                               (concat hlc (org-minutes-to-hh:mm-string (cdr (assoc 'time line))) hlc)
+                               " |" ) result)))
+                  (push "|-" result)
+                  (push (concat "| " (if (equal "" (car tag_time)) "UNALLOCATED" (car tag_time)) (if timestamp "|" "") "| |*Total*|*"
+                                      (org-minutes-to-hh:mm-string
+                                       (cdr tag_time))
+                                      "*|")
+                              result)
+                  (push "|-" result)
+                  )
+            )
+          (mapconcat 'identity result "\n")
+          )
+        )
+      
       (when step
 	(unless (or block (and ts te))
 	  (error "Clocktable `:step' can only be used with `:block' or `:tstart,:end'"))
-	(org-clocktable-steps params)
-	(throw 'exit nil))
+	
+	(throw 'exit (org-clocktable-steps params)))
       (when block
 	(setq cc (org-clock-special-range block nil t)
 	      ts (car cc) te (nth 1 cc) range-text (nth 2 cc)))
@@ -1817,34 +1963,42 @@ the currently selected interval size."
 		 (scope 'agenda)
 		 (p1 (copy-sequence params))
 		 file)
-	    (setq p1 (plist-put p1 :tostring t))
+            ;; bg: for recursion we must collect the results
+            ;; rather than print the table and manipulate the
+            ;; the resulting string
+	    (setq p1 (plist-put p1 :to_alist t)) 
 	    (setq p1 (plist-put p1 :multifile t))
 	    (setq p1 (plist-put p1 :scope 'file))
 	    (org-prepare-agenda-buffers files)
 	    (while (setq file (pop files))
 	      (with-current-buffer (find-buffer-visiting file)
-		(setq tbl1 (org-dblock-write:clocktable p1))
+                (setq p1 (plist-put p1 :tag_times tag_times))
+		(setq tbl_tags (org-dblock-write:clocktable p1)
+                      tbl1 (car tbl_tags)
+                      tag_times (cadr tbl_tags))
 		(when tbl1
-		  (push (org-clocktable-add-file
-			 file
-			 (concat "| |*File time*|*"
-				 (org-minutes-to-hh:mm-string
-				  org-clock-file-total-minutes)
-				 "*|\n"
-				 tbl1)) tbl)
+                  ;; bg: augment each line with information about the file and total file-time
+                  (setq tbl
+                        (append
+                         (mapcar (lambda (x) (cons `(file . ,(list file org-clock-file-total-minutes)) x))
+                                 tbl1) 
+
+                         tbl))
+
 		  (setq total-time (+ (or total-time 0)
 				      org-clock-file-total-minutes))))))))
 	(goto-char pos)
 
 	(unless scope-is-list
-	  (org-clock-sum ts te
-			 (unless (null matcher)
-			   (lambda ()
-			     (let ((tags-list
-				    (org-split-string
-				     (or (org-entry-get (point) "ALLTAGS") "")
-				     ":")))
-			       (eval matcher)))))
+            (if tags 
+                (org-clock-sum ts te (lambda ()
+                                          (let ((tags-list
+                                                 (org-split-string
+                                                  (or (org-entry-get (point) "ALLTAGS") "")
+                                                  ":")))
+                                            (eval matcher))))
+              (org-clock-sum ts te )
+            )
 	  (goto-char (point-min))
 	  (setq st t)
 	  (while (or (and (bobp) (prog1 st (setq st nil))
@@ -1852,7 +2006,14 @@ the currently selected interval size."
 			  (setq p (point-min)))
 		     (setq p (next-single-property-change (point) :org-clock-minutes)))
 	    (goto-char p)
-	    (when (setq time (get-text-property p :org-clock-minutes))
+            (setq time_found
+                  ;; depending on whether clocktimes is set, we collect the
+                  ;; clocked time for the heading only or the cumulative time for
+                  ;; the heading
+                  (if clocktags (setq time (get-text-property p :org-clock-h-minutes))
+                    (setq time (get-text-property p :org-clock-minutes))))
+	    (when 
+                time_found
 	      (save-excursion
 		(beginning-of-line 1)
 		(when (and (looking-at (org-re "\\(\\*+\\)[ \t]+\\(.*?\\)\\([ \t]+:[[:alnum:]_@:]+:\\)?[ \t]*$"))
@@ -1874,18 +2035,51 @@ the currently selected interval size."
 			      (or (cdr (assoc "SCHEDULED" props))
 				  (cdr (assoc "TIMESTAMP" props))
 				  (cdr (assoc "DEADLINE" props))
-				  (cdr (assoc "TIMESTAMP_IA" props)))))
-		  (if (and (not multifile) (= level 1)) (push "|-" tbl))
-		  (push (concat
-			 "| " (int-to-string level) "|"
-			 (if timestamp (concat tsp "|") "")
-			 hlc hdl hlc " |"
-			 (make-string (1- level) ?|)
-			 hlc (org-minutes-to-hh:mm-string time) hlc
-			 " |") tbl))))))
-	(setq tbl (nreverse tbl))
-	(if tostring
-	    (if tbl (mapconcat 'identity tbl "\n") nil)
+				  (cdr (assoc "TIMESTAMP_IA" props))))
+                        ;; determine the clocktag (take the first one if
+                        ;; more than one match
+                        tag (if clocktags
+                                (progn (setq props (org-entry-properties (point)))
+                                       (or (car-safe 
+                                            (remove-if-not 
+                                             (lambda (x) (string-match (format "^%s.*" clocktags) x) )
+                                             (when (cdr (assoc "ALLTAGS" props))
+                                               (split-string (cdr (assoc "ALLTAGS" props)) ":" 1)
+                                               )))
+                                           ""))
+                              "")
+
+                        )
+                  (when clocktags
+                    (let (
+                          (accumulated_time (or (cdr-safe (assoc tag tag_times)) 0))
+                          )
+                      ;; bg: 
+                      ;; we build an association list that maps each clocktag
+                      ;; to the added clocked time. We need to delete old
+                      ;; entries rather than just consing new key-value pairs,
+                      ;; because we later want to sort with respect to tags
+                      (setq tag_times (cons `(,tag . ,(+ time accumulated_time)) 
+                                            (assoc-delete-all tag tag_times)))
+
+                      ))
+
+
+                  (push `(    (level . ,level)
+                              (tsp . ,(if timestamp tsp ""))
+                              (title . ,hdl)
+                              (hlc . ,hlc)
+                              (tag . ,tag)
+                              (time . ,time))
+                        tbl)
+
+                  )))))
+	(if (or tostring to_alist)
+            (progn
+              ;; bg:  we return the table, either printed or as alist
+              (if (and tbl tostring) (print_table tbl tag_times) 
+                (list tbl tag_times)))
+
 	  (goto-char ins)
 	  (insert-before-markers
 	   (or header
@@ -1897,23 +2091,17 @@ the currently selected interval size."
 		"]"
 		(if block (concat ", for " range-text ".") "")
 		"\n\n"))
-	   (if scope-is-list "|File" "")
+	   (if (or clocktags scope-is-list) (if clocktags "|ClockTag" "|File") "")
 	   "|L|" (if timestamp "Timestamp|" "") "Headline|Time|\n")
 	  (setq total-time (or total-time org-clock-file-total-minutes))
 	  (insert-before-markers
 	   "|-\n|"
-	   (if scope-is-list "|" "")
+	   (if (or clocktags scope-is-list) "|" "")
 	   (if timestamp "|Timestamp|" "|")
 	   "*Total time*| *"
 	   (org-minutes-to-hh:mm-string (or total-time 0))
 	   "*|\n|-\n")
-	  (setq tbl (delq nil tbl))
-	  (if (and (stringp (car tbl)) (> (length (car tbl)) 1)
-		   (equal (substring (car tbl) 0 2) "|-"))
-	      (pop tbl))
-	  (insert-before-markers (mapconcat
-				  'identity (delq nil tbl)
-				  (if scope-is-list "\n|-\n" "\n")))
+          (insert-before-markers (print_table tbl tag_times))
 	  (backward-delete-char 1)
 	  (if (setq formula (plist-get params :formula))
 	      (cond
@@ -1953,13 +2141,14 @@ the currently selected interval size."
 
 (defun org-clocktable-steps (params)
   (let* ((p1 (copy-sequence params))
+         (to_alist (plist-get p1 :to_alist))
 	 (ts (plist-get p1 :tstart))
 	 (te (plist-get p1 :tend))
 	 (step0 (plist-get p1 :step))
 	 (step (cdr (assoc step0 '((day . 86400) (week . 604800)))))
 	 (stepskip0 (plist-get p1 :stepskip0))
 	 (block (plist-get p1 :block))
-	 cc range-text step-time)
+	 cc range-text step-time result)
     (when block
       (setq cc (org-clock-special-range block nil t)
 	    ts (car cc) te (nth 1 cc) range-text (nth 2 cc)))
@@ -1970,37 +2159,38 @@ the currently selected interval size."
     (setq p1 (plist-put p1 :header ""))
     (setq p1 (plist-put p1 :step nil))
     (setq p1 (plist-put p1 :block nil))
+    (setq p1 (plist-put p1 :to_alist to_alist))
     (while (< ts te)
-      (or (bolp) (insert "\n"))
+      (or to_alist (bolp) (insert "\n"))
       (setq p1 (plist-put p1 :tstart (format-time-string
 				      (org-time-stamp-format nil t)
 				      (seconds-to-time ts))))
       (setq p1 (plist-put p1 :tend (format-time-string
 				    (org-time-stamp-format nil t)
 				    (seconds-to-time (setq ts (+ ts step))))))
-      (insert "\n" (if (eq step0 'day) "Daily report: " "Weekly report starting on: ")
-	      (plist-get p1 :tstart) "\n")
-      (setq step-time (org-dblock-write:clocktable p1))
-      (re-search-forward "#\\+END:")
-      (when (and (equal step-time 0) stepskip0)
-	;; Remove the empty table
-	(delete-region (point-at-bol)
-		       (save-excursion
-			 (re-search-backward "^\\(Daily\\|Weekly\\) report" nil t)
-			 (point))))
-      (end-of-line 0))))
-
-(defun org-clocktable-add-file (file table)
-  (if table
-      (let ((lines (org-split-string table "\n"))
-	    (ff (file-name-nondirectory file)))
-	(mapconcat 'identity
-		   (mapcar (lambda (x)
-			     (if (string-match org-table-dataline-regexp x)
-				 (concat "|" ff x)
-			       x))
-			   lines)
-		   "\n"))))
+      (when (not to_alist)
+        (insert "\n" (if (eq step0 'day) "Daily report: " "Weekly report starting on: ")
+	      (plist-get p1 :tstart) "\n"))
+      ;; bg: 
+      ;; return value is either an association list with clocking info
+      ;; (if to_alist is set) or the total time of the step (if to_alist
+      ;; is not set)
+      (setq step-time_or_alist (org-dblock-write:clocktable p1))
+      (if to_alist 
+          (setq result (cons (cons (plist-get p1 :tstart) step-time_or_alist) result))
+        (re-search-forward "#\\+END:")
+        (when (and (equal step-time 0) stepskip0)
+          ;; Remove the empty table
+          (delete-region (point-at-bol)
+                         (save-excursion
+                           (re-search-backward "^\\(Daily\\|Weekly\\) report" nil t)
+                           (point)))))
+        (when (not to_alist) (end-of-line 0))
+        )
+    result
+    )
+  )
+
 
 (defun org-clock-time% (total &rest strings)
   "Compute a time fraction in percent.

[-- Attachment #4: Type: text/plain, Size: 201 bytes --]

_______________________________________________
Emacs-orgmode mailing list
Please use `Reply All' to send replies to the list.
Emacs-orgmode@gnu.org
http://lists.gnu.org/mailman/listinfo/emacs-orgmode

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH] New clocktable-feature: Structure clocktable by tags rather than by hierarchy
  2010-06-20 10:24 [PATCH] New clocktable-feature: Structure clocktable by tags rather than by hierarchy B Grobauer
@ 2010-06-20 10:52 ` Carsten Dominik
  2010-06-20 11:26   ` B Grobauer
  2013-08-26  5:18   ` [PATCH] New clocktable-feature: Structure clocktable bytags " Jeff Kowalczyk
  0 siblings, 2 replies; 5+ messages in thread
From: Carsten Dominik @ 2010-06-20 10:52 UTC (permalink / raw)
  To: B Grobauer; +Cc: Emacs-orgmode

Hi Bernd,

wow, if this works as advertised, this is great.

Does your mechanism see inherited tags?

You need to sign the papers with the FSF for this patch.  Are
you willing to do this?

- Carsten

On Jun 20, 2010, at 12:24 PM, B Grobauer wrote:

> Hi,
>
> at work I am required to specify what I have spent my time on by  
> assigning
> clocked time to one of several projects/accounts. To do so, the
> clocktable-feature
> of org-mode already is a tremendous help, but I found that I still  
> had to
> do quite a bit of manual work, because the structure of my org-files
> does not correspond 1:1 with my projects.
>
> The attached patch allows me to tag headings with project/account  
> names
> and then have clocktable generate a table that structures the  
> clocked time
> by these project names rather than by the hierarchical layout of the
> org-files.
>
> Here is what a clock-table for the attached clocktags_test.org
> looks like without the new feature:
>
>
> | L | Headline                            |        Time |         
> |      |
> |---+-------------------------------------+-------------+-------- 
> +------|
> |   | *Total time*                        |     *17:30* |         
> |      |
> |---+-------------------------------------+-------------+-------- 
> +------|
> |   |                                     | *FILE time* | *0:00*  
> |      |
> | 1 | Todos                               |        7:00 |         
> |      |
> | 2 | DONE Unallocated Task               |             |   1:00  
> |      |
> | 2 | DONE Some Task  for Project A       |             |   1:00  
> |      |
> | 2 | DONE Some Task for Project B        |             |   1:00  
> |      |
> | 2 | DONE Another Task for Project A     |             |   1:00  
> |      |
> | 2 | DONE Antother Task for Project B    |             |   1:00  
> |      |
> | 2 | DONE Yet another Task for Project A |             |   1:00  
> |      |
> | 2 | DONE Yet antoher Task for Project B |             |   1:00  
> |      |
> | 1 | Project A                           |        7:30 |         
> |      |
> | 2 | Subproject A1                       |             |   4:30  
> |      |
> | 3 | Task 1 for Subproject A1            |             |        |  
> 2:00 |
> | 3 | Task 2 for Subproject A1            |             |        |  
> 1:00 |
> | 3 | Task 3 for Subproject A1            |             |        |  
> 1:00 |
> | 2 | Subproject A2                       |             |   3:00  
> |      |
> | 3 | Task 1 for Subproject A2            |             |        |  
> 1:00 |
> | 3 | Task 2 for Subproject A2            |             |        |  
> 1:00 |
> | 3 | Task 3 for Subproject A2            |             |        |  
> 1:00 |
> | 1 | Project B                           |        3:00 |         
> |      |
> | 2 | Task 1 for Project B                |             |   1:00  
> |      |
> | 2 | Task 2 for Project B                |             |   1:00  
> |      |
> | 2 | Task 3 for Project B                |             |   1:00  
> |      |
>
> Below is what it looks like with the new "clocktags" feature,
> assuming that
>
> - all tasks that pertain to project A have been tagged with
>  "00Project_A" (possibly using tag inheritance)
> - all tasks that pertain to project B have been tagged with
>  "01Project_B" (possibly using tag inheritance)
> - the clocktable is called with parameter ':clocktable "[0-9][0-9]"'
>  where the parameter value specifies a regular expression which
>  a tag has to match in order to be used for structuring
>  the clocktable
>
> | ClockTag    | L | Headline                              |    Time |
> |-------------+---+---------------------------------------+---------|
> |             |   | *Total time*                          | *17:30* |
> |-------------+---+---------------------------------------+---------|
> |-------------+---+---------------------------------------+---------|
> | UNALLOCATED |   | *Total*                               |  *1:00* |
> |-------------+---+---------------------------------------+---------|
> |             | 2 | DONE Unallocated Task                 |    1:00 |
> |-------------+---+---------------------------------------+---------|
> | 00Project_A |   | *Total*                               | *10:30* |
> |-------------+---+---------------------------------------+---------|
> |             | 2 | DONE Some Task  for Project A         |    1:00 |
> |             | 2 | DONE Another Task for Project A       |    1:00 |
> |             | 2 | DONE Yet another Task for Project A   |    1:00 |
> |             | 2 | Subproject A1                         |    0:30 |
> |             | 3 | Task 1 for Subproject A1              |    1:00 |
> |             | 4 | Subtask 1 of Task 1 for Subproject A1 |    1:00 |
> |             | 3 | Task 2 for Subproject A1              |    1:00 |
> |             | 3 | Task 3 for Subproject A1              |    1:00 |
> |             | 3 | Task 1 for Subproject A2              |    1:00 |
> |             | 3 | Task 2 for Subproject A2              |    1:00 |
> |             | 3 | Task 3 for Subproject A2              |    1:00 |
> |-------------+---+---------------------------------------+---------|
> | 01Project_B |   | *Total*                               |  *6:00* |
> |-------------+---+---------------------------------------+---------|
> |             | 2 | DONE Some Task for Project B          |    1:00 |
> |             | 2 | DONE Antother Task for Project B      |    1:00 |
> |             | 2 | DONE Yet antoher Task for Project B   |    1:00 |
> |             | 2 | Task 1 for Project B                  |    1:00 |
> |             | 2 | Task 2 for Project B                  |    1:00 |
> |             | 2 | Task 3 for Project B                  |    1:00 |
>
> Note that the hierarchical structure of the table is gone --
> it does not make sense here, because the times of
> children are not necessarily added to the time of the parent:
> the hierarchical structure does not have meaning anymore
> as far as clocking is concerned. Having said that, of course
> in many instances one will use the hierarchy by tagging
> a top-level name with a project/account tag and then
> use inheritance to have all children attributed to that project.
>
> The feature also works for scope=agenda and pulls
> together clocked times from all files for each "clocktag".
>
> The patch is not a small one, because I had to factor
> out the printing of the table into a function of its
> own and construct the table information as an association
> list rather than directly as a string. As a result, you now
> also have the possibility to receive the collected information
> of the clocktable as list structure when setting the parameter
> "to_alist" to a non-nil value. This may be interesting if you
> want to use the function org-dblock-write:clocktable from
> another function rather than through the inteface for
> dynamic blocks.
>
> I have tried to extensively comment the changes I made
> to org-clock.el in order to facilitate evaluation whether the
> patch is fit for inclusion into the distribution.
>
> The attached clocktags_test.org contains also examles
> for using the step parameter and the output of clocktable
> results as list structure for further use in other functions.
>
> Let me close with expressing my deep gratitude to Carsten Dominik
> and all contributors to orgmode for the fantastic orgmode
> project: I have been using org-mode for two years now and
> don't quite know how I managed without it before that...
>
> Best regards,
>
> Bernd
> < 
> clocktags_test 
> .org><clocktags.patch>_______________________________________________
> Emacs-orgmode mailing list
> Please use `Reply All' to send replies to the list.
> Emacs-orgmode@gnu.org
> http://lists.gnu.org/mailman/listinfo/emacs-orgmode

- Carsten

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] New clocktable-feature: Structure clocktable by tags rather than by hierarchy
  2010-06-20 10:52 ` Carsten Dominik
@ 2010-06-20 11:26   ` B Grobauer
  2010-06-21  9:10     ` Giovanni Ridolfi
  2013-08-26  5:18   ` [PATCH] New clocktable-feature: Structure clocktable bytags " Jeff Kowalczyk
  1 sibling, 1 reply; 5+ messages in thread
From: B Grobauer @ 2010-06-20 11:26 UTC (permalink / raw)
  To: Carsten Dominik; +Cc: Emacs-orgmode

Hi Carsten,

>
> wow, if this works as advertised, this is great.

I hope it does :) -- I did not find problems with the
featuers of the clocktable that I am using regularly.

Please let me know when you encounter problems
and I shall try to fix those.

>
> Does your mechanism see inherited tags?

Yes -- in the sample file, for example, tag inheritance
is used to mark all sub-nodes of the two top-level trees
for "Project A" and "Project B" as pertaining to
those projects, respectively.

>
> You need to sign the papers with the FSF for this patch.  Are
> you willing to do this?

Yes, certainly. I just sent the request and will let
you know when the process is through.

Thanks again for org-mode!

Best regards,

Bernd

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] New clocktable-feature: Structure clocktable by tags rather than by hierarchy
  2010-06-20 11:26   ` B Grobauer
@ 2010-06-21  9:10     ` Giovanni Ridolfi
  0 siblings, 0 replies; 5+ messages in thread
From: Giovanni Ridolfi @ 2010-06-21  9:10 UTC (permalink / raw)
  To: B Grobauer; +Cc: Emacs-orgmode, Carsten Dominik

B Grobauer <bgrobauer@googlemail.com> writes:

>> Does your mechanism see inherited tags?
>
> Yes -- in the sample file, for example, tag inheritance
> is used to mark all sub-nodes of the two top-level trees
> for "Project A" and "Project B" as pertaining to
> those projects, respectively.

Feature request: Is it possible to check also for properies 
and not only for tag?

we can add a special PROPERTY: CLOCKTAG

Rationale:
In my setup I have a remember template where I write the task I'm doing.
with some information e.g.:

** reading bibliography
   CLOCK: [2010-06-21 lun 09:24]
  :PROPERTIES:
  :ID: lab            
  :Type:expected
  :People: Giovanni
  :Location: computer
  :Connection: nil
  :CLOCKTAG:PV Project
  :Comment:  
  :END:

If the function checks also the :PROPERTIES: drawer and the existence of
the special PROPERTY: CLOCKTAG I can keep my setup and exploit this
marvellous new feature.

If it's too complicated I will use the tags ;-)

TIA

Giovanni

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] New clocktable-feature: Structure clocktable bytags rather than by hierarchy
  2010-06-20 10:52 ` Carsten Dominik
  2010-06-20 11:26   ` B Grobauer
@ 2013-08-26  5:18   ` Jeff Kowalczyk
  1 sibling, 0 replies; 5+ messages in thread
From: Jeff Kowalczyk @ 2013-08-26  5:18 UTC (permalink / raw)
  To: emacs-orgmode

Carsten Dominik <carsten.dominik <at> gmail.com> writes:

> 
> Hi Bernd,
> 
> wow, if this works as advertised, this is great.
> 
> Does your mechanism see inherited tags?
> 
> You need to sign the papers with the FSF for this patch.  Are
> you willing to do this?
> 
> - Carsten

Did any variation of this functionality eventually land in Org? I don't see 

I am looking for something similar to group time tracking by tag, and/or 
display task tags as a column in the clocktable.

Thanks,
Jeff

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2013-08-26  5:19 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-06-20 10:24 [PATCH] New clocktable-feature: Structure clocktable by tags rather than by hierarchy B Grobauer
2010-06-20 10:52 ` Carsten Dominik
2010-06-20 11:26   ` B Grobauer
2010-06-21  9:10     ` Giovanni Ridolfi
2013-08-26  5:18   ` [PATCH] New clocktable-feature: Structure clocktable bytags " Jeff Kowalczyk

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).