redup 0.4.4__tar.gz → 0.4.6__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.
Files changed (63) hide show
  1. {redup-0.4.4 → redup-0.4.6}/PKG-INFO +3 -2
  2. {redup-0.4.4 → redup-0.4.6}/README.md +1 -1
  3. {redup-0.4.4 → redup-0.4.6}/pyproject.toml +2 -1
  4. {redup-0.4.4 → redup-0.4.6}/src/redup/__init__.py +1 -1
  5. {redup-0.4.4 → redup-0.4.6}/src/redup/cli_app/main.py +15 -12
  6. {redup-0.4.4 → redup-0.4.6}/src/redup/cli_app/output_writer.py +4 -3
  7. {redup-0.4.4 → redup-0.4.6}/src/redup/cli_app/scan_commands.py +2 -2
  8. redup-0.4.6/src/redup/config.py +172 -0
  9. {redup-0.4.4 → redup-0.4.6}/src/redup/core/config.py +21 -4
  10. {redup-0.4.4 → redup-0.4.6}/src/redup.egg-info/PKG-INFO +3 -2
  11. {redup-0.4.4 → redup-0.4.6}/src/redup.egg-info/SOURCES.txt +1 -0
  12. {redup-0.4.4 → redup-0.4.6}/src/redup.egg-info/requires.txt +1 -0
  13. {redup-0.4.4 → redup-0.4.6}/tests/test_e2e.py +6 -6
  14. {redup-0.4.4 → redup-0.4.6}/LICENSE +0 -0
  15. {redup-0.4.4 → redup-0.4.6}/setup.cfg +0 -0
  16. {redup-0.4.4 → redup-0.4.6}/src/redup/__main__.py +0 -0
  17. {redup-0.4.4 → redup-0.4.6}/src/redup/cli_app/__init__.py +0 -0
  18. {redup-0.4.4 → redup-0.4.6}/src/redup/cli_app/config_builder.py +0 -0
  19. {redup-0.4.4 → redup-0.4.6}/src/redup/cli_app/fuzzy_similarity.py +0 -0
  20. {redup-0.4.4 → redup-0.4.6}/src/redup/cli_app/scan_helpers.py +0 -0
  21. {redup-0.4.4 → redup-0.4.6}/src/redup/core/__init__.py +0 -0
  22. {redup-0.4.4 → redup-0.4.6}/src/redup/core/cache.py +0 -0
  23. {redup-0.4.4 → redup-0.4.6}/src/redup/core/differ.py +0 -0
  24. {redup-0.4.4 → redup-0.4.6}/src/redup/core/fuzzy_similarity.py +0 -0
  25. {redup-0.4.4 → redup-0.4.6}/src/redup/core/hash_cache.py +0 -0
  26. {redup-0.4.4 → redup-0.4.6}/src/redup/core/hasher.py +0 -0
  27. {redup-0.4.4 → redup-0.4.6}/src/redup/core/lazy_grouper.py +0 -0
  28. {redup-0.4.4 → redup-0.4.6}/src/redup/core/lsh_matcher.py +0 -0
  29. {redup-0.4.4 → redup-0.4.6}/src/redup/core/matcher.py +0 -0
  30. {redup-0.4.4 → redup-0.4.6}/src/redup/core/models.py +0 -0
  31. {redup-0.4.4 → redup-0.4.6}/src/redup/core/pipeline.py +0 -0
  32. {redup-0.4.4 → redup-0.4.6}/src/redup/core/planner.py +0 -0
  33. {redup-0.4.4 → redup-0.4.6}/src/redup/core/python_parser.py +0 -0
  34. {redup-0.4.4 → redup-0.4.6}/src/redup/core/scanner.py +0 -0
  35. {redup-0.4.4 → redup-0.4.6}/src/redup/core/semantic.py +0 -0
  36. {redup-0.4.4 → redup-0.4.6}/src/redup/core/ts_extractor.py +0 -0
  37. {redup-0.4.4 → redup-0.4.6}/src/redup/core/universal_fuzzy.py +0 -0
  38. {redup-0.4.4 → redup-0.4.6}/src/redup/core/utils/__init__.py +0 -0
  39. {redup-0.4.4 → redup-0.4.6}/src/redup/core/utils/diff_helpers.py +0 -0
  40. {redup-0.4.4 → redup-0.4.6}/src/redup/core/utils/duplicate_finders.py +0 -0
  41. {redup-0.4.4 → redup-0.4.6}/src/redup/core/utils/function_extractor.py +0 -0
  42. {redup-0.4.4 → redup-0.4.6}/src/redup/core/utils/hash_utils.py +0 -0
  43. {redup-0.4.4 → redup-0.4.6}/src/redup/core/utils/language_dispatcher.py +0 -0
  44. {redup-0.4.4 → redup-0.4.6}/src/redup/mcp_server.py +0 -0
  45. {redup-0.4.4 → redup-0.4.6}/src/redup/reporters/__init__.py +0 -0
  46. {redup-0.4.4 → redup-0.4.6}/src/redup/reporters/code2llm_reporter.py +0 -0
  47. {redup-0.4.4 → redup-0.4.6}/src/redup/reporters/enhanced_reporter.py +0 -0
  48. {redup-0.4.4 → redup-0.4.6}/src/redup/reporters/json_reporter.py +0 -0
  49. {redup-0.4.4 → redup-0.4.6}/src/redup/reporters/markdown_reporter.py +0 -0
  50. {redup-0.4.4 → redup-0.4.6}/src/redup/reporters/toon_reporter.py +0 -0
  51. {redup-0.4.4 → redup-0.4.6}/src/redup/reporters/yaml_reporter.py +0 -0
  52. {redup-0.4.4 → redup-0.4.6}/src/redup.egg-info/dependency_links.txt +0 -0
  53. {redup-0.4.4 → redup-0.4.6}/src/redup.egg-info/entry_points.txt +0 -0
  54. {redup-0.4.4 → redup-0.4.6}/src/redup.egg-info/top_level.txt +0 -0
  55. {redup-0.4.4 → redup-0.4.6}/tests/test_hasher.py +0 -0
  56. {redup-0.4.4 → redup-0.4.6}/tests/test_matcher.py +0 -0
  57. {redup-0.4.4 → redup-0.4.6}/tests/test_mcp_server.py +0 -0
  58. {redup-0.4.4 → redup-0.4.6}/tests/test_models.py +0 -0
  59. {redup-0.4.4 → redup-0.4.6}/tests/test_pipeline.py +0 -0
  60. {redup-0.4.4 → redup-0.4.6}/tests/test_planner.py +0 -0
  61. {redup-0.4.4 → redup-0.4.6}/tests/test_reporters.py +0 -0
  62. {redup-0.4.4 → redup-0.4.6}/tests/test_scanner.py +0 -0
  63. {redup-0.4.4 → redup-0.4.6}/tests/test_ts_extractor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redup
