redup 0.2.4__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {redup-0.2.4 → redup-0.3.2}/PKG-INFO +76 -6
- {redup-0.2.4 → redup-0.3.2}/README.md +57 -5
- {redup-0.2.4 → redup-0.3.2}/pyproject.toml +19 -1
- {redup-0.2.4 → redup-0.3.2}/src/redup/__init__.py +1 -1
- {redup-0.2.4 → redup-0.3.2}/src/redup/cli_app/main.py +83 -50
- {redup-0.2.4 → redup-0.3.2}/src/redup/core/config.py +3 -3
- redup-0.3.2/src/redup/core/parallel_scanner.py +185 -0
- {redup-0.2.4 → redup-0.3.2}/src/redup/core/pipeline.py +60 -0
- {redup-0.2.4 → redup-0.3.2}/src/redup/core/ts_extractor.py +151 -11
- {redup-0.2.4 → redup-0.3.2}/src/redup/reporters/json_reporter.py +20 -13
- {redup-0.2.4 → redup-0.3.2}/src/redup.egg-info/PKG-INFO +76 -6
- {redup-0.2.4 → redup-0.3.2}/src/redup.egg-info/SOURCES.txt +1 -0
- redup-0.3.2/src/redup.egg-info/requires.txt +49 -0
- redup-0.2.4/src/redup.egg-info/requires.txt +0 -31
- {redup-0.2.4 → redup-0.3.2}/LICENSE +0 -0
- {redup-0.2.4 → redup-0.3.2}/setup.cfg +0 -0
- {redup-0.2.4 → redup-0.3.2}/src/redup/__main__.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/src/redup/cli_app/__init__.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/src/redup/core/__init__.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/src/redup/core/differ.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/src/redup/core/hasher.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/src/redup/core/lsh_matcher.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/src/redup/core/matcher.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/src/redup/core/models.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/src/redup/core/planner.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/src/redup/core/scanner.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/src/redup/reporters/__init__.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/src/redup/reporters/markdown_reporter.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/src/redup/reporters/toon_reporter.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/src/redup/reporters/yaml_reporter.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/src/redup.egg-info/dependency_links.txt +0 -0
- {redup-0.2.4 → redup-0.3.2}/src/redup.egg-info/entry_points.txt +0 -0
- {redup-0.2.4 → redup-0.3.2}/src/redup.egg-info/top_level.txt +0 -0
- {redup-0.2.4 → redup-0.3.2}/tests/test_e2e.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/tests/test_hasher.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/tests/test_matcher.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/tests/test_models.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/tests/test_pipeline.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/tests/test_planner.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/tests/test_reporters.py +0 -0
- {redup-0.2.4 → redup-0.3.2}/tests/test_scanner.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: redup
|
|
3
|
-
Version: 0.2
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Code duplication analyzer and refactoring planner for LLMs
|
|
5
5
|
Author-email: Tom Sapletta <tom@sapletta.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -41,6 +41,24 @@ Requires-Dist: tree-sitter-rust>=0.20; extra == "ast"
|
|
|
41
41
|
Requires-Dist: tree-sitter-java>=0.20; extra == "ast"
|
|
42
42
|
Requires-Dist: tree-sitter-c>=0.20; extra == "ast"
|
|
43
43
|
Requires-Dist: tree-sitter-cpp>=0.20; extra == "ast"
|
|
44
|
+
Requires-Dist: tree-sitter-html>=0.20; extra == "ast"
|
|
45
|
+
Requires-Dist: tree-sitter-css>=0.20; extra == "ast"
|
|
46
|
+
Requires-Dist: tree-sitter-sql>=0.20; extra == "ast"
|
|
47
|
+
Requires-Dist: tree-sitter-json>=0.20; extra == "ast"
|
|
48
|
+
Requires-Dist: tree-sitter-yaml>=0.20; extra == "ast"
|
|
49
|
+
Requires-Dist: tree-sitter-toml>=0.20; extra == "ast"
|
|
50
|
+
Requires-Dist: tree-sitter-xml>=0.20; extra == "ast"
|
|
51
|
+
Requires-Dist: tree-sitter-markdown>=0.20; extra == "ast"
|
|
52
|
+
Requires-Dist: tree-sitter-bash>=0.20; extra == "ast"
|
|
53
|
+
Requires-Dist: tree-sitter-ruby>=0.20; extra == "ast"
|
|
54
|
+
Requires-Dist: tree-sitter-php>=0.20; extra == "ast"
|
|
55
|
+
Requires-Dist: tree-sitter-embedded-template>=0.20; extra == "ast"
|
|
56
|
+
Requires-Dist: tree-sitter-regex>=0.20; extra == "ast"
|
|
57
|
+
Requires-Dist: tree-sitter-scala>=0.20; extra == "ast"
|
|
58
|
+
Requires-Dist: tree-sitter-kotlin>=0.20; extra == "ast"
|
|
59
|
+
Requires-Dist: tree-sitter-swift>=0.20; extra == "ast"
|
|
60
|
+
Requires-Dist: tree-sitter-objc>=0.20; extra == "ast"
|
|
61
|
+
Requires-Dist: tree-sitter-c-sharp>=0.20; extra == "ast"
|
|
44
62
|
Provides-Extra: lsh
|
|
45
63
|
Requires-Dist: datasketch>=1.6; extra == "lsh"
|
|
46
64
|
Provides-Extra: dev
|
|
@@ -57,7 +75,7 @@ Dynamic: license-file
|
|
|
57
75
|
[](https://pypi.org/project/redup/)
|
|
58
76
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
59
77
|
[](https://python.org)
|
|
60
|
-
[](https://pypi.org/project/redup/)
|
|
61
79
|
|
|
62
80
|
reDUP scans codebases for duplicated functions, blocks, and structural patterns — then builds a prioritized refactoring map that LLMs can consume to eliminate redundancy systematically.
|
|
63
81
|
|
|
@@ -65,14 +83,18 @@ reDUP scans codebases for duplicated functions, blocks, and structural patterns
|
|
|
65
83
|
|
|
66
84
|
- **Exact duplicate detection** via SHA-256 block hashing
|
|
67
85
|
- **Structural clone detection** — same AST shape, different variable names
|
|
86
|
+
- **LSH near-duplicate detection** for large code blocks (>50 lines)
|
|
87
|
+
- **Multi-language support** — Python, JavaScript, TypeScript, Go, Rust, Java, C/C++
|
|
88
|
+
- **Parallel scanning** for large projects (2x+ performance improvement)
|
|
68
89
|
- **Fuzzy near-duplicate matching** via SequenceMatcher / rapidfuzz
|
|
69
|
-
- **Function-level analysis** using Python AST extraction
|
|
90
|
+
- **Function-level analysis** using Python AST and tree-sitter extraction
|
|
70
91
|
- **Impact scoring** — prioritizes duplicates by `saved_lines × similarity`
|
|
71
92
|
- **Refactoring planner** — generates concrete extract/inline suggestions
|
|
72
|
-
- **
|
|
73
|
-
- **
|
|
93
|
+
- **Multiple output formats**: JSON, YAML, TOON, Markdown
|
|
94
|
+
- **Configuration system** — TOML files and environment variables
|
|
95
|
+
- **CLI commands**: `scan`, `diff`, `check`, `config`, `info`
|
|
96
|
+
- **CI integration** with configurable quality gates
|
|
74
97
|
- **Clean output** — no syntax warnings from external libraries
|
|
75
|
-
- **Optimized performance** — reduced complexity and improved maintainability
|
|
76
98
|
|
|
77
99
|
## Installation
|
|
78
100
|
|
|
@@ -100,6 +122,23 @@ redup scan .
|
|
|
100
122
|
# Scan with JSON output saved to file
|
|
101
123
|
redup scan ./src --format json --output ./reports/
|
|
102
124
|
|
|
125
|
+
# Parallel scanning for large projects
|
|
126
|
+
redup scan . --parallel --max-workers 4
|
|
127
|
+
|
|
128
|
+
# Multi-language scanning
|
|
129
|
+
redup scan . --ext ".py,.js,.ts,.go"
|
|
130
|
+
|
|
131
|
+
# CI gate with thresholds
|
|
132
|
+
redup check . --max-groups 10 --max-lines 100
|
|
133
|
+
|
|
134
|
+
# Compare two scans
|
|
135
|
+
redup diff before.json after.json
|
|
136
|
+
|
|
137
|
+
# Initialize configuration
|
|
138
|
+
redup config --init
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```bash
|
|
103
142
|
# Scan with all formats
|
|
104
143
|
redup scan . --format all --output ./redup_output/
|
|
105
144
|
|
|
@@ -113,6 +152,37 @@ redup scan . --min-lines 5 --min-sim 0.9
|
|
|
113
152
|
redup info
|
|
114
153
|
```
|
|
115
154
|
|
|
155
|
+
### Configuration
|
|
156
|
+
|
|
157
|
+
Create a `redup.toml` file:
|
|
158
|
+
|
|
159
|
+
```toml
|
|
160
|
+
[scan]
|
|
161
|
+
extensions = ".py,.js,.ts,.go,.rs,.java"
|
|
162
|
+
min_lines = 3
|
|
163
|
+
min_similarity = 0.85
|
|
164
|
+
include_tests = false
|
|
165
|
+
|
|
166
|
+
[lsh]
|
|
167
|
+
enabled = true
|
|
168
|
+
min_lines = 50
|
|
169
|
+
threshold = 0.8
|
|
170
|
+
|
|
171
|
+
[check]
|
|
172
|
+
max_groups = 10
|
|
173
|
+
max_lines = 100
|
|
174
|
+
|
|
175
|
+
[output]
|
|
176
|
+
format = "toon"
|
|
177
|
+
output = "redup_output"
|
|
178
|
+
|
|
179
|
+
[reporting]
|
|
180
|
+
include_snippets = true
|
|
181
|
+
generate_suggestions = true
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Or use `[tool.redup]` in `pyproject.toml`. Environment variables with `REDUP_` prefix override file settings.
|
|
185
|
+
|
|
116
186
|
### Python API
|
|
117
187
|
|
|
118
188
|
```python
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://pypi.org/project/redup/)
|
|
6
6
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
7
7
|
[](https://python.org)
|
|
8
|
-
[](https://pypi.org/project/redup/)
|
|
9
9
|
|
|
10
10
|
reDUP scans codebases for duplicated functions, blocks, and structural patterns — then builds a prioritized refactoring map that LLMs can consume to eliminate redundancy systematically.
|
|
11
11
|
|
|
@@ -13,14 +13,18 @@ reDUP scans codebases for duplicated functions, blocks, and structural patterns
|
|
|
13
13
|
|
|
14
14
|
- **Exact duplicate detection** via SHA-256 block hashing
|
|
15
15
|
- **Structural clone detection** — same AST shape, different variable names
|
|
16
|
+
- **LSH near-duplicate detection** for large code blocks (>50 lines)
|
|
17
|
+
- **Multi-language support** — Python, JavaScript, TypeScript, Go, Rust, Java, C/C++
|
|
18
|
+
- **Parallel scanning** for large projects (2x+ performance improvement)
|
|
16
19
|
- **Fuzzy near-duplicate matching** via SequenceMatcher / rapidfuzz
|
|
17
|
-
- **Function-level analysis** using Python AST extraction
|
|
20
|
+
- **Function-level analysis** using Python AST and tree-sitter extraction
|
|
18
21
|
- **Impact scoring** — prioritizes duplicates by `saved_lines × similarity`
|
|
19
22
|
- **Refactoring planner** — generates concrete extract/inline suggestions
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
23
|
+
- **Multiple output formats**: JSON, YAML, TOON, Markdown
|
|
24
|
+
- **Configuration system** — TOML files and environment variables
|
|
25
|
+
- **CLI commands**: `scan`, `diff`, `check`, `config`, `info`
|
|
26
|
+
- **CI integration** with configurable quality gates
|
|
22
27
|
- **Clean output** — no syntax warnings from external libraries
|
|
23
|
-
- **Optimized performance** — reduced complexity and improved maintainability
|
|
24
28
|
|
|
25
29
|
## Installation
|
|
26
30
|
|
|
@@ -48,6 +52,23 @@ redup scan .
|
|
|
48
52
|
# Scan with JSON output saved to file
|
|
49
53
|
redup scan ./src --format json --output ./reports/
|
|
50
54
|
|
|
55
|
+
# Parallel scanning for large projects
|
|
56
|
+
redup scan . --parallel --max-workers 4
|
|
57
|
+
|
|
58
|
+
# Multi-language scanning
|
|
59
|
+
redup scan . --ext ".py,.js,.ts,.go"
|
|
60
|
+
|
|
61
|
+
# CI gate with thresholds
|
|
62
|
+
redup check . --max-groups 10 --max-lines 100
|
|
63
|
+
|
|
64
|
+
# Compare two scans
|
|
65
|
+
redup diff before.json after.json
|
|
66
|
+
|
|
67
|
+
# Initialize configuration
|
|
68
|
+
redup config --init
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```bash
|
|
51
72
|
# Scan with all formats
|
|
52
73
|
redup scan . --format all --output ./redup_output/
|
|
53
74
|
|
|
@@ -61,6 +82,37 @@ redup scan . --min-lines 5 --min-sim 0.9
|
|
|
61
82
|
redup info
|
|
62
83
|
```
|
|
63
84
|
|
|
85
|
+
### Configuration
|
|
86
|
+
|
|
87
|
+
Create a `redup.toml` file:
|
|
88
|
+
|
|
89
|
+
```toml
|
|
90
|
+
[scan]
|
|
91
|
+
extensions = ".py,.js,.ts,.go,.rs,.java"
|
|
92
|
+
min_lines = 3
|
|
93
|
+
min_similarity = 0.85
|
|
94
|
+
include_tests = false
|
|
95
|
+
|
|
96
|
+
[lsh]
|
|
97
|
+
enabled = true
|
|
98
|
+
min_lines = 50
|
|
99
|
+
threshold = 0.8
|
|
100
|
+
|
|
101
|
+
[check]
|
|
102
|
+
max_groups = 10
|
|
103
|
+
max_lines = 100
|
|
104
|
+
|
|
105
|
+
[output]
|
|
106
|
+
format = "toon"
|
|
107
|
+
output = "redup_output"
|
|
108
|
+
|
|
109
|
+
[reporting]
|
|
110
|
+
include_snippets = true
|
|
111
|
+
generate_suggestions = true
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Or use `[tool.redup]` in `pyproject.toml`. Environment variables with `REDUP_` prefix override file settings.
|
|
115
|
+
|
|
64
116
|
### Python API
|
|
65
117
|
|
|
66
118
|
```python
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "redup"
|
|
7
|
-
version = "0.2
|
|
7
|
+
version = "0.3.2"
|
|
8
8
|
description = "Code duplication analyzer and refactoring planner for LLMs"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -48,6 +48,24 @@ ast = [
|
|
|
48
48
|
"tree-sitter-java>=0.20",
|
|
49
49
|
"tree-sitter-c>=0.20",
|
|
50
50
|
"tree-sitter-cpp>=0.20",
|
|
51
|
+
"tree-sitter-html>=0.20",
|
|
52
|
+
"tree-sitter-css>=0.20",
|
|
53
|
+
"tree-sitter-sql>=0.20",
|
|
54
|
+
"tree-sitter-json>=0.20",
|
|
55
|
+
"tree-sitter-yaml>=0.20",
|
|
56
|
+
"tree-sitter-toml>=0.20",
|
|
57
|
+
"tree-sitter-xml>=0.20",
|
|
58
|
+
"tree-sitter-markdown>=0.20",
|
|
59
|
+
"tree-sitter-bash>=0.20",
|
|
60
|
+
"tree-sitter-ruby>=0.20",
|
|
61
|
+
"tree-sitter-php>=0.20",
|
|
62
|
+
"tree-sitter-embedded-template>=0.20",
|
|
63
|
+
"tree-sitter-regex>=0.20",
|
|
64
|
+
"tree-sitter-scala>=0.20",
|
|
65
|
+
"tree-sitter-kotlin>=0.20",
|
|
66
|
+
"tree-sitter-swift>=0.20",
|
|
67
|
+
"tree-sitter-objc>=0.20",
|
|
68
|
+
"tree-sitter-c-sharp>=0.20",
|
|
51
69
|
]
|
|
52
70
|
lsh = ["datasketch>=1.6"]
|
|
53
71
|
dev = [
|
|
@@ -15,7 +15,7 @@ warnings.filterwarnings("ignore", category=SyntaxWarning)
|
|
|
15
15
|
import redup # noqa: E402
|
|
16
16
|
from redup.core.config import config_to_scan_config, create_sample_redup_toml, load_config # noqa: E402
|
|
17
17
|
from redup.core.models import DuplicationMap, ScanConfig # noqa: E402
|
|
18
|
-
from redup.core.pipeline import analyze # noqa: E402
|
|
18
|
+
from redup.core.pipeline import analyze, analyze_parallel # noqa: E402
|
|
19
19
|
from redup.reporters.json_reporter import to_json # noqa: E402
|
|
20
20
|
from redup.reporters.markdown_reporter import to_markdown # noqa: E402
|
|
21
21
|
from redup.reporters.toon_reporter import to_toon # noqa: E402
|
|
@@ -28,7 +28,7 @@ app = typer.Typer(
|
|
|
28
28
|
)
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
OutputFormat = Literal["json", "yaml", "toon", "all"]
|
|
31
|
+
OutputFormat = Literal["json", "yaml", "toon", "markdown", "all"]
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
DEFAULT_PATH = Path(".")
|
|
@@ -92,14 +92,12 @@ def _build_config_with_file_support(
|
|
|
92
92
|
|
|
93
93
|
def _print_scan_header(
|
|
94
94
|
path: Path,
|
|
95
|
-
|
|
96
|
-
min_lines: int,
|
|
97
|
-
min_similarity: float,
|
|
95
|
+
config: ScanConfig,
|
|
98
96
|
) -> None:
|
|
99
97
|
"""Print scan configuration header."""
|
|
100
98
|
typer.echo(f"reDUP v{redup.__version__} — scanning {path.resolve()}")
|
|
101
|
-
typer.echo(f" extensions: {', '.join(
|
|
102
|
-
typer.echo(f" min_lines: {
|
|
99
|
+
typer.echo(f" extensions: {', '.join(config.extensions)}")
|
|
100
|
+
typer.echo(f" min_lines: {config.min_block_lines}, min_similarity: {config.min_similarity}")
|
|
103
101
|
typer.echo("")
|
|
104
102
|
|
|
105
103
|
|
|
@@ -136,11 +134,15 @@ def _write_results(
|
|
|
136
134
|
]
|
|
137
135
|
for _fmt, renderer, suffix in renderers:
|
|
138
136
|
content = renderer(dup_map)
|
|
137
|
+
if suffix == "json":
|
|
138
|
+
include_snippets = load_config().get("reporting", {}).get("include_snippets", False)
|
|
139
|
+
content = to_json(dup_map, include_snippets=include_snippets)
|
|
139
140
|
target = output_dir / f"duplication.{suffix}"
|
|
140
141
|
target.write_text(content, encoding="utf-8")
|
|
141
142
|
typer.echo(f" → {target}")
|
|
142
143
|
elif format == "json":
|
|
143
|
-
|
|
144
|
+
include_snippets = load_config().get("reporting", {}).get("include_snippets", False)
|
|
145
|
+
_write_output(to_json(dup_map, include_snippets=include_snippets), output_dir, "json")
|
|
144
146
|
elif format == "yaml":
|
|
145
147
|
_write_output(to_yaml(dup_map), output_dir, "yaml")
|
|
146
148
|
elif format == "toon":
|
|
@@ -168,38 +170,52 @@ def scan(
|
|
|
168
170
|
"--output", "-o",
|
|
169
171
|
help="Output directory or file path. Defaults to stdout.",
|
|
170
172
|
),
|
|
171
|
-
extensions: str = typer.Option(
|
|
172
|
-
|
|
173
|
+
extensions: str | None = typer.Option(
|
|
174
|
+
None,
|
|
173
175
|
"--ext", "-e",
|
|
174
|
-
help="Comma-separated file extensions to scan.",
|
|
176
|
+
help="Comma-separated file extensions to scan. Overrides config.",
|
|
175
177
|
),
|
|
176
|
-
min_lines: int = typer.Option(
|
|
177
|
-
|
|
178
|
+
min_lines: int | None = typer.Option(
|
|
179
|
+
None,
|
|
178
180
|
"--min-lines",
|
|
179
|
-
help="Minimum block size (lines)
|
|
181
|
+
help="Minimum block size (lines). Overrides config.",
|
|
180
182
|
),
|
|
181
|
-
min_similarity: float = typer.Option(
|
|
182
|
-
|
|
183
|
+
min_similarity: float | None = typer.Option(
|
|
184
|
+
None,
|
|
183
185
|
"--min-sim",
|
|
184
|
-
help="Minimum similarity score (0.0-1.0)
|
|
186
|
+
help="Minimum similarity score (0.0-1.0). Overrides config.",
|
|
185
187
|
),
|
|
186
|
-
include_tests: bool = typer.Option(
|
|
187
|
-
|
|
188
|
+
include_tests: bool | None = typer.Option(
|
|
189
|
+
None,
|
|
188
190
|
"--include-tests",
|
|
189
|
-
help="Include test files
|
|
191
|
+
help="Include test files. Overrides config.",
|
|
190
192
|
),
|
|
191
193
|
functions_only: bool = typer.Option(
|
|
192
194
|
False,
|
|
193
195
|
"--functions-only",
|
|
194
196
|
help="Only analyze function-level duplicates (faster).",
|
|
195
197
|
),
|
|
198
|
+
parallel: bool = typer.Option(
|
|
199
|
+
False,
|
|
200
|
+
"--parallel",
|
|
201
|
+
help="Use parallel scanning for large projects.",
|
|
202
|
+
),
|
|
203
|
+
max_workers: int = typer.Option(
|
|
204
|
+
None,
|
|
205
|
+
"--max-workers",
|
|
206
|
+
help="Number of parallel workers (default: CPU count).",
|
|
207
|
+
),
|
|
196
208
|
) -> None:
|
|
197
209
|
"""Scan a project for code duplicates and generate a refactoring map."""
|
|
198
|
-
config =
|
|
210
|
+
config = _build_config_with_file_support(path, extensions, min_lines, min_similarity, include_tests)
|
|
199
211
|
|
|
200
|
-
_print_scan_header(path, config
|
|
212
|
+
_print_scan_header(path, config)
|
|
201
213
|
|
|
202
|
-
|
|
214
|
+
# Choose analysis method
|
|
215
|
+
if parallel:
|
|
216
|
+
dup_map = analyze_parallel(config=config, function_level_only=functions_only, max_workers=max_workers)
|
|
217
|
+
else:
|
|
218
|
+
dup_map = analyze(config=config, function_level_only=functions_only)
|
|
203
219
|
|
|
204
220
|
_print_scan_summary(dup_map)
|
|
205
221
|
_write_results(dup_map, format, output, path)
|
|
@@ -252,61 +268,78 @@ def check(
|
|
|
252
268
|
dir_okay=True,
|
|
253
269
|
file_okay=False,
|
|
254
270
|
),
|
|
255
|
-
max_groups: int = typer.Option(
|
|
256
|
-
|
|
271
|
+
max_groups: int | None = typer.Option(
|
|
272
|
+
None,
|
|
257
273
|
"--max-groups",
|
|
258
|
-
help="Maximum allowed duplicate groups
|
|
274
|
+
help="Maximum allowed duplicate groups. Overrides config.",
|
|
259
275
|
),
|
|
260
|
-
max_saved_lines: int = typer.Option(
|
|
261
|
-
|
|
276
|
+
max_saved_lines: int | None = typer.Option(
|
|
277
|
+
None,
|
|
262
278
|
"--max-lines",
|
|
263
|
-
help="Maximum allowed recoverable lines
|
|
279
|
+
help="Maximum allowed recoverable lines. Overrides config.",
|
|
264
280
|
),
|
|
265
|
-
extensions: str = typer.Option(
|
|
266
|
-
|
|
281
|
+
extensions: str | None = typer.Option(
|
|
282
|
+
None,
|
|
267
283
|
"--ext", "-e",
|
|
268
|
-
help="Comma-separated file extensions to scan.",
|
|
284
|
+
help="Comma-separated file extensions to scan. Overrides config.",
|
|
269
285
|
),
|
|
270
|
-
min_lines: int = typer.Option(
|
|
271
|
-
|
|
286
|
+
min_lines: int | None = typer.Option(
|
|
287
|
+
None,
|
|
272
288
|
"--min-lines",
|
|
273
|
-
help="Minimum block size (lines)
|
|
289
|
+
help="Minimum block size (lines). Overrides config.",
|
|
274
290
|
),
|
|
275
|
-
min_similarity: float = typer.Option(
|
|
276
|
-
|
|
291
|
+
min_similarity: float | None = typer.Option(
|
|
292
|
+
None,
|
|
277
293
|
"--min-sim",
|
|
278
|
-
help="Minimum similarity score (0.0-1.0)
|
|
294
|
+
help="Minimum similarity score (0.0-1.0). Overrides config.",
|
|
279
295
|
),
|
|
280
|
-
include_tests: bool = typer.Option(
|
|
281
|
-
|
|
296
|
+
include_tests: bool | None = typer.Option(
|
|
297
|
+
None,
|
|
282
298
|
"--include-tests",
|
|
283
|
-
help="Include test files
|
|
299
|
+
help="Include test files. Overrides config.",
|
|
284
300
|
),
|
|
285
301
|
) -> None:
|
|
286
302
|
"""Check project for duplicates and exit with non-zero code if thresholds exceeded."""
|
|
287
|
-
|
|
303
|
+
file_config = load_config()
|
|
304
|
+
|
|
305
|
+
# Override with CLI arguments
|
|
306
|
+
if extensions is not None:
|
|
307
|
+
file_config["extensions"] = extensions
|
|
308
|
+
if min_lines is not None:
|
|
309
|
+
file_config["min_lines"] = min_lines
|
|
310
|
+
if min_similarity is not None:
|
|
311
|
+
file_config["min_similarity"] = min_similarity
|
|
312
|
+
if include_tests is not None:
|
|
313
|
+
file_config["include_tests"] = include_tests
|
|
314
|
+
|
|
315
|
+
config = config_to_scan_config(file_config, path)
|
|
316
|
+
|
|
317
|
+
# Get thresholds from config or CLI
|
|
318
|
+
check_config = file_config.get("check", {})
|
|
319
|
+
threshold_groups = max_groups if max_groups is not None else check_config.get("max_groups", 10)
|
|
320
|
+
threshold_lines = max_saved_lines if max_saved_lines is not None else check_config.get("max_lines", 100)
|
|
288
321
|
|
|
289
322
|
dup_map = analyze(config=config, function_level_only=True)
|
|
290
323
|
|
|
291
324
|
# Check thresholds
|
|
292
|
-
groups_exceeded = dup_map.total_groups >
|
|
293
|
-
lines_exceeded = dup_map.total_saved_lines >
|
|
325
|
+
groups_exceeded = dup_map.total_groups > threshold_groups
|
|
326
|
+
lines_exceeded = dup_map.total_saved_lines > threshold_lines
|
|
294
327
|
|
|
295
328
|
if groups_exceeded or lines_exceeded:
|
|
296
329
|
typer.echo(f"❌ reDUP check FAILED - thresholds exceeded:", err=True)
|
|
297
|
-
typer.echo(f" Duplicate groups: {dup_map.total_groups} (max: {
|
|
298
|
-
typer.echo(f" Recoverable lines: {dup_map.total_saved_lines} (max: {
|
|
330
|
+
typer.echo(f" Duplicate groups: {dup_map.total_groups} (max: {threshold_groups})", err=True)
|
|
331
|
+
typer.echo(f" Recoverable lines: {dup_map.total_saved_lines} (max: {threshold_lines})", err=True)
|
|
299
332
|
|
|
300
333
|
if groups_exceeded:
|
|
301
|
-
typer.echo(f" ❌ Too many duplicate groups ({dup_map.total_groups} > {
|
|
334
|
+
typer.echo(f" ❌ Too many duplicate groups ({dup_map.total_groups} > {threshold_groups})", err=True)
|
|
302
335
|
if lines_exceeded:
|
|
303
|
-
typer.echo(f" ❌ Too many duplicate lines ({dup_map.total_saved_lines} > {
|
|
336
|
+
typer.echo(f" ❌ Too many duplicate lines ({dup_map.total_saved_lines} > {threshold_lines})", err=True)
|
|
304
337
|
|
|
305
338
|
raise typer.Exit(1)
|
|
306
339
|
else:
|
|
307
340
|
typer.echo(f"✅ reDUP check PASSED")
|
|
308
|
-
typer.echo(f" Duplicate groups: {dup_map.total_groups}/{
|
|
309
|
-
typer.echo(f" Recoverable lines: {dup_map.total_saved_lines}/{
|
|
341
|
+
typer.echo(f" Duplicate groups: {dup_map.total_groups}/{threshold_groups}")
|
|
342
|
+
typer.echo(f" Recoverable lines: {dup_map.total_saved_lines}/{threshold_lines}")
|
|
310
343
|
# Don't exit - just return normally (exit code 0)
|
|
311
344
|
return
|
|
312
345
|
|
|
@@ -103,9 +103,9 @@ def config_to_scan_config(config: dict[str, Any], path: Path) -> ScanConfig:
|
|
|
103
103
|
return ScanConfig(
|
|
104
104
|
root=path,
|
|
105
105
|
extensions=ext_list,
|
|
106
|
-
min_block_lines=scan_config.get("min_lines", 3),
|
|
107
|
-
min_similarity=scan_config.get("min_similarity", 0.85),
|
|
108
|
-
include_tests=scan_config.get("include_tests", False),
|
|
106
|
+
min_block_lines=config.get("min_lines", scan_config.get("min_lines", 3)),
|
|
107
|
+
min_similarity=config.get("min_similarity", scan_config.get("min_similarity", 0.85)),
|
|
108
|
+
include_tests=config.get("include_tests", scan_config.get("include_tests", False)),
|
|
109
109
|
lsh_enabled=lsh_config.get("enabled", True),
|
|
110
110
|
lsh_min_lines=lsh_config.get("min_lines", 50),
|
|
111
111
|
lsh_threshold=lsh_config.get("threshold", 0.8),
|