UML Generator script

This script builds UML graphs taking Murano DSL manifests as input.

It is able to build the following graphs:
* single class inheritance tree
* class inheritance tree with all resources,
  required by that and other classes

Change-Id: Iac40f4524809e6637662c8db450816d57e7065f1
This commit is contained in:
Dmitry Teselkin 2014-03-27 15:33:51 +04:00
parent 39c2726540
commit 2bd4afb2bf
2 changed files with 253 additions and 0 deletions

View File

@ -0,0 +1,28 @@
# MuranoPL UML Generator
This folder contains scripts (currently only one) to generate UML graphs based on MuranoPL manifests.
## Installation
1. Copy **umlgen.py** to **meta** folder under MuranoPL directory.
2. Download **plantuml.jar** from http://plantuml.sourceforge.net/ and copy it to the folder ablve.
3. Generate UML graph using the command below:
```
./umlgen.py
```
## Usage
```
./umlgen.py [--no-namespaces] [--parents-only] [CLASS_FQDN]
```
* **--no-namespaces** - disables automatic classes grouping
* **--parents-only** - generate graph using only parent-child dependencies
* **CLASS_FQDN** - MuranoPL class FQDN

View File

@ -0,0 +1,225 @@
#!/usr/bin/python
# Copyright (c) 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import argparse
import os
import re
import yaml
# Workaround for unknown yaml tags
def yaml_default_ctor(loader, tag_suffix, node):
return tag_suffix + ' ' + node.value
yaml.add_multi_constructor('', yaml_default_ctor)
class PlantUmlNode():
def __init__(self, node_dn):
self._dn = node_dn
self.attributes = []
self.operations = []
self.extras = []
def write(self, f):
f.write('class {0} {{\n'.format(self._dn))
f.write('-- Properties --\n')
for item in self.attributes:
f.write('<{type}> {name}: {contract}\n'.format(**item))
f.write('-- Workflows --\n')
for item in self.operations:
f.write('{name}()\n'.format(**item))
f.write('-- Namespaces --\n')
for item in self.extras:
if item['key'] != '=':
f.write('{key}: {value}\n'.format(**item))
f.write('}\n')
def add_attribute(self, name, type, contract):
d = {'type': type, 'name': name, 'contract': contract}
self.attributes.append(d)
def add_operation(self, name):
d = {'name': name}
self.operations.append(d)
def add_extra(self, key, value):
d = {'key': key, 'value': value}
self.extras.append(d)
class DslSpec():
def __init__(self, class_dn, basepath='.'):
self._dn = class_dn
self._id = self._dn.replace('.', '_')
self._name = self._dn.split('.')[-1]
self._ns = ('=', self._dn.split('.')[:-1])
self._graph = None
self.is_virtual = True
self.parent_dn = None
self.manifest = None
manifest_path = os.path.join(basepath, self._dn, 'manifest.yaml')
if os.path.exists(manifest_path):
self.manifest = yaml.load(open(manifest_path))
if self.manifest:
self.is_virtual = False
self.name = self.manifest['Name']
if 'Extends' in self.manifest:
self.parent_dn = self.get_dn(self.manifest['Extends'])
def split_ns(self, name=None):
if name:
parts = name.split(':')
if len(parts) == 1:
parts.insert(0, '=')
else:
parts = ['=', self._name]
return parts
def get_ns(self, name=None):
parts = self.split_ns(name)
return {
'key': parts[0],
'value': self.manifest['Namespaces'][parts[0]]
}
def get_dn(self, name=None):
parts = self.split_ns(name)
parts[0] = self.get_ns(parts[0] + ':')['value']
return '.'.join(parts)
def get_name(self):
return self._name
class DslPlantUmlNode(DslSpec):
def write(self, file):
uml_class = PlantUmlNode(self._dn)
ext_classes = []
if not self.is_virtual:
namespaces = [self.get_ns()]
for name, item in self.manifest.get('Properties', {}).iteritems():
item_type = item.get('Type', 'In')
item_contract = str(item.get('Contract', 'UNDEFINED'))
match = re.search('class\((.*?)\)', item_contract)
if match:
ns = self.get_ns(match.group(1))
ext_classes.append(self.get_dn(match.group(1)))
if not ns in namespaces:
namespaces.append(ns)
uml_class.add_attribute(
name,
type=item_type,
contract=item_contract
)
for m in self.manifest.get('Workflow', []):
uml_class.add_operation(m)
for ns in namespaces:
uml_class.add_extra(ns['key'], ns['value'])
uml_class.write(file)
return ext_classes
class DslPlantUmlGraph():
def __init__(self):
self._nodes = []
self._edges = []
self._options = {}
self._file = None
def write(self, classname, level=0):
if level == 0:
self._file.write('@startuml\n')
if self.get_option('NoNamespaces', False):
self._file.write('set namespaceSeparator none\n')
if self.node_exists(classname):
return
self.add_node(classname)
node = DslPlantUmlNode(classname)
ext_classes = node.write(self._file)
if not self.get_option('ParentsOnly', False):
for ext_class in ext_classes:
self.add_edge(
from_node=ext_class,
to_node=classname,
edge_type='<..'
)
self.write(ext_class, level + 1)
if node.is_virtual:
return
if node.parent_dn:
self.add_edge(
from_node=node.parent_dn,
to_node=classname,
edge_type='<|--'
)
self.write(node.parent_dn, level + 1)
if level == 0:
for edge in self._edges:
self._file.write('{from_node} {type} {to_node}\n'
.format(**edge))
self._file.write('@enduml\n')
self._file.close()
def add_node(self, classname):
self._nodes.append(classname)
def node_exists(self, dn):
return dn in self._nodes
def add_edge(self, from_node, to_node, edge_type):
edge = {'from_node': from_node, 'to_node': to_node, 'type': edge_type}
if not edge in self._edges:
self._edges.append(edge)
def set_option(self, key, value):
self._options[key] = value
def get_option(self, key, default=None):
return self._options.get(key, default)
def open_file(self, file_name):
self._file = open(file_name, 'w')
def close_file(self):
self._file.close()
parser = argparse.ArgumentParser(description='Qwerty')
parser.add_argument('classname',
default='com.mirantis.murano.demoApp.DemoHost',
help='Dsl Class Name to draw.', nargs='?')
parser.add_argument('-n', '--no-namespaces', action='store_true')
parser.add_argument('-p', '--parents-only', action='store_true')
args = parser.parse_args()
graph = DslPlantUmlGraph()
graph.set_option('NoNamespaces', args.no_namespaces)
graph.set_option('ParentsOnly', args.parents_only)
graph.open_file('plantuml.txt')
graph.write(args.classname)
graph.close_file()
os.system('java -jar plantuml.jar plantuml.txt')
os.system('xdg-open plantuml.png')