Skip to content

Commit

Permalink
Add plantUML format
Browse files Browse the repository at this point in the history
  • Loading branch information
tefra committed Jan 8, 2020
1 parent eda4df4 commit 4690795
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 30 deletions.
Empty file.
42 changes: 42 additions & 0 deletions tests/formats/plantuml/test_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from pathlib import Path

from tests.factories import AttrFactory, ClassFactory, FactoryTestCase
from xsdata.formats.plantuml.generator import PlantUmlGenerator
from xsdata.models.elements import Schema


class PlantUmlGeneratorTests(FactoryTestCase):
def test_render(self):
schema = Schema.create(location=Path("foo.xsd"))
package = "some.Foo.Some.ThugLife"
classes = [
ClassFactory.create(attrs=AttrFactory.list(2)),
ClassFactory.create(attrs=AttrFactory.list(3)),
]

iterator = PlantUmlGenerator().render(schema, classes, package)

actual = [(file, output) for file, output in iterator]
self.assertEqual(1, len(actual))
self.assertEqual(2, len(actual[0]))
self.assertIsInstance(actual[0][0], Path)
self.assertTrue(actual[0][0].is_absolute())
self.assertEqual(
"some/Foo/Some/ThugLife/foo.pu",
str(actual[0][0].relative_to(Path.cwd())),
)

output = """@startuml
class class_B {
+attr_B : xs:string
+attr_C : xs:string
}
class class_C {
+attr_D : xs:string
+attr_E : xs:string
+attr_F : xs:string
}
@enduml"""
self.assertEqual(output, actual[0][1])
2 changes: 1 addition & 1 deletion tests/test_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def tearDown(self) -> None:
writer.generators = self.generators

