Commit cbda8b1c authored by Vincent Texier's avatar Vincent Texier
Browse files

Add support for plantuml/graphviz diagrams via the plantuml plugin

Readme updated with documentation about the plugin
Add an architecture page in the wiki to show an example
parent 83776a78
......@@ -9,7 +9,7 @@ You may want to reproduce this website locally, for developement purposes for ex
Clone the sources
git clone
Install python stuff
cd website
......@@ -17,6 +17,11 @@ Install python stuff
source bin/activate
pip install pelican pelican-youtube markdown beautifulsoup4
Install system dependencies for plantuml plugin (plantuml and GraphViz utilities) :
* Install plantuml : use your package manager or
* Install GraphViz : use your package manager or
Generate the site
......@@ -38,3 +43,9 @@ You may want to change the production parameters, like the domain name: just edi
For example if you want to host the site at ``, set:
## Plantuml plugin documentation
* Plantuml plugin documentation :
* Plantuml documentation:
* Plantuml support DOT language of GraphViz:
......@@ -17,3 +17,7 @@ Page concernant spécifiquement le logiciel Duniter.
* [Duniter CLI](./commands)
* [Development tutorial (in french)](
## Concepts
* [Architecture](./architecture)
Title: Architecture
Order: 10
Date: 2017-10-09
Slug: architecture
Authors: vit
## Clients network architecture
Here is a glimpse of the architecture of Duniter between one server and clients.
::uml:: format="svg" alt="clients architecture"
title Clients network architecture
node "Duniter" {
interface "HTTP" as HTTP_duniter
HTTP_duniter - [Basic Merkle Api]
database "Elastic Search" as ES {
interface "HTTP" as HTTP_ES
frame "Duniter4j Plugin" {
[Sync] -- HTTP_duniter
[Members Profiles Index]
[Companies Registry Index]
[Market Place Index]
package "Duniter Desktop" {
interface "client BMA" as client_duniter_desktop
client_duniter_desktop --- HTTP_duniter
package "Remuniter" {
interface "client BMA" as client_remuniter
client_remuniter --- HTTP_duniter
package "Cesium" {
interface "client BMA" as client_bma
frame "ES Data Store Plugin" {
interface "client ES" as client_es
[Members Profiles]
[Companies Registry]
[Market Place]
client_es --- HTTP_ES
client_bma -- HTTP_duniter
package "Duniter Android App" {
frame "Duniter4j API" {
interface "client BMA" as client_android_bma
client_android_bma --- HTTP_duniter
package "Sakia" {
frame "Duniter Python API" {
interface "client BMA" as client_sakia
client_sakia --- HTTP_duniter
PlantUML plugin for Pelican rst and md documents
This plugin allows you to define UML diagrams directly into rst or md documents using the great PlantUML_ tool.
It gets the content of ``uml`` directive, passes it to the external program PlantUML_ and then links the generated image to the document.
.. contents::
You need to install PlantUML_ (see the site for details) and Graphviz_ 2.26.3 or later. The plugin expects a program ``plantuml`` in the classpath. If not installed by your package manager, you can create a shell script and place it somewhere in the classpath. For example, save te following into ``/usr/local/bin/plantuml`` (supposing PlantUML_ installed into ``/opt/plantuml``):
.. code-block:: bash
java -jar /opt/plantuml/plantuml.jar ${@}
For Gentoo_ there is an ebuild at you can download the ebuild and the ``files`` subfolder or you can add the ``zugaina`` repository with _layman (reccomended).
Add ``plantuml`` to plugin list in ````. For example:
.. code-block:: ptyhon
PLUGINS = [ "sitemap", "plantuml" ]
One loaded the plugin register also the Pyhton-Markdown_ extension.
RST usage
Use the ``uml`` directive to start UML diagram description. It is not necessary to enclose diagram body between ``@startuml`` and ``@enduml`` directives: they are added automatically before calling ``plantuml``.
In addition to ``class`` and ``alt`` options common to all images, you can use the ``format`` option to select what kind of image must be produced. At the moment only ``png`` and ``svg`` are supported; the default is ``png``.
Please note that the ``format`` option in not recognized by the ``plantuml`` extension of ``rst2pdf`` utility (call it with ``-e``) so if you use it you can get errors from that program.
MD usage
For use with the Pyhton-Markdown_ syntax, the UML block must be enclose with ``::uml::``:
.. code-block:: markdown
::uml:: [format=...] [classes=...] [alt=...]
PlantUML script
Please keep a blank line before ``::uml::`` and after ``::end-uml::`` to be sure that the UML code will be correctly recognized. See Examples_ for more details.
With MD syntax options must be specified in the same line as the opening ``:uml::``, with the order ``format``, ``classes`` anmd ``alt``. The general syntax for option is
.. code-block:: text
Option can be enclosed with single or double quotes, as you like. Options defaults are the same as for the rst plugin.
For pandoc_reader plugin users
The plugin ``pandoc_reader`` sends the Markdown body to the pandoc_ tool which has it's own Markdown parser, written in Haskell_ language: Python_ plugins manipulating Markdown posts (such this) are not used because the entire body id passed to pandoc_ without any iteraction by Pelican_.
For those who are using the ``pandoc_reader`` plugin and wants to include PlantUML_ diagrams, use the ``pandoc-plantuml`` script (only *nix, sorry): it is a wrapper for filtering the code blocks parsed by pandoc_ before
writing out the converted file. It is an adaption of the great work by Kurt Bonne for his `pandoc-plantuml-filter <>`_.
To use it, copy the ``pandoc-plantuml`` file in a subdirectory of your pelican project (for example `pandoc_extensions`) and make sure it is executable (``chmod +x pandoc-plantuml``).
In the ```` configure the needed plugins:
.. code-block:: python
PLUGINS = ['pandoc_reader'] // Yes, plantuml plugin non necessary
PANDOC_ARGS = ['--filter=pandoc_extensions/pandoc-plantuml']
In Markdown posts use the following syntax to include PlantUML_ diagrams:
.. code-block:: markdown
Alice -> Bob: Authentication Request
Bob --> Alice: Authentication Response
Alice -> Bob: Another authentication Request
Alice <-- Bob: another authentication Response
Rendered images will bu put in the output/images folder.
**NOTE:** ``pandoc-plantuml`` is broken from pandoc 1.16 cause an API change in pandoc ``Image`` function. I'm working on a fix but in the meanwhile use a version of pandoc prior to ``1.16`` .
The plugin can produce debugging informations to help to locate errors. To enable debugging execute ``pelican`` in debug mode:
.. code-block:: console
make DEBUG=1 html
Sequence diagram (from PlantUML_ site):
.. code-block:: rst
.. uml::
:alt: Sample sequence diagram
participant User
User -> A: DoWork
activate A #FFBBBB
A -> A: Internal call
activate A #DarkSalmon
A -> B: << createRequest >>
activate B
B --> A: RequestCreated
deactivate B
deactivate A
A -> User: Done
deactivate A
.. image::
:alt: Sample sequence diagram
Same diagram with Pyhton-Markdown_ syntax:
.. code-block:: markdown
::uml:: format="png" alt="Sample sequence diagram"
participant User
User -> A: DoWork
activate A #FFBBBB
A -> A: Internal call
activate A #DarkSalmon
A -> B: << createRequest >>
activate B
B --> A: RequestCreated
deactivate B
deactivate A
A -> User: Done
deactivate A
Another example from PlantUML_ site (activity diagram):
.. code-block:: rst
.. uml::
:new page;
if (Page.onSecurityCheck) then (true)
if (isForward?) then (no)
:Process controls;
if (continue processing?) then (no)
if (isPost?) then (yes)
else (no)
else (false)
if (do redirect?) then (yes)
:redirect process;
if (do forward?) then (yes)
:Forward request;
else (no)
:Render page template;
Generated image:
.. image::
:alt: Sample activity diagram
.. _PlantUML:
.. _Sabayon:
.. _Gentoo:
.. _layman:
.. _Graphviz:
.. _Pyhton-Markdown:
.. _pandoc:
.. _Haskell:
.. _Python::
.. _Pelican:
from .plantuml_rst import *
#!/usr/bin/env python
import logging
import os
import tempfile
from zlib import adler32
from subprocess import Popen, PIPE
from pelican import logger
def generate_uml_image(path, plantuml_code, imgformat):
tf = tempfile.NamedTemporaryFile(delete=False)
logger.debug("[plantuml] Temporary PlantUML source at "+(
if imgformat == 'png':
imgext = ".png"
outopt = "-tpng"
elif imgformat == 'svg':
imgext = ".svg"
outopt = "-tsvg"
logger.error("Bad uml image format '"+imgformat+"', using png")
imgext = ".png"
outopt = "-tpng"
# make a name
name =
# build cmd line
cmdline = ['plantuml', '-o', path, outopt,]
logger.debug("[plantuml] About to execute "+" ".join(cmdline))
p = Popen(cmdline, stdout=PIPE, stderr=PIPE)
out, err = p.communicate()
except Exception as exc:
raise Exception('Failed to run plantuml: %s' % exc)
if p.returncode == 0:
# diagram was correctly generated, we can remove the temporary file (if not debugging)
if not logger.isEnabledFor(logging.DEBUG):
# renaming output image using an hash code, just to not pollute
# output directory with a growing number of images
name = os.path.join(path, os.path.basename(name))
newname = os.path.join(path, "%08x" % (adler32(plantuml_code.encode()) & 0xffffffff))+imgext
if os.path.exists(newname):
os.rename(name, newname)
return 'images/' + os.path.basename(newname)
# the temporary file is still available as aid understanding errors
raise RuntimeError('Error calling plantuml: %s' % err)
Script adapted from pandoc-plantuml-filter by Kurt Bonne
Original source at
This script is meant to be run by pandoc executed by the pandoc_reader pelican plugin.
I've changed output paths to be compatibile with pelican output structure.
If using the pandoc_reader pelican plugin with this script, the plantuml plugin is not necessary.
This script requires Haskell, but if you are using pandoc, it's already installed :-)
Copy this file in your pelican project, in the same directory of, and make sure it is executable.
In the configure the need plugins:
PLUGINS = ['pandoc_reader']
PANDOC_ARGS = ['--filter=pandoc-plantuml']
If this script will be putted in a different location, adapt the PANDOC_ARGS value.
In Markdown posts use the following syntax to include PlantUML diagrams:
Alice -> Bob: Authentication Request
Bob --> Alice: Authentication Response
Alice -> Bob: Another authentication Request
Alice <-- Bob: another authentication Response
Rendered images will bu put in the output/images folder.
import Text.Pandoc.JSON
import Data.ByteString.Lazy (hGetContents, hPut)
import Data.ByteString.Lazy.UTF8 (fromString)
import Data.Digest.Pure.SHA (sha1, showDigest)
import System.IO (hClose, hPutStr, IOMode(..), openBinaryFile, hPutStrLn, stderr)
import System.Process
import System.Directory
processBlocks :: Block -> IO Block
processBlocks b =
case b of
CodeBlock (_ , ["plantuml"], _) content -> plantUMLToImg content
_ -> return b
plantUMLToImg :: String -> IO Block
plantUMLToImg content = do
path <- renderImage content
--hPutStrLn stderr "dopo renderImage"
return $ Para [Image [] (path, "")]
renderImage :: String -> IO String
renderImage content = do
createDirectoryIfMissing (True) "output/images"
let path = uniqueName content ++ ".png"
(Just hIn, Just hOut, _, _) <-
createProcess (proc "plantuml" ["-pipe", "-tepg"]){ std_in = CreatePipe,
std_out = CreatePipe }
hPutStr hIn content
hClose hIn
let outPath = "output/images/" ++ path
hFile <- openBinaryFile outPath WriteMode
img <- hGetContents hOut
hPut hFile img
hClose hFile
hClose hOut
let imgPath = "images/" ++ path
return imgPath
uniqueName :: String -> String
uniqueName = showDigest . sha1 . fromString
main :: IO ()
main = toJSONFilter processBlocks
#!/usr/bin/env python
PlantUML_ Extension for Python-Markdown_
::uml:: [format="png|svg"] [classes="class1 class2 ..."] [alt="text for alt"]
PlantUML script diagram
::uml:: format="png" classes="uml myDiagram" alt="My super diagram"
Goofy -> MickeyMouse: calls
Goofy <-- MickeyMouse: responds
Options are optional, but if present must be specified in the order format, classes, alt.
The option value may be enclosed in single or double quotes.
.. _Python-Markdown:
.. _PlantUML:
import os
import re
import markdown
from markdown.util import etree
from .generateUmlDiagram import generate_uml_image
# For details see
class PlantUMLBlockProcessor(markdown.blockprocessors.BlockProcessor):
# Regular expression inspired by the codehilite Markdown plugin
RE = re.compile(r'''::uml::
''', re.VERBOSE)
# Regular expression for identify end of UML script
RE_END = re.compile(r'::end-uml::\s*$')
def test(self, parent, block):
def run(self, parent, blocks):
block = blocks.pop(0)
text = block
# Parse configuration params
m =
format ='format') if'format') else self.config['format']
classes ='classes') if'classes') else self.config['classes']
alt ='alt') if'alt') else self.config['alt']
# Read blocks until end marker found
while blocks and not
block = blocks.pop(0)
text = text + '\n' + block
if not blocks:
raise RuntimeError("[plantuml] UML block not closed, text is:\n"+text)
# Remove block header and footer
text = re.sub(self.RE, "", re.sub(self.RE_END, "", text))
path = os.path.abspath(os.path.join('output', 'images'))
if not os.path.exists(path):
# Generate image from PlantUML script
imageurl = self.config['siteurl']+'/'+generate_uml_image(path, text, format)
# Create image tag and append to the document
etree.SubElement(parent, "img", src=imageurl, alt=alt, attrib={'class':classes})
# For details see
class PlantUMLMarkdownExtension(markdown.Extension):
# For details see
def __init__(self, *args, **kwargs):
self.config = {
'classes': ["uml","Space separated list of classes for the generated image. Default uml."],
'alt' : ["uml diagram", "Text to show when image is not available."],
'format' : ["png", "Format of image to generate (png or svg). Default png."],
'siteurl': ["", "URL of document, used as a prefix for the image diagram."]
super(PlantUMLMarkdownExtension, self).__init__(*args, **kwargs)
def extendMarkdown(self, md, md_globals):
blockprocessor = PlantUMLBlockProcessor(md.parser)
blockprocessor.config = self.getConfigs()
md.parser.blockprocessors.add('plantuml', blockprocessor, '>code')
def makeExtension(**kwargs):
return PlantUMLMarkdownExtension(**kwargs)
#!/usr/bin/env python
"""Custom reST_ directive for plantuml_ integration.
Adapted from ditaa_rst plugin.
.. _reST:
.. _plantuml:
import sys
import os
from docutils.nodes import image, literal_block
from docutils.parsers.rst import Directive, directives
from pelican import signals, logger
from .generateUmlDiagram import generate_uml_image
global_siteurl = "" # URL of the site, filled on plugin initialization
class PlantUML_rst(Directive):
""" reST directive for PlantUML """
required_arguments = 0
optional_arguments = 0
has_content = True