diff --git a/.gitignore b/.gitignore index 617e422..73c5544 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ build/ dist/ clim.spec +clim.json +.coverage +coverage.xml diff --git a/clim b/clim index 22cfd17..4555448 100755 --- a/clim +++ b/clim @@ -1,13 +1,17 @@ #!/usr/bin/env python3 - +from codelimit.common.Report import Report from codelimit.common.Scanner import scan from codelimit.version import version, release_date print('Code Limit') print(f'Version: {version}, released on: {release_date}') -report = scan('.') -print(f'Total lines of code: {report.total_lines_of_code}') +measurements = scan('.') +print(f'Total lines of code: {measurements.total_loc()}') +report = Report(measurements) print(f'Average length (LOC): {report.get_average()}') print(f'90th percentile: {report.ninetieth_percentile()}') -report.display_risk_category_plot() \ No newline at end of file +report.display_risk_category_plot() +with open('clim.json', 'w') as outfile: + outfile.write(measurements.to_json(True)) +print('Output written to clim.json') diff --git a/codelimit/common/Measurement.py b/codelimit/common/Measurement.py new file mode 100644 index 0000000..32391c7 --- /dev/null +++ b/codelimit/common/Measurement.py @@ -0,0 +1,8 @@ +from dataclasses import dataclass + + +@dataclass +class Measurement: + filename: str + line: int + length: int diff --git a/codelimit/common/Measurements.py b/codelimit/common/Measurements.py new file mode 100644 index 0000000..a3e8f00 --- /dev/null +++ b/codelimit/common/Measurements.py @@ -0,0 +1,36 @@ +import dataclasses +import json + +from codelimit.common.Measurement import Measurement + + +class Measurements: + def __init__(self): + self._measurements = [] + + def add(self, measurement: Measurement): + self._measurements.append(measurement) + + def all(self) -> list[Measurement]: + return self._measurements + + def total_loc(self) -> int: + result = 0 + for m in self._measurements: + result += m.length + return result + + def sorted_by_length(self): + return sorted(self._measurements, key=lambda m: m.length) + + def to_json(self, pretty_print=False) -> str: + class EnhancedJSONEncoder(json.JSONEncoder): + def default(self, o): + if dataclasses.is_dataclass(o): + return dataclasses.asdict(o) + return super().default(o) + + if pretty_print: + return json.dumps(self._measurements, cls=EnhancedJSONEncoder, indent=2) + else: + return json.dumps(self._measurements, cls=EnhancedJSONEncoder) diff --git a/codelimit/common/Report.py b/codelimit/common/Report.py index b19b0fb..2bfc078 100644 --- a/codelimit/common/Report.py +++ b/codelimit/common/Report.py @@ -1,40 +1,29 @@ -from dataclasses import dataclass from math import floor, ceil import plotext - -@dataclass -class Measurement: - filename: str - line: int - length: int +from codelimit.common.Measurements import Measurements class Report: - def __init__(self): - self.measurements: list[Measurement] = [] - self.total_lines_of_code = 0 - - def add_measurement(self, measurement: Measurement): - self.measurements.append(measurement) - self.total_lines_of_code += measurement.length + def __init__(self, measurements: Measurements): + self.measurements = measurements def get_average(self): - return ceil(self.total_lines_of_code / len(self.measurements)) + return ceil(self.measurements.total_loc() / len(self.measurements.all())) def ninetieth_percentile(self): - self.measurements.sort(key=lambda m: m.length) - lines_of_code_90_percent = floor(self.total_lines_of_code * 0.9) + sorted_measurements = self.measurements.sorted_by_length() + lines_of_code_90_percent = floor(self.measurements.total_loc() * 0.9) smallest_units_loc = 0 - for index, m in enumerate(self.measurements): + for index, m in enumerate(sorted_measurements): smallest_units_loc += m.length if smallest_units_loc > lines_of_code_90_percent: - return self.measurements[index].length + return sorted_measurements[index].length def risk_categories(self): result = [0, 0, 0, 0] - for m in self.measurements: + for m in self.measurements.all(): if m.length <= 10: result[0] += m.length elif m.length <= 20: @@ -53,5 +42,5 @@ def display_risk_category_plot(self): plotext.show() def display(self): - for m in self.measurements: + for m in self.measurements.all(): print(f'{m.filename}#{m.line}: {m.length}') diff --git a/codelimit/common/Scanner.py b/codelimit/common/Scanner.py index 7e4b8ca..260d107 100644 --- a/codelimit/common/Scanner.py +++ b/codelimit/common/Scanner.py @@ -2,9 +2,10 @@ from halo import Halo -from codelimit.languages.python.Python import get_headers, get_blocks -from codelimit.common.Report import Report, Measurement +from codelimit.common.Measurement import Measurement +from codelimit.common.Measurements import Measurements from codelimit.common.Scope import build_scopes +from codelimit.languages.python.Python import get_headers, get_blocks def is_hidden(root, file): @@ -13,8 +14,8 @@ def is_hidden(root, file): return len([d for d in root.split(os.sep)[1:] if d.startswith('.')]) > 0 -def scan(path: str) -> Report: - report = Report() +def scan(path: str) -> Measurements: + result = Measurements() spinner = Halo(text='Scanning', spinner='dots') spinner.start() scanned = 0 @@ -31,8 +32,8 @@ def scan(path: str) -> Report: for scope in scopes: length = scope.block.end.line - scope.block.start.line + 1 measurement = Measurement(file, scope.header.start.line, length) - report.add_measurement(measurement) + result.add(measurement) scanned += 1 spinner.text = f'Scanned {scanned} file(s)' spinner.succeed() - return report + return result diff --git a/tests/common/test_Measurements.py b/tests/common/test_Measurements.py new file mode 100644 index 0000000..6e27745 --- /dev/null +++ b/tests/common/test_Measurements.py @@ -0,0 +1,21 @@ +from codelimit.common.Measurement import Measurement +from codelimit.common.Measurements import Measurements + + +def test_to_json(): + measurements = Measurements() + m = Measurement('foo.py', 10, 10) + measurements.add(m) + + assert measurements.to_json() == '[{"filename": "foo.py", "line": 10, "length": 10}]' + + +def test_to_json_multiple(): + measurements = Measurements() + m = Measurement('foo.py', 10, 10) + measurements.add(m) + m = Measurement('bar.py', 20, 10) + measurements.add(m) + + assert measurements.to_json() == '[{"filename": "foo.py", "line": 10, "length": 10}' + \ + ', {"filename": "bar.py", "line": 20, "length": 10}]'