emacs-orgmode
[Top][All Lists]
Advanced

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

[Orgmode] [PATCH] Add tags filter parameter to clock table


From: Adam Elliott
Subject: [Orgmode] [PATCH] Add tags filter parameter to clock table
Date: Tue, 23 Feb 2010 02:13:40 -0500
User-agent: Thunderbird 2.0.0.23 (Windows/20090812)

I have attached a git patch against master that implements a new
parameter to clock tables, "tags".  This parameter is a tags-query as a
string and is used to filter the headlines which are consulted when
building the clock table.

In my search of the archives to see if this feature already existed, I
found a reference here:
  http://article.gmane.org/gmane.emacs.orgmode/17304
suggesting it was difficult.  The patch is not so large, though, so
perhaps I am missing something.

My rationale in implementing this feature was to keep track of the
occasional task item that is not billable, yet still makes sense to
include in the overall project structure.  Of course I could just avoid
clocking the task item, or manually delete clock lines before generating
a report, but this feature reduces the chance for error; no doubt there
are other workflows enabled with this feature as well.  I don't make
significant use of tags myself, but I know many do.

In order to maintain a sensible report, headlines that don't match the
tag filter may be included if they have descendants that do.  Any time
clocked directly on non-matching headlines, however, is excluded.

Specifying even a simple filter noticeably slows down clock table
generation for non-toy reports, particularly for clock table reports
with :step.  If there is no filter, though, there is no degradation in
performance.

Tag filter syntax is the standard one, as described at:
  http://orgmode.org/manual/Matching-tags-and-properties.html
Only tags are considered at the moment, although I suspect querying
against all properties would be possible (if even slower).

Examples:

* development
  CLOCK: => 1:00
*** task 1
    CLOCK: => 1:00
*** task 2                                              :must:
***** task 2a
      CLOCK: => 1:00
***** task 2b                                           :mustnot:
      CLOCK: => 1:00

Note I am using an unconventional but legal(ish) clock format for
brevity.  Clock tables are also pruned to only relevant lines.

[1] #+BEGIN: clocktable
|   | *Total time* | *4:00* |      |      |
|---+--------------+--------+------+------|
| 1 | development  | 4:00   |      |      |
| 2 | task 1       |        | 1:00 |      |
| 2 | task 2       |        | 2:00 |      |
| 3 | task 2a      |        |      | 1:00 |
| 3 | task 2b      |        |      | 1:00 |

[2] #+BEGIN: clocktable :tags "must"
|   | *Total time* | *2:00* |      |      |
|---+--------------+--------+------+------|
| 1 | development  | 2:00   |      |      |
| 2 | task 2       |        | 2:00 |      |
| 3 | task 2a      |        |      | 1:00 |
| 3 | task 2b      |        |      | 1:00 |

[3] #+BEGIN: clocktable :tags "-mustnot"
|   | *Total time* | *3:00* |      |      |
|---+--------------+--------+------+------|
| 1 | development  | 3:00   |      |      |
| 2 | task 1       |        | 1:00 |      |
| 2 | task 2       |        | 1:00 |      |
| 3 | task 2a      |        |      | 1:00 |

[4] #+BEGIN: clocktable :tags "must-mustnot"
|   | *Total time* | *1:00* |      |      |
|---+--------------+--------+------+------|
| 1 | development  | 1:00   |      |      |
| 2 | task 2       |        | 1:00 |      |
| 3 | task 2a      |        |      | 1:00 |

[5] #+BEGIN: clocktable :tags "must+mustnot"
|   | *Total time* | *1:00* |      |      |
|---+--------------+--------+------+------|
| 1 | development  | 1:00   |      |      |
| 2 | task 2       |        | 1:00 |      |
| 3 | task 2b      |        |      | 1:00 |

As you can see, in examples 2, 4, and 5, the time clocked on
"development" itself is being removed.  Example 2 illustrates the effect
of tag inheritance.

Adam
From 80fb7b01c58989ed69b5a176784d71fe557fc4c6 Mon Sep 17 00:00:00 2001
From: Adam Elliott <address@hidden>
Date: Mon, 22 Feb 2010 00:57:22 -0500
Subject: [PATCH] Add tags filter parameter to clocktables.

---
 lisp/org-clock.el |   59 +++++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 46 insertions(+), 13 deletions(-)
 mode change 100644 => 100755 lisp/org-clock.el

