diff options
author | Carsten Dominik <carsten.dominik@gmail.com> | 2010-04-23 21:24:19 +0200 |
---|---|---|
committer | Carsten Dominik <carsten.dominik@gmail.com> | 2010-04-23 21:24:19 +0200 |
commit | c59da3a3dd5ec282f955994e309d8fe008acb171 (patch) | |
tree | f968f7aa918da327586ca0c213a331ce65031d9e | |
parent | 07198e34f6b9895498cdf0aa840c5dddaef070e0 (diff) | |
download | org-mode-c59da3a3dd5ec282f955994e309d8fe008acb171.tar.gz |
Add tags matching to clock tables
Adam Elliott writes:
> 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
-rw-r--r-- | doc/org.texi | 1 | ||||
-rwxr-xr-x | lisp/ChangeLog | 4 | ||||
-rwxr-xr-x[-rw-r--r--] | lisp/org-clock.el | 59 |
3 files changed, 51 insertions, 13 deletions
diff --git a/doc/org.texi b/doc/org.texi index 69e23e0..5c13e07 100644 --- a/doc/org.texi +++ b/doc/org.texi @@ -5582,6 +5582,7 @@ new table. The @samp{BEGIN} line can specify options: :tend @r{A time string specifying when to stop considering times.} :step @r{@code{week} or @code{day}, to split the table into chunks.} @r{To use this, @code{:block} or @code{:tstart}, @code{:tend} are needed.} +:tags @r{A tags match to select entries that should contribute} :link @r{Link the item headlines in the table to their origins.} :formula @r{Content of a @code{#+TBLFM} line to be added and evaluated.} @r{As a special case, @samp{:formula %} adds a column with % time.} diff --git a/lisp/ChangeLog b/lisp/ChangeLog index ad6d7c6..ea730e3 100755 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,5 +1,9 @@ 2010-04-23 Carsten Dominik <carsten.dominik@gmail.com> + * org-clock.el (org-clock-sum): New argument HEADLINE-FILTER. + (org-clock-sum): Add property to selected headlines. + (org-dblock-write:clocktable): Make tags matcher. + * org.el (org-set-autofill-regexps): XEmacs compatibility. * org-latex.el (org-export-latex-set-initial-vars): Allow "-" diff --git a/lisp/org-clock.el b/lisp/org-clock.el index d1805e6..c8bf6cc 100644..100755 --- a/lisp/org-clock.el +++ b/lisp/org-clock.el @@ -1311,10 +1311,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]*" @@ -1330,7 +1333,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) @@ -1359,15 +1364,34 @@ TSTART and TEND can mark a time range to be considered." (let ((time (floor (- (org-float-time) (org-float-time org-clock-start-time)) 60))) (setq t1 (+ t1 time)))) - (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))) @@ -1687,6 +1711,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) @@ -1768,7 +1794,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)) |