Skip to content

Instantly share code, notes, and snippets.

@panicoenlaxbox
Last active December 24, 2023 12:52
Show Gist options
  • Save panicoenlaxbox/17fbeca0f668353d5216569830b60d98 to your computer and use it in GitHub Desktop.
Save panicoenlaxbox/17fbeca0f668353d5216569830b60d98 to your computer and use it in GitHub Desktop.
Get number of lines of code, classes and functions from a Python base code
import ast
from dataclasses import dataclass
from functools import reduce
from pathlib import Path
from types import TracebackType
from typing import Self, TextIO
@dataclass
class Symbols:
classes: int = 0
functions: int = 0
class InsightsGenerator:
def __init__(self, source_dir: Path):
if not source_dir.exists() or not source_dir.is_dir():
raise ValueError(f"{source_dir} is not a valid directory")
self._source_dir = source_dir
self._output_file_path = source_dir / "index.html"
self._f: TextIO
def __enter__(self) -> Self:
self._f = open(self._output_file_path, "w")
return self
def __exit__(
self,
exc_type: type[BaseException] | None,
exc_val: BaseException | None,
exc_tb: TracebackType | None,
) -> None:
self._f.close()
@staticmethod
def _is_dir_excluded(dir_: Path) -> bool:
excluded_dirs = ["__pycache__"]
return any(dir_.name.endswith(exclude) for exclude in excluded_dirs)
@classmethod
def _get_symbols(cls, path: Path) -> Symbols:
classes = 0
functions = 0
if path.is_file():
return cls._get_symbols_from_file(path)
for file in path.glob("**/*.py"):
symbols = cls._get_symbols_from_file(file)
classes += symbols.classes
functions += symbols.functions
return Symbols(classes, functions)
@staticmethod
def _get_symbols_from_file(file_path: Path) -> Symbols:
with open(file_path, "r", errors="ignore") as f:
tree = ast.parse(f.read())
classes = 0
functions = 0
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef):
classes += 1
elif isinstance(node, ast.FunctionDef):
functions += 1
else:
continue
return Symbols(classes, functions)
@classmethod
def _get_kloc(cls, path: Path) -> int:
return cls._get_dir_kloc(path) if path.is_dir() else cls._get_file_kloc(path)
@staticmethod
def _get_file_kloc(file_path: Path) -> int:
with open(file_path, "r", errors="ignore") as f:
return len(
[
line
for line in f.readlines()
if line.strip() != "" and not line.strip().startswith("#")
]
)
@classmethod
def _get_dir_kloc(cls, dir_: Path) -> int:
return reduce(
lambda x, y: x + y,
[cls._get_file_kloc(f) for f in dir_.glob(f"**/*.py")],
0,
)
@classmethod
def _skip_path(cls, path: Path) -> bool:
return (path.is_file() and not path.suffix == ".py") or (
path.is_dir() and cls._is_dir_excluded(path)
)
def _to_html(self, path: Path) -> None:
for entry in path.iterdir():
if self._skip_path(entry):
continue
print("Generating insights for", entry)
self._f.write(
f"""<tr>
<td>{('d' if entry.is_dir() else '')}</td>
<td><a href='{entry}'>{entry}</a></td>
<td>{self._get_kloc(entry)}</td>"""
)
symbols = self._get_symbols(entry)
self._f.write(
f"""
<td>{symbols.classes}</td>
<td>{symbols.functions}</td>
</tr>"""
)
if entry.is_dir():
self._to_html(entry)
def generate(self) -> None:
style = r"""
table {
border: 1px solid #1C6EA4;
text-align: left;
border-collapse: collapse;
}
table td, table th {
border: 1px solid #AAAAAA;
padding: 3px 2px;
}
table tr:nth-child(even) {
background: #D0E4F5;
}
table thead {
background: #1C6EA4;
background: -moz-linear-gradient(top, #5592bb 0%, #327cad 66%, #1C6EA4 100%);
background: -webkit-linear-gradient(top, #5592bb 0%, #327cad 66%, #1C6EA4 100%);
background: linear-gradient(to bottom, #5592bb 0%, #327cad 66%, #1C6EA4 100%);
}
table thead th {
font-size: 15px;
font-weight: bold;
color: #FFFFFF;
}
table tbody td:nth-child(3), table tbody td:nth-child(4), table tbody td:nth-child(5) {
text-align: right;
}
"""
self._f.write(
f"""
<html>
<head>
<style>
{style}
</style>
</head>
<body>
<h1>{self._source_dir.name}</h1>
<table>
<thead>
<tr>
<th></th>
<th>path</th>
<th>kloc</th>
<th>classes</th>
<th>functions</th>
</thead>
<tbody>
<tr>
<td>d</td>
<td><a href='{self._source_dir}'>{self._source_dir}</a></td>
<td>{self._get_dir_kloc(self._source_dir)}</td>
"""
)
symbols = self._get_symbols(self._source_dir)
self._f.write(
f"""
<td>{symbols.classes}</td>
<td>{symbols.functions}</td>
</tr>"""
)
self._to_html(self._source_dir)
self._f.write(
"""
</tbody>
</table>
</body>
</html>"""
)
if __name__ == "__main__":
with InsightsGenerator(
Path(r"C:\Temp\requests\src\requests")
) as insights_generator:
insights_generator.generate()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment