diff --git a/README.md b/README.md new file mode 100644 index 0000000..0ba2395 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# Code skeleton + +This project gives a simple tool to construct class diagrams from pseudo-code + +## Installation + +The easiest is through pip, that max need to be installed: + +```bash +sudo apt-get install python3-pip +``` + +Then for a user installation (recommended): +```bash +pip3 install --user git+https://gitlab.com/ganciaux/code_skeleton.git +``` diff --git a/scripts/class_dumper_dot.py b/scripts/class_dumper_dot.py index 8aa44d9..e5513e0 100755 --- a/scripts/class_dumper_dot.py +++ b/scripts/class_dumper_dot.py @@ -1,306 +1,306 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ @author Guillaume Anciaux @brief Module used to produces a GraphViz (.dot) file @section LICENCE This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ ################################################################ import argparse import subprocess import os from code_skeleton.class_dumper import ClassDumper ################################################################ __author__ = "Guillaume Anciaux" __copyright__ = "Copyright EPFL" __credits__ = ["Guillaume Anciaux"] __license__ = "GPL" __version__ = "1.0.0" __maintainer__ = "Guillaume Anciaux" __email__ = "guillaume.anciaux@epfl.ch" __status__ = "Beta" ################################################################ def _protect_str(string): return string.replace('<', r'\<').replace('>', r'\>') ################################################################ class ClassDumperDOT(ClassDumper): " Dumper to DOT (GraphViz format) " def __init__(self, output_file, inheritance_flag=True, collaboration_flag=True): if not isinstance(output_file, str): raise Exception('invalid filename: {0}'.format(output_file)) ClassDumper.__init__(self) self.encaps_symbol = {'public': '+', 'private': '-', 'protected': '#'} self.inheritance_flag = inheritance_flag self.collaboration_flag = collaboration_flag self.output_file = output_file def dump_classes(self, classes, **kwargs): fout = open(self.output_file, 'w') sstr = 'digraph "test"\n{' sstr += """ edge [fontname="Helvetica",fontsize="10", labelfontname="Helvetica", labelfontsize="10"]; node [fontname="Helvetica",fontsize="10",shape=record]; """ fout.write(sstr) for _class in classes: self.dump_class(_class, fout) fout.write("}") fout.close() def dump_class(self, _class, _file): - " dumps a class into the provided file " sstr = self._format_class_declaration(_class) sstr += self._format_constructors(_class) sstr += self._format_methods(_class) sstr += self._format_members(_class) sstr += '}"];\n' if self.inheritance_flag: sstr += self._format_inheritance(_class) if self.collaboration_flag: sstr += self._format_compositions(_class) sstr += self._format_types(_class) _file.write(sstr) return sstr @classmethod def _format_class_declaration(cls, _class): sstr = '"{0}" '.format(_class.name) sstr += '[label="{' + format(_class.name) + "\\n" return sstr @classmethod def _format_inheritance(cls, _class): if _class.inheritance is not None: sstr = "" for mother in _class.inheritance: sstr += ('"{0}" '.format(mother) + " -> " '"{0}" '.format(_class.name)) sstr += ('[style="solid",color="midnightblue",' 'fontname="Helvetica"' ',arrowtail="onormal",fontsize="10",dir="back"];\n') return sstr return "" def _format_constructors(self, _class): sstr = "" for encaps in ['public', 'private', 'protected']: meths = _class.get_methods(encaps) if _class.name in meths: if sstr == "": sstr = "|" sstr += self.encaps_symbol[encaps] + " " for cons in meths[_class.name]: sstr += self._format_method(cons) sstr += "\\l" if '~' + _class.name in meths: if sstr == "": sstr = "|" sstr += self.encaps_symbol[encaps] + " " for cons in meths['~' + _class.name]: sstr += self._format_method(cons) sstr += "\\l" return sstr def _format_methods(self, _class): sstr = "" for encaps in ['public', 'private', 'protected']: meths = _class.get_methods(encaps) meths_names = set(meths.keys()) - set([_class.name, '~' + _class.name]) meths_names = list(meths_names) - if len(meths_names) is not 0: + if len(meths_names) != 0: for _name in meths_names: for meth in meths[_name]: if sstr == "": sstr = "|" sstr += self.encaps_symbol[encaps] + " " sstr += self._format_method(meth) sstr += "\\l" return sstr def _format_members(self, _class): sstr = "" for encaps in ['public', 'private', 'protected']: membs = _class.get_members(encaps) - if len(membs) is not 0: + if len(membs) != 0: try: _iter = membs.iteritems() except Exception as ex: _iter = membs.items() for dummy_name, memb in _iter: if sstr == "": sstr = "|" sstr += self.encaps_symbol[encaps] + " " sstr += self._format_member(memb) sstr += "\\l" return sstr def _format_compositions(self, _class): composition_set = set() for encaps in ['public', 'private', 'protected']: membs = _class.get_members(encaps) - if len(membs) is not 0: + if len(membs) != 0: try: _iter = membs.iteritems() except Exception as ex: _iter = membs.items() for dummy_name, memb in _iter: if memb.type in self.base_types: continue composition_set.add(self.base_type(memb.type)) sstr = "" for comp in composition_set: - sstr += '"{0}" '.format(comp) + " -> " + '"{0}" '.format(_class.name) + sstr += '"{0}" '.format(comp) + " -> " + \ + '"{0}" '.format(_class.name) sstr += ('[style="dashed",color="midnightblue",' 'fontname="Helvetica",arrowtail="odiamond",' 'fontsize="10",dir="back"];\n') return sstr def _format_types(self, _class): sstr = "" for encaps in ['public', 'private', 'protected']: if _class.types[encaps] is not None: for _type in _class.types[encaps]: if _type in self.base_types: continue sstr += '"{0}" '.format(_class.name) + " -> " \ + '"{0}" '.format(_type) sstr += ('[style="solid",color="black",' 'fontname="Helvetica",arrowtail="odiamond",' 'fontsize="10",dir="back"];\n') return sstr @classmethod def _format_method(cls, meth): arg_types = [_protect_str(a) for a, dummy_b in meth.args] sstr = "" if meth.virtual == 'virtual' or\ meth.virtual == 'pure virtual': sstr += 'virtual ' if meth.static: sstr += meth.static + " " if meth.ret: sstr += _protect_str(meth.ret) + " " sstr += meth.name + "(" + ",".join(arg_types) + ")" if meth.virtual == 'pure virtual': sstr += "=0" return sstr @classmethod def _format_member(cls, memb): sstr = "" if memb.static == 'static': sstr += 'static ' return sstr + _protect_str(memb.type) + " " + memb.name ################################################################ def main(): parser = argparse.ArgumentParser( description='DOT graph producer for class representation') parser.add_argument('--class_file', '-c', help='The class file to process', required=True) parser.add_argument('--format', '-f', default="pdf", help='The format of the produced graph file') parser.add_argument('--output', '-o', help='The file to be produced') parser.add_argument('--collaboration_no', action='store_false', help='Disable the collaboration output') parser.add_argument('--inheritance_no', action='store_false', help='Disable the inheritance output') parser.add_argument('--class_filter', type=str, help='The classes to output') args = parser.parse_args() args = vars(args) if args["output"] is None: args['output'] = os.path.splitext(args['class_file'])[0] +\ - "." + args['format'] + "." + args['format'] if args["class_filter"] is not None: args["class_filter"] = args["class_filter"].split(',') inheritance_flag = True collaboration_flag = True if not args['inheritance_no']: inheritance_flag = False if not args['collaboration_no']: collaboration_flag = False class_file = args['class_file'] del args['class_file'] dot_file = os.path.splitext(class_file)[0] + ".dot" dumper_class = ClassDumperDOT(dot_file, inheritance_flag, collaboration_flag) dumper_class.dump(class_file, **args) exe = ['dot'] option_format = ['-T'+args['format']] option_output = ['-o', args['output']] option_input = [dot_file] subprocess.call(exe+option_format+option_output+option_input) if __name__ == '__main__': main()