Björn Kettunen <[email protected]> writes: > Ihor Radchenko <[email protected]> writes: > >> Björn Kettunen <[email protected]> writes: >> >>>>> Ideally I would like that similar to single file-with-archices that >>>>> archive and the their files are shown as one file. >>>>> But not sure how to do that. >>>> >>>> That's possible to do by combining the results coming from file and its >>>> archives together. >>> >>> How would you do this? >> >> (mapcar (lambda (file) >> (with-current-buffer (find-buffer-visiting file) >> (save-excursion >> (save-restriction >> (org-clock-get-table-data file params))))) >> files) >> >> In the above, you will need to merge table data coming from files and their >> archives. > > Looks good. I think I sort the tables and then merge the entries next > to each other if there name without extension matches. > However the thing I haven't figure out yet is to how to merge the table > data here. > > Any clues?
I think I got something. I wrote a function that merges table with matching file-names as keys based of the description in org-clock-get-table-data. The result is very similar to what file-with-archives for a single file did before the patch. I.e. headings from main file and archive are not merged. I haven't updated the documentation or removed any of the commented out code. I'm mostly looking for feedback on how the merge table data function is called and on the function itself.
>From 632caed07c11b4915438ca7bc811b30700804fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kettunen?= <[email protected]> Date: Tue, 3 Mar 2026 22:47:12 +0200 Subject: [PATCH 1/2] org-clock: clock-report be able to also take directories * lisp/org-clock.el (org-dblock-write:clocktable): Expand files in directories if any of the entries in scope is a directory. Just like in org-agenda-files. * lisp/org.el (org-file-list-expand): (org-agenda-files): Refactor file expansion into separate function. * doc/org-manual.org: Document. * etc/ORG-NEWS: Announce --- doc/org-manual.org | 2 +- etc/ORG-NEWS | 9 ++++ lisp/org-clock.el | 124 ++++++++++++++++++++++++++++++++++++--------- lisp/org.el | 20 +++++--- 4 files changed, 123 insertions(+), 32 deletions(-) diff --git a/doc/org-manual.org b/doc/org-manual.org index 9c4c27877..cfcf8994b 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -7109,7 +7109,7 @@ *** The clock table | =treeN= | the surrounding level N tree, for example =tree3= | | =tree= | the surrounding level 1 tree | | =agenda= | all agenda files | - | =("file" ...)= | scan these files | + | =("file" "dir" "...)= | scan these files or files in directories | | =FUNCTION= | scan files returned by calling {{{var(FUNCTION)}}} with no argument | | =file-with-archives= | current file and its archives | | =agenda-with-archives= | all agenda files, including archives | diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index 40fa1e6aa..ca18ab041 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -40,6 +40,11 @@ into a "Tags" section and a "Priorities" section. Priorities can now be increased, decreased, set to the default, and set interactively from the priority context menus. +*** Clocktable option =:scope (file)= list of files can now also include directories + +The scope option can now also include directories in the list of files. +Files are resolved just like in ~org-agenda-files~. + ** New and changed options # Changes dealing with changing default values of customizations, @@ -60,6 +65,10 @@ variable. Given the completed and total number of tasks, format the percent cookie =[N%]=. +*** New function ~org-agenda-directory-files-recursively~ + +Expand list of flies according to ~org-agenda-file-regexp~. + ** Removed or renamed functions and variables ** Miscellaneous diff --git a/lisp/org-clock.el b/lisp/org-clock.el index ce2d23a9b..afb27c7ba 100644 --- a/lisp/org-clock.el +++ b/lisp/org-clock.el @@ -2677,22 +2677,21 @@ (defun org-dblock-write:clocktable (params) (catch 'exit (let* ((scope (plist-get params :scope)) (base-buffer (org-base-buffer (current-buffer))) + (scope (or (and (functionp scope) + (funcall scope)) + scope)) (files (pcase scope - (`agenda + ((or `agenda `agenda-with-archives) (org-agenda-files t)) - (`agenda-with-archives - (org-add-archive-files (org-agenda-files t))) - (`file-with-archives - (let ((base-file (buffer-file-name base-buffer))) - (and base-file - (org-add-archive-files (list base-file))))) - ((or `nil `file `subtree `tree + ((or `file-with-archives) + (list (buffer-file-name base-buffer))) + ((or `nil `subtree `tree `file (and (pred symbolp) (guard (string-match "\\`tree\\([0-9]+\\)\\'" (symbol-name scope))))) base-buffer) - ((pred functionp) (funcall scope)) ((pred consp) scope) + ((pred stringp) scope) ;; To not break previous function calls here (_ (user-error "Unknown scope: %S" scope)))) (block (plist-get params :block)) (ts (plist-get params :tstart)) @@ -2704,7 +2703,25 @@ (defun org-dblock-write:clocktable (params) (formatter (or (plist-get params :formatter) org-clock-clocktable-formatter 'org-clocktable-write-default)) - cc) + (multifile + ;; Even though `file-with-archives' can consist of + ;; multiple files, we consider this is one extended file + ;; instead. + (and (not hide-files) + (consp files) + (not (eq scope 'file-with-archives)))) + cc) + + (when (consp files) + (when-let* ((cons-scope (car files)) + (cons-scope (and (symbolp cons-scope) + cons-scope))) + (setq scope cons-scope) + (setq files (cdr files))) + (setq files (org-agenda-directory-files-recursively files))) + (pcase scope ((or `agenda-with-archives `file-with-archives) + (setq files (org-add-archive-files files)))) + ;; Check if we need to do steps (when block ;; Get the range text for the header @@ -2723,12 +2740,38 @@ (defun org-dblock-write:clocktable (params) (let ((origin (point)) (tables (if (consp files) - (mapcar (lambda (file) - (with-current-buffer (find-buffer-visiting file) - (save-excursion - (save-restriction - (org-clock-get-table-data file params))))) - files) + ;; (let* ((files (sort files :lessp #'string-lessp)) + ;; (previous-file (nth 0 files)) + ;; previous-table + ;; previous-file + ;; (tables + ;; table) + ;; (while-let ((file (pop files))) + ;; (setq table (with-current-buffer (find-buffer-visiting file) + ;; (save-excursion + ;; (save-restriction + ;; (org-clock-get-table-data file params))))) + ;; (if (equal (file-name-sans-extension file) + ;; previous-file) + ;; (progn + ;; (setf tables (push (cl-union table previous-table) tables)) + ;; (setf previous-file nil)) + ;; (setf previous-file (file-name-sans-extension file)) + ;; (setf tables (push previous-table tables)) + ;; (setf previous-table table)))) + (let ((tables + (mapcar + (lambda (file) + (with-current-buffer + (find-buffer-visiting file) + (save-excursion + (save-restriction + (org-clock-get-table-data file params))))) + files))) + (if (eq scope 'file-with-archives) + (setq tables (org-clock-merge-table-data tables)) + tables)) + ;; Get the right restriction for the scope. (save-restriction (cond @@ -2750,14 +2793,7 @@ (defun org-dblock-write:clocktable (params) level) (throw 'exit nil)))) (org-narrow-to-subtree)))) - (list (org-clock-get-table-data nil params))))) - (multifile - ;; Even though `file-with-archives' can consist of - ;; multiple files, we consider this is one extended file - ;; instead. - (and (not hide-files) - (consp files) - (not (eq scope 'file-with-archives))))) + (list (org-clock-get-table-data nil params)))))) (funcall formatter origin @@ -3112,6 +3148,46 @@ (defun org-clocktable-steps (params) (setq start next)) (end-of-line 0)))) + +(defun org-clock-merge-table-data (tables) + "Merge table data for TABLES. +TABLES can be any table data returned in the format returned by +`org-clock-get-table-data'. +Returns the same format but with the file name of the first +table." + ;; Need to: + ;; First sort tables by index, index being the file-name + ;; Either by merging them first not. + (let ((table-keys (mapcar #'file-name-sans-extension + (mapcar #'car tables))) + (index-tables (copy-tree tables)) + (index 0)) + (while-let ((table (pop index-tables)) + (key (nth index table-keys))) + (setf (nth index table-keys) nil) + (when-let* ((elm-pos (seq-position table-keys key)) + (table-a (nth elm-pos tables)) + (table-b (nth index tables)) + (total-time (+ (nth 1 table-a) + (nth 1 table-b))) + (file-name (concat + (file-name-sans-extension + (car table-a)) + ".org"))) + + (when (and (car (nthcdr 2 table-b)) + (car (nthcdr 2 table-a))) + (let* ((table-entries (car (nthcdr 2 table-b))) + (table-entries (append (car (nthcdr 2 table-a)) + table-entries))) + + (setf (nth index tables) + (cl-merge 'list (list file-name total-time) (list table-entries) #'equal)))) + (setq tables (seq-remove-at-position tables elm-pos))) + (incf index)) + + tables)) + (defun org-clock-get-table-data (file params) "Get the clocktable data for file FILE, with parameters PARAMS. FILE is only for identification - this function assumes that diff --git a/lisp/org.el b/lisp/org.el index fc51d4ba3..05607bd2c 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -15977,6 +15977,18 @@ (defun org-switchb (&optional arg) (mapcar #'list (mapcar #'buffer-name blist)) nil t)))) + +(defun org-agenda-directory-files-recursively (files) + "Expand list of FILES according to `org-agenda-file-regexp'." + (apply 'append + (mapcar (lambda (f) + (if (file-directory-p f) + (directory-files + f t org-agenda-file-regexp) + (list (expand-file-name f org-directory)))) + files))) + + (defun org-agenda-files (&optional unrestricted archives) "Get the list of agenda files. Optional UNRESTRICTED means return the full list even if a restriction @@ -15990,13 +16002,7 @@ (defun org-agenda-files (&optional unrestricted archives) ((stringp org-agenda-files) (org-read-agenda-file-list)) ((listp org-agenda-files) org-agenda-files) (t (error "Invalid value of `org-agenda-files'"))))) - (setq files (apply 'append - (mapcar (lambda (f) - (if (file-directory-p f) - (directory-files - f t org-agenda-file-regexp) - (list (expand-file-name f org-directory)))) - files))) + (setq files (org-agenda-directory-files-recursively files)) (when org-agenda-skip-unavailable-files (setq files (delq nil (mapcar (lambda (file) -- 2.53.0
