org-taskjuggler.el 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. ;;; org-taskjuggler.el --- TaskJuggler exporter for org-mode
  2. ;;
  3. ;; Copyright (C) 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
  4. ;;
  5. ;; Emacs Lisp Archive Entry
  6. ;; Filename: org-taskjuggler.el
  7. ;; Version: 6.34trans
  8. ;; Author: Christian Egli
  9. ;; Maintainer: Christian Egli
  10. ;; Keywords: org, taskjuggler, project planning
  11. ;; Description: Converts an org-mode buffer into a taskjuggler project plan
  12. ;; URL:
  13. ;; This file is part of GNU Emacs.
  14. ;; GNU Emacs is free software: you can redistribute it and/or modify
  15. ;; it under the terms of the GNU General Public License as published by
  16. ;; the Free Software Foundation, either version 3 of the License, or
  17. ;; (at your option) any later version.
  18. ;; GNU Emacs is distributed in the hope that it will be useful,
  19. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. ;; GNU General Public License for more details.
  22. ;; You should have received a copy of the GNU General Public License
  23. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  24. ;; Commentary:
  25. ;;
  26. ;; This library implements a TaskJuggler exporter for org-mode.
  27. ;;
  28. ;; The interactive functions are similar to those of the HTML and LaTeX
  29. ;; exporters:
  30. ;;
  31. ;; M-x `org-export-as-taskjuggler'
  32. ;; M-x `org-export-as-taskjuggler-and-open'
  33. ;;
  34. ;;; TODO:
  35. ;; * derive completeness info from TODO state
  36. ;; * Handle explicit dependencies such as BLOCKER and depends attribute
  37. ;; * Code cleanup
  38. ;; * Add documentation
  39. ;; * Try using plists instead of alists
  40. ;;
  41. ;;; Code:
  42. (eval-when-compile
  43. (require 'cl))
  44. (require 'org)
  45. (require 'org-exp)
  46. ;;; User variables:
  47. (defgroup org-export-taskjuggler nil
  48. "Options for exporting Org-mode files to TaskJuggler."
  49. :tag "Org Export TaskJuggler"
  50. :group 'org-export)
  51. (defcustom org-export-taskjuggler-extension ".tjp"
  52. "Extension of TaskJuggler files."
  53. :group 'org-export-taskjuggler
  54. :type 'string)
  55. (defcustom org-export-taskjuggler-project-tag "project"
  56. "."
  57. :group 'org-export-taskjuggler
  58. :type 'string)
  59. (defcustom org-export-taskjuggler-resource-tag "resource"
  60. "."
  61. :group 'org-export-taskjuggler
  62. :type 'string)
  63. (defcustom org-export-taskjuggler-default-project-version "1.0"
  64. "."
  65. :group 'org-export-taskjuggler
  66. :type 'string)
  67. (defcustom org-export-taskjuggler-default-project-duration 180
  68. "."
  69. :group 'org-export-taskjuggler
  70. :type 'integer)
  71. (defcustom org-export-taskjuggler-default-reports
  72. '("taskreport \"Gantt Chart\" {
  73. headline \"Project Gantt Chart\"
  74. columns hierarchindex, name, start, end, effort, duration, completed, chart
  75. timeformat \"%a %Y-%m-%d\"
  76. loadunit days
  77. }"
  78. "resourcereport \"Resource Graph\" {
  79. headline \"Resource Allocation Graph\"
  80. columns no, name, rate, utilization, freeload, chart
  81. loadunit days
  82. hidetask 1
  83. }")
  84. ""
  85. :group 'org-export-taskjuggler
  86. :type '(repeat (string :tag "Report")))
  87. ;;; Hooks
  88. (defvar org-export-taskjuggler-final-hook nil
  89. "Hook run at the end of TaskJuggler export, in the new buffer.")
  90. ;;; Autoload functions:
  91. ;;;###autoload
  92. (defun org-export-as-taskjuggler ()
  93. "Export the current buffer as a TaskJuggler file."
  94. (interactive)
  95. (message "Exporting...")
  96. (let* ((tasks
  97. (org-taskjuggler-resolve-dependencies
  98. (org-taskjuggler-assign-task-ids
  99. (org-map-entries '(org-taskjuggler-components)
  100. org-export-taskjuggler-project-tag nil 'archive 'comment))))
  101. (resources
  102. (org-taskjuggler-assign-resource-ids
  103. (org-map-entries '(org-taskjuggler-components)
  104. org-export-taskjuggler-resource-tag nil 'archive 'comment)))
  105. (filename (expand-file-name
  106. (concat
  107. (file-name-sans-extension
  108. (file-name-nondirectory buffer-file-name))
  109. org-export-taskjuggler-extension)))
  110. (buffer (find-file-noselect filename))
  111. (old-level 0)
  112. task resource)
  113. ;; add a default resource
  114. (unless resources
  115. (setq resources
  116. `((("ID" . ,(user-login-name))
  117. ("headline" . ,user-full-name)
  118. ("level" . 1)))))
  119. ;; add a default allocation to the first task if none was given
  120. (unless (assoc "allocate" (car tasks))
  121. (let ((task (car tasks))
  122. (resource-id (cdr (assoc "ID" (car resources)))))
  123. (setcar tasks (push (cons "allocate" resource-id) task))))
  124. ;; add a default start date to the first task if none was given
  125. (unless (assoc "start" (car tasks))
  126. (let ((task (car tasks))
  127. (time-string (format-time-string "%Y-%m-%d")))
  128. (setcar tasks (push (cons "start" time-string) task))))
  129. ;; add a default end date to the first task if none was given
  130. (unless (assoc "end" (car tasks))
  131. (let* ((task (car tasks))
  132. (now (current-time))
  133. (duration
  134. (days-to-time org-export-taskjuggler-default-project-duration))
  135. (time-string
  136. (format-time-string "%Y-%m-%d" (time-add now duration))))
  137. (setcar tasks (push (cons "end" time-string) task))))
  138. ;; add a default version if none was given
  139. (unless (assoc "version" (car tasks))
  140. (let ((task (car tasks))
  141. (version org-export-taskjuggler-default-project-version))
  142. (setcar tasks (push (cons "version" version) task))))
  143. (with-current-buffer buffer
  144. (erase-buffer)
  145. (org-taskjuggler-open-project (car tasks))
  146. (dolist (resource resources)
  147. (let ((level (cdr (assoc "level" resource))))
  148. (org-taskjuggler-close-maybe level)
  149. (org-taskjuggler-open-resource resource)
  150. (setq old-level level)))
  151. (org-taskjuggler-close-maybe 1)
  152. (setq old-level 0)
  153. (dolist (task tasks)
  154. (let ((level (cdr (assoc "level" task))))
  155. (org-taskjuggler-close-maybe level)
  156. (org-taskjuggler-open-task task)
  157. (setq old-level level)))
  158. (org-taskjuggler-close-maybe 1)
  159. (org-taskjuggler-insert-reports)
  160. (save-buffer)
  161. (or (org-export-push-to-kill-ring "TaskJuggler")
  162. (message "Exporting... done"))
  163. (current-buffer))))
  164. ;;;###autoload
  165. (defun org-export-as-taskjuggler-and-open ()
  166. "Export the current buffer as a TaskJuggler file and open it with the TaskJuggler GUI."
  167. (interactive)
  168. (let ((file-name (buffer-file-name (org-export-as-taskjuggler)))
  169. (command "TaskJugglerUI"))
  170. (start-process-shell-command command nil command file-name)))
  171. (defun org-taskjuggler-parent-is-ordered-p ()
  172. (save-excursion
  173. (and (org-up-heading-safe) (org-entry-get (point) "ORDERED"))))
  174. (defun org-taskjuggler-components ()
  175. (let* ((props (org-entry-properties))
  176. (components (org-heading-components))
  177. (level (car components))
  178. (headline (nth 4 components))
  179. (parent-ordered (org-taskjuggler-parent-is-ordered-p)))
  180. (push (cons "level" level) props)
  181. (push (cons "headline" headline) props)
  182. (push (cons "parent-ordered" parent-ordered) props)))
  183. (defun org-taskjuggler-assign-task-ids (tasks)
  184. (let ((previous-level 0)
  185. unique-ids
  186. path
  187. task resolved-tasks tmp)
  188. (dolist (task tasks resolved-tasks)
  189. (let ((level (cdr (assoc "level" task))))
  190. (cond
  191. ((< previous-level level)
  192. (setq unique-id (org-taskjuggler-get-unique-id task (car unique-ids)))
  193. (dotimes (tmp (- level previous-level))
  194. (push (list unique-id) unique-ids)
  195. (push unique-id path)))
  196. ((= previous-level level)
  197. (setq unique-id (org-taskjuggler-get-unique-id task (car unique-ids)))
  198. (push unique-id (car unique-ids)))
  199. ((> previous-level level)
  200. (dotimes (tmp (- previous-level level))
  201. (pop unique-ids)
  202. (pop path))
  203. (setq unique-id (org-taskjuggler-get-unique-id task (car unique-ids)))
  204. (push unique-id (car unique-ids))))
  205. (push (cons "unique-id" unique-id) task)
  206. (push (cons "path" (mapconcat 'identity (reverse path) ".")) task)
  207. (setq previous-level level)
  208. (setq resolved-tasks (append resolved-tasks (list task)))))))
  209. (defun org-taskjuggler-assign-resource-ids (resources)
  210. (let (unique-ids
  211. unique-id
  212. resource resolved-resources)
  213. (dolist (resource resources resolved-resources)
  214. (setq unique-id (org-taskjuggler-get-unique-id resource unique-ids))
  215. (push unique-id unique-ids)
  216. (push (cons "unique-id" unique-id) resource)
  217. (setq resolved-resources (append resolved-resources (list resource))))))
  218. (defun org-taskjuggler-resolve-dependencies (tasks)
  219. (let ((previous-level 0)
  220. siblings
  221. task resolved-tasks)
  222. (dolist (task tasks resolved-tasks)
  223. (let ((level (cdr (assoc "level" task)))
  224. (depends (cdr (assoc "depends" task)))
  225. (parent-ordered (cdr (assoc "parent-ordered" task)))
  226. previous-sibling)
  227. (cond
  228. ((< previous-level level)
  229. (dotimes (tmp (- level previous-level))
  230. (push task siblings)))
  231. ((= previous-level level)
  232. (setq previous-sibling (car siblings))
  233. (setcar siblings task))
  234. ((> previous-level level)
  235. (dotimes (tmp (- previous-level level))
  236. (pop siblings))
  237. (setq previous-sibling (car siblings))
  238. (setcar siblings task)))
  239. (when (and previous-sibling parent-ordered)
  240. (push
  241. (cons "depends"
  242. (format "!%s" (cdr (assoc "unique-id" previous-sibling)))) task))
  243. (setq previous-level level)
  244. (setq resolved-tasks (append resolved-tasks (list task)))))))
  245. (defun org-taskjuggler-get-unique-id (task unique-ids)
  246. (let* ((headline (cdr (assoc "headline" task)))
  247. (parts (split-string headline))
  248. (id (downcase (pop parts))))
  249. ; try to add more parts of the headline to make it unique
  250. (while (member id unique-ids)
  251. (setq id (concat id "_" (downcase (pop parts)))))
  252. ; if its still not unique add "_"
  253. (while (member id unique-ids)
  254. (setq id (concat id "_")))
  255. (org-taskjuggler-clean-id id)))
  256. (defun org-taskjuggler-clean-id (id)
  257. (and id (replace-regexp-in-string "[^a-zA-Z0-9_]" "_" id)))
  258. (defun org-taskjuggler-open-project (project)
  259. (let ((unique-id (cdr (assoc "unique-id" project)))
  260. (headline (cdr (assoc "headline" project)))
  261. (version (cdr (assoc "version" project)))
  262. (start (cdr (assoc "start" project)))
  263. (end (cdr (assoc "end" project))))
  264. (insert
  265. (concat
  266. "project " unique-id
  267. " \"" headline "\" \"" version "\" " start " - " end " {\n }\n"))))
  268. (defun org-taskjuggler-open-resource (resource)
  269. (let ((id (org-taskjuggler-clean-id (cdr (assoc "ID" resource))))
  270. (unique-id (org-taskjuggler-clean-id (cdr (assoc "unique-id" resource))))
  271. (headline (cdr (assoc "headline" resource)))
  272. (limits (cdr (assoc "limits" resource)))
  273. (vacation (cdr (assoc "vacation" resource))))
  274. (insert
  275. (concat "resource " (or id unique-id) " \"" headline "\" {\n "
  276. (and limits (concat "\n limits { " limits " }\n"))
  277. (and vacation (concat "\n vacation " vacation "\n"))))))
  278. (defun org-taskjuggler-clean-effort (effort)
  279. (cond
  280. ((null effort) effort)
  281. ((string-match "\\([0-9]+\\):\\([0-9]+\\)" effort)
  282. (concat (match-string 1 effort) "." (match-string 2 effort) "h"))
  283. ((string-match "\\([0-9]+\\).\\([0-9]+\\)" effort) (concat effort "d"))
  284. (t (error "Not a valid effort (%s)" effort))))
  285. (defun org-taskjuggler-open-task (task)
  286. (let ((unique-id (cdr (assoc "unique-id" task)))
  287. (headline (cdr (assoc "headline" task)))
  288. (effort (org-taskjuggler-clean-effort (cdr (assoc org-effort-property task))))
  289. (depends (cdr (assoc "depends" task)))
  290. (allocate (cdr (assoc "allocate" task)))
  291. (account (cdr (assoc "account" task)))
  292. (start (cdr (assoc "start" task)))
  293. (complete (cdr (assoc "complete" task)))
  294. (note (cdr (assoc "note" task)))
  295. (priority (cdr (assoc "priority" task)))
  296. (parent-ordered (cdr (assoc "parent-ordered" task)))
  297. (previous-sibling (cdr (assoc "previous-sibling" task))))
  298. (insert
  299. (concat
  300. "task " unique-id " \"" headline "\" {"
  301. (and effort (concat "\n effort " effort))
  302. (if (and parent-ordered previous-sibling)
  303. (concat "\n depends " previous-sibling)
  304. (and depends (concat "\n depends " depends)))
  305. (and allocate (concat "\n purge allocations\n allocate " allocate))
  306. (and account (concat "\n account " account))
  307. (and start (concat "\n start " start))
  308. (and complete (concat "\n complete " complete))
  309. (and note (concat "\n note " note))
  310. (and priority (concat "\n priority " priority))
  311. "\n"))))
  312. (defun org-taskjuggler-close-maybe (level)
  313. (while (> old-level level)
  314. (insert "}\n")
  315. (setq old-level (1- old-level)))
  316. (when (= old-level level)
  317. (insert "}\n")))
  318. (defun org-taskjuggler-insert-reports ()
  319. (let (report)
  320. (dolist (report org-export-taskjuggler-default-reports)
  321. (insert report "\n"))))
  322. (provide 'org-taskjuggler)
  323. ;;; org-taskjuggler.el ends here