Source code for logilab.common.vcgutils

# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of logilab-common.
#
# logilab-common is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option) any
# later version.
#
# logilab-common 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with logilab-common.  If not, see <http://www.gnu.org/licenses/>.
"""Functions to generate files readable with Georg Sander's vcg
(Visualization of Compiler Graphs).

You can download vcg at http://rw4.cs.uni-sb.de/~sander/html/gshome.html
Note that vcg exists as a debian package.

See vcg's documentation for explanation about the different values that
maybe used for the functions parameters.




"""
__docformat__ = "restructuredtext en"

import string

ATTRS_VAL = {
    "algos": (
        "dfs",
        "tree",
        "minbackward",
        "left_to_right",
        "right_to_left",
        "top_to_bottom",
        "bottom_to_top",
        "maxdepth",
        "maxdepthslow",
        "mindepth",
        "mindepthslow",
        "mindegree",
        "minindegree",
        "minoutdegree",
        "maxdegree",
        "maxindegree",
        "maxoutdegree",
    ),
    "booleans": ("yes", "no"),
    "colors": (
        "black",
        "white",
        "blue",
        "red",
        "green",
        "yellow",
        "magenta",
        "lightgrey",
        "cyan",
        "darkgrey",
        "darkblue",
        "darkred",
        "darkgreen",
        "darkyellow",
        "darkmagenta",
        "darkcyan",
        "gold",
        "lightblue",
        "lightred",
        "lightgreen",
        "lightyellow",
        "lightmagenta",
        "lightcyan",
        "lilac",
        "turquoise",
        "aquamarine",
        "khaki",
        "purple",
        "yellowgreen",
        "pink",
        "orange",
        "orchid",
    ),
    "shapes": ("box", "ellipse", "rhomb", "triangle"),
    "textmodes": ("center", "left_justify", "right_justify"),
    "arrowstyles": ("solid", "line", "none"),
    "linestyles": ("continuous", "dashed", "dotted", "invisible"),
}

# meaning of possible values:
#   O    -> string
#   1    -> int
#   list -> value in list
GRAPH_ATTRS = {
    "title": 0,
    "label": 0,
    "color": ATTRS_VAL["colors"],
    "textcolor": ATTRS_VAL["colors"],
    "bordercolor": ATTRS_VAL["colors"],
    "width": 1,
    "height": 1,
    "borderwidth": 1,
    "textmode": ATTRS_VAL["textmodes"],
    "shape": ATTRS_VAL["shapes"],
    "shrink": 1,
    "stretch": 1,
    "orientation": ATTRS_VAL["algos"],
    "vertical_order": 1,
    "horizontal_order": 1,
    "xspace": 1,
    "yspace": 1,
    "layoutalgorithm": ATTRS_VAL["algos"],
    "late_edge_labels": ATTRS_VAL["booleans"],
    "display_edge_labels": ATTRS_VAL["booleans"],
    "dirty_edge_labels": ATTRS_VAL["booleans"],
    "finetuning": ATTRS_VAL["booleans"],
    "manhattan_edges": ATTRS_VAL["booleans"],
    "smanhattan_edges": ATTRS_VAL["booleans"],
    "port_sharing": ATTRS_VAL["booleans"],
    "edges": ATTRS_VAL["booleans"],
    "nodes": ATTRS_VAL["booleans"],
    "splines": ATTRS_VAL["booleans"],
}
NODE_ATTRS = {
    "title": 0,
    "label": 0,
    "color": ATTRS_VAL["colors"],
    "textcolor": ATTRS_VAL["colors"],
    "bordercolor": ATTRS_VAL["colors"],
    "width": 1,
    "height": 1,
    "borderwidth": 1,
    "textmode": ATTRS_VAL["textmodes"],
    "shape": ATTRS_VAL["shapes"],
    "shrink": 1,
    "stretch": 1,
    "vertical_order": 1,
    "horizontal_order": 1,
}
EDGE_ATTRS = {
    "sourcename": 0,
    "targetname": 0,
    "label": 0,
    "linestyle": ATTRS_VAL["linestyles"],
    "class": 1,
    "thickness": 0,
    "color": ATTRS_VAL["colors"],
    "textcolor": ATTRS_VAL["colors"],
    "arrowcolor": ATTRS_VAL["colors"],
    "backarrowcolor": ATTRS_VAL["colors"],
    "arrowsize": 1,
    "backarrowsize": 1,
    "arrowstyle": ATTRS_VAL["arrowstyles"],
    "backarrowstyle": ATTRS_VAL["arrowstyles"],
    "textmode": ATTRS_VAL["textmodes"],
    "priority": 1,
    "anchor": 1,
    "horizontal_order": 1,
}


# Misc utilities ###############################################################


[docs]def latin_to_vcg(st): """Convert latin characters using vcg escape sequence.""" for char in st: if char not in string.ascii_letters: try: num = ord(char) if num >= 192: st = st.replace(char, r"\fi%d" % ord(char)) except Exception: pass return st
[docs]class VCGPrinter: """A vcg graph writer.""" def __init__(self, output_stream): self._stream = output_stream self._indent = ""
[docs] def open_graph(self, **args): """open a vcg graph""" self._stream.write("%sgraph:{\n" % self._indent) self._inc_indent() self._write_attributes(GRAPH_ATTRS, **args)
[docs] def close_graph(self): """close a vcg graph""" self._dec_indent() self._stream.write("%s}\n" % self._indent)
[docs] def node(self, title, **args): """draw a node""" self._stream.write(f'{self._indent}node: {{title:"{title}"') self._write_attributes(NODE_ATTRS, **args) self._stream.write("}\n")
[docs] def edge(self, from_node, to_node, edge_type="", **args): """draw an edge from a node to another.""" self._stream.write( '%s%sedge: {sourcename:"%s" targetname:"%s"' % (self._indent, edge_type, from_node, to_node) ) self._write_attributes(EDGE_ATTRS, **args) self._stream.write("}\n")
# private ################################################################## def _write_attributes(self, attributes_dict, **args): """write graph, node or edge attributes""" for key, value in args.items(): try: _type = attributes_dict[key] except KeyError: raise Exception( f"""no such attribute {key} possible attributes are {attributes_dict.keys()}""" ) if not _type: self._stream.write(f'{self._indent}{key}:"{value}"\n') elif _type == 1: self._stream.write(f"{self._indent}{key}:{int(value)}\n") elif value in _type: self._stream.write(f"{self._indent}{key}:{value}\n") else: raise Exception( f"""value {value} isn't correct for attribute {key} correct values are {_type}""" ) def _inc_indent(self): """increment indentation""" self._indent = f" {self._indent}" def _dec_indent(self): """decrement indentation""" self._indent = self._indent[:-2]