X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/95b6d681b1121e1be8955aa3f79dd39098edf4cf..8223b1d23361b74ede10bac47974ce7803804380:/lisp/org/org-taskjuggler.el diff --git a/lisp/org/org-taskjuggler.el b/lisp/org/org-taskjuggler.el index da9e156870..aa645d296e 100644 --- a/lisp/org/org-taskjuggler.el +++ b/lisp/org/org-taskjuggler.el @@ -1,10 +1,9 @@ ;;; org-taskjuggler.el --- TaskJuggler exporter for org-mode ;; -;; Copyright (C) 2007, 2008, 2009, 2010 Free Software Foundation, Inc. +;; Copyright (C) 2007-2012 Free Software Foundation, Inc. ;; ;; Emacs Lisp Archive Entry ;; Filename: org-taskjuggler.el -;; Version: 7.01 ;; Author: Christian Egli ;; Maintainer: Christian Egli ;; Keywords: org, taskjuggler, project planning @@ -30,7 +29,7 @@ ;; ;; This library implements a TaskJuggler exporter for org-mode. ;; TaskJuggler uses a text format to define projects, tasks and -;; resources, so it is a natural fit for org-mode. It can produce all +;; resources, so it is a natural fit for org-mode. It can produce all ;; sorts of reports for tasks or resources in either HTML, CSV or PDF. ;; The current version of TaskJuggler requires KDE but the next ;; version is implemented in Ruby and should therefore run on any @@ -43,7 +42,7 @@ ;; ;; Instead the TaskJuggler exporter looks for a tree that defines the ;; tasks and a optionally tree that defines the resources for this -;; project. It then creates a TaskJuggler file based on these trees +;; project. It then creates a TaskJuggler file based on these trees ;; and the attributes defined in all the nodes. ;; ;; * Installation @@ -61,8 +60,8 @@ ;; ;; * Tasks ;; -;; Let's illustrate the usage with a small example. Create your tasks -;; as you usually do with org-mode. Assign efforts to each task using +;; Let's illustrate the usage with a small example. Create your tasks +;; as you usually do with org-mode. Assign efforts to each task using ;; properties (it's easiest to do this in the column view). You should ;; end up with something similar to the example by Peter Jones in ;; http://www.contextualdevelopment.com/static/artifacts/articles/2008/project-planning/project-planning.org. @@ -70,13 +69,13 @@ ;; "taskjuggler_project" (or whatever you customized ;; `org-export-taskjuggler-project-tag' to). You are now ready to ;; export the project plan with `org-export-as-taskjuggler-and-open' -;; which will export the project plan and open a gant chart in +;; which will export the project plan and open a Gantt chart in ;; TaskJugglerUI. ;; ;; * Resources -;; +;; ;; Next you can define resources and assign those to work on specific -;; tasks. You can group your resources hierarchically. Tag the top +;; tasks. You can group your resources hierarchically. Tag the top ;; node of the resources with "taskjuggler_resource" (or whatever you ;; customized `org-export-taskjuggler-resource-tag' to). You can ;; optionally assign an identifier (named "resource_id") to the @@ -85,8 +84,8 @@ ;; picks the first word of the headline as the identifier as long as ;; it is unique, see the documentation of ;; `org-taskjuggler-get-unique-id'). Using that identifier you can -;; then allocate resources to tasks. This is again done with the -;; "allocate" property on the tasks. Do this in column view or when on +;; then allocate resources to tasks. This is again done with the +;; "allocate" property on the tasks. Do this in column view or when on ;; the task type ;; ;; C-c C-x p allocate RET RET @@ -107,17 +106,17 @@ ;; etc for tasks. ;; ;; * Dependencies -;; +;; ;; The exporter will handle dependencies that are defined in the tasks ;; either with the ORDERED attribute (see TODO dependencies in the Org ;; mode manual) or with the BLOCKER attribute (see org-depend.el) or -;; alternatively with a depends attribute. Both the BLOCKER and the +;; alternatively with a depends attribute. Both the BLOCKER and the ;; depends attribute can be either "previous-sibling" or a reference ;; to an identifier (named "task_id") which is defined for another -;; task in the project. BLOCKER and the depends attribute can define -;; multiple dependencies separated by either space or comma. You can +;; task in the project. BLOCKER and the depends attribute can define +;; multiple dependencies separated by either space or comma. You can ;; also specify optional attributes on the dependency by simply -;; appending it. The following examples should illustrate this: +;; appending it. The following examples should illustrate this: ;; ;; * Training material ;; :PROPERTIES: @@ -126,18 +125,18 @@ ;; :END: ;; ** Markup Guidelines ;; :PROPERTIES: -;; :Effort: 2.0 +;; :Effort: 2d ;; :END: ;; ** Workflow Guidelines ;; :PROPERTIES: -;; :Effort: 2.0 +;; :Effort: 2d ;; :END: ;; * Presentation ;; :PROPERTIES: -;; :Effort: 2.0 +;; :Effort: 2d ;; :BLOCKER: training_material { gapduration 1d } some_other_task ;; :END: -;; +;; ;;;; * TODO ;; - Use SCHEDULED and DEADLINE information (not just start and end ;; properties). @@ -145,7 +144,7 @@ ;; org-global-properties-fixed ;; - What about property inheritance and org-property-inherit-p? ;; - Use TYPE_TODO as an way to assign resources -;; - Make sure multiple dependency definitions (i.e. BLOCKER on +;; - Make sure multiple dependency definitions (i.e. BLOCKER on ;; previous-sibling and on a specific task_id) in multiple ;; attributes are properly exported. ;; @@ -167,23 +166,33 @@ (defcustom org-export-taskjuggler-extension ".tjp" "Extension of TaskJuggler files." :group 'org-export-taskjuggler + :version "24.1" :type 'string) (defcustom org-export-taskjuggler-project-tag "taskjuggler_project" "Tag, property or todo used to find the tree containing all the tasks for the project." :group 'org-export-taskjuggler + :version "24.1" :type 'string) (defcustom org-export-taskjuggler-resource-tag "taskjuggler_resource" "Tag, property or todo used to find the tree containing all the resources for the project." :group 'org-export-taskjuggler + :version "24.1" :type 'string) +(defcustom org-export-taskjuggler-target-version 2.4 + "Which version of TaskJuggler the exporter is targeting." + :group 'org-export-taskjuggler + :version "24.1" + :type 'number) + (defcustom org-export-taskjuggler-default-project-version "1.0" "Default version string for the project." :group 'org-export-taskjuggler + :version "24.1" :type 'string) (defcustom org-export-taskjuggler-default-project-duration 280 @@ -191,9 +200,10 @@ resources for the project." in the root node of the task tree, i.e. the tree that has been marked with `org-export-taskjuggler-project-tag'" :group 'org-export-taskjuggler + :version "24.1" :type 'integer) -(defcustom org-export-taskjuggler-default-reports +(defcustom org-export-taskjuggler-default-reports '("taskreport \"Gantt Chart\" { headline \"Project Gantt Chart\" columns hierarchindex, name, start, end, effort, duration, completed, chart @@ -201,7 +211,7 @@ with `org-export-taskjuggler-project-tag'" hideresource 1 loadunit shortauto }" -"resourcereport \"Resource Graph\" { + "resourcereport \"Resource Graph\" { headline \"Resource Allocation Graph\" columns no, name, utilization, freeload, chart loadunit shortauto @@ -210,22 +220,24 @@ with `org-export-taskjuggler-project-tag'" }") "Default reports for the project." :group 'org-export-taskjuggler + :version "24.1" :type '(repeat (string :tag "Report"))) -(defcustom org-export-taskjuggler-default-global-properties +(defcustom org-export-taskjuggler-default-global-properties "shift s40 \"Part time shift\" { workinghours wed, thu, fri off } " - "Default global properties for the project. Here you typically + "Default global properties for the project. Here you typically define global properties such as shifts, accounts, rates, -vacation, macros and flags. Any property that is allowed within -the TaskJuggler file can be inserted. You could for example -include another TaskJuggler file. +vacation, macros and flags. Any property that is allowed within +the TaskJuggler file can be inserted. You could for example +include another TaskJuggler file. The global properties are inserted after the project declaration but before any resource and task declarations." :group 'org-export-taskjuggler + :version "24.1" :type '(string :tag "Preamble")) ;;; Hooks @@ -243,12 +255,12 @@ but before any resource and task declarations." "Export parts of the current buffer as a TaskJuggler file. The exporter looks for a tree with tag, property or todo that matches `org-export-taskjuggler-project-tag' and takes this as -the tasks for this project. The first node of this tree defines +the tasks for this project. The first node of this tree defines the project properties such as project name and project period. If there is a tree with tag, property or todo that matches `org-export-taskjuggler-resource-tag' this three is taken as -resources for the project. If no resources are specified, a -default resource is created and allocated to the project. Also +resources for the project. If no resources are specified, a +default resource is created and allocated to the project. Also the taskjuggler project will be created with default reports as defined in `org-export-taskjuggler-default-reports'." (interactive) @@ -257,14 +269,15 @@ defined in `org-export-taskjuggler-default-reports'." (setq-default org-done-keywords org-done-keywords) (let* ((tasks (org-taskjuggler-resolve-dependencies - (org-taskjuggler-assign-task-ids - (org-map-entries - '(org-taskjuggler-components) - org-export-taskjuggler-project-tag nil 'archive 'comment)))) + (org-taskjuggler-assign-task-ids + (org-taskjuggler-compute-task-leafiness + (org-map-entries + 'org-taskjuggler-components + org-export-taskjuggler-project-tag nil 'archive 'comment))))) (resources (org-taskjuggler-assign-resource-ids - (org-map-entries - '(org-taskjuggler-components) + (org-map-entries + 'org-taskjuggler-components org-export-taskjuggler-resource-tag nil 'archive 'comment))) (filename (expand-file-name (concat @@ -272,15 +285,16 @@ defined in `org-export-taskjuggler-default-reports'." (file-name-nondirectory buffer-file-name)) org-export-taskjuggler-extension))) (buffer (find-file-noselect filename)) + (old-buffer (current-buffer)) (org-export-taskjuggler-old-level 0) task resource) (unless tasks (error "No tasks specified")) ;; add a default resource (unless resources - (setq resources - `((("resource_id" . ,(user-login-name)) - ("headline" . ,user-full-name) + (setq resources + `((("resource_id" . ,(user-login-name)) + ("headline" . ,user-full-name) ("level" . 1))))) ;; add a default allocation to the first task if none was given (unless (assoc "allocate" (car tasks)) @@ -299,6 +313,7 @@ defined in `org-export-taskjuggler-default-reports'." (setcar tasks (push (cons "version" version) task)))) (with-current-buffer buffer (erase-buffer) + (org-clone-local-variables old-buffer "^org-") (org-taskjuggler-open-project (car tasks)) (insert org-export-taskjuggler-default-global-properties) (insert "\n") @@ -331,9 +346,13 @@ with the TaskJuggler GUI." (command (concat process-name " " file-name))) (start-process-shell-command process-name nil command))) +(defun org-taskjuggler-targeting-tj3-p () + "Return true if we are targeting TaskJuggler III." + (>= org-export-taskjuggler-target-version 3.0)) + (defun org-taskjuggler-parent-is-ordered-p () "Return true if the parent of the current node has a property -\"ORDERED\". Return nil otherwise." +\"ORDERED\". Return nil otherwise." (save-excursion (and (org-up-heading-safe) (org-entry-get (point) "ORDERED")))) @@ -344,7 +363,9 @@ information, all the properties, etc." (let* ((props (org-entry-properties)) (components (org-heading-components)) (level (nth 1 components)) - (headline (nth 4 components)) + (headline + (replace-regexp-in-string + "\"" "\\\"" (nth 4 components) t t)) ; quote double quotes in headlines (parent-ordered (org-taskjuggler-parent-is-ordered-p))) (push (cons "level" level) props) (push (cons "headline" headline) props) @@ -352,7 +373,7 @@ information, all the properties, etc." (defun org-taskjuggler-assign-task-ids (tasks) "Given a list of tasks return the same list assigning a unique id -and the full path to each task. Taskjuggler takes hierarchical ids. +and the full path to each task. Taskjuggler takes hierarchical ids. For that reason we have to make ids locally unique and we have to keep a path to the current task." (let ((previous-level 0) @@ -362,16 +383,16 @@ a path to the current task." (dolist (task tasks resolved-tasks) (let ((level (cdr (assoc "level" task)))) (cond - ((< previous-level level) + ((< previous-level level) (setq unique-id (org-taskjuggler-get-unique-id task (car unique-ids))) (dotimes (tmp (- level previous-level)) (push (list unique-id) unique-ids) (push unique-id path))) - ((= previous-level level) + ((= previous-level level) (setq unique-id (org-taskjuggler-get-unique-id task (car unique-ids))) (push unique-id (car unique-ids)) (setcar path unique-id)) - ((> previous-level level) + ((> previous-level level) (dotimes (tmp (- previous-level level)) (pop unique-ids) (pop path)) @@ -383,18 +404,36 @@ a path to the current task." (setq previous-level level) (setq resolved-tasks (append resolved-tasks (list task))))))) -(defun org-taskjuggler-assign-resource-ids (resources &optional unique-ids) +(defun org-taskjuggler-compute-task-leafiness (tasks) + "Figure out if each task is a leaf by looking at it's level, +and the level of its successor. If the successor is higher (ie +deeper), then it's not a leaf." + (let (new-list) + (while (car tasks) + (let ((task (car tasks)) + (successor (car (cdr tasks)))) + (cond + ;; if a task has no successors it is a leaf + ((null successor) + (push (cons (cons "leaf-node" t) task) new-list)) + ;; if the successor has a lower level than task it is a leaf + ((<= (cdr (assoc "level" successor)) (cdr (assoc "level" task))) + (push (cons (cons "leaf-node" t) task) new-list)) + ;; otherwise examine the rest of the tasks + (t (push task new-list)))) + (setq tasks (cdr tasks))) + (nreverse new-list))) + +(defun org-taskjuggler-assign-resource-ids (resources) "Given a list of resources return the same list, assigning a unique id to each resource." - (cond - ((null resources) nil) - (t - (let* ((resource (car resources)) - (unique-id (org-taskjuggler-get-unique-id resource unique-ids))) - (push (cons "unique-id" unique-id) resource) - (cons resource - (org-taskjuggler-assign-resource-ids (cdr resources) - (cons unique-id unique-ids))))))) + (let (unique-ids new-list) + (dolist (resource resources new-list) + (let ((unique-id (org-taskjuggler-get-unique-id resource unique-ids))) + (push (cons "unique-id" unique-id) resource) + (push unique-id unique-ids) + (push resource new-list))) + (nreverse new-list))) (defun org-taskjuggler-resolve-dependencies (tasks) (let ((previous-level 0) @@ -405,34 +444,34 @@ unique id to each resource." (depends (cdr (assoc "depends" task))) (parent-ordered (cdr (assoc "parent-ordered" task))) (blocker (cdr (assoc "BLOCKER" task))) - (blocked-on-previous + (blocked-on-previous (and blocker (string-match "previous-sibling" blocker))) (dependencies (org-taskjuggler-resolve-explicit-dependencies - (append + (append (and depends (org-taskjuggler-tokenize-dependencies depends)) - (and blocker (org-taskjuggler-tokenize-dependencies blocker))) + (and blocker (org-taskjuggler-tokenize-dependencies blocker))) tasks)) - previous-sibling) - ; update previous sibling info + previous-sibling) + ; update previous sibling info (cond - ((< previous-level level) + ((< previous-level level) (dotimes (tmp (- level previous-level)) (push task siblings))) ((= previous-level level) (setq previous-sibling (car siblings)) (setcar siblings task)) - ((> previous-level level) + ((> previous-level level) (dotimes (tmp (- previous-level level)) (pop siblings)) (setq previous-sibling (car siblings)) (setcar siblings task))) - ; insert a dependency on previous sibling if the parent is - ; ordered or if the tasks has a BLOCKER attribute with value "previous-sibling" + ; insert a dependency on previous sibling if the parent is + ; ordered or if the tasks has a BLOCKER attribute with value "previous-sibling" (when (or (and previous-sibling parent-ordered) blocked-on-previous) (push (format "!%s" (cdr (assoc "unique-id" previous-sibling))) dependencies)) - ; store dependency information - (when dependencies + ; store dependency information + (when dependencies (push (cons "depends" (mapconcat 'identity dependencies ", ")) task)) (setq previous-level level) (setq resolved-tasks (append resolved-tasks (list task))))))) @@ -441,11 +480,11 @@ unique id to each resource." "Split a dependency property value DEPENDENCIES into the individual dependencies and return them as a list while keeping the optional arguments (such as gapduration) for the -dependencies. A dependency will have to match `[-a-zA-Z0-9_]+'." - (cond +dependencies. A dependency will have to match `[-a-zA-Z0-9_]+'." + (cond ((string-match "^ *$" dependencies) nil) ((string-match "^[ \t]*\\([-a-zA-Z0-9_]+\\([ \t]*{[^}]+}\\)?\\)[ \t,]*" dependencies) - (cons + (cons (substring dependencies (match-beginning 1) (match-end 1)) (org-taskjuggler-tokenize-dependencies (substring dependencies (match-end 0))))) (t (error (format "invalid dependency id %s" dependencies))))) @@ -454,41 +493,41 @@ dependencies. A dependency will have to match `[-a-zA-Z0-9_]+'." "For each dependency in DEPENDENCIES try to find a corresponding task with a matching property \"task_id\" in TASKS. Return a list containing the resolved links for all DEPENDENCIES -where a matching tasks was found. If the dependency is +where a matching tasks was found. If the dependency is \"previous-sibling\" it is ignored (as this is dealt with in `org-taskjuggler-resolve-dependencies'). If there is no matching task the dependency is ignored and a warning is displayed ." (unless (null dependencies) - (let* + (let* ;; the dependency might have optional attributes such as "{ ;; gapduration 5d }", so only use the first string as id for the ;; dependency ((dependency (car dependencies)) (id (car (split-string dependency))) - (optional-attributes + (optional-attributes (mapconcat 'identity (cdr (split-string dependency)) " ")) (path (org-taskjuggler-find-task-with-id id tasks))) - (cond + (cond ;; ignore previous sibling dependencies ((equal (car dependencies) "previous-sibling") (org-taskjuggler-resolve-explicit-dependencies (cdr dependencies) tasks)) ;; if the id is found in another task use its path - ((not (null path)) + ((not (null path)) (cons (mapconcat 'identity (list path optional-attributes) " ") - (org-taskjuggler-resolve-explicit-dependencies + (org-taskjuggler-resolve-explicit-dependencies (cdr dependencies) tasks))) ;; warn about dangling dependency but otherwise ignore it - (t (display-warning - 'org-export-taskjuggler + (t (display-warning + 'org-export-taskjuggler (format "No task with matching property \"task_id\" found for id %s" id)) (org-taskjuggler-resolve-explicit-dependencies (cdr dependencies) tasks)))))) (defun org-taskjuggler-find-task-with-id (id tasks) - "Find ID in tasks. If found return the path of task. Otherwise + "Find ID in tasks. If found return the path of task. Otherwise return nil." (let ((task-id (cdr (assoc "task_id" (car tasks)))) (path (cdr (assoc "path" (car tasks))))) - (cond + (cond ((null tasks) nil) ((equal task-id id) path) (t (org-taskjuggler-find-task-with-id id (cdr tasks)))))) @@ -502,29 +541,34 @@ finally add more underscore characters (\"_\")." (let* ((headline (cdr (assoc "headline" item))) (parts (split-string headline)) (id (org-taskjuggler-clean-id (downcase (pop parts))))) - ; try to add more parts of the headline to make it unique - (while (member id unique-ids) + ; try to add more parts of the headline to make it unique + (while (and (member id unique-ids) (car parts)) (setq id (concat id "_" (org-taskjuggler-clean-id (downcase (pop parts)))))) - ; if its still not unique add "_" + ; if its still not unique add "_" (while (member id unique-ids) (setq id (concat id "_"))) id)) - + (defun org-taskjuggler-clean-id (id) "Clean and return ID to make it acceptable for taskjuggler." - (and id (replace-regexp-in-string "[^a-zA-Z0-9_]" "_" id))) + (and id + ;; replace non-ascii by _ + (replace-regexp-in-string + "[^a-zA-Z0-9_]" "_" + ;; make sure id doesn't start with a number + (replace-regexp-in-string "^\\([0-9]\\)" "_\\1" id)))) (defun org-taskjuggler-open-project (project) - "Insert the beginning of a project declaration. All valid -attributes from the PROJECT alist are inserted. If no end date is + "Insert the beginning of a project declaration. All valid +attributes from the PROJECT alist are inserted. If no end date is specified it is calculated `org-export-taskjuggler-default-project-duration' days from now." (let* ((unique-id (cdr (assoc "unique-id" project))) - (headline (cdr (assoc "headline" project))) - (version (cdr (assoc "version" project))) - (start (cdr (assoc "start" project))) - (end (cdr (assoc "end" project)))) - (insert + (headline (cdr (assoc "headline" project))) + (version (cdr (assoc "version" project))) + (start (cdr (assoc "start" project))) + (end (cdr (assoc "end" project)))) + (insert (format "project %s \"%s\" \"%s\" %s +%sd {\n }\n" unique-id headline version start org-export-taskjuggler-default-project-duration)))) @@ -534,16 +578,16 @@ specified it is calculated with separator \"\n\"." (let ((filtered-items (remq nil items))) (and filtered-items (mapconcat 'identity filtered-items "\n")))) - + (defun org-taskjuggler-get-attributes (item attributes) - "Return all attribute as a single formated string. ITEM is an -alist representing either a resource or a task. ATTRIBUTES is a -list of symbols. Only entries from ITEM are considered that are + "Return all attribute as a single formatted string. ITEM is an +alist representing either a resource or a task. ATTRIBUTES is a +list of symbols. Only entries from ITEM are considered that are listed in ATTRIBUTES." - (org-taskjuggler-filter-and-join + (org-taskjuggler-filter-and-join (mapcar - (lambda (attribute) - (org-taskjuggler-filter-and-join + (lambda (attribute) + (org-taskjuggler-filter-and-join (org-taskjuggler-get-attribute item attribute))) attributes))) @@ -551,7 +595,7 @@ listed in ATTRIBUTES." "Return a list of strings containing the properly formatted taskjuggler declaration for a given ATTRIBUTE in ITEM (an alist). If the ATTRIBUTE is not in ITEM return nil." - (cond + (cond ((null item) nil) ((equal (symbol-name attribute) (car (car item))) (cons (format "%s %s" (symbol-name attribute) (cdr (car item))) @@ -559,80 +603,83 @@ If the ATTRIBUTE is not in ITEM return nil." (t (org-taskjuggler-get-attribute (cdr item) attribute)))) (defun org-taskjuggler-open-resource (resource) - "Insert the beginning of a resource declaration. All valid -attributes from the RESOURCE alist are inserted. If the RESOURCE + "Insert the beginning of a resource declaration. All valid +attributes from the RESOURCE alist are inserted. If the RESOURCE defines a property \"resource_id\" it will be used as the id for -this resource. Otherwise it will use the ID property. If neither +this resource. Otherwise it will use the ID property. If neither is defined it will calculate a unique id for the resource using `org-taskjuggler-get-unique-id'." - (let ((id (org-taskjuggler-clean-id - (or (cdr (assoc "resource_id" resource)) - (cdr (assoc "ID" resource)) + (let ((id (org-taskjuggler-clean-id + (or (cdr (assoc "resource_id" resource)) + (cdr (assoc "ID" resource)) (cdr (assoc "unique-id" resource))))) (headline (cdr (assoc "headline" resource))) (attributes '(limits vacation shift booking efficiency journalentry rate))) - (insert - (concat + (insert + (concat "resource " id " \"" headline "\" {\n " (org-taskjuggler-get-attributes resource attributes) "\n")))) (defun org-taskjuggler-clean-effort (effort) "Translate effort strings into a format acceptable to taskjuggler, -i.e. REAL UNIT. If the effort string is something like 5:30 it -will be assumed to be hours and will be translated into 5.5h. -Otherwise if it contains something like 3.0 it is assumed to be -days and will be translated into 3.0d. Other formats that -taskjuggler supports (like weeks, months and years) are currently -not supported." - (cond +i.e. REAL UNIT. A valid effort string can be anything that is +accepted by `org-duration-string-to-minutes´." + (cond ((null effort) effort) - ((string-match "\\([0-9]+\\):\\([0-9]+\\)" effort) - (let ((hours (string-to-number (match-string 1 effort))) - (minutes (string-to-number (match-string 2 effort)))) - (format "%dh" (+ hours (/ minutes 60.0))))) - ((string-match "\\([0-9]+\\).\\([0-9]+\\)" effort) (concat effort "d")) - (t (error "Not a valid effort (%s)" effort)))) + (t (let* ((minutes (org-duration-string-to-minutes effort)) + (hours (/ minutes 60.0))) + (format "%.1fh" hours))))) (defun org-taskjuggler-get-priority (priority) "Return a priority between 1 and 1000 based on PRIORITY, an org-mode priority string." - (max 1 (/ (* 1000 (- org-lowest-priority (string-to-char priority))) + (max 1 (/ (* 1000 (- org-lowest-priority (string-to-char priority))) (- org-lowest-priority org-highest-priority)))) (defun org-taskjuggler-open-task (task) (let* ((unique-id (cdr (assoc "unique-id" task))) - (headline (cdr (assoc "headline" task))) - (effort (org-taskjuggler-clean-effort (cdr (assoc org-effort-property task)))) - (depends (cdr (assoc "depends" task))) - (allocate (cdr (assoc "allocate" task))) - (priority-raw (cdr (assoc "PRIORITY" task))) - (priority (and priority-raw (org-taskjuggler-get-priority priority-raw))) - (state (cdr (assoc "TODO" task))) - (complete (or (and (member state org-done-keywords) "100") - (cdr (assoc "complete" task)))) - (parent-ordered (cdr (assoc "parent-ordered" task))) - (previous-sibling (cdr (assoc "previous-sibling" task))) - (attributes - '(account start note duration endbuffer endcredit end - flags journalentry length maxend maxstart milestone - minend minstart period reference responsible - scheduling startbuffer startcredit statusnote))) + (headline (cdr (assoc "headline" task))) + (effort (org-taskjuggler-clean-effort (cdr (assoc org-effort-property task)))) + (depends (cdr (assoc "depends" task))) + (allocate (cdr (assoc "allocate" task))) + (priority-raw (cdr (assoc "PRIORITY" task))) + (priority (and priority-raw (org-taskjuggler-get-priority priority-raw))) + (state (cdr (assoc "TODO" task))) + (complete (or (and (member state org-done-keywords) "100") + (cdr (assoc "complete" task)))) + (parent-ordered (cdr (assoc "parent-ordered" task))) + (previous-sibling (cdr (assoc "previous-sibling" task))) + (milestone (or (cdr (assoc "milestone" task)) + (and (assoc "leaf-node" task) + (not (or effort + (cdr (assoc "duration" task)) + (cdr (assoc "end" task)) + (cdr (assoc "period" task))))))) + (attributes + '(account start note duration endbuffer endcredit end + flags journalentry length maxend maxstart minend + minstart period reference responsible scheduling + startbuffer startcredit statusnote))) (insert - (concat - "task " unique-id " \"" headline "\" {\n" + (concat + "task " unique-id " \"" headline "\" {\n" (if (and parent-ordered previous-sibling) (format " depends %s\n" previous-sibling) (and depends (format " depends %s\n" depends))) - (and allocate (format " purge allocations\n allocate %s\n" allocate)) + (and allocate (format " purge %s\n allocate %s\n" + (or (and (org-taskjuggler-targeting-tj3-p) "allocate") + "allocations") + allocate)) (and complete (format " complete %s\n" complete)) (and effort (format " effort %s\n" effort)) (and priority (format " priority %s\n" priority)) - + (and milestone (format " milestone\n")) + (org-taskjuggler-get-attributes task attributes) "\n")))) (defun org-taskjuggler-close-maybe (level) - (while (> org-export-taskjuggler-old-level level) + (while (> org-export-taskjuggler-old-level level) (insert "}\n") (setq org-export-taskjuggler-old-level (1- org-export-taskjuggler-old-level))) (when (= org-export-taskjuggler-old-level level)