From mboxrd@z Thu Jan 1 00:00:00 1970 From: Myles English Subject: Remaining work/progress report: nearly useful, help needed Date: Wed, 10 Oct 2012 14:49:22 +0100 Message-ID: <87fw5mjv31.fsf@ed.ac.uk> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Return-path: Received: from eggs.gnu.org ([208.118.235.92]:52564) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1TLwXq-0004DF-UV for emacs-orgmode@gnu.org; Wed, 10 Oct 2012 09:42:04 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1TLwXg-0003C0-24 for emacs-orgmode@gnu.org; Wed, 10 Oct 2012 09:41:54 -0400 Received: from mail-wg0-f49.google.com ([74.125.82.49]:46365) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1TLwXf-0003Bj-LU for emacs-orgmode@gnu.org; Wed, 10 Oct 2012 09:41:43 -0400 Received: by mail-wg0-f49.google.com with SMTP id gg4so335983wgb.30 for ; Wed, 10 Oct 2012 06:41:42 -0700 (PDT) List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org Sender: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org To: Emacs Org mode --=-=-= Content-Type: text/plain Hi orgees, In March I had a go at making a "Remaining work/progress report"[1]. Having make some progress, I am looking for: - help, - collaborators, - opinions on whether this would be useful enough to be worth the effort, - whether anyone else has something like this (but better) and - any other wisdom, with a view to contributing it. Table of Contents _________________ 1 Features 2 Usage 3 How it works 4 Improvements necessary to make it truly useful 1 Features ~~~~~~~~~~ Attachments: minimal.el my-progress.el a.org b.org Currently it can make a table consisting of a mixture of the Table of Contents (TOC) entries and the inline tasks within each section, e.g.: ITEM Effort CLOCKSUM Remaining ------------------------------------------------------------ .1 Heading One 1:25 ... 1.1 A sub heading 0:36 . Draw a figure 1:00 0:36 0:24 ... 1.2 Another sub heading 0:49 . Write this bit 1:20 0:49 0:31 Most of the code was available from various places so there is not much original work from me in this. 2 Usage ~~~~~~~ - start emacs: emacs -Q -l minimal.el a.org - excute the emacs-lisp source blocks - refresh the "Remaining" properties: goto each property and press C-c s RET RET - Execute the dynamic block C-c c in "Progress table" 3 How it works ~~~~~~~~~~~~~~ Whenever a task is clocked in to, the value of a new "Effort" property is prompted for. When the task is clocked out of, a new property "Remaining" is added that is the difference of Effort special property CLOCKSUM. The buffer is exported ascii-like to get the TOC with its headline numbering, and is matched with corresponding lines of the captured column view. 4 Improvements necessary to make it truly useful ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1. include a captured column view of the INCLUDEd file b.org: the file's headings are included in the TOC because it uses the new exporter, but not in the column-view. Maybe exporting as org first and then taking the column view of that buffer would work. 2. Accumulate the time Remaining in the same way that CLOCKSUM does; it propagates upwards to higher level headings so that you can see (e.g.) how long Chapter 1 will take in total 3. Refresh all the Remaining properties automatically I would really appreciate some help, particularly with 1. Thanks, Myles Footnotes: [1] http://comments.gmane.org/gmane.emacs.orgmode/53567 --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=minimal.el Content-Transfer-Encoding: quoted-printable (add-to-list 'load-path "~/.emacs.d/plugins/org-mode/lisp") (add-to-list 'load-path "~/.emacs.d/plugins/org-mode/contrib/lisp" t) (require 'org) --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=my-progress.el Content-Transfer-Encoding: quoted-printable (defun my-progress-org-mode-ask-effort () "Ask for an effort estimate when clocking in. From http://nflath.com/201= 0/03/" (unless (org-entry-get (point) "Effort")=20 (let ((effort (completing-read "Effort: " (org-entry-get-multivalued-property (point) "Effort")))) (unless (equal effort "") (org-set-property "Effort" effort))))) (provide 'my-progress) (defun toc-alist () (let ( (ascii (org-export-as 'my-progress-toc)) (mylist nil) (newcons "")) (dolist (bb (split-string ascii "\n")) (when (string-match "^\\(\.*\\( \\)*[.1-9]+\\) \\([a-z].*\\)$" bb) (setq newcons (cons (match-string 3 bb) (match-string 1 bb))) (add-to-list 'mylist newcons))) mylist)) (defun org-dblock-write:columnview-toc (params) "Write the column view table. PARAMS is a property list of parameters: :width enforce same column widths with specifiers. :id the :ID: property of the entry where the columns view should be built. When the symbol `local', call locally. When `global' call column view with the cursor at the beginning of the buffer (usually this means that the whole buffer switches to column view). When \"file:path/to/file.org\", invoke column view at the start of that file. Otherwise, the ID is located using `org-id-find'. :hlines When t, insert a hline before each item. When a number, insert a hline before each level <=3D that number. :vlines When t, make each column a colgroup to enforce vertical lines. :maxlevel When set to a number, don't capture headlines below this level. :skip-empty-rows When t, skip rows where all specifiers other than ITEM are empt= y." (require 'org-timer) (let ((pos (move-marker (make-marker) (point))) (hlines (plist-get params :hlines)) (vlines (plist-get params :vlines)) (maxlevel (plist-get params :maxlevel)) (content-lines (org-split-string (plist-get params :content) "\n"= )) (skip-empty-rows (plist-get params :skip-empty-rows)) tbl id idpos nfields tmp recalc line id-as-string view-file view-pos mytoc) (when (setq id (plist-get params :id)) (setq id-as-string (cond ((numberp id) (number-to-string id)) ((symbolp id) (symbol-name id)) ((stringp id) id) (t ""))) (cond ((not id) nil) ((eq id 'global) (setq view-pos (point-min))) ((eq id 'local)) ((string-match "^file:\\(.*\\)" id-as-string) (setq view-file (match-string 1 id-as-string) view-pos 1) (unless (file-exists-p view-file) (error "No such file: \"%s\"" id-as-string))) ((setq idpos (org-find-entry-with-id id)) (setq view-pos idpos)) ((setq idpos (org-id-find id)) (setq view-file (car idpos)) (setq view-pos (cdr idpos))) (t (error "Cannot find entry with :ID: %s" id)))) (with-current-buffer (if view-file (get-file-buffer view-file) (current-buffer)) (save-excursion (setq mytoc (reverse (toc-alist)))) (save-excursion (save-restriction (widen) (goto-char (or view-pos (point))) (org-columns) (setq tbl (org-columns-capture-view maxlevel skip-empty-rows)) (setq nfields (length (car tbl))) (org-columns-quit)))) (goto-char pos) (move-marker pos nil) (when tbl (when (plist-get params :hlines) (setq tmp nil) (while tbl (if (eq (car tbl) 'hline) (push (pop tbl) tmp) (if (string-match "\\` *\\(\\*+\\)" (caar tbl)) (if (and (not (eq (car tmp) 'hline)) (or (eq hlines t) (and (numberp hlines) (<=3D (- (match-end 1) (match-beginning= 1)) hlines)))) (push 'hline tmp))) (push (pop tbl) tmp))) (setq tbl (nreverse tmp))) (when vlines (setq tbl (mapcar (lambda (x) (if (eq 'hline x) x (cons "" x))) tbl)) (setq tbl (append tbl (list (cons "/" (make-list nfields "<>"))))= )) (setq pos (point)) (when content-lines (while (string-match "^#" (car content-lines)) (insert (pop content-lines) "\n"))) (setq tmptbl (org-listtable-to-string tbl)) ;; swap half of the line for the numbers in the TOC (let ((shline "") (keya "") (nline "") (lenline 1) (task "") (was-dash nil) (first-char ".") (accum-time 0)) (dolist (shline (split-string tmptbl "\n")) (let ((is-dash (string=3D "|-|" shline)) (writeline t)) (if (and (string-match "^|\\([*]*\\) \\(.*\\)" shline) (> (length (match-string 1 shline)) 12)) (progn (setq task (match-string 2 shline)) (when (string-match "\\(TODO \\|NEXT \\)" task) (setq task (replace-match "" nil t task 0)) (setq shline (concat "|" first-char (make-string lenline ?\s) tas= k "|"))) (when (string-match "DONE " task) (setq shline "DEL"))) ;; if it is a first level heading then accumulate the time va= lue ;; e.g. "|* XX |49:30:2|" (when (string-match "^|[*][ ]+.*|\\([0-9]*:*[0-9]*:*[0-9]+\\)= |" shline) (let ((str-time (match-string 1 shline))) (when str-time ;; found a time (setq accum-time (+ accum-time (org-timer-hms-to-secs (org-timer-fix-incomplete ;; assume times are HH:MM and so append :00 = seconds (concat (match-string 1 shline) ":00")))))))) ;; if it is a headline, add the section number to the start (when (string-match "^|\\(.*[*] \\)\\(.*?\\)[ ]*|.*" shline) (setq keya (match-string 2 shline)) (setq toc-num (cdr (assoc-string keya mytoc))) ;; if the heading had an entry in the toc then include the ;; heading and replace with the toc section number, otherwi= se ;; don't include the line at all, this will effectively exc= lude ;; headings not for export (if toc-num (progn (setq mstr (concat first-char toc-num " ")) (message (format "shline: %s keya: %s mstr: %s" shlin= e keya mstr)) (setq lenline (length mstr)) (setq shline (replace-match mstr nil t shline 1)) (setq mytoc (remove (assoc-string keya mytoc) mytoc )= )) (setq shline "DEL")))) ;; add back in (if (string-match "\\(^|[*]* END.*\\|^DEL\\)" shline) (progn (message (concat " org-dblock-write:columnview-toc : Excluding" shline)) (setq writeline nil))) ;; don't want two dashed rows next to each other (if is-dash (progn (when was-dash (setq writeline nil)) (setq was-dash t)) ;; if writeline is false then leave was-dash untouched (when writeline (setq was-dash nil))) (when writeline (setq nline (concat nline shline "\n"))))) ;; add a row for the the accumulated time ;; how many rows? (setq accum-time-hms (org-timer-secs-to-hms accum-time)) (string-match "^\\([0-9]+:[0-9]+\\)\\(:[0-9]+\\)" accum-time-hms) (message accum-time-hms) (setq accum-time-hm (match-string 1 accum-time-hms)) ;;(match-string 1 shline) (setq nline (concat nline ;; "|Total time: |" (org-timer-secs-to-hms accum-time) "|= \n")) "|-|-|\n|Total time [H:M]: |" accum-time-hm "|\n")) (setq tmptbl nline)) ;; ----------- (end swap) --------- (insert tmptbl) (when (plist-get params :width) (insert "\n|" (mapconcat (lambda (x) (format "<%d>" (max 3 x))) org-columns-current-widths "|"))) (while (setq line (pop content-lines)) (when (string-match "^#" line) (insert "\n" line) (when (string-match "^[ \t]*#\\+TBLFM" line) (setq recalc t)))) (if recalc (progn (goto-char pos) (org-table-recalculate 'all)) (goto-char pos) (org-table-align))))) (defun my-progress-ascii-toc-template (contents info) "Return complete document string after ASCII conversion. CONTENTS is the transcoded contents string. INFO is a plist holding export options. Cookie cut from org-e-ascii-template. " (org-element-normalize-string (org-e-ascii--indent-string (let ((text-width (- org-e-ascii-text-width org-e-ascii-global-margin))) (concat (let ((depth (plist-get info :with-toc))) (when depth (concat (org-e-ascii--build-toc info (and (wholenump depth) depth)) "\n\n\n"))))) org-e-ascii-global-margin))) (require 'org-e-ascii) (org-export-define-derived-backend my-progress-toc e-ascii :translate-alist ((template . my-progress-ascii-toc-template))) (defun my-progress-remove-toc-heading (output backend info) (when (and (memq backend '(my-progress-toc)) (string-match "Table of Contents\n_________________\n\n" output)) (replace-match "" nil nil output))) ;;(add-to-list 'org-export-filter-final-output-functions 'my-progress-remov= e-toc-heading) --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=a.org #+COLUMNS: %50ITEM %5Effort %5CLOCKSUM %5Remaining #+BEGIN_SRC emacs-lisp ;; (unload-feature 'my-progress) ;; (setq org-clock-in-prepare-hook nil ;; org-clock-out-hook nil ;; org-export-filter-final-output-functions nil) (load (concat default-directory "my-progress.el") nil t t) (add-to-list 'org-export-filter-final-output-functions 'my-progress-remove-toc-heading) #+END_SRC #+BEGIN_SRC emacs-lisp (setq org-properties-postprocess-alist '(("Remaining" lambda(value) (let ((clocksum (org-clock-sum-current-item)) (effort (org-duration-string-to-minutes (org-entry-get (point) "Effort")))) (org-minutes-to-hh:mm-string (- effort clocksum)))))) (require 'org-inlinetask) (add-hook 'org-clock-in-prepare-hook 'my-progress-org-mode-ask-effort 'append) (add-hook 'org-clock-out-hook (lambda () (org-set-property "Remaining" 0)) 'append) #+END_SRC * Heading One ** A sub heading *************** TODO Inline ting CLOCK: [2012-10-10 Wed 11:07]--[2012-10-10 Wed 11:34] => 0:27 CLOCK: [2012-10-10 Wed 10:55]--[2012-10-10 Wed 11:04] => 0:09 :PROPERTIES: :Effort: 1:00 :Remaining: 0:51 :END: *************** END ** Another sub heading *************** TODO Write this bit CLOCK: [2012-10-10 Wed 11:10]--[2012-10-10 Wed 11:59] => 0:49 :PROPERTIES: :Effort: 1:20 :Remaining: 0:31 :END: *************** END ** Heading with no inline tasks *** a This heading could be omitted because it has no tasks. * Heading Two *************** TODO Finish writing under heading two *************** END ** Sub heading that will show up in the table *** Will this one? *************** TODO Test CLOCK: [2012-10-10 Wed 11:55]--[2012-10-10 Wed 11:56] => 0:01 CLOCK: [2012-10-10 Wed 11:50]--[2012-10-10 Wed 11:54] => 0:04 :PROPERTIES: :Effort: 0:20 :Remaining: 0:14 :END: *************** END **** And this? ** Yet another subheading *** A sub sub heading And an inline task: *************** TODO Do an inline thing CLOCK: [2012-10-10 Wed 11:45]--[2012-10-10 Wed 11:50] => 0:05 :PROPERTIES: :Remaining: 0:15 :Effort: 0:20 :END: *************** END *** Another sub sub heading **** This heading contains no tasks ...and so will not appear in the progress table. #+INCLUDE: "b.org" :minlevel 1 * Progress table #+name: progressTbl #+BEGIN: columnview-toc :id file:/home/myles/tmp/toctbl/mwe/a.org | ITEM | Effort | CLOCKSUM | Remaining | | |----------------------------------------------------+--------+----------+-----------+---| | .1 Heading One | | 1:25 | | | | ... 1.1 A sub heading | | 0:36 | | | | . Inline ting | 1:00 | 0:36 | 0:51 | | | ... 1.2 Another sub heading | | 0:49 | | | | . Write this bit | 1:20 | 0:49 | 0:31 | | | ... 1.3 Heading with no inline tasks | | | | | | ...... 1.3.1 a | | | | | | .2 Heading Two | | 0:10 | | | | . Finish writing under heading two | | | | | | ... 2.1 Sub heading that will show up in the table | | 0:05 | | | | ...... 2.1.1 Will this one? | | 0:05 | | | | . Test | 0:20 | 0:05 | 0:14 | | | ... 2.2 Yet another subheading | | 0:05 | | | | ...... 2.2.1 A sub sub heading | | 0:05 | | | | . Do an inline thing | 0:20 | 0:05 | 0:15 | | | ...... 2.2.2 Another sub sub heading | | | | | | .5 Progress table | | | | | |----------------------------------------------------+--------+----------+-----------+---| | Total time [H:M]: | 1:35 | | | | #+END: #+BEGIN_SRC elisp (org-export-as 'my-progress-toc) #+END_SRC #+RESULTS: #+begin_example 1 Heading One .. 1.1 A sub heading .. 1.2 Another sub heading .. 1.3 Heading with no inline tasks ..... 1.3.1 a 2 Heading Two .. 2.1 Sub heading that will show up in the table ..... 2.1.1 Will this one? .. 2.2 Yet another subheading ..... 2.2.1 A sub sub heading ..... 2.2.2 Another sub sub heading 3 Heading Three of main doc .. 3.1 An included sub heading .. 3.2 Yet another included subheading ..... 3.2.1 An included sub sub heading 4 Heading Four 5 Progress table #+end_example --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=b.org * Heading Three of main doc ** An included sub heading ** Yet another included subheading *** An included sub sub heading And an inline task: *************** TODO Yet More Do an included inline thing CLOCK: [2012-10-10 Wed 12:42]--[2012-10-10 Wed 13:42] => 1:00 :PROPERTIES: :Effort: 1:30 :Remaining: 0:30 :END: *************** END * Heading Four *************** TODO Finish writing under included heading two *************** END --=-=-=--