branch: elpa/org-contrib commit 8e32471fc8d7b4edc07e416488e55f0f33c86f23 Author: Harold Ollivier <h...@mailbox.org> Commit: Bastien Guerry <b...@gnu.org>
ox-taskjuggler.el: Update * lisp/ox-taskjuggler.el (org-taskjuggler-report-tag): New option. (org-taskjuggler-valid-resource-attributes): New argument. (org-taskjuggler-valid-account-attributes): New option. (org-taskjuggler-valid-report-attributes): New arguments. (org-taskjuggler-assign-account-ids): New function. (org-taskjuggler-get-end): Use `org-taskjuggler-valid-task-attributes'. (org-taskjuggler-project-plan): Use `org-taskjuggler-account-tag'. (org-taskjuggler-project-plan): Rewrite. (org-taskjuggler--build-account): New function. (org-taskjuggler--build-report): Update. --- lisp/ox-taskjuggler.el | 238 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 165 insertions(+), 73 deletions(-) diff --git a/lisp/ox-taskjuggler.el b/lisp/ox-taskjuggler.el index 4144685e1a..19e4da9d01 100644 --- a/lisp/ox-taskjuggler.el +++ b/lisp/ox-taskjuggler.el @@ -177,6 +177,13 @@ for the project." :group 'org-export-taskjuggler :type 'string) +(defcustom org-taskjuggler-account-tag "taskjuggler_account" + "Tag marking project's accounts. +This tag is used to find the tree containing all the accounts +for the project." + :group 'org-export-taskjuggler + :type 'string) + (defcustom org-taskjuggler-report-tag "taskjuggler_report" "Tag marking project's reports. This tag is used to find the tree containing all the reports for @@ -330,16 +337,23 @@ list." (defcustom org-taskjuggler-valid-resource-attributes '(limits vacation shift booking efficiency journalentry rate - workinghours flags) + workinghours flags chargeset) "Valid attributes for Taskjuggler resources. If one of these appears as a property for a headline, it will be exported with the corresponding resource." :group 'org-export-taskjuggler :type '(repeat symbol)) +(defcustom org-taskjuggler-valid-account-attributes + '(aggregate credits flags) + "Valid attributes for Taskjuggler accounts. +If one of these appears as a property for a headline, it will be +exported with the corresponding account." + :group 'org-export-taskjuggler) + (defcustom org-taskjuggler-valid-report-attributes - '(headline columns definitions timeformat hideresource hidetask - loadunit sorttasks formats period) + '(headline columns definitions timeformat hideaccount hideresource hidetask + loadunit sorttasks formats period start end) "Valid attributes for Taskjuggler reports. If one of these appears as a property for a headline, it will be exported with the corresponding report." @@ -446,6 +460,19 @@ headlines and their associated ID." (cons resource id))) info))) +(defun org-taskjuggler-assign-account-ids (accounts info) + "Assign a unique ID to each account within ACCOUNTS. +ACCOUNTS is a list of headlines. INFO is a plist used as a +communication channel. Return value is an alist between +headlines and their associated ID." + (let (ids) + (org-element-map accounts 'headline + (lambda (account) + (let ((id (org-taskjuggler--build-unique-id account ids))) + (push id ids) + (cons account id))) + info))) + ;;; Accessors @@ -497,11 +524,10 @@ doesn't have any start date defined." ITEM is a headline. Return value is a string or nil if ITEM doesn't have any end date defined." (let ((deadline (org-element-property :deadline item))) - (and deadline (funcall (eval-and-compile - (if (fboundp 'org-format-timestamp) - #'org-format-timestamp - (with-no-warnings #'org-timestamp-format))) - deadline "%Y-%02m-%02d")))) + (or + (and deadline (org-timestamp-format deadline "%Y-%02m-%02d")) + (and (memq 'end org-taskjuggler-valid-task-attributes) + (org-element-property :END item))))) @@ -654,89 +680,115 @@ Return complete project plan as a string in TaskJuggler syntax." (org-taskjuggler--build-project project info) ;; 3. Insert global properties. (org-element-normalize-string org-taskjuggler-default-global-properties) - ;; 4. Insert resources. Provide a default one if none is + ;; 3.5. Insert accounts. Provide a default one if none is ;; specified. - (let ((main-resources + (let ((main-accounts ;; Collect contents from various trees marked with - ;; `org-taskjuggler-resource-tag'. Only gather top level - ;; resources. + ;; `org-taskjuggler-account-tag'. Only gather top level + ;; accounts. (apply 'append (org-element-map tree 'headline (lambda (hl) - (and (member org-taskjuggler-resource-tag + (and (member org-taskjuggler-account-tag (org-export-get-tags hl info)) (org-element-map (org-element-contents hl) 'headline 'identity info nil 'headline))) info nil 'headline)))) - ;; Assign a unique ID to each resource. Store it under + ;; Assign a unique ID to each account. Store it under ;; `:taskjuggler-unique-ids' property in INFO. (setq info (plist-put info :taskjuggler-unique-ids - (org-taskjuggler-assign-resource-ids - main-resources info))) + (org-taskjuggler-assign-account-ids + main-accounts info))) (concat - (if main-resources + (if main-accounts (mapconcat - (lambda (resource) (org-taskjuggler--build-resource resource info)) - main-resources "") - (format "resource %s \"%s\" {\n}\n" (user-login-name) user-full-name)) - ;; 5. Insert tasks. - (let ((main-tasks - ;; If `org-taskjuggler-keep-project-as-task' is - ;; non-nil, there is only one task. Otherwise, every - ;; direct children of PROJECT is a top level task. - (if org-taskjuggler-keep-project-as-task (list project) - (or (org-element-map (org-element-contents project) 'headline - 'identity info nil 'headline) - (error "No task specified"))))) - ;; Assign a unique ID to each task. Add it to - ;; `:taskjuggler-unique-ids' property in INFO. - (setq info - (plist-put info :taskjuggler-unique-ids - (append - (org-taskjuggler-assign-task-ids main-tasks info) - (plist-get info :taskjuggler-unique-ids)))) - ;; If no resource is allocated among tasks, allocate one to - ;; the first task. - (unless (org-element-map main-tasks 'headline - (lambda (task) (org-element-property :ALLOCATE task)) - info t) - (org-element-put-property - (car main-tasks) :ALLOCATE - (or (org-taskjuggler-get-id (car main-resources) info) - (user-login-name)))) - (mapconcat - (lambda (task) (org-taskjuggler--build-task task info)) - main-tasks "")) - ;; 6. Insert reports. If no report is defined, insert default - ;; reports. - (let ((main-reports + (lambda (account) (org-taskjuggler--build-account account info)) + main-accounts "") + (format "account %s \"%s\" {\n}\n" (user-login-name) user-full-name)) + ;; 4. Insert resources. Provide a default one if none is + ;; specified. + (let ((main-resources ;; Collect contents from various trees marked with - ;; `org-taskjuggler-report-tag'. Only gather top level - ;; reports. + ;; `org-taskjuggler-resource-tag'. Only gather top level + ;; resources. (apply 'append (org-element-map tree 'headline (lambda (hl) - (and (member org-taskjuggler-report-tag + (and (member org-taskjuggler-resource-tag (org-export-get-tags hl info)) - (org-element-map (org-element-contents hl) - 'headline 'identity info nil 'headline))) + (org-element-map (org-element-contents hl) 'headline + 'identity info nil 'headline))) info nil 'headline)))) - (if main-reports - (mapconcat - (lambda (report) (org-taskjuggler--build-report report info)) - main-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 - (lambda (report) - (replace-regexp-in-string "%title" report-title report t t)) - org-taskjuggler-default-reports) ""))))))))) + ;; Assign a unique ID to each resource. Store it under + ;; `:taskjuggler-unique-ids' property in INFO. + (setq info + (plist-put info :taskjuggler-unique-ids + (org-taskjuggler-assign-resource-ids + main-resources info))) + (concat + (if main-resources + (mapconcat + (lambda (resource) (org-taskjuggler--build-resource resource info)) + main-resources "") + (format "resource %s \"%s\" {\n}\n" (user-login-name) user-full-name)) + ;; 5. Insert tasks. + (let ((main-tasks + ;; If `org-taskjuggler-keep-project-as-task' is + ;; non-nil, there is only one task. Otherwise, every + ;; direct children of PROJECT is a top level task. + (if org-taskjuggler-keep-project-as-task (list project) + (or (org-element-map (org-element-contents project) 'headline + 'identity info nil 'headline) + (error "No task specified"))))) + ;; Assign a unique ID to each task. Add it to + ;; `:taskjuggler-unique-ids' property in INFO. + (setq info + (plist-put info :taskjuggler-unique-ids + (append + (org-taskjuggler-assign-task-ids main-tasks info) + (plist-get info :taskjuggler-unique-ids)))) + ;; If no resource is allocated among tasks, allocate one to + ;; the first task. + (unless (org-element-map main-tasks 'headline + (lambda (task) (org-element-property :ALLOCATE task)) + info t) + (org-element-put-property + (car main-tasks) :ALLOCATE + (or (org-taskjuggler-get-id (car main-resources) info) + (user-login-name)))) + (mapconcat + (lambda (task) (org-taskjuggler--build-task task info)) + main-tasks "")) + ;; 6. Insert reports. If no report is defined, insert default + ;; reports. + (let ((main-reports + ;; Collect contents from various trees marked with + ;; `org-taskjuggler-report-tag'. Only gather top level + ;; reports. + (apply 'append + (org-element-map tree 'headline + (lambda (hl) + (and (member org-taskjuggler-report-tag + (org-export-get-tags hl info)) + (org-element-map (org-element-contents hl) + 'headline 'identity info nil 'headline))) + info nil 'headline)))) + (if main-reports + (mapconcat + (lambda (report) (org-taskjuggler--build-report report info)) + main-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 + (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. @@ -799,20 +851,60 @@ neither is defined a unique id will be associated to it." ;; Closing resource. "}\n")) +(defun org-taskjuggler--build-account (account info) + "Return a account declaration. + +ACCOUNT is a headline. INFO is a plist used as a communication +channel. + +All valid attributes from ACCOUNT are inserted. If ACCOUNT +defines a property \"account_id\" it will be used as the id for +this account. Otherwise it will use the ID property. If +neither is defined a unique id will be associated to it." + (concat + ;; Opening account. + (format "account %s \"%s\" {\n" + (org-taskjuggler--clean-id + (or (org-element-property :ACCOUNT_ID account) + (org-element-property :ID account) + (org-taskjuggler-get-id account info))) + (org-taskjuggler-get-name account)) + ;; Add attributes. + (org-taskjuggler--indent-string + (org-taskjuggler--build-attributes + account org-taskjuggler-valid-account-attributes)) + ;; Add inner accounts. + (org-taskjuggler--indent-string + (mapconcat + 'identity + (org-element-map (org-element-contents account) 'headline + (lambda (hl) (org-taskjuggler--build-account hl info)) + info nil 'headline) + "")) + ;; Closing account. + "}\n")) + (defun org-taskjuggler--build-report (report info) "Return a report declaration. REPORT is a headline. INFO is a plist used as a communication channel." (concat ;; Opening report. - (format "%s \"%s\" {\n" + (format "%s %s \"%s\" {\n" (or (org-element-property :REPORT_KIND report) "taskreport") + (or (org-element-property :REPORT_ID report) + (org-element-property :ID report) + (org-taskjuggler-get-id report info)) (org-taskjuggler-get-name report)) ;; Add attributes. (org-taskjuggler--indent-string (org-taskjuggler--build-attributes report org-taskjuggler-valid-report-attributes)) - ;; Add inner reports. + ;; Add core of report, ie the first paragraph after the headline + ;; and before the next sub-headline + (format "%s" (if (eq (org-element-type (car (org-element-contents report))) 'section) + (org-element-interpret-data (car (org-element-contents report))) + "")) (org-taskjuggler--indent-string (mapconcat 'identity