Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ jobs:
poetry run codelimit report --format markdown .
echo "Generate findings"
poetry run codelimit findings .
echo "Generate findings (Markdown)"
poetry run codelimit findings --format markdown .

- name: 'Build and run codelimit binary'
run: |
Expand Down
5 changes: 4 additions & 1 deletion codelimit/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,15 @@ def report(
path: Annotated[
Path, typer.Argument(exists=True, file_okay=False, help="Codebase root")
] = Path("."),
diff: Annotated[
Optional[Path], typer.Option(exists=True, dir_okay=False, help="Report to compare with")
] = None,
fmt: Annotated[
ReportFormat, typer.Option("--format", help="Output format")
] = ReportFormat.text,
):
Configuration.load(path)
report_command(path, fmt)
report_command(path, fmt, diff)


@cli.command(help="Show findings for codebase")
Expand Down
6 changes: 3 additions & 3 deletions codelimit/commands/findings.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

from codelimit.commands.report import ReportFormat
from codelimit.common.report import format_markdown, format_text
from codelimit.utils import read_report
from codelimit.utils import read_report, make_report_path


def findings_command(path: Path, full: bool, fmt: ReportFormat):
stdout = Console(soft_wrap=True)
report = read_report(path, stdout)
report = read_report(make_report_path(path), stdout)
if fmt == ReportFormat.markdown:
format_markdown.print_findings(report, stdout, full)
else:
format_text.print_findings(report, stdout, full)
format_text.print_findings(stdout, report, full)
11 changes: 6 additions & 5 deletions codelimit/commands/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@

from codelimit.common.report import format_markdown, format_text
from codelimit.common.report.ReportFormat import ReportFormat
from codelimit.utils import read_report
from codelimit.utils import read_report, make_report_path


def report_command(path: Path, fmt: ReportFormat):
def report_command(path: Path, fmt: ReportFormat, diff_path: Path | None = None):
stdout = Console(soft_wrap=True)
report = read_report(path, stdout)
report = read_report(make_report_path(path), stdout)
diff_report = read_report(diff_path, stdout) if diff_path else None
if fmt == ReportFormat.markdown:
format_markdown.print_report(report, stdout)
format_markdown.print_report(stdout, report, diff_report)
else:
format_text.print_report(report, stdout)
format_text.print_report(stdout, report, diff_report)
2 changes: 1 addition & 1 deletion codelimit/commands/scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def scan_command(path: Path):
codebase = scan_codebase(path, cached_report)
codebase.aggregate()
report = Report(codebase, Configuration.repository)
format_text.print_summary(report, stdout)
format_text.print_summary(stdout, report)
if not cache_dir.exists():
cache_dir.mkdir()
cache_dir_tag = cache_dir.joinpath("CACHEDIR.TAG").resolve()
Expand Down
38 changes: 38 additions & 0 deletions codelimit/common/LanguageTotalsDelta.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from codelimit.common.LanguageTotals import LanguageTotals


class LanguageTotalsDelta:
def __init__(self, language_totals_current: LanguageTotals, language_totals_previous: LanguageTotals | None):
self._language_totals_current = language_totals_current
self._language_totals_previous = language_totals_previous

def files(self) -> str:
total_files = self._language_totals_current.files
delta = total_files - (self._language_totals_previous.files if self._language_totals_previous else 0)
return f"{total_files:n}" if delta == 0 else f"{total_files:n} ({delta:+n})"

def functions(self) -> str:
total_functions = self._language_totals_current.functions
delta = total_functions - (self._language_totals_previous.functions if self._language_totals_previous else 0)
return f"{total_functions:n}" if delta == 0 else f"{total_functions:n} ({delta:+n})"

def loc(self) -> str:
total_loc = self._language_totals_current.loc
delta = total_loc - (self._language_totals_previous.loc if self._language_totals_previous else 0)
return f"{total_loc:n}" if delta == 0 else f"{total_loc:n} ({delta:+n})"

def hard_to_maintain(self) -> str:
total_hard_to_maintain = self._language_totals_current.hard_to_maintain
if self._language_totals_previous:
delta = total_hard_to_maintain - self._language_totals_previous.hard_to_maintain
return f"{total_hard_to_maintain:n}" if delta == 0 else f"{total_hard_to_maintain:n} ({delta:+n})"
else:
return f"{total_hard_to_maintain:n}"

def unmaintainable(self) -> str:
total_unmaintainable = self._language_totals_current.unmaintainable
if self._language_totals_previous:
delta = total_unmaintainable - self._language_totals_previous.unmaintainable
return f"{total_unmaintainable:n}" if delta == 0 else f"{total_unmaintainable:n} ({delta:+n})"
else:
return f"{total_unmaintainable:n}"
65 changes: 40 additions & 25 deletions codelimit/common/ScanResultTable.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,53 @@
from rich import box
from rich.table import Table

from codelimit.common.LanguageTotalsDelta import LanguageTotalsDelta
from codelimit.common.ScanTotals import ScanTotals
from codelimit.common.ScanTotalsDelta import ScanTotalsDelta


