Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Raw visitor, take 2 (continues #194) #253

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
10 changes: 9 additions & 1 deletion docs/commandline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,8 @@ The :command:`hal` command

This command analyzes Python source files and computes their Halstead
complexity metrics. Files can be analyzed as wholes, or in terms of their
top-level functions with the :option:`-f` flag.
top-level functions with the :option:`-f` flag. Method names can be
prefixed with their class names if the :option:`-c` flag is used.

Excluding files or directories is supported through glob patterns with the
:option:`-e` flag. Every positional argument is interpreted as a path. The
Expand Down Expand Up @@ -496,6 +497,13 @@ Options

Value can be set in a configuration file using the ``ipynb_cells`` property.

.. option:: -c, --class_names

When showing metrics on the function level, prefix method names with their
class names.

Value can be set in a configuration file using the ``class_names`` property.

Examples
++++++++

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ colorama = [
{version = ">=0.4.1", markers = "python_version > \"3.4\""},
{version = "==0.4.1", markers = "python_version <= \"3.4\""}
]
asttokens = "*"

[tool.poetry.group.dev.dependencies]
coverage = "*"
Expand Down
7 changes: 7 additions & 0 deletions radon/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ def raw(
output_file=_cfg.get_value('output_file', str, None),
include_ipynb=_cfg.get_value('include_ipynb', bool, False),
ipynb_cells=_cfg.get_value('ipynb_cells', bool, False),
detailed=_cfg.get_value('detailed', bool, False),
):
'''Analyze the given Python modules and compute raw metrics.

Expand All @@ -203,13 +204,15 @@ def raw(
:param -O, --output-file <str>: The output file (default to stdout).
:param --include-ipynb: Include IPython Notebook files
:param --ipynb-cells: Include reports for individual IPYNB cells
:param -d, --detailed: Display metrics for functions, classes and methods.
'''
config = Config(
exclude=exclude,
ignore=ignore,
summary=summary,
include_ipynb=include_ipynb,
ipynb_cells=ipynb_cells,
detailed=detailed,
)
harvester = RawHarvester(paths, config)
with outstream(output_file) as stream:
Expand Down Expand Up @@ -284,6 +287,7 @@ def hal(
output_file=_cfg.get_value('output_file', str, None),
include_ipynb=_cfg.get_value('include_ipynb', bool, False),
ipynb_cells=_cfg.get_value('ipynb_cells', bool, False),
class_names=_cfg.get_value('class_names', bool, False),
):
"""
Analyze the given Python modules and compute their Halstead metrics.
Expand All @@ -305,13 +309,16 @@ def hal(
:param -O, --output-file <str>: The output file (default to stdout).
:param --include-ipynb: Include IPython Notebook files
:param --ipynb-cells: Include reports for individual IPYNB cells
:param -c, --class-names: Include class names before method names as
class.method.
"""
config = Config(
exclude=exclude,
ignore=ignore,
by_function=functions,
include_ipynb=include_ipynb,
ipynb_cells=ipynb_cells,
class_names=class_names,
)

harvester = HCHarvester(paths, config)
Expand Down
73 changes: 43 additions & 30 deletions radon/cli/harvest.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
sorted_results,
)
from radon.metrics import h_visit, mi_rank, mi_visit
from radon.raw import analyze
from radon.raw import Module, analyze
from radon.raw_visitor import RawClassMetrics, RawFunctionMetrics, RawVisitor

if sys.version_info[0] < 3:
from StringIO import StringIO
Expand Down Expand Up @@ -267,7 +268,7 @@ class RawHarvester(Harvester):

def gobble(self, fobj):
'''Analyze the content of the file object.'''
return raw_to_dict(analyze(fobj.read()))
return RawVisitor.from_code(fobj.read()).blocks

def as_xml(self):
'''Placeholder method. Currently not implemented.'''
Expand All @@ -276,33 +277,44 @@ def as_xml(self):
def to_terminal(self):
'''Yield lines to be printed to a terminal.'''
sum_metrics = collections.defaultdict(int)
for path, mod in self.results:
if 'error' in mod:
yield path, (mod['error'],), {'error': True}
continue
yield path, (), {}
for header in self.headers:
value = mod[header.lower().replace(' ', '_')]
yield '{0}: {1}', (header, value), {'indent': 1}
sum_metrics[header] += value

loc, comments = mod['loc'], mod['comments']
yield '- Comment Stats', (), {'indent': 1}
yield (
'(C % L): {0:.0%}',
(comments / (float(loc) or 1),),
{'indent': 2},
)
yield (
'(C % S): {0:.0%}',
(comments / (float(mod['sloc']) or 1),),
{'indent': 2},
)
yield (
'(C + M % L): {0:.0%}',
((comments + mod['multi']) / (float(loc) or 1),),
{'indent': 2},
)
for path, mods in self.results:
for name, result in mods:
if isinstance(result, (Module, RawClassMetrics, RawFunctionMetrics)):
mod = raw_to_dict(result)
else:
mod = result
if 'error' in mod:
yield name, (mod['error'],), {'error': True}
continue
if name == "__ModuleMetrics__":
yield path, (), {}
elif not hasattr(self.config, "detailed") or not self.config.detailed:
continue
else:
yield f"{path}:{name}", (), {}
for header in self.headers:
value = mod[header.lower().replace(' ', '_')]
yield '{0}: {1}', (header, value), {'indent': 1}
if name == "__ModuleMetrics__":
sum_metrics[header] += value

loc, comments = mod['loc'], mod['comments']
yield '- Comment Stats', (), {'indent': 1}
yield (
'(C % L): {0:.0%}',
(comments / (float(loc) or 1),),
{'indent': 2},
)
yield (
'(C % S): {0:.0%}',
(comments / (float(mod['sloc']) or 1),),
{'indent': 2},
)
yield (
'(C + M % L): {0:.0%}',
((comments + mod['multi']) / (float(loc) or 1),),
{'indent': 2},
)

if self.config.summary:
_get = lambda k, v=0: sum_metrics.get(k, v)
Expand Down Expand Up @@ -384,11 +396,12 @@ class HCHarvester(Harvester):
def __init__(self, paths, config):
super().__init__(paths, config)
self.by_function = config.by_function
self.class_names = getattr(config, "class_names", False)

def gobble(self, fobj):
"""Analyze the content of the file object."""
code = fobj.read()
return h_visit(code)
return h_visit(code, self.class_names)

def as_json(self):
"""Format the results as JSON."""
Expand Down
8 changes: 4 additions & 4 deletions radon/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@
Halstead = collections.namedtuple("Halstead", "total functions")


def h_visit(code):
def h_visit(code, class_names=False):
'''Compile the code into an AST tree and then pass it to
:func:`~radon.metrics.h_visit_ast`.
'''
return h_visit_ast(ast.parse(code))
return h_visit_ast(ast.parse(code), class_names)


def h_visit_ast(ast_node):
def h_visit_ast(ast_node, class_names=False):
'''
Visit the AST node using the :class:`~radon.visitors.HalsteadVisitor`
visitor. The results are `HalsteadReport` namedtuples with the following
Expand All @@ -56,7 +56,7 @@ def h_visit_ast(ast_node):

Nested functions are not tracked.
'''
visitor = HalsteadVisitor.from_ast(ast_node)
visitor = HalsteadVisitor.from_ast(ast_node, class_names=class_names)
total = halstead_visitor_report(visitor)
functions = [
(v.context, halstead_visitor_report(v))
Expand Down
Loading