org-taskjuggler.el 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  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. ;;; Code:
  35. (eval-when-compile
  36. (require 'cl))
  37. (require 'org)
  38. (require 'org-exp)
  39. ;;; User variables:
  40. (defgroup org-export-taskjuggler nil
  41. "Options for exporting Org-mode files to TaskJuggler."
  42. :tag "Org Export TaskJuggler"
  43. :group 'org-export)
  44. (defcustom org-export-taskjuggler-extension ".tjp"
  45. "Extension of TaskJuggler files."
  46. :group 'org-export-taskjuggler
  47. :type 'string)
  48. (defcustom org-export-taskjuggler-project-tag "project"
  49. "."
  50. :group 'org-export-taskjuggler
  51. :type 'string)
  52. (defcustom org-export-taskjuggler-resource-tag "resource"
  53. "."
  54. :group 'org-export-taskjuggler
  55. :type 'string)
  56. (defcustom org-export-taskjuggler-default-project-version "1.0"
  57. "."
  58. :group 'org-export-taskjuggler
  59. :type 'string)
  60. (defcustom org-export-taskjuggler-default-project-duration 180
  61. "."
  62. :group 'org-export-taskjuggler
  63. :type 'integer)
  64. (defcustom org-export-taskjuggler-default-reports
  65. '("taskreport \"Gantt Chart\" {
  66. headline \"Project Gantt Chart\"
  67. columns hierarchindex, name, start, end, effort, duration, completed, chart
  68. timeformat \"%a %Y-%m-%d\"
  69. loadunit days
  70. }"
  71. "resourcereport \"Resource Graph\" {
  72. headline \"Resource Allocation Graph\"
  73. columns no, name, rate, utilization, freeload, chart
  74. loadunit days
  75. hidetask 1
  76. }")
  77. ""
  78. :group 'org-export-taskjuggler
  79. :type '(repeat (string :tag "Report")))
  80. ;;; Hooks
  81. (defvar org-export-taskjuggler-final-hook nil
  82. "Hook run at the end of TaskJuggler export, in the new buffer.")
  83. ;;; Autoload functions:
  84. ;;;###autoload
  85. (defun org-export-as-taskjuggler ()
  86. "Export the current buffer as a TaskJuggler file."
  87. (interactive)
  88. (message "Exporting...")
  89. (let* ((tasks
  90. (org-taskjuggler-resolve-dependencies
  91. (org-taskjuggler-assign-task-ids
  92. (org-map-entries '(org-taskjuggler-components)
  93. org-export-taskjuggler-project-tag nil 'archive 'comment))))
  94. (resources
  95. (org-taskjuggler-assign-resource-ids
  96. (org-map-entries '(org-taskjuggler-components)
  97. org-export-taskjuggler-resource-tag nil 'archive 'comment)))
  98. (filename (expand-file-name
  99. (concat
  100. (file-name-sans-extension
  101. (file-name-nondirectory buffer-file-name))
  102. org-export-taskjuggler-extension)))
  103. (buffer (find-file-noselect filename))
  104. (old-level 0)
  105. task resource)
  106. ;; add a default resource
  107. (unless resources
  108. (setq resources
  109. `((("ID" . ,(user-login-name))
  110. ("headline" . ,user-full-name)
  111. ("level" . 1)))))
  112. ;; add a default allocation to the first task if none was given
  113. (unless (assoc "allocate" (car tasks))
  114. (let ((task (car tasks))
  115. (resource-id (cdr (assoc "ID" (car resources)))))
  116. (setcar tasks (push (cons "allocate" resource-id) task))))
  117. ;; add a default start date to the first task if none was given
  118. (unless (assoc "start" (car tasks))
  119. (let ((task (car tasks))
  120. (time-string (format-time-string "%Y-%m-%d")))
  121. (setcar tasks (push (cons "start" time-string) task))))
  122. ;; add a default end date to the first task if none was given
  123. (unless (assoc "end" (car tasks))
  124. (let* ((task (car tasks))
  125. (now (current-time))
  126. (duration
  127. (days-to-time org-export-taskjuggler-default-project-duration))
  128. (time-string
  129. (format-time-string "%Y-%m-%d" (time-add now duration))))
  130. (setcar tasks (push (cons "end" time-string) task))))
  131. ;; add a default version if none was given
  132. (unless (assoc "version" (car tasks))
  133. (let ((task (car tasks))
  134. (version org-export-taskjuggler-default-project-version))
  135. (setcar tasks (push (cons "version" version) task))))
  136. (with-current-buffer buffer
  137. (erase-buffer)
  138. (org-taskjuggler-open-project (car tasks))
  139. (dolist (resource resources)
  140. (let ((level (cdr (assoc "level" resource))))
  141. (org-taskjuggler-close-maybe level)
  142. (org-taskjuggler-open-resource resource)
  143. (setq old-level level)))
  144. (org-taskjuggler-close-maybe 1)
  145. (setq old-level 0)
  146. (dolist (task tasks)
  147. (let ((level (cdr (assoc "level" task))))
  148. (org-taskjuggler-close-maybe level)
  149. (org-taskjuggler-open-task task)
  150. (setq old-level level)))
  151. (org-taskjuggler-close-maybe 1)
  152. (org-taskjuggler-insert-reports)
  153. (save-buffer)
  154. (or (org-export-push-to-kill-ring "TaskJuggler")
  155. (message "Exporting... done"))
  156. (current-buffer))))
  157. ;;;###autoload
  158. (defun org-export-as-taskjuggler-and-open ()
  159. "Export the current buffer as a TaskJuggler file and open it with the TaskJuggler GUI."
  160. (interactive)
  161. (let ((file-name (buffer-file-name (org-export-as-taskjuggler)))
  162. (command "TaskJugglerUI"))
  163. (start-process-shell-command command nil command file-name)))
  164. (defun org-taskjuggler-parent-is-ordered-p ()
  165. (save-excursion
  166. (and (org-up-heading-safe) (org-entry-get (point) "ORDERED"))))
  167. (defun org-taskjuggler-components ()
  168. (let* ((props (org-entry-properties))
  169. (components (org-heading-components))
  170. (level (car components))
  171. (headline (nth 4 components))
  172. (parent-ordered (org-taskjuggler-parent-is-ordered-p)))
  173. (push (cons "level" level) props)
  174. (push (cons "headline" headline) props)
  175. (push (cons "parent-ordered" parent-ordered) props)))
  176. (defun org-taskjuggler-assign-task-ids (tasks)
  177. (let ((previous-level 0)
  178. unique-ids
  179. path
  180. task resolved-tasks tmp)
  181. (dolist (task tasks resolved-tasks)
  182. (let ((level (cdr (assoc "level" task))))
  183. (cond
  184. ((< previous-level level)
  185. (setq unique-id (org-taskjuggler-get-unique-id task (car unique-ids)))
  186. (dotimes (tmp (- level previous-level))
  187. (push (list unique-id) unique-ids)
  188. (push unique-id path)))
  189. ((= previous-level level)
  190. (setq unique-id (org-taskjuggler-get-unique-id task (car unique-ids)))
  191. (push unique-id (car unique-ids)))
  192. ((> previous-level level)
  193. (dotimes (tmp (- previous-level level))
  194. (pop unique-ids)
  195. (pop path))
  196. (setq unique-id (org-taskjuggler-get-unique-id task (car unique-ids)))))
  197. (push (cons "unique-id" unique-id) task)
  198. (push (cons "path" (mapconcat 'identity (reverse path) ".")) task)
  199. (setq previous-level level)
  200. (setq resolved-tasks (append resolved-tasks (list task)))))))
  201. (defun org-taskjuggler-assign-resource-ids (resources)
  202. (let (unique-ids
  203. unique-id
  204. resource resolved-resources)
  205. (dolist (resource resources resolved-resources)
  206. (setq unique-id (org-taskjuggler-get-unique-id resource unique-ids))
  207. (push unique-id unique-ids)
  208. (push (cons "unique-id" unique-id) resource)
  209. (setq resolved-resources (append resolved-resources (list resource))))))
  210. (defun org-taskjuggler-resolve-dependencies (tasks)
  211. (let ((previous-level 0)
  212. siblings
  213. task resolved-tasks)
  214. (dolist (task tasks resolved-tasks)
  215. (let ((level (cdr (assoc "level" task)))
  216. (depends (cdr (assoc "depends" task)))
  217. (parent-ordered (cdr (assoc "parent-ordered" task)))
  218. previous-sibling)
  219. (cond
  220. ((< previous-level level)
  221. (dotimes (tmp (- level previous-level))
  222. (push task siblings)))
  223. ((= previous-level level)
  224. (setq previous-sibling (car siblings))
  225. (setcar siblings task))
  226. ((> previous-level level)
  227. (dotimes (tmp (- previous-level level))
  228. (pop siblings))
  229. (setq previous-sibling (car siblings))
  230. (setcar siblings task)))
  231. (when (and previous-sibling parent-ordered)
  232. (push
  233. (cons "depends"
  234. (format "!%s" (cdr (assoc "unique-id" previous-sibling)))) task))
  235. (setq previous-level level)
  236. (setq resolved-tasks (append resolved-tasks (list task)))))))
  237. (defun org-taskjuggler-get-unique-id (task unique-ids)
  238. (let* ((headline (cdr (assoc "headline" task)))
  239. (parts (split-string headline))
  240. (id (downcase (pop parts))))
  241. ; try to add more parts of the headline to make it unique
  242. (while (member id unique-ids)
  243. (setq id (concat id "_" (downcase (pop parts)))))
  244. ; if its still not unique add "_"
  245. (while (member id unique-ids)
  246. (setq id (concat id "_")))
  247. (org-taskjuggler-clean-id id)))
  248. (defun org-taskjuggler-clean-id (id)
  249. (and id (replace-regexp-in-string "[^a-zA-Z0-9_]" "_" id)))
  250. (defun org-taskjuggler-open-project (project)
  251. (let ((unique-id (cdr (assoc "unique-id" project)))
  252. (headline (cdr (assoc "headline" project)))
  253. (version (cdr (assoc "version" project)))
  254. (start (cdr (assoc "start" project)))
  255. (end (cdr (assoc "end" project))))
  256. (insert
  257. (concat
  258. "project " unique-id
  259. " \"" headline "\" \"" version "\" " start " - " end " {\n " "}\n"))))
  260. (defun org-taskjuggler-open-resource (resource)
  261. (let ((id (org-taskjuggler-clean-id (cdr (assoc "ID" resource))))
  262. (unique-id (org-taskjuggler-clean-id (cdr (assoc "unique-id" resource))))
  263. (headline (cdr (assoc "headline" resource))))
  264. (insert
  265. (concat "resource " (or id unique-id) " \"" headline "\" {\n "))))
  266. (defun org-taskjuggler-clean-effort (effort)
  267. (cond
  268. ((null effort) effort)
  269. ((string-match "\\([0-9]+\\):\\([0-9]+\\)" effort)
  270. (concat (match-string 1 effort) "." (match-string 2 effort) "h"))
  271. ((string-match "\\([0-9]+\\).\\([0-9]+\\)" effort) (concat effort "d"))
  272. (t (error "Not a valid effort (%s)" effort))))
  273. (defun org-taskjuggler-open-task (task)
  274. (let ((unique-id (cdr (assoc "unique-id" task)))
  275. (headline (cdr (assoc "headline" task)))
  276. (effort (org-taskjuggler-clean-effort (cdr (assoc org-effort-property task))))
  277. (depends (cdr (assoc "depends" task)))
  278. (allocate (cdr (assoc "allocate" task)))
  279. (account (cdr (assoc "account" task)))
  280. (start (cdr (assoc "start" task)))
  281. (complete (cdr (assoc "complete" task)))
  282. (note (cdr (assoc "note" task)))
  283. (priority (cdr (assoc "priority" task)))
  284. (parent-ordered (cdr (assoc "parent-ordered" task)))
  285. (previous-sibling (cdr (assoc "previous-sibling" task))))
  286. (insert
  287. (concat
  288. "task " unique-id " \"" headline "\" {"
  289. (and effort (concat "\n effort " effort))
  290. (if (and parent-ordered previous-sibling)
  291. (concat "\n depends " previous-sibling)
  292. (and depends (concat "\n depends " depends)))
  293. (and allocate (concat "\n purge allocations\n allocate " allocate))
  294. (and account (concat "\n account " account))
  295. (and start (concat "\n start " start))
  296. (and complete (concat "\n complete " complete))
  297. (and note (concat "\n note " note))
  298. (and priority (concat "\n priority " priority))
  299. "\n"))))
  300. (defun org-taskjuggler-close-maybe (level)
  301. (while (> old-level level)
  302. (insert "}\n")
  303. (setq old-level (1- old-level)))
  304. (when (= old-level level)
  305. (insert "}\n")))
  306. (defun org-taskjuggler-insert-reports ()
  307. (let (report)
  308. (dolist (report org-export-taskjuggler-default-reports)
  309. (insert report "\n"))))
  310. (provide 'org-taskjuggler)
  311. ;; arch-tag: a24a127c-d365-4c2a-9e9b-f7dcb0ebfdc3
  312. ;;; org-taskjuggler.el ends here