class ScanResultTable(Table):
def __init__(self, scan_totals: ScanTotals):
def __init__(self, scan_totals_current: ScanTotals, scan_totals_previous: ScanTotals | None = None):
super().__init__(
expand=True, box=box.SIMPLE, show_footer=len(scan_totals.languages()) > 1
expand=True, box=box.SIMPLE, show_footer=len(scan_totals_current.languages()) > 1
)
self.scan_totals = scan_totals
self._stc = scan_totals_current
self._stp = scan_totals_previous
self.add_column("Language")
self.add_column("Files", f"{scan_totals.total_files():n}", justify="right")
self.add_column(
"Functions", f"{scan_totals.total_functions():n}", justify="right"
)
self.add_column(
"Lines of Code", f"{scan_totals.total_loc():n}", justify="right"
)
self.add_column(
"\u26A0", f"{scan_totals.total_hard_to_maintain():n}", justify="right"
)
self.add_column(
"\u2716", f"{scan_totals.total_unmaintainable():n}", justify="right"
)
if self._stp:
std = ScanTotalsDelta(self._stc, self._stp)
self.add_column("Files", f"{std.total_files()}", justify="right")
self.add_column("Functions", f"{std.total_functions()}", justify="right")
self.add_column("Lines of Code", f"{std.total_loc()}", justify="right")
self.add_column("\u26A0", f"{std.total_hard_to_maintain()}", justify="right")
self.add_column("\u2716", f"{std.total_unmaintainable()}", justify="right")
else:
self.add_column("Files", f"{self._stc.total_files():n}", justify="right")
self.add_column("Functions", f"{self._stc.total_functions():n}", justify="right")
self.add_column("Lines of Code", f"{self._stc.total_loc():n}", justify="right")
self.add_column("\u26A0", f"{self._stc.total_hard_to_maintain():n}", justify="right")
self.add_column("\u2716", f"{self._stc.total_unmaintainable():n}", justify="right")
self._populate()

def _populate(self):
for language_totals in self.scan_totals.languages_totals():
self.add_row(
language_totals.language,
f"{language_totals.files:n}",
f"{language_totals.functions:n}",
f"{language_totals.loc:n}",
f"{language_totals.hard_to_maintain:n}",
f"{language_totals.unmaintainable:n}"
)
for language_totals in self._stc.languages_totals():
if self._stp:
language_totals_previous = self._stc.language_total(language_totals.language)
ltd = LanguageTotalsDelta(language_totals, language_totals_previous)
self.add_row(
language_totals.language,
f"{ltd.files()}",
f"{ltd.functions()}",
f"{ltd.loc()}",
f"{ltd.hard_to_maintain()}",
f"{ltd.unmaintainable()}"
)
else:
self.add_row(
language_totals.language,
f"{language_totals.files:n}",
f"{language_totals.functions:n}",
f"{language_totals.loc:n}",
f"{language_totals.hard_to_maintain:n}",
f"{language_totals.unmaintainable:n}"
)
11 changes: 7 additions & 4 deletions codelimit/common/ScanTotals.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

