Skip to content

Commit

Permalink
Merge pull request #7 from MikhailKravets/release
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
MikhailKravets authored Sep 17, 2022
2 parents 65c8bd8 + 230dd6b commit 0cbd138
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 262 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master", "develop" ]
branches: [ "master", "release", "develop" ]

jobs:
test:
Expand Down
204 changes: 9 additions & 195 deletions Pipfile.lock

Large diffs are not rendered by default.

73 changes: 30 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# MkDocs. PlantUML
# mkdocs-puml

[![PyPI version](https://badge.fury.io/py/mkdocs_puml.svg)](https://badge.fury.io/py/mkdocs_puml)

`mkdocs_puml` package brings `puml` diagrams into `mkdocs` documentation.
It converts your inline puml diagrams into `svg` images.
This package has a markdown extension to be used in `mkdocs.yml`.
`mkdocs_puml` is a fast and simple package that brings PUML diagrams into `mkdocs`
documentation.

## Install

Expand All @@ -23,9 +22,16 @@ To use puml with mkdocs, just add `mkdocs_puml.extensions` into
markdown_extensions:
- mkdocs_puml.extensions:
puml_url: https://www.plantuml.com/plantuml/
num_workers: 5
```
Where `puml_url` is URL to the `PlantUML` service.
Where
* `puml_url` is URL to the plantuml service.
* `num_workers` is max amount of concurrent workers that requests plantuml service.
This variable should take the value of average amount of diagrams on a single page.
This setting is set to 5 by default.
**NOTE** that the extension processes each markdown page simultaneously;
concurrent workers improves performance if you have more than 1 diagman per page.

Now, you can put your puml diagrams into your `.md` documentation. For example,

Expand All @@ -40,15 +46,14 @@ Bob -> Alice : hello
</pre>

At the build phase `mkdocs` will send request to `puml_url` and substitute your
diagram with the inline `svg` image.
diagram with the `svg` image from response.

### Connect PlantUML service with Docker
### Run PlantUML service with Docker

It is possible to run [plantuml/plantuml-server](https://hub.docker.com/r/plantuml/plantuml-server)
in Docker.
as a Docker container.

Either follow the instructions of the plantuml docker page or add a new service
to the `docker-compose.yml` file
Add a new service to the `docker-compose.yml` file

```yaml
version: "3"
Expand All @@ -59,7 +64,7 @@ services:
- '8080:8080'
```

Then write the following instructions in your `mkdocs.yml` file
Then substitute `puml_url` setting with the local's one in the `mkdocs.yml` file

```yaml
markdown_extensions:
Expand All @@ -70,24 +75,30 @@ markdown_extensions:
Obviously, this approach works faster than
using [plantuml.com](https://www.plantuml.com/plantuml/).

### Use PlantUML converter directly
### Standalone usage

If you wish, you can use `PlantUML` converter on your own without `mkdocs`.
The example below shows how to do that.
You can use `PlantUML` converter on your own without `mkdocs`.
The example below shows it.

```python
from mkdocs_puml.puml import PlantUML
puml_url = "https://www.plantuml.com/plantuml"
diagram = """
diagram1 = """
@startuml
Bob -> Alice : hello
@enduml
"""
puml = PlantUML(puml_url)
svg = puml.translate(diagram)
diagram2 = """
@startuml
Jon -> Sansa : hello
@enduml
"""
puml = PlantUML(puml_url, num_worker=2)
svg_for_diag1, svg_for_diag2 = puml.translate([diagram1, diagram2])
```

## How it works
Expand All @@ -109,32 +120,8 @@ code blocks.
**NOTE** you must set `puml` keyword as an indicator that the plant uml is located in the block.

After the page is parsed, `mkdocs_puml` extension encodes diagrams and
sends requests to `PlantUML` then substitutes `puml` diagram with the received `svg`
in the final `html` file.

Schematically, the overall process looks like

```mermaid
sequenceDiagram
participant mkdocs as MkDocs
participant mkdocs_puml as mkdocs_puml extension
participant puml as PlantUML Service
mkdocs ->>+ mkdocs_puml: Pass markdown to mkdocs_puml extension
mkdocs_puml ->> mkdocs_puml: Select all ```puml ``` blocks

loop For each puml diagram
mkdocs_puml ->> mkdocs_puml: Encode diagram
mkdocs_puml ->>+ puml: GET SVG diagram
puml ->>- mkdocs_puml: SVG returned
mkdocs_puml ->> mkdocs_puml: Save SVG
end

mkdocs_puml ->> mkdocs_puml: Substitute puml diagrams with corresponding SVG
mkdocs_puml ->>- mkdocs: Return updated md string
```
sends requests to `PlantUML`. After responses are received, the package
substitutes `puml` diagrams with the `svg` images.

## License

Expand Down
2 changes: 1 addition & 1 deletion mkdocs_puml/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Package that brings PlantUML into MkDocs"""

__version__ = "0.1"
__version__ = "0.2"
15 changes: 6 additions & 9 deletions mkdocs_puml/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

class PumlExtension(Extension):
"""PUML Extension for Python-Markdown"""
def __init__(self, puml_url):
self.puml = PlantUML(puml_url)
def __init__(self, puml_url, num_worker=5):
self.puml = PlantUML(puml_url, num_worker)
super().__init__()

def extendMarkdown(self, md: Markdown):
Expand Down Expand Up @@ -43,13 +43,10 @@ def __init__(self, md, puml):

def run(self, lines):
text = '\n'.join(lines)
schemes = {}
for scheme in self.regex.findall(text):
converted = self.puml.translate(scheme)
schemes[f"```{self.name}{scheme}```"] = converted

for k, v in schemes.items():
text = text.replace(k, f'<div class="{self.name}">{v}</div>')
schemes = self.regex.findall(text)
converted = self.puml.translate(schemes)
for scheme, svg in zip(schemes, converted):
text = text.replace(f"```{self.name}{scheme}```", f'<div class="{self.name}">{svg}</div>')

return text.split('\n')

Expand Down
64 changes: 57 additions & 7 deletions mkdocs_puml/puml.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import re
import typing
from concurrent.futures import ThreadPoolExecutor, as_completed
from urllib.parse import urljoin
from xml.dom.minidom import Element, parseString # nosec

Expand All @@ -14,6 +16,7 @@ class PlantUML:
Attributes:
base_url (str): Base URL to the PUML service
num_worker (int): The size of pool to run requests in
_format (str): The format of build diagram. Used in the requesting URL
_html_comment_regex (re.Pattern): Regex pattern to remove html comments from received svg
Expand All @@ -26,10 +29,14 @@ class PlantUML:
_format = 'svg'
_html_comment_regex = re.compile(r"<!--.*?-->", flags=re.DOTALL)

def __init__(self, base_url: str):
def __init__(self, base_url: str, num_worker: int = 5):
self.base_url = base_url if base_url.endswith('/') else f"{base_url}/"

def translate(self, content: str) -> str:
if num_worker <= 0:
raise ValueError("`num_worker` argument should be bigger than 0.")
self.num_worker = num_worker

def translate(self, diagrams: typing.List[str]) -> typing.List[str]:
"""Translate string diagram into HTML div
block containing the received SVG image.
Expand All @@ -38,20 +45,63 @@ def translate(self, content: str) -> str:
into <svg> image of the diagram
Args:
content (str): string representation of PUML diagram
diagrams (str): string representation of PUML diagram
Returns:
SVG image of built diagram
"""
encoded = encode(content)
resp = requests.get(urljoin(self.base_url, f"{self._format}/{encoded}"))
diagram_content = resp.content.decode('utf-8')
diagram_content = self._clean_comments(diagram_content)
encoded = [self.preprocess(v) for v in diagrams]

with ThreadPoolExecutor(max_workers=self.num_worker) as executor:
futures = [executor.submit(self.request, v) for v in encoded]
svg_images = [v.result() for v in as_completed(futures)]

return [self.postprocess(v) for v in svg_images]

def preprocess(self, content: str) -> str:
"""Preprocess the content before pass it
to the plantuml service.
Encoding of the content should be
done in the step of preprocessing.
Args:
content (str): string representation PUML diagram
Returns:
Preprocessed PUML diagram
"""
return encode(content)

def postprocess(self, content: str) -> str:
"""Postprocess the received from plantuml service
SVG diagram.
Potentially, here could be the code
that applies CSS styling to the SVG.
Args:
content (str): SVG representation of build diagram
Returns:
Postprocessed SVG diagram
"""
diagram_content = self._clean_comments(content)

svg = self._convert_to_dom(diagram_content)
self._stylize_svg(svg)

return svg.toxml()

def request(self, encoded_diagram: str) -> str:
"""Request plantuml service with the encoded diagram;
return SVG content
Args:
encoded_diagram (str): Encoded string representation of the diagram
Returns:
SVG representation of the diagram
"""
resp = requests.get(urljoin(self.base_url, f"{self._format}/{encoded_diagram}"))
return resp.content.decode('utf-8')

def _clean_comments(self, content: str) -> str:
return self._html_comment_regex.sub("", content)

Expand Down
4 changes: 3 additions & 1 deletion tests/test_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@

def test_make_extension():
url = BASE_PUML_URL
num_worker = 10

ext = makeExtension(puml_url=url)
ext = makeExtension(puml_url=url, num_worker=num_worker)

assert isinstance(ext, PumlExtension)
assert ext.puml.base_url == url
assert ext.puml.num_worker == num_worker


def test_puml_extension():
Expand Down
23 changes: 18 additions & 5 deletions tests/test_puml.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest

from mkdocs_puml.puml import PlantUML
from tests.conftest import BASE_PUML_URL

Expand All @@ -12,13 +14,24 @@ def test_url_without_slash():
assert puml.base_url.endswith('/')


def test_num_worker_less_or_equal_zero():
with pytest.raises(ValueError):
PlantUML(BASE_PUML_URL, num_worker=0)


def test_translate(diagram_and_encoded: (str, str), mock_requests):
_, encoded = diagram_and_encoded
diagram, encoded = diagram_and_encoded

diagrams = [diagram] * 2

puml = PlantUML(BASE_PUML_URL)
resp = puml.translate(encoded)
resp = puml.translate(diagrams)

assert puml.base_url.endswith('/')
assert resp.startswith("<svg")
assert not puml._html_comment_regex.search(resp)
assert 'preserveAspectRatio="true"' in resp

assert len(resp) == 2

for r in resp:
assert r.startswith("<svg")
assert not puml._html_comment_regex.search(r)
assert 'preserveAspectRatio="true"' in r

0 comments on commit 0cbd138

Please sign in to comment.