org-colview: Introduce custom summary types

* lisp/org-colview.el (org-columns-summary-types): New variable.
(org-columns-compile-map): Rename into...
(org-columns-summary-types-default): ... this.

(org-columns-new):
(org-columns-compile-format): Use new variables.

* testing/lisp/test-org-colview.el (test-org-colview/columns-summary):
  Add test.

* doc/org.texi (Column attributes): Document new variable.  Improve
  description of time and age based summary types.
This commit is contained in:
Nicolas Goaziou 2016-02-21 00:16:51 +01:00
parent 15101fe7a6
commit fedd6be97a
4 changed files with 94 additions and 32 deletions

View File

@ -5626,22 +5626,26 @@ optional. The individual parts have the following meaning:
@{+@} @r{Sum numbers in this column.}
@{+;%.1f@} @r{Like @samp{+}, but format result with @samp{%.1f}.}
@{$@} @r{Currency, short for @samp{+;%.2f}.}
@{:@} @r{Sum times, HH:MM, plain numbers are hours.}
@{X@} @r{Checkbox status, @samp{[X]} if all children are @samp{[X]}.}
@{X/@} @r{Checkbox status, @samp{[n/m]}.}
@{X%@} @r{Checkbox status, @samp{[n%]}.}
@{min@} @r{Smallest number in column.}
@{max@} @r{Largest number.}
@{mean@} @r{Arithmetic mean of numbers.}
@{X@} @r{Checkbox status, @samp{[X]} if all children are @samp{[X]}.}
@{X/@} @r{Checkbox status, @samp{[n/m]}.}
@{X%@} @r{Checkbox status, @samp{[n%]}.}
@{:@} @r{Sum times, HH:MM, plain numbers are
hours@footnote{A time can also be a duration, using effort
modifiers defined in @code{org-effort-durations}, e.g.,
@samp{3d 1h}. If any value in the column is as such, the
summary will also be an effort duration.}.}
@{:min@} @r{Smallest time value in column.}
@{:max@} @r{Largest time value.}
@{:mean@} @r{Arithmetic mean of time values.}
@{@@min@} @r{Minimum age (in
days/hours/mins/seconds@footnote{Days, hours, minutes and
seconds are represented with, respectively, @samp{d},
@samp{h}, @samp{m} and @samp{s} suffixes, e.g., @samp{13h
10s}. Alternatively, an age can be defined as a duration
since a given time-stamp (@pxref{Timestamps}).}).}
@{@@min@} @r{Minimum age@footnote{An age is defined as
a duration since a given time-stamp (@pxref{Timestamps}). It
can also be expressed as days, hours, minutes and seconds,
identified by @samp{d}, @samp{h}, @samp{m} and @samp{s}
suffixes, all mandatory, e.g., @samp{0d 13h 0m 10s}.} (in
days/hours/mins/seconds).}
@{@@max@} @r{Maximum age (in days/hours/mins/seconds).}
@{@@mean@} @r{Arithmetic mean of ages (in days/hours/mins/seconds).}
@{est+@} @r{Add @samp{low-high} estimates.}
@ -5672,6 +5676,10 @@ full job more realistically, at 10--15 days.
Numbers are right-aligned when a format specifier with an explicit width like
@code{%5d} or @code{%5.1f} is used.
@vindex org-columns-summary-types
You can also define custom summary types by setting
@code{org-columns-summary-types}, which see.
Here is an example for a complete columns definition, along with allowed
values.

View File

@ -214,6 +214,9 @@ When called with a prefix argument, ~org-columns~ apply to the whole
buffer unconditionally.
**** New variable : ~org-agenda-view-columns-initially~
The variable used to be a ~defvar~, it is now a ~defcustom~.
**** Allow custom summaries
It is now possible to add new summary types, or override those
provided by Org by customizing ~org-columns-summary-types~, which see.
*** Preview LaTeX snippets in buffers not visiting files
*** New option ~org-attach-commit~
When non-nil, commit attachments with git, assuming the document is in

View File