class ScanTotals:
def __init__(
self, language_totals: dict[str, LanguageTotals] | None = None
self, language_totals: dict[str, LanguageTotals] | None = None
) -> None:
self._languages_totals: dict[str, LanguageTotals] = (
language_totals if language_totals else {}
Expand All @@ -18,20 +18,23 @@ def add(self, entry: SourceFileEntry):
def languages(self):
return self._languages_totals.keys()

def language_total(self, language: str) -> LanguageTotals | None:
return self._languages_totals.get(language)

def languages_totals(self) -> list[LanguageTotals]:
return sorted(
self._languages_totals.values(), key=lambda x: x.loc, reverse=True
)

def total_loc(self) -> int:
return sum([language.loc for language in self._languages_totals.values()])

def total_files(self) -> int:
return sum([language.files for language in self._languages_totals.values()])

def total_functions(self) -> int:
return sum([language.functions for language in self._languages_totals.values()])

def total_loc(self) -> int:
return sum([language.loc for language in self._languages_totals.values()])

def total_hard_to_maintain(self) -> int:
return sum(
[language.hard_to_maintain for language in self._languages_totals.values()]
Expand Down
32 changes: 32 additions & 0 deletions codelimit/common/ScanTotalsDelta.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from codelimit.common.ScanTotals import ScanTotals


class ScanTotalsDelta:
def __init__(self, scan_totals_current: ScanTotals, scan_totals_previous: ScanTotals):
self._scan_totals_current = scan_totals_current
self._scan_totals_previous = scan_totals_previous

def total_files(self) -> str:
total_files = self._scan_totals_current.total_files()
delta = total_files - self._scan_totals_previous.total_files()
return f"{total_files:n}" if delta == 0 else f"{total_files:n} ({delta:+n})"

def total_functions(self) -> str:
total_functions = self._scan_totals_current.total_functions()
delta = total_functions - self._scan_totals_previous.total_functions()
return f"{total_functions:n}" if delta == 0 else f"{total_functions:n} ({delta:+n})"

def total_loc(self) -> str:
total_loc = self._scan_totals_current.total_loc()
delta = total_loc - self._scan_totals_previous.total_loc()
return f"{total_loc:n}" if delta == 0 else f"{total_loc:n} ({delta:+n})"

def total_hard_to_maintain(self) -> str:
hard_to_maintain = self._scan_totals_current.total_hard_to_maintain()
delta = hard_to_maintain - self._scan_totals_previous.total_hard_to_maintain()
return f"{hard_to_maintain:n}" if delta == 0 else f"{hard_to_maintain:n} ({delta:+n})"

def total_unmaintainable(self) -> str:
unmaintainable = self._scan_totals_current.total_unmaintainable()
delta = unmaintainable - self._scan_totals_previous.total_unmaintainable()
return f"{unmaintainable:n}" if delta == 0 else f"{unmaintainable:n} ({delta:+n})"
91 changes: 60 additions & 31 deletions codelimit/common/report/format_markdown.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,77 @@
from rich.console import Console

from codelimit.common.GithubRepository import GithubRepository
from codelimit.common.LanguageTotalsDelta import LanguageTotalsDelta
from codelimit.common.ScanTotals import ScanTotals
from codelimit.common.ScanTotalsDelta import ScanTotalsDelta
from codelimit.common.report.Report import Report
from codelimit.common.report.ReportUnit import ReportUnit


def print_report(report: Report, console: Console):
print_totals(report, console)
print_summary(report, console)
def print_report(console: Console, report: Report, diff_report: Report | None = None):
print_totals(console, report, diff_report)
print_summary(console, report)


def print_totals(report, console):
def print_totals(console: Console, report: Report, diff_report: Report | None = None):
console.print("### Overview")
_print_totals(ScanTotals(report.codebase.totals), console)
scan_totals = ScanTotals(report.codebase.totals)
if diff_report:
_print_totals(console, scan_totals, ScanTotals(diff_report.codebase.totals))
else:
_print_totals(console, scan_totals)
console.print("")


def print_summary(report: Report, console: Console):
def _print_totals(console: Console, scan_totals_current: ScanTotals, scan_totals_previous: ScanTotals | None = None):
console.print(
"| **Language** | **Files** | **Functions** | **Lines of Code** | **\u26A0** | **\u26CC** |"
)
console.print("| --- | ---: | ---: | ---: | ---: | ---: |")
for language_totals in scan_totals_current.languages_totals():
if scan_totals_previous:
language_totals_previous = scan_totals_previous.language_total(language_totals.language)
ltd = LanguageTotalsDelta(language_totals, language_totals_previous)
console.print(
language_totals.language,
f"| {ltd.files()} | ",
f"{ltd.functions()} | ",
f"{ltd.loc()} | ",
f"{ltd.hard_to_maintain()} | ",
f"{ltd.unmaintainable()} |"
)
else:
console.print(
f"| {language_totals.language} | "
f"{language_totals.files} | "
f"{language_totals.functions} | "
f"{language_totals.loc} | "
f"{language_totals.hard_to_maintain} | "
f"{language_totals.unmaintainable} |"
)
if len(scan_totals_current.languages_totals()) > 1:
if scan_totals_previous:
std = ScanTotalsDelta(scan_totals_current, scan_totals_previous)
console.print(
f"| **Totals** | "
f"**{std.total_files()}** | "
f"**{std.total_functions()}** | "
f"**{std.total_loc()}** | "
f"**{std.total_hard_to_maintain()}** | "
f"**{std.total_unmaintainable()}** |"
)
else:
console.print(
f"| **Totals** | "
f"**{scan_totals_current.total_files()}** | "
f"**{scan_totals_current.total_functions()}** | "
f"**{scan_totals_current.total_loc()}** | "
f"**{scan_totals_current.total_hard_to_maintain()}** | "
f"**{scan_totals_current.total_unmaintainable()}** |"
)


def print_summary(console: Console, report: Report):
console.print("### Summary")
easy, verbose, hard_to_maintain, unmaintainable = report.quality_profile_percentage()
console.print("| **Easy / Verbose** | **Hard-to-maintain \u26A0** | **Unmaintainable \u26CC** |")
Expand All @@ -34,31 +88,6 @@ def print_summary(report: Report, console: Console):
console.print("")


def _print_totals(st: ScanTotals, console: Console):
console.print(
"| **Language** | **Files** | **Functions** | **Lines of Code** | **\u26A0** | **\u26CC** |"
)
console.print("| --- | ---: | ---: | ---: | ---: | ---: |")
for lt in st.languages_totals():
console.print(
f"| {lt.language} | "
f"{lt.files} | "
f"{lt.functions} | "
f"{lt.loc} | "
f"{lt.hard_to_maintain} | "
f"{lt.unmaintainable} |"
)
if len(st.languages_totals()) > 1:
console.print(
f"| **Totals** | "
f"**{st.total_files()}** | "
f"**{st.total_functions()}** | "
f"**{st.total_loc()}** | "
f"**{st.total_hard_to_maintain()}** | "
f"**{st.total_unmaintainable()}** |"
)


def print_findings(report: Report, console: Console, full: bool = False):
functions = report.all_report_units_sorted_by_length_asc(30)
total_findings = len(functions)
Expand Down
Loading