Skip to content

Commit 5239051

Browse files
feat: ✨ Include pathspec library (#49)
Co-authored-by: robvanderleek <[email protected]>
1 parent 79997a3 commit 5239051

File tree

13 files changed

+90
-98
lines changed

13 files changed

+90
-98
lines changed

codelimit/__main__.py

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ def list_commands(self, ctx: Context):
2626

2727
@cli.command(help="Check file(s)")
2828
def check(
29-
paths: Annotated[List[Path], typer.Argument(exists=True)],
30-
exclude: Annotated[
31-
Optional[list[str]], typer.Option(help="Glob patterns for exclusion")
32-
] = None,
33-
quiet: Annotated[
34-
bool, typer.Option("--quiet", help="No output when successful")
35-
] = False,
29+
paths: Annotated[List[Path], typer.Argument(exists=True)],
30+
exclude: Annotated[
31+
Optional[list[str]], typer.Option(help="Glob patterns for exclusion")
32+
] = None,
33+
quiet: Annotated[
34+
bool, typer.Option("--quiet", help="No output when successful")
35+
] = False,
3636
):
3737
if exclude:
3838
Configuration.excludes.extend(exclude)
@@ -41,12 +41,12 @@ def check(
4141

4242
@cli.command(help="Scan a codebase")
4343
def scan(
44-
path: Annotated[
45-
Path, typer.Argument(exists=True, file_okay=False, help="Codebase root")
46-
] = Path("."),
47-
exclude: Annotated[
48-
Optional[list[str]], typer.Option(help="Glob patterns for exclusion")
49-
] = None
44+
path: Annotated[
45+
Path, typer.Argument(exists=True, file_okay=False, help="Codebase root")
46+
] = Path("."),
47+
exclude: Annotated[
48+
Optional[list[str]], typer.Option(help="Glob patterns for exclusion")
49+
] = None,
5050
):
5151
if exclude:
5252
Configuration.excludes.extend(exclude)
@@ -55,14 +55,14 @@ def scan(
5555

5656
@cli.command(help="Show report for codebase")
5757
def report(
58-
path: Annotated[
59-
Path, typer.Argument(exists=True, file_okay=False, help="Codebase root")
60-
] = Path("."),
61-
full: Annotated[bool, typer.Option("--full", help="Show full report")] = False,
62-
totals: Annotated[bool, typer.Option("--totals", help="Only show totals")] = False,
63-
fmt: Annotated[
64-
ReportFormat, typer.Option("--format", help="Output format")
65-
] = ReportFormat.text,
58+
path: Annotated[
59+
Path, typer.Argument(exists=True, file_okay=False, help="Codebase root")
60+
] = Path("."),
61+
full: Annotated[bool, typer.Option("--full", help="Show full report")] = False,
62+
totals: Annotated[bool, typer.Option("--totals", help="Only show totals")] = False,
63+
fmt: Annotated[
64+
ReportFormat, typer.Option("--format", help="Output format")
65+
] = ReportFormat.text,
6666
):
6767
report_command(path, full, totals, fmt)
6868

@@ -75,15 +75,15 @@ def _version_callback(show: bool):
7575

7676
@cli.callback()
7777
def main(
78-
verbose: Annotated[
79-
Optional[bool], typer.Option("--verbose", "-v", help="Verbose output")
80-
] = False,
81-
version: Annotated[
82-
Optional[bool],
83-
typer.Option(
84-
"--version", "-V", help="Show version", callback=_version_callback
85-
),
86-
] = None,
78+
verbose: Annotated[
79+
Optional[bool], typer.Option("--verbose", "-v", help="Verbose output")
80+
] = False,
81+
version: Annotated[
82+
Optional[bool],
83+
typer.Option(
84+
"--version", "-V", help="Show version", callback=_version_callback
85+
),
86+
] = None,
8787
):
8888
"""Code Limit: Your refactoring alarm."""
8989
if verbose:

codelimit/commands/check.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,49 @@
22
from pathlib import Path
33

44
import typer
5+
from pathspec import PathSpec
56
from pygments.lexers import get_lexer_for_filename
67
from pygments.util import ClassNotFound
78

89
from codelimit.common.CheckResult import CheckResult
10+
from codelimit.common.Configuration import Configuration
911
from codelimit.common.Scanner import is_excluded, scan_file
1012
from codelimit.common.lexer_utils import lex
1113
from codelimit.languages import Languages
1214

1315

1416
def check_command(paths: list[Path], quiet: bool):
1517
check_result = CheckResult()
18+
excludes_spec = PathSpec.from_lines("gitignore", Configuration.excludes)
1619
for path in paths:
1720
if path.is_file():
18-
_handle_file_path(path, check_result)
21+
_handle_file_path(path, check_result, excludes_spec)
1922
elif path.is_dir():
2023
for root, dirs, files in os.walk(path.absolute()):
2124
files = [f for f in files if not f[0] == "."]
2225
dirs[:] = [d for d in dirs if not d[0] == "."]
2326
for file in files:
2427
abs_path = Path(os.path.join(root, file))
2528
rel_path = abs_path.relative_to(path.absolute())
26-
if is_excluded(rel_path):
29+
if is_excluded(rel_path, excludes_spec):
2730
continue
2831
check_file(abs_path, check_result)
2932
exit_code = 1 if check_result.unmaintainable > 0 else 0
3033
if (
31-
not quiet
32-
or check_result.hard_to_maintain > 0
33-
or check_result.unmaintainable > 0
34+
not quiet
35+
or check_result.hard_to_maintain > 0
36+
or check_result.unmaintainable > 0
3437
):
3538
check_result.report()
3639
raise typer.Exit(code=exit_code)
3740

3841

39-
def _handle_file_path(path: Path, check_result: CheckResult):
42+
def _handle_file_path(path: Path, check_result: CheckResult, excludes_spec: PathSpec):
4043
if not path.is_absolute():
4144
abs_path = path.absolute().resolve()
4245
try:
4346
rel_path = abs_path.relative_to(Path.cwd())
44-
if is_excluded(rel_path):
47+
if is_excluded(rel_path, excludes_spec):
4548
return
4649
except ValueError:
4750
pass

codelimit/commands/report.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ def _report_functions(report: Report, path: Path, full: bool, fmt, console: Cons
7777
if fmt == ReportFormat.markdown:
7878
console.print(_report_functions_markdown(root, report_units), soft_wrap=True)
7979
else:
80-
console.print(_report_functions_text(root, units, report_units, full), soft_wrap=True)
80+
console.print(
81+
_report_functions_text(root, units, report_units, full), soft_wrap=True
82+
)
8183

8284

8385
def get_root(path: Path) -> Path | None:
@@ -109,17 +111,21 @@ def _report_functions_text(root, units, report_units, full) -> Text:
109111
file_path = unit.file if root is None else root.joinpath(unit.file)
110112
result.append(format_measurement(str(file_path), unit.measurement).append("\n"))
111113
if not full and len(units) > REPORT_LENGTH:
112-
result.append(f"[bold]{len(units) - REPORT_LENGTH} more rows, use --full option to get all rows[/bold]\n")
114+
result.append(
115+
f"[bold]{len(units) - REPORT_LENGTH} more rows, use --full option to get all rows[/bold]\n"
116+
)
113117
return result
114118

115119

116-
def _report_functions_markdown(root: Path | None, report_units: list[ReportUnit]) -> str:
120+
def _report_functions_markdown(
121+
root: Path | None, report_units: list[ReportUnit]
122+
) -> str:
117123
result = ""
118124
result += "| **File** | **Line** | **Column** | **Length** | **Function** |\n"
119125
result += "| --- | ---: | ---: | ---: | --- |\n"
120126
for unit in report_units:
121127
file_path = unit.file if root is None else root.joinpath(unit.file)
122-
type = '✖' if unit.measurement.value > 60 else '⚠'
128+
type = "✖" if unit.measurement.value > 60 else "⚠"
123129
result += (
124130
f"| {str(file_path)} | {unit.measurement.start.line} | {unit.measurement.start.column} | "
125131
f"{unit.measurement.value} | {type} {unit.measurement.unit_name} |\n"

codelimit/commands/upload.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
from codelimit.common.report.Report import Report
66
from codelimit.common.report.ReportReader import ReportReader
77
from codelimit.github_auth import get_github_token
8-
from codelimit.utils import read_cached_report, upload_report, make_report_path
8+
from codelimit.utils import upload_report, make_report_path
99

1010

1111
def upload_command(
12-
repository: str, branch: str, report_file: Path, token: str, url: str
12+
repository: str, branch: str, report_file: Path, token: str, url: str
1313
):
1414
if report_file:
1515
report = ReportReader.from_json(report_file.read_text())

codelimit/common/Scanner.py

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import fnmatch
21
import locale
32
import os
43
from datetime import datetime
@@ -86,15 +85,17 @@ def _scan_folder(
8685
cached_report: Union[Report, None] = None,
8786
add_file_entry: Union[Callable[[SourceFileEntry], None], None] = None,
8887
):
89-
gitignore = _read_gitignore(folder)
88+
excludes = Configuration.excludes.copy()
89+
gitignore_excludes = _read_gitignore(folder)
90+
if gitignore_excludes:
91+
excludes.extend(gitignore_excludes)
92+
excludes_spec = PathSpec.from_lines("gitignore", excludes)
9093
for root, dirs, files in os.walk(folder.absolute()):
9194
files = [f for f in files if not f[0] == "."]
9295
dirs[:] = [d for d in dirs if not d[0] == "."]
9396
for file in files:
9497
rel_path = Path(os.path.join(root, file)).relative_to(folder.absolute())
95-
if is_excluded(rel_path) or (
96-
gitignore is not None and is_excluded_by_gitignore(rel_path, gitignore)
97-
):
98+
if is_excluded(rel_path, excludes_spec):
9899
continue
99100
try:
100101
lexer = get_lexer_for_filename(rel_path)
@@ -178,25 +179,12 @@ def scan_file(tokens: list[Token], language: Language) -> list[Measurement]:
178179
return measurements
179180

180181

181-
def is_excluded(path: Path):
182-
for exclude in Configuration.excludes:
183-
exclude_parts = exclude.split(os.sep)
184-
if len(exclude_parts) == 1:
185-
for part in path.parts:
186-
if fnmatch.fnmatch(part, exclude):
187-
return True
188-
else:
189-
if fnmatch.fnmatch(str(path), exclude):
190-
return True
191-
return False
192-
193-
194-
def _read_gitignore(path: Path) -> PathSpec | None:
182+
def _read_gitignore(path: Path) -> list[str] | None:
195183
gitignore_path = path.joinpath(".gitignore")
196184
if gitignore_path.exists():
197-
return PathSpec.from_lines("gitignore", gitignore_path.read_text().splitlines())
185+
return gitignore_path.read_text().splitlines()
198186
return None
199187

200188

201-
def is_excluded_by_gitignore(path: Path, gitignore: PathSpec):
202-
return gitignore.match_file(path)
189+
def is_excluded(path: Path, spec: PathSpec):
190+
return spec.match_file(path)

codelimit/common/gsm/Pattern.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def __init__(self, start: int, automata: DFA):
1717
def consume(self, item) -> State | None:
1818
for transition in self.state.transition:
1919
predicate_id = id(transition[0])
20-
if not predicate_id in self.predicate_map:
20+
if predicate_id not in self.predicate_map:
2121
self.predicate_map[predicate_id] = deepcopy(transition[0])
2222
predicate = self.predicate_map[predicate_id]
2323
if predicate.accept(item):

codelimit/common/token_matching/predicate/Balanced.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,4 @@ def __hash__(self):
4343
return hash((self.left, self.right, self.depth))
4444

4545
def __str__(self):
46-
return f"<Balanced {self.left} {self.right} {id(self)}>"
46+
return f"<Balanced {self.left} {self.right} {id(self)}>"

codelimit/common/token_matching/predicate/Keyword.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ def __hash__(self):
2222
return hash(self.keyword)
2323

2424
def __str__(self):
25-
return f'<Keyword {self.keyword}>'
25+
return f"<Keyword {self.keyword}>"

codelimit/common/token_matching/predicate/Symbol.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ def __hash__(self):
2222
return hash(self.symbol)
2323

2424
def __str__(self):
25-
return f'<Symbol {self.symbol}>'
25+
return f"<Symbol {self.symbol}>"

codelimit/common/token_matching/predicate/TokenValue.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,5 @@ def __eq__(self, other: object) -> bool:
2121
def __hash__(self):
2222
return hash(self.value)
2323

24-
2524
def __str__(self):
26-
return f'<TokenValue {self.value}>'
25+
return f"<TokenValue {self.value}>"

0 commit comments

Comments
 (0)