multimetric 2.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- multimetric/__init__.py +3 -0
- multimetric/__main__.py +215 -0
- multimetric/cls/__init__.py +0 -0
- multimetric/cls/base.py +26 -0
- multimetric/cls/base_calc.py +16 -0
- multimetric/cls/base_stats.py +12 -0
- multimetric/cls/calc/__init__.py +0 -0
- multimetric/cls/calc/halstead.py +308 -0
- multimetric/cls/calc/maintenance.py +228 -0
- multimetric/cls/calc/pylint.py +51 -0
- multimetric/cls/calc/tiobe.py +90 -0
- multimetric/cls/importer/__init__.py +0 -0
- multimetric/cls/importer/base.py +43 -0
- multimetric/cls/importer/filtered.py +9 -0
- multimetric/cls/importer/mods/__init__.py +0 -0
- multimetric/cls/importer/mods/csv_importer.py +22 -0
- multimetric/cls/importer/mods/json_importer.py +24 -0
- multimetric/cls/importer/pick.py +20 -0
- multimetric/cls/metric/__init__.py +0 -0
- multimetric/cls/metric/comments.py +55 -0
- multimetric/cls/metric/cyclomatic.py +100 -0
- multimetric/cls/metric/fanout.py +173 -0
- multimetric/cls/metric/loc.py +33 -0
- multimetric/cls/metric/operands.py +74 -0
- multimetric/cls/metric/operators.py +49 -0
- multimetric/cls/modules.py +52 -0
- multimetric/cls/stats/__init__.py +0 -0
- multimetric/cls/stats/stats.py +39 -0
- multimetric/cls/tokentree.py +89 -0
- multimetric-2.2.1.dist-info/LICENSE.Zlib +17 -0
- multimetric-2.2.1.dist-info/METADATA +246 -0
- multimetric-2.2.1.dist-info/RECORD +35 -0
- multimetric-2.2.1.dist-info/WHEEL +5 -0
- multimetric-2.2.1.dist-info/entry_points.txt +2 -0
- multimetric-2.2.1.dist-info/top_level.txt +1 -0
multimetric/__init__.py
ADDED
multimetric/__main__.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023 Konrad Weihmann
|
|
2
|
+
# SPDX-License-Identifier: Zlib
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import multiprocessing as mp
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import textwrap
|
|
11
|
+
|
|
12
|
+
import chardet
|
|
13
|
+
from pygments import lexers
|
|
14
|
+
|
|
15
|
+
from multimetric.cls.importer.filtered import FilteredImporter
|
|
16
|
+
from multimetric.cls.importer.pick import importer_pick
|
|
17
|
+
from multimetric.cls.modules import get_additional_parser_args
|
|
18
|
+
from multimetric.cls.modules import get_modules_calculated
|
|
19
|
+
from multimetric.cls.modules import get_modules_metrics
|
|
20
|
+
from multimetric.cls.modules import get_modules_stats
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def ArgParser():
|
|
24
|
+
parser = argparse.ArgumentParser(
|
|
25
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
26
|
+
prog="multimetric", description='Calculate code metrics in various languages',
|
|
27
|
+
epilog=textwrap.dedent("""
|
|
28
|
+
Currently you could import files of the following types for --warn_* or --coverage
|
|
29
|
+
|
|
30
|
+
Following information can be read
|
|
31
|
+
|
|
32
|
+
<file> = full path to file
|
|
33
|
+
<severity> = severity [error, warning, info]
|
|
34
|
+
<content> = optional string
|
|
35
|
+
|
|
36
|
+
Note: you could also add a single line, then <content>
|
|
37
|
+
has to be a number reflecting to total number of findings
|
|
38
|
+
|
|
39
|
+
File formats
|
|
40
|
+
|
|
41
|
+
csv: CSV file of following line format
|
|
42
|
+
<file>,<severity>,[<content>]
|
|
43
|
+
|
|
44
|
+
json: JSON file
|
|
45
|
+
<file>: {
|
|
46
|
+
["content": <content>,]
|
|
47
|
+
"severity": <severity>
|
|
48
|
+
}
|
|
49
|
+
"""))
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--warn_compiler",
|
|
52
|
+
default=None,
|
|
53
|
+
help="File(s) holding information about compiler warnings")
|
|
54
|
+
parser.add_argument(
|
|
55
|
+
"--warn_duplication",
|
|
56
|
+
default=None,
|
|
57
|
+
help="File(s) holding information about code duplications")
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"--warn_functional",
|
|
60
|
+
default=None,
|
|
61
|
+
help="File(s) holding information about static code analysis findings")
|
|
62
|
+
parser.add_argument(
|
|
63
|
+
"--warn_standard",
|
|
64
|
+
default=None,
|
|
65
|
+
help="File(s) holding information about language standard violations")
|
|
66
|
+
parser.add_argument(
|
|
67
|
+
"--warn_security",
|
|
68
|
+
default=None,
|
|
69
|
+
help="File(s) File(s) holding information about found security issue")
|
|
70
|
+
parser.add_argument(
|
|
71
|
+
"--coverage",
|
|
72
|
+
default=None,
|
|
73
|
+
help="File(s) with compiler warningsFile(s) holding information about testing coverage")
|
|
74
|
+
parser.add_argument(
|
|
75
|
+
"--dump",
|
|
76
|
+
default=False,
|
|
77
|
+
action="store_true",
|
|
78
|
+
help="Just dump the token tree")
|
|
79
|
+
parser.add_argument(
|
|
80
|
+
"--verbose",
|
|
81
|
+
default=False,
|
|
82
|
+
action="store_true",
|
|
83
|
+
help="Verbose logging output")
|
|
84
|
+
parser.add_argument(
|
|
85
|
+
"--jobs",
|
|
86
|
+
type=int,
|
|
87
|
+
default=mp.cpu_count(),
|
|
88
|
+
help="Run x jobs in parallel")
|
|
89
|
+
get_additional_parser_args(parser)
|
|
90
|
+
parser.add_argument("files", nargs='+', help="Files to parse")
|
|
91
|
+
return parser
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def parse_args(*args):
|
|
95
|
+
RUNARGS = ArgParser().parse_args(*args)
|
|
96
|
+
# Turn all paths to abs-paths right here
|
|
97
|
+
RUNARGS.files = [os.path.abspath(x) for x in RUNARGS.files]
|
|
98
|
+
|
|
99
|
+
# Setup logging
|
|
100
|
+
stdout_log = logging.getLogger('stdout')
|
|
101
|
+
stdout_log.setLevel(logging.DEBUG if RUNARGS.verbose else logging.INFO)
|
|
102
|
+
|
|
103
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
104
|
+
handler.setLevel(logging.DEBUG if RUNARGS.verbose else logging.INFO)
|
|
105
|
+
formatter = logging.Formatter('%(message)s')
|
|
106
|
+
handler.setFormatter(formatter)
|
|
107
|
+
stdout_log.addHandler(handler)
|
|
108
|
+
|
|
109
|
+
stderr_log = logging.getLogger('stderr')
|
|
110
|
+
stderr_log.setLevel(logging.DEBUG if RUNARGS.verbose else logging.INFO)
|
|
111
|
+
|
|
112
|
+
handler = logging.StreamHandler(sys.stderr)
|
|
113
|
+
handler.setLevel(logging.DEBUG if RUNARGS.verbose else logging.INFO)
|
|
114
|
+
formatter = logging.Formatter('%(levelname)s - %(message)s')
|
|
115
|
+
handler.setFormatter(formatter)
|
|
116
|
+
stderr_log.addHandler(handler)
|
|
117
|
+
|
|
118
|
+
return RUNARGS
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def file_process(_file, _args, _importer):
|
|
122
|
+
res = {}
|
|
123
|
+
store = {}
|
|
124
|
+
try:
|
|
125
|
+
_lexer = lexers.get_lexer_for_filename(_file)
|
|
126
|
+
except ValueError: # pragma: no cover - bug in pytest-cov
|
|
127
|
+
logging.getLogger('stderr').error(
|
|
128
|
+
f'The file {_file} could not be identified automatically. Skipping this file.') # pragma: no cover - bug in pytest-cov
|
|
129
|
+
return ({}, _file, 'lexer.error', [], {}) # pragma: no cover - bug in pytest-cov
|
|
130
|
+
try:
|
|
131
|
+
with open(_file, "rb") as i:
|
|
132
|
+
_cnt = i.read()
|
|
133
|
+
_enc = chardet.detect(_cnt)
|
|
134
|
+
_cnt = _cnt.decode(_enc["encoding"]).encode("utf-8")
|
|
135
|
+
_localImporter = {k: FilteredImporter(
|
|
136
|
+
v, _file) for k, v in _importer.items()}
|
|
137
|
+
tokens = list(_lexer.get_tokens(_cnt))
|
|
138
|
+
if _args.dump: # pragma: no cover
|
|
139
|
+
for x in tokens: # pragma: no cover
|
|
140
|
+
logging.getLogger('stdout').info(f"{_file}: {x[0]} -> {repr(x[1])}") # pragma: no cover
|
|
141
|
+
else:
|
|
142
|
+
_localMetrics = get_modules_metrics(_args, **_localImporter)
|
|
143
|
+
_localCalc = get_modules_calculated(_args, **_localImporter)
|
|
144
|
+
for x in _localMetrics:
|
|
145
|
+
x.parse_tokens(_lexer.name, tokens)
|
|
146
|
+
res.update(x.get_results())
|
|
147
|
+
store.update(x.get_internal_store())
|
|
148
|
+
for x in _localCalc:
|
|
149
|
+
res.update(x.get_results(res))
|
|
150
|
+
store.update(x.get_internal_store())
|
|
151
|
+
except Exception as e: # pragma: no cover
|
|
152
|
+
logging.getLogger('stderr').exception(e) # pragma: no cover
|
|
153
|
+
tokens = [] # pragma: no cover
|
|
154
|
+
return (res, _file, _lexer.name, tokens, store)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def run(_args):
|
|
158
|
+
_result = {"files": {}, "overall": {}}
|
|
159
|
+
|
|
160
|
+
# Get importer
|
|
161
|
+
_importer = {}
|
|
162
|
+
_importer["import_compiler"] = importer_pick(_args, _args.warn_compiler)
|
|
163
|
+
_importer["import_coverage"] = importer_pick(_args, _args.coverage)
|
|
164
|
+
_importer["import_duplication"] = importer_pick(
|
|
165
|
+
_args, _args.warn_duplication)
|
|
166
|
+
_importer["import_functional"] = importer_pick(
|
|
167
|
+
_args, _args.warn_functional)
|
|
168
|
+
_importer["import_security"] = importer_pick(_args, _args.warn_standard)
|
|
169
|
+
_importer["import_standard"] = importer_pick(_args, _args.warn_security)
|
|
170
|
+
# sanity check
|
|
171
|
+
_importer = {k: v for k, v in _importer.items() if v}
|
|
172
|
+
|
|
173
|
+
# instance metric modules
|
|
174
|
+
_overallMetrics = get_modules_metrics(_args, **_importer)
|
|
175
|
+
_overallCalc = get_modules_calculated(_args, **_importer)
|
|
176
|
+
|
|
177
|
+
with mp.Pool(processes=_args.jobs) as pool:
|
|
178
|
+
results = [pool.apply(file_process, args=(
|
|
179
|
+
f, _args, _importer)) for f in _args.files]
|
|
180
|
+
|
|
181
|
+
for x in results:
|
|
182
|
+
_result["files"][x[1]] = x[0]
|
|
183
|
+
|
|
184
|
+
for y in _overallMetrics:
|
|
185
|
+
_result["overall"].update(
|
|
186
|
+
y.get_results_global([x[4] for x in results]))
|
|
187
|
+
for y in _overallCalc:
|
|
188
|
+
_result["overall"].update(y.get_results(_result["overall"]))
|
|
189
|
+
for m in get_modules_stats(_args, **_importer):
|
|
190
|
+
_result = m.get_results(_result, "files", "overall")
|
|
191
|
+
|
|
192
|
+
def round_float(item):
|
|
193
|
+
if isinstance(item, dict):
|
|
194
|
+
for k, v in item.items():
|
|
195
|
+
item[k] = round_float(v)
|
|
196
|
+
elif isinstance(item, list):
|
|
197
|
+
for index, value in enumerate(item):
|
|
198
|
+
item[index] = round_float(value)
|
|
199
|
+
elif isinstance(item, float):
|
|
200
|
+
item = round(item, 3)
|
|
201
|
+
return item
|
|
202
|
+
|
|
203
|
+
return round_float(_result)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def main(): # pragma: no cover
|
|
207
|
+
_args = parse_args() # pragma: no cover
|
|
208
|
+
_result = run(_args) # pragma: no cover
|
|
209
|
+
if not _args.dump: # pragma: no cover
|
|
210
|
+
# Output
|
|
211
|
+
logging.getLogger('stdout').info(json.dumps(_result, indent=2, sort_keys=True)) # pragma: no cover
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
if __name__ == '__main__':
|
|
215
|
+
main() # pragma: no cover
|
|
File without changes
|
multimetric/cls/base.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023 Konrad Weihmann
|
|
2
|
+
# SPDX-License-Identifier: Zlib
|
|
3
|
+
class MetricBase():
|
|
4
|
+
def __init__(self, args, **kwargs):
|
|
5
|
+
self._metrics = {"lang": []}
|
|
6
|
+
self._internalstore = {}
|
|
7
|
+
|
|
8
|
+
def parse_tokens(self, language, tokens):
|
|
9
|
+
if language not in self._metrics["lang"]: # pragma: no cover
|
|
10
|
+
self._metrics["lang"].append(language)
|
|
11
|
+
|
|
12
|
+
def get_results(self):
|
|
13
|
+
return self._metrics
|
|
14
|
+
|
|
15
|
+
def get_internal_store(self):
|
|
16
|
+
return {self.__class__.__name__: self._internalstore}
|
|
17
|
+
|
|
18
|
+
def _get_all_matching_store_objects(self, store):
|
|
19
|
+
res = []
|
|
20
|
+
for item in store:
|
|
21
|
+
if self.__class__.__name__ in item:
|
|
22
|
+
res.append(item[self.__class__.__name__])
|
|
23
|
+
return res
|
|
24
|
+
|
|
25
|
+
def get_results_global(self, value_stores):
|
|
26
|
+
return {} # pragma: no cover
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023 Konrad Weihmann
|
|
2
|
+
# SPDX-License-Identifier: Zlib
|
|
3
|
+
class MetricBaseCalc():
|
|
4
|
+
|
|
5
|
+
def __init__(self, args, **kwargs):
|
|
6
|
+
self._metrics = {}
|
|
7
|
+
self._internalstore = {}
|
|
8
|
+
|
|
9
|
+
def get_results(self, metrics):
|
|
10
|
+
"""
|
|
11
|
+
This alters the originally passed metrics by calculated ones
|
|
12
|
+
"""
|
|
13
|
+
return metrics
|
|
14
|
+
|
|
15
|
+
def get_internal_store(self):
|
|
16
|
+
return self._internalstore
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023 Konrad Weihmann
|
|
2
|
+
# SPDX-License-Identifier: Zlib
|
|
3
|
+
class MetricBaseStats():
|
|
4
|
+
|
|
5
|
+
def __init__(self, args, **kwargs):
|
|
6
|
+
self._metrics = {}
|
|
7
|
+
|
|
8
|
+
def get_results(self, metrics, files="files", overall="overall"):
|
|
9
|
+
"""
|
|
10
|
+
This alters the originally passed metrics by calculated ones
|
|
11
|
+
"""
|
|
12
|
+
return metrics
|
|
File without changes
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023 Konrad Weihmann
|
|
2
|
+
# SPDX-License-Identifier: Zlib
|
|
3
|
+
|
|
4
|
+
import math
|
|
5
|
+
|
|
6
|
+
from multimetric.cls.base_calc import MetricBaseCalc
|
|
7
|
+
from multimetric.cls.metric.operands import MetricBaseOperands
|
|
8
|
+
from multimetric.cls.metric.operators import MetricBaseOperator
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MetricBaseCalcHalstead(MetricBaseCalc):
|
|
12
|
+
"""A class for calculating Halstead metrics.
|
|
13
|
+
|
|
14
|
+
This class inherits from MetricBaseCalc.
|
|
15
|
+
|
|
16
|
+
Attributes
|
|
17
|
+
----------
|
|
18
|
+
METRIC_HALSTEAD_VOLUME : str
|
|
19
|
+
The name of the Halstead volume metric.
|
|
20
|
+
METRIC_HALSTEAD_EFFORT : str
|
|
21
|
+
The name of the Halstead effort metric.
|
|
22
|
+
METRIC_HALSTEAD_DIFFICULTY : str
|
|
23
|
+
The name of the Halstead difficulty metric.
|
|
24
|
+
METRIC_HALSTEAD_BUGS : str
|
|
25
|
+
The name of the Halstead bugprop metric.
|
|
26
|
+
METRIC_HALSTEAD_TIMEREQ : str
|
|
27
|
+
The name of the Halstead timerequired metric.
|
|
28
|
+
|
|
29
|
+
Methods
|
|
30
|
+
-------
|
|
31
|
+
_bugpred_old(obj)
|
|
32
|
+
Calculate the bug prediction using the old method.
|
|
33
|
+
_bugpred_new(obj)
|
|
34
|
+
Calculate the bug prediction using the new method.
|
|
35
|
+
_getNs(metrics)
|
|
36
|
+
Calculate the values of N1, N2, n1, n2.
|
|
37
|
+
_getVocabulary(metrics)
|
|
38
|
+
Calculate the vocabulary of the program.
|
|
39
|
+
_getProgLength(metrics)
|
|
40
|
+
Calculate the program length.
|
|
41
|
+
_getVolume(metrics)
|
|
42
|
+
Calculate the Halstead volume.
|
|
43
|
+
_getDifficulty(metrics)
|
|
44
|
+
Calculate the Halstead difficulty.
|
|
45
|
+
_getEffort(metrics)
|
|
46
|
+
Calculate the Halstead effort.
|
|
47
|
+
_getTime(metrics)
|
|
48
|
+
Calculate the time required.
|
|
49
|
+
_getBug(metrics)
|
|
50
|
+
Calculate the bugprop.
|
|
51
|
+
get_results(metrics)
|
|
52
|
+
Calculate all the Halstead metrics and return the results.
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
METRIC_HALSTEAD_VOLUME = "halstead_volume"
|
|
57
|
+
METRIC_HALSTEAD_EFFORT = "halstead_effort"
|
|
58
|
+
METRIC_HALSTEAD_DIFFICULTY = "halstead_difficulty"
|
|
59
|
+
METRIC_HALSTEAD_BUGS = "halstead_bugprop"
|
|
60
|
+
METRIC_HALSTEAD_TIMEREQ = "halstead_timerequired"
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def _bugpred_old(obj):
|
|
64
|
+
"""Calculate the bug prediction using the old method.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
obj : MetricBaseCalcHalstead
|
|
69
|
+
The MetricBaseCalcHalstead object.
|
|
70
|
+
|
|
71
|
+
Returns
|
|
72
|
+
-------
|
|
73
|
+
float
|
|
74
|
+
The bug prediction using the old method.
|
|
75
|
+
|
|
76
|
+
"""
|
|
77
|
+
return (obj._effort * (2.0 / 3.0)) / 3000.0
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def _bugpred_new(obj):
|
|
81
|
+
"""Calculate the bug prediction using the new method.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
obj : MetricBaseCalcHalstead
|
|
86
|
+
The MetricBaseCalcHalstead object.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
float
|
|
91
|
+
The bug prediction using the new method.
|
|
92
|
+
|
|
93
|
+
"""
|
|
94
|
+
return obj._volume / 3000.0
|
|
95
|
+
|
|
96
|
+
BUGPRED_DEFAULT = "new"
|
|
97
|
+
BUGPRED_METHOD = {
|
|
98
|
+
"old": _bugpred_old,
|
|
99
|
+
"new": _bugpred_new,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
def __init__(self, args, **kwargs):
|
|
103
|
+
"""Initialize the MetricBaseCalcHalstead object.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
args : argparse.Namespace
|
|
108
|
+
The command line arguments.
|
|
109
|
+
**kwargs
|
|
110
|
+
Additional keyword arguments.
|
|
111
|
+
|
|
112
|
+
"""
|
|
113
|
+
super().__init__(args, **kwargs)
|
|
114
|
+
self.__bugPredicMethod = args.halstead_bug_predict_method
|
|
115
|
+
|
|
116
|
+
def _getNs(self, metrics):
|
|
117
|
+
"""Calculate the values of N1, N2, n1, n2.
|
|
118
|
+
|
|
119
|
+
Parameters
|
|
120
|
+
----------
|
|
121
|
+
metrics : dict
|
|
122
|
+
The metrics dictionary.
|
|
123
|
+
|
|
124
|
+
Notes
|
|
125
|
+
-----
|
|
126
|
+
- N1 is the total number of operators
|
|
127
|
+
- N2 is the total number of operands
|
|
128
|
+
- n1 is the number of distinct operators
|
|
129
|
+
- n2 is the number of distinct operands
|
|
130
|
+
"""
|
|
131
|
+
self._N2 = float(metrics[MetricBaseOperands.METRIC_OPERANDS_SUM])
|
|
132
|
+
self._N1 = float(metrics[MetricBaseOperator.METRIC_OPERATORS_SUM])
|
|
133
|
+
self._n2 = float(metrics[MetricBaseOperands.METRIC_OPERANDS_UNIQUE])
|
|
134
|
+
self._n1 = float(metrics[MetricBaseOperator.METRIC_OPERATORS_UNIQUE])
|
|
135
|
+
# to avoid any Divbyzero bugs set the minimum to 1
|
|
136
|
+
self._n1 = max(self._n1, 1)
|
|
137
|
+
self._n2 = max(self._n2, 1)
|
|
138
|
+
self._N1 = max(self._N1, 1)
|
|
139
|
+
self._N2 = max(self._N2, 1)
|
|
140
|
+
|
|
141
|
+
def _getVocabulary(self, metrics):
|
|
142
|
+
"""Calculate the vocabulary of the program.
|
|
143
|
+
|
|
144
|
+
Halstead vocabulary (n) is defined as the sum of distinct
|
|
145
|
+
operators and operands. (n1 + n2)
|
|
146
|
+
|
|
147
|
+
Parameters
|
|
148
|
+
----------
|
|
149
|
+
metrics : dict
|
|
150
|
+
The metrics dictionary.
|
|
151
|
+
|
|
152
|
+
Returns
|
|
153
|
+
-------
|
|
154
|
+
float
|
|
155
|
+
The vocabulary of the program.
|
|
156
|
+
|
|
157
|
+
"""
|
|
158
|
+
self._getNs(metrics)
|
|
159
|
+
self._vocabulary = self._n1 + self._n2
|
|
160
|
+
# to avoid a log(0) the minimum of vocabulary is 1
|
|
161
|
+
self._vocabulary = max(1, self._vocabulary)
|
|
162
|
+
return self._vocabulary
|
|
163
|
+
|
|
164
|
+
def _getProgLength(self, metrics):
|
|
165
|
+
"""Calculate the program length.
|
|
166
|
+
|
|
167
|
+
Halstead program length (N) is defined as the sum of total
|
|
168
|
+
operators and operands. (N1 + N2).
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
metrics : dict
|
|
173
|
+
The metrics dictionary.
|
|
174
|
+
|
|
175
|
+
Returns
|
|
176
|
+
-------
|
|
177
|
+
float
|
|
178
|
+
The program length.
|
|
179
|
+
|
|
180
|
+
"""
|
|
181
|
+
self._getNs(metrics)
|
|
182
|
+
self._length = self._N1 + self._N2
|
|
183
|
+
return self._length
|
|
184
|
+
|
|
185
|
+
def _getVolume(self, metrics):
|
|
186
|
+
"""Calculate the Halstead volume.
|
|
187
|
+
|
|
188
|
+
Parameters
|
|
189
|
+
----------
|
|
190
|
+
metrics : dict
|
|
191
|
+
The metrics dictionary.
|
|
192
|
+
|
|
193
|
+
Returns
|
|
194
|
+
-------
|
|
195
|
+
float
|
|
196
|
+
The Halstead volume.
|
|
197
|
+
|
|
198
|
+
"""
|
|
199
|
+
self._getVocabulary(metrics)
|
|
200
|
+
self._getProgLength(metrics)
|
|
201
|
+
self._volume = self._length * math.log2(self._vocabulary)
|
|
202
|
+
# to avoid a log(0) the minimum of volume is 1
|
|
203
|
+
self._volume = max(1, self._volume)
|
|
204
|
+
return self._volume
|
|
205
|
+
|
|
206
|
+
def _getDifficulty(self, metrics):
|
|
207
|
+
"""Calculate the Halstead difficulty.
|
|
208
|
+
|
|
209
|
+
The difficulty measure is related to the difficulty of the program
|
|
210
|
+
to write or understand, e.g. when doing code review.
|
|
211
|
+
|
|
212
|
+
Parameters
|
|
213
|
+
----------
|
|
214
|
+
metrics : dict
|
|
215
|
+
The metrics dictionary.
|
|
216
|
+
|
|
217
|
+
Returns
|
|
218
|
+
-------
|
|
219
|
+
float
|
|
220
|
+
The Halstead difficulty.
|
|
221
|
+
|
|
222
|
+
"""
|
|
223
|
+
self._getNs(metrics)
|
|
224
|
+
self._difficulty = (self._n1 / 2.0) * (self._N2 / self._n2)
|
|
225
|
+
return self._difficulty
|
|
226
|
+
|
|
227
|
+
def _getEffort(self, metrics):
|
|
228
|
+
"""Calculate the Halstead effort.
|
|
229
|
+
|
|
230
|
+
The effort measure translates into actual coding time.
|
|
231
|
+
|
|
232
|
+
Parameters
|
|
233
|
+
----------
|
|
234
|
+
metrics : dict
|
|
235
|
+
The metrics dictionary.
|
|
236
|
+
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
float
|
|
240
|
+
The Halstead effort.
|
|
241
|
+
|
|
242
|
+
"""
|
|
243
|
+
self._getVolume(metrics)
|
|
244
|
+
self._getDifficulty(metrics)
|
|
245
|
+
self._effort = self._volume * self._difficulty
|
|
246
|
+
return self._effort
|
|
247
|
+
|
|
248
|
+
def _getTime(self, metrics):
|
|
249
|
+
"""Calculate the estimated time required to write program.
|
|
250
|
+
|
|
251
|
+
Parameters
|
|
252
|
+
----------
|
|
253
|
+
metrics : dict
|
|
254
|
+
The metrics dictionary.
|
|
255
|
+
|
|
256
|
+
Returns
|
|
257
|
+
-------
|
|
258
|
+
float
|
|
259
|
+
The time required to write the program, in seconds.
|
|
260
|
+
|
|
261
|
+
"""
|
|
262
|
+
self._getEffort(metrics)
|
|
263
|
+
self._timeRequired = self._effort / 18.0
|
|
264
|
+
return self._timeRequired
|
|
265
|
+
|
|
266
|
+
def _getBug(self, metrics):
|
|
267
|
+
"""Calculate the estimate for the number of errors in the implementation.
|
|
268
|
+
|
|
269
|
+
Parameters
|
|
270
|
+
----------
|
|
271
|
+
metrics : dict
|
|
272
|
+
The metrics dictionary.
|
|
273
|
+
|
|
274
|
+
Returns
|
|
275
|
+
-------
|
|
276
|
+
float
|
|
277
|
+
The number of delivered bugs.
|
|
278
|
+
"""
|
|
279
|
+
self._getEffort(metrics)
|
|
280
|
+
self._bug = MetricBaseCalcHalstead.BUGPRED_METHOD[self.__bugPredicMethod](self)
|
|
281
|
+
return self._bug
|
|
282
|
+
|
|
283
|
+
def get_results(self, metrics):
|
|
284
|
+
"""Calculate all the Halstead metrics and return the results.
|
|
285
|
+
|
|
286
|
+
Parameters
|
|
287
|
+
----------
|
|
288
|
+
metrics : dict
|
|
289
|
+
The metrics dictionary.
|
|
290
|
+
|
|
291
|
+
Returns
|
|
292
|
+
-------
|
|
293
|
+
dict
|
|
294
|
+
The metrics dictionary with the Halstead metrics added.
|
|
295
|
+
|
|
296
|
+
"""
|
|
297
|
+
metrics[MetricBaseCalcHalstead.METRIC_HALSTEAD_VOLUME] = self._getVolume(
|
|
298
|
+
metrics,
|
|
299
|
+
)
|
|
300
|
+
metrics[
|
|
301
|
+
MetricBaseCalcHalstead.METRIC_HALSTEAD_DIFFICULTY
|
|
302
|
+
] = self._getDifficulty(metrics)
|
|
303
|
+
metrics[MetricBaseCalcHalstead.METRIC_HALSTEAD_EFFORT] = self._getEffort(
|
|
304
|
+
metrics,
|
|
305
|
+
)
|
|
306
|
+
metrics[MetricBaseCalcHalstead.METRIC_HALSTEAD_TIMEREQ] = self._getTime(metrics)
|
|
307
|
+
metrics[MetricBaseCalcHalstead.METRIC_HALSTEAD_BUGS] = self._getBug(metrics)
|
|
308
|
+
return super().get_results(metrics)
|