def test_formats(self):
expected = ["pydata"]
expected = ["pydata", "plantuml"]
self.assertEqual(expected, writer.formats)
self.assertIsInstance(
writer.get_renderer("pydata"), DataclassGenerator
Expand Down
12 changes: 3 additions & 9 deletions xsdata/formats/dataclass/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,19 @@
from pathlib import Path
from typing import DefaultDict, Dict, Iterator, List, Tuple

from jinja2 import Environment, FileSystemLoader, Template

from xsdata.formats.dataclass.filters import filters
from xsdata.generators import PythonAbstractGenerator
from xsdata.models.codegen import Class, Package
from xsdata.models.elements import Schema
from xsdata.resolver import DependenciesResolver
from xsdata.utils.text import snake_case


class DataclassGenerator(PythonAbstractGenerator):
templates_dir = Path(__file__).parent.joinpath("templates")

def __init__(self):
templates_dir = Path(__file__).parent.joinpath("templates")
self.env = Environment(loader=FileSystemLoader(str(templates_dir)),)
super(DataclassGenerator, self).__init__()
self.env.filters.update(filters)
self.resolver = DependenciesResolver()

def template(self, name: str) -> Template:
return self.env.get_template("{}.jinja2".format(name))

def render_module(
self, output: str, imports: Dict[str, List[Package]]
Expand Down
Empty file.
40 changes: 40 additions & 0 deletions xsdata/formats/plantuml/generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from pathlib import Path
from typing import Iterator, List, Tuple

from xsdata.generators import AbstractGenerator
from xsdata.models.codegen import Class
from xsdata.models.elements import Schema


class PlantUmlGenerator(AbstractGenerator):
templates_dir = Path(__file__).parent.joinpath("templates")

def render_module(self, output: str) -> str:
return self.template("module").render(output=output)

def render_class(self, obj: Class) -> str:
template = "enum" if obj.is_enumeration else "class"
return self.template(template).render(obj=obj)

def render(
self, schema: Schema, classes: List[Class], package: str
) -> Iterator[Tuple[Path, str]]:
"""Given a schema, a list of classes and a target package return to the
writer factory the target file path and the rendered output."""
module = schema.module
package_arr = package.split(".")
package = "{}.{}".format(".".join(package_arr), module)
target = Path.cwd().joinpath(*package_arr)
file_path = target.joinpath(f"{module}.pu")

self.resolver.process(classes=classes, schema=schema, package=package)

output = self.render_classes()

yield file_path, self.render_module(output=output)

def render_classes(self) -> str:
"""Sort classes by name and return the rendered output."""
classes = sorted(self.resolver.sorted_classes(), key=lambda x: x.name)
output = "\n".join(map(self.render_class, classes)).strip()
return f"\n{output}\n"
14 changes: 14 additions & 0 deletions xsdata/formats/plantuml/templates/class.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class {{ obj.name }} {
{%- for attr in obj.attrs %}
+{{ attr.name }} : {{ attr.type }}{{ "[]" if attr.is_list else "" }}
{%- endfor %}
}
{%- for ext in obj.extensions %}
{{ obj.name }} *- {{ ext.name }}
{%- endfor %}
{%- for inner in obj.inner %}
{{ obj.name }} +-- {{ inner.name }}
{% with obj=inner -%}
{%- include "class.jinja2" %}
{%- endwith -%}
{%- endfor %}
5 changes: 5 additions & 0 deletions xsdata/formats/plantuml/templates/enum.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
enum {{ obj.name }} {
{%- for attr in obj.attrs %}
{{ attr.name }} = {{ attr.default }}
{%- endfor %}
}
3 changes: 3 additions & 0 deletions xsdata/formats/plantuml/templates/module.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@startuml
{{ output }}
@enduml
17 changes: 17 additions & 0 deletions xsdata/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,31 @@
from pathlib import Path
from typing import Any, Iterator, List, Optional, Tuple

from jinja2 import Environment, FileSystemLoader, Template

from xsdata.formats.dataclass.utils import replace_words
from xsdata.models.codegen import Attr, Class, Package
from xsdata.models.elements import Schema
from xsdata.models.enums import XSDType
from xsdata.resolver import DependenciesResolver
from xsdata.utils import text


class AbstractGenerator(ABC):
templates_dir: Optional[Path] = None

def __init__(self):
if self.templates_dir is None:
raise TypeError("Missing renderer templates directory")

self.env = Environment(
loader=FileSystemLoader(str(self.templates_dir))
)
self.resolver = DependenciesResolver()

def template(self, name: str) -> Template:
return self.env.get_template("{}.jinja2".format(name))

@abstractmethod
def render(
self, schema: Schema, classes: List[Class], package: str
Expand Down
24 changes: 4 additions & 20 deletions xsdata/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Dict, List

from xsdata.formats.dataclass.generator import DataclassGenerator
from xsdata.formats.plantuml.generator import PlantUmlGenerator
from xsdata.generators import AbstractGenerator
from xsdata.logger import logger
from xsdata.models.codegen import Class
Expand Down Expand Up @@ -38,27 +39,10 @@ def print(
self, schema: Schema, classes: List[Class], package: str, renderer: str
):
engine = self.get_renderer(renderer)
for package, item in engine.print(schema, classes, package):
self.print_class(package, item)

def print_class(self, package: str, obj: Class, indent: int = 0):

extensions = ", ".join(sorted([ext.name for ext in obj.extensions]))
print(f"\n{indent * ' '}{package}.{obj.name}({extensions})")

for attr in sorted(obj.attrs, key=lambda x: x.name):
params = [("default", attr.default)]
params.extend(
[
(key, value)
for key, value in sorted(attr.restrictions.items())
]
)
print(f"{(indent + 4) * ' '}{attr.name}: {attr.type} = {params}")

for inner in sorted(obj.inner, key=lambda x: x.name):
self.print_class(f"{package}.{obj.name}", inner, indent + 4)
for file, output in engine.render(schema, classes, package):
print(output)


writer = CodeWriter()
writer.register_generator("pydata", DataclassGenerator())
writer.register_generator("plantuml", PlantUmlGenerator())

0 comments on commit 4690795

Please sign in to comment.