# -*- coding: utf-8 -*-
"""cli entrypoints.
"""
from __future__ import print_function
import json
import os
import sys
from pydeps.configs import Config
from . import py2depgraph, cli, dot, target
from .depgraph2dot import dep2dot, cycles2dot
import logging
from . import colors
log = logging.getLogger(__name__)
def _pydeps(trgt, **kw):
# Pass args as a **kw dict since we need to pass it down to functions
# called, but extract locally relevant parameters first to make the
# code prettier (and more fault tolerant).
# print("KW:", kw, '\n', os.getcwd())
# print('abspath:', os.path.abspath(kw.get('deps_out')))
# print('target', trgt.workdir)
# print('target', trgt)
colors.START_COLOR = kw.get('start_color')
# show_cycles = kw.get('show_cycles')
nodot = kw.get('no_dot')
no_output = kw.get('no_output')
output = kw.get('output')
fmt = kw['format']
show_svg = kw.get('show')
deps_out = kw.get('deps_out')
dot_out = kw.get('dot_out')
# reverse = kw.get('reverse')
if os.getcwd() != trgt.workdir:
# the tests are calling _pydeps directoy
os.chdir(trgt.workdir)
dep_graph = py2depgraph.py2dep(trgt, **kw)
if kw.get('show_deps'):
cli.verbose("DEPS:")
if deps_out:
# make sure output files are written to sensible directories
directory, _fname = os.path.split(deps_out)
if not directory:
deps_out = os.path.join(trgt.calling_dir, deps_out)
with open(deps_out, 'w') as fp:
fp.write(dep_graph.__json__())
else:
print(dep_graph.__json__())
dotsrc = depgraph_to_dotsrc(trgt, dep_graph, **kw)
if not nodot:
if kw.get('show_dot'):
cli.verbose("DOTSRC:")
if dot_out:
# make sure output files are written to sensible directories
directory, _fname = os.path.split(dot_out)
if not directory:
dot_out = os.path.join(trgt.calling_dir, dot_out)
with open(dot_out, 'w') as fp:
fp.write(dotsrc)
else:
print(dotsrc)
if not no_output:
try:
svg = dot.call_graphviz_dot(dotsrc, fmt)
except OSError as cause:
raise RuntimeError("While rendering {!r}: {}".format(output, cause))
if fmt == 'svg':
svg = svg.replace(b'</title>', b'</title><style>.edge>path:hover{stroke-width:8}</style>')
try:
with open(output, 'wb') as fp:
cli.verbose("Writing output to:", output)
fp.write(svg)
except OSError as cause:
raise RuntimeError("While writing {!r}: {}".format(output, cause))
if show_svg:
try:
dot.display_svg(kw, output)
except OSError as cause:
helpful = ""
if cause.errno == 2:
helpful = " (can be caused by not finding the program to open this file)"
raise RuntimeError("While opening {!r}: {}{}".format(output, cause, helpful))
[docs]
def depgraph_to_dotsrc(target, dep_graph, **kw):
"""Convert the dependency graph (DepGraph class) to dot source code.
"""
if kw.get('show_cycles'):
dotsrc = cycles2dot(target, dep_graph, **kw)
elif not kw.get('no_dot'):
dotsrc = dep2dot(target, dep_graph, **kw)
else:
dotsrc = None
return dotsrc
[docs]
def externals(trgt, **kwargs):
"""Return a list of direct external dependencies of ``pkgname``.
Called for the ``pydeps --externals`` command.
"""
kw = dict(
T='svg', config=None, debug=False, display=None, exclude=[], exclude_exact=[],
externals=True, format='svg', max_bacon=2**65, no_config=True, nodot=False,
noise_level=2**65, no_show=True, output=None, pylib=True, pylib_all=True,
show=False, show_cycles=False, show_deps=False, show_dot=False,
show_raw_deps=False, verbose=0, include_missing=True, start_color=0
)
kw.update(kwargs)
depgraph = py2depgraph.py2dep(trgt, **kw)
pkgname = trgt.fname
log.info("DEPGRAPH: %s", depgraph)
pkgname = os.path.splitext(pkgname)[0]
res = {}
ext = set()
for k, src in list(depgraph.sources.items()):
if k.startswith('_'):
continue
if not k.startswith(pkgname):
continue
if src.imports:
imps = [imp for imp in src.imports if not imp.startswith(pkgname)]
if imps:
for imp in imps:
ext.add(imp.split('.')[0])
res[k] = imps
# return res # debug
return list(sorted(ext))
[docs]
def pydeps(**args):
"""Entry point for the ``pydeps`` command.
This function should do all the initial parameter and environment
munging before calling ``_pydeps`` (so that function has a clean
execution path).
"""
sys.setrecursionlimit(10000)
_args = dict(iter(Config(**args))) if args else cli.parse_args(sys.argv[1:])
_args['curdir'] = os.getcwd()
inp = target.Target(_args['fname'])
log.debug("Target: %r", inp)
if _args.get('output'):
_args['output'] = os.path.abspath(_args['output'])
else:
_args['output'] = os.path.join(
inp.calling_dir,
inp.modpath.replace('.', '_') + '.' + _args.get('format', 'svg')
)
with inp.chdir_work():
# log.debug("Current directory: %s", os.getcwd())
_args['fname'] = inp.fname
_args['isdir'] = inp.is_dir
if _args.get('externals'):
del _args['fname']
exts = externals(inp, **_args)
print(json.dumps(exts, indent=4))
# return exts # so the tests can assert
else:
# this is the call you're looking for :-)
try:
return _pydeps(inp, **_args)
except (OSError, RuntimeError) as cause:
if log.isEnabledFor(logging.DEBUG):
# we only want to log the exception if we're in debug mode
log.exception("While running pydeps:")
cli.error(str(cause))
[docs]
def call_pydeps(file_or_dir, **kwargs):
"""Programatic entry point for pydeps.
See :class:`pydeps.configs.Config` class for the available options.
"""
sys.setrecursionlimit(10000)
inp = target.Target(file_or_dir)
log.debug("Target: %r", inp)
config = Config(**kwargs)
if config.output:
config.output = os.path.abspath(config.output)
else:
config.output = os.path.join(
inp.calling_dir,
inp.modpath.replace('.', '_') + '.' + config.format
)
ctx = dict(iter(config))
with inp.chdir_work():
ctx['fname'] = inp.fname
ctx['isdir'] = inp.is_dir
if config.externals:
del ctx['fname']
return externals(inp, **ctx)
return _pydeps(inp, **ctx)
if __name__ == '__main__': # pragma: nocover
pydeps()