@ -62,6 +62,32 @@ or nil if the normal value should be used."
:group 'org-properties
:type '(choice (const nil) (function)))
(defcustom org-columns-summary-types nil
"Alist between operators and summarize functions.
Each association follows the pattern (LABEL . SUMMARIZE) where
LABEL is a string used in #+COLUMNS definition describing the
summary type. It can contain any character but \"}\". It is
case-sensitive.
SUMMARIZE is a function called with two arguments. The first
argument is a non-empty list of values, as non-empty strings.
The second one is a format string or nil. It has to return
a string summarizing the list of values.
Note that the return value can become one value for an higher
order summary, so the function is expected to handle its own
output.
Types defined in this variable take precedence over those defined
in `org-columns-summary-types-default', which see."
:group 'org-properties
:version "25.1"
:package-version '(Org . "9.0")
:type '(alist :key-type (string :tag " Label")
:value-type (function :tag "Summarize")))
;;; Column View
@ -87,7 +113,7 @@ This is the compiled version of the format.")
(defvar org-columns-map (make-sparse-keymap)
"The keymap valid in column display.")
(defconst org-columns-compile-map
(defconst org-columns-summary-types-default
'(("+" . org-columns--summary-sum)
("$" . org-columns--summary-currencies)
("X" . org-columns--summary-checkbox)
@ -105,13 +131,7 @@ This is the compiled version of the format.")
("@min" . org-columns--summary-min-age)
("est+" . org-columns--summary-estimate))
"Map operators to summarize functions.
Used to compile/uncompile columns format and completing read in
interactive function `org-columns-new'.
operator string used in #+COLUMNS definition describing the
summary type
function called with a list of values as argument to calculate
the summary value")
See `org-columns-summary-types' for details.")
(defun org-columns-content ()
"Switch to contents view while in columns view."
@ -803,12 +823,17 @@ When COLUMNS-FMT-STRING is non-nil, use it as the column format."
(org-string-nw-p
(completing-read
"Summary: "
(mapcar (lambda (x) (list (car x))) org-columns-compile-map)
(delete-dups
(mapcar (lambda (x) (list (car x)))
(append org-columns-summary-types
org-columns-summary-types-default)))
nil t))))
(summarize (or summarize
(cdr (assoc operator org-columns-compile-map))))
(edit (and prop
(assoc-string prop org-columns-current-fmt-compiled t))))
(summarize
(or summarize
(cdr (or (assoc operator org-columns-summary-types)
(assoc operator org-columns-summary-types-default)))))
(edit
(and prop (assoc-string prop org-columns-current-fmt-compiled t))))
(if edit
(progn
(setcar edit prop)
@ -996,15 +1021,23 @@ This function updates `org-columns-current-fmt-compiled'."
(let* ((width (and (match-end 1) (string-to-number (match-string 1 fmt))))
(prop (match-string 2 fmt))
(title (or (match-string 3 fmt) prop))
(op (match-string 4 fmt))
(printf nil)
(fun '+))
(when (and op (string-match ";" op))
(setq printf (substring op (match-end 0)))
(setq op (substring op 0 (match-beginning 0))))
(let ((op-match (assoc op org-columns-compile-map)))
(when op-match (setq fun (cdr op-match))))
(push (list prop title width op printf fun)
(operator (match-string 4 fmt)))
(push (if (not operator) (list prop title width nil nil nil)
(let (printf)
(when (string-match ";" operator)
(setq printf (substring operator (match-end 0)))
(setq operator (substring operator 0 (match-beginning 0))))
(let* ((summary-type
(or (assoc operator org-columns-summary-types)
(assoc operator org-columns-summary-types-default)))
(summarize
(cond
((not summary-type)
(user-error "Unknown summary operator: %S" operator))
((cdr summary-type))
(t (user-error "Missing summary function for type: %S"
operator)))))
(list prop title width operator printf summarize))))
org-columns-current-fmt-compiled)))
(setq org-columns-current-fmt-compiled
(nreverse org-columns-current-fmt-compiled))))

View File

@ -503,6 +503,24 @@
:END:
"
(let ((org-columns-default-format "%A{est+}")) (org-columns))
(get-char-property (point) 'org-columns-value-modified))))
;; Test custom summary types.
(should
(equal
"1|2"
(org-test-with-temp-text
"* H
** S1
:PROPERTIES:
:A: 1
:END:
** S1
:PROPERTIES:
:A: 2
:END:"
(let ((org-columns-summary-types
'(("custom" . (lambda (s _) (mapconcat #'identity s "|")))))
(org-columns-default-format "%A{custom}")) (org-columns))
(get-char-property (point) 'org-columns-value-modified)))))
(ert-deftest test-org-colview/columns-update ()