Source code for pydeps.cli

# -*- coding: utf-8 -*-
"""
command line interface (cli) code.
"""
# pylint: disable=line-too-long
from __future__ import print_function
import argparse

from pydeps.configs import Config
from .arguments import Arguments
# import json
# from .pycompat import configparser
import logging
import os
import sys
import subprocess
import textwrap
from . import __version__


[docs] def error(*args, **kwargs): # pragma: nocover """Print an error message and exit. """ kwargs['file'] = sys.stderr print("\n\tERROR:", *args, **kwargs) if args and args[0].startswith("[Errno 2] No such file or directory"): print("\t(Did you forget to include an __init__.py?)") sys.exit(1)
#: the (will become) verbose function verbose = None def _not_verbose(*args, **kwargs): # pragma: nocover pass verbose = _not_verbose def _mkverbose(level): def _verbose(n, *args, **kwargs): if not isinstance(n, int): # we're only interested in small integers # this allows the simpler usage cli.verbose(msg) args = (n,) + args n = 1 if 0 < level <= n: print(*args, **kwargs) return _verbose def _find_current_package(): cwd = os.getcwd() while 'setup.py' not in os.listdir(cwd) and cwd != os.path.dirname(cwd): cwd = os.path.dirname(cwd) if 'setup.py' not in os.listdir(cwd): raise Exception("--find-package didn't find setup.py in current, or any parent, directory") os.chdir(cwd) package_name = subprocess.check_output("python setup.py --name", shell=True).decode('u8').strip() return package_name
[docs] def base_argparser(argv=()): """Initial parser that can set values for the rest of the parsing process. """ global verbose verbose = _not_verbose _p = argparse.ArgumentParser(add_help=False) _p.add_argument('--debug', action='store_true', dest='debug', help="turn on all the show and verbose options (mainly for debugging pydeps itself)") _p.add_argument('--config', help="specify config file", metavar="FILE") _p.add_argument('--no-config', help="disable processing of config files", action='store_true') _p.add_argument('--version', action='store_true', help='print pydeps version') _p.add_argument('-L', '--log', help=textwrap.dedent(''' set log-level to one of CRITICAL, ERROR, WARNING, INFO, DEBUG, NOTSET. ''')) _p.add_argument('--find-package', action='store_true', help="tries to automatically find the name of the current package.") _args, argv = _p.parse_known_args(argv) if _args.log: loglevels = "CRITICAL DEBUG ERROR FATAL INFO WARN" if _args.log not in loglevels: # pragma: nocover error('legal values for the -L parameter are:', loglevels) loglevel = getattr(logging, _args.log) else: loglevel = None logging.basicConfig( level=loglevel, format='%(filename)s:%(lineno)d: %(levelname)s: %(message)s' ) if _args.version: # pragma: nocover print("pydeps v" + __version__) sys.exit(0) return _p, _args, argv # return parsed and remaining args
[docs] def parse_args(argv=()): """Parse command line arguments, and return a dict. """ _p, _args, argv = base_argparser(argv) find_package = _args.find_package config_files = [] if not _args.no_config: # process config files # extra config file specified with --config <fname> has highest precedence if _args.config: config_files.append(_args.config) # .pydeps file specified in current directory is next local_pydeps = os.path.join(os.getcwd(), '.pydeps') if os.path.exists(local_pydeps): config_files.append(local_pydeps) # pydeps.yml file specified in current directory is next local_pydeps = os.path.join(os.getcwd(), 'pydeps.yml') if os.path.exists(local_pydeps): config_files.append(local_pydeps) # is there a pyproject.toml file? pyproj = os.path.join(os.getcwd(), 'pyproject.toml') if os.path.exists(pyproj): config_files.append(pyproj) # is there a setup.cfg file? pyproj = os.path.join(os.getcwd(), 'setup.cfg') if os.path.exists(pyproj): config_files.append(pyproj) # finally the .pydeps file in the the user's homedir home = os.environ['USERPROFILE' if sys.platform == 'win32' else 'HOME'] home_pydeps = os.path.join(home, '.pydeps') if os.path.exists(home_pydeps): config_files.append(home_pydeps) args = Arguments(config_files, debug=True, parents=[_p]) if not find_package: args.add('fname', kind="FNAME:input", help='filename') else: args.add('--fname', kind="FNAME:input", help='filename') args.add('-v', '--verbose', default=0, dest='verbose', action='count', help="be more verbose (-vv, -vvv for more verbosity)") args.add('-o', default=None, kind="FNAME:output", dest='output', metavar="file", help="write output to 'file'") args.add('-T', default='svg', dest='format', help="output format (svg|png)") args.add('--display', kind="FNAME:exe", default=None, help="program to use to display the graph (png or svg file depending on the T parameter)", metavar="PROGRAM") args.add('--noshow', '--no-show', action='store_true', default=False, dest='no_show', help="don't call external program to display graph") args.add('--show-deps', action='store_true', help="show output of dependency analysis") args.add('--show-raw-deps', action='store_true', help="show output of dependency analysis before removing skips") args.add('--deps-output', dest='deps_out', default=None, kind="FNAME:output", help="write output of dependency analysis to 'file'") args.add('--show-dot', action='store_true', help="show output of dot conversion") args.add('--dot-output', dest='dot_out', default=None, kind="FNAME:output", help="write dot code to 'file'") args.add('--nodot', '--no-dot', action='store_true', default=False, dest='no_dot', help="skip dot conversion") args.add('--no-output', action='store_true', help="don't create .svg/.png file, implies --no-show (-t/-o will be ignored)") args.add('--show-cycles', action='store_true', help="show only import cycles") args.add('--debug-mf', default=0, type=int, metavar="INT", help="set the ModuleFinder.debug flag to this value") args.add('--noise-level', default=200, type=int, metavar="INT", help="exclude sources or sinks with degree greater than noise-level") args.add('--max-bacon', default=2, type=int, metavar="INT", help="exclude nodes that are more than n hops away (default=2, 0 -> infinite)") args.add('--max-module-depth', default=0, type=int, metavar="INT", help="coalesce deep modules to at most n levels") args.add('--pylib', action='store_true', help="include python std lib modules") args.add('--pylib-all', action='store_true', help="include python all std lib modules (incl. C modules)") args.add('--include-missing', action='store_true', help="include modules that are not installed (or can't be found on sys.path)") args.add('-x', '--exclude', default=[], nargs="+", metavar="PATTERN", help="input files to skip (e.g. `foo.*`), multiple file names can be provided") args.add('-xx', '--exclude-exact', default=[], nargs="+", metavar="MODULE", help="same as --exclude, except requires the full match. `-xx foo.bar` will exclude foo.bar, but not foo.bar.blob") args.add('--only', default=[], nargs="+", metavar="MODULE_PATH", help="only include modules that start with MODULE_PATH") args.add('--externals', action='store_true', help='create list of direct external dependencies') args.add('--reverse', action='store_true', help="draw arrows to (instead of from) imported modules") args.add('--rankdir', default='TB', type=str, choices=['TB', 'BT', 'LR', 'RL'], help="set the direction of the graph, legal values are TB (default, imported modules above importing modules), " "BT (opposite direction of TB), LR (left-to-right), and RL (right-to-left)") args.add('--cluster', action='store_true', help="draw external dependencies as separate clusters") args.add('--min-cluster-size', default=0, type=int, metavar="INT", help="the minimum number of nodes a dependency must have before being clustered (default=0)") args.add('--max-cluster-size', default=0, type=int, metavar="INT", help="the maximum number of nodes a dependency can have before the cluster is collapsed to a single node (default=0)") args.add('--keep-target-cluster', action='store_true', help="draw target module as a cluster") args.add('--collapse-target-cluster', action='store_true', help="collapse target module (--keep-target-cluster will be ignored)") args.add('--rmprefix', default=[], nargs="+", metavar="PREFIX", help="remove PREFIX from the displayed name of the nodes") args.add('--start-color', default=0, type=int, metavar="INT", help="starting value for hue from 0 (red/default) to 360.") # args.write_default_config() _args = args.parse_args(argv) if _args.externals: return dict(vars(Config( externals=True, fname=_args.fname, max_bacon=10, include_missing=True, no_show=True ))) if _args.no_output: _args.no_show = True _args.show = not _args.no_show if _args.no_dot and _args.show_cycles: error("Can't use --no=dot and --show-cycles together") # pragma: nocover if _args.no_dot: _args.show_dot = False if _args.max_bacon == 0: _args.max_bacon = sys.maxsize if ( _args.keep_target_cluster or _args.min_cluster_size > 0 or _args.max_cluster_size > 0 or _args.collapse_target_cluster ): _args.cluster = True if find_package: _args.fname = _find_current_package() _args.format = getattr(_args, 'format', 'svg') verbose = _mkverbose(max(_args.verbose, int(_args.debug))) verbose(2, _args, '\n') if _args.debug: # pragma: nocover _args.verbose = 1 _args.show = True _args.show_deps = True _args.show_dot = True return vars(_args)