Merge "UML Generator script"
This commit is contained in:
commit
29ae351a7d
28
contrib/utils/uml-generator/README.md
Normal file
28
contrib/utils/uml-generator/README.md
Normal 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
|
||||||
|
|
225
contrib/utils/uml-generator/umlgen.py
Executable file
225
contrib/utils/uml-generator/umlgen.py
Executable 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')
|
Loading…
Reference in New Issue
Block a user