Function: org-clocktable-write-default

org-clocktable-write-default is a byte-compiled function defined in org-clock.el.gz.

Signature

(org-clocktable-write-default IPOS TABLES PARAMS)

Documentation

Write out a clock table at position IPOS in the current buffer.

TABLES is a list of tables with clocking data as produced by org-clock-get-table-data. PARAMS is the parameter property list obtained from the dynamic block definition.

Source Code

;; Defined in /usr/src/emacs/lisp/org/org-clock.el.gz
(defun org-clocktable-write-default (ipos tables params)
  "Write out a clock table at position IPOS in the current buffer.
TABLES is a list of tables with clocking data as produced by
`org-clock-get-table-data'.  PARAMS is the parameter property list obtained
from the dynamic block definition."
  ;; This function looks quite complicated, mainly because there are a
  ;; lot of options which can add or remove columns.  I have massively
  ;; commented this function, the I hope it is understandable.  If
  ;; someone wants to write their own special formatter, this maybe
  ;; much easier because there can be a fixed format with a
  ;; well-defined number of columns...
  (let* ((lang (or (plist-get params :lang) "en"))
	 (multifile (plist-get params :multifile))
	 (block (plist-get params :block))
	 (sort (plist-get params :sort))
	 (header (plist-get params :header))
	 (link (plist-get params :link))
	 (maxlevel (or (plist-get params :maxlevel) 3))
	 (emph (plist-get params :emphasize))
	 (compact? (plist-get params :compact))
	 (narrow (or (plist-get params :narrow) (and compact? '40!)))
	 (filetitle (plist-get params :filetitle))
	 (level? (and (not compact?) (plist-get params :level)))
	 (timestamp (plist-get params :timestamp))
	 (tags (plist-get params :tags))
	 (properties (plist-get params :properties))
	 (time-columns
	  (if (or compact? (< maxlevel 2)) 1
	    ;; Deepest headline level is a hard limit for the number
	    ;; of time columns.
	    (let ((levels
		   (cl-mapcan
		    (lambda (table)
		      (pcase table
			(`(,_ ,(and (pred wholenump) (pred (/= 0))) ,entries)
			 (mapcar #'car entries))))
		    tables)))
	      (min maxlevel
		   (or (plist-get params :tcolumns) 100)
		   (if (null levels) 1 (apply #'max levels))))))
	 (indent (or compact? (plist-get params :indent)))
	 (formula (plist-get params :formula))
	 (case-fold-search t)
	 (total-time (apply #'+ (mapcar #'cadr tables)))
	 recalc narrow-cut-p)

    (when (and narrow (integerp narrow) link)
      ;; We cannot have both integer narrow and link.
      (message "Using hard narrowing in clocktable to allow for links")
      (setq narrow (intern (format "%d!" narrow))))

    (pcase narrow
      ((or `nil (pred integerp)) nil)	;nothing to do
      ((and (pred symbolp)
	    (guard (string-match-p "\\`[0-9]+!\\'" (symbol-name narrow))))
       (setq narrow-cut-p t)
       (setq narrow (string-to-number (symbol-name narrow))))
      (_ (user-error "Invalid value %s of :narrow property in clock table" narrow)))

    ;; Now we need to output this table stuff.
    (goto-char ipos)

    ;; Insert the text *before* the actual table.
    (insert-before-markers
     (or header
	 ;; Format the standard header.
	 (format "#+CAPTION: %s %s%s\n"
		 (org-clock--translate "Clock summary at" lang)
		 (format-time-string (org-time-stamp-format t t))
		 (if block
		     (let ((range-text
			    (nth 2 (org-clock-special-range
				    block nil t
				    (plist-get params :wstart)
				    (plist-get params :mstart)))))
		       (format ", for %s." range-text))
		   ""))))

    ;; Insert the narrowing line
    (when (and narrow (integerp narrow) (not narrow-cut-p))
      (insert-before-markers
       "|"				;table line starter
       (if multifile "|" "")		;file column, maybe
       (if level? "|" "")		;level column, maybe
       (if timestamp "|" "")		;timestamp column, maybe
       (if tags "|" "")                 ;tags columns, maybe
       (if properties			;properties columns, maybe
	   (make-string (length properties) ?|)
	 "")
       (format "<%d>| |\n" narrow)))	;headline and time columns

    ;; Insert the table header line
    (insert-before-markers
     "|"				;table line starter
     (if multifile			;file column, maybe
	 (concat (org-clock--translate "File" lang) "|")
       "")
     (if level?				;level column, maybe
	 (concat (org-clock--translate "L" lang) "|")
       "")
     (if timestamp			;timestamp column, maybe
	 (concat (org-clock--translate "Timestamp" lang) "|")
       "")
     (if tags "Tags |" "")              ;tags columns, maybe

     (if properties			;properties columns, maybe
	 (concat (mapconcat #'identity properties "|") "|")
       "")
     (concat (org-clock--translate "Headline" lang)"|")
     (concat (org-clock--translate "Time" lang) "|")
     (make-string (max 0 (1- time-columns)) ?|) ;other time columns
     (if (eq formula '%) "%|\n" "\n"))

    ;; Insert the total time in the table
    (insert-before-markers
     "|-\n"				;a hline
     "|"				;table line starter
     (if multifile (format "| %s " (org-clock--translate "ALL" lang)) "")
					;file column, maybe
     (if level?    "|" "")		;level column, maybe
     (if timestamp "|" "")		;timestamp column, maybe
     (if tags      "|" "")		;timestamp column, maybe
     (make-string (length properties) ?|) ;properties columns, maybe
     (concat (format org-clock-total-time-cell-format
		     (org-clock--translate "Total time" lang))
	     "| ")
     (format org-clock-total-time-cell-format
	     (org-duration-from-minutes (or total-time 0))) ;time
     "|"
     (make-string (max 0 (1- time-columns)) ?|)
     (cond ((not (eq formula '%)) "")
	   ((or (not total-time) (= total-time 0)) "0.0|")
	   (t  "100.0|"))
     "\n")

    ;; Now iterate over the tables and insert the data but only if any
    ;; time has been collected.
    (when (and total-time (> total-time 0))
      (pcase-dolist (`(,file-name ,file-time ,entries) tables)
	(when (or (and file-time (> file-time 0))
		  (not (plist-get params :fileskip0)))
	  (insert-before-markers "|-\n") ;hline at new file
	  ;; First the file time, if we have multiple files.
	  (when multifile
	    ;; Summarize the time collected from this file.
	    (insert-before-markers
	     (format (concat "| %s %s | %s%s%s"
			     (format org-clock-file-time-cell-format
				     (org-clock--translate "File time" lang))

			     ;; The file-time rollup value goes in the first time
			     ;; column (of which there is always at least one)...
			     " | *%s*|"
			     ;; ...and the remaining file time cols (if any) are blank.
			     (make-string (max 0 (1- time-columns)) ?|)

			     ;; Optionally show the percentage contribution of "this"
			     ;; file time to the total time.
			     (if (eq formula '%) " %s |" "")
			     "\n")

                     (if filetitle
                         (or (org-get-title file-name)
                             (file-name-nondirectory file-name))
                       (file-name-nondirectory file-name))
		     (if level?    "| " "") ;level column, maybe
		     (if timestamp "| " "") ;timestamp column, maybe
		     (if tags      "| " "") ;tags column, maybe
		     (if properties	    ;properties columns, maybe
			 (make-string (length properties) ?|)
		       "")
		     (org-duration-from-minutes file-time) ;time

		     (cond ((not (eq formula '%)) "")	   ;time percentage, maybe
			   ((or (not total-time) (= total-time 0)) "0.0")
			   (t
			    (format "%.1f" (* 100 (/ file-time (float total-time)))))))))

	  ;; Get the list of node entries and iterate over it
	  (when (> maxlevel 0)
	    (pcase-dolist (`(,level ,headline ,tgs ,ts ,time ,props) entries)
	      (when narrow-cut-p
		(setq headline
		      (if (and (string-match
				(format "\\`%s\\'" org-link-bracket-re)
				headline)
			       (match-end 2))
			  (format "[[%s][%s]]"
				  (match-string 1 headline)
				  (org-shorten-string (match-string 2 headline)
						      narrow))
			(org-shorten-string headline narrow))))
	      (cl-flet ((format-field (f) (format (cond ((not emph) "%s |")
							((= level 1) "*%s* |")
							((= level 2) "/%s/ |")
							(t "%s |"))
						  f)))
		(insert-before-markers
		 "|"		       ;start the table line
		 (if multifile "|" "") ;free space for file name column?
		 (if level? (format "%d|" level) "") ;level, maybe
		 (if timestamp (concat ts "|") "")   ;timestamp, maybe
		 (if tags (concat (mapconcat #'identity tgs ", ") "|") "")   ;tags, maybe
		 (if properties		;properties columns, maybe
		     (concat (mapconcat (lambda (p) (or (cdr (assoc p props)) ""))
                                        properties
                                        "|")
			     "|")
		   "")
		 (if indent		;indentation
		     (org-clocktable-indent-string level)
		   "")
		 (format-field headline)
		 ;; Empty fields for higher levels.
		 (make-string (max 0 (1- (min time-columns level))) ?|)
		 (format-field (org-duration-from-minutes time))
		 (make-string (max 0 (- time-columns level)) ?|)
		 (if (eq formula '%)
		     (format "%.1f |" (* 100 (/ time (float total-time))))
		   "")
		 "\n")))))))
    (delete-char -1)
    (cond
     ;; Possibly rescue old formula?
     ((or (not formula) (eq formula '%))
      (let ((contents (org-string-nw-p (plist-get params :content))))
	(when (and contents (string-match "^\\([ \t]*#\\+tblfm:.*\\)" contents))
	  (setq recalc t)
	  (insert "\n" (match-string 1 contents))
	  (forward-line -1))))
     ;; Insert specified formula line.
     ((stringp formula)
      (insert "\n#+TBLFM: " formula)
      (setq recalc t))
     (t
      (user-error "Invalid :formula parameter in clocktable")))
    ;; Back to beginning, align the table, recalculate if necessary.
    (goto-char ipos)
    (skip-chars-forward "^|")
    (org-table-align)
    (when org-hide-emphasis-markers
      ;; We need to align a second time.
      (org-table-align))
    (when sort
      (save-excursion
	(org-table-goto-line 3)
	(org-table-goto-column (car sort))
	(org-table-sort-lines nil (cdr sort))))
    (when recalc (org-table-recalculate 'all))
    total-time))