bee_python 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +202 -0
- data/build/README +23 -0
- data/egg/egg/build.yml +70 -0
- data/egg/egg/ez_setup.py +280 -0
- data/egg/egg/script.py +12 -0
- data/egg/egg/setup.erb +11 -0
- data/egg/egg/suite.py +37 -0
- data/egg/egg/test.py +31 -0
- data/egg/egg.yml +69 -0
- data/egg/http/build.erb +53 -0
- data/egg/http/server.py +34 -0
- data/egg/http.yml +50 -0
- data/egg/mysql/mysql.py +43 -0
- data/egg/mysql.yml +44 -0
- data/egg/project/build.erb +77 -0
- data/egg/project/script.py +42 -0
- data/egg/project/test.py +31 -0
- data/egg/project.yml +59 -0
- data/egg/script/build.erb +35 -0
- data/egg/script/script.py +42 -0
- data/egg/script.yml +50 -0
- data/egg/soap/build.erb +52 -0
- data/egg/soap/client.py +18 -0
- data/egg/soap/server.py +30 -0
- data/egg/soap.yml +58 -0
- data/egg/source/source.py +42 -0
- data/egg/source.yml +47 -0
- data/egg/test/test.py +28 -0
- data/egg/test.yml +44 -0
- data/egg/xmlrpc/build.erb +52 -0
- data/egg/xmlrpc/client.py +22 -0
- data/egg/xmlrpc/server.py +24 -0
- data/egg/xmlrpc.yml +51 -0
- data/lib/bee_task_python.rb +390 -0
- data/test/build.yml +16 -0
- data/test/tc_bee_task_python.rb +27 -0
- data/test/test_build.rb +26 -0
- data/test/test_build_listener.rb +62 -0
- data/test/ts_bee_python.rb +26 -0
- data/tools/common/__init__.py +5 -0
- data/tools/common/common/__init__.py +140 -0
- data/tools/common/common/__pkginfo__.py +43 -0
- data/tools/common/common/adbh.py +35 -0
- data/tools/common/common/cache.py +114 -0
- data/tools/common/common/changelog.py +234 -0
- data/tools/common/common/clcommands.py +181 -0
- data/tools/common/common/cli.py +212 -0
- data/tools/common/common/compat.py +328 -0
- data/tools/common/common/configuration.py +1087 -0
- data/tools/common/common/contexts.py +58 -0
- data/tools/common/common/corbautils.py +117 -0
- data/tools/common/common/daemon.py +171 -0
- data/tools/common/common/date.py +279 -0
- data/tools/common/common/db.py +49 -0
- data/tools/common/common/dbf.py +229 -0
- data/tools/common/common/debugger.py +208 -0
- data/tools/common/common/decorators.py +190 -0
- data/tools/common/common/deprecation.py +118 -0
- data/tools/common/common/fileutils.py +409 -0
- data/tools/common/common/graph.py +259 -0
- data/tools/common/common/html.py +142 -0
- data/tools/common/common/interface.py +76 -0
- data/tools/common/common/logging_ext.py +166 -0
- data/tools/common/common/modutils.py +670 -0
- data/tools/common/common/optik_ext.py +383 -0
- data/tools/common/common/optparser.py +92 -0
- data/tools/common/common/pdf_ext.py +111 -0
- data/tools/common/common/proc.py +276 -0
- data/tools/common/common/pyro_ext.py +146 -0
- data/tools/common/common/pytest.py +754 -0
- data/tools/common/common/shellutils.py +383 -0
- data/tools/common/common/sphinx_ext.py +87 -0
- data/tools/common/common/sphinxutils.py +122 -0
- data/tools/common/common/sqlgen.py +31 -0
- data/tools/common/common/table.py +930 -0
- data/tools/common/common/tasksqueue.py +97 -0
- data/tools/common/common/test/__init__.py +1 -0
- data/tools/common/common/test/data/ChangeLog +184 -0
- data/tools/common/common/test/data/MyPyPa-0.1.0-py2.5.egg +0 -0
- data/tools/common/common/test/data/__init__.py +1 -0
- data/tools/common/common/test/data/content_differ_dir/NOTHING +0 -0
- data/tools/common/common/test/data/content_differ_dir/README +1 -0
- data/tools/common/common/test/data/content_differ_dir/subdir/coin +1 -0
- data/tools/common/common/test/data/content_differ_dir/subdir/toto.txt +53 -0
- data/tools/common/common/test/data/file_differ_dir/NOTHING +0 -0
- data/tools/common/common/test/data/file_differ_dir/README +1 -0
- data/tools/common/common/test/data/file_differ_dir/subdir/toto.txt +53 -0
- data/tools/common/common/test/data/file_differ_dir/subdirtwo/Hello +0 -0
- data/tools/common/common/test/data/find_test/__init__.py +0 -0
- data/tools/common/common/test/data/find_test/foo.txt +0 -0
- data/tools/common/common/test/data/find_test/module.py +0 -0
- data/tools/common/common/test/data/find_test/module2.py +0 -0
- data/tools/common/common/test/data/find_test/newlines.txt +0 -0
- data/tools/common/common/test/data/find_test/noendingnewline.py +0 -0
- data/tools/common/common/test/data/find_test/nonregr.py +0 -0
- data/tools/common/common/test/data/find_test/normal_file.txt +0 -0
- data/tools/common/common/test/data/find_test/spam.txt +0 -0
- data/tools/common/common/test/data/find_test/sub/doc.txt +0 -0
- data/tools/common/common/test/data/find_test/sub/momo.py +0 -0
- data/tools/common/common/test/data/find_test/test.ini +0 -0
- data/tools/common/common/test/data/find_test/test1.msg +0 -0
- data/tools/common/common/test/data/find_test/test2.msg +0 -0
- data/tools/common/common/test/data/find_test/write_protected_file.txt +0 -0
- data/tools/common/common/test/data/foo.txt +9 -0
- data/tools/common/common/test/data/module.py +88 -0
- data/tools/common/common/test/data/module2.py +77 -0
- data/tools/common/common/test/data/newlines.txt +3 -0
- data/tools/common/common/test/data/noendingnewline.py +36 -0
- data/tools/common/common/test/data/nonregr.py +14 -0
- data/tools/common/common/test/data/normal_file.txt +0 -0
- data/tools/common/common/test/data/reference_dir/NOTHING +0 -0
- data/tools/common/common/test/data/reference_dir/README +1 -0
- data/tools/common/common/test/data/reference_dir/subdir/coin +1 -0
- data/tools/common/common/test/data/reference_dir/subdir/toto.txt +53 -0
- data/tools/common/common/test/data/same_dir/NOTHING +0 -0
- data/tools/common/common/test/data/same_dir/README +1 -0
- data/tools/common/common/test/data/same_dir/subdir/coin +1 -0
- data/tools/common/common/test/data/same_dir/subdir/toto.txt +53 -0
- data/tools/common/common/test/data/spam.txt +9 -0
- data/tools/common/common/test/data/sub/doc.txt +1 -0
- data/tools/common/common/test/data/sub/momo.py +1 -0
- data/tools/common/common/test/data/subdir_differ_dir/NOTHING +0 -0
- data/tools/common/common/test/data/subdir_differ_dir/README +1 -0
- data/tools/common/common/test/data/subdir_differ_dir/subdir/coin +1 -0
- data/tools/common/common/test/data/subdir_differ_dir/subdir/toto.txt +53 -0
- data/tools/common/common/test/data/test.ini +20 -0
- data/tools/common/common/test/data/test1.msg +30 -0
- data/tools/common/common/test/data/test2.msg +42 -0
- data/tools/common/common/test/data/write_protected_file.txt +0 -0
- data/tools/common/common/test/foomod.py +17 -0
- data/tools/common/common/test/unittest_cache.py +129 -0
- data/tools/common/common/test/unittest_changelog.py +37 -0
- data/tools/common/common/test/unittest_compat.py +239 -0
- data/tools/common/common/test/unittest_configuration.py +348 -0
- data/tools/common/common/test/unittest_date.py +154 -0
- data/tools/common/common/test/unittest_decorators.py +62 -0
- data/tools/common/common/test/unittest_deprecation.py +76 -0
- data/tools/common/common/test/unittest_fileutils.py +133 -0
- data/tools/common/common/test/unittest_graph.py +50 -0
- data/tools/common/common/test/unittest_html.py +76 -0
- data/tools/common/common/test/unittest_interface.py +87 -0
- data/tools/common/common/test/unittest_modutils.py +244 -0
- data/tools/common/common/test/unittest_pytest.py +50 -0
- data/tools/common/common/test/unittest_shellutils.py +248 -0
- data/tools/common/common/test/unittest_table.py +448 -0
- data/tools/common/common/test/unittest_taskqueue.py +71 -0
- data/tools/common/common/test/unittest_testlib.py +956 -0
- data/tools/common/common/test/unittest_textutils.py +247 -0
- data/tools/common/common/test/unittest_tree.py +248 -0
- data/tools/common/common/test/unittest_umessage.py +55 -0
- data/tools/common/common/test/unittest_ureports_html.py +64 -0
- data/tools/common/common/test/unittest_ureports_text.py +105 -0
- data/tools/common/common/test/unittest_xmlutils.py +75 -0
- data/tools/common/common/test/utils.py +87 -0
- data/tools/common/common/testlib.py +1927 -0
- data/tools/common/common/textutils.py +476 -0
- data/tools/common/common/tree.py +372 -0
- data/tools/common/common/umessage.py +161 -0
- data/tools/common/common/ureports/__init__.py +174 -0
- data/tools/common/common/ureports/docbook_writer.py +139 -0
- data/tools/common/common/ureports/html_writer.py +131 -0
- data/tools/common/common/ureports/nodes.py +201 -0
- data/tools/common/common/ureports/text_writer.py +140 -0
- data/tools/common/common/vcgutils.py +216 -0
- data/tools/common/common/visitor.py +107 -0
- data/tools/common/common/xmlrpcutils.py +136 -0
- data/tools/common/common/xmlutils.py +61 -0
- data/tools/compile/compile.py +16 -0
- data/tools/coverage/coverage.py +602 -0
- data/tools/epydoc/__init__.py +227 -0
- data/tools/epydoc/__init__.pyc +0 -0
- data/tools/epydoc/apidoc.py +2203 -0
- data/tools/epydoc/apidoc.pyc +0 -0
- data/tools/epydoc/checker.py +349 -0
- data/tools/epydoc/checker.pyc +0 -0
- data/tools/epydoc/cli.py +1470 -0
- data/tools/epydoc/cli.pyc +0 -0
- data/tools/epydoc/compat.py +250 -0
- data/tools/epydoc/compat.pyc +0 -0
- data/tools/epydoc/docbuilder.py +1358 -0
- data/tools/epydoc/docbuilder.pyc +0 -0
- data/tools/epydoc/docintrospecter.py +1056 -0
- data/tools/epydoc/docintrospecter.pyc +0 -0
- data/tools/epydoc/docparser.py +2113 -0
- data/tools/epydoc/docparser.pyc +0 -0
- data/tools/epydoc/docstringparser.py +1111 -0
- data/tools/epydoc/docstringparser.pyc +0 -0
- data/tools/epydoc/docwriter/__init__.py +12 -0
- data/tools/epydoc/docwriter/__init__.pyc +0 -0
- data/tools/epydoc/docwriter/dotgraph.py +1351 -0
- data/tools/epydoc/docwriter/dotgraph.pyc +0 -0
- data/tools/epydoc/docwriter/html.py +3491 -0
- data/tools/epydoc/docwriter/html.pyc +0 -0
- data/tools/epydoc/docwriter/html_colorize.py +909 -0
- data/tools/epydoc/docwriter/html_colorize.pyc +0 -0
- data/tools/epydoc/docwriter/html_css.py +550 -0
- data/tools/epydoc/docwriter/html_css.pyc +0 -0
- data/tools/epydoc/docwriter/html_help.py +190 -0
- data/tools/epydoc/docwriter/html_help.pyc +0 -0
- data/tools/epydoc/docwriter/latex.py +1187 -0
- data/tools/epydoc/docwriter/latex.pyc +0 -0
- data/tools/epydoc/docwriter/plaintext.py +276 -0
- data/tools/epydoc/docwriter/plaintext.pyc +0 -0
- data/tools/epydoc/docwriter/xlink.py +505 -0
- data/tools/epydoc/docwriter/xlink.pyc +0 -0
- data/tools/epydoc/gui.py +1148 -0
- data/tools/epydoc/gui.pyc +0 -0
- data/tools/epydoc/log.py +204 -0
- data/tools/epydoc/log.pyc +0 -0
- data/tools/epydoc/markup/__init__.py +623 -0
- data/tools/epydoc/markup/__init__.pyc +0 -0
- data/tools/epydoc/markup/doctest.py +311 -0
- data/tools/epydoc/markup/doctest.pyc +0 -0
- data/tools/epydoc/markup/epytext.py +2116 -0
- data/tools/epydoc/markup/epytext.pyc +0 -0
- data/tools/epydoc/markup/javadoc.py +250 -0
- data/tools/epydoc/markup/javadoc.pyc +0 -0
- data/tools/epydoc/markup/plaintext.py +78 -0
- data/tools/epydoc/markup/plaintext.pyc +0 -0
- data/tools/epydoc/markup/pyval_repr.py +532 -0
- data/tools/epydoc/markup/pyval_repr.pyc +0 -0
- data/tools/epydoc/markup/restructuredtext.py +906 -0
- data/tools/epydoc/markup/restructuredtext.pyc +0 -0
- data/tools/epydoc/test/__init__.py +97 -0
- data/tools/epydoc/test/__init__.pyc +0 -0
- data/tools/epydoc/test/util.py +226 -0
- data/tools/epydoc/test/util.pyc +0 -0
- data/tools/epydoc/util.py +289 -0
- data/tools/epydoc/util.pyc +0 -0
- data/tools/logilab/logilab/__init__.py +5 -0
- data/tools/logilab/logilab/astng/__init__.py +82 -0
- data/tools/logilab/logilab/astng/__pkginfo__.py +76 -0
- data/tools/logilab/logilab/astng/_exceptions.py +64 -0
- data/tools/logilab/logilab/astng/_nodes_ast.py +667 -0
- data/tools/logilab/logilab/astng/_nodes_compiler.py +758 -0
- data/tools/logilab/logilab/astng/bases.py +608 -0
- data/tools/logilab/logilab/astng/builder.py +239 -0
- data/tools/logilab/logilab/astng/inference.py +426 -0
- data/tools/logilab/logilab/astng/inspector.py +289 -0
- data/tools/logilab/logilab/astng/manager.py +421 -0
- data/tools/logilab/logilab/astng/mixins.py +165 -0
- data/tools/logilab/logilab/astng/node_classes.py +848 -0
- data/tools/logilab/logilab/astng/nodes.py +85 -0
- data/tools/logilab/logilab/astng/nodes_as_string.py +389 -0
- data/tools/logilab/logilab/astng/patchcomptransformer.py +159 -0
- data/tools/logilab/logilab/astng/protocols.py +333 -0
- data/tools/logilab/logilab/astng/raw_building.py +212 -0
- data/tools/logilab/logilab/astng/rebuilder.py +307 -0
- data/tools/logilab/logilab/astng/scoped_nodes.py +951 -0
- data/tools/logilab/logilab/astng/test/__init__.py +19 -0
- data/tools/logilab/logilab/astng/test/data/MyPyPa-0.1.0-py2.5.egg +0 -0
- data/tools/logilab/logilab/astng/test/data/MyPyPa-0.1.0-py2.5.zip +0 -0
- data/tools/logilab/logilab/astng/test/data/SSL1/Connection1.py +33 -0
- data/tools/logilab/logilab/astng/test/data/SSL1/__init__.py +20 -0
- data/tools/logilab/logilab/astng/test/data/__init__.py +20 -0
- data/tools/logilab/logilab/astng/test/data/all.py +29 -0
- data/tools/logilab/logilab/astng/test/data/appl/__init__.py +23 -0
- data/tools/logilab/logilab/astng/test/data/appl/myConnection.py +30 -0
- data/tools/logilab/logilab/astng/test/data/format.py +34 -0
- data/tools/logilab/logilab/astng/test/data/module.py +90 -0
- data/tools/logilab/logilab/astng/test/data/module2.py +112 -0
- data/tools/logilab/logilab/astng/test/data/noendingnewline.py +57 -0
- data/tools/logilab/logilab/astng/test/data/nonregr.py +76 -0
- data/tools/logilab/logilab/astng/test/data/notall.py +28 -0
- data/tools/logilab/logilab/astng/test/data2/__init__.py +20 -0
- data/tools/logilab/logilab/astng/test/data2/clientmodule_test.py +51 -0
- data/tools/logilab/logilab/astng/test/data2/suppliermodule_test.py +32 -0
- data/tools/logilab/logilab/astng/test/regrtest.py +135 -0
- data/tools/logilab/logilab/astng/test/regrtest_data/absimport.py +22 -0
- data/tools/logilab/logilab/astng/test/regrtest_data/descriptor_crash.py +31 -0
- data/tools/logilab/logilab/astng/test/regrtest_data/import_package_subpackage_module.py +68 -0
- data/tools/logilab/logilab/astng/test/regrtest_data/package/__init__.py +24 -0
- data/tools/logilab/logilab/astng/test/regrtest_data/package/subpackage/__init__.py +20 -0
- data/tools/logilab/logilab/astng/test/regrtest_data/package/subpackage/module.py +20 -0
- data/tools/logilab/logilab/astng/test/unittest_builder.py +684 -0
- data/tools/logilab/logilab/astng/test/unittest_inference.py +1112 -0
- data/tools/logilab/logilab/astng/test/unittest_inspector.py +105 -0
- data/tools/logilab/logilab/astng/test/unittest_lookup.py +302 -0
- data/tools/logilab/logilab/astng/test/unittest_manager.py +98 -0
- data/tools/logilab/logilab/astng/test/unittest_nodes.py +302 -0
- data/tools/logilab/logilab/astng/test/unittest_scoped_nodes.py +501 -0
- data/tools/logilab/logilab/astng/test/unittest_utils.py +104 -0
- data/tools/logilab/logilab/astng/utils.py +342 -0
- data/tools/logilab/logilab/common/__init__.py +140 -0
- data/tools/logilab/logilab/common/__pkginfo__.py +43 -0
- data/tools/logilab/logilab/common/adbh.py +35 -0
- data/tools/logilab/logilab/common/cache.py +114 -0
- data/tools/logilab/logilab/common/changelog.py +234 -0
- data/tools/logilab/logilab/common/clcommands.py +181 -0
- data/tools/logilab/logilab/common/cli.py +212 -0
- data/tools/logilab/logilab/common/compat.py +328 -0
- data/tools/logilab/logilab/common/configuration.py +1087 -0
- data/tools/logilab/logilab/common/contexts.py +58 -0
- data/tools/logilab/logilab/common/corbautils.py +117 -0
- data/tools/logilab/logilab/common/daemon.py +171 -0
- data/tools/logilab/logilab/common/date.py +279 -0
- data/tools/logilab/logilab/common/db.py +49 -0
- data/tools/logilab/logilab/common/dbf.py +229 -0
- data/tools/logilab/logilab/common/debugger.py +208 -0
- data/tools/logilab/logilab/common/decorators.py +190 -0
- data/tools/logilab/logilab/common/deprecation.py +118 -0
- data/tools/logilab/logilab/common/fileutils.py +409 -0
- data/tools/logilab/logilab/common/graph.py +259 -0
- data/tools/logilab/logilab/common/html.py +142 -0
- data/tools/logilab/logilab/common/interface.py +76 -0
- data/tools/logilab/logilab/common/logging_ext.py +166 -0
- data/tools/logilab/logilab/common/modutils.py +670 -0
- data/tools/logilab/logilab/common/optik_ext.py +383 -0
- data/tools/logilab/logilab/common/optparser.py +92 -0
- data/tools/logilab/logilab/common/pdf_ext.py +111 -0
- data/tools/logilab/logilab/common/proc.py +276 -0
- data/tools/logilab/logilab/common/pyro_ext.py +146 -0
- data/tools/logilab/logilab/common/pytest.py +754 -0
- data/tools/logilab/logilab/common/shellutils.py +383 -0
- data/tools/logilab/logilab/common/sphinx_ext.py +87 -0
- data/tools/logilab/logilab/common/sphinxutils.py +122 -0
- data/tools/logilab/logilab/common/sqlgen.py +31 -0
- data/tools/logilab/logilab/common/table.py +930 -0
- data/tools/logilab/logilab/common/tasksqueue.py +97 -0
- data/tools/logilab/logilab/common/test/__init__.py +1 -0
- data/tools/logilab/logilab/common/test/data/ChangeLog +184 -0
- data/tools/logilab/logilab/common/test/data/MyPyPa-0.1.0-py2.5.egg +0 -0
- data/tools/logilab/logilab/common/test/data/__init__.py +1 -0
- data/tools/logilab/logilab/common/test/data/content_differ_dir/NOTHING +0 -0
- data/tools/logilab/logilab/common/test/data/content_differ_dir/README +1 -0
- data/tools/logilab/logilab/common/test/data/content_differ_dir/subdir/coin +1 -0
- data/tools/logilab/logilab/common/test/data/content_differ_dir/subdir/toto.txt +53 -0
- data/tools/logilab/logilab/common/test/data/file_differ_dir/NOTHING +0 -0
- data/tools/logilab/logilab/common/test/data/file_differ_dir/README +1 -0
- data/tools/logilab/logilab/common/test/data/file_differ_dir/subdir/toto.txt +53 -0
- data/tools/logilab/logilab/common/test/data/file_differ_dir/subdirtwo/Hello +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/__init__.py +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/foo.txt +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/module.py +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/module2.py +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/newlines.txt +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/noendingnewline.py +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/nonregr.py +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/normal_file.txt +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/spam.txt +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/sub/doc.txt +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/sub/momo.py +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/test.ini +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/test1.msg +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/test2.msg +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/write_protected_file.txt +0 -0
- data/tools/logilab/logilab/common/test/data/foo.txt +9 -0
- data/tools/logilab/logilab/common/test/data/module.py +88 -0
- data/tools/logilab/logilab/common/test/data/module2.py +77 -0
- data/tools/logilab/logilab/common/test/data/newlines.txt +3 -0
- data/tools/logilab/logilab/common/test/data/noendingnewline.py +36 -0
- data/tools/logilab/logilab/common/test/data/nonregr.py +14 -0
- data/tools/logilab/logilab/common/test/data/normal_file.txt +0 -0
- data/tools/logilab/logilab/common/test/data/reference_dir/NOTHING +0 -0
- data/tools/logilab/logilab/common/test/data/reference_dir/README +1 -0
- data/tools/logilab/logilab/common/test/data/reference_dir/subdir/coin +1 -0
- data/tools/logilab/logilab/common/test/data/reference_dir/subdir/toto.txt +53 -0
- data/tools/logilab/logilab/common/test/data/same_dir/NOTHING +0 -0
- data/tools/logilab/logilab/common/test/data/same_dir/README +1 -0
- data/tools/logilab/logilab/common/test/data/same_dir/subdir/coin +1 -0
- data/tools/logilab/logilab/common/test/data/same_dir/subdir/toto.txt +53 -0
- data/tools/logilab/logilab/common/test/data/spam.txt +9 -0
- data/tools/logilab/logilab/common/test/data/sub/doc.txt +1 -0
- data/tools/logilab/logilab/common/test/data/sub/momo.py +1 -0
- data/tools/logilab/logilab/common/test/data/subdir_differ_dir/NOTHING +0 -0
- data/tools/logilab/logilab/common/test/data/subdir_differ_dir/README +1 -0
- data/tools/logilab/logilab/common/test/data/subdir_differ_dir/subdir/coin +1 -0
- data/tools/logilab/logilab/common/test/data/subdir_differ_dir/subdir/toto.txt +53 -0
- data/tools/logilab/logilab/common/test/data/test.ini +20 -0
- data/tools/logilab/logilab/common/test/data/test1.msg +30 -0
- data/tools/logilab/logilab/common/test/data/test2.msg +42 -0
- data/tools/logilab/logilab/common/test/data/write_protected_file.txt +0 -0
- data/tools/logilab/logilab/common/test/foomod.py +17 -0
- data/tools/logilab/logilab/common/test/unittest_cache.py +129 -0
- data/tools/logilab/logilab/common/test/unittest_changelog.py +37 -0
- data/tools/logilab/logilab/common/test/unittest_compat.py +239 -0
- data/tools/logilab/logilab/common/test/unittest_configuration.py +348 -0
- data/tools/logilab/logilab/common/test/unittest_date.py +154 -0
- data/tools/logilab/logilab/common/test/unittest_decorators.py +62 -0
- data/tools/logilab/logilab/common/test/unittest_deprecation.py +76 -0
- data/tools/logilab/logilab/common/test/unittest_fileutils.py +133 -0
- data/tools/logilab/logilab/common/test/unittest_graph.py +50 -0
- data/tools/logilab/logilab/common/test/unittest_html.py +76 -0
- data/tools/logilab/logilab/common/test/unittest_interface.py +87 -0
- data/tools/logilab/logilab/common/test/unittest_modutils.py +244 -0
- data/tools/logilab/logilab/common/test/unittest_pytest.py +50 -0
- data/tools/logilab/logilab/common/test/unittest_shellutils.py +248 -0
- data/tools/logilab/logilab/common/test/unittest_table.py +448 -0
- data/tools/logilab/logilab/common/test/unittest_taskqueue.py +71 -0
- data/tools/logilab/logilab/common/test/unittest_testlib.py +956 -0
- data/tools/logilab/logilab/common/test/unittest_textutils.py +247 -0
- data/tools/logilab/logilab/common/test/unittest_tree.py +248 -0
- data/tools/logilab/logilab/common/test/unittest_umessage.py +55 -0
- data/tools/logilab/logilab/common/test/unittest_ureports_html.py +64 -0
- data/tools/logilab/logilab/common/test/unittest_ureports_text.py +105 -0
- data/tools/logilab/logilab/common/test/unittest_xmlutils.py +75 -0
- data/tools/logilab/logilab/common/test/utils.py +87 -0
- data/tools/logilab/logilab/common/testlib.py +1927 -0
- data/tools/logilab/logilab/common/textutils.py +476 -0
- data/tools/logilab/logilab/common/tree.py +372 -0
- data/tools/logilab/logilab/common/umessage.py +161 -0
- data/tools/logilab/logilab/common/ureports/__init__.py +174 -0
- data/tools/logilab/logilab/common/ureports/docbook_writer.py +139 -0
- data/tools/logilab/logilab/common/ureports/html_writer.py +131 -0
- data/tools/logilab/logilab/common/ureports/nodes.py +201 -0
- data/tools/logilab/logilab/common/ureports/text_writer.py +140 -0
- data/tools/logilab/logilab/common/vcgutils.py +216 -0
- data/tools/logilab/logilab/common/visitor.py +107 -0
- data/tools/logilab/logilab/common/xmlrpcutils.py +136 -0
- data/tools/logilab/logilab/common/xmlutils.py +61 -0
- data/tools/pychecker/COPYRIGHT +31 -0
- data/tools/pychecker/ChangeLog +349 -0
- data/tools/pychecker/CodeChecks.py +1969 -0
- data/tools/pychecker/CodeChecks.pyc +0 -0
- data/tools/pychecker/CodeChecks.pyo +0 -0
- data/tools/pychecker/Config.py +475 -0
- data/tools/pychecker/Config.pyc +0 -0
- data/tools/pychecker/Config.pyo +0 -0
- data/tools/pychecker/KNOWN_BUGS +100 -0
- data/tools/pychecker/MAINTAINERS +81 -0
- data/tools/pychecker/NEWS +406 -0
- data/tools/pychecker/OP.py +131 -0
- data/tools/pychecker/OP.pyc +0 -0
- data/tools/pychecker/OP.pyo +0 -0
- data/tools/pychecker/OptionTypes.py +117 -0
- data/tools/pychecker/OptionTypes.pyc +0 -0
- data/tools/pychecker/OptionTypes.pyo +0 -0
- data/tools/pychecker/README +152 -0
- data/tools/pychecker/Stack.py +115 -0
- data/tools/pychecker/Stack.pyc +0 -0
- data/tools/pychecker/Stack.pyo +0 -0
- data/tools/pychecker/TODO +101 -0
- data/tools/pychecker/VERSION +1 -0
- data/tools/pychecker/Warning.py +50 -0
- data/tools/pychecker/Warning.pyc +0 -0
- data/tools/pychecker/Warning.pyo +0 -0
- data/tools/pychecker/__init__.py +17 -0
- data/tools/pychecker/__init__.pyc +0 -0
- data/tools/pychecker/__init__.pyo +0 -0
- data/tools/pychecker/checker.py +961 -0
- data/tools/pychecker/checker.pyc +0 -0
- data/tools/pychecker/checker.pyo +0 -0
- data/tools/pychecker/function.py +159 -0
- data/tools/pychecker/function.pyc +0 -0
- data/tools/pychecker/function.pyo +0 -0
- data/tools/pychecker/msgs.py +175 -0
- data/tools/pychecker/msgs.pyc +0 -0
- data/tools/pychecker/msgs.pyo +0 -0
- data/tools/pychecker/options.py +275 -0
- data/tools/pychecker/options.pyc +0 -0
- data/tools/pychecker/options.pyo +0 -0
- data/tools/pychecker/pcmodules.py +19 -0
- data/tools/pychecker/pcmodules.pyc +0 -0
- data/tools/pychecker/pcmodules.pyo +0 -0
- data/tools/pychecker/printer.py +47 -0
- data/tools/pychecker/printer.pyc +0 -0
- data/tools/pychecker/printer.pyo +0 -0
- data/tools/pychecker/python.py +427 -0
- data/tools/pychecker/python.pyc +0 -0
- data/tools/pychecker/python.pyo +0 -0
- data/tools/pychecker/utils.py +102 -0
- data/tools/pychecker/utils.pyc +0 -0
- data/tools/pychecker/utils.pyo +0 -0
- data/tools/pychecker/warn.py +778 -0
- data/tools/pychecker/warn.pyc +0 -0
- data/tools/pychecker/warn.pyo +0 -0
- data/tools/pylint2/pylint/__init__.py +16 -0
- data/tools/pylint2/pylint/__pkginfo__.py +67 -0
- data/tools/pylint2/pylint/checkers/__init__.py +155 -0
- data/tools/pylint2/pylint/checkers/base.py +749 -0
- data/tools/pylint2/pylint/checkers/classes.py +527 -0
- data/tools/pylint2/pylint/checkers/design_analysis.py +344 -0
- data/tools/pylint2/pylint/checkers/exceptions.py +183 -0
- data/tools/pylint2/pylint/checkers/format.py +367 -0
- data/tools/pylint2/pylint/checkers/imports.py +379 -0
- data/tools/pylint2/pylint/checkers/logging.py +98 -0
- data/tools/pylint2/pylint/checkers/misc.py +128 -0
- data/tools/pylint2/pylint/checkers/newstyle.py +107 -0
- data/tools/pylint2/pylint/checkers/raw_metrics.py +125 -0
- data/tools/pylint2/pylint/checkers/similar.py +333 -0
- data/tools/pylint2/pylint/checkers/string_format.py +239 -0
- data/tools/pylint2/pylint/checkers/typecheck.py +364 -0
- data/tools/pylint2/pylint/checkers/utils.py +208 -0
- data/tools/pylint2/pylint/checkers/variables.py +498 -0
- data/tools/pylint2/pylint/config.py +149 -0
- data/tools/pylint2/pylint/epylint.py +149 -0
- data/tools/pylint2/pylint/gui.py +433 -0
- data/tools/pylint2/pylint/interfaces.py +98 -0
- data/tools/pylint2/pylint/lint.py +914 -0
- data/tools/pylint2/pylint/pyreverse/__init__.py +5 -0
- data/tools/pylint2/pylint/pyreverse/diadefslib.py +229 -0
- data/tools/pylint2/pylint/pyreverse/diagrams.py +247 -0
- data/tools/pylint2/pylint/pyreverse/main.py +123 -0
- data/tools/pylint2/pylint/pyreverse/utils.py +131 -0
- data/tools/pylint2/pylint/pyreverse/writer.py +196 -0
- data/tools/pylint2/pylint/reporters/__init__.py +67 -0
- data/tools/pylint2/pylint/reporters/guireporter.py +36 -0
- data/tools/pylint2/pylint/reporters/html.py +69 -0
- data/tools/pylint2/pylint/reporters/text.py +156 -0
- data/tools/pylint2/pylint/utils.py +518 -0
- data/tools/pylint2/pylint.py +16 -0
- data/tools/test/suite.py +35 -0
- metadata +566 -0
@@ -0,0 +1,1351 @@
|
|
1
|
+
# epydoc -- Graph generation
|
2
|
+
#
|
3
|
+
# Copyright (C) 2005 Edward Loper
|
4
|
+
# Author: Edward Loper <edloper@loper.org>
|
5
|
+
# URL: <http://epydoc.sf.net>
|
6
|
+
#
|
7
|
+
# $Id: dotgraph.py,v 1.1 2009/11/26 13:20:43 casa Exp $
|
8
|
+
|
9
|
+
"""
|
10
|
+
Render Graphviz directed graphs as images. Below are some examples.
|
11
|
+
|
12
|
+
.. importgraph::
|
13
|
+
|
14
|
+
.. classtree:: epydoc.apidoc.APIDoc
|
15
|
+
|
16
|
+
.. packagetree:: epydoc
|
17
|
+
|
18
|
+
:see: `The Graphviz Homepage
|
19
|
+
<http://www.research.att.com/sw/tools/graphviz/>`__
|
20
|
+
"""
|
21
|
+
__docformat__ = 'restructuredtext'
|
22
|
+
|
23
|
+
import re
|
24
|
+
import sys
|
25
|
+
from epydoc import log
|
26
|
+
from epydoc.apidoc import *
|
27
|
+
from epydoc.util import *
|
28
|
+
from epydoc.compat import * # Backwards compatibility
|
29
|
+
|
30
|
+
# colors for graphs of APIDocs
|
31
|
+
MODULE_BG = '#d8e8ff'
|
32
|
+
CLASS_BG = '#d8ffe8'
|
33
|
+
SELECTED_BG = '#ffd0d0'
|
34
|
+
BASECLASS_BG = '#e0b0a0'
|
35
|
+
SUBCLASS_BG = '#e0b0a0'
|
36
|
+
ROUTINE_BG = '#e8d0b0' # maybe?
|
37
|
+
INH_LINK_COLOR = '#800000'
|
38
|
+
|
39
|
+
######################################################################
|
40
|
+
#{ Dot Graphs
|
41
|
+
######################################################################
|
42
|
+
|
43
|
+
DOT_COMMAND = 'dot'
|
44
|
+
"""The command that should be used to spawn dot"""
|
45
|
+
|
46
|
+
class DotGraph:
|
47
|
+
"""
|
48
|
+
A ``dot`` directed graph. The contents of the graph are
|
49
|
+
constructed from the following instance variables:
|
50
|
+
|
51
|
+
- `nodes`: A list of `DotGraphNode`\\s, encoding the nodes
|
52
|
+
that are present in the graph. Each node is characterized
|
53
|
+
a set of attributes, including an optional label.
|
54
|
+
- `edges`: A list of `DotGraphEdge`\\s, encoding the edges
|
55
|
+
that are present in the graph. Each edge is characterized
|
56
|
+
by a set of attributes, including an optional label.
|
57
|
+
- `node_defaults`: Default attributes for nodes.
|
58
|
+
- `edge_defaults`: Default attributes for edges.
|
59
|
+
- `body`: A string that is appended as-is in the body of
|
60
|
+
the graph. This can be used to build more complex dot
|
61
|
+
graphs.
|
62
|
+
|
63
|
+
The `link()` method can be used to resolve crossreference links
|
64
|
+
within the graph. In particular, if the 'href' attribute of any
|
65
|
+
node or edge is assigned a value of the form ``<name>``, then it
|
66
|
+
will be replaced by the URL of the object with that name. This
|
67
|
+
applies to the `body` as well as the `nodes` and `edges`.
|
68
|
+
|
69
|
+
To render the graph, use the methods `write()` and `render()`.
|
70
|
+
Usually, you should call `link()` before you render the graph.
|
71
|
+
"""
|
72
|
+
_uids = set()
|
73
|
+
"""A set of all uids that that have been generated, used to ensure
|
74
|
+
that each new graph has a unique uid."""
|
75
|
+
|
76
|
+
DEFAULT_NODE_DEFAULTS={'fontsize':10, 'fontname': 'Helvetica'}
|
77
|
+
DEFAULT_EDGE_DEFAULTS={'fontsize':10, 'fontname': 'Helvetica'}
|
78
|
+
|
79
|
+
def __init__(self, title, body='', node_defaults=None,
|
80
|
+
edge_defaults=None, caption=None):
|
81
|
+
"""
|
82
|
+
Create a new `DotGraph`.
|
83
|
+
"""
|
84
|
+
self.title = title
|
85
|
+
"""The title of the graph."""
|
86
|
+
|
87
|
+
self.caption = caption
|
88
|
+
"""A caption for the graph."""
|
89
|
+
|
90
|
+
self.nodes = []
|
91
|
+
"""A list of the nodes that are present in the graph.
|
92
|
+
|
93
|
+
:type: ``list`` of `DotGraphNode`"""
|
94
|
+
|
95
|
+
self.edges = []
|
96
|
+
"""A list of the edges that are present in the graph.
|
97
|
+
|
98
|
+
:type: ``list`` of `DotGraphEdge`"""
|
99
|
+
|
100
|
+
self.body = body
|
101
|
+
"""A string that should be included as-is in the body of the
|
102
|
+
graph.
|
103
|
+
|
104
|
+
:type: ``str``"""
|
105
|
+
|
106
|
+
self.node_defaults = node_defaults or self.DEFAULT_NODE_DEFAULTS
|
107
|
+
"""Default attribute values for nodes."""
|
108
|
+
|
109
|
+
self.edge_defaults = edge_defaults or self.DEFAULT_EDGE_DEFAULTS
|
110
|
+
"""Default attribute values for edges."""
|
111
|
+
|
112
|
+
self.uid = re.sub(r'\W', '_', title).lower()
|
113
|
+
"""A unique identifier for this graph. This can be used as a
|
114
|
+
filename when rendering the graph. No two `DotGraph`\s will
|
115
|
+
have the same uid."""
|
116
|
+
|
117
|
+
# Encode the title, if necessary.
|
118
|
+
if isinstance(self.title, unicode):
|
119
|
+
self.title = self.title.encode('ascii', 'xmlcharrefreplace')
|
120
|
+
|
121
|
+
# Make sure the UID isn't too long.
|
122
|
+
self.uid = self.uid[:30]
|
123
|
+
|
124
|
+
# Make sure the UID is unique
|
125
|
+
if self.uid in self._uids:
|
126
|
+
n = 2
|
127
|
+
while ('%s_%s' % (self.uid, n)) in self._uids: n += 1
|
128
|
+
self.uid = '%s_%s' % (self.uid, n)
|
129
|
+
self._uids.add(self.uid)
|
130
|
+
|
131
|
+
def to_html(self, image_file, image_url, center=True):
|
132
|
+
"""
|
133
|
+
Return the HTML code that should be uesd to display this graph
|
134
|
+
(including a client-side image map).
|
135
|
+
|
136
|
+
:param image_url: The URL of the image file for this graph;
|
137
|
+
this should be generated separately with the `write()` method.
|
138
|
+
"""
|
139
|
+
# If dotversion >1.8.10, then we can generate the image and
|
140
|
+
# the cmapx with a single call to dot. Otherwise, we need to
|
141
|
+
# run dot twice.
|
142
|
+
if get_dot_version() > [1,8,10]:
|
143
|
+
cmapx = self._run_dot('-Tgif', '-o%s' % image_file, '-Tcmapx')
|
144
|
+
if cmapx is None: return '' # failed to render
|
145
|
+
else:
|
146
|
+
if not self.write(image_file):
|
147
|
+
return '' # failed to render
|
148
|
+
cmapx = self.render('cmapx') or ''
|
149
|
+
|
150
|
+
# Decode the cmapx (dot uses utf-8)
|
151
|
+
try:
|
152
|
+
cmapx = cmapx.decode('utf-8')
|
153
|
+
except UnicodeDecodeError:
|
154
|
+
log.debug('%s: unable to decode cmapx from dot; graph will '
|
155
|
+
'not have clickable regions' % image_file)
|
156
|
+
cmapx = ''
|
157
|
+
|
158
|
+
title = plaintext_to_html(self.title or '')
|
159
|
+
caption = plaintext_to_html(self.caption or '')
|
160
|
+
if title or caption:
|
161
|
+
css_class = 'graph-with-title'
|
162
|
+
else:
|
163
|
+
css_class = 'graph-without-title'
|
164
|
+
if len(title)+len(caption) > 80:
|
165
|
+
title_align = 'left'
|
166
|
+
table_width = ' width="600"'
|
167
|
+
else:
|
168
|
+
title_align = 'center'
|
169
|
+
table_width = ''
|
170
|
+
|
171
|
+
if center: s = '<center>'
|
172
|
+
if title or caption:
|
173
|
+
s += ('<table border="0" cellpadding="0" cellspacing="0" '
|
174
|
+
'class="graph"%s>\n <tr><td align="center">\n' %
|
175
|
+
table_width)
|
176
|
+
s += (' %s\n <img src="%s" alt=%r usemap="#%s" '
|
177
|
+
'ismap="ismap" class="%s" />\n' %
|
178
|
+
(cmapx.strip(), image_url, title, self.uid, css_class))
|
179
|
+
if title or caption:
|
180
|
+
s += ' </td></tr>\n <tr><td align=%r>\n' % title_align
|
181
|
+
if title:
|
182
|
+
s += '<span class="graph-title">%s</span>' % title
|
183
|
+
if title and caption:
|
184
|
+
s += ' -- '
|
185
|
+
if caption:
|
186
|
+
s += '<span class="graph-caption">%s</span>' % caption
|
187
|
+
s += '\n </td></tr>\n</table><br />'
|
188
|
+
if center: s += '</center>'
|
189
|
+
return s
|
190
|
+
|
191
|
+
def link(self, docstring_linker):
|
192
|
+
"""
|
193
|
+
Replace any href attributes whose value is ``<name>`` with
|
194
|
+
the url of the object whose name is ``<name>``.
|
195
|
+
"""
|
196
|
+
# Link xrefs in nodes
|
197
|
+
self._link_href(self.node_defaults, docstring_linker)
|
198
|
+
for node in self.nodes:
|
199
|
+
self._link_href(node.attribs, docstring_linker)
|
200
|
+
|
201
|
+
# Link xrefs in edges
|
202
|
+
self._link_href(self.edge_defaults, docstring_linker)
|
203
|
+
for edge in self.nodes:
|
204
|
+
self._link_href(edge.attribs, docstring_linker)
|
205
|
+
|
206
|
+
# Link xrefs in body
|
207
|
+
def subfunc(m):
|
208
|
+
url = docstring_linker.url_for(m.group(1))
|
209
|
+
if url: return 'href="%s"%s' % (url, m.group(2))
|
210
|
+
else: return ''
|
211
|
+
self.body = re.sub("href\s*=\s*['\"]?<([\w\.]+)>['\"]?\s*(,?)",
|
212
|
+
subfunc, self.body)
|
213
|
+
|
214
|
+
def _link_href(self, attribs, docstring_linker):
|
215
|
+
"""Helper for `link()`"""
|
216
|
+
if 'href' in attribs:
|
217
|
+
m = re.match(r'^<([\w\.]+)>$', attribs['href'])
|
218
|
+
if m:
|
219
|
+
url = docstring_linker.url_for(m.group(1))
|
220
|
+
if url: attribs['href'] = url
|
221
|
+
else: del attribs['href']
|
222
|
+
|
223
|
+
def write(self, filename, language='gif'):
|
224
|
+
"""
|
225
|
+
Render the graph using the output format `language`, and write
|
226
|
+
the result to `filename`.
|
227
|
+
|
228
|
+
:return: True if rendering was successful.
|
229
|
+
"""
|
230
|
+
result = self._run_dot('-T%s' % language,
|
231
|
+
'-o%s' % filename)
|
232
|
+
# Decode into unicode, if necessary.
|
233
|
+
if language == 'cmapx' and result is not None:
|
234
|
+
result = result.decode('utf-8')
|
235
|
+
return (result is not None)
|
236
|
+
|
237
|
+
def render(self, language='gif'):
|
238
|
+
"""
|
239
|
+
Use the ``dot`` command to render this graph, using the output
|
240
|
+
format `language`. Return the result as a string, or ``None``
|
241
|
+
if the rendering failed.
|
242
|
+
"""
|
243
|
+
return self._run_dot('-T%s' % language)
|
244
|
+
|
245
|
+
def _run_dot(self, *options):
|
246
|
+
try:
|
247
|
+
result, err = run_subprocess((DOT_COMMAND,)+options,
|
248
|
+
self.to_dotfile())
|
249
|
+
if err: log.warning("Graphviz dot warning(s):\n%s" % err)
|
250
|
+
except OSError, e:
|
251
|
+
log.warning("Unable to render Graphviz dot graph:\n%s" % e)
|
252
|
+
#log.debug(self.to_dotfile())
|
253
|
+
return None
|
254
|
+
|
255
|
+
return result
|
256
|
+
|
257
|
+
def to_dotfile(self):
|
258
|
+
"""
|
259
|
+
Return the string contents of the dot file that should be used
|
260
|
+
to render this graph.
|
261
|
+
"""
|
262
|
+
lines = ['digraph %s {' % self.uid,
|
263
|
+
'node [%s]' % ','.join(['%s="%s"' % (k,v) for (k,v)
|
264
|
+
in self.node_defaults.items()]),
|
265
|
+
'edge [%s]' % ','.join(['%s="%s"' % (k,v) for (k,v)
|
266
|
+
in self.edge_defaults.items()])]
|
267
|
+
if self.body:
|
268
|
+
lines.append(self.body)
|
269
|
+
lines.append('/* Nodes */')
|
270
|
+
for node in self.nodes:
|
271
|
+
lines.append(node.to_dotfile())
|
272
|
+
lines.append('/* Edges */')
|
273
|
+
for edge in self.edges:
|
274
|
+
lines.append(edge.to_dotfile())
|
275
|
+
lines.append('}')
|
276
|
+
|
277
|
+
# Default dot input encoding is UTF-8
|
278
|
+
return u'\n'.join(lines).encode('utf-8')
|
279
|
+
|
280
|
+
class DotGraphNode:
|
281
|
+
_next_id = 0
|
282
|
+
def __init__(self, label=None, html_label=None, **attribs):
|
283
|
+
if label is not None and html_label is not None:
|
284
|
+
raise ValueError('Use label or html_label, not both.')
|
285
|
+
if label is not None: attribs['label'] = label
|
286
|
+
self._html_label = html_label
|
287
|
+
self._attribs = attribs
|
288
|
+
self.id = self.__class__._next_id
|
289
|
+
self.__class__._next_id += 1
|
290
|
+
self.port = None
|
291
|
+
|
292
|
+
def __getitem__(self, attr):
|
293
|
+
return self._attribs[attr]
|
294
|
+
|
295
|
+
def __setitem__(self, attr, val):
|
296
|
+
if attr == 'html_label':
|
297
|
+
self._attribs.pop('label')
|
298
|
+
self._html_label = val
|
299
|
+
else:
|
300
|
+
if attr == 'label': self._html_label = None
|
301
|
+
self._attribs[attr] = val
|
302
|
+
|
303
|
+
def to_dotfile(self):
|
304
|
+
"""
|
305
|
+
Return the dot commands that should be used to render this node.
|
306
|
+
"""
|
307
|
+
attribs = ['%s="%s"' % (k,v) for (k,v) in self._attribs.items()
|
308
|
+
if v is not None]
|
309
|
+
if self._html_label:
|
310
|
+
attribs.insert(0, 'label=<%s>' % (self._html_label,))
|
311
|
+
if attribs: attribs = ' [%s]' % (','.join(attribs))
|
312
|
+
return 'node%d%s' % (self.id, attribs)
|
313
|
+
|
314
|
+
class DotGraphEdge:
|
315
|
+
def __init__(self, start, end, label=None, **attribs):
|
316
|
+
"""
|
317
|
+
:type start: `DotGraphNode`
|
318
|
+
:type end: `DotGraphNode`
|
319
|
+
"""
|
320
|
+
assert isinstance(start, DotGraphNode)
|
321
|
+
assert isinstance(end, DotGraphNode)
|
322
|
+
if label is not None: attribs['label'] = label
|
323
|
+
self.start = start #: :type: `DotGraphNode`
|
324
|
+
self.end = end #: :type: `DotGraphNode`
|
325
|
+
self._attribs = attribs
|
326
|
+
|
327
|
+
def __getitem__(self, attr):
|
328
|
+
return self._attribs[attr]
|
329
|
+
|
330
|
+
def __setitem__(self, attr, val):
|
331
|
+
self._attribs[attr] = val
|
332
|
+
|
333
|
+
def to_dotfile(self):
|
334
|
+
"""
|
335
|
+
Return the dot commands that should be used to render this edge.
|
336
|
+
"""
|
337
|
+
# Set head & tail ports, if the nodes have preferred ports.
|
338
|
+
attribs = self._attribs.copy()
|
339
|
+
if (self.start.port is not None and 'headport' not in attribs):
|
340
|
+
attribs['headport'] = self.start.port
|
341
|
+
if (self.end.port is not None and 'tailport' not in attribs):
|
342
|
+
attribs['tailport'] = self.end.port
|
343
|
+
# Convert attribs to a string
|
344
|
+
attribs = ','.join(['%s="%s"' % (k,v) for (k,v) in attribs.items()
|
345
|
+
if v is not None])
|
346
|
+
if attribs: attribs = ' [%s]' % attribs
|
347
|
+
# Return the dotfile edge.
|
348
|
+
return 'node%d -> node%d%s' % (self.start.id, self.end.id, attribs)
|
349
|
+
|
350
|
+
######################################################################
|
351
|
+
#{ Specialized Nodes for UML Graphs
|
352
|
+
######################################################################
|
353
|
+
|
354
|
+
class DotGraphUmlClassNode(DotGraphNode):
|
355
|
+
"""
|
356
|
+
A specialized dot graph node used to display `ClassDoc`\s using
|
357
|
+
UML notation. The node is rendered as a table with three cells:
|
358
|
+
the top cell contains the class name; the middle cell contains a
|
359
|
+
list of attributes; and the bottom cell contains a list of
|
360
|
+
operations::
|
361
|
+
|
362
|
+
+-------------+
|
363
|
+
| ClassName |
|
364
|
+
+-------------+
|
365
|
+
| x: int |
|
366
|
+
| ... |
|
367
|
+
+-------------+
|
368
|
+
| f(self, x) |
|
369
|
+
| ... |
|
370
|
+
+-------------+
|
371
|
+
|
372
|
+
`DotGraphUmlClassNode`\s may be *collapsed*, in which case they are
|
373
|
+
drawn as a simple box containing the class name::
|
374
|
+
|
375
|
+
+-------------+
|
376
|
+
| ClassName |
|
377
|
+
+-------------+
|
378
|
+
|
379
|
+
Attributes with types corresponding to documented classes can
|
380
|
+
optionally be converted into edges, using `link_attributes()`.
|
381
|
+
|
382
|
+
:todo: Add more options?
|
383
|
+
- show/hide operation signature
|
384
|
+
- show/hide operation signature types
|
385
|
+
- show/hide operation signature return type
|
386
|
+
- show/hide attribute types
|
387
|
+
- use qualifiers
|
388
|
+
"""
|
389
|
+
|
390
|
+
def __init__(self, class_doc, linker, context, collapsed=False,
|
391
|
+
bgcolor=CLASS_BG, **options):
|
392
|
+
"""
|
393
|
+
Create a new `DotGraphUmlClassNode` based on the class
|
394
|
+
`class_doc`.
|
395
|
+
|
396
|
+
:Parameters:
|
397
|
+
`linker` : `markup.DocstringLinker`
|
398
|
+
Used to look up URLs for classes.
|
399
|
+
`context` : `APIDoc`
|
400
|
+
The context in which this node will be drawn; dotted
|
401
|
+
names will be contextualized to this context.
|
402
|
+
`collapsed` : ``bool``
|
403
|
+
If true, then display this node as a simple box.
|
404
|
+
`bgcolor` : ```str```
|
405
|
+
The background color for this node.
|
406
|
+
`options` : ``dict``
|
407
|
+
A set of options used to control how the node should
|
408
|
+
be displayed.
|
409
|
+
|
410
|
+
:Keywords:
|
411
|
+
- `show_private_vars`: If false, then private variables
|
412
|
+
are filtered out of the attributes & operations lists.
|
413
|
+
(Default: *False*)
|
414
|
+
- `show_magic_vars`: If false, then magic variables
|
415
|
+
(such as ``__init__`` and ``__add__``) are filtered out of
|
416
|
+
the attributes & operations lists. (Default: *True*)
|
417
|
+
- `show_inherited_vars`: If false, then inherited variables
|
418
|
+
are filtered out of the attributes & operations lists.
|
419
|
+
(Default: *False*)
|
420
|
+
- `max_attributes`: The maximum number of attributes that
|
421
|
+
should be listed in the attribute box. If the class has
|
422
|
+
more than this number of attributes, some will be
|
423
|
+
ellided. Ellipsis is marked with ``'...'``.
|
424
|
+
- `max_operations`: The maximum number of operations that
|
425
|
+
should be listed in the operation box.
|
426
|
+
- `add_nodes_for_linked_attributes`: If true, then
|
427
|
+
`link_attributes()` will create new a collapsed node for
|
428
|
+
the types of a linked attributes if no node yet exists for
|
429
|
+
that type.
|
430
|
+
"""
|
431
|
+
if not isinstance(class_doc, ClassDoc):
|
432
|
+
raise TypeError('Expected a ClassDoc as 1st argument')
|
433
|
+
|
434
|
+
self.class_doc = class_doc
|
435
|
+
"""The class represented by this node."""
|
436
|
+
|
437
|
+
self.linker = linker
|
438
|
+
"""Used to look up URLs for classes."""
|
439
|
+
|
440
|
+
self.context = context
|
441
|
+
"""The context in which the node will be drawn."""
|
442
|
+
|
443
|
+
self.bgcolor = bgcolor
|
444
|
+
"""The background color of the node."""
|
445
|
+
|
446
|
+
self.options = options
|
447
|
+
"""Options used to control how the node is displayed."""
|
448
|
+
|
449
|
+
self.collapsed = collapsed
|
450
|
+
"""If true, then draw this node as a simple box."""
|
451
|
+
|
452
|
+
self.attributes = []
|
453
|
+
"""The list of VariableDocs for attributes"""
|
454
|
+
|
455
|
+
self.operations = []
|
456
|
+
"""The list of VariableDocs for operations"""
|
457
|
+
|
458
|
+
self.qualifiers = []
|
459
|
+
"""List of (key_label, port) tuples."""
|
460
|
+
|
461
|
+
self.edges = []
|
462
|
+
"""List of edges used to represent this node's attributes.
|
463
|
+
These should not be added to the `DotGraph`; this node will
|
464
|
+
generate their dotfile code directly."""
|
465
|
+
|
466
|
+
# Initialize operations & attributes lists.
|
467
|
+
show_private = options.get('show_private_vars', False)
|
468
|
+
show_magic = options.get('show_magic_vars', True)
|
469
|
+
show_inherited = options.get('show_inherited_vars', False)
|
470
|
+
for var in class_doc.sorted_variables:
|
471
|
+
name = var.canonical_name[-1]
|
472
|
+
if ((not show_private and var.is_public == False) or
|
473
|
+
(not show_magic and re.match('__\w+__$', name)) or
|
474
|
+
(not show_inherited and var.container != class_doc)):
|
475
|
+
pass
|
476
|
+
elif isinstance(var.value, RoutineDoc):
|
477
|
+
self.operations.append(var)
|
478
|
+
else:
|
479
|
+
self.attributes.append(var)
|
480
|
+
|
481
|
+
# Initialize our dot node settings.
|
482
|
+
tooltip = self._summary(class_doc)
|
483
|
+
if tooltip:
|
484
|
+
# dot chokes on a \n in the attribute...
|
485
|
+
tooltip = " ".join(tooltip.split())
|
486
|
+
else:
|
487
|
+
tooltip = class_doc.canonical_name
|
488
|
+
DotGraphNode.__init__(self, tooltip=tooltip,
|
489
|
+
width=0, height=0, shape='plaintext',
|
490
|
+
href=linker.url_for(class_doc) or NOOP_URL)
|
491
|
+
|
492
|
+
#/////////////////////////////////////////////////////////////////
|
493
|
+
#{ Attribute Linking
|
494
|
+
#/////////////////////////////////////////////////////////////////
|
495
|
+
|
496
|
+
SIMPLE_TYPE_RE = re.compile(
|
497
|
+
r'^([\w\.]+)$')
|
498
|
+
"""A regular expression that matches descriptions of simple types."""
|
499
|
+
|
500
|
+
COLLECTION_TYPE_RE = re.compile(
|
501
|
+
r'^(list|set|sequence|tuple|collection) of ([\w\.]+)$')
|
502
|
+
"""A regular expression that matches descriptions of collection types."""
|
503
|
+
|
504
|
+
MAPPING_TYPE_RE = re.compile(
|
505
|
+
r'^(dict|dictionary|map|mapping) from ([\w\.]+) to ([\w\.]+)$')
|
506
|
+
"""A regular expression that matches descriptions of mapping types."""
|
507
|
+
|
508
|
+
MAPPING_TO_COLLECTION_TYPE_RE = re.compile(
|
509
|
+
r'^(dict|dictionary|map|mapping) from ([\w\.]+) to '
|
510
|
+
r'(list|set|sequence|tuple|collection) of ([\w\.]+)$')
|
511
|
+
"""A regular expression that matches descriptions of mapping types
|
512
|
+
whose value type is a collection."""
|
513
|
+
|
514
|
+
OPTIONAL_TYPE_RE = re.compile(
|
515
|
+
r'^(None or|optional) ([\w\.]+)$|^([\w\.]+) or None$')
|
516
|
+
"""A regular expression that matches descriptions of optional types."""
|
517
|
+
|
518
|
+
def link_attributes(self, nodes):
|
519
|
+
"""
|
520
|
+
Convert any attributes with type descriptions corresponding to
|
521
|
+
documented classes to edges. The following type descriptions
|
522
|
+
are currently handled:
|
523
|
+
|
524
|
+
- Dotted names: Create an attribute edge to the named type,
|
525
|
+
labelled with the variable name.
|
526
|
+
- Collections: Create an attribute edge to the named type,
|
527
|
+
labelled with the variable name, and marked with '*' at the
|
528
|
+
type end of the edge.
|
529
|
+
- Mappings: Create an attribute edge to the named type,
|
530
|
+
labelled with the variable name, connected to the class by
|
531
|
+
a qualifier box that contains the key type description.
|
532
|
+
- Optional: Create an attribute edge to the named type,
|
533
|
+
labelled with the variable name, and marked with '0..1' at
|
534
|
+
the type end of the edge.
|
535
|
+
|
536
|
+
The edges created by `link_attributes()` are handled internally
|
537
|
+
by `DotGraphUmlClassNode`; they should *not* be added directly
|
538
|
+
to the `DotGraph`.
|
539
|
+
|
540
|
+
:param nodes: A dictionary mapping from `ClassDoc`\s to
|
541
|
+
`DotGraphUmlClassNode`\s, used to look up the nodes for
|
542
|
+
attribute types. If the ``add_nodes_for_linked_attributes``
|
543
|
+
option is used, then new nodes will be added to this
|
544
|
+
dictionary for any types that are not already listed.
|
545
|
+
These added nodes must be added to the `DotGraph`.
|
546
|
+
"""
|
547
|
+
# Try to convert each attribute var into a graph edge. If
|
548
|
+
# _link_attribute returns true, then it succeeded, so remove
|
549
|
+
# that var from our attribute list; otherwise, leave that var
|
550
|
+
# in our attribute list.
|
551
|
+
self.attributes = [var for var in self.attributes
|
552
|
+
if not self._link_attribute(var, nodes)]
|
553
|
+
|
554
|
+
def _link_attribute(self, var, nodes):
|
555
|
+
"""
|
556
|
+
Helper for `link_attributes()`: try to convert the attribute
|
557
|
+
variable `var` into an edge, and add that edge to
|
558
|
+
`self.edges`. Return ``True`` iff the variable was
|
559
|
+
successfully converted to an edge (in which case, it should be
|
560
|
+
removed from the attributes list).
|
561
|
+
"""
|
562
|
+
type_descr = self._type_descr(var) or self._type_descr(var.value)
|
563
|
+
|
564
|
+
# Simple type.
|
565
|
+
m = self.SIMPLE_TYPE_RE.match(type_descr)
|
566
|
+
if m and self._add_attribute_edge(var, nodes, m.group(1)):
|
567
|
+
return True
|
568
|
+
|
569
|
+
# Collection type.
|
570
|
+
m = self.COLLECTION_TYPE_RE.match(type_descr)
|
571
|
+
if m and self._add_attribute_edge(var, nodes, m.group(2),
|
572
|
+
headlabel='*'):
|
573
|
+
return True
|
574
|
+
|
575
|
+
# Optional type.
|
576
|
+
m = self.OPTIONAL_TYPE_RE.match(type_descr)
|
577
|
+
if m and self._add_attribute_edge(var, nodes, m.group(2) or m.group(3),
|
578
|
+
headlabel='0..1'):
|
579
|
+
return True
|
580
|
+
|
581
|
+
# Mapping type.
|
582
|
+
m = self.MAPPING_TYPE_RE.match(type_descr)
|
583
|
+
if m:
|
584
|
+
port = 'qualifier_%s' % var.name
|
585
|
+
if self._add_attribute_edge(var, nodes, m.group(3),
|
586
|
+
tailport='%s:e' % port):
|
587
|
+
self.qualifiers.append( (m.group(2), port) )
|
588
|
+
return True
|
589
|
+
|
590
|
+
# Mapping to collection type.
|
591
|
+
m = self.MAPPING_TO_COLLECTION_TYPE_RE.match(type_descr)
|
592
|
+
if m:
|
593
|
+
port = 'qualifier_%s' % var.name
|
594
|
+
if self._add_attribute_edge(var, nodes, m.group(4), headlabel='*',
|
595
|
+
tailport='%s:e' % port):
|
596
|
+
self.qualifiers.append( (m.group(2), port) )
|
597
|
+
return True
|
598
|
+
|
599
|
+
# We were unable to link this attribute.
|
600
|
+
return False
|
601
|
+
|
602
|
+
def _add_attribute_edge(self, var, nodes, type_str, **attribs):
|
603
|
+
"""
|
604
|
+
Helper for `link_attributes()`: try to add an edge for the
|
605
|
+
given attribute variable `var`. Return ``True`` if
|
606
|
+
successful.
|
607
|
+
"""
|
608
|
+
# Use the type string to look up a corresponding ValueDoc.
|
609
|
+
type_doc = self.linker.docindex.find(type_str, var)
|
610
|
+
if not type_doc: return False
|
611
|
+
|
612
|
+
# Make sure the type is a class.
|
613
|
+
if not isinstance(type_doc, ClassDoc): return False
|
614
|
+
|
615
|
+
# Get the type ValueDoc's node. If it doesn't have one (and
|
616
|
+
# add_nodes_for_linked_attributes=True), then create it.
|
617
|
+
type_node = nodes.get(type_doc)
|
618
|
+
if not type_node:
|
619
|
+
if self.options.get('add_nodes_for_linked_attributes', True):
|
620
|
+
type_node = DotGraphUmlClassNode(type_doc, self.linker,
|
621
|
+
self.context, collapsed=True)
|
622
|
+
nodes[type_doc] = type_node
|
623
|
+
else:
|
624
|
+
return False
|
625
|
+
|
626
|
+
# Add an edge from self to the target type node.
|
627
|
+
# [xx] should I set constraint=false here?
|
628
|
+
attribs.setdefault('headport', 'body')
|
629
|
+
attribs.setdefault('tailport', 'body')
|
630
|
+
url = self.linker.url_for(var) or NOOP_URL
|
631
|
+
self.edges.append(DotGraphEdge(self, type_node, label=var.name,
|
632
|
+
arrowhead='open', href=url,
|
633
|
+
tooltip=var.canonical_name, labeldistance=1.5,
|
634
|
+
**attribs))
|
635
|
+
return True
|
636
|
+
|
637
|
+
#/////////////////////////////////////////////////////////////////
|
638
|
+
#{ Helper Methods
|
639
|
+
#/////////////////////////////////////////////////////////////////
|
640
|
+
def _summary(self, api_doc):
|
641
|
+
"""Return a plaintext summary for `api_doc`"""
|
642
|
+
if not isinstance(api_doc, APIDoc): return ''
|
643
|
+
if api_doc.summary in (None, UNKNOWN): return ''
|
644
|
+
summary = api_doc.summary.to_plaintext(None).strip()
|
645
|
+
return plaintext_to_html(summary)
|
646
|
+
|
647
|
+
_summary = classmethod(_summary)
|
648
|
+
|
649
|
+
def _type_descr(self, api_doc):
|
650
|
+
"""Return a plaintext type description for `api_doc`"""
|
651
|
+
if not hasattr(api_doc, 'type_descr'): return ''
|
652
|
+
if api_doc.type_descr in (None, UNKNOWN): return ''
|
653
|
+
type_descr = api_doc.type_descr.to_plaintext(self.linker).strip()
|
654
|
+
return plaintext_to_html(type_descr)
|
655
|
+
|
656
|
+
def _tooltip(self, var_doc):
|
657
|
+
"""Return a tooltip for `var_doc`."""
|
658
|
+
return (self._summary(var_doc) or
|
659
|
+
self._summary(var_doc.value) or
|
660
|
+
var_doc.canonical_name)
|
661
|
+
|
662
|
+
#/////////////////////////////////////////////////////////////////
|
663
|
+
#{ Rendering
|
664
|
+
#/////////////////////////////////////////////////////////////////
|
665
|
+
|
666
|
+
def _attribute_cell(self, var_doc):
|
667
|
+
# Construct the label
|
668
|
+
label = var_doc.name
|
669
|
+
type_descr = (self._type_descr(var_doc) or
|
670
|
+
self._type_descr(var_doc.value))
|
671
|
+
if type_descr: label += ': %s' % type_descr
|
672
|
+
# Get the URL
|
673
|
+
url = self.linker.url_for(var_doc) or NOOP_URL
|
674
|
+
# Construct & return the pseudo-html code
|
675
|
+
return self._ATTRIBUTE_CELL % (url, self._tooltip(var_doc), label)
|
676
|
+
|
677
|
+
def _operation_cell(self, var_doc):
|
678
|
+
"""
|
679
|
+
:todo: do 'word wrapping' on the signature, by starting a new
|
680
|
+
row in the table, if necessary. How to indent the new
|
681
|
+
line? Maybe use align=right? I don't think dot has a
|
682
|
+
.
|
683
|
+
:todo: Optionally add return type info?
|
684
|
+
"""
|
685
|
+
# Construct the label (aka function signature)
|
686
|
+
func_doc = var_doc.value
|
687
|
+
args = [self._operation_arg(n, d, func_doc) for (n, d)
|
688
|
+
in zip(func_doc.posargs, func_doc.posarg_defaults)]
|
689
|
+
args = [plaintext_to_html(arg) for arg in args]
|
690
|
+
if func_doc.vararg: args.append('*'+func_doc.vararg)
|
691
|
+
if func_doc.kwarg: args.append('**'+func_doc.kwarg)
|
692
|
+
label = '%s(%s)' % (var_doc.name, ', '.join(args))
|
693
|
+
# Get the URL
|
694
|
+
url = self.linker.url_for(var_doc) or NOOP_URL
|
695
|
+
# Construct & return the pseudo-html code
|
696
|
+
return self._OPERATION_CELL % (url, self._tooltip(var_doc), label)
|
697
|
+
|
698
|
+
def _operation_arg(self, name, default, func_doc):
|
699
|
+
"""
|
700
|
+
:todo: Handle tuple args better
|
701
|
+
:todo: Optionally add type info?
|
702
|
+
"""
|
703
|
+
if default is None:
|
704
|
+
return '%s' % name
|
705
|
+
else:
|
706
|
+
pyval_repr = default.summary_pyval_repr().to_plaintext(None)
|
707
|
+
return '%s=%s' % (name, pyval_repr)
|
708
|
+
|
709
|
+
def _qualifier_cell(self, key_label, port):
|
710
|
+
return self._QUALIFIER_CELL % (port, self.bgcolor, key_label)
|
711
|
+
|
712
|
+
#: args: (url, tooltip, label)
|
713
|
+
_ATTRIBUTE_CELL = '''
|
714
|
+
<TR><TD ALIGN="LEFT" HREF="%s" TOOLTIP="%s">%s</TD></TR>
|
715
|
+
'''
|
716
|
+
|
717
|
+
#: args: (url, tooltip, label)
|
718
|
+
_OPERATION_CELL = '''
|
719
|
+
<TR><TD ALIGN="LEFT" HREF="%s" TOOLTIP="%s">%s</TD></TR>
|
720
|
+
'''
|
721
|
+
|
722
|
+
#: args: (port, bgcolor, label)
|
723
|
+
_QUALIFIER_CELL = '''
|
724
|
+
<TR><TD VALIGN="BOTTOM" PORT="%s" BGCOLOR="%s" BORDER="1">%s</TD></TR>
|
725
|
+
'''
|
726
|
+
|
727
|
+
_QUALIFIER_DIV = '''
|
728
|
+
<TR><TD VALIGN="BOTTOM" HEIGHT="10" WIDTH="10" FIXEDSIZE="TRUE"></TD></TR>
|
729
|
+
'''
|
730
|
+
|
731
|
+
#: Args: (rowspan, bgcolor, classname, attributes, operations, qualifiers)
|
732
|
+
_LABEL = '''
|
733
|
+
<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" CELLPADDING="0">
|
734
|
+
<TR><TD ROWSPAN="%s">
|
735
|
+
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"
|
736
|
+
CELLPADDING="0" PORT="body" BGCOLOR="%s">
|
737
|
+
<TR><TD>%s</TD></TR>
|
738
|
+
<TR><TD><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0">
|
739
|
+
%s</TABLE></TD></TR>
|
740
|
+
<TR><TD><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0">
|
741
|
+
%s</TABLE></TD></TR>
|
742
|
+
</TABLE>
|
743
|
+
</TD></TR>
|
744
|
+
%s
|
745
|
+
</TABLE>'''
|
746
|
+
|
747
|
+
_COLLAPSED_LABEL = '''
|
748
|
+
<TABLE CELLBORDER="0" BGCOLOR="%s" PORT="body">
|
749
|
+
<TR><TD>%s</TD></TR>
|
750
|
+
</TABLE>'''
|
751
|
+
|
752
|
+
def _get_html_label(self):
|
753
|
+
# Get the class name & contextualize it.
|
754
|
+
classname = self.class_doc.canonical_name
|
755
|
+
classname = classname.contextualize(self.context.canonical_name)
|
756
|
+
|
757
|
+
# If we're collapsed, display the node as a single box.
|
758
|
+
if self.collapsed:
|
759
|
+
return self._COLLAPSED_LABEL % (self.bgcolor, classname)
|
760
|
+
|
761
|
+
# Construct the attribute list. (If it's too long, truncate)
|
762
|
+
attrib_cells = [self._attribute_cell(a) for a in self.attributes]
|
763
|
+
max_attributes = self.options.get('max_attributes', 15)
|
764
|
+
if len(attrib_cells) == 0:
|
765
|
+
attrib_cells = ['<TR><TD></TD></TR>']
|
766
|
+
elif len(attrib_cells) > max_attributes:
|
767
|
+
attrib_cells[max_attributes-2:-1] = ['<TR><TD>...</TD></TR>']
|
768
|
+
attributes = ''.join(attrib_cells)
|
769
|
+
|
770
|
+
# Construct the operation list. (If it's too long, truncate)
|
771
|
+
oper_cells = [self._operation_cell(a) for a in self.operations]
|
772
|
+
max_operations = self.options.get('max_operations', 15)
|
773
|
+
if len(oper_cells) == 0:
|
774
|
+
oper_cells = ['<TR><TD></TD></TR>']
|
775
|
+
elif len(oper_cells) > max_operations:
|
776
|
+
oper_cells[max_operations-2:-1] = ['<TR><TD>...</TD></TR>']
|
777
|
+
operations = ''.join(oper_cells)
|
778
|
+
|
779
|
+
# Construct the qualifier list & determine the rowspan.
|
780
|
+
if self.qualifiers:
|
781
|
+
rowspan = len(self.qualifiers)*2+2
|
782
|
+
div = self._QUALIFIER_DIV
|
783
|
+
qualifiers = div+div.join([self._qualifier_cell(l,p) for
|
784
|
+
(l,p) in self.qualifiers])+div
|
785
|
+
else:
|
786
|
+
rowspan = 1
|
787
|
+
qualifiers = ''
|
788
|
+
|
789
|
+
# Put it all together.
|
790
|
+
return self._LABEL % (rowspan, self.bgcolor, classname,
|
791
|
+
attributes, operations, qualifiers)
|
792
|
+
|
793
|
+
def to_dotfile(self):
|
794
|
+
attribs = ['%s="%s"' % (k,v) for (k,v) in self._attribs.items()]
|
795
|
+
attribs.append('label=<%s>' % self._get_html_label())
|
796
|
+
s = 'node%d%s' % (self.id, ' [%s]' % (','.join(attribs)))
|
797
|
+
if not self.collapsed:
|
798
|
+
for edge in self.edges:
|
799
|
+
s += '\n' + edge.to_dotfile()
|
800
|
+
return s
|
801
|
+
|
802
|
+
class DotGraphUmlModuleNode(DotGraphNode):
|
803
|
+
"""
|
804
|
+
A specialized dot grah node used to display `ModuleDoc`\s using
|
805
|
+
UML notation. Simple module nodes look like::
|
806
|
+
|
807
|
+
.----.
|
808
|
+
+------------+
|
809
|
+
| modulename |
|
810
|
+
+------------+
|
811
|
+
|
812
|
+
Packages nodes are drawn with their modules & subpackages nested
|
813
|
+
inside::
|
814
|
+
|
815
|
+
.----.
|
816
|
+
+----------------------------------------+
|
817
|
+
| packagename |
|
818
|
+
| |
|
819
|
+
| .----. .----. .----. |
|
820
|
+
| +---------+ +---------+ +---------+ |
|
821
|
+
| | module1 | | module2 | | module3 | |
|
822
|
+
| +---------+ +---------+ +---------+ |
|
823
|
+
| |
|
824
|
+
+----------------------------------------+
|
825
|
+
|
826
|
+
"""
|
827
|
+
def __init__(self, module_doc, linker, context, collapsed=False,
|
828
|
+
excluded_submodules=(), **options):
|
829
|
+
self.module_doc = module_doc
|
830
|
+
self.linker = linker
|
831
|
+
self.context = context
|
832
|
+
self.collapsed = collapsed
|
833
|
+
self.options = options
|
834
|
+
self.excluded_submodules = excluded_submodules
|
835
|
+
DotGraphNode.__init__(self, shape='plaintext',
|
836
|
+
href=linker.url_for(module_doc) or NOOP_URL,
|
837
|
+
tooltip=module_doc.canonical_name)
|
838
|
+
|
839
|
+
#: Expects: (color, color, url, tooltip, body)
|
840
|
+
_MODULE_LABEL = '''
|
841
|
+
<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" ALIGN="LEFT">
|
842
|
+
<TR><TD ALIGN="LEFT" VALIGN="BOTTOM" HEIGHT="8" WIDTH="16"
|
843
|
+
FIXEDSIZE="true" BGCOLOR="%s" BORDER="1" PORT="tab"></TD></TR>
|
844
|
+
<TR><TD ALIGN="LEFT" VALIGN="TOP" BGCOLOR="%s" BORDER="1" WIDTH="20"
|
845
|
+
PORT="body" HREF="%s" TOOLTIP="%s">%s</TD></TR>
|
846
|
+
</TABLE>'''
|
847
|
+
|
848
|
+
#: Expects: (name, body_rows)
|
849
|
+
_NESTED_BODY = '''
|
850
|
+
<TABLE BORDER="0" CELLBORDER="0" CELLPADDING="0" CELLSPACING="0">
|
851
|
+
<TR><TD ALIGN="LEFT">%s</TD></TR>
|
852
|
+
%s
|
853
|
+
</TABLE>'''
|
854
|
+
|
855
|
+
#: Expects: (cells,)
|
856
|
+
_NESTED_BODY_ROW = '''
|
857
|
+
<TR><TD>
|
858
|
+
<TABLE BORDER="0" CELLBORDER="0"><TR>%s</TR></TABLE>
|
859
|
+
</TD></TR>'''
|
860
|
+
|
861
|
+
def _get_html_label(self, package):
|
862
|
+
"""
|
863
|
+
:Return: (label, depth, width) where:
|
864
|
+
|
865
|
+
- ``label`` is the HTML label
|
866
|
+
- ``depth`` is the depth of the package tree (for coloring)
|
867
|
+
- ``width`` is the max width of the HTML label, roughly in
|
868
|
+
units of characters.
|
869
|
+
"""
|
870
|
+
MAX_ROW_WIDTH = 80 # unit is roughly characters.
|
871
|
+
pkg_name = package.canonical_name
|
872
|
+
pkg_url = self.linker.url_for(package) or NOOP_URL
|
873
|
+
|
874
|
+
if (not package.is_package or len(package.submodules) == 0 or
|
875
|
+
self.collapsed):
|
876
|
+
pkg_color = self._color(package, 1)
|
877
|
+
label = self._MODULE_LABEL % (pkg_color, pkg_color,
|
878
|
+
pkg_url, pkg_name, pkg_name[-1])
|
879
|
+
return (label, 1, len(pkg_name[-1])+3)
|
880
|
+
|
881
|
+
# Get the label for each submodule, and divide them into rows.
|
882
|
+
row_list = ['']
|
883
|
+
row_width = 0
|
884
|
+
max_depth = 0
|
885
|
+
max_row_width = len(pkg_name[-1])+3
|
886
|
+
for submodule in package.submodules:
|
887
|
+
if submodule in self.excluded_submodules: continue
|
888
|
+
# Get the submodule's label.
|
889
|
+
label, depth, width = self._get_html_label(submodule)
|
890
|
+
# Check if we should start a new row.
|
891
|
+
if row_width > 0 and width+row_width > MAX_ROW_WIDTH:
|
892
|
+
row_list.append('')
|
893
|
+
row_width = 0
|
894
|
+
# Add the submodule's label to the row.
|
895
|
+
row_width += width
|
896
|
+
row_list[-1] += '<TD ALIGN="LEFT">%s</TD>' % label
|
897
|
+
# Update our max's.
|
898
|
+
max_depth = max(depth, max_depth)
|
899
|
+
max_row_width = max(row_width, max_row_width)
|
900
|
+
|
901
|
+
# Figure out which color to use.
|
902
|
+
pkg_color = self._color(package, depth+1)
|
903
|
+
|
904
|
+
# Assemble & return the label.
|
905
|
+
rows = ''.join([self._NESTED_BODY_ROW % r for r in row_list])
|
906
|
+
body = self._NESTED_BODY % (pkg_name, rows)
|
907
|
+
label = self._MODULE_LABEL % (pkg_color, pkg_color,
|
908
|
+
pkg_url, pkg_name, body)
|
909
|
+
return label, max_depth+1, max_row_width
|
910
|
+
|
911
|
+
_COLOR_DIFF = 24
|
912
|
+
def _color(self, package, depth):
|
913
|
+
if package == self.context: return SELECTED_BG
|
914
|
+
else:
|
915
|
+
# Parse the base color.
|
916
|
+
if re.match(MODULE_BG, 'r#[0-9a-fA-F]{6}$'):
|
917
|
+
base = int(MODULE_BG[1:], 16)
|
918
|
+
else:
|
919
|
+
base = int('d8e8ff', 16)
|
920
|
+
red = (base & 0xff0000) >> 16
|
921
|
+
green = (base & 0x00ff00) >> 8
|
922
|
+
blue = (base & 0x0000ff)
|
923
|
+
# Make it darker with each level of depth. (but not *too*
|
924
|
+
# dark -- package name needs to be readable)
|
925
|
+
red = max(64, red-(depth-1)*self._COLOR_DIFF)
|
926
|
+
green = max(64, green-(depth-1)*self._COLOR_DIFF)
|
927
|
+
blue = max(64, blue-(depth-1)*self._COLOR_DIFF)
|
928
|
+
# Convert it back to a color string
|
929
|
+
return '#%06x' % ((red<<16)+(green<<8)+blue)
|
930
|
+
|
931
|
+
def to_dotfile(self):
|
932
|
+
attribs = ['%s="%s"' % (k,v) for (k,v) in self._attribs.items()]
|
933
|
+
label, depth, width = self._get_html_label(self.module_doc)
|
934
|
+
attribs.append('label=<%s>' % label)
|
935
|
+
return 'node%d%s' % (self.id, ' [%s]' % (','.join(attribs)))
|
936
|
+
|
937
|
+
|
938
|
+
|
939
|
+
######################################################################
|
940
|
+
#{ Graph Generation Functions
|
941
|
+
######################################################################
|
942
|
+
|
943
|
+
def package_tree_graph(packages, linker, context=None, **options):
|
944
|
+
"""
|
945
|
+
Return a `DotGraph` that graphically displays the package
|
946
|
+
hierarchies for the given packages.
|
947
|
+
"""
|
948
|
+
if options.get('style', 'uml') == 'uml': # default to uml style?
|
949
|
+
if get_dot_version() >= [2]:
|
950
|
+
return uml_package_tree_graph(packages, linker, context,
|
951
|
+
**options)
|
952
|
+
elif 'style' in options:
|
953
|
+
log.warning('UML style package trees require dot version 2.0+')
|
954
|
+
|
955
|
+
graph = DotGraph('Package Tree for %s' % name_list(packages, context),
|
956
|
+
body='ranksep=.3\n;nodesep=.1\n',
|
957
|
+
edge_defaults={'dir':'none'})
|
958
|
+
|
959
|
+
# Options
|
960
|
+
if options.get('dir', 'TB') != 'TB': # default: top-to-bottom
|
961
|
+
graph.body += 'rankdir=%s\n' % options.get('dir', 'TB')
|
962
|
+
|
963
|
+
# Get a list of all modules in the package.
|
964
|
+
queue = list(packages)
|
965
|
+
modules = set(packages)
|
966
|
+
for module in queue:
|
967
|
+
queue.extend(module.submodules)
|
968
|
+
modules.update(module.submodules)
|
969
|
+
|
970
|
+
# Add a node for each module.
|
971
|
+
nodes = add_valdoc_nodes(graph, modules, linker, context)
|
972
|
+
|
973
|
+
# Add an edge for each package/submodule relationship.
|
974
|
+
for module in modules:
|
975
|
+
for submodule in module.submodules:
|
976
|
+
graph.edges.append(DotGraphEdge(nodes[module], nodes[submodule],
|
977
|
+
headport='tab'))
|
978
|
+
|
979
|
+
return graph
|
980
|
+
|
981
|
+
def uml_package_tree_graph(packages, linker, context=None, **options):
|
982
|
+
"""
|
983
|
+
Return a `DotGraph` that graphically displays the package
|
984
|
+
hierarchies for the given packages as a nested set of UML
|
985
|
+
symbols.
|
986
|
+
"""
|
987
|
+
graph = DotGraph('Package Tree for %s' % name_list(packages, context))
|
988
|
+
# Remove any packages whose containers are also in the list.
|
989
|
+
root_packages = []
|
990
|
+
for package1 in packages:
|
991
|
+
for package2 in packages:
|
992
|
+
if (package1 is not package2 and
|
993
|
+
package2.canonical_name.dominates(package1.canonical_name)):
|
994
|
+
break
|
995
|
+
else:
|
996
|
+
root_packages.append(package1)
|
997
|
+
# If the context is a variable, then get its value.
|
998
|
+
if isinstance(context, VariableDoc) and context.value is not UNKNOWN:
|
999
|
+
context = context.value
|
1000
|
+
# Return a graph with one node for each root package.
|
1001
|
+
for package in root_packages:
|
1002
|
+
graph.nodes.append(DotGraphUmlModuleNode(package, linker, context))
|
1003
|
+
return graph
|
1004
|
+
|
1005
|
+
######################################################################
|
1006
|
+
def class_tree_graph(bases, linker, context=None, **options):
|
1007
|
+
"""
|
1008
|
+
Return a `DotGraph` that graphically displays the class
|
1009
|
+
hierarchy for the given classes. Options:
|
1010
|
+
|
1011
|
+
- exclude
|
1012
|
+
- dir: LR|RL|BT requests a left-to-right, right-to-left, or
|
1013
|
+
bottom-to- top, drawing. (corresponds to the dot option
|
1014
|
+
'rankdir'
|
1015
|
+
"""
|
1016
|
+
if isinstance(bases, ClassDoc): bases = [bases]
|
1017
|
+
graph = DotGraph('Class Hierarchy for %s' % name_list(bases, context),
|
1018
|
+
body='ranksep=0.3\n',
|
1019
|
+
edge_defaults={'sametail':True, 'dir':'none'})
|
1020
|
+
|
1021
|
+
# Options
|
1022
|
+
if options.get('dir', 'TB') != 'TB': # default: top-down
|
1023
|
+
graph.body += 'rankdir=%s\n' % options.get('dir', 'TB')
|
1024
|
+
exclude = options.get('exclude', ())
|
1025
|
+
|
1026
|
+
# Find all superclasses & subclasses of the given classes.
|
1027
|
+
classes = set(bases)
|
1028
|
+
queue = list(bases)
|
1029
|
+
for cls in queue:
|
1030
|
+
if isinstance(cls, ClassDoc):
|
1031
|
+
if cls.subclasses not in (None, UNKNOWN):
|
1032
|
+
subclasses = cls.subclasses
|
1033
|
+
if exclude:
|
1034
|
+
subclasses = [d for d in subclasses if d not in exclude]
|
1035
|
+
queue.extend(subclasses)
|
1036
|
+
classes.update(subclasses)
|
1037
|
+
queue = list(bases)
|
1038
|
+
for cls in queue:
|
1039
|
+
if isinstance(cls, ClassDoc):
|
1040
|
+
if cls.bases not in (None, UNKNOWN):
|
1041
|
+
bases = cls.bases
|
1042
|
+
if exclude:
|
1043
|
+
bases = [d for d in bases if d not in exclude]
|
1044
|
+
queue.extend(bases)
|
1045
|
+
classes.update(bases)
|
1046
|
+
|
1047
|
+
# Add a node for each cls.
|
1048
|
+
classes = [d for d in classes if isinstance(d, ClassDoc)
|
1049
|
+
if d.pyval is not object]
|
1050
|
+
nodes = add_valdoc_nodes(graph, classes, linker, context)
|
1051
|
+
|
1052
|
+
# Add an edge for each package/subclass relationship.
|
1053
|
+
edges = set()
|
1054
|
+
for cls in classes:
|
1055
|
+
for subcls in cls.subclasses:
|
1056
|
+
if cls in nodes and subcls in nodes:
|
1057
|
+
edges.add((nodes[cls], nodes[subcls]))
|
1058
|
+
graph.edges = [DotGraphEdge(src,dst) for (src,dst) in edges]
|
1059
|
+
|
1060
|
+
return graph
|
1061
|
+
|
1062
|
+
######################################################################
|
1063
|
+
def uml_class_tree_graph(class_doc, linker, context=None, **options):
|
1064
|
+
"""
|
1065
|
+
Return a `DotGraph` that graphically displays the class hierarchy
|
1066
|
+
for the given class, using UML notation. Options:
|
1067
|
+
|
1068
|
+
- max_attributes
|
1069
|
+
- max_operations
|
1070
|
+
- show_private_vars
|
1071
|
+
- show_magic_vars
|
1072
|
+
- link_attributes
|
1073
|
+
"""
|
1074
|
+
nodes = {} # ClassDoc -> DotGraphUmlClassNode
|
1075
|
+
exclude = options.get('exclude', ())
|
1076
|
+
|
1077
|
+
# Create nodes for class_doc and all its bases.
|
1078
|
+
for cls in class_doc.mro():
|
1079
|
+
if cls.pyval is object: continue # don't include `object`.
|
1080
|
+
if cls in exclude: break # stop if we get to an excluded class.
|
1081
|
+
if cls == class_doc: color = SELECTED_BG
|
1082
|
+
else: color = BASECLASS_BG
|
1083
|
+
nodes[cls] = DotGraphUmlClassNode(cls, linker, context,
|
1084
|
+
show_inherited_vars=False,
|
1085
|
+
collapsed=False, bgcolor=color)
|
1086
|
+
|
1087
|
+
# Create nodes for all class_doc's subclasses.
|
1088
|
+
queue = [class_doc]
|
1089
|
+
for cls in queue:
|
1090
|
+
if (isinstance(cls, ClassDoc) and
|
1091
|
+
cls.subclasses not in (None, UNKNOWN)):
|
1092
|
+
for subcls in cls.subclasses:
|
1093
|
+
subcls_name = subcls.canonical_name[-1]
|
1094
|
+
if subcls not in nodes and subcls not in exclude:
|
1095
|
+
queue.append(subcls)
|
1096
|
+
nodes[subcls] = DotGraphUmlClassNode(
|
1097
|
+
subcls, linker, context, collapsed=True,
|
1098
|
+
bgcolor=SUBCLASS_BG)
|
1099
|
+
|
1100
|
+
# Only show variables in the class where they're defined for
|
1101
|
+
# *class_doc*.
|
1102
|
+
mro = class_doc.mro()
|
1103
|
+
for name, var in class_doc.variables.items():
|
1104
|
+
i = mro.index(var.container)
|
1105
|
+
for base in mro[i+1:]:
|
1106
|
+
if base.pyval is object: continue # don't include `object`.
|
1107
|
+
overridden_var = base.variables.get(name)
|
1108
|
+
if overridden_var and overridden_var.container == base:
|
1109
|
+
try:
|
1110
|
+
if isinstance(overridden_var.value, RoutineDoc):
|
1111
|
+
nodes[base].operations.remove(overridden_var)
|
1112
|
+
else:
|
1113
|
+
nodes[base].attributes.remove(overridden_var)
|
1114
|
+
except ValueError:
|
1115
|
+
pass # var is filtered (eg private or magic)
|
1116
|
+
|
1117
|
+
# Keep track of which nodes are part of the inheritance graph
|
1118
|
+
# (since link_attributes might add new nodes)
|
1119
|
+
inheritance_nodes = set(nodes.values())
|
1120
|
+
|
1121
|
+
# Turn attributes into links.
|
1122
|
+
if options.get('link_attributes', True):
|
1123
|
+
for node in nodes.values():
|
1124
|
+
node.link_attributes(nodes)
|
1125
|
+
# Make sure that none of the new attribute edges break the
|
1126
|
+
# rank ordering assigned by inheritance.
|
1127
|
+
for edge in node.edges:
|
1128
|
+
if edge.end in inheritance_nodes:
|
1129
|
+
edge['constraint'] = 'False'
|
1130
|
+
|
1131
|
+
# Construct the graph.
|
1132
|
+
graph = DotGraph('UML class diagram for %s' % class_doc.canonical_name,
|
1133
|
+
body='ranksep=.2\n;nodesep=.3\n')
|
1134
|
+
graph.nodes = nodes.values()
|
1135
|
+
|
1136
|
+
# Add inheritance edges.
|
1137
|
+
for node in inheritance_nodes:
|
1138
|
+
for base in node.class_doc.bases:
|
1139
|
+
if base in nodes:
|
1140
|
+
graph.edges.append(DotGraphEdge(nodes[base], node,
|
1141
|
+
dir='back', arrowtail='empty',
|
1142
|
+
headport='body', tailport='body',
|
1143
|
+
color=INH_LINK_COLOR, weight=100,
|
1144
|
+
style='bold'))
|
1145
|
+
|
1146
|
+
# And we're done!
|
1147
|
+
return graph
|
1148
|
+
|
1149
|
+
######################################################################
|
1150
|
+
def import_graph(modules, docindex, linker, context=None, **options):
|
1151
|
+
graph = DotGraph('Import Graph', body='ranksep=.3\n;nodesep=.3\n')
|
1152
|
+
|
1153
|
+
# Options
|
1154
|
+
if options.get('dir', 'RL') != 'TB': # default: right-to-left.
|
1155
|
+
graph.body += 'rankdir=%s\n' % options.get('dir', 'RL')
|
1156
|
+
|
1157
|
+
# Add a node for each module.
|
1158
|
+
nodes = add_valdoc_nodes(graph, modules, linker, context)
|
1159
|
+
|
1160
|
+
# Edges.
|
1161
|
+
edges = set()
|
1162
|
+
for dst in modules:
|
1163
|
+
if dst.imports in (None, UNKNOWN): continue
|
1164
|
+
for var_name in dst.imports:
|
1165
|
+
for i in range(len(var_name), 0, -1):
|
1166
|
+
val_doc = docindex.find(var_name[:i], context)
|
1167
|
+
if isinstance(val_doc, ModuleDoc):
|
1168
|
+
if val_doc in nodes and dst in nodes:
|
1169
|
+
edges.add((nodes[val_doc], nodes[dst]))
|
1170
|
+
break
|
1171
|
+
graph.edges = [DotGraphEdge(src,dst) for (src,dst) in edges]
|
1172
|
+
|
1173
|
+
return graph
|
1174
|
+
|
1175
|
+
######################################################################
|
1176
|
+
def call_graph(api_docs, docindex, linker, context=None, **options):
|
1177
|
+
"""
|
1178
|
+
:param options:
|
1179
|
+
- ``dir``: rankdir for the graph. (default=LR)
|
1180
|
+
- ``add_callers``: also include callers for any of the
|
1181
|
+
routines in ``api_docs``. (default=False)
|
1182
|
+
- ``add_callees``: also include callees for any of the
|
1183
|
+
routines in ``api_docs``. (default=False)
|
1184
|
+
:todo: Add an ``exclude`` option?
|
1185
|
+
"""
|
1186
|
+
if docindex.callers is None:
|
1187
|
+
log.warning("No profiling information for call graph!")
|
1188
|
+
return DotGraph('Call Graph') # return None instead?
|
1189
|
+
|
1190
|
+
if isinstance(context, VariableDoc):
|
1191
|
+
context = context.value
|
1192
|
+
|
1193
|
+
# Get the set of requested functions.
|
1194
|
+
functions = []
|
1195
|
+
for api_doc in api_docs:
|
1196
|
+
# If it's a variable, get its value.
|
1197
|
+
if isinstance(api_doc, VariableDoc):
|
1198
|
+
api_doc = api_doc.value
|
1199
|
+
# Add the value to the functions list.
|
1200
|
+
if isinstance(api_doc, RoutineDoc):
|
1201
|
+
functions.append(api_doc)
|
1202
|
+
elif isinstance(api_doc, NamespaceDoc):
|
1203
|
+
for vardoc in api_doc.variables.values():
|
1204
|
+
if isinstance(vardoc.value, RoutineDoc):
|
1205
|
+
functions.append(vardoc.value)
|
1206
|
+
|
1207
|
+
# Filter out functions with no callers/callees?
|
1208
|
+
# [xx] this isnt' quite right, esp if add_callers or add_callees
|
1209
|
+
# options are fales.
|
1210
|
+
functions = [f for f in functions if
|
1211
|
+
(f in docindex.callers) or (f in docindex.callees)]
|
1212
|
+
|
1213
|
+
# Add any callers/callees of the selected functions
|
1214
|
+
func_set = set(functions)
|
1215
|
+
if options.get('add_callers', False) or options.get('add_callees', False):
|
1216
|
+
for func_doc in functions:
|
1217
|
+
if options.get('add_callers', False):
|
1218
|
+
func_set.update(docindex.callers.get(func_doc, ()))
|
1219
|
+
if options.get('add_callees', False):
|
1220
|
+
func_set.update(docindex.callees.get(func_doc, ()))
|
1221
|
+
|
1222
|
+
graph = DotGraph('Call Graph for %s' % name_list(api_docs, context),
|
1223
|
+
node_defaults={'shape':'box', 'width': 0, 'height': 0})
|
1224
|
+
|
1225
|
+
# Options
|
1226
|
+
if options.get('dir', 'LR') != 'TB': # default: left-to-right
|
1227
|
+
graph.body += 'rankdir=%s\n' % options.get('dir', 'LR')
|
1228
|
+
|
1229
|
+
nodes = add_valdoc_nodes(graph, func_set, linker, context)
|
1230
|
+
|
1231
|
+
# Find the edges.
|
1232
|
+
edges = set()
|
1233
|
+
for func_doc in functions:
|
1234
|
+
for caller in docindex.callers.get(func_doc, ()):
|
1235
|
+
if caller in nodes:
|
1236
|
+
edges.add( (nodes[caller], nodes[func_doc]) )
|
1237
|
+
for callee in docindex.callees.get(func_doc, ()):
|
1238
|
+
if callee in nodes:
|
1239
|
+
edges.add( (nodes[func_doc], nodes[callee]) )
|
1240
|
+
graph.edges = [DotGraphEdge(src,dst) for (src,dst) in edges]
|
1241
|
+
|
1242
|
+
return graph
|
1243
|
+
|
1244
|
+
######################################################################
|
1245
|
+
#{ Dot Version
|
1246
|
+
######################################################################
|
1247
|
+
|
1248
|
+
_dot_version = None
|
1249
|
+
_DOT_VERSION_RE = re.compile(r'dot version ([\d\.]+)')
|
1250
|
+
def get_dot_version():
|
1251
|
+
global _dot_version
|
1252
|
+
if _dot_version is None:
|
1253
|
+
try:
|
1254
|
+
out, err = run_subprocess([DOT_COMMAND, '-V'])
|
1255
|
+
version_info = err or out
|
1256
|
+
m = _DOT_VERSION_RE.match(version_info)
|
1257
|
+
if m:
|
1258
|
+
_dot_version = [int(x) for x in m.group(1).split('.')]
|
1259
|
+
else:
|
1260
|
+
_dot_version = (0,)
|
1261
|
+
except OSError, e:
|
1262
|
+
_dot_version = (0,)
|
1263
|
+
log.info('Detected dot version %s' % _dot_version)
|
1264
|
+
return _dot_version
|
1265
|
+
|
1266
|
+
######################################################################
|
1267
|
+
#{ Helper Functions
|
1268
|
+
######################################################################
|
1269
|
+
|
1270
|
+
def add_valdoc_nodes(graph, val_docs, linker, context):
|
1271
|
+
"""
|
1272
|
+
:todo: Use different node styles for different subclasses of APIDoc
|
1273
|
+
"""
|
1274
|
+
nodes = {}
|
1275
|
+
val_docs = sorted(val_docs, key=lambda d:d.canonical_name)
|
1276
|
+
for i, val_doc in enumerate(val_docs):
|
1277
|
+
label = val_doc.canonical_name.contextualize(context.canonical_name)
|
1278
|
+
node = nodes[val_doc] = DotGraphNode(label)
|
1279
|
+
graph.nodes.append(node)
|
1280
|
+
specialize_valdoc_node(node, val_doc, context, linker.url_for(val_doc))
|
1281
|
+
return nodes
|
1282
|
+
|
1283
|
+
NOOP_URL = 'javascript:void(0);'
|
1284
|
+
MODULE_NODE_HTML = '''
|
1285
|
+
<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0"
|
1286
|
+
CELLPADDING="0" PORT="table" ALIGN="LEFT">
|
1287
|
+
<TR><TD ALIGN="LEFT" VALIGN="BOTTOM" HEIGHT="8" WIDTH="16" FIXEDSIZE="true"
|
1288
|
+
BGCOLOR="%s" BORDER="1" PORT="tab"></TD></TR>
|
1289
|
+
<TR><TD ALIGN="LEFT" VALIGN="TOP" BGCOLOR="%s" BORDER="1"
|
1290
|
+
PORT="body" HREF="%s" TOOLTIP="%s">%s</TD></TR>
|
1291
|
+
</TABLE>'''.strip()
|
1292
|
+
|
1293
|
+
def specialize_valdoc_node(node, val_doc, context, url):
|
1294
|
+
"""
|
1295
|
+
Update the style attributes of `node` to reflext its type
|
1296
|
+
and context.
|
1297
|
+
"""
|
1298
|
+
# We can only use html-style nodes if dot_version>2.
|
1299
|
+
dot_version = get_dot_version()
|
1300
|
+
|
1301
|
+
# If val_doc or context is a variable, get its value.
|
1302
|
+
if isinstance(val_doc, VariableDoc) and val_doc.value is not UNKNOWN:
|
1303
|
+
val_doc = val_doc.value
|
1304
|
+
if isinstance(context, VariableDoc) and context.value is not UNKNOWN:
|
1305
|
+
context = context.value
|
1306
|
+
|
1307
|
+
# Set the URL. (Do this even if it points to the page we're
|
1308
|
+
# currently on; otherwise, the tooltip is ignored.)
|
1309
|
+
node['href'] = url or NOOP_URL
|
1310
|
+
|
1311
|
+
if isinstance(val_doc, ModuleDoc) and dot_version >= [2]:
|
1312
|
+
node['shape'] = 'plaintext'
|
1313
|
+
if val_doc == context: color = SELECTED_BG
|
1314
|
+
else: color = MODULE_BG
|
1315
|
+
node['tooltip'] = node['label']
|
1316
|
+
node['html_label'] = MODULE_NODE_HTML % (color, color, url,
|
1317
|
+
val_doc.canonical_name,
|
1318
|
+
node['label'])
|
1319
|
+
node['width'] = node['height'] = 0
|
1320
|
+
node.port = 'body'
|
1321
|
+
|
1322
|
+
elif isinstance(val_doc, RoutineDoc):
|
1323
|
+
node['shape'] = 'box'
|
1324
|
+
node['style'] = 'rounded'
|
1325
|
+
node['width'] = 0
|
1326
|
+
node['height'] = 0
|
1327
|
+
node['label'] = '%s()' % node['label']
|
1328
|
+
node['tooltip'] = node['label']
|
1329
|
+
if val_doc == context:
|
1330
|
+
node['fillcolor'] = SELECTED_BG
|
1331
|
+
node['style'] = 'filled,rounded,bold'
|
1332
|
+
|
1333
|
+
else:
|
1334
|
+
node['shape'] = 'box'
|
1335
|
+
node['width'] = 0
|
1336
|
+
node['height'] = 0
|
1337
|
+
node['tooltip'] = node['label']
|
1338
|
+
if val_doc == context:
|
1339
|
+
node['fillcolor'] = SELECTED_BG
|
1340
|
+
node['style'] = 'filled,bold'
|
1341
|
+
|
1342
|
+
def name_list(api_docs, context=None):
|
1343
|
+
if context is not None:
|
1344
|
+
context = context.canonical_name
|
1345
|
+
names = [str(d.canonical_name.contextualize(context)) for d in api_docs]
|
1346
|
+
if len(names) == 0: return ''
|
1347
|
+
if len(names) == 1: return '%s' % names[0]
|
1348
|
+
elif len(names) == 2: return '%s and %s' % (names[0], names[1])
|
1349
|
+
else:
|
1350
|
+
return '%s, and %s' % (', '.join(names[:-1]), names[-1])
|
1351
|
+
|