src\wily\operators\halstead.py
-- -- --- --- --- --- --- --- ------- ------- ------- | """
-- -- --- --- --- --- --- --- ------- ------- ------- | Halstead operator.
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- | Measures all of the halstead metrics (volume, vocab, difficulty)
-- -- --- --- --- --- --- --- ------- ------- ------- | """
-- -- --- --- --- --- --- --- ------- ------- ------- | import ast
-- -- --- --- --- --- --- --- ------- ------- ------- | import collections
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- | import radon.cli.harvest as harvesters
-- -- --- --- --- --- --- --- ------- ------- ------- | from radon.cli import Config
-- -- --- --- --- --- --- --- ------- ------- ------- | from radon.metrics import Halstead, HalsteadReport, halstead_visitor_report
-- -- --- --- --- --- --- --- ------- ------- ------- | from radon.visitors import HalsteadVisitor
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- | from wily import logger
-- -- --- --- --- --- --- --- ------- ------- ------- | from wily.lang import _
-- -- --- --- --- --- --- --- ------- ------- ------- | from wily.operators import BaseOperator, Metric, MetricType
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- | NumberedHalsteadReport = collections.namedtuple(
-- -- --- --- --- --- --- --- ------- ------- ------- | "NumberedHalsteadReport",
-- -- --- --- --- --- --- --- ------- ------- ------- | HalsteadReport._fields + ("lineno", "endline"),
-- -- --- --- --- --- --- --- ------- ------- ------- | )
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
03 -- --- --- --- --- --- --- ------- ------- ------- | class NumberedHalsteadVisitor(HalsteadVisitor):
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | def __init__(self, context=None, lineno=None, endline=None, classname=None):
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | super().__init__(context)
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | self.lineno = lineno
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | self.endline = endline
03 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | self.class_name = classname
03 -- --- --- --- --- --- --- ------- ------- ------- |
03 02 001 001 002 002 002 004 0004.00 0004.00 0001.00 | def visit_FunctionDef(self, node):
03 02 001 001 002 002 002 004 0004.00 0004.00 0001.00 | if self.class_name:
03 02 001 001 002 002 002 004 0004.00 0004.00 0001.00 | node.name = f"{self.class_name}.{node.name}"
03 02 001 001 002 002 002 004 0004.00 0004.00 0001.00 | super().visit_FunctionDef(node)
03 02 001 001 002 002 002 004 0004.00 0004.00 0001.00 | self.function_visitors[-1].lineno = node.lineno
03 02 001 001 002 002 002 004 0004.00 0004.00 0001.00 | self.function_visitors[-1].endline = node.end_lineno
03 -- --- --- --- --- --- --- ------- ------- ------- |
03 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | def visit_ClassDef(self, node):
03 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | self.class_name = node.name
03 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | for child in node.body:
03 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | visitor = NumberedHalsteadVisitor(classname=self.class_name)
03 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | visitor.visit(child)
03 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | self.function_visitors.extend(visitor.function_visitors)
03 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | self.class_name = None
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 | def number_report(visitor):
-- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 | return NumberedHalsteadReport(
-- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 | *(halstead_visitor_report(visitor) + (visitor.lineno, visitor.endline))
-- -- 001 002 001 002 003 003 0004.75 0002.38 0000.50 | )
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
03 -- --- --- --- --- --- --- ------- ------- ------- | class NumberedHCHarvester(harvesters.HCHarvester):
03 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | def gobble(self, fobj):
03 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | """Analyze the content of the file object."""
03 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | code = fobj.read()
03 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | visitor = NumberedHalsteadVisitor.from_ast(ast.parse(code))
03 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | total = number_report(visitor)
03 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | functions = [(v.context, number_report(v)) for v in visitor.function_visitors]
03 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | return Halstead(total, functions)
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
04 -- --- --- --- --- --- --- ------- ------- ------- | class HalsteadOperator(BaseOperator):
04 -- --- --- --- --- --- --- ------- ------- ------- | """Halstead Operator."""
04 -- --- --- --- --- --- --- ------- ------- ------- |
04 -- --- --- --- --- --- --- ------- ------- ------- | name = "halstead"
04 -- --- --- --- --- --- --- ------- ------- ------- | defaults = {
04 -- --- --- --- --- --- --- ------- ------- ------- | "exclude": None,
04 -- --- --- --- --- --- --- ------- ------- ------- | "ignore": None,
04 -- --- --- --- --- --- --- ------- ------- ------- | "min": "A",
04 -- --- --- --- --- --- --- ------- ------- ------- | "max": "C",
04 -- --- --- --- --- --- --- ------- ------- ------- | "multi": True,
04 -- --- --- --- --- --- --- ------- ------- ------- | "show": False,
04 -- --- --- --- --- --- --- ------- ------- ------- | "sort": False,
04 -- --- --- --- --- --- --- ------- ------- ------- | "by_function": True,
04 -- --- --- --- --- --- --- ------- ------- ------- | "include_ipynb": True,
04 -- --- --- --- --- --- --- ------- ------- ------- | "ipynb_cells": True,
04 -- --- --- --- --- --- --- ------- ------- ------- | }
04 -- --- --- --- --- --- --- ------- ------- ------- |
04 -- --- --- --- --- --- --- ------- ------- ------- | metrics = (
04 -- --- --- --- --- --- --- ------- ------- ------- | Metric("h1", _("Unique Operands"), int, MetricType.AimLow, sum),
04 -- --- --- --- --- --- --- ------- ------- ------- | Metric("h2", _("Unique Operators"), int, MetricType.AimLow, sum),
04 -- --- --- --- --- --- --- ------- ------- ------- | Metric("N1", _("Number of Operands"), int, MetricType.AimLow, sum),
04 -- --- --- --- --- --- --- ------- ------- ------- | Metric("N2", _("Number of Operators"), int, MetricType.AimLow, sum),
04 -- --- --- --- --- --- --- ------- ------- ------- | Metric(
04 -- --- --- --- --- --- --- ------- ------- ------- | "vocabulary", _("Unique vocabulary (h1 + h2)"), int, MetricType.AimLow, sum
04 -- --- --- --- --- --- --- ------- ------- ------- | ),
04 -- --- --- --- --- --- --- ------- ------- ------- | Metric("length", _("Length of application"), int, MetricType.AimLow, sum),
04 -- --- --- --- --- --- --- ------- ------- ------- | Metric("volume", _("Code volume"), float, MetricType.AimLow, sum),
04 -- --- --- --- --- --- --- ------- ------- ------- | Metric("difficulty", _("Difficulty"), float, MetricType.AimLow, sum),
04 -- --- --- --- --- --- --- ------- ------- ------- | Metric("effort", _("Effort"), float, MetricType.AimLow, sum),
04 -- --- --- --- --- --- --- ------- ------- ------- | )
04 -- --- --- --- --- --- --- ------- ------- ------- |
04 -- --- --- --- --- --- --- ------- ------- ------- | default_metric_index = 0 # MI
04 -- --- --- --- --- --- --- ------- ------- ------- |
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | def __init__(self, config, targets):
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | """
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | Instantiate a new HC operator.
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | :param config: The wily configuration.
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | :type config: :class:`WilyConfig`
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | """
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | # TODO : Import config from wily.cfg
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | logger.debug(f"Using {targets} with {self.defaults} for HC metrics")
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | self.harvester = NumberedHCHarvester(targets, config=Config(**self.defaults))
04 -- --- --- --- --- --- --- ------- ------- ------- |
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | def run(self, module, options):
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | """
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | Run the operator.
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 |
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | :param module: The target module path.
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | :type module: ``str``
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 |
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | :param options: Any runtime options.
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | :type options: ``dict``
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 |
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | :return: The operator results.
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | :rtype: ``dict``
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | """
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | logger.debug("Running halstead harvester")
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | results = {}
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | for filename, details in dict(self.harvester.results).items():
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | results[filename] = {"detailed": {}, "total": {}}
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | for instance in details:
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | if isinstance(instance, list):
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | for item in instance:
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | function, report = item
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | results[filename]["detailed"][function] = self._report_to_dict(
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | report
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | )
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | else:
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | if isinstance(instance, str) and instance == "error":
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | logger.debug(
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | f"Failed to run Halstead harvester on {filename} : {details['error']}"
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | )
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | continue
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | results[filename]["total"] = self._report_to_dict(instance)
04 07 002 004 002 004 006 006 0015.51 0015.51 0001.00 | return results
04 -- --- --- --- --- --- --- ------- ------- ------- |
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | def _report_to_dict(self, report):
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | return {
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | "h1": report.h1,
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | "h2": report.h2,
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | "N1": report.N1,
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | "N2": report.N2,
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | "vocabulary": report.vocabulary,
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | "volume": report.volume,
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | "length": report.length,
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | "effort": report.effort,
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | "difficulty": report.difficulty,
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | "lineno": report.lineno,
04 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | "endline": report.endline,
-- -- 000 000 000 000 000 000 0000.00 0000.00 0000.00 | }