From 40ce6b75e6245659a3a14622356e32e7dd1125dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Delafond?= Date: Sun, 13 Jul 2014 13:35:29 +0200 Subject: Imported Upstream version 8.2.1 --- contrib/lisp/ox-taskjuggler.el | 237 ++++++++++++++++++++++++++++++----------- 1 file changed, 172 insertions(+), 65 deletions(-) (limited to 'contrib/lisp/ox-taskjuggler.el') diff --git a/contrib/lisp/ox-taskjuggler.el b/contrib/lisp/ox-taskjuggler.el index 4724ec3..13f2f5e 100644 --- a/contrib/lisp/ox-taskjuggler.el +++ b/contrib/lisp/ox-taskjuggler.el @@ -141,6 +141,7 @@ ;; org-global-properties-fixed ;; - What about property inheritance and org-property-inherit-p? ;; - Use TYPE_TODO as an way to assign resources +;; - Add support for org-export-with-planning ;; ;;; Code: @@ -213,7 +214,7 @@ marked with `org-taskjuggler-project-tag'" (defcustom org-taskjuggler-default-reports '("textreport report \"Plan\" { formats html - header '== <-query attribute=\"name\"-> ==' + header '== %title ==' center -8<- [#Plan Plan] | [#Resource_Allocation Resource Allocation] @@ -245,10 +246,11 @@ resourcereport resourceGraph \"\" { }") "Default reports for the project. These are sensible default reports to give a good out-of-the-box -result when exporting without defining any reports. If you want -to define your own reports you can change them here or simply -define the default reports so that they include an external -report definition as follows: +result when exporting without defining any reports. \"%title\" +anywhere in the reports will be replaced with the document title. +If you want to define your own reports you can change them here +or simply define the default reports so that they include an +external report definition as follows: include reports.tji @@ -324,6 +326,31 @@ If one of these appears as a property for a headline, it will be exported with the corresponding report." :group 'org-export-taskjuggler) +(defcustom org-taskjuggler-process-command + "tj3 --silent --no-color --output-dir %o %f" + "Command to process a Taskjuggler file. +The command will be given to the shell as a command to process a +Taskjuggler file. \"%f\" in the command will be replaced by the +full file name, \"%o\" by the reports directory (see +`org-taskjuggler-reports-directory'). + +If you are targeting Taskjuggler 2.4 (see +`org-taskjuggler-target-version') this setting is ignored." + :group 'org-export-taskjuggler) + +(defcustom org-taskjuggler-reports-directory "reports" + "Default directory to generate the Taskjuggler reports in. +The command `org-taskjuggler-process-command' generates the +reports and associated files such as CSS inside this directory. + +If the directory is not an absolute path it is relative to the +directory of the exported file. The directory is created if it +doesn't exist. + +If you are targeting Taskjuggler 2.4 (see +`org-taskjuggler-target-version') this setting is ignored." + :group 'org-export-taskjuggler) + (defcustom org-taskjuggler-keep-project-as-task t "Non-nil keeps the project headline as an umbrella task for all tasks. Setting this to nil will allow maintaining completely separated @@ -348,10 +375,14 @@ This hook is run with the name of the file as argument.") :menu-entry '(?J "Export to TaskJuggler" ((?j "As TJP file" (lambda (a s v b) (org-taskjuggler-export a s v))) - (?o "As TJP file and open" + (?p "As TJP file and process" (lambda (a s v b) (if a (org-taskjuggler-export a s v) - (org-taskjuggler-export-and-open s v)))))) + (org-taskjuggler-export-and-process s v)))) + (?o "As TJP file, process and open" + (lambda (a s v b) + (if a (org-taskjuggler-export a s v) + (org-taskjuggler-export-process-and-open s v)))))) ;; This property will be used to store unique ids in communication ;; channel. Ids will be retrieved with `org-taskjuggler-get-id'. :options-alist '((:taskjuggler-unique-ids nil nil nil))) @@ -367,6 +398,7 @@ communication channel. Return value is an alist between headlines and their associated ID. IDs are hierarchical, which means they only need to be unique among the task siblings." (let* (alist + build-id ; For byte-compiler. (build-id (lambda (tasks local-ids) (org-element-map tasks 'headline @@ -402,13 +434,14 @@ INFO is a plist used as a communication channel. First headline in buffer with `org-taskjuggler-project-tag' defines the project. If no such task is defined, pick the first headline in buffer. If there is no headline at all, return nil." - (or (org-element-map (plist-get info :parse-tree) 'headline - (lambda (hl) - (and (member org-taskjuggler-project-tag - (org-export-get-tags hl info)) - hl)) - info t) - (org-element-map tree 'headline 'identity info t))) + (let ((tree (plist-get info :parse-tree))) + (or (org-element-map tree 'headline + (lambda (hl) + (and (member org-taskjuggler-project-tag + (org-export-get-tags hl info)) + hl)) + info t) + (org-element-map tree 'headline 'identity info t)))) (defun org-taskjuggler-get-id (item info) "Return id for task or resource ITEM. @@ -426,14 +459,17 @@ ITEM is a headline. Return value is a string." (defun org-taskjuggler-get-start (item) "Return start date for task or resource ITEM. ITEM is a headline. Return value is a string or nil if ITEM -doesn't have any start date defined.." +doesn't have any start date defined." (let ((scheduled (org-element-property :scheduled item))) - (and scheduled (org-timestamp-format scheduled "%Y-%02m-%02d")))) + (or + (and scheduled (org-timestamp-format scheduled "%Y-%02m-%02d")) + (and (memq 'start org-taskjuggler-valid-task-attributes) + (org-element-property :START item))))) (defun org-taskjuggler-get-end (item) "Return end date for task or resource ITEM. ITEM is a headline. Return value is a string or nil if ITEM -doesn't have any end date defined.." +doesn't have any end date defined." (let ((deadline (org-element-property :deadline item))) (and deadline (org-timestamp-format deadline "%Y-%02m-%02d")))) @@ -514,7 +550,8 @@ channel." (setq depends (org-element-map tasks 'headline (lambda (task) - (let ((task-id (org-element-property :TASK_ID task))) + (let ((task-id (or (org-element-property :TASK_ID task) + (org-element-property :ID task)))) (and task-id (member task-id deps-ids) task))) info))) ;; Check BLOCKER and DEPENDS properties. If "previous-sibling" @@ -538,34 +575,34 @@ DEPENDENCIES is list of dependencies for TASK, as returned by `org-taskjuggler-resolve-depedencies'. TASK is a headline. INFO is a plist used as a communication channel. Return value doesn't include leading \"depends\"." - (let ((dep-str (concat (org-element-property :BLOCKER task) - " " - (org-element-property :DEPENDS task))) - (get-path - (lambda (dep) - ;; Return path to DEP relatively to TASK. - (let ((parent (org-export-get-parent task)) - (exclamations 1) - (option - (let ((id (org-element-property :TASK_ID dep))) - (and id - (string-match (concat id " +\\({.*?}\\)") dep-str) - (org-match-string-no-properties 1)))) - path) - ;; Compute number of exclamation marks by looking for the - ;; common ancestor between TASK and DEP. - (while (not (org-element-map parent 'headline - (lambda (hl) (eq hl dep)))) - (incf exclamations) - (setq parent (org-export-get-parent parent))) - ;; Build path from DEP to PARENT. - (while (not (eq parent dep)) - (push (org-taskjuggler-get-id dep info) path) - (setq dep (org-export-get-parent dep))) - ;; Return full path. Add dependency options, if any. - (concat (make-string exclamations ?!) - (mapconcat 'identity path ".") - (and option (concat " " option))))))) + (let* ((dep-str (concat (org-element-property :BLOCKER task) + " " + (org-element-property :DEPENDS task))) + (get-path + (lambda (dep) + ;; Return path to DEP relatively to TASK. + (let ((parent (org-export-get-parent task)) + (exclamations 1) + (option + (let ((id (org-element-property :TASK_ID dep))) + (and id + (string-match (concat id " +\\({.*?}\\)") dep-str) + (org-match-string-no-properties 1)))) + path) + ;; Compute number of exclamation marks by looking for the + ;; common ancestor between TASK and DEP. + (while (not (org-element-map parent 'headline + (lambda (hl) (eq hl dep)))) + (incf exclamations) + (setq parent (org-export-get-parent parent))) + ;; Build path from DEP to PARENT. + (while (not (eq parent dep)) + (push (org-taskjuggler-get-id dep info) path) + (setq dep (org-export-get-parent dep))) + ;; Return full path. Add dependency options, if any. + (concat (make-string exclamations ?!) + (mapconcat 'identity path ".") + (and option (concat " " option))))))) ;; Return dependencies string, without the leading "depends". (mapconcat (lambda (dep) (funcall get-path dep)) dependencies ", "))) @@ -659,8 +696,18 @@ Return complete project plan as a string in TaskJuggler syntax." (mapconcat (lambda (report) (org-taskjuggler--build-report report info)) main-reports "") - (mapconcat 'org-element-normalize-string - org-taskjuggler-default-reports "")))))))) + ;; insert title in default reports + (let* ((title (org-export-data (plist-get info :title) info)) + (report-title (if (string= title "") + (org-taskjuggler-get-name project) + title))) + (mapconcat + 'org-element-normalize-string + (mapcar + (function + (lambda (report) + (replace-regexp-in-string "%title" report-title report t t))) + org-taskjuggler-default-reports) ""))))))))) (defun org-taskjuggler--build-project (project info) "Return a project declaration. @@ -847,20 +894,14 @@ Return output file's name." (interactive) (let ((outfile (org-export-output-file-name org-taskjuggler-extension subtreep))) - (if async - (org-export-async-start - (lambda (f) - (org-export-add-to-stack f 'taskjuggler) - (run-hook-with-args 'org-taskjuggler-final-hook f)) - `(expand-file-name - (org-export-to-file 'taskjuggler ,outfile ,subtreep ,visible-only))) - (org-export-to-file 'taskjuggler outfile subtreep visible-only) - (run-hook-with-args 'org-taskjuggler-final-hook outfile) - outfile))) + (org-export-to-file 'taskjuggler outfile + async subtreep visible-only nil nil + (lambda (file) + (run-hook-with-args 'org-taskjuggler-final-hook file) nil)))) ;;;###autoload -(defun org-taskjuggler-export-and-open (&optional subtreep visible-only) - "Export current buffer to a TaskJuggler file and open it. +(defun org-taskjuggler-export-and-process (&optional subtreep visible-only) + "Export current buffer to a TaskJuggler file and process it. The exporter looks for a tree with tag that matches `org-taskjuggler-project-tag' and takes this as the tasks for @@ -887,12 +928,78 @@ first. When optional argument VISIBLE-ONLY is non-nil, don't export contents of hidden elements. -Open file with the TaskJuggler GUI." +Return a list of reports." + (interactive) + (let ((file (org-taskjuggler-export nil subtreep visible-only))) + (org-taskjuggler-compile file))) + +;;;###autoload +(defun org-taskjuggler-export-process-and-open (&optional subtreep visible-only) + "Export current buffer to a TaskJuggler file, process and open it. + +Export and process the file using +`org-taskjuggler-export-and-process' and open the generated +reports with a browser. + +If you are targeting TaskJuggler 2.4 (see +`org-taskjuggler-target-version') the processing and display of +the reports is done using the TaskJuggler GUI." (interactive) - (let* ((file (org-taskjuggler-export nil subtreep visible-only)) - (process-name "TaskJugglerUI") - (command (concat process-name " " file))) - (start-process-shell-command process-name nil command))) + (if (< org-taskjuggler-target-version 3.0) + (let* ((process-name "TaskJugglerUI") + (command + (concat process-name " " + (org-taskjuggler-export nil subtreep visible-only)))) + (start-process-shell-command process-name nil command)) + (dolist (report (org-taskjuggler-export-and-process subtreep visible-only)) + (org-open-file report)))) + +(defun org-taskjuggler-compile (file) + "Compile a TaskJuggler file. + +FILE is the name of the file being compiled. Processing is done +through the command given in `org-taskjuggler-process-command'. + +Return a list of reports." + (let* ((full-name (file-truename file)) + (out-dir + (expand-file-name + org-taskjuggler-reports-directory (file-name-directory file))) + errors) + (message (format "Processing TaskJuggler file %s..." file)) + (save-window-excursion + (let ((outbuf (get-buffer-create "*Org Taskjuggler Output*"))) + (unless (file-directory-p out-dir) + (make-directory out-dir t)) + (with-current-buffer outbuf (erase-buffer)) + (shell-command + (replace-regexp-in-string + "%f" (shell-quote-argument full-name) + (replace-regexp-in-string + "%o" (shell-quote-argument out-dir) + org-taskjuggler-process-command t t) t t) outbuf) + ;; Collect standard errors from output buffer. + (setq errors (org-taskjuggler--collect-errors outbuf))) + (if (not errors) + (message "Process completed.") + (error (format "TaskJuggler failed with errors: %s" errors)))) + (file-expand-wildcards (format "%s/*.html" out-dir)))) + +(defun org-taskjuggler--collect-errors (buffer) + "Collect some kind of errors from \"tj3\" command output. + +BUFFER is the buffer containing output. + +Return collected error types as a string, or nil if there was +none." + (with-current-buffer buffer + (save-excursion + (goto-char (point-min)) + (let ((case-fold-search t) + (errors "")) + (while (re-search-forward "^.+:[0-9]+: \\(.*\\)$" nil t) + (setq errors (concat errors " " (match-string 1)))) + (and (org-string-nw-p errors) (org-trim errors)))))) (provide 'ox-taskjuggler) -- cgit v1.2.3