org-taskjuggler.el 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  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-ids
  92. (org-map-entries '(org-taskjuggler-components)
  93. org-export-taskjuggler-project-tag nil 'archive 'comment))))
  94. (resources
  95. (org-map-entries '(org-taskjuggler-components)
  96. org-export-taskjuggler-resource-tag nil 'archive 'comment))
  97. (filename (expand-file-name
  98. (concat
  99. (file-name-sans-extension
  100. (file-name-nondirectory buffer-file-name))
  101. org-export-taskjuggler-extension)))
  102. (buffer (find-file-noselect filename))
  103. (old-level 0)
  104. task resource)
  105. ;; add a default resource
  106. (unless resources
  107. (setq resources
  108. `((("ID" . ,(user-login-name))
  109. ("headline" . ,user-full-name)
  110. ("level" . 1)))))
  111. ;; add a default allocation to the first task if none was given
  112. (unless (assoc "allocate" (car tasks))
  113. (let ((task (car tasks))
  114. (resource-id (cdr (assoc "ID" (car resources)))))
  115. (setcar tasks (push (cons "allocate" resource-id) task))))
  116. ;; add a default start date to the first task if none was given
  117. (unless (assoc "start" (car tasks))
  118. (let ((task (car tasks))
  119. (time-string (format-time-string "%Y-%m-%d")))
  120. (setcar tasks (push (cons "start" time-string) task))))
  121. ;; add a default end date to the first task if none was given
  122. (unless (assoc "end" (car tasks))
  123. (let* ((task (car tasks))
  124. (now (current-time))
  125. (duration
  126. (days-to-time org-export-taskjuggler-default-project-duration))
  127. (time-string
  128. (format-time-string "%Y-%m-%d" (time-add now duration))))
  129. (setcar tasks (push (cons "end" time-string) task))))
  130. ;; add a default version if none was given
  131. (unless (assoc "version" (car tasks))
  132. (let ((task (car tasks))
  133. (version org-export-taskjuggler-default-project-version))
  134. (setcar tasks (push (cons "version" version) task))))
  135. (with-current-buffer buffer
  136. (erase-buffer)
  137. (org-taskjuggler-open-project (car tasks))
  138. (dolist (resource resources)
  139. (let ((level (cdr (assoc "level" resource))))
  140. (org-taskjuggler-close-maybe level)
  141. (org-taskjuggler-open-resource resource)
  142. (setq old-level level)))
  143. (org-taskjuggler-close-maybe 1)
  144. (setq old-level 0)
  145. (dolist (task tasks)
  146. (let ((level (cdr (assoc "level" task))))
  147. (org-taskjuggler-close-maybe level)
  148. (org-taskjuggler-open-task task)
  149. (setq old-level level)))
  150. (org-taskjuggler-close-maybe 1)
  151. (org-taskjuggler-insert-reports)
  152. (save-buffer)
  153. (or (org-export-push-to-kill-ring "TaskJuggler")
  154. (message "Exporting... done"))
  155. (current-buffer))))
  156. ;;;###autoload
  157. (defun org-export-as-taskjuggler-and-open ()
  158. "Export the current buffer as a TaskJuggler file and open it with the TaskJuggler GUI."
  159. (interactive)
  160. (let ((file-name (buffer-file-name (org-export-as-taskjuggler)))
  161. (command "TaskJugglerUI"))
  162. (start-process-shell-command command nil command file-name)))
  163. (defun org-taskjuggler-parent-is-ordered-p ()
  164. (save-excursion
  165. (and (org-up-heading-safe) (org-entry-get (point) "ORDERED"))))
  166. (defun org-taskjuggler-components ()
  167. (let* ((props (org-entry-properties))
  168. (components (org-heading-components))
  169. (level (car components))
  170. (headline (nth 4 components))
  171. (parent-ordered (org-taskjuggler-parent-is-ordered-p)))
  172. (push (cons "level" level) props)
  173. (push (cons "headline" headline) props)
  174. (push (cons "parent-ordered" parent-ordered) props)))
  175. (defun org-taskjuggler-assign-ids (tasks)
  176. (let ((previous-level 0)
  177. unique-ids
  178. path
  179. task resolved-tasks tmp)
  180. (dolist (task tasks resolved-tasks)
  181. (let ((level (cdr (assoc "level" task))))
  182. (cond
  183. ((< previous-level level)
  184. (setq unique-id (org-taskjuggler-get-unique-id task (car unique-ids)))
  185. (dotimes (tmp (- level previous-level))
  186. (push (list unique-id) unique-ids)
  187. (push unique-id path)))
  188. ((= previous-level level)
  189. (setq unique-id (org-taskjuggler-get-unique-id task (car unique-ids)))
  190. (push unique-id (car unique-ids)))
  191. ((> previous-level level)
  192. (dotimes (tmp (- previous-level level))
  193. (pop unique-ids)
  194. (pop path))
  195. (setq unique-id (org-taskjuggler-get-unique-id task (car unique-ids)))))
  196. (push (cons "unique-id" unique-id) task)
  197. (push (cons "path" (mapconcat 'identity (reverse path) ".")) task)
  198. (setq previous-level level)
  199. (setq resolved-tasks (append resolved-tasks (list task)))))))
  200. (defun org-taskjuggler-resolve-dependencies (tasks)
  201. (let ((previous-level 0)
  202. siblings
  203. task resolved-tasks)
  204. (dolist (task tasks resolved-tasks)
  205. (let ((level (cdr (assoc "level" task)))
  206. (depends (cdr (assoc "depends" task)))
  207. (parent-ordered (cdr (assoc "parent-ordered" task)))
  208. previous-sibling)
  209. (cond
  210. ((< previous-level level)
  211. (dotimes (tmp (- level previous-level))
  212. (push task siblings)))
  213. ((= previous-level level)
  214. (setq previous-sibling (car siblings))
  215. (setcar siblings task))
  216. ((> previous-level level)
  217. (dotimes (tmp (- previous-level level))
  218. (pop siblings))
  219. (setq previous-sibling (car siblings))
  220. (setcar siblings task)))
  221. (when (and previous-sibling parent-ordered)
  222. (push
  223. (cons "depends"
  224. (format "!%s" (cdr (assoc "unique-id" previous-sibling)))) task))
  225. (setq previous-level level)
  226. (setq resolved-tasks (append resolved-tasks (list task)))))))
  227. (defun org-taskjuggler-get-unique-id (task unique-ids)
  228. (let* ((headline (cdr (assoc "headline" task)))
  229. (parts (split-string headline))
  230. (id (downcase (pop parts))))
  231. ; try to add more parts of the headline to make it unique
  232. (while (member id unique-ids)
  233. (setq id (concat id "_" (downcase (pop parts)))))
  234. ; if its still not unique add "_"
  235. (while (member id unique-ids)
  236. (setq id (concat id "_")))
  237. (org-taskjuggler-clean-id id)))
  238. (defun org-taskjuggler-clean-id (id)
  239. (and id (replace-regexp-in-string "[^a-zA-Z0-9_]" "_" id)))
  240. (defun org-taskjuggler-open-project (project)
  241. (let ((id (org-taskjuggler-clean-id (cdr (assoc "ID" project))))
  242. (headline (cdr (assoc "headline" project)))
  243. (version (cdr (assoc "version" project)))
  244. (start (cdr (assoc "start" project)))
  245. (end (cdr (assoc "end" project))))
  246. (insert
  247. (concat
  248. "project "
  249. (or id "FIXME")
  250. " \"" headline "\" \"" version "\" " start " - " end " {\n " "}\n"))))
  251. (defun org-taskjuggler-open-resource (resource)
  252. (let ((id (org-taskjuggler-clean-id (cdr (assoc "ID" resource))))
  253. (headline (cdr (assoc "headline" resource))))
  254. (insert
  255. (concat "resource " id " \"" headline "\" {\n "))))
  256. (defun org-taskjuggler-clean-effort (effort)
  257. (cond
  258. ((null effort) effort)
  259. ((string-match "\\([0-9]+\\):\\([0-9]+\\)" effort)
  260. (concat (match-string 1 effort) "." (match-string 2 effort) "h"))
  261. ((string-match "\\([0-9]+\\).\\([0-9]+\\)" effort) (concat effort "d"))
  262. (t (error "Not a valid effort (%s)" effort))))
  263. (defun org-taskjuggler-open-task (task)
  264. (let ((unique-id (cdr (assoc "unique-id" task)))
  265. (headline (cdr (assoc "headline" task)))
  266. (effort (org-taskjuggler-clean-effort (cdr (assoc org-effort-property task))))
  267. (depends (cdr (assoc "depends" task)))
  268. (allocate (cdr (assoc "allocate" task)))
  269. (account (cdr (assoc "account" task)))
  270. (start (cdr (assoc "start" task)))
  271. (complete (cdr (assoc "complete" task)))
  272. (note (cdr (assoc "note" task)))
  273. (priority (cdr (assoc "priority" task)))
  274. (parent-ordered (cdr (assoc "parent-ordered" task)))
  275. (previous-sibling (cdr (assoc "previous-sibling" task))))
  276. (insert
  277. (concat
  278. "task " unique-id " \"" headline "\" {"
  279. (and effort (concat "\n effort " effort))
  280. (if (and parent-ordered previous-sibling)
  281. (concat "\n depends " previous-sibling)
  282. (and depends (concat "\n depends " depends)))
  283. (and allocate (concat "\n purge allocations\n allocate " allocate))
  284. (and account (concat "\n account " account))
  285. (and start (concat "\n start " start))
  286. (and complete (concat "\n complete " complete))
  287. (and note (concat "\n note " note))
  288. (and priority (concat "\n priority " priority))
  289. "\n"))))
  290. (defun org-taskjuggler-close-maybe (level)
  291. (while (> old-level level)
  292. (insert "}\n")
  293. (setq old-level (1- old-level)))
  294. (when (= old-level level)
  295. (insert "}\n")))
  296. (defun org-taskjuggler-insert-reports ()
  297. (let (report)
  298. (dolist (report org-export-taskjuggler-default-reports)
  299. (insert report "\n"))))
  300. (provide 'org-taskjuggler)
  301. ;; arch-tag: a24a127c-d365-4c2a-9e9b-f7dcb0ebfdc3
  302. ;;; org-taskjuggler.el ends here