diff options
| author | Bastien Guerry | 2013-11-12 16:45:48 +0100 |
|---|---|---|
| committer | Bastien Guerry | 2013-11-12 16:45:48 +0100 |
| commit | 70e9f75d7a4c324608501363dce588babbd83fa0 (patch) | |
| tree | 59637b6e3988161bb4c1b534607c87a0a570323f | |
| parent | ae0cb5361ecde3386b96bb036e012992a58ce5ac (diff) | |
| download | emacs-70e9f75d7a4c324608501363dce588babbd83fa0.tar.gz emacs-70e9f75d7a4c324608501363dce588babbd83fa0.zip | |
Remove org-taskjuggler.el as it's not part of Org 8.2.3a
| -rw-r--r-- | lisp/org/org-taskjuggler.el | 699 |
1 files changed, 0 insertions, 699 deletions
diff --git a/lisp/org/org-taskjuggler.el b/lisp/org/org-taskjuggler.el deleted file mode 100644 index bd4c10b2ee5..00000000000 --- a/lisp/org/org-taskjuggler.el +++ /dev/null | |||
| @@ -1,699 +0,0 @@ | |||
| 1 | ;;; org-taskjuggler.el --- TaskJuggler exporter for org-mode | ||
| 2 | ;; | ||
| 3 | ;; Copyright (C) 2007-2013 Free Software Foundation, Inc. | ||
| 4 | ;; | ||
| 5 | ;; Emacs Lisp Archive Entry | ||
| 6 | ;; Filename: org-taskjuggler.el | ||
| 7 | ;; Author: Christian Egli | ||
| 8 | ;; Maintainer: Christian Egli | ||
| 9 | ;; Keywords: org, taskjuggler, project planning | ||
| 10 | ;; Description: Converts an org-mode buffer into a taskjuggler project plan | ||
| 11 | ;; URL: | ||
| 12 | |||
| 13 | ;; This file is part of GNU Emacs. | ||
| 14 | |||
| 15 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 16 | ;; it under the terms of the GNU General Public License as published by | ||
| 17 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 18 | ;; (at your option) any later version. | ||
| 19 | |||
| 20 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 21 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 22 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 23 | ;; GNU General Public License for more details. | ||
| 24 | |||
| 25 | ;; You should have received a copy of the GNU General Public License | ||
| 26 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | ||
| 27 | |||
| 28 | ;; Commentary: | ||
| 29 | ;; | ||
| 30 | ;; This library implements a TaskJuggler exporter for org-mode. | ||
| 31 | ;; TaskJuggler uses a text format to define projects, tasks and | ||
| 32 | ;; resources, so it is a natural fit for org-mode. It can produce all | ||
| 33 | ;; sorts of reports for tasks or resources in either HTML, CSV or PDF. | ||
| 34 | ;; The current version of TaskJuggler requires KDE but the next | ||
| 35 | ;; version is implemented in Ruby and should therefore run on any | ||
| 36 | ;; platform. | ||
| 37 | ;; | ||
| 38 | ;; The exporter is a bit different from other exporters, such as the | ||
| 39 | ;; HTML and LaTeX exporters for example, in that it does not export | ||
| 40 | ;; all the nodes of a document or strictly follow the order of the | ||
| 41 | ;; nodes in the document. | ||
| 42 | ;; | ||
| 43 | ;; Instead the TaskJuggler exporter looks for a tree that defines the | ||
| 44 | ;; tasks and a optionally tree that defines the resources for this | ||
| 45 | ;; project. It then creates a TaskJuggler file based on these trees | ||
| 46 | ;; and the attributes defined in all the nodes. | ||
| 47 | ;; | ||
| 48 | ;; * Installation | ||
| 49 | ;; | ||
| 50 | ;; Put this file into your load-path and the following line into your | ||
| 51 | ;; ~/.emacs: | ||
| 52 | ;; | ||
| 53 | ;; (require 'org-taskjuggler) | ||
| 54 | ;; | ||
| 55 | ;; The interactive functions are similar to those of the HTML and LaTeX | ||
| 56 | ;; exporters: | ||
| 57 | ;; | ||
| 58 | ;; M-x `org-export-as-taskjuggler' | ||
| 59 | ;; M-x `org-export-as-taskjuggler-and-open' | ||
| 60 | ;; | ||
| 61 | ;; * Tasks | ||
| 62 | ;; | ||
| 63 | ;; Let's illustrate the usage with a small example. Create your tasks | ||
| 64 | ;; as you usually do with org-mode. Assign efforts to each task using | ||
| 65 | ;; properties (it's easiest to do this in the column view). You should | ||
| 66 | ;; end up with something similar to the example by Peter Jones in | ||
| 67 | ;; http://www.contextualdevelopment.com/static/artifacts/articles/2008/project-planning/project-planning.org. | ||
| 68 | ;; Now mark the top node of your tasks with a tag named | ||
| 69 | ;; "taskjuggler_project" (or whatever you customized | ||
| 70 | ;; `org-export-taskjuggler-project-tag' to). You are now ready to | ||
| 71 | ;; export the project plan with `org-export-as-taskjuggler-and-open' | ||
| 72 | ;; which will export the project plan and open a Gantt chart in | ||
| 73 | ;; TaskJugglerUI. | ||
| 74 | ;; | ||
| 75 | ;; * Resources | ||
| 76 | ;; | ||
| 77 | ;; Next you can define resources and assign those to work on specific | ||
| 78 | ;; tasks. You can group your resources hierarchically. Tag the top | ||
| 79 | ;; node of the resources with "taskjuggler_resource" (or whatever you | ||
| 80 | ;; customized `org-export-taskjuggler-resource-tag' to). You can | ||
| 81 | ;; optionally assign an identifier (named "resource_id") to the | ||
| 82 | ;; resources (using the standard org properties commands) or you can | ||
| 83 | ;; let the exporter generate identifiers automatically (the exporter | ||
| 84 | ;; picks the first word of the headline as the identifier as long as | ||
| 85 | ;; it is unique, see the documentation of | ||
| 86 | ;; `org-taskjuggler-get-unique-id'). Using that identifier you can | ||
| 87 | ;; then allocate resources to tasks. This is again done with the | ||
| 88 | ;; "allocate" property on the tasks. Do this in column view or when on | ||
| 89 | ;; the task type | ||
| 90 | ;; | ||
| 91 | ;; C-c C-x p allocate RET <resource_id> RET | ||
| 92 | ;; | ||
| 93 | ;; Once the allocations are done you can again export to TaskJuggler | ||
| 94 | ;; and check in the Resource Allocation Graph which person is working | ||
| 95 | ;; on what task at what time. | ||
| 96 | ;; | ||
| 97 | ;; * Export of properties | ||
| 98 | ;; | ||
| 99 | ;; The exporter also takes TODO state information into consideration, | ||
| 100 | ;; i.e. if a task is marked as done it will have the corresponding | ||
| 101 | ;; attribute in TaskJuggler ("complete 100"). Also it will export any | ||
| 102 | ;; property on a task resource or resource node which is known to | ||
| 103 | ;; TaskJuggler, such as limits, vacation, shift, booking, efficiency, | ||
| 104 | ;; journalentry, rate for resources or account, start, note, duration, | ||
| 105 | ;; end, journalentry, milestone, reference, responsible, scheduling, | ||
| 106 | ;; etc for tasks. | ||
| 107 | ;; | ||
| 108 | ;; * Dependencies | ||
| 109 | ;; | ||
| 110 | ;; The exporter will handle dependencies that are defined in the tasks | ||
| 111 | ;; either with the ORDERED attribute (see TODO dependencies in the Org | ||
| 112 | ;; mode manual) or with the BLOCKER attribute (see org-depend.el) or | ||
| 113 | ;; alternatively with a depends attribute. Both the BLOCKER and the | ||
| 114 | ;; depends attribute can be either "previous-sibling" or a reference | ||
| 115 | ;; to an identifier (named "task_id") which is defined for another | ||
| 116 | ;; task in the project. BLOCKER and the depends attribute can define | ||
| 117 | ;; multiple dependencies separated by either space or comma. You can | ||
| 118 | ;; also specify optional attributes on the dependency by simply | ||
| 119 | ;; appending it. The following examples should illustrate this: | ||
| 120 | ;; | ||
| 121 | ;; * Training material | ||
| 122 | ;; :PROPERTIES: | ||
| 123 | ;; :task_id: training_material | ||
| 124 | ;; :ORDERED: t | ||
| 125 | ;; :END: | ||
| 126 | ;; ** Markup Guidelines | ||
| 127 | ;; :PROPERTIES: | ||
| 128 | ;; :Effort: 2d | ||
| 129 | ;; :END: | ||
| 130 | ;; ** Workflow Guidelines | ||
| 131 | ;; :PROPERTIES: | ||
| 132 | ;; :Effort: 2d | ||
| 133 | ;; :END: | ||
| 134 | ;; * Presentation | ||
| 135 | ;; :PROPERTIES: | ||
| 136 | ;; :Effort: 2d | ||
| 137 | ;; :BLOCKER: training_material { gapduration 1d } some_other_task | ||
| 138 | ;; :END: | ||
| 139 | ;; | ||
| 140 | ;;;; * TODO | ||
| 141 | ;; - Use SCHEDULED and DEADLINE information (not just start and end | ||
| 142 | ;; properties). | ||
| 143 | ;; - Look at org-file-properties, org-global-properties and | ||
| 144 | ;; org-global-properties-fixed | ||
| 145 | ;; - What about property inheritance and org-property-inherit-p? | ||
| 146 | ;; - Use TYPE_TODO as an way to assign resources | ||
| 147 | ;; - Make sure multiple dependency definitions (i.e. BLOCKER on | ||
| 148 | ;; previous-sibling and on a specific task_id) in multiple | ||
| 149 | ;; attributes are properly exported. | ||
| 150 | ;; | ||
| 151 | ;;; Code: | ||
| 152 | |||
| 153 | (eval-when-compile | ||
| 154 | (require 'cl)) | ||
| 155 | |||
| 156 | (require 'org) | ||
| 157 | (require 'org-exp) | ||
| 158 | |||
| 159 | ;;; User variables: | ||
| 160 | |||
| 161 | (defgroup org-export-taskjuggler nil | ||
| 162 | "Options for exporting Org-mode files to TaskJuggler." | ||
| 163 | :tag "Org Export TaskJuggler" | ||
| 164 | :group 'org-export) | ||
| 165 | |||
| 166 | (defcustom org-export-taskjuggler-extension ".tjp" | ||
| 167 | "Extension of TaskJuggler files." | ||
| 168 | :group 'org-export-taskjuggler | ||
| 169 | :version "24.1" | ||
| 170 | :type 'string) | ||
| 171 | |||
| 172 | (defcustom org-export-taskjuggler-project-tag "taskjuggler_project" | ||
| 173 | "Tag, property or todo used to find the tree containing all | ||
| 174 | the tasks for the project." | ||
| 175 | :group 'org-export-taskjuggler | ||
| 176 | :version "24.1" | ||
| 177 | :type 'string) | ||
| 178 | |||
| 179 | (defcustom org-export-taskjuggler-resource-tag "taskjuggler_resource" | ||
| 180 | "Tag, property or todo used to find the tree containing all the | ||
| 181 | resources for the project." | ||
| 182 | :group 'org-export-taskjuggler | ||
| 183 | :version "24.1" | ||
| 184 | :type 'string) | ||
| 185 | |||
| 186 | (defcustom org-export-taskjuggler-target-version 2.4 | ||
| 187 | "Which version of TaskJuggler the exporter is targeting." | ||
| 188 | :group 'org-export-taskjuggler | ||
| 189 | :version "24.1" | ||
| 190 | :type 'number) | ||
| 191 | |||
| 192 | (defcustom org-export-taskjuggler-default-project-version "1.0" | ||
| 193 | "Default version string for the project." | ||
| 194 | :group 'org-export-taskjuggler | ||
| 195 | :version "24.1" | ||
| 196 | :type 'string) | ||
| 197 | |||
| 198 | (defcustom org-export-taskjuggler-default-project-duration 280 | ||
| 199 | "Default project duration if no start and end date have been defined | ||
| 200 | in the root node of the task tree, i.e. the tree that has been marked | ||
| 201 | with `org-export-taskjuggler-project-tag'" | ||
| 202 | :group 'org-export-taskjuggler | ||
| 203 | :version "24.1" | ||
| 204 | :type 'integer) | ||
| 205 | |||
| 206 | (defcustom org-export-taskjuggler-default-reports | ||
| 207 | '("taskreport \"Gantt Chart\" { | ||
| 208 | headline \"Project Gantt Chart\" | ||
| 209 | columns hierarchindex, name, start, end, effort, duration, completed, chart | ||
| 210 | timeformat \"%Y-%m-%d\" | ||
| 211 | hideresource 1 | ||
| 212 | loadunit shortauto | ||
| 213 | }" | ||
| 214 | "resourcereport \"Resource Graph\" { | ||
| 215 | headline \"Resource Allocation Graph\" | ||
| 216 | columns no, name, utilization, freeload, chart | ||
| 217 | loadunit shortauto | ||
| 218 | sorttasks startup | ||
| 219 | hidetask ~isleaf() | ||
| 220 | }") | ||
| 221 | "Default reports for the project." | ||
| 222 | :group 'org-export-taskjuggler | ||
| 223 | :version "24.1" | ||
| 224 | :type '(repeat (string :tag "Report"))) | ||
| 225 | |||
| 226 | (defcustom org-export-taskjuggler-default-global-properties | ||
| 227 | "shift s40 \"Part time shift\" { | ||
| 228 | workinghours wed, thu, fri off | ||
| 229 | } | ||
| 230 | " | ||
| 231 | "Default global properties for the project. Here you typically | ||
| 232 | define global properties such as shifts, accounts, rates, | ||
| 233 | vacation, macros and flags. Any property that is allowed within | ||
| 234 | the TaskJuggler file can be inserted. You could for example | ||
| 235 | include another TaskJuggler file. | ||
| 236 | |||
| 237 | The global properties are inserted after the project declaration | ||
| 238 | but before any resource and task declarations." | ||
| 239 | :group 'org-export-taskjuggler | ||
| 240 | :version "24.1" | ||
| 241 | :type '(string :tag "Preamble")) | ||
| 242 | |||
| 243 | ;;; Hooks | ||
| 244 | |||
| 245 | (defvar org-export-taskjuggler-final-hook nil | ||
| 246 | "Hook run at the end of TaskJuggler export, in the new buffer.") | ||
| 247 | |||
| 248 | ;;; Autoload functions: | ||
| 249 | |||
| 250 | ;; avoid compiler warning about free variable | ||
| 251 | (defvar org-export-taskjuggler-old-level) | ||
| 252 | |||
| 253 | ;;;###autoload | ||
| 254 | (defun org-export-as-taskjuggler () | ||
| 255 | "Export parts of the current buffer as a TaskJuggler file. | ||
| 256 | The exporter looks for a tree with tag, property or todo that | ||
| 257 | matches `org-export-taskjuggler-project-tag' and takes this as | ||
| 258 | the tasks for this project. The first node of this tree defines | ||
| 259 | the project properties such as project name and project period. | ||
| 260 | If there is a tree with tag, property or todo that matches | ||
| 261 | `org-export-taskjuggler-resource-tag' this three is taken as | ||
| 262 | resources for the project. If no resources are specified, a | ||
| 263 | default resource is created and allocated to the project. Also | ||
| 264 | the taskjuggler project will be created with default reports as | ||
| 265 | defined in `org-export-taskjuggler-default-reports'." | ||
| 266 | (interactive) | ||
| 267 | |||
| 268 | (message "Exporting...") | ||
| 269 | (setq-default org-done-keywords org-done-keywords) | ||
| 270 | (let* ((tasks | ||
| 271 | (org-taskjuggler-resolve-dependencies | ||
| 272 | (org-taskjuggler-assign-task-ids | ||
| 273 | (org-taskjuggler-compute-task-leafiness | ||
| 274 | (org-map-entries | ||
| 275 | 'org-taskjuggler-components | ||
| 276 | org-export-taskjuggler-project-tag nil 'archive 'comment))))) | ||
| 277 | (resources | ||
| 278 | (org-taskjuggler-assign-resource-ids | ||
| 279 | (org-map-entries | ||
| 280 | 'org-taskjuggler-components | ||
| 281 | org-export-taskjuggler-resource-tag nil 'archive 'comment))) | ||
| 282 | (filename (expand-file-name | ||
| 283 | (concat | ||
| 284 | (file-name-sans-extension | ||
| 285 | (file-name-nondirectory buffer-file-name)) | ||
| 286 | org-export-taskjuggler-extension))) | ||
| 287 | (buffer (find-file-noselect filename)) | ||
| 288 | (old-buffer (current-buffer)) | ||
| 289 | (org-export-taskjuggler-old-level 0) | ||
| 290 | task resource) | ||
| 291 | (unless tasks | ||
| 292 | (error "No tasks specified")) | ||
| 293 | ;; add a default resource | ||
| 294 | (unless resources | ||
| 295 | (setq resources | ||
| 296 | `((("resource_id" . ,(user-login-name)) | ||
| 297 | ("headline" . ,user-full-name) | ||
| 298 | ("level" . 1))))) | ||
| 299 | ;; add a default allocation to the first task if none was given | ||
| 300 | (unless (assoc "allocate" (car tasks)) | ||
| 301 | (let ((task (car tasks)) | ||
| 302 | (resource-id (cdr (assoc "resource_id" (car resources))))) | ||
| 303 | (setcar tasks (push (cons "allocate" resource-id) task)))) | ||
| 304 | ;; add a default start date to the first task if none was given | ||
| 305 | (unless (assoc "start" (car tasks)) | ||
| 306 | (let ((task (car tasks)) | ||
| 307 | (time-string (format-time-string "%Y-%m-%d"))) | ||
| 308 | (setcar tasks (push (cons "start" time-string) task)))) | ||
| 309 | ;; add a default version if none was given | ||
| 310 | (unless (assoc "version" (car tasks)) | ||
| 311 | (let ((task (car tasks)) | ||
| 312 | (version org-export-taskjuggler-default-project-version)) | ||
| 313 | (setcar tasks (push (cons "version" version) task)))) | ||
| 314 | (with-current-buffer buffer | ||
| 315 | (erase-buffer) | ||
| 316 | (org-clone-local-variables old-buffer "^org-") | ||
| 317 | (org-taskjuggler-open-project (car tasks)) | ||
| 318 | (insert org-export-taskjuggler-default-global-properties) | ||
| 319 | (insert "\n") | ||
| 320 | (dolist (resource resources) | ||
| 321 | (let ((level (cdr (assoc "level" resource)))) | ||
| 322 | (org-taskjuggler-close-maybe level) | ||
| 323 | (org-taskjuggler-open-resource resource) | ||
| 324 | (setq org-export-taskjuggler-old-level level))) | ||
| 325 | (org-taskjuggler-close-maybe 1) | ||
| 326 | (setq org-export-taskjuggler-old-level 0) | ||
| 327 | (dolist (task tasks) | ||
| 328 | (let ((level (cdr (assoc "level" task)))) | ||
| 329 | (org-taskjuggler-close-maybe level) | ||
| 330 | (org-taskjuggler-open-task task) | ||
| 331 | (setq org-export-taskjuggler-old-level level))) | ||
| 332 | (org-taskjuggler-close-maybe 1) | ||
| 333 | (org-taskjuggler-insert-reports) | ||
| 334 | (save-buffer) | ||
| 335 | (or (org-export-push-to-kill-ring "TaskJuggler") | ||
| 336 | (message "Exporting... done")) | ||
| 337 | (current-buffer)))) | ||
| 338 | |||
| 339 | ;;;###autoload | ||
| 340 | (defun org-export-as-taskjuggler-and-open () | ||
| 341 | "Export the current buffer as a TaskJuggler file and open it | ||
| 342 | with the TaskJuggler GUI." | ||
| 343 | (interactive) | ||
| 344 | (let* ((file-name (buffer-file-name (org-export-as-taskjuggler))) | ||
| 345 | (process-name "TaskJugglerUI") | ||
| 346 | (command (concat process-name " " file-name))) | ||
| 347 | (start-process-shell-command process-name nil command))) | ||
| 348 | |||
| 349 | (defun org-taskjuggler-targeting-tj3-p () | ||
| 350 | "Return true if we are targeting TaskJuggler III." | ||
| 351 | (>= org-export-taskjuggler-target-version 3.0)) | ||
| 352 | |||
| 353 | (defun org-taskjuggler-parent-is-ordered-p () | ||
| 354 | "Return true if the parent of the current node has a property | ||
| 355 | \"ORDERED\". Return nil otherwise." | ||
| 356 | (save-excursion | ||
| 357 | (and (org-up-heading-safe) (org-entry-get (point) "ORDERED")))) | ||
| 358 | |||
| 359 | (defun org-taskjuggler-components () | ||
| 360 | "Return an alist containing all the pertinent information for | ||
| 361 | the current node such as the headline, the level, todo state | ||
| 362 | information, all the properties, etc." | ||
| 363 | (let* ((props (org-entry-properties)) | ||
| 364 | (components (org-heading-components)) | ||
| 365 | (level (nth 1 components)) | ||
| 366 | (headline | ||
| 367 | (replace-regexp-in-string | ||
| 368 | "\"" "\\\"" (nth 4 components) t t)) ; quote double quotes in headlines | ||
| 369 | (parent-ordered (org-taskjuggler-parent-is-ordered-p))) | ||
| 370 | (push (cons "level" level) props) | ||
| 371 | (push (cons "headline" headline) props) | ||
| 372 | (push (cons "parent-ordered" parent-ordered) props))) | ||
| 373 | |||
| 374 | (defun org-taskjuggler-assign-task-ids (tasks) | ||
| 375 | "Given a list of tasks return the same list assigning a unique id | ||
| 376 | and the full path to each task. Taskjuggler takes hierarchical ids. | ||
| 377 | For that reason we have to make ids locally unique and we have to keep | ||
| 378 | a path to the current task." | ||
| 379 | (let ((previous-level 0) | ||
| 380 | unique-ids unique-id | ||
| 381 | path | ||
| 382 | task resolved-tasks tmp) | ||
| 383 | (dolist (task tasks resolved-tasks) | ||
| 384 | (let ((level (cdr (assoc "level" task)))) | ||
| 385 | (cond | ||
| 386 | ((< previous-level level) | ||
| 387 | (setq unique-id (org-taskjuggler-get-unique-id task (car unique-ids))) | ||
| 388 | (dotimes (tmp (- level previous-level)) | ||
| 389 | (push (list unique-id) unique-ids) | ||
| 390 | (push unique-id path))) | ||
| 391 | ((= previous-level level) | ||
| 392 | (setq unique-id (org-taskjuggler-get-unique-id task (car unique-ids))) | ||
| 393 | (push unique-id (car unique-ids)) | ||
| 394 | (setcar path unique-id)) | ||
| 395 | ((> previous-level level) | ||
| 396 | (dotimes (tmp (- previous-level level)) | ||
| 397 | (pop unique-ids) | ||
| 398 | (pop path)) | ||
| 399 | (setq unique-id (org-taskjuggler-get-unique-id task (car unique-ids))) | ||
| 400 | (push unique-id (car unique-ids)) | ||
| 401 | (setcar path unique-id))) | ||
| 402 | (push (cons "unique-id" unique-id) task) | ||
| 403 | (push (cons "path" (mapconcat 'identity (reverse path) ".")) task) | ||
| 404 | (setq previous-level level) | ||
| 405 | (setq resolved-tasks (append resolved-tasks (list task))))))) | ||
| 406 | |||
| 407 | (defun org-taskjuggler-compute-task-leafiness (tasks) | ||
| 408 | "Figure out if each task is a leaf by looking at it's level, | ||
| 409 | and the level of its successor. If the successor is higher (ie | ||
| 410 | deeper), then it's not a leaf." | ||
| 411 | (let (new-list) | ||
| 412 | (while (car tasks) | ||
| 413 | (let ((task (car tasks)) | ||
| 414 | (successor (car (cdr tasks)))) | ||
| 415 | (cond | ||
| 416 | ;; if a task has no successors it is a leaf | ||
| 417 | ((null successor) | ||
| 418 | (push (cons (cons "leaf-node" t) task) new-list)) | ||
| 419 | ;; if the successor has a lower level than task it is a leaf | ||
| 420 | ((<= (cdr (assoc "level" successor)) (cdr (assoc "level" task))) | ||
| 421 | (push (cons (cons "leaf-node" t) task) new-list)) | ||
| 422 | ;; otherwise examine the rest of the tasks | ||
| 423 | (t (push task new-list)))) | ||
| 424 | (setq tasks (cdr tasks))) | ||
| 425 | (nreverse new-list))) | ||
| 426 | |||
| 427 | (defun org-taskjuggler-assign-resource-ids (resources) | ||
| 428 | "Given a list of resources return the same list, assigning a | ||
| 429 | unique id to each resource." | ||
| 430 | (let (unique-ids new-list) | ||
| 431 | (dolist (resource resources new-list) | ||
| 432 | (let ((unique-id (org-taskjuggler-get-unique-id resource unique-ids))) | ||
| 433 | (push (cons "unique-id" unique-id) resource) | ||
| 434 | (push unique-id unique-ids) | ||
| 435 | (push resource new-list))) | ||
| 436 | (nreverse new-list))) | ||
| 437 | |||
| 438 | (defun org-taskjuggler-resolve-dependencies (tasks) | ||
| 439 | (let ((previous-level 0) | ||
| 440 | siblings | ||
| 441 | task resolved-tasks) | ||
| 442 | (dolist (task tasks resolved-tasks) | ||
| 443 | (let* ((level (cdr (assoc "level" task))) | ||
| 444 | (depends (cdr (assoc "depends" task))) | ||
| 445 | (parent-ordered (cdr (assoc "parent-ordered" task))) | ||
| 446 | (blocker (cdr (assoc "BLOCKER" task))) | ||
| 447 | (blocked-on-previous | ||
| 448 | (and blocker (string-match "previous-sibling" blocker))) | ||
| 449 | (dependencies | ||
| 450 | (org-taskjuggler-resolve-explicit-dependencies | ||
| 451 | (append | ||
| 452 | (and depends (org-taskjuggler-tokenize-dependencies depends)) | ||
| 453 | (and blocker (org-taskjuggler-tokenize-dependencies blocker))) | ||
| 454 | tasks)) | ||
| 455 | previous-sibling) | ||
| 456 | ; update previous sibling info | ||
| 457 | (cond | ||
| 458 | ((< previous-level level) | ||
| 459 | (dotimes (tmp (- level previous-level)) | ||
| 460 | (push task siblings))) | ||
| 461 | ((= previous-level level) | ||
| 462 | (setq previous-sibling (car siblings)) | ||
| 463 | (setcar siblings task)) | ||
| 464 | ((> previous-level level) | ||
| 465 | (dotimes (tmp (- previous-level level)) | ||
| 466 | (pop siblings)) | ||
| 467 | (setq previous-sibling (car siblings)) | ||
| 468 | (setcar siblings task))) | ||
| 469 | ; insert a dependency on previous sibling if the parent is | ||
| 470 | ; ordered or if the tasks has a BLOCKER attribute with value "previous-sibling" | ||
| 471 | (when (or (and previous-sibling parent-ordered) blocked-on-previous) | ||
| 472 | (push (format "!%s" (cdr (assoc "unique-id" previous-sibling))) dependencies)) | ||
| 473 | ; store dependency information | ||
| 474 | (when dependencies | ||
| 475 | (push (cons "depends" (mapconcat 'identity dependencies ", ")) task)) | ||
| 476 | (setq previous-level level) | ||
| 477 | (setq resolved-tasks (append resolved-tasks (list task))))))) | ||
| 478 | |||
| 479 | (defun org-taskjuggler-tokenize-dependencies (dependencies) | ||
| 480 | "Split a dependency property value DEPENDENCIES into the | ||
| 481 | individual dependencies and return them as a list while keeping | ||
| 482 | the optional arguments (such as gapduration) for the | ||
| 483 | dependencies. A dependency will have to match `[-a-zA-Z0-9_]+'." | ||
| 484 | (cond | ||
| 485 | ((string-match "^ *$" dependencies) nil) | ||
| 486 | ((string-match "^[ \t]*\\([-a-zA-Z0-9_]+\\([ \t]*{[^}]+}\\)?\\)[ \t,]*" dependencies) | ||
| 487 | (cons | ||
| 488 | (substring dependencies (match-beginning 1) (match-end 1)) | ||
| 489 | (org-taskjuggler-tokenize-dependencies (substring dependencies (match-end 0))))) | ||
| 490 | (t (error (format "invalid dependency id %s" dependencies))))) | ||
| 491 | |||
| 492 | (defun org-taskjuggler-resolve-explicit-dependencies (dependencies tasks) | ||
| 493 | "For each dependency in DEPENDENCIES try to find a | ||
| 494 | corresponding task with a matching property \"task_id\" in TASKS. | ||
| 495 | Return a list containing the resolved links for all DEPENDENCIES | ||
| 496 | where a matching tasks was found. If the dependency is | ||
| 497 | \"previous-sibling\" it is ignored (as this is dealt with in | ||
| 498 | `org-taskjuggler-resolve-dependencies'). If there is no matching | ||
| 499 | task the dependency is ignored and a warning is displayed ." | ||
| 500 | (unless (null dependencies) | ||
| 501 | (let* | ||
| 502 | ;; the dependency might have optional attributes such as "{ | ||
| 503 | ;; gapduration 5d }", so only use the first string as id for the | ||
| 504 | ;; dependency | ||
| 505 | ((dependency (car dependencies)) | ||
| 506 | (id (car (split-string dependency))) | ||
| 507 | (optional-attributes | ||
| 508 | (mapconcat 'identity (cdr (split-string dependency)) " ")) | ||
| 509 | (path (org-taskjuggler-find-task-with-id id tasks))) | ||
| 510 | (cond | ||
| 511 | ;; ignore previous sibling dependencies | ||
| 512 | ((equal (car dependencies) "previous-sibling") | ||
| 513 | (org-taskjuggler-resolve-explicit-dependencies (cdr dependencies) tasks)) | ||
| 514 | ;; if the id is found in another task use its path | ||
| 515 | ((not (null path)) | ||
| 516 | (cons (mapconcat 'identity (list path optional-attributes) " ") | ||
| 517 | (org-taskjuggler-resolve-explicit-dependencies | ||
| 518 | (cdr dependencies) tasks))) | ||
| 519 | ;; warn about dangling dependency but otherwise ignore it | ||
| 520 | (t (display-warning | ||
| 521 | 'org-export-taskjuggler | ||
| 522 | (format "No task with matching property \"task_id\" found for id %s" id)) | ||
| 523 | (org-taskjuggler-resolve-explicit-dependencies (cdr dependencies) tasks)))))) | ||
| 524 | |||
| 525 | (defun org-taskjuggler-find-task-with-id (id tasks) | ||
| 526 | "Find ID in tasks. If found return the path of task. Otherwise | ||
| 527 | return nil." | ||
| 528 | (let ((task-id (cdr (assoc "task_id" (car tasks)))) | ||
| 529 | (path (cdr (assoc "path" (car tasks))))) | ||
| 530 | (cond | ||
| 531 | ((null tasks) nil) | ||
| 532 | ((equal task-id id) path) | ||
| 533 | (t (org-taskjuggler-find-task-with-id id (cdr tasks)))))) | ||
| 534 | |||
| 535 | (defun org-taskjuggler-get-unique-id (item unique-ids) | ||
| 536 | "Return a unique id for an ITEM which can be a task or a resource. | ||
| 537 | The id is derived from the headline and made unique against | ||
| 538 | UNIQUE-IDS. If the (downcased) first token of the headline is not | ||
| 539 | unique try to add more (downcased) tokens of the headline or | ||
| 540 | finally add more underscore characters (\"_\")." | ||
| 541 | (let* ((headline (cdr (assoc "headline" item))) | ||
| 542 | (parts (split-string headline)) | ||
| 543 | (id (org-taskjuggler-clean-id (downcase (pop parts))))) | ||
| 544 | ; try to add more parts of the headline to make it unique | ||
| 545 | (while (and (member id unique-ids) (car parts)) | ||
| 546 | (setq id (concat id "_" (org-taskjuggler-clean-id (downcase (pop parts)))))) | ||
| 547 | ; if its still not unique add "_" | ||
| 548 | (while (member id unique-ids) | ||
| 549 | (setq id (concat id "_"))) | ||
| 550 | id)) | ||
| 551 | |||
| 552 | (defun org-taskjuggler-clean-id (id) | ||
| 553 | "Clean and return ID to make it acceptable for taskjuggler." | ||
| 554 | (and id | ||
| 555 | ;; replace non-ascii by _ | ||
| 556 | (replace-regexp-in-string | ||
| 557 | "[^a-zA-Z0-9_]" "_" | ||
| 558 | ;; make sure id doesn't start with a number | ||
| 559 | (replace-regexp-in-string "^\\([0-9]\\)" "_\\1" id)))) | ||
| 560 | |||
| 561 | (defun org-taskjuggler-open-project (project) | ||
| 562 | "Insert the beginning of a project declaration. All valid | ||
| 563 | attributes from the PROJECT alist are inserted. If no end date is | ||
| 564 | specified it is calculated | ||
| 565 | `org-export-taskjuggler-default-project-duration' days from now." | ||
| 566 | (let* ((unique-id (cdr (assoc "unique-id" project))) | ||
| 567 | (headline (cdr (assoc "headline" project))) | ||
| 568 | (version (cdr (assoc "version" project))) | ||
| 569 | (start (cdr (assoc "start" project))) | ||
| 570 | (end (cdr (assoc "end" project)))) | ||
| 571 | (insert | ||
| 572 | (format "project %s \"%s\" \"%s\" %s +%sd {\n }\n" | ||
| 573 | unique-id headline version start | ||
| 574 | org-export-taskjuggler-default-project-duration)))) | ||
| 575 | |||
| 576 | (defun org-taskjuggler-filter-and-join (items) | ||
| 577 | "Filter all nil elements from ITEMS and join the remaining ones | ||
| 578 | with separator \"\n\"." | ||
| 579 | (let ((filtered-items (remq nil items))) | ||
| 580 | (and filtered-items (mapconcat 'identity filtered-items "\n")))) | ||
| 581 | |||
| 582 | (defun org-taskjuggler-get-attributes (item attributes) | ||
| 583 | "Return all attribute as a single formatted string. ITEM is an | ||
| 584 | alist representing either a resource or a task. ATTRIBUTES is a | ||
| 585 | list of symbols. Only entries from ITEM are considered that are | ||
| 586 | listed in ATTRIBUTES." | ||
| 587 | (org-taskjuggler-filter-and-join | ||
| 588 | (mapcar | ||
| 589 | (lambda (attribute) | ||
| 590 | (org-taskjuggler-filter-and-join | ||
| 591 | (org-taskjuggler-get-attribute item attribute))) | ||
| 592 | attributes))) | ||
| 593 | |||
| 594 | (defun org-taskjuggler-get-attribute (item attribute) | ||
| 595 | "Return a list of strings containing the properly formatted | ||
| 596 | taskjuggler declaration for a given ATTRIBUTE in ITEM (an alist). | ||
| 597 | If the ATTRIBUTE is not in ITEM return nil." | ||
| 598 | (cond | ||
| 599 | ((null item) nil) | ||
| 600 | ((equal (symbol-name attribute) (car (car item))) | ||
| 601 | (cons (format "%s %s" (symbol-name attribute) (cdr (car item))) | ||
| 602 | (org-taskjuggler-get-attribute (cdr item) attribute))) | ||
| 603 | (t (org-taskjuggler-get-attribute (cdr item) attribute)))) | ||
| 604 | |||
| 605 | (defun org-taskjuggler-open-resource (resource) | ||
| 606 | "Insert the beginning of a resource declaration. All valid | ||
| 607 | attributes from the RESOURCE alist are inserted. If the RESOURCE | ||
| 608 | defines a property \"resource_id\" it will be used as the id for | ||
| 609 | this resource. Otherwise it will use the ID property. If neither | ||
| 610 | is defined it will calculate a unique id for the resource using | ||
| 611 | `org-taskjuggler-get-unique-id'." | ||
| 612 | (let ((id (org-taskjuggler-clean-id | ||
| 613 | (or (cdr (assoc "resource_id" resource)) | ||
| 614 | (cdr (assoc "ID" resource)) | ||
| 615 | (cdr (assoc "unique-id" resource))))) | ||
| 616 | (headline (cdr (assoc "headline" resource))) | ||
| 617 | (attributes '(limits vacation shift booking efficiency journalentry rate))) | ||
| 618 | (insert | ||
| 619 | (concat | ||
| 620 | "resource " id " \"" headline "\" {\n " | ||
| 621 | (org-taskjuggler-get-attributes resource attributes) "\n")))) | ||
| 622 | |||
| 623 | (defun org-taskjuggler-clean-effort (effort) | ||
| 624 | "Translate effort strings into a format acceptable to taskjuggler, | ||
| 625 | i.e. REAL UNIT. A valid effort string can be anything that is | ||
| 626 | accepted by `org-duration-string-to-minutes“." | ||
| 627 | (cond | ||
| 628 | ((null effort) effort) | ||
| 629 | (t (let* ((minutes (org-duration-string-to-minutes effort)) | ||
| 630 | (hours (/ minutes 60.0))) | ||
| 631 | (format "%.1fh" hours))))) | ||
| 632 | |||
| 633 | (defun org-taskjuggler-get-priority (priority) | ||
| 634 | "Return a priority between 1 and 1000 based on PRIORITY, an | ||
| 635 | org-mode priority string." | ||
| 636 | (max 1 (/ (* 1000 (- org-lowest-priority (string-to-char priority))) | ||
| 637 | (- org-lowest-priority org-highest-priority)))) | ||
| 638 | |||
| 639 | (defun org-taskjuggler-open-task (task) | ||
| 640 | (let* ((unique-id (cdr (assoc "unique-id" task))) | ||
| 641 | (headline (cdr (assoc "headline" task))) | ||
| 642 | (effort (org-taskjuggler-clean-effort (cdr (assoc org-effort-property task)))) | ||
| 643 | (depends (cdr (assoc "depends" task))) | ||
| 644 | (allocate (cdr (assoc "allocate" task))) | ||
| 645 | (priority-raw (cdr (assoc "PRIORITY" task))) | ||
| 646 | (priority (and priority-raw (org-taskjuggler-get-priority priority-raw))) | ||
| 647 | (state (cdr (assoc "TODO" task))) | ||
| 648 | (complete (or (and (member state org-done-keywords) "100") | ||
| 649 | (cdr (assoc "complete" task)))) | ||
| 650 | (parent-ordered (cdr (assoc "parent-ordered" task))) | ||
| 651 | (previous-sibling (cdr (assoc "previous-sibling" task))) | ||
| 652 | (milestone (or (cdr (assoc "milestone" task)) | ||
| 653 | (and (assoc "leaf-node" task) | ||
| 654 | (not (or effort | ||
| 655 | (cdr (assoc "duration" task)) | ||
| 656 | (cdr (assoc "end" task)) | ||
| 657 | (cdr (assoc "period" task))))))) | ||
| 658 | (attributes | ||
| 659 | '(account start note duration endbuffer endcredit end | ||
| 660 | flags journalentry length maxend maxstart minend | ||
| 661 | minstart period reference responsible scheduling | ||
| 662 | startbuffer startcredit statusnote))) | ||
| 663 | (insert | ||
| 664 | (concat | ||
| 665 | "task " unique-id " \"" headline "\" {\n" | ||
| 666 | (if (and parent-ordered previous-sibling) | ||
| 667 | (format " depends %s\n" previous-sibling) | ||
| 668 | (and depends (format " depends %s\n" depends))) | ||
| 669 | (and allocate (format " purge %s\n allocate %s\n" | ||
| 670 | (or (and (org-taskjuggler-targeting-tj3-p) "allocate") | ||
| 671 | "allocations") | ||
| 672 | allocate)) | ||
| 673 | (and complete (format " complete %s\n" complete)) | ||
| 674 | (and effort (format " effort %s\n" effort)) | ||
| 675 | (and priority (format " priority %s\n" priority)) | ||
| 676 | (and milestone (format " milestone\n")) | ||
| 677 | |||
| 678 | (org-taskjuggler-get-attributes task attributes) | ||
| 679 | "\n")))) | ||
| 680 | |||
| 681 | (defun org-taskjuggler-close-maybe (level) | ||
| 682 | (while (> org-export-taskjuggler-old-level level) | ||
| 683 | (insert "}\n") | ||
| 684 | (setq org-export-taskjuggler-old-level (1- org-export-taskjuggler-old-level))) | ||
| 685 | (when (= org-export-taskjuggler-old-level level) | ||
| 686 | (insert "}\n"))) | ||
| 687 | |||
| 688 | (defun org-taskjuggler-insert-reports () | ||
| 689 | (let (report) | ||
| 690 | (dolist (report org-export-taskjuggler-default-reports) | ||
| 691 | (insert report "\n")))) | ||
| 692 | |||
| 693 | (provide 'org-taskjuggler) | ||
| 694 | |||
| 695 | ;; Local variables: | ||
| 696 | ;; generated-autoload-file: "org-loaddefs.el" | ||
| 697 | ;; End: | ||
| 698 | |||
| 699 | ;;; org-taskjuggler.el ends here | ||