redup 0.3.1__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.3.1 → redup-0.3.2}/PKG-INFO +50 -2
- {redup-0.3.1 → redup-0.3.2}/README.md +49 -1
- {redup-0.3.1 → redup-0.3.2}/pyproject.toml +1 -1
- {redup-0.3.1 → redup-0.3.2}/src/redup/__init__.py +1 -1
- {redup-0.3.1 → redup-0.3.2}/src/redup/cli_app/main.py +62 -47
- {redup-0.3.1 → redup-0.3.2}/src/redup/core/config.py +3 -3
- {redup-0.3.1 → redup-0.3.2}/src/redup/core/ts_extractor.py +151 -11
- {redup-0.3.1 → redup-0.3.2}/src/redup.egg-info/PKG-INFO +50 -2
- {redup-0.3.1 → redup-0.3.2}/LICENSE +0 -0
- {redup-0.3.1 → redup-0.3.2}/setup.cfg +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup/__main__.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup/cli_app/__init__.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup/core/__init__.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup/core/differ.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup/core/hasher.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup/core/lsh_matcher.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup/core/matcher.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup/core/models.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup/core/parallel_scanner.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup/core/pipeline.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup/core/planner.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup/core/scanner.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup/reporters/__init__.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup/reporters/json_reporter.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup/reporters/markdown_reporter.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup/reporters/toon_reporter.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup/reporters/yaml_reporter.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup.egg-info/SOURCES.txt +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup.egg-info/dependency_links.txt +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup.egg-info/entry_points.txt +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup.egg-info/requires.txt +0 -0
- {redup-0.3.1 → redup-0.3.2}/src/redup.egg-info/top_level.txt +0 -0
- {redup-0.3.1 → redup-0.3.2}/tests/test_e2e.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/tests/test_hasher.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/tests/test_matcher.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/tests/test_models.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/tests/test_pipeline.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/tests/test_planner.py +0 -0
- {redup-0.3.1 → redup-0.3.2}/tests/test_reporters.py +0 -0
- {redup-0.3.1 → 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.3.
|
|
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
|
|
@@ -75,7 +75,7 @@ Dynamic: license-file
|
|
|
75
75
|
[](https://pypi.org/project/redup/)
|
|
76
76
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
77
77
|
[](https://python.org)
|
|
78
|
-
[](https://pypi.org/project/redup/)
|
|
79
79
|
|
|
80
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.
|
|
81
81
|
|
|
@@ -122,6 +122,23 @@ redup scan .
|
|
|
122
122
|
# Scan with JSON output saved to file
|
|
123
123
|
redup scan ./src --format json --output ./reports/
|
|
124
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
|
|
125
142
|
# Scan with all formats
|
|
126
143
|
redup scan . --format all --output ./redup_output/
|
|
127
144
|
|
|
@@ -135,6 +152,37 @@ redup scan . --min-lines 5 --min-sim 0.9
|
|
|
135
152
|
redup info
|
|
136
153
|
```
|
|
137
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
|
+
|
|
138
186
|
### Python API
|
|
139
187
|
|
|
140
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
|
|
|
@@ -52,6 +52,23 @@ redup scan .
|
|
|
52
52
|
# Scan with JSON output saved to file
|
|
53
53
|
redup scan ./src --format json --output ./reports/
|
|
54
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
|
|
55
72
|
# Scan with all formats
|
|
56
73
|
redup scan . --format all --output ./redup_output/
|
|
57
74
|
|
|
@@ -65,6 +82,37 @@ redup scan . --min-lines 5 --min-sim 0.9
|
|
|
65
82
|
redup info
|
|
66
83
|
```
|
|
67
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
|
+
|
|
68
116
|
### Python API
|
|
69
117
|
|
|
70
118
|
```python
|
|
@@ -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
|
|
|
@@ -172,25 +170,25 @@ def scan(
|
|
|
172
170
|
"--output", "-o",
|
|
173
171
|
help="Output directory or file path. Defaults to stdout.",
|
|
174
172
|
),
|
|
175
|
-
extensions: str = typer.Option(
|
|
176
|
-
|
|
173
|
+
extensions: str | None = typer.Option(
|
|
174
|
+
None,
|
|
177
175
|
"--ext", "-e",
|
|
178
|
-
help="Comma-separated file extensions to scan.",
|
|
176
|
+
help="Comma-separated file extensions to scan. Overrides config.",
|
|
179
177
|
),
|
|
180
|
-
min_lines: int = typer.Option(
|
|
181
|
-
|
|
178
|
+
min_lines: int | None = typer.Option(
|
|
179
|
+
None,
|
|
182
180
|
"--min-lines",
|
|
183
|
-
help="Minimum block size (lines)
|
|
181
|
+
help="Minimum block size (lines). Overrides config.",
|
|
184
182
|
),
|
|
185
|
-
min_similarity: float = typer.Option(
|
|
186
|
-
|
|
183
|
+
min_similarity: float | None = typer.Option(
|
|
184
|
+
None,
|
|
187
185
|
"--min-sim",
|
|
188
|
-
help="Minimum similarity score (0.0-1.0)
|
|
186
|
+
help="Minimum similarity score (0.0-1.0). Overrides config.",
|
|
189
187
|
),
|
|
190
|
-
include_tests: bool = typer.Option(
|
|
191
|
-
|
|
188
|
+
include_tests: bool | None = typer.Option(
|
|
189
|
+
None,
|
|
192
190
|
"--include-tests",
|
|
193
|
-
help="Include test files
|
|
191
|
+
help="Include test files. Overrides config.",
|
|
194
192
|
),
|
|
195
193
|
functions_only: bool = typer.Option(
|
|
196
194
|
False,
|
|
@@ -209,9 +207,9 @@ def scan(
|
|
|
209
207
|
),
|
|
210
208
|
) -> None:
|
|
211
209
|
"""Scan a project for code duplicates and generate a refactoring map."""
|
|
212
|
-
config =
|
|
210
|
+
config = _build_config_with_file_support(path, extensions, min_lines, min_similarity, include_tests)
|
|
213
211
|
|
|
214
|
-
_print_scan_header(path, config
|
|
212
|
+
_print_scan_header(path, config)
|
|
215
213
|
|
|
216
214
|
# Choose analysis method
|
|
217
215
|
if parallel:
|
|
@@ -270,61 +268,78 @@ def check(
|
|
|
270
268
|
dir_okay=True,
|
|
271
269
|
file_okay=False,
|
|
272
270
|
),
|
|
273
|
-
max_groups: int = typer.Option(
|
|
274
|
-
|
|
271
|
+
max_groups: int | None = typer.Option(
|
|
272
|
+
None,
|
|
275
273
|
"--max-groups",
|
|
276
|
-
help="Maximum allowed duplicate groups
|
|
274
|
+
help="Maximum allowed duplicate groups. Overrides config.",
|
|
277
275
|
),
|
|
278
|
-
max_saved_lines: int = typer.Option(
|
|
279
|
-
|
|
276
|
+
max_saved_lines: int | None = typer.Option(
|
|
277
|
+
None,
|
|
280
278
|
"--max-lines",
|
|
281
|
-
help="Maximum allowed recoverable lines
|
|
279
|
+
help="Maximum allowed recoverable lines. Overrides config.",
|
|
282
280
|
),
|
|
283
|
-
extensions: str = typer.Option(
|
|
284
|
-
|
|
281
|
+
extensions: str | None = typer.Option(
|
|
282
|
+
None,
|
|
285
283
|
"--ext", "-e",
|
|
286
|
-
help="Comma-separated file extensions to scan.",
|
|
284
|
+
help="Comma-separated file extensions to scan. Overrides config.",
|
|
287
285
|
),
|
|
288
|
-
min_lines: int = typer.Option(
|
|
289
|
-
|
|
286
|
+
min_lines: int | None = typer.Option(
|
|
287
|
+
None,
|
|
290
288
|
"--min-lines",
|
|
291
|
-
help="Minimum block size (lines)
|
|
289
|
+
help="Minimum block size (lines). Overrides config.",
|
|
292
290
|
),
|
|
293
|
-
min_similarity: float = typer.Option(
|
|
294
|
-
|
|
291
|
+
min_similarity: float | None = typer.Option(
|
|
292
|
+
None,
|
|
295
293
|
"--min-sim",
|
|
296
|
-
help="Minimum similarity score (0.0-1.0)
|
|
294
|
+
help="Minimum similarity score (0.0-1.0). Overrides config.",
|
|
297
295
|
),
|
|
298
|
-
include_tests: bool = typer.Option(
|
|
299
|
-
|
|
296
|
+
include_tests: bool | None = typer.Option(
|
|
297
|
+
None,
|
|
300
298
|
"--include-tests",
|
|
301
|
-
help="Include test files
|
|
299
|
+
help="Include test files. Overrides config.",
|
|
302
300
|
),
|
|
303
301
|
) -> None:
|
|
304
302
|
"""Check project for duplicates and exit with non-zero code if thresholds exceeded."""
|
|
305
|
-
|
|
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)
|
|
306
321
|
|
|
307
322
|
dup_map = analyze(config=config, function_level_only=True)
|
|
308
323
|
|
|
309
324
|
# Check thresholds
|
|
310
|
-
groups_exceeded = dup_map.total_groups >
|
|
311
|
-
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
|
|
312
327
|
|
|
313
328
|
if groups_exceeded or lines_exceeded:
|
|
314
329
|
typer.echo(f"❌ reDUP check FAILED - thresholds exceeded:", err=True)
|
|
315
|
-
typer.echo(f" Duplicate groups: {dup_map.total_groups} (max: {
|
|
316
|
-
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)
|
|
317
332
|
|
|
318
333
|
if groups_exceeded:
|
|
319
|
-
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)
|
|
320
335
|
if lines_exceeded:
|
|
321
|
-
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)
|
|
322
337
|
|
|
323
338
|
raise typer.Exit(1)
|
|
324
339
|
else:
|
|
325
340
|
typer.echo(f"✅ reDUP check PASSED")
|
|
326
|
-
typer.echo(f" Duplicate groups: {dup_map.total_groups}/{
|
|
327
|
-
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}")
|
|
328
343
|
# Don't exit - just return normally (exit code 0)
|
|
329
344
|
return
|
|
330
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),
|
|
@@ -15,12 +15,30 @@ from redup.core.scanner import CodeBlock
|
|
|
15
15
|
|
|
16
16
|
# Language mappings for tree-sitter
|
|
17
17
|
_LANGUAGE_MAPPING = {
|
|
18
|
+
# Web/JavaScript
|
|
18
19
|
".js": "javascript",
|
|
19
|
-
".jsx": "javascript",
|
|
20
|
+
".jsx": "javascript",
|
|
20
21
|
".ts": "typescript",
|
|
21
22
|
".tsx": "typescript",
|
|
23
|
+
".mjs": "javascript",
|
|
24
|
+
".cjs": "javascript",
|
|
25
|
+
".mts": "typescript",
|
|
26
|
+
# HTML/XML
|
|
27
|
+
".html": "html",
|
|
28
|
+
".htm": "html",
|
|
29
|
+
".xhtml": "html",
|
|
30
|
+
".xml": "xml",
|
|
31
|
+
".svg": "xml",
|
|
32
|
+
# CSS
|
|
33
|
+
".css": "css",
|
|
34
|
+
".scss": "css",
|
|
35
|
+
".sass": "css",
|
|
36
|
+
".less": "css",
|
|
37
|
+
# Go
|
|
22
38
|
".go": "go",
|
|
39
|
+
# Rust
|
|
23
40
|
".rs": "rust",
|
|
41
|
+
# Java/C-family
|
|
24
42
|
".java": "java",
|
|
25
43
|
".c": "c",
|
|
26
44
|
".cpp": "cpp",
|
|
@@ -28,7 +46,57 @@ _LANGUAGE_MAPPING = {
|
|
|
28
46
|
".cxx": "cpp",
|
|
29
47
|
".h": "c",
|
|
30
48
|
".hpp": "cpp",
|
|
31
|
-
".
|
|
49
|
+
".cs": "c_sharp",
|
|
50
|
+
".scala": "scala",
|
|
51
|
+
".kt": "kotlin",
|
|
52
|
+
# Swift/Objective-C
|
|
53
|
+
".swift": "swift",
|
|
54
|
+
".m": "objc",
|
|
55
|
+
".mm": "objc",
|
|
56
|
+
# Python (fallback to AST)
|
|
57
|
+
".py": "python",
|
|
58
|
+
".pyw": "python",
|
|
59
|
+
# Ruby
|
|
60
|
+
".rb": "ruby",
|
|
61
|
+
".rbw": "ruby",
|
|
62
|
+
".rake": "ruby",
|
|
63
|
+
".gemspec": "ruby",
|
|
64
|
+
# PHP
|
|
65
|
+
".php": "php",
|
|
66
|
+
".phtml": "php",
|
|
67
|
+
".php3": "php",
|
|
68
|
+
".php4": "php",
|
|
69
|
+
".php5": "php",
|
|
70
|
+
".phps": "php",
|
|
71
|
+
# SQL
|
|
72
|
+
".sql": "sql",
|
|
73
|
+
".ddl": "sql",
|
|
74
|
+
".dml": "sql",
|
|
75
|
+
# Data formats
|
|
76
|
+
".json": "json",
|
|
77
|
+
".json5": "json",
|
|
78
|
+
".jsonl": "json",
|
|
79
|
+
".yaml": "yaml",
|
|
80
|
+
".yml": "yaml",
|
|
81
|
+
".toml": "toml",
|
|
82
|
+
# Markdown
|
|
83
|
+
".md": "markdown",
|
|
84
|
+
".markdown": "markdown",
|
|
85
|
+
".mdown": "markdown",
|
|
86
|
+
".mkd": "markdown",
|
|
87
|
+
".mkdown": "markdown",
|
|
88
|
+
# Shell
|
|
89
|
+
".sh": "bash",
|
|
90
|
+
".bash": "bash",
|
|
91
|
+
".zsh": "bash",
|
|
92
|
+
".fish": "bash",
|
|
93
|
+
# Templates
|
|
94
|
+
".erb": "embedded_template",
|
|
95
|
+
".ejs": "embedded_template",
|
|
96
|
+
".hbs": "embedded_template",
|
|
97
|
+
".handlebars": "embedded_template",
|
|
98
|
+
# Regex
|
|
99
|
+
".regex": "regex",
|
|
32
100
|
}
|
|
33
101
|
|
|
34
102
|
|
|
@@ -40,25 +108,79 @@ def _get_tree_sitter_language(language_name: str) -> Any:
|
|
|
40
108
|
try:
|
|
41
109
|
if language_name == "javascript":
|
|
42
110
|
import tree_sitter_javascript
|
|
43
|
-
return tree_sitter_javascript.language()
|
|
111
|
+
return tree_sitter.Language(tree_sitter_javascript.language())
|
|
44
112
|
elif language_name == "typescript":
|
|
45
113
|
import tree_sitter_typescript
|
|
46
|
-
return tree_sitter_typescript.language_typescript()
|
|
114
|
+
return tree_sitter.Language(tree_sitter_typescript.language_typescript())
|
|
115
|
+
elif language_name == "html":
|
|
116
|
+
import tree_sitter_html
|
|
117
|
+
return tree_sitter.Language(tree_sitter_html.language())
|
|
118
|
+
elif language_name == "xml":
|
|
119
|
+
import tree_sitter_xml
|
|
120
|
+
return tree_sitter.Language(tree_sitter_xml.language())
|
|
121
|
+
elif language_name == "css":
|
|
122
|
+
import tree_sitter_css
|
|
123
|
+
return tree_sitter.Language(tree_sitter_css.language())
|
|
47
124
|
elif language_name == "go":
|
|
48
125
|
import tree_sitter_go
|
|
49
|
-
return tree_sitter_go.language()
|
|
126
|
+
return tree_sitter.Language(tree_sitter_go.language())
|
|
50
127
|
elif language_name == "rust":
|
|
51
128
|
import tree_sitter_rust
|
|
52
|
-
return tree_sitter_rust.language()
|
|
129
|
+
return tree_sitter.Language(tree_sitter_rust.language())
|
|
53
130
|
elif language_name == "java":
|
|
54
131
|
import tree_sitter_java
|
|
55
|
-
return tree_sitter_java.language()
|
|
132
|
+
return tree_sitter.Language(tree_sitter_java.language())
|
|
56
133
|
elif language_name == "c":
|
|
57
134
|
import tree_sitter_c
|
|
58
|
-
return tree_sitter_c.language()
|
|
135
|
+
return tree_sitter.Language(tree_sitter_c.language())
|
|
59
136
|
elif language_name == "cpp":
|
|
60
137
|
import tree_sitter_cpp
|
|
61
|
-
return tree_sitter_cpp.language()
|
|
138
|
+
return tree_sitter.Language(tree_sitter_cpp.language())
|
|
139
|
+
elif language_name == "c_sharp":
|
|
140
|
+
import tree_sitter_c_sharp
|
|
141
|
+
return tree_sitter.Language(tree_sitter_c_sharp.language())
|
|
142
|
+
elif language_name == "scala":
|
|
143
|
+
import tree_sitter_scala
|
|
144
|
+
return tree_sitter.Language(tree_sitter_scala.language())
|
|
145
|
+
elif language_name == "kotlin":
|
|
146
|
+
import tree_sitter_kotlin
|
|
147
|
+
return tree_sitter.Language(tree_sitter_kotlin.language())
|
|
148
|
+
elif language_name == "swift":
|
|
149
|
+
import tree_sitter_swift
|
|
150
|
+
return tree_sitter.Language(tree_sitter_swift.language())
|
|
151
|
+
elif language_name == "objc":
|
|
152
|
+
import tree_sitter_objc
|
|
153
|
+
return tree_sitter.Language(tree_sitter_objc.language())
|
|
154
|
+
elif language_name == "ruby":
|
|
155
|
+
import tree_sitter_ruby
|
|
156
|
+
return tree_sitter.Language(tree_sitter_ruby.language())
|
|
157
|
+
elif language_name == "php":
|
|
158
|
+
import tree_sitter_php
|
|
159
|
+
return tree_sitter.Language(tree_sitter_php.language())
|
|
160
|
+
elif language_name == "sql":
|
|
161
|
+
import tree_sitter_sql
|
|
162
|
+
return tree_sitter.Language(tree_sitter_sql.language())
|
|
163
|
+
elif language_name == "json":
|
|
164
|
+
import tree_sitter_json
|
|
165
|
+
return tree_sitter.Language(tree_sitter_json.language())
|
|
166
|
+
elif language_name == "yaml":
|
|
167
|
+
import tree_sitter_yaml
|
|
168
|
+
return tree_sitter.Language(tree_sitter_yaml.language())
|
|
169
|
+
elif language_name == "toml":
|
|
170
|
+
import tree_sitter_toml
|
|
171
|
+
return tree_sitter.Language(tree_sitter_toml.language())
|
|
172
|
+
elif language_name == "markdown":
|
|
173
|
+
import tree_sitter_markdown
|
|
174
|
+
return tree_sitter.Language(tree_sitter_markdown.language())
|
|
175
|
+
elif language_name == "bash":
|
|
176
|
+
import tree_sitter_bash
|
|
177
|
+
return tree_sitter.Language(tree_sitter_bash.language())
|
|
178
|
+
elif language_name == "embedded_template":
|
|
179
|
+
import tree_sitter_embedded_template
|
|
180
|
+
return tree_sitter.Language(tree_sitter_embedded_template.language())
|
|
181
|
+
elif language_name == "regex":
|
|
182
|
+
import tree_sitter_regex
|
|
183
|
+
return tree_sitter.Language(tree_sitter_regex.language())
|
|
62
184
|
else:
|
|
63
185
|
return None
|
|
64
186
|
except ImportError:
|
|
@@ -302,8 +424,7 @@ def extract_functions_treesitter(source: str, file_path: str) -> list[CodeBlock]
|
|
|
302
424
|
return []
|
|
303
425
|
|
|
304
426
|
# Parse the source code
|
|
305
|
-
parser = tree_sitter.Parser()
|
|
306
|
-
parser.set_language(language)
|
|
427
|
+
parser = tree_sitter.Parser(language)
|
|
307
428
|
|
|
308
429
|
try:
|
|
309
430
|
tree = parser.parse(bytes(source, "utf8"))
|
|
@@ -321,10 +442,29 @@ def extract_functions_treesitter(source: str, file_path: str) -> list[CodeBlock]
|
|
|
321
442
|
return _extract_functions_rust(tree.root_node, source_lines, file_path)
|
|
322
443
|
elif language_name == "java":
|
|
323
444
|
return _extract_functions_java(tree.root_node, source_lines, file_path)
|
|
445
|
+
elif language_name == "c_sharp":
|
|
446
|
+
return _extract_functions_c_sharp(tree.root_node, source_lines, file_path)
|
|
447
|
+
elif language_name == "scala":
|
|
448
|
+
return _extract_functions_scala(tree.root_node, source_lines, file_path)
|
|
449
|
+
elif language_name == "kotlin":
|
|
450
|
+
return _extract_functions_kotlin(tree.root_node, source_lines, file_path)
|
|
451
|
+
elif language_name == "swift":
|
|
452
|
+
return _extract_functions_swift(tree.root_node, source_lines, file_path)
|
|
453
|
+
elif language_name == "objc":
|
|
454
|
+
return _extract_functions_objc(tree.root_node, source_lines, file_path)
|
|
455
|
+
elif language_name == "ruby":
|
|
456
|
+
return _extract_functions_ruby(tree.root_node, source_lines, file_path)
|
|
457
|
+
elif language_name == "php":
|
|
458
|
+
return _extract_functions_php(tree.root_node, source_lines, file_path)
|
|
459
|
+
elif language_name == "bash":
|
|
460
|
+
return _extract_functions_bash(tree.root_node, source_lines, file_path)
|
|
324
461
|
elif language_name in ("c", "cpp"):
|
|
325
462
|
# C/C++ function extraction would be more complex
|
|
326
463
|
# For now, return empty list
|
|
327
464
|
return []
|
|
465
|
+
elif language_name in ("html", "xml", "css", "json", "yaml", "toml", "markdown", "embedded_template", "regex", "sql"):
|
|
466
|
+
# Data formats and markup languages don't have functions in the traditional sense
|
|
467
|
+
return []
|
|
328
468
|
|
|
329
469
|
return []
|
|
330
470
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: redup
|
|
3
|
-
Version: 0.3.
|
|
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
|
|
@@ -75,7 +75,7 @@ Dynamic: license-file
|
|
|
75
75
|
[](https://pypi.org/project/redup/)
|
|
76
76
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
77
77
|
[](https://python.org)
|
|
78
|
-
[](https://pypi.org/project/redup/)
|
|
79
79
|
|
|
80
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.
|
|
81
81
|
|
|
@@ -122,6 +122,23 @@ redup scan .
|
|
|
122
122
|
# Scan with JSON output saved to file
|
|
123
123
|
redup scan ./src --format json --output ./reports/
|
|
124
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
|
|
125
142
|
# Scan with all formats
|
|
126
143
|
redup scan . --format all --output ./redup_output/
|
|
127
144
|
|
|
@@ -135,6 +152,37 @@ redup scan . --min-lines 5 --min-sim 0.9
|
|
|
135
152
|
redup info
|
|
136
153
|
```
|
|
137
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
|
+
|
|
138
186
|
### Python API
|
|
139
187
|
|
|
140
188
|
```python
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|