Merge master

Signed-off-by: Dan Yeaw <dan@yeaw.me>
This commit is contained in:
Dan Yeaw 2019-10-23 21:32:36 -04:00
commit 369ba17021
No known key found for this signature in database
GPG Key ID: 77A923EF537B61A4
19 changed files with 2 additions and 2972 deletions

View File

@ -1,244 +0,0 @@
"""
This module provides a means to automatically layout diagrams.
The layout is done like this:
- First all nodes (Classes, packages, comments) on a diagram are determined
- A vertical ordering is determined based on the inheritance
- A horizontal ordering is determined based on associations and dependencies
- The nodes are moved to their place
- Lines are reconnected to the nodes, so everything looks pretty.
"""
import logging
import random
from gaphor.core import action, transactional
from gaphor.diagram.presentation import LinePresentation
from gaphor.diagram.classes import GeneralizationItem, ImplementationItem
from gaphor.abc import Service, ActionProvider
from gaphor.plugins.diagramlayout import toposort
from gaphor.ui.abc import UIComponent
log = logging.getLogger(__name__)
class DiagramLayout(Service, ActionProvider):
def __init__(self, component_registry):
self.component_registry = component_registry
def shutdown(self):
pass
def get_current_diagram(self):
return self.component_registry.get(
UIComponent, "diagrams"
).get_current_diagram()
def update(self):
self.sensitive = bool(self.get_current_diagram())
@action(
name="diagram-layout", label="La_yout diagram", tooltip="simple diagram layout"
)
def execute(self):
d = self.get_current_diagram()
self.layout_diagram(d)
@transactional
def layout_diagram(self, diag):
layout_diagram(diag)
MARGIN = 100
def layout_diagram(diag):
"""
So an attempt to layout (order) the items on a diagram. The items
should already be placed on the diagram and the items should already be
connected.
This function works on the diagram items (hence it does not check relations
in the datamodel, only the ones drawn on the diagram) to produce a
decent layout.
"""
nodes = []
primary_nodes = []
relations = []
other_relations = []
# Make sure all items are updated
diag.canvas.update_now()
# First extract data from the diagram (which ones are the nodes, and
# the relationships).
for item in diag.canvas.get_root_items():
if isinstance(item, (GeneralizationItem, ImplementationItem)):
# Primary relationships, should be drawn top-down
try:
relations.append(
(item.handles[0].connected_to, item.handles[-1].connected_to)
)
primary_nodes.extend(relations[-1])
except Exception as e:
log.error(e)
elif isinstance(item, LinePresentation):
# Secondary (associations, dependencies) may be drawn top-down
# or left-right
try:
other_relations.append(
(item.handles[0].connected_to, item.handles[-1].connected_to)
)
# other_relations.append((item.handles[-1].connected_to,
# item.handles[0].connected_to))
except Exception as e:
log.error(e)
else:
nodes.append(item)
# Add some randomness:
random.shuffle(other_relations)
primary_nodes = uniq(primary_nodes)
# Find out our horizontal and vertical sorting
sorted = toposort.toposort(nodes, relations, 0)
other_sorted = toposort.toposort(nodes, other_relations, 0)
if not sorted:
return
# Move nodes from the first (generalization) row to the other rows
# if they are not superclasses for some other class
# Run the list twice, just to ensure no items are left behind.
for item in list(sorted[0]) * 2:
if item not in primary_nodes and item in sorted[0]:
# Find nodes that do have a relation to this one
related = find_related_nodes(item, other_relations)
# Figure out what row(s) they're on
row = find_row(item, related, sorted[1:])
if row:
# print 'moving', item.subject.name, 'to row', sorted.index(row)
sorted[0].remove(item)
row.append(item)
# Order each row based on the sort order of other_sorted
# (the secondary sort alg.).
for row in sorted:
for other_row in other_sorted:
for other_item in other_row:
if other_item in row:
row.remove(other_item)
row.append(other_item)
# Place the nodes on the diagram.
y = MARGIN / 2
for row in sorted:
x = MARGIN / 2
maxy = 0
for item in row:
if not item:
continue
maxy = max(maxy, item.height)
a = item.matrix
a = (a[0], a[1], a[2], a[3], x, y)
item.matrix = a
item.request_update()
x += item.width + MARGIN
y += maxy + MARGIN
# Reattach the relationships to the nodes, in a way that it looks nice.
simple_layout_lines(diag)
def simple_layout_lines(diag):
"""
Just do the layout of the lines in a diagram. The nodes (class, package)
are left where they are (use layout_diagram() if you want to reorder
everything).
The line layout is basically very simple: just draw straight lines
between nodes on the diagram.
"""
lines = {}
for item in diag.canvas.get_root_items():
if isinstance(item, LinePresentation):
# Secondary (associations, dependencies) may be drawn top-down
# or left-right
try:
lines[item] = (
item.handles[0].connected_to,
item.handles[-1].connected_to,
)
except Exception as e:
log.error(e)
# Now we have the lines, let's first ensure we only have a begin and an
# end handle
for line in list(lines.keys()):
while len(line.handles) > 2:
line.set_property("del_segment", 0)
# Strategy 1:
# Now we have nice short lines. Let's move them to a point between
# both nodes and let the connect() do the work:
for line, nodes in list(lines.items()):
if not nodes[0] or not nodes[1]:
# loose end
continue
center0 = find_center(nodes[0])
center1 = find_center(nodes[1])
center = ((center0[0] + center1[0]) / 2.0, (center0[1] + center1[1]) / 2.0)
line.handles[0].set_pos_w(*center)
line.handles[-1].set_pos_w(*center)
nodes[0].connect_handle(line.handles[0])
nodes[1].connect_handle(line.handles[-1])
def uniq(lst):
d = {}
for l in lst:
d[l] = None
return list(d.keys())
def find_related_nodes(item, relations):
"""
Find related nodes of item, given a list of tuples.
References to itself are ignored.
"""
related = []
for pair in relations:
if pair[0] is item:
if pair[1] is not item:
related.append(pair[1])
elif pair[1] is item:
if pair[0] is not item:
related.append(pair[0])
return uniq(related)
def find_row(item, related_items, sorted):
"""
Find the row that contains the most references to item.
"""
max_refs = 0
max_row = None
for row in sorted:
cnt = len([i for i in row if i in related_items])
if cnt > max_refs:
max_row = row
max_refs = cnt
return max_row
def find_center(item):
"""
Find the center point of the item, in world coordinates
"""
x = item.width / 2.0
y = item.height / 2.0
return item.canvas.get_matrix_i2c(item).transform_point(x, y)
# vim:sw=4:et