3
- Version: 0.4.4
3
+ Version: 0.4.6
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
@@ -27,6 +27,7 @@ Requires-Dist: typer>=0.12.0
27
27
  Requires-Dist: rich>=13.0
28
28
  Requires-Dist: pydantic>=2.0
29
29
  Requires-Dist: tomli>=2.0; python_version < "3.11"
30
+ Requires-Dist: python-dotenv>=1.0
30
31
  Provides-Extra: all
31
32
  Requires-Dist: redup[fast]; extra == "all"
32
33
  Requires-Dist: redup[fuzzy]; extra == "all"
@@ -92,7 +93,7 @@ Dynamic: license-file
92
93
  [![PyPI](https://img.shields.io/pypi/v/redup)](https://pypi.org/project/redup/)
93
94
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
94
95
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://python.org)
95
- [![Version](https://img.shields.io/badge/version-0.4.4-green.svg)](https://pypi.org/project/redup/)
96
+ [![Version](https://img.shields.io/badge/version-0.4.6-green.svg)](https://pypi.org/project/redup/)
96
97
 
97
98
  reDUP scans codebases for duplicated functions, blocks, and structural patterns — then builds a prioritized refactoring map that LLMs can consume to eliminate redundancy systematically.
98
99
 
@@ -5,7 +5,7 @@
5
5
  [![PyPI](https://img.shields.io/pypi/v/redup)](https://pypi.org/project/redup/)
6
6
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
7
7
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://python.org)
8
- [![Version](https://img.shields.io/badge/version-0.4.4-green.svg)](https://pypi.org/project/redup/)
8
+ [![Version](https://img.shields.io/badge/version-0.4.6-green.svg)](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
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "redup"
7
- version = "0.4.4"
7
+ version = "0.4.6"
8
8
  description = "Code duplication analyzer and refactoring planner for LLMs"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -31,6 +31,7 @@ dependencies = [
31
31
  "rich>=13.0",
32
32
  "pydantic>=2.0",
33
33
  "tomli>=2.0; python_version<'3.11'",
34
+ "python-dotenv>=1.0",
34
35
  ]
35
36
 
36
37
  [project.optional-dependencies]
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "0.4.4"
5
+ __version__ = "0.4.6"
6
6
 
7
7
  from redup.core.models import (
8
8
  DuplicateFragment,
@@ -13,6 +13,7 @@ import typer
13
13
  warnings.filterwarnings("ignore", category=SyntaxWarning)
14
14
 
15
15
  import redup # noqa: E402
16
+ from redup.config import config as global_config, reload_config # noqa: E402
16
17
  from redup.core.config import create_sample_redup_toml, load_config # noqa: E402
17
18
  from redup.core.models import DuplicationMap, ScanConfig # noqa: E402
18
19
 
@@ -35,7 +36,8 @@ OutputFormat = Literal["json", "yaml", "toon", "markdown", "all", "enhanced", "c
35
36
 
36
37
 
37
38
  DEFAULT_PATH = Path(".")
38
- DEFAULT_FORMAT: OutputFormat = "toon"
39
+ # Use centralized config for defaults
40
+ DEFAULT_FORMAT: OutputFormat = global_config.DEFAULT_FORMAT # type: ignore
39
41
  DEFAULT_OUTPUT: Path | None = None
40
42
 
41
43
 
@@ -78,14 +80,15 @@ def scan(
78
80
  "--include-tests",
79
81
  help="Include test files. Overrides config.",
80
82
  ),
81
- functions_only: bool = typer.Option(
82
- True,
83
- "--functions-only/--no-functions-only",
84
- help="Only analyze function-level duplicates (default: enabled for speed).",
83
+ no_functions_only: bool = typer.Option(
84
+ False,
85
+ "--no-functions-only",
86
+ help="Analyze all code, not just functions (default: functions-only for speed).",
85
87
  ),
86
88
  parallel: bool = typer.Option(
87
89
  False,
88
- "--parallel/--no-parallel",
90
+ "--parallel",
91
+ "--no-parallel",
89
92
  help="Use parallel scanning for large projects (default: disabled due to issues).",
90
93
  ),
91
94
  max_workers: int | None = typer.Option(
@@ -98,10 +101,10 @@ def scan(
98
101
  "--incremental",
99
102
  help="Use incremental scanning with caching.",
100
103
  ),
101
- memory_cache: bool = typer.Option(
102
- True,
103
- "--memory-cache/--no-memory-cache",
104
- help="Use memory cache for faster scanning.",
104
+ no_memory_cache: bool = typer.Option(
105
+ False,
106
+ "--no-memory-cache",
107
+ help="Disable memory cache for scanning.",
105
108
  ),
106
109
  max_cache_mb: int = typer.Option(
107
110
  512,
@@ -122,8 +125,8 @@ def scan(
122
125
  """Scan a project for code duplicates."""
123
126
  return scan_command(
124
127
  path, format, output, extensions, min_lines, min_similarity,
125
- include_tests, functions_only, parallel, max_workers,
126
- incremental, memory_cache, max_cache_mb, fuzzy, fuzzy_threshold
128
+ include_tests, not no_functions_only, parallel, max_workers,
129
+ incremental, not no_memory_cache, max_cache_mb, fuzzy, fuzzy_threshold
127
130
  )
128
131
 
129
132
 
@@ -3,6 +3,7 @@
3
3
  from pathlib import Path
4
4
  from typing import Any
5
5
 
6
+ from redup.config import config, get_default_filename
6
7
  from redup.core.models import DuplicationMap
7
8
  from redup.reporters.json_reporter import to_json
8
9
  from redup.reporters.markdown_reporter import to_markdown
@@ -15,9 +16,9 @@ from redup.reporters.enhanced_reporter import EnhancedReporter
15
16
  def write_output(content: str, output: Path | None, suffix: str) -> None:
16
17
  """Write content to file or stdout."""
17
18
  import typer
18
-
19
+
19
20
  if output:
20
- target = output if output.suffix else output / f"duplication.{suffix}"
21
+ target = output if output.suffix else output / get_default_filename(suffix)
21
22
  target.parent.mkdir(parents=True, exist_ok=True)
22
23
  target.write_text(content, encoding="utf-8")
23
24
  typer.echo(f" → {target}")
@@ -70,7 +71,7 @@ def write_results(dup_map: DuplicationMap, format: str, output: Any, path: Path)
70
71
  base_dir = path
71
72
 
72
73
  for fmt, content in formats.items():
73
- target = base_dir / f"duplication.{fmt}"
74
+ target = base_dir / get_default_filename(fmt)
74
75
  target.write_text(content, encoding="utf-8")
75
76
  typer.echo(f" → {target}")
76
77
  else:
@@ -21,11 +21,11 @@ def scan_command(
21
21
  min_lines: Any = typer.Option(None, "--min-lines", help="Minimum block size (lines). Overrides config."),
22
22
  min_similarity: Any = typer.Option(None, "--min-sim", help="Minimum similarity score (0.0-1.0). Overrides config."),
23
23
  include_tests: bool = typer.Option(False, "--include-tests", help="Include test files in analysis."),
24
- functions_only: bool = typer.Option(True, "--functions-only/--no-functions-only", help="Only analyze function-level duplicates (default: enabled for speed)."),
24
+ functions_only: bool = typer.Option(True, "--functions-only", help="Only analyze function-level duplicates (default: enabled for speed)."),
25
25
  parallel: bool = typer.Option(False, "--parallel", help="Use parallel processing for faster analysis."),
26
26
  max_workers: Any = typer.Option(None, "--max-workers", help="Maximum number of parallel workers."),
27
27
  incremental: bool = typer.Option(False, "--incremental", help="Use incremental scanning (cache-based)."),
28
- memory_cache: bool = typer.Option(True, "--memory-cache/--no-memory-cache", help="Use memory cache for faster processing."),
28
+ memory_cache: bool = typer.Option(True, "--memory-cache", help="Use memory cache for faster processing."),
29
29
  max_cache_mb: int = typer.Option(512, "--max-cache-mb", help="Maximum memory cache size in MB."),
30
30
  fuzzy: bool = typer.Option(False, "--fuzzy", help="Enable fuzzy similarity detection."),
31
31
  fuzzy_threshold: float = typer.Option(0.8, "--fuzzy-threshold", help="Fuzzy similarity threshold (0.0-1.0)."),
@@ -0,0 +1,172 @@
1
+ """Centralized configuration for reDUP with .env file support.
2
+
3
+ This module provides global configuration settings that can be customized via:
4
+ 1. Environment variables (REDUP_* prefix)
5
+ 2. .env files in the project directory
6
+ 3. redup.toml configuration file
7
+ 4. Programmatic overrides
8
+
9
+ Example .env file:
10
+ REDUP_DEFAULT_OUTPUT_FILENAME=duplication.toon.yaml
11
+ REDUP_DEFAULT_FORMAT=toon
12
+ REDUP_MIN_LINES=5
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import os
18
+ from pathlib import Path
19
+ from typing import Any
20
+
21
+ # Try to load .env file if python-dotenv is available
22
+ try:
23
+ from dotenv import load_dotenv
24
+
25
+ # Load .env from current working directory if it exists
26
+ _env_path = Path.cwd() / ".env"
27
+ if _env_path.exists():
28
+ load_dotenv(_env_path, override=True)
29
+ except ImportError:
30
+ load_dotenv = None # type: ignore
31
+
32
+
33
+ class RedupConfig:
34
+ """Global configuration container for reDUP settings.
35
+
36
+ Attributes can be set via:
37
+ - Environment variables (REDUP_* prefix, e.g., REDUP_DEFAULT_OUTPUT_FILENAME)
38
+ - .env files in the project directory (loaded automatically)
39
+ - Direct attribute assignment (e.g., config.DEFAULT_OUTPUT_FILENAME = "...")
40
+ """
41
+
42
+ # Output settings
43
+ DEFAULT_OUTPUT_FILENAME: str = "duplication.toon.yaml"
44
+ DEFAULT_FORMAT: str = "toon"
45
+ DEFAULT_OUTPUT_DIR: str = "."
46
+
47
+ # Scan settings
48
+ DEFAULT_MIN_LINES: int = 3
49
+ DEFAULT_MIN_SIMILARITY: float = 0.85
50
+ DEFAULT_EXTENSIONS: str = ".py"
51
+ DEFAULT_INCLUDE_TESTS: bool = False
52
+
53
+ # Performance settings
54
+ DEFAULT_PARALLEL: bool = False
55
+ DEFAULT_MAX_WORKERS: int | None = None
56
+ DEFAULT_INCREMENTAL: bool = False
57
+ DEFAULT_MEMORY_CACHE: bool = True
58
+ DEFAULT_MAX_CACHE_MB: int = 512
59
+
60
+ # Fuzzy settings
61
+ DEFAULT_FUZZY: bool = False
62
+ DEFAULT_FUZZY_THRESHOLD: float = 0.8
63
+
64
+ # LSH settings
65
+ DEFAULT_LSH_ENABLED: bool = True
66
+ DEFAULT_LSH_MIN_LINES: int = 50
67
+ DEFAULT_LSH_THRESHOLD: float = 0.8
68
+
69
+ # Cache settings
70
+ DEFAULT_CACHE_DIR: str = ".redup/cache"
71
+
72
+ @classmethod
73
+ def _env_name(cls, attr_name: str) -> str:
74
+ """Convert attribute name to environment variable name."""
75
+ return f"REDUP_{attr_name}"
76
+
77
+ @classmethod
78
+ def _load_from_env(cls) -> dict[str, Any]:
79
+ """Load configuration from environment variables."""
80
+ config = {}
81
+ for attr_name in dir(cls):
82
+ if attr_name.startswith("DEFAULT_"):
83
+ env_name = cls._env_name(attr_name)
84
+ value = os.getenv(env_name)
85
+ if value is not None:
86
+ # Convert to appropriate type based on default
87
+ default_value = getattr(cls, attr_name)
88
+ if isinstance(default_value, bool):
89
+ config[attr_name] = value.lower() in ("true", "1", "yes", "on")
90
+ elif isinstance(default_value, int):
91
+ try:
92
+ config[attr_name] = int(value)
93
+ except ValueError:
94
+ pass
95
+ elif isinstance(default_value, float):
96
+ try:
97
+ config[attr_name] = float(value)
98
+ except ValueError:
99
+ pass
100
+ else:
101
+ config[attr_name] = value
102
+ return config
103
+
104
+ @classmethod
105
+ def reload(cls, env_path: Path | None = None) -> None:
106
+ """Reload configuration from .env file and environment variables.
107
+
108
+ Args:
109
+ env_path: Optional path to .env file. If None, looks for .env in current directory.
110
+ """
111
+ # Load from .env file if specified or found
112
+ if load_dotenv is not None:
113
+ if env_path is not None:
114
+ if env_path.exists():
115
+ load_dotenv(env_path, override=True)
116
+ else:
117
+ cwd_env = Path.cwd() / ".env"
118
+ if cwd_env.exists():
119
+ load_dotenv(cwd_env, override=True)
120
+
121
+ # Update class attributes from environment variables
122
+ for attr_name, value in cls._load_from_env().items():
123
+ setattr(cls, attr_name, value)
124
+
125
+ @classmethod
126
+ def get(cls, attr_name: str, default: Any = None) -> Any:
127
+ """Get configuration value by name."""
128
+ if hasattr(cls, attr_name):
129
+ return getattr(cls, attr_name)
130
+ return default
131
+
132
+ @classmethod
133
+ def set(cls, attr_name: str, value: Any) -> None:
134
+ """Set configuration value by name."""
135
+ if hasattr(cls, attr_name):
136
+ setattr(cls, attr_name, value)
137
+ else:
138
+ raise AttributeError(f"No such config attribute: {attr_name}")
139
+
140
+
141
+ # Global config instance
142
+ config = RedupConfig()
143
+
144
+ # Load initial configuration from .env if present
145
+ config.reload()
146
+
147
+
148
+ def get_default_filename(suffix: str | None = None) -> str:
149
+ """Get default output filename.
150
+
151
+ Args:
152
+ suffix: Optional file suffix. If None, uses the suffix from DEFAULT_OUTPUT_FILENAME.
153
+
154
+ Returns:
155
+ Default filename string.
156
+ """
157
+ if suffix is None:
158
+ return config.DEFAULT_OUTPUT_FILENAME
159
+ # If suffix is provided, construct filename from base + suffix
160
+ base = config.DEFAULT_OUTPUT_FILENAME.rsplit(".", 1)[0]
161
+ return f"{base}.{suffix}"
162
+
163
+
164
+ def reload_config(env_path: Path | None = None) -> None:
165
+ """Reload configuration from .env file.
166
+
167
+ This function can be called to reload settings when .env file changes.
168
+
169
+ Args:
170
+ env_path: Optional path to .env file. If None, looks for .env in current directory.
171
+ """
172
+ config.reload(env_path)
@@ -14,6 +14,7 @@ except ImportError:
14
14
  except ImportError:
15
15
  tomllib = None
16
16
 
17
+ from redup.config import config as global_config, reload_config
17
18
  from redup.core.models import ScanConfig
18
19
 
19
20
 
@@ -50,11 +51,20 @@ def load_config() -> dict[str, Any]:
50
51
 
51
52
  Priority order:
52
53
  1. Environment variables (REDUP_*)
53
- 2. redup.toml in current directory
54
- 3. [tool.redup] in pyproject.toml
55
- 4. Defaults
54
+ 2. .env file in current directory (auto-loaded via redup.config)
55
+ 3. redup.toml in current directory
56
+ 4. [tool.redup] in pyproject.toml
57
+ 5. Global config defaults (redup.config)
56
58
  """
57
- config = {}
59
+ # Start with global config defaults (already includes .env if present)
60
+ config = {
61
+ "extensions": global_config.DEFAULT_EXTENSIONS,
62
+ "min_lines": global_config.DEFAULT_MIN_LINES,
63
+ "min_similarity": global_config.DEFAULT_MIN_SIMILARITY,
64
+ "include_tests": global_config.DEFAULT_INCLUDE_TESTS,
65
+ "output": global_config.DEFAULT_OUTPUT_DIR,
66
+ "format": global_config.DEFAULT_FORMAT,
67
+ }
58
68
 
59
69
  # Load from redup.toml
60
70
  config.update(_get_config_from_redup_toml())
@@ -116,6 +126,11 @@ def create_sample_redup_toml() -> str:
116
126
  """Create a sample redup.toml configuration file content."""
117
127
  return """# reDUP Configuration File
118
128
  # See https://github.com/semcod/redup for documentation
129
+ #
130
+ # You can also use a .env file in your project directory:
131
+ # REDUP_DEFAULT_OUTPUT_FILENAME=my_duplication.yaml
132
+ # REDUP_DEFAULT_FORMAT=toon
133
+ # REDUP_MIN_LINES=5
119
134
 
120
135
  [scan]
121
136
  # File extensions to scan (comma-separated)
@@ -143,6 +158,8 @@ max_lines = 100
143
158
  [output]
144
159
  # Default output format
145
160
  format = "toon"
161
+ # Default output filename (can include suffix)
162
+ filename = "duplication.toon.yaml"
146
163
  # Default output directory (relative to project root)
147
164
  output = "redup_output"
148
165
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redup
3
- Version: 0.4.4
3
+ Version: 0.4.6
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
@@ -27,6 +27,7 @@ Requires-Dist: typer>=0.12.0
27
27
  Requires-Dist: rich>=13.0
28
28
  Requires-Dist: pydantic>=2.0
29
29
  Requires-Dist: tomli>=2.0; python_version < "3.11"
30
+ Requires-Dist: python-dotenv>=1.0
30
31
  Provides-Extra: all
31
32
  Requires-Dist: redup[fast]; extra == "all"
32
33
  Requires-Dist: redup[fuzzy]; extra == "all"
@@ -92,7 +93,7 @@ Dynamic: license-file
92
93
  [![PyPI](https://img.shields.io/pypi/v/redup)](https://pypi.org/project/redup/)
93
94
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
94
95
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://python.org)
95
- [![Version](https://img.shields.io/badge/version-0.4.4-green.svg)](https://pypi.org/project/redup/)
96
+ [![Version](https://img.shields.io/badge/version-0.4.6-green.svg)](https://pypi.org/project/redup/)
96
97
 
97
98
  reDUP scans codebases for duplicated functions, blocks, and structural patterns — then builds a prioritized refactoring map that LLMs can consume to eliminate redundancy systematically.
98
99
 
@@ -3,6 +3,7 @@ README.md
3
3
  pyproject.toml
4
4
  src/redup/__init__.py
5
5
  src/redup/__main__.py
6
+ src/redup/config.py
6
7
  src/redup/mcp_server.py
7
8
  src/redup.egg-info/PKG-INFO
8
9
  src/redup.egg-info/SOURCES.txt
@@ -2,6 +2,7 @@ pyyaml>=6.0
2
2
  typer>=0.12.0
3
3
  rich>=13.0
4
4
  pydantic>=2.0
5
+ python-dotenv>=1.0
5
6
 
6
7
  [:python_version < "3.11"]
7
8
  tomli>=2.0
@@ -139,7 +139,7 @@ class TestCLIScanToon:
139
139
  "scan", str(project_with_duplicates), "-f", "toon", "-o", str(out)
140
140
  ])
141
141
  assert result.exit_code == 0
142
- toon_file = out / "duplication.toon"
142
+ toon_file = out / "duplication.toon.yaml"
143
143
  assert toon_file.exists()
144
144
  content = toon_file.read_text()
145
145
  assert "DUPLICATES" in content
@@ -239,7 +239,7 @@ class TestCLIScanAll:
239
239
  assert result.exit_code == 0
240
240
  assert (out / "duplication.json").exists()
241
241
  assert (out / "duplication.yaml").exists()
242
- assert (out / "duplication.toon").exists()
242
+ assert (out / "duplication.toon.yaml").exists()
243
243
 
244
244
  def test_format_all_json_valid(self, project_with_duplicates: Path, tmp_path: Path):
245
245
  out = tmp_path / "output"
@@ -256,7 +256,7 @@ class TestCLIScanAll:
256
256
  "scan", str(project_with_duplicates), "-f", "all",
257
257
  "-o", str(out), "--functions-only"
258
258
  ])
259
- toon = (out / "duplication.toon").read_text()
259
+ toon = (out / "duplication.toon.yaml").read_text()
260
260
  assert "REFACTOR" in toon
261
261
 
262
262
  def test_all_three_formats_consistent(self, project_with_duplicates: Path, tmp_path: Path):
@@ -267,7 +267,7 @@ class TestCLIScanAll:
267
267
  "-o", str(out), "--functions-only"
268
268
  ])
269
269
  json_data = json.loads((out / "duplication.json").read_text())
270
- toon_text = (out / "duplication.toon").read_text()
270
+ toon_text = (out / "duplication.toon.yaml").read_text()
271
271
  yaml_text = (out / "duplication.yaml").read_text()
272
272
 
273
273
  json_groups = json_data["summary"]["total_groups"]
@@ -423,7 +423,7 @@ class TestFullRoundtrip:
423
423
  # All files created
424
424
  assert (out / "duplication.json").exists()
425
425
  assert (out / "duplication.yaml").exists()
426
- assert (out / "duplication.toon").exists()
426
+ assert (out / "duplication.toon.yaml").exists()
427
427
 
428
428
  # JSON parseable
429
429
  json_data = json.loads((out / "duplication.json").read_text())
@@ -431,7 +431,7 @@ class TestFullRoundtrip:
431
431
  json_saved = json_data["summary"]["total_saved_lines"]
432
432
 
433
433
  # TOON mentions same numbers
434
- toon = (out / "duplication.toon").read_text()
434
+ toon = (out / "duplication.toon.yaml").read_text()
435
435
  assert f"dup_groups: {json_groups}" in toon
436
436
  assert f"saved_lines: {json_saved}" in toon
437
437
 
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