-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcode_scanner.py
More file actions
115 lines (93 loc) · 2.49 KB
/
Copy pathcode_scanner.py
File metadata and controls
115 lines (93 loc) · 2.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
"""
Python 3 Object-Oriented Programming
Chapter 9. Strings and Serialization
"""
from pathlib import Path
from typing import Callable
def scan_python_1(path: Path) -> int:
sloc = 0
with path.open() as source:
for line in source:
line = line.strip()
if line and not line.startswith("#"):
sloc += 1
return sloc
def scan_python_2(path: Path) -> int:
with path.open() as source:
partitioned = (line.partition("#") for line in source)
code_only = (code.strip() for code, _, comment in partitioned)
non_empty = filter(None, code_only)
sloc = sum(1 for line in non_empty)
return sloc
import ast
class StatementVisitor(ast.NodeVisitor):
def __init__(self) -> None:
self.sloc = 0
def visit_statement(self, node: ast.AST) -> None:
self.sloc += 1
super().generic_visit(node)
for classname in (
"FunctionDef",
"AsyncFunctionDef",
"ClassDef",
"Return",
"Delete",
"Assign",
"AugAssign",
"AnnAssign",
"For",
"AsyncFor",
"While",
"If",
"With",
"AsyncWith",
"Raise",
"Try",
"Assert",
"Import",
"ImportFrom",
"Global",
"Nonlocal",
"Expr",
"Pass",
"Break",
"Continue",
):
setattr(StatementVisitor, f"visit_{classname}", StatementVisitor.visit_statement)
def scan_python_3(path: Path) -> int:
stmt_count = StatementVisitor()
with path.open() as source:
code = ast.parse(source.read(), str(path))
stmt_count.visit(code)
return stmt_count.sloc
def count_sloc(path: Path, scanner: Callable[[Path], int]) -> int:
if path.name.startswith("."):
return 0
elif path.is_file():
if path.suffix != ".py":
return 0
with path.open() as source:
return scanner(path)
elif path.is_dir():
count = sum(count_sloc(name, scanner) for name in path.iterdir())
return count
else:
return 0
test_ch_02 = """
>>> base = Path.cwd().parent
>>> chapter = base / "ch_02"
>>> count = count_sloc(chapter, scan_python_1)
>>> print(
... f"{chapter.relative_to(base)}: {count} lines of code"
... )
ch_02: 545 lines of code
>>> count = count_sloc(chapter, scan_python_2)
>>> count
545
>>> count = count_sloc(chapter, scan_python_3)
>>> print(
... f"{chapter.relative_to(base)}: {count} statements"
... )
ch_02: 260 statements
"""
__test__ = {name: case for name, case in globals().items() if name.startswith("test_")}