View File

@ -1,29 +0,0 @@
from gaphor import UML
from gaphor.application import Application
from gaphor.diagram.classes import ClassItem
from gaphor.tests.testcase import TestCase
class DiagramLayoutTestCase(TestCase):
services = TestCase.services + [
"main_window",
"properties",
"diagram_layout",
"import_menu",
"export_menu",
"tools_menu",
]
def testDiagramLayout(self):
elemfact = Application.get_service("element_factory")
diagram_layout = Application.get_service("diagram_layout")
diagram = elemfact.create(UML.Diagram)
c1 = diagram.create(ClassItem, subject=elemfact.create(UML.Class))
c2 = diagram.create(ClassItem, subject=elemfact.create(UML.Class))
c2.matrix.translate(100, 100)
c2.request_update()
diagram_layout.layout_diagram(diagram)

View File

@ -1,210 +0,0 @@
class RecursionError(OverflowError, ValueError):
"""Unable to calculate result because of recursive structure"""
def sort(nodes, routes, noRecursion=1):
"""Passed a list of node IDs and a list of source,dest ID routes
attempt to create a list of stages where each sub list
is one stage in a process.
"""
children, parents = _buildChildrenLists(routes)
# first stage is those nodes
# having no incoming routes...
stage = []
stages = [stage]
taken = []
for node in nodes:
if not parents.get(node):
stage.append(node)
if nodes and not stage:
# there is no element which does not depend on
# some other element!!!
stage.append(nodes[0])
taken.extend(stage)
nodes = list(filter(lambda x, l=stage: x not in l, nodes))
while nodes:
previousStageChildren = []
nodelen = len(nodes)
# second stage are those nodes
# which are direct children of the first stage
for node in stage:
for child in children.get(node, []):
if child not in previousStageChildren and child not in taken:
previousStageChildren.append(child)
elif child in taken and noRecursion:
raise RecursionError((child, node))
# unless they are children of other direct children...
# TODO, actually do that...
stage = previousStageChildren
removes = []
for current in stage:
currentParents = parents.get(current, [])
for parent in currentParents:
if parent in stage and parent != current:
# might wind up removing current...
if not current in parents.get(parent, []):
# is not mutually dependent...
removes.append(current)
for remove in removes:
while remove in stage:
stage.remove(remove)
stages.append(stage)
taken.extend(stage)
nodes = list(filter(lambda x, l=stage: x not in l, nodes))
if nodelen == len(nodes):
if noRecursion:
raise RecursionError(nodes)
else:
stages.append(nodes[:])
nodes = []
return stages
def _buildChildrenLists(routes):
childrenTable = {}
parentTable = {}
for sourceID, destinationID in routes:
currentChildren = childrenTable.get(sourceID, [])
currentParents = parentTable.get(destinationID, [])
if not destinationID in currentChildren:
currentChildren.append(destinationID)
if not sourceID in currentParents:
currentParents.append(sourceID)
childrenTable[sourceID] = currentChildren
parentTable[destinationID] = currentParents
return childrenTable, parentTable
def toposort(nodes, routes, noRecursion=1):
"""Topological sort from Tim Peters, fairly efficient
in comparison (it seems)."""
# first calculate the recursion depth
dependencies = {}
inversedependencies = {}
if not nodes:
return []
if not routes:
return [nodes]
for node in nodes:
dependencies[node] = (0, node)
inversedependencies[node] = []
for depended, depends in routes:
# is it a null rule
try:
newdependencylevel, object = dependencies.get(depends, (0, depends))
except TypeError:
print(depends)
raise
dependencies[depends] = (newdependencylevel + 1, depends)
# "dependency (existence) of depended-on"
newdependencylevel, object = dependencies.get(depended, (0, depended))
dependencies[depended] = (newdependencylevel, depended)
# Inverse dependency set up
dependencieslist = inversedependencies.get(depended, [])
dependencieslist.append(depends)
inversedependencies[depended] = dependencieslist
### Now we do the actual sorting
# The first task is to create the sortable
# list of dependency-levels
sortinglist = sorted(dependencies.values())
output = []
while sortinglist:
deletelist = []
generation = []
output.append(generation)
while sortinglist and sortinglist[0][0] == 0:
number, object = sortinglist[0]
generation.append(object)
deletelist.append(object)
for inverse in inversedependencies.get(object, ()):
try:
oldcount, inverse = dependencies[inverse]
if oldcount > 0:
# will be dealt with on later pass
dependencies[inverse] = (oldcount - 1, inverse)
else:
# will be dealt with on this pass,
# so needs not to be in the sorting list next time
deletelist.append(inverse)
# just in case a loop comes through
inversedependencies[object] = []
except KeyError:
# dealing with a recursion-breaking run...
pass
del sortinglist[0]
# if no elements could be deleted, then
# there is something which depends upon itself
if not deletelist:
if noRecursion:
raise RecursionError(sortinglist)
else:
# hack so that something gets deleted...
## import pdb
## pdb.set_trace()
dependencies[sortinglist[0][1]] = (0, sortinglist[0][1])
# delete the items that were dealt with
for item in deletelist:
try:
del dependencies[item]
except KeyError:
pass
# need to recreate the sortinglist
sortinglist = list(dependencies.values())
if not generation:
output.remove(generation)
sortinglist.sort()
return output
if __name__ == "__main__":
pass
nodes = [0, 1, 2, 3, 4, 5]
testingValues = [
[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)],
[(0, 1), (0, 2), (1, 2), (3, 4), (4, 5)],
[(0, 1), (0, 2), (0, 2), (2, 4), (2, 5), (3, 2), (0, 3)],
[
(0, 1), # 3-element cycle test, no orphan nodes
(1, 2),
(2, 0),
(2, 4),
(2, 5),
(3, 2),
(0, 3),
],
[(0, 1), (1, 1), (1, 1), (1, 4), (1, 5), (1, 2), (3, 1), (2, 1), (2, 0)],
[(0, 1), (1, 0), (0, 2), (0, 3)],
[(0, 1), (1, 0), (0, 2), (3, 1)],
]
print("sort, no recursion allowed")
for index in range(len(testingValues)):
## print ' %s -- %s'%( index, testingValues[index])
try:
print(" ", sort(nodes, testingValues[index]))
except:
print("exception raised")
print("toposort, no recursion allowed")
for index in range(len(testingValues)):
## print ' %s -- %s'%( index, testingValues[index])
try:
print(" ", toposort(nodes, testingValues[index]))
except:
print("exception raised")
print("sort, recursion allowed")
for index in range(len(testingValues)):
## print ' %s -- %s'%( index, testingValues[index])
try:
print(" ", sort(nodes, testingValues[index], 0))
except:
print("exception raised")
print("toposort, recursion allowed")
for index in range(len(testingValues)):
## print ' %s -- %s'%( index, testingValues[index])
try:
print(" ", toposort(nodes, testingValues[index], 0))
except:
print("exception raised")

View File

@ -1,109 +0,0 @@
PyNSource
http://www.atug.com/andypatterns/pynsource.htm
(c) 2003, 2004, 2005, 2006 Andy Bulka
License: Free to use as long as acknowledgement is made in source code.
abulka@netspace.net.au
http://www.atug.com/andypatterns
Version 1.4c
- Fixed some parsing bugs.
- Parsing now more correct under python 2.4 (python changed token.py !!)
- Unit tests now all pass
Version 1.4b
- Added wxpython 2.5 compatibility (thanks Thomas Margraf!)
Version 1.4a
GUI changes:
- Right Click on a node to delete it.
- Run Layout anytime from menu.
- Left click on background will deselect any selected shapes
Version 1.4
- Fixed indentation error causing more output than normal in text ouput
- Module level functions not treated as classes any more
- Smarter detection of composition relationships, as long as classname
and variable name are the same (ignoring case) then PyNSource will detect e.g.
class Cat:
pass
class A:
def __init__(self, cat):
self.cats.append(Cat()) # always has worked, composition detected.
self.cats.append(cat) # new 1.4 feature, composition detected here too.
Version 1.3a
A reverse engineering tool for Python source code
- UML diagram models that you can layout, arrange and print out.
- UML text diagrams, which you can paste into your source code for documentation purposes.
- Java or Delphi code (which can be subsequently imported into more sophisticated UML
modeling tools, like Enterprise Architect or ESS-Model (free).)
Features
- Resilient: doesn't import the python files, thus will never get "stuck" when syntax is wrong.<2E>
- Fast
- Recognises inheritance and composition relationships
- Detects the cardinality of associations e.g. one to one or 1..* etc
- Optionally treat modules as classes - creating a pseudo class for each
module - module variables and functions are treated as attributes and methods of a class
- Has been developed using unit tests (supplied) so that you can trust it just that little bit more ;-)
GUI FRONT END
-------------
The PyNSource Gui is started in two ways:
* By running the standalone pynsourceGui.exe via the shortcut created by the standalone installer. or
* By running pynsourceGui.py ( you need wxpython installed. See http://www.wxpython.org )
e.g. \Python22\python.exe \Python22\Lib\site-packages\pynsource\pyNsourceGui.py
The PyNSource command line tool is pynsource.py
Command line Usage
------------------
<EFBFBD>pynsource -v -m [-j outdir] sourceDirOrListOfPythonFiles...<2E><><EFBFBD>
no options - generate Ascii UML
-j generate java files, specify output folder for java files
-d generate pascal files, specify output folder for pascal files
-v verbose
-m create psuedo class for each module,
module attrs/defs etc treated as class attrs/defs
Examples
e.g.
\python22\python.exe \Python22\Lib\site-packages\pynsource\pynsource.py -d c:\delphiouputdir c:\pythoninputdir\*.py
The above line will scan all the files in c:\pythoninputdir and generate
a bunch of delphi files in the folder c:\delphiouputdir
BASIC ASCII UML OUTPUT from PYTHON - EXAMPLES
e.g. pynsource Test/testmodule01.py
e.g. pynsource -m Test/testmodule03.py
GENERATE JAVA FILES from PYTHON - EXAMPLES
e.g. pynsource -j c:/try c:/try
e.g. pynsource -v -m -j c:/try c:/try
e.g. pynsource -v -m -j c:/try c:/try/s*.py
e.g. pynsource -j c:/try c:/try/s*.py Tests/u*.py
e.g. pynsource -v -m -j c:/try c:/try/s*.py Tests/u*.py c:\cc\Devel\Client\w*.py
GENERATE DELPHI FILES from PYTHON - EXAMPLE
e.g. pynsource -d c:/delphiouputdir c:/pythoninputdir/*.py
see http://www.atug.com/andypatterns/pynsource.htm for more documentation.
Bugs to abulka@netspace.net.au

View File

@ -1,242 +0,0 @@
"""
Code reverse engineer plugin for Python source code.
This plugin uses PyNSource, written by Andy Bulka
[http://www.atug.com/andypatterns/pynsource.htm].
Depends on the Diagram Layout plugin.
"""
from gi.repository import GObject
from gi.repository import Gtk
from gaphor.core import action
from gaphor.abc import Service, ActionProvider
from gaphor.plugins.pynsource.engineer import Engineer
NAME_COLUMN = 0
class PyNSource(Service, ActionProvider):
def __init__(self, element_factory, diagram_layout, main_window):
self.element_factory = element_factory
self.diagram_layout = diagram_layout
self.main_window = main_window
self.win = None
def shutdown(self):
pass
@action(
name="file-import-pynsource",
label="Python source code",
tooltip="Import Python source code",
)
def execute(self):
dialog = self.create_dialog()
response = dialog.run()
if response != Gtk.ResponseType.OK:
dialog.destroy()
self.reset()
return
files = []
for row in self.filelist:
files.append(row[0])
dialog.destroy()
self.process(files)
self.reset()
def process(self, files):
"""Create a diagram based on a list of files.
"""
engineer = Engineer(self.element_factory, self.diagram_layout)
engineer.process(files)
main_window = self.main_window
# Open and select the new diagram in the main window:
main_window.select_element(engineer.diagram)
main_window.show_diagram(engineer.diagram)
def create_dialog(self):
dialog = Gtk.Dialog(
"Import Python files",
self.main_window.window,
Gtk.DialogFlags.MODAL,
(
Gtk.STOCK_OK,
Gtk.ResponseType.OK,
Gtk.STOCK_CANCEL,
Gtk.ResponseType.CANCEL,
),
)
dialog.set_default_response(Gtk.ResponseType.OK)
filelist = Gtk.ListStore(GObject.TYPE_STRING)
filelist.connect("row-inserted", self.on_view_rows_changed)
filelist.connect("row-deleted", self.on_view_rows_changed)
hbox = Gtk.HBox()
frame = Gtk.Frame.new("Files to reverse-engineer")
frame.set_border_width(8)
frame.set_size_request(500, 300)
frame.show()
hbox.pack_start(frame, True, True, 0)
treeview = Gtk.TreeView(filelist)
treeview.set_property("headers-visible", False)
selection = treeview.get_selection()
selection.set_mode(Gtk.SelectionMode.SINGLE)
treeview.set_size_request(200, -1)
treeview.connect_after("cursor_changed", self.on_view_cursor_changed)
scrolled_window = Gtk.ScrolledWindow()
scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scrolled_window.set_shadow_type(Gtk.ShadowType.IN)
scrolled_window.set_border_width(4)
scrolled_window.add(treeview)
frame.add(scrolled_window)
scrolled_window.show()
cell = Gtk.CellRendererText()
column = Gtk.TreeViewColumn("Filename", cell, text=NAME_COLUMN)
treeview.append_column(column)
bbox = Gtk.VButtonBox()
bbox.set_layout(Gtk.ButtonBoxStyle.SPREAD)
bbox.set_border_width(10)
button = Gtk.Button(stock="gtk-add")
button.connect("clicked", self.on_add_dir_clicked)
bbox.add(button)
self.add_button = button
# button = Gtk.Button('Add dir...')
# button.connect('clicked', self.on_add_dir_clicked)
# bbox.add(button)
# self.add_dir_button = button
button = Gtk.Button(stock="gtk-remove")
button.connect("clicked", self.on_remove_clicked)
button.set_property("sensitive", False)
bbox.add(button)
self.remove_button = button
# button = Gtk.Button(stock='gtk-execute')
# button.connect('clicked', self.on_execute_clicked)
# button.set_property('sensitive', False)
# bbox.add(button)
# self.execute_button = button
hbox.pack_start(bbox, False, True, 0)
hbox.show_all()
dialog.vbox.pack_start(hbox, True, True, 0)
self.filelist = filelist
self.treeview = treeview
return dialog
def reset(self):
self.add_button = None
self.add_dir_button = None
self.remove_button = None
self.treeview = None
self.filelist = None
def Walk(self, root, recurse=0, pattern="*", return_folders=0):
import fnmatch
import os
import string
# initialize
result = []
# must have at least root folder
try:
names = os.listdir(root)
except os.error:
return result
# expand pattern
pattern = pattern or "*"
pat_list = string.splitfields(pattern, ";")
# check each file
for name in names:
fullname = os.path.normpath(os.path.join(root, name))
# grab if it matches our pattern and entry type
for pat in pat_list:
if fnmatch.fnmatch(name, pat):
if os.path.isfile(fullname) or (
return_folders and os.path.isdir(fullname)
):
result.append(fullname)
continue
# recursively scan other folders, appending results
if recurse:
if os.path.isdir(fullname) and not os.path.islink(fullname):
result = result + self.Walk(
fullname, recurse, pattern, return_folders
)
return result
def on_view_cursor_changed(self, view):
selection = view.get_selection()
filelist, iter = selection.get_selected()
if not iter:
self.remove_button.set_property("sensitive", False)
return
# element = filelist.get_value(iter, NAME_COLUMN)
self.remove_button.set_property("sensitive", True)
# self.update_detail(element)
def on_view_rows_changed(self, view, *args):
iter = None
try:
iter = view.get_iter("0")
except ValueError:
pass
# self.execute_button.set_property('sensitive', bool(iter))
def on_add_dir_clicked(self, button):
import os
filesel = Gtk.FileChooserNative(
title="Add Source Code", action=Gtk.FileChooserAction.OPEN
)
filesel.set_select_multiple(True)
filesel.set_filename("~/")
response = filesel.run()
selection = filesel.get_filenames()
filesel.destroy()
if response == Gtk.ResponseType.ACCEPT:
for filename in selection:
if os.path.isdir(filename):
list = self.Walk(filename, 1, "*.py", 1)
for file in list:
iter = self.filelist.append()
self.filelist.set_value(iter, NAME_COLUMN, file)
else:
list = filename
iter = self.filelist.append()
self.filelist.set_value(iter, NAME_COLUMN, list)
def on_remove_clicked(self, button):
selection = self.treeview.get_selection()
filelist, iter = selection.get_selected()
if not iter:
return
element = filelist.remove(iter)
self.remove_button.set_property("sensitive", False)

View File

@ -1,249 +0,0 @@
"""The code reverse engineer.
"""
from gaphas.aspect import ConnectionSink, Connector
from gaphor import UML
from gaphor.diagram.classes import AssociationItem, ClassItem, GeneralizationItem
from gaphor.diagram.connectors import IConnect
from gaphor.plugins.pynsource.pynsource import PySourceAsText
BASE_CLASSES = ("object", "type", "dict", "list", "tuple", "int", "float")
class Engineer:
"""
The Engineer class will create a Gaphor model based on a list of Python
files.
"""
def process(self, element_factory, diagram_layout, files=None):
self.element_factory = element_factory
self.diagram_layout = diagram_layout
# these are tuples between class names.
# self.associations_generalisation = []
# self.associations_composition = []
p = PySourceAsText()
self.parser = p
if files:
# u = PythonToJava(None, treatmoduleasclass=0, verbose=0)
for f in files:
# Build a shape with all attrs and methods, and prepare association dict
p.Parse(f)
print(p)
try:
self._root_package = self.element_factory.lselect(
lambda e: isinstance(e, UML.Package) and not e.namespace
)[0]
except IndexError:
pass # running as test?
for m in p.modulemethods:
print("ModuleMethod:", m)
# Step 0: create a diagram to put the newly created elements on
self.diagram = self.element_factory.create(UML.Diagram)
self.diagram.name = "New classes"
self.diagram.package = self._root_package
# Step 1: create the classes
for name, clazz in list(p.classlist.items()):
print(type(clazz), dir(clazz))
self._create_class(clazz, name)
# Create generalization relationships:
for name, clazz in list(p.classlist.items()):
self._create_generalization(clazz)
# Create attributes (and associations) on the classes
for name, clazz in list(p.classlist.items()):
self._create_attributes(clazz)
# Create operations
for name, clazz in list(p.classlist.items()):
self._create_methods(clazz)
self.diagram_layout.layout_diagram(self.diagram)
def _create_class(self, clazz, name):
c = self.element_factory.create(UML.Class)
c.name = name
c.package = self.diagram.namespace
ci = self.diagram.create(ClassItem)
ci.subject = c
clazz.gaphor_class = c
clazz.gaphor_class_item = ci
def _create_generalization(self, clazz):
if not clazz.ismodulenotrealclass:
for superclassname in clazz.classesinheritsfrom:
if superclassname in BASE_CLASSES:
continue
try:
superclass = self.parser.classlist[superclassname].gaphor_class
superclass_item = self.parser.classlist[
superclassname
].gaphor_class_item
except KeyError as e:
print("No class found named", superclassname)
others = self.element_factory.lselect(
lambda e: isinstance(e, UML.Class) and e.name == superclassname
)
if others:
superclass = others[0]
print(f"Found class in factory: {superclass.name}")
superclass_item = self.diagram.create(ClassItem)
superclass_item.subject = superclass
else:
continue
# Finally, create the generalization relationship
print(f"Creating Generalization for {clazz}", superclass)
# gen = self.element_factory.create(UML.Generalization)
# gen.general = superclass
# gen.specific = clazz.gaphor_class
geni = self.diagram.create(GeneralizationItem)
# geni.subject = gen
self.connect(geni, geni.tail, clazz.gaphor_class_item)
self.connect(geni, geni.head, superclass_item)
# adapter = IConnect(superclass_item, geni)
# assert adapter
# handle = geni.handles()[0]
# adapter.connect(handle)
# clazz.gaphor_class_item.connect_handle(geni.handles[-1])
# adapter = IConnect(clazz.gaphor_class_item, geni)
# assert adapter
# handle = geni.handles()[-1]
# adapter.connect(handle)
def connect(self, line, handle, item, port=None):
"""
Connect line's handle to an item.
If port is not provided, then first port is used.
"""
canvas = line.canvas
if port is None and len(item.ports()) > 0:
port = item.ports()[0]
sink = ConnectionSink(item, port)
connector = Connector(line, handle)
connector.connect(sink)
def _create_attributes(self, clazz):
for attrobj in clazz.attrs:
# TODO: Check object type and figure out if it should be an
# attribute or an association.
self._create_attribute(clazz, attrobj)
def _create_methods(self, clazz):
for adef in clazz.defs:
op = self.element_factory.create(UML.Operation)
op.name = adef
clazz.gaphor_class.ownedOperation = op
def _find_class_by_name(self, classname):
try:
superclass = self.parser.classlist[classname].gaphor_class
superclass_item = self.parser.classlist[classname].gaphor_class_item
except KeyError as e:
print("No class found named", classname)
others = self.element_factory.lselect(
lambda e: isinstance(e, UML.Class) and e.name == classname
)
if others:
superclass = others[0]
print(f"Found class in factory: {superclass.name}")
superclass_item = self.diagram.create(ClassItem)
superclass_item.subject = superclass
else:
return None, None
return superclass, superclass_item
def _visibility(self, attrname):
if attrname.startswith("__"):
return "private"
elif attrname.startswith("_"):
return "protected"
return "public"
def _create_attribute(self, clazz, attr):
static = False
many = False
if "static" in attr.attrtype:
static = True
if "many" in attr.attrtype:
many = True
compositescreated = self.parser.GetCompositeClassesForAttr(attr.attrname, clazz)
tail_type = None
if compositescreated:
tail_type, tail_type_item = self._find_class_by_name(compositescreated[0])
if tail_type:
# Create an association:
# print "%s %s <@>----> %s" % (attr.attrname, static, str(compositescreated))
# The property on the tail of the association (tail_end) is owned
# by the class connected on the head_end (head_type)
head_type = clazz.gaphor_class
head_type_item = clazz.gaphor_class_item
# relation = self.element_factory.create(UML.Association)
# head_end = self.element_factory.create(UML.Property)
# head_end.lowerValue = self.element_factory.create(UML.LiteralSpecification)
# tail_end = self.element_factory.create(UML.Property)
# tail_end.name = attr.attrname
# tail_end.visibility = self._visibility(attr.attrname)
# tail_end.aggregation = 'composite'
# tail_end.lowerValue = self.element_factory.create(UML.LiteralSpecification)
# relation.package = self.diagram.namespace
# relation.memberEnd = head_end
# relation.memberEnd = tail_end
# head_end.type = head_type
# tail_end.type = tail_type
# head_type.ownedAttribute = tail_end
# tail_type.ownedAttribute = head_end
# Now the subject
# association.subject = relation
# association.head_end.subject = head_end
# association.tail_end.subject = tail_end
# Create the diagram item:
association = self.diagram.create(AssociationItem)
adapter = IConnect(head_type_item, association)
assert adapter
handle = association.handles()[0]
adapter.connect(handle)
adapter = IConnect(tail_type_item, association)
assert adapter
handle = association.handles()[-1]
adapter.connect(handle)
# Apply attribute information to the association (ends)
association.head_end.navigability = False
tail_prop = association.tail_end.subject
tail_prop.name = attr.attrname
tail_prop.visibility = self._visibility(attr.attrname)
tail_prop.aggregation = "composite"
else:
# Create a simple attribute:
# print "%s %s" % (attr.attrname, static)
prop = self.element_factory.create(UML.Property)
prop.name = attr.attrname
prop.visibility = self._visibility(attr.attrname)
prop.isStatic = static
clazz.gaphor_class.ownedAttribute = prop
# print many
import pprint
pprint.pprint(attr)
# print dir(attr)

View File

@ -1,571 +0,0 @@
"""Definitions of Python, Java and Delphi keywords.
The definitions are used so that pynsource can skip these and not treat them
like you are creating an instance of a locally defined class.
"""
pythonbuiltinfunctions_txt = """
ArithmeticError
AssertionError
AttributeError
DeprecationWarning
EOFError
EnvironmentError
Exception
FloatingPointError
FutureWarning
IOError
ImportError
IndentationError
IndexError
KeyError
KeyboardInterrupt
LookupError
MemoryError
NameError
NotImplementedError
OSError
OverflowError
OverflowWarning
PendingDeprecationWarning
ReferenceError
RuntimeError
RuntimeWarning
StandardError
StopIteration
SyntaxError
SyntaxWarning
SystemError
SystemExit
TabError
TypeError
UnboundLocalError
UnicodeError
UserWarning
ValueError
Warning
WindowsError
ZeroDivisionError
Ellipsis
False
None
NotImplemented
True
UnicodeDecodeError
UnicodeEncodeError
UnicodeTranslateError
__debug__
__import__
abs
apply
basestring
bool
buffer
callable
chr
classmethod
cmp
coerce
compile
complex
copyright
credits
delattr
dict
dir
divmod
enumerate
eval
execfile
exit
file
filter
float
getattr
globals
hasattr
hash
help
hex
id
input
int
intern
isinstance
issubclass
iter
len
license
list
locals
long
map
max
min
object
oct
open
ord
pow
property
quit
range
raw_input
reduce
reload
repr
round
setattr
slice
staticmethod
str
sum
super
tuple
type
unichr
unicode
vars
xrange
zip
__base__
__bases__
__basicsize__
__class__
__dict__
__dictoffset__
__doc__
__flags__
__itemsize__
__module__
__mro__
__name__
__self__
__weakrefoffset__
__abs__
__add__
__and__
__call__
__cmp__
__coerce__
__complex__
__contains__
__del__
__delattr__
__delitem__
__delslice__
__div__
__divmod__
__eq__
__float__
__floordiv__
__ge__
__get__
__getattribute__
__getitem__
__getnewargs__
__getslice__
__gt__
__hash__
__hex__
__iadd__
__iand__
__idiv__
__ifloordiv__
__ilshift__
__imod__
__imul__
__init__
__int__
__invert__
__ior__
__ipow__
__irshift__
__isub__
__iter__
__itruediv__
__ixor__
__le__
__len__
__long__
__lshift__
__lt__
__mod__
__mul__
__ne__
__neg__
__new__
__nonzero__
__oct__
__or__
__pos__
__pow__
__radd__
__rand__
__rdiv__
__rdivmod__
__reduce__
__reduce_ex__
__repr__
__rfloordiv__
__rlshift__
__rmod__
__rmul__
__ror__
__rpow__
__rrshift__
__rshift__
__rsub__
__rtruediv__
__rxor__
__setattr__
__setitem__
__setslice__
__str__
__sub__
__subclasses__
__truediv__
__xor__
append
capitalize
center
clear
close
conjugate
copy
count
decode
encode
endswith
expandtabs
extend
fileno
find
flush
fromkeys
get
has_key
index
indices
insert
isalnum
isalpha
isatty
isdecimal
isdigit
islower
isnumeric
isspace
istitle
isupper
items
iteritems
iterkeys
itervalues
join
keys
ljust
lower
lstrip
mro
next
pop
popitem
read
readinto
readline
readlines
remove
replace
reverse
rfind
rindex
rjust
rstrip
seek
setdefault
sort
split
splitlines
startswith
strip
swapcase
tell
title
translate
truncate
update
upper
values
write
writelines
xreadlines
zfill
closed
co_argcount
co_cellvars
co_code
co_consts
co_filename
co_firstlineno
co_flags
co_freevars
co_lnotab
co_name
co_names
co_nlocals
co_stacksize
co_varnames
f_back
f_builtins
f_code
f_exc_traceback
f_exc_type
f_exc_value
f_globals
f_lasti
f_lineno
f_locals
f_restricted
f_trace
func_closure
func_code
func_defaults
func_dict
func_doc
func_globals
func_name
gi_frame
gi_running
im_class
im_func
im_self
imag
mode
name
newlines
real
softspace
start
step
stop
BooleanType
BufferType
BuiltinFunctionType
BuiltinMethodType
ClassType
CodeType
ComplexType
DictProxyType
DictType
DictionaryType
EllipsisType
FileType
FloatType
FrameType
FunctionType
GeneratorType
InstanceType
IntType
LambdaType
ListType
LongType
MethodType
ModuleType
NoneType
NotImplementedType
ObjectType
SliceType
StringType
StringTypes
TracebackType
TupleType
TypeType
UnboundMethodType
UnicodeType
XRangeType
__builtins__
__file__
"""
pythonbuiltinfunctions = pythonbuiltinfunctions_txt.split()
javakeywords_txt = """
abstract
boolean
break
byte
case
catch
char
class
continue
default
delegate
do
double
else
extends
false
final
finally
float
for
if
implements
import
instanceof
int
interface
long
native
new
null
package
private
protected
public
return
short
static
super
switch
synchronized
this
throw
throws
transient
true
try
void
volatile
while
goto
const
strictfp
"""
javakeywords = javakeywords_txt.split()
delphikeywords_txt = """
And
Array
As
Begin
Case
Class
Const
Constructor
Destructor
Div
Do
DownTo
Else
End
Except
File
Finally
For
System
Goto
If
Implementation
In
Inherited
Interface
System
Is
Mod
Not
System
Of
On
Or
Packed
System
System
System
Raise
Record
Repeat
Set
Shl
Shr
Then
ThreadVar
To
Try
Type
Unit
Until
Uses
Var
While
With
Xor
"""
delphikeywords = delphikeywords_txt.split()
delphikeywords = [
x.lower() for x in delphikeywords
] # delphi is case insensitive, so convert everything to lowercase for comparisons
# See Token.py in \python2x\Lib
TOKEN_MEANINGS_FORDOCO_ONLY = """
AMPER = 19
AMPEREQUAL = 42
AT = 50
BACKQUOTE = 25
CIRCUMFLEX = 33
CIRCUMFLEXEQUAL = 44
COLON = 11
COMMA = 12
COMMENT = 53
DEDENT = 6
DOT = 23
DOUBLESLASH = 48
DOUBLESLASHEQUAL = 49
DOUBLESTAR = 36
DOUBLESTAREQUAL = 47
ENDMARKER = 0
EQEQUAL = 28
EQUAL = 22
ERRORTOKEN = 52
GREATER = 21
GREATEREQUAL = 31
INDENT = 5
LBRACE = 26
LEFTSHIFT = 34
LEFTSHIFTEQUAL = 45
LESS = 20
LESSEQUAL = 30
LPAR = 7
LSQB = 9
MINEQUAL = 38
MINUS = 15
NAME = 1
NEWLINE = 4
NL = 54
NOTEQUAL = 29
NT_OFFSET = 256
NUMBER = 2
N_TOKENS = 55
OP = 51
PERCENT = 24
PERCENTEQUAL = 41
PLUS = 14
PLUSEQUAL = 37
RBRACE = 27
RIGHTSHIFT = 35
RIGHTSHIFTEQUAL = 46
RPAR = 8
RSQB = 10
SEMI = 13
SLASH = 17
SLASHEQUAL = 40
STAR = 16
STAREQUAL = 39
STRING = 3
TILDE = 32
VBAR = 18
VBAREQUAL = 43
"""

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,6 @@ class CopyServiceTestCase(TestCase):
"main_window",
"properties",
"undo_manager",
"import_menu",
"export_menu",
"tools_menu",
]

View File

@ -73,7 +73,7 @@ def hamburger_menu(hamburger_model):
return button
def create_hamburger_model(import_menu, export_menu, tools_menu):
def create_hamburger_model(export_menu, tools_menu):
model = Gio.Menu.new()
part = Gio.Menu.new()
@ -83,10 +83,6 @@ def create_hamburger_model(import_menu, export_menu, tools_menu):
part = Gio.Menu.new()
part.append(_("Save As..."), "win.file-save-as")
model.append_section(None, part)
part = Gio.Menu.new()
part.append_submenu(_("Import"), import_menu)
part.append_submenu(_("Export"), export_menu)
model.append_section(None, part)
@ -134,7 +130,6 @@ class MainWindow(Service, ActionProvider):
component_registry,
element_factory,
properties,
import_menu,
export_menu,
tools_menu,
):
@ -142,7 +137,6 @@ class MainWindow(Service, ActionProvider):
self.component_registry = component_registry
self.element_factory = element_factory
self.properties = properties
self.import_menu = import_menu
self.export_menu = export_menu
self.tools_menu = tools_menu
@ -213,9 +207,7 @@ class MainWindow(Service, ActionProvider):
header.pack_end(
hamburger_menu(
create_hamburger_model(
self.import_menu.menu, self.export_menu.menu, self.tools_menu.menu
)
create_hamburger_model(self.export_menu.menu, self.tools_menu.menu)
)
)
header.pack_end(button(_("Save"), "win.file-save"))

View File

@ -21,7 +21,6 @@ class DiagramPageTestCase(unittest.TestCase):
"diagrams",
"toolbox",
"elementeditor",
"import_menu",
"export_menu",
"tools_menu",
]

View File

@ -21,7 +21,6 @@ class DiagramToolboxTestCase(TestCase):
"element_factory",
"properties",
"main_window",
"import_menu",
"export_menu",
"tools_menu",
]

View File

@ -18,7 +18,6 @@ class DiagramItemConnectorTestCase(TestCase):
"namespace",
"diagrams",
"toolbox",
"import_menu",
"export_menu",
"tools_menu",
"elementeditor",

View File

@ -33,7 +33,6 @@ def application():
"diagrams",
"toolbox",
"elementeditor",
"import_menu",
"export_menu",
"tools_menu",
]
@ -114,7 +113,6 @@ class HandleToolTestCase(unittest.TestCase):
"diagrams",
"toolbox",
"elementeditor",
"import_menu",
"export_menu",
"tools_menu",
]

View File

@ -19,7 +19,6 @@ def application():
"diagrams",
"toolbox",
"elementeditor",
"import_menu",
"export_menu",
"tools_menu",
]

View File

@ -69,7 +69,6 @@ gaphorconvert = 'gaphor.tools.gaphorconvert:main'
"main_window" = "gaphor.ui.mainwindow:MainWindow"
"preferences" = "gaphor.ui.preferences:Preferences"
"export_menu" = "gaphor.ui.menufragment:MenuFragment"
"import_menu" = "gaphor.ui.menufragment:MenuFragment"
"tools_menu" = "gaphor.ui.menufragment:MenuFragment"
"copy" = "gaphor.services.copyservice:CopyService"
"sanitizer" = "gaphor.services.sanitizerservice:SanitizerService"
@ -81,8 +80,6 @@ gaphorconvert = 'gaphor.tools.gaphorconvert:main'
"elementeditor" = "gaphor.ui.elementeditor:ElementEditor"
"diagram_export" = "gaphor.plugins.diagramexport:DiagramExport"
"xmi_export" = "gaphor.plugins.xmiexport:XMIExport"
"diagram_layout" = "gaphor.plugins.diagramlayout:DiagramLayout"
"pynsource" = "gaphor.plugins.pynsource:PyNSource"
[build-system]
requires = ["poetry==1.0.0b2"]

View File

@ -100,7 +100,6 @@ setup(
"main_window = gaphor.ui.mainwindow:MainWindow",
"preferences = gaphor.ui.preferences:Preferences",
"export_menu = gaphor.ui.menufragment:MenuFragment",
"import_menu = gaphor.ui.menufragment:MenuFragment",
"tools_menu = gaphor.ui.menufragment:MenuFragment",
"copy = gaphor.services.copyservice:CopyService",
"sanitizer = gaphor.services.sanitizerservice:SanitizerService",
@ -112,8 +111,6 @@ setup(
"elementeditor = gaphor.ui.elementeditor:ElementEditor",
"diagram_export = gaphor.plugins.diagramexport:DiagramExport",
"xmi_export = gaphor.plugins.xmiexport:XMIExport",
"diagram_layout = gaphor.plugins.diagramlayout:DiagramLayout",
"pynsource = gaphor.plugins.pynsource:PyNSource",
],
},
cmdclass={

View File

@ -3,8 +3,6 @@ if __name__ == "__main__":
from gaphor import core
from gaphor.UML.elementfactory import ElementFactory
from gaphor.plugins.diagramexport import DiagramExport
from gaphor.plugins.diagramlayout import DiagramLayout
from gaphor.plugins.pynsource import PyNSource
from gaphor.plugins.xmiexport import XMIExport
from gaphor.services.componentregistry import ComponentRegistry
from gaphor.services.copyservice import CopyService