diff --git a/docs/installation_guide.rst b/docs/installation_guide.rst index efbf52f..21cdc8e 100644 --- a/docs/installation_guide.rst +++ b/docs/installation_guide.rst @@ -6,8 +6,8 @@ Dependencies The following software and modules are required to run dot2tex: -- Python_ 2.7 or 3.x. -- pyparsing_. Version 1.4.8 or later is recommended. +- Python_ 3.x. +- pyparsing_. Version 3.0 or later is required. - Graphviz_. A recent version is required. - preview_. A LaTeX package for extracting parts of a document. A free-standing part of the `preview-latex`_/`AUCTeX`_ bundle. - `PGF/TikZ`_ version 2.0 or later. diff --git a/dot2tex/pgfformat.py b/dot2tex/pgfformat.py index 0ded611..0fd598a 100644 --- a/dot2tex/pgfformat.py +++ b/dot2tex/pgfformat.py @@ -1,7 +1,7 @@ import logging -from .base import DotConvBase, parse_drawstring, get_drawobj_lblstyle -from .utils import smart_float, nsplit, getboolattr, tikzify +from .base import DotConvBase, get_drawobj_lblstyle, parse_drawstring +from .utils import getboolattr, nsplit, smart_float, tikzify log = logging.getLogger("dot2tex") @@ -171,13 +171,16 @@ def __init__(self, options=None): self.template = PGF210_TEMPLATE else: self.template = PGF_TEMPLATE - self.styles = dict(dashed='dashed', dotted='dotted', - bold='very thick', filled='fill', invis="", - rounded='rounded corners', ) - self.dashstyles = dict( - dashed=r'\pgfsetdash{{3pt}{3pt}}{0pt}', - dotted=r'\pgfsetdash{{\pgflinewidth}{2pt}}{0pt}', - bold=r'\pgfsetlinewidth{1.2pt}') + self.styles = { + 'dashed': 'dashed', 'dotted': 'dotted', + 'bold': 'very thick', 'filled': 'fill', 'invis': '', + 'rounded': 'rounded corners' + } + self.dashstyles = { + 'dashed': r'\pgfsetdash{{3pt}{3pt}}{0pt}', + 'dotted': r'\pgfsetdash{{\pgflinewidth}{2pt}}{0pt}', + 'bold': r'\pgfsetlinewidth{1.2pt}' + } def start_node(self, node): # Todo: Should find a more elegant solution @@ -267,12 +270,9 @@ def set_color(self, drawop): return s def set_style(self, drawop): - c, style = drawop + _, style = drawop pgfstyle = self.dashstyles.get(style, "") - if pgfstyle: - return " %s\n" % pgfstyle - else: - return "" + return f" {pgfstyle}\n" if pgfstyle else "" def filter_styles(self, style): filtered_styles = [] @@ -299,36 +299,27 @@ def draw_with_opacity(self, pathstr, fill=False): def draw_ellipse(self, drawop, style=None): op, x, y, w, h = drawop should_fill = (op == 'E') - - if style: - stylestr = " [%s]" % style - else: - stylestr = '' + stylestr = f" [{style}]" if style else '' return self.draw_with_opacity( - "%s (%sbp,%sbp) ellipse (%sbp and %sbp)" % ( - stylestr, smart_float(x), smart_float(y), - smart_float(w), smart_float(h)), - fill=should_fill) + "%s (%sbp,%sbp) ellipse (%sbp and %sbp)" % ( + stylestr, smart_float(x), smart_float(y), + smart_float(w), smart_float(h)), + fill=should_fill) def draw_polygon(self, drawop, style=None): op, points = drawop - pp = ['(%sbp,%sbp)' % (smart_float(p[0]), smart_float(p[1])) for p in points] - if op == 'P': - should_fill = True - else: - should_fill = False - - if style: - stylestr = " [%s]" % style - else: - stylestr = '' + pp = ['(%sbp,%sbp)' % (smart_float(p0), smart_float(p1)) + for p0, p1 in points] + should_fill = op == 'P' + stylestr = f" [{style}]" if style else '' return self.draw_with_opacity( "%s %s -- cycle" % (stylestr, " -- ".join(pp)), fill=should_fill) def draw_polyline(self, drawop, style=None): op, points = drawop - pp = ['(%sbp,%sbp)' % (smart_float(p[0]), smart_float(p[1])) for p in points] + pp = ['(%sbp,%sbp)' % (smart_float(p[0]), smart_float(p[1])) + for p in points] stylestr = '' return self.draw_with_opacity("%s %s" % (stylestr, " -- ".join(pp))) @@ -350,7 +341,7 @@ def draw_text(self, drawop, style=None): alignstr = "" # centered (default) styles.append(alignstr) styles.append(style) - lblstyle = ",".join([i for i in styles if i]) + lblstyle = ",".join(i for i in styles if i) if lblstyle: lblstyle = '[' + lblstyle + ']' s = " \\draw (%sbp,%sbp) node%s {%s};\n" % (smart_float(x), smart_float(y), lblstyle, text) @@ -448,10 +439,7 @@ def draw_edge(self, edge): # log.warning('label: %s', edgelabel) lblstyle = get_drawobj_lblstyle(edge) - if lblstyle: - lblstyle = '[' + lblstyle + ']' - else: - lblstyle = '' + lblstyle = ('[' + lblstyle + ']') if lblstyle else '' if edgelabel: extra = " node%s {%s}" % (lblstyle, edgelabel) src = pp[0] @@ -496,24 +484,21 @@ def get_node_preproc_code(self, node): text = node.attr.get('texlbl', '') if lblstyle: return " \\tikz \\node[%s] {%s};\n" % (lblstyle, text) - else: - return r"\tikz \node {" + text + "};" + return r"\tikz \node {" + text + "};" def get_edge_preproc_code(self, edge, attribute="texlbl"): lblstyle = edge.attr.get('lblstyle', '') text = edge.attr.get(attribute, '') if lblstyle: return " \\tikz \\node[%s] {%s};\n" % (lblstyle, text) - else: - return r"\tikz \node " + "{" + text + "};" + return r"\tikz \node " + "{" + text + "};" def get_graph_preproc_code(self, graph): lblstyle = graph.attr.get('lblstyle', '') text = graph.attr.get('texlbl', '') if lblstyle: return " \\tikz \\node[%s] {%s};\n" % (lblstyle, text) - else: - return r"\tikz \node {" + text + "};" + return r"\tikz \node {" + text + "};" TIKZ_TEMPLATE = r"""\documentclass{standalone} @@ -699,13 +684,16 @@ def __init__(self, options=None): self.template = TIKZ_TEMPLATE DotConvBase.__init__(self, options) - self.styles = dict(dashed='dashed', dotted='dotted', - bold='very thick', filled='fill', invis="", invisible="", - rounded='rounded corners', ) - self.dashstyles = dict( - dashed=r'\pgfsetdash{{3pt}{3pt}}{0pt}', - dotted=r'\pgfsetdash{{\pgflinewidth}{2pt}}{0pt}', - bold=r'\pgfsetlinewidth{1.2pt}') + self.styles = { + 'dashed': 'dashed', 'dotted': 'dotted', + 'bold': 'very thick', 'filled': 'fill', 'invis': '', + 'invisible': '', 'rounded': 'rounded corners' + } + self.dashstyles = { + 'dashed': r'\pgfsetdash{{3pt}{3pt}}{0pt}', + 'dotted': r'\pgfsetdash{{\pgflinewidth}{2pt}}{0pt}', + 'bold': r'\pgfsetlinewidth{1.2pt}' + } def set_options(self): Dot2PGFConv.set_options(self) @@ -784,10 +772,7 @@ def do_nodes(self): if not pos: continue x, y = pos.split(',') - if dotshape != 'point': - label = self.get_label(node) - else: - label = '' + label = '' if dotshape == 'point' else self.get_label(node) pos = "%sbp,%sbp" % (smart_float(x), smart_float(y)) style = node.attr.get('style') or "" @@ -942,10 +927,7 @@ def draw_edge(self, edge): edgelabel = self.get_label(edge) # log.warning('label: %s', edgelabel) lblstyle = get_drawobj_lblstyle(edge, extra_styles=edge.attr.get('exstyle')) - if lblstyle: - lblstyle = '[' + lblstyle + ']' - else: - lblstyle = '' + lblstyle = ('[' + lblstyle + ']') if lblstyle else '' if edgelabel: extra = " node%s {%s}" % (lblstyle, edgelabel) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..55bb56f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,41 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "dot2tex" +version = '2.12.0' + +description = "A Graphviz to LaTeX converter" +readme = "readme.md" +requires-python = ">=3.7" +dependencies = [ + "pyparsing >= 3.0", +] +authors = [ + {name = "Kjell Magne Fauske", email = "kjellmf@gmail.com"}, +] +license = "MIT" + +classifiers = [ + 'Development Status :: 4 - Beta', + 'Environment :: Console', + 'Intended Audience :: Science/Research', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 3', + 'Topic :: Scientific/Engineering :: Visualization', + 'Topic :: Text Processing :: Markup :: LaTeX', + 'Topic :: Utilities', +] + +[project.urls] +"Homepage" = "https://github.com/sagemath/dot2tex" + +[project.scripts] +dot2tex = "dot2tex.dot2tex:main" + +[project.optional-dependencies] +test = [ + "pytest", +] diff --git a/readme.md b/readme.md index ff64615..0d8d321 100644 --- a/readme.md +++ b/readme.md @@ -22,8 +22,7 @@ Before you install dot2tex you have to have a working Python environment installed on your system. Dot2tex works with Python 3. In addition you will need the following modules: -* [pyparsing](https://github.com/pyparsing/pyparsing). A recent version is required. - Older version like for instance 1.3.2 does not work with dot2tex. +* [pyparsing](https://github.com/pyparsing/pyparsing). A recent version is required, at least version 3.0. * [preview](https://ctan.org/pkg/preview) A stand-alone part of the preview-latex bundle. Required for preprocessing graphs with LaTeX. diff --git a/setup.py b/setup.py deleted file mode 100644 index b273f17..0000000 --- a/setup.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python -from setuptools import setup - -setup(name='dot2tex', - version='2.12.0', - description='A Graphviz to LaTeX converter', - long_description="""\ -The purpose of dot2tex is to give graphs generated by the graph layout tool -Graphviz_, a more LaTeX friendly look and feel. This is accomplished by: - -- Using native PSTricks_ and `PGF/TikZ`_ commands for drawing arrows, - edges and nodes. -- Typesetting labels with LaTeX, allowing mathematical notation. -- Using backend specific styles to customize the output. - -.. _Graphviz: http://www.graphviz.org/ -.. _PSTricks: http://tug.org/PSTricks/main.cgi/ -.. _PGF/TikZ: http://www.ctan.org/tex-archive/help/Catalogue/entries/pgf.html -""", - author='Kjell Magne Fauske', - author_email='kjellmf@gmail.com', - url="https://github.com/sagemath/dot2tex", - packages=['dot2tex'], - classifiers=[ - 'Development Status :: 4 - Beta', - 'Environment :: Console', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: MIT License', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'Programming Language :: Python :: 3', - 'Topic :: Scientific/Engineering :: Visualization', - 'Topic :: Text Processing :: Markup :: LaTeX', - 'Topic :: Utilities', - ], - install_requires=['pyparsing', 'setuptools'], - entry_points={ - 'console_scripts': [ - 'dot2tex = dot2tex.dot2tex:main', - ] - - } -) diff --git a/tests/experimental/test_comparedotparsing.py b/tests/experimental/test_comparedotparsing.py index ccd188d..b2503a1 100644 --- a/tests/experimental/test_comparedotparsing.py +++ b/tests/experimental/test_comparedotparsing.py @@ -127,7 +127,7 @@ def test_subgraphs(self): def test_quoting(self): self.assertEqual(test_dotfile("quoting.dot"), 1) - # the following three files are missing, so desactivate + # the following three files are missing, so deactivate # def test_unicode1(self): # self.assertEqual(test_dotfile("unicode1.dot"), 1) diff --git a/tests/test_dotparsing.py b/tests/test_dotparsing.py index 1ca52a2..8eaa67e 100644 --- a/tests/test_dotparsing.py +++ b/tests/test_dotparsing.py @@ -51,11 +51,10 @@ def test_create_with_attributes(self): self.assertEqual(g.attr['style'], 'fancy') self.assertEqual(g.attr['label'], 'testgraph') - def test_add_node(self): g = dotp.DotGraph() - na = g.add_node('a') - nb = g.add_node('b') + _ = g.add_node('a') + _ = g.add_node('b') self.assertEqual(len(list(g.nodes)), 2) ## @@ -79,8 +78,8 @@ def test_add_nonstring_nodes(self): def test_add_edge(self): g = dotp.DotGraph() - e = g.add_edge('a', 'b', label='test') - e2 = g.add_edge('a', 'd', label='test2') + _ = g.add_edge('a', 'b', label='test') + _ = g.add_edge('a', 'd', label='test2') self.assertEqual(len(g), 3) self.assertEqual(len(g.edges), 2) @@ -142,157 +141,153 @@ def test_add_default_node_subgraph(self): self.assertFalse('style' in nn.attr) -## -## -## def test_addequalnodes2(self): -## g = dotp.DotGraph() -## n = dotp.DotNode('a',label="test1") -## g.add_node(n) -## n = dotp.DotNode('a',style="filled") -## g.add_node(n) -## g.add_node('a',label="test2",texmode="math") -## n = g.get_node('a') -## self.assertEqual(len(g.nodes),1) -## self.assertEqual(len(n.attributes),3) -## self.assertEqual(n.attributes['label'],'test2') -## self.assertEqual(set(n.attributes.keys()),set(['label','style','texmode'])) -## -## def test_getnodes(self): -## g = dotp.DotGraph() -## n = g.get_node('a') -## self.assertEqual(n,None) -## g.add_node('a') -## n = g.get_node('a') -## self.assertEqual(n.name,'a') -## -## def test_addedge(self): -## g = dotp.DotGraph(graph_type='digraph') -## e1 = g.add_edge('a','b',label='a->b') -## e2 = g.add_edge('b','a',label='b->a') -## self.assertEqual(len(g.nodes),2) -## self.assertEqual(len(g.edges),2) -## self.assertEqual(e1.attributes['label'],'a->b') -## self.assertEqual(e2.attributes['label'],'b->a') -## -## -## -## -## -## -## def test_strictgraph(self): -## """A strict graph have to ignore duplicate edges""" -## g = dotp.DotGraph(strict=True) -## g.add_edge('a','b') -## g.add_edge('a','b') -## self.assertEqual(len(g.edges),1) -## -## def test_addsubgraphs(self): -## g = dotp.DotGraph() -## g.add_edge('a','b') -## n = g.get_node('a') -## self.assertEqual(n.parent,g) -## gs = g.add_subgraph('clusterG') -## gs.add_edge('a','b') -## #n = g.get_node('a') -## self.assertEqual(n.parent,gs) -## n = g.add_node('a',label="test") -## -## self.assertEqual(n.parent,gs) -## -## def test_graphlevels(self): -## g = dotp.DotGraph() -## e = g.add_edge('a','b') -## self.assertEqual(g.level,0) -## gs1 = g.add_subgraph('clusterG') -## self.assertEqual(gs1.level,1) -## gs2 = g.add_subgraph('clusterGG') -## self.assertEqual(gs2.level,1) -## gs12 = gs1.add_subgraph('ClusterG2') -## self.assertEqual(gs12.level,2) -## e = gs12.add_edge('c','d') -## self.assertEqual(e.src.parent.level,2) -## self.assertEqual(e.parent.level,2) -## -## def test_addedgesubgraphs(self): -## """ -## digraph G { -## a -> b; -## subgraph clusterG { -## a -> b; -## } -## subgraph clusterGG { -## a -> b; -## } -## -##strict digraph G { -## node [label="\N"]; -## graph [bb="0,0,86,140"]; -## subgraph clusterG { -## graph [bb="8,8,78,132"]; -## a [pos="43,106", width="0.75", height="0.50"]; -## b [pos="43,34", width="0.75", height="0.50"]; -## a -> b [pos="e,43,52 43,88 43,80 43,71 43,62"]; -## } -##} -## }""" -## g = dotp.DotGraph() -## e1 = g.add_edge('a','b') -## self.assertEqual(e1.parent.name,g.name) -## gs = g.add_subgraph('clusterG') -## e2 = gs.add_edge('a','b') -## self.assertEqual(e2.parent.name,gs.name) -## self.assertEqual(e1.parent.name,gs.name) -## gs2 = g.add_subgraph('clusterGG') -## e3 = gs2.add_edge('a','b') -## self.assertEqual(e2.parent.name,gs.name) -## self.assertEqual(e3.parent.name,gs.name) -## self.assertEqual(len(gs2.edges),3) -## -## def test_add_single_edge_to_subgraph(self): -## """Edge should belong to subgraph""" -## g = dotp.DotGraph() -## gs = g.add_subgraph('clusterG') -## na = gs.add_node('a') -## nb = gs.add_node('b') -## edge = g.add_edge('a','b') -## self.assertEqual(na.parent,gs) -## self.assertEqual(nb.parent,gs) -## self.assertEqual(edge.parent.name,gs.name) -## -## def test_add_multiple_edges_to_subgraph(self): -## """Edges should belong to subgraph""" -## g = dotp.DotGraph() -## gs = g.add_subgraph('clusterG') -## na = gs.add_node('a') -## nb = gs.add_node('b') -## edge = g.add_edge('a','b') -## self.assertEqual(edge.parent.name,gs.name) -## edge2 = g.add_edge('a','b') -## self.assertEqual(edge2.parent.name,gs.name) -## -## def test_change_edge_parent(self): -## """If edge nodes change parents, so should the edge""" -## g = dotp.DotGraph() -## edge = g.add_edge('a','b') -## gs = g.add_subgraph('clusterG') -## na = gs.add_node('a') -## nb = gs.add_node('b') -## self.assertEqual(edge.parent.name,gs.name) -## -## -## -## -## -## -## -## -## -## -## - - - - +# +# +# def test_addequalnodes2(self): +# g = dotp.DotGraph() +# n = dotp.DotNode('a',label="test1") +# g.add_node(n) +# n = dotp.DotNode('a',style="filled") +# g.add_node(n) +# g.add_node('a',label="test2",texmode="math") +# n = g.get_node('a') +# self.assertEqual(len(g.nodes),1) +# self.assertEqual(len(n.attributes),3) +# self.assertEqual(n.attributes['label'],'test2') +# self.assertEqual(set(n.attributes.keys()),set(['label','style','texmode'])) +# +# def test_getnodes(self): +# g = dotp.DotGraph() +# n = g.get_node('a') +# self.assertEqual(n,None) +# g.add_node('a') +# n = g.get_node('a') +# self.assertEqual(n.name,'a') +# +# def test_addedge(self): +# g = dotp.DotGraph(graph_type='digraph') +# e1 = g.add_edge('a','b',label='a->b') +# e2 = g.add_edge('b','a',label='b->a') +# self.assertEqual(len(g.nodes),2) +# self.assertEqual(len(g.edges),2) +# self.assertEqual(e1.attributes['label'],'a->b') +# self.assertEqual(e2.attributes['label'],'b->a') +# +# +# +# +# +# +# def test_strictgraph(self): +# """A strict graph have to ignore duplicate edges""" +# g = dotp.DotGraph(strict=True) +# g.add_edge('a','b') +# g.add_edge('a','b') +# self.assertEqual(len(g.edges),1) +# +# def test_addsubgraphs(self): +# g = dotp.DotGraph() +# g.add_edge('a','b') +# n = g.get_node('a') +# self.assertEqual(n.parent,g) +# gs = g.add_subgraph('clusterG') +# gs.add_edge('a','b') +# #n = g.get_node('a') +# self.assertEqual(n.parent,gs) +# n = g.add_node('a',label="test") +# +# self.assertEqual(n.parent,gs) +# +# def test_graphlevels(self): +# g = dotp.DotGraph() +# e = g.add_edge('a','b') +# self.assertEqual(g.level,0) +# gs1 = g.add_subgraph('clusterG') +# self.assertEqual(gs1.level,1) +# gs2 = g.add_subgraph('clusterGG') +# self.assertEqual(gs2.level,1) +# gs12 = gs1.add_subgraph('ClusterG2') +# self.assertEqual(gs12.level,2) +# e = gs12.add_edge('c','d') +# self.assertEqual(e.src.parent.level,2) +# self.assertEqual(e.parent.level,2) +# +# def test_addedgesubgraphs(self): +# """ +# digraph G { +# a -> b; +# subgraph clusterG { +# a -> b; +# } +# subgraph clusterGG { +# a -> b; +# } +# +# strict digraph G { +# node [label="\N"]; +# graph [bb="0,0,86,140"]; +# subgraph clusterG { +# graph [bb="8,8,78,132"]; +# a [pos="43,106", width="0.75", height="0.50"]; +# b [pos="43,34", width="0.75", height="0.50"]; +# a -> b [pos="e,43,52 43,88 43,80 43,71 43,62"]; +# } +# } +# }""" +# g = dotp.DotGraph() +# e1 = g.add_edge('a','b') +# self.assertEqual(e1.parent.name,g.name) +# gs = g.add_subgraph('clusterG') +# e2 = gs.add_edge('a','b') +# self.assertEqual(e2.parent.name,gs.name) +# self.assertEqual(e1.parent.name,gs.name) +# gs2 = g.add_subgraph('clusterGG') +# e3 = gs2.add_edge('a','b') +# self.assertEqual(e2.parent.name,gs.name) +# self.assertEqual(e3.parent.name,gs.name) +# self.assertEqual(len(gs2.edges),3) +# +# def test_add_single_edge_to_subgraph(self): +# """Edge should belong to subgraph""" +# g = dotp.DotGraph() +# gs = g.add_subgraph('clusterG') +# na = gs.add_node('a') +# nb = gs.add_node('b') +# edge = g.add_edge('a','b') +# self.assertEqual(na.parent,gs) +# self.assertEqual(nb.parent,gs) +# self.assertEqual(edge.parent.name,gs.name) +# +# def test_add_multiple_edges_to_subgraph(self): +# """Edges should belong to subgraph""" +# g = dotp.DotGraph() +# gs = g.add_subgraph('clusterG') +# na = gs.add_node('a') +# nb = gs.add_node('b') +# edge = g.add_edge('a','b') +# self.assertEqual(edge.parent.name,gs.name) +# edge2 = g.add_edge('a','b') +# self.assertEqual(edge2.parent.name,gs.name) +# +# def test_change_edge_parent(self): +# """If edge nodes change parents, so should the edge""" +# g = dotp.DotGraph() +# edge = g.add_edge('a','b') +# gs = g.add_subgraph('clusterG') +# na = gs.add_node('a') +# nb = gs.add_node('b') +# self.assertEqual(edge.parent.name,gs.name) +# +# +# +# +# +# +# +# +# +# +# if __name__ == '__main__':