Creating uml images by using plantuml and org-babel in emacs

I came across Ian Yang’s org-export-blocks-format-plantuml a few days ago, which brought me in front of the great software Plantuml. It is an open-source tool in java that allows to quickly write:

  • sequence diagram,
  • use case diagram,
  • class diagram,
  • activity diagram,
  • component diagram,
  • state diagram
  • object diagram

using a simple and intuitive language.

Although Ian Yang’s code has incorporated plantuml in org-mode by #+BEGIN_UML and #+END_UML, I think it should be a good idea to introduce the plantuml as a new language into org-babel.

Here is the “ob-plantuml.el”:

;;; ob-plantuml.el --- org-babel functions for plantuml evaluation

;; Author: Zhang Weize
;; Homepage:

;;; Commentary:

;; Org-Babel support for evaluating plantuml.

;; Some code in ob-plantuml was adopted from
;; Ian Yang's org-export-blocks-format-plantuml

;;; Code:
(require 'ob)

(defvar org-babel-default-header-args:plantuml
  '((:results . "file") (:exports . "results"))
  "Default arguments for evaluating a plantuml source block.")

(defun org-babel-expand-body:plantuml (body params &optional processed-params)
  "Expand BODY according to PARAMS, return the expanded body." body)

(defvar org-plantuml-jar-path)
(defun org-babel-execute:plantuml (body params)
  "Execute a block of plantuml code with org-babel.
This function is called by `org-babel-execute-src-block'."
  (let ((result-params (split-string (or (cdr (assoc :results params)) "")))
        (out-file (cdr (assoc :file params)))
        (cmdline (cdr (assoc :cmdline params)))
        (in-file (make-temp-file "org-babel-plantuml")))
    (unless (file-exists-p org-plantuml-jar-path)
      (error "Could not find plantuml.jar at %s" org-plantuml-jar-path))
    (with-temp-file in-file (insert (concat "@startuml\n" body "\n@enduml")))
    (message (concat "java -jar " org-plantuml-jar-path " -p " cmdline " " in-file))
       (concat "java -jar " org-plantuml-jar-path " -p " cmdline)
       '(t nil))
      (write-region nil nil out-file))

(defun org-babel-prep-session:plantuml (session params)
  "Return an error because plantuml does not support sessions."
  (error "Plantuml does not support sessions"))

(provide 'ob-plantuml)

;;; ob-plantuml.el ends here

And a new major mode “plantuml-mode.el”, which provides preliminary font-lock function for plantuml scripts:

;; plantuml-mode.el -- Major mode for plantuml

;; Author: Zhang Weize (zwz)
;; Keywords: uml ascii

;; You can redistribute this program and/or modify it under the terms
;; of the GNU General Public License as published by the Free Software
;; Foundation; either version 2, or (at your option) any later
;; version.


;; A major mode for plantuml, see:
;; Plantuml is an open-source tool in java that allows to quickly write :
;;     - sequence diagram,
;;     - use case diagram,
;;     - class diagram,
;;     - activity diagram,
;;     - component diagram,
;;     - state diagram
;;     - object diagram
;; using a simple and intuitive language.


;; version 0.1, 2010-08-25 First version

(require 'thingatpt)

(defgroup plantuml-mode nil
  "Major mode for editing plantuml file."
  :group 'languages)

(defvar plantuml-mode-hook nil "Standard hook for plantuml-mode.")

(defvar plantuml-mode-version nil "plantuml-mode version string.")

(defvar plantuml-mode-map nil "Keymap for plantuml-mode")

;;; syntax table
(defvar plantuml-mode-syntax-table
  (let ((synTable (make-syntax-table)))
    (modify-syntax-entry ?' "< b" synTable)
    (modify-syntax-entry ?\n "> b" synTable)
    (modify-syntax-entry ?! "w" synTable)
    (modify-syntax-entry ?@ "w" synTable)
    (modify-syntax-entry ?# "'" synTable)
  "Syntax table for `plantuml-mode'.")

;;; font-lock
(defvar plantuml-types '("participant" "actor" "usecase" "abstract" "abstract class" "interface" "enum" "package" "partition" "component" "state" "object" "title" "note" "end note" "end title" "end header" "end footer"
                         ;;"note left" "note right" "note top" "note bottom" "note left of" "note right of" "note top of" "note bottom of" "note over" "left header" "center header" "right header" "left footer" "center footer" "right footer"
(defvar plantuml-keywords '("\@startuml" "\@enduml" "as" "autonumber" "newpage" "alt" "else" "opt" "loop" "par" "break" "critical" "end" "create" "footbox off" "skin" "skinparam" "if" "then" "else" "endif" "rotate" "activate" "deactivate" "destroy"))

(defvar plantuml-preprocessors '("!include" "!define" "!undef" "!ifdef" "!endif" "!ifndef"))

(defvar plantuml-builtins '("backgroundColor"
                            "AliceBlue" "GhostWhite" "NavajoWhite"
                            "AntiqueWhite" "GoldenRod" "Navy"
                            "Aquamarine" "Gold" "OldLace"
                            "Aqua" "Gray" "OliveDrab"
                            "Azure" "GreenYellow" "Olive"
                            "Beige" "Green" "OrangeRed"
                            "Bisque" "HoneyDew" "Orange"
                            "Black" "HotPink" "Orchid"
                            "BlanchedAlmond" "IndianRed" "PaleGoldenRod"
                            "BlueViolet" "Indigo" "PaleGreen"
                            "Blue" "Ivory" "PaleTurquoise"
                            "Brown" "Khaki" "PaleVioletRed"
                            "BurlyWood" "LavenderBlush" "PapayaWhip"
                            "CadetBlue" "Lavender" "PeachPuff"
                            "Chartreuse" "LawnGreen" "Peru"
                            "Chocolate" "LemonChiffon" "Pink"
                            "Coral" "LightBlue" "Plum"
                            "CornflowerBlue" "LightCoral" "PowderBlue"
                            "Cornsilk" "LightCyan" "Purple"
                            "Crimson" "LightGoldenRodYellow" "Red"
                            "Cyan" "LightGreen" "RosyBrown"
                            "DarkBlue" "LightGrey" "RoyalBlue"
                            "DarkCyan" "LightPink" "SaddleBrown"
                            "DarkGoldenRod" "LightSalmon" "Salmon"
                            "DarkGray" "LightSeaGreen" "SandyBrown"
                            "DarkGreen" "LightSkyBlue" "SeaGreen"
                            "DarkKhaki" "LightSlateGray" "SeaShell"
                            "DarkMagenta" "LightSteelBlue" "Sienna"
                            "DarkOliveGreen" "LightYellow" "Silver"
                            "DarkOrchid" "LimeGreen" "SkyBlue"
                            "DarkRed" "Lime" "SlateBlue"
                            "DarkSalmon" "Linen" "SlateGray"
                            "DarkSeaGreen" "Magenta" "Snow"
                            "DarkSlateBlue" "Maroon" "SpringGreen"
                            "DarkSlateGray" "MediumAquaMarine" "SteelBlue"
                            "DarkTurquoise" "MediumBlue" "Tan"
                            "DarkViolet" "MediumOrchid" "Teal"
                            "Darkorange" "MediumPurple" "Thistle"
                            "DeepPink" "MediumSeaGreen" "Tomato"
                            "DeepSkyBlue" "MediumSlateBlue" "Turquoise"
                            "DimGray" "MediumSpringGreen" "Violet"
                            "DodgerBlue" "MediumTurquoise" "Wheat"
                            "FireBrick" "MediumVioletRed" "WhiteSmoke"
                            "FloralWhite" "MidnightBlue" "White"
                            "ForestGreen" "MintCream" "YellowGreen"
                            "Fuchsia" "MistyRose" "Yellow"
                            "Gainsboro" "Moccasin"
(defvar plantuml-types-regexp (concat "^\\s *\\(" (regexp-opt plantuml-types 'words) "\\|\\<\\(note\\s +over\\|note\\s +\\(left\\|right\\|bottom\\|top\\)\\s +\\(of\\)?\\)\\>\\|\\<\\(\\(left\\|center\\|right\\)\\s +\\(header\\|footer\\)\\)\\>\\)"))
(defvar plantuml-keywords-regexp (concat "^\\s *" (regexp-opt plantuml-keywords 'words)  "\\|\\(<\\|<|\\|\\*\\|o\\)\\(\\.+\\|-+\\)\\|\\(\\.+\\|-+\\)\\(>\\||>\\|\\*\\|o\\)\\|\\.\\{2,\\}\\|-\\{2,\\}"))
(defvar plantuml-builtins-regexp (regexp-opt plantuml-builtins 'words))
(defvar plantuml-preprocessors-regexp (concat "^\\s *" (regexp-opt plantuml-preprocessors 'words)))

(setq plantuml-font-lock-keywords
        (,plantuml-types-regexp . font-lock-type-face)
        (,plantuml-keywords-regexp . font-lock-keyword-face)
        (,plantuml-builtins-regexp . font-lock-builtin-face)
        (,plantuml-preprocessors-regexp . font-lock-preprocessor-face)
        ;; note: order matters

;; keyword completion
(defvar plantuml-kwdList nil "plantuml keywords.")

(setq plantuml-kwdList (make-hash-table :test 'equal))
(mapc (lambda (x) (puthash x t plantuml-kwdList)) plantuml-types)
(mapc (lambda (x) (puthash x t plantuml-kwdList)) plantuml-keywords)
(mapc (lambda (x) (puthash x t plantuml-kwdList)) plantuml-builtins)
(mapc (lambda (x) (puthash x t plantuml-kwdList)) plantuml-preprocessors)
(put 'plantuml-kwdList 'risky-local-variable t)

(defun plantuml-complete-symbol ()
  "Perform keyword completion on word before cursor."
  (let ((posEnd (point))
        (meat (thing-at-point 'symbol))

    (when (not meat) (setq meat ""))

    (setq maxMatchResult (try-completion meat plantuml-kwdList))
    (cond ((eq maxMatchResult t))
          ((null maxMatchResult)
           (message "Can't find completion for \"%s\"" meat)
          ((not (string= meat maxMatchResult))
           (delete-region (- posEnd (length meat)) posEnd)
           (insert maxMatchResult))
          (t (message "Making completion list...")
             (with-output-to-temp-buffer "*Completions*"
                (all-completions meat plantuml-kwdList)
             (message "Making completion list...%s" "done")))))

;; clear memory
(setq plantuml-types nil)
(setq plantuml-keywords nil)
(setq plantuml-builtins nil)
(setq plantuml-preprocessors nil)
(setq plantuml-types-regexp nil)
(setq plantuml-keywords-regexp nil)
(setq plantuml-builtins-regexp nil)
(setq plantuml-preprocessors-regexp nil)

(add-to-list 'auto-mode-alist '("\\.plu$" . plantuml-mode))

(defun plantuml-mode ()
  "Major mode for plantuml.

Shortcuts             Command Name
\\[plantuml-complete-symbol]      `plantuml-complete-symbol'"


;;  (python-mode) ; for indentation
  (setq major-mode 'plantuml-mode
        mode-name "plantuml")
  (set-syntax-table plantuml-mode-syntax-table)
  (use-local-map plantuml-mode-map)

  (make-local-variable 'font-lock-defaults)
  (setq font-lock-defaults '((plantuml-font-lock-keywords) nil t))

  (run-mode-hooks 'plantuml-mode-hook))

(provide 'plantuml-mode)
This entry was posted in emacs and tagged , , . Bookmark the permalink.

2 Responses to Creating uml images by using plantuml and org-babel in emacs

  1. Oh really, excellent piece of writing! Many thanks for sharing.

    I seriously liked this and definitely will relate it with my associates and families.

  2. Katrina says:

    Hello webmaster do you need unlimited content for your site ?
    What if you could copy article from other websites, make it unique and publish on your website – i know the
    right tool for you, just search in google:
    kisamtai’s article tool

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s