diff --git a/lisp/org-clock.el b/lisp/org-clock.el
old mode 100644
new mode 100755
index e3866be..fb85380
--- a/lisp/org-clock.el
+++ b/lisp/org-clock.el
@@ -1289,10 +1289,13 @@ With prefix arg SELECT, offer recently clocked tasks 
for selection."
   "Holds the file total time in minutes, after a call to `org-clock-sum'.")
 (make-variable-buffer-local 'org-clock-file-total-minutes)
 
-(defun org-clock-sum (&optional tstart tend)
+(defun org-clock-sum (&optional tstart tend headline-filter)
   "Sum the times for each subtree.
 Puts the resulting times in minutes as a text property on each headline.
-TSTART and TEND can mark a time range to be considered."
+TSTART and TEND can mark a time range to be considered.  HEADLINE-FILTER is a
+zero-arg function that, if specified, is called for each headline in the time 
+range with point at the headline.  Headlines for which HEADLINE-FILTER returns
+nil are excluded from the clock summation."
   (interactive)
   (let* ((bmp (buffer-modified-p))
         (re (concat "^\\(\\*+\\)[ \t]\\|^[ \t]*"
@@ -1308,7 +1311,9 @@ TSTART and TEND can mark a time range to be considered."
     (if (stringp tend) (setq tend (org-time-string-to-seconds tend)))
     (if (consp tstart) (setq tstart (org-float-time tstart)))
     (if (consp tend) (setq tend (org-float-time tend)))
-    (remove-text-properties (point-min) (point-max) '(:org-clock-minutes t))
+    (remove-text-properties (point-min) (point-max)
+                            '(:org-clock-minutes t
+                              :org-clock-force-headline-inclusion t))
     (save-excursion
       (goto-char (point-max))
       (while (re-search-backward re nil t)
@@ -1330,15 +1335,34 @@ TSTART and TEND can mark a time range to be considered."
          (setq t1 (+ t1 (string-to-number (match-string 5))
                      (* 60 (string-to-number (match-string 4))))))
         (t ;; A headline
-         (setq level (- (match-end 1) (match-beginning 1)))
-         (when (or (> t1 0) (> (aref ltimes level) 0))
-           (loop for l from 0 to level do
-                 (aset ltimes l (+ (aref ltimes l) t1)))
-           (setq t1 0 time (aref ltimes level))
-           (loop for l from level to (1- lmax) do
-                 (aset ltimes l 0))
-           (goto-char (match-beginning 0))
-           (put-text-property (point) (point-at-eol) :org-clock-minutes 
time)))))
+         (let* ((headline-forced
+                  (get-text-property (point)
+                                     :org-clock-force-headline-inclusion))
+                 (headline-included
+                  (or (null headline-filter)
+                      (save-excursion
+                        (save-match-data (funcall headline-filter))))))
+           (setq level (- (match-end 1) (match-beginning 1)))
+           (when (or (> t1 0) (> (aref ltimes level) 0))
+             (when (or headline-included headline-forced)
+                (if headline-included
+                    (loop for l from 0 to level do
+                          (aset ltimes l (+ (aref ltimes l) t1))))
+               (setq time (aref ltimes level))
+               (goto-char (match-beginning 0))
+               (put-text-property (point) (point-at-eol) :org-clock-minutes 
time)
+                (if headline-filter
+                    (save-excursion
+                      (save-match-data
+                        (while
+                            (> (funcall outline-level) 1)
+                          (outline-up-heading 1 t)
+                          (put-text-property
+                           (point) (point-at-eol)
+                           :org-clock-force-headline-inclusion t))))))
+             (setq t1 0)
+             (loop for l from level to (1- lmax) do
+                   (aset ltimes l 0)))))))
       (setq org-clock-file-total-minutes (aref ltimes 0)))
     (set-buffer-modified-p bmp)))
 
@@ -1658,6 +1682,8 @@ the currently selected interval size."
           (te (plist-get params :tend))
           (block (plist-get params :block))
           (link (plist-get params :link))
+          (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)
       (setq org-clock-file-total-minutes nil)
@@ -1739,7 +1765,14 @@ the currently selected interval size."
        (goto-char pos)
 
        (unless scope-is-list
-         (org-clock-sum ts te)
+         (org-clock-sum ts te
+                        (unless (null matcher)
+                          (lambda ()
+                            (let ((tags-list
+                                   (org-split-string
+                                    (or (org-entry-get (point) "ALLTAGS") "")
+                                    ":")))
+                              (eval matcher)))))
          (goto-char (point-min))
          (setq st t)
          (while (or (and (bobp) (prog1 st (setq st nil))
-- 
1.6.6.1


reply via email to

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