thailint 0.10.0__tar.gz → 0.11.0__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.
- {thailint-0.10.0 → thailint-0.11.0}/PKG-INFO +2 -2
- {thailint-0.10.0 → thailint-0.11.0}/README.md +1 -1
- {thailint-0.10.0 → thailint-0.11.0}/pyproject.toml +4 -3
- {thailint-0.10.0 → thailint-0.11.0}/src/__init__.py +1 -0
- thailint-0.11.0/src/cli/__init__.py +27 -0
- thailint-0.11.0/src/cli/__main__.py +22 -0
- thailint-0.11.0/src/cli/config.py +478 -0
- thailint-0.11.0/src/cli/linters/__init__.py +58 -0
- thailint-0.11.0/src/cli/linters/code_patterns.py +372 -0
- thailint-0.11.0/src/cli/linters/code_smells.py +343 -0
- thailint-0.11.0/src/cli/linters/documentation.py +155 -0
- thailint-0.11.0/src/cli/linters/shared.py +89 -0
- thailint-0.11.0/src/cli/linters/structure.py +313 -0
- thailint-0.11.0/src/cli/linters/structure_quality.py +316 -0
- thailint-0.11.0/src/cli/main.py +120 -0
- thailint-0.11.0/src/cli/utils.py +375 -0
- thailint-0.11.0/src/cli_main.py +34 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/core/types.py +13 -0
- thailint-0.11.0/src/core/violation_utils.py +69 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linter_config/ignore.py +32 -16
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/collection_pipeline/linter.py +2 -2
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/dry/block_filter.py +97 -1
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/dry/cache.py +94 -6
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/dry/config.py +47 -10
- thailint-0.11.0/src/linters/dry/constant.py +92 -0
- thailint-0.11.0/src/linters/dry/constant_matcher.py +214 -0
- thailint-0.11.0/src/linters/dry/constant_violation_builder.py +98 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/dry/linter.py +89 -48
- thailint-0.11.0/src/linters/dry/python_analyzer.py +281 -0
- thailint-0.11.0/src/linters/dry/python_constant_extractor.py +101 -0
- thailint-0.11.0/src/linters/dry/single_statement_detector.py +415 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/dry/token_hasher.py +5 -5
- thailint-0.11.0/src/linters/dry/typescript_analyzer.py +273 -0
- thailint-0.11.0/src/linters/dry/typescript_constant_extractor.py +134 -0
- thailint-0.11.0/src/linters/dry/typescript_statement_detector.py +255 -0
- thailint-0.11.0/src/linters/dry/typescript_value_extractor.py +66 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_header/linter.py +2 -2
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_placement/linter.py +2 -2
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_placement/pattern_matcher.py +19 -5
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/magic_numbers/linter.py +8 -67
- thailint-0.11.0/src/linters/magic_numbers/typescript_ignore_checker.py +81 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/nesting/linter.py +12 -9
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/print_statements/linter.py +7 -24
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/srp/class_analyzer.py +9 -9
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/srp/heuristics.py +2 -2
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/srp/linter.py +2 -2
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/stateless_class/linter.py +2 -2
- thailint-0.11.0/src/linters/stringly_typed/__init__.py +23 -0
- thailint-0.11.0/src/linters/stringly_typed/config.py +165 -0
- thailint-0.11.0/src/linters/stringly_typed/python/__init__.py +29 -0
- thailint-0.11.0/src/linters/stringly_typed/python/analyzer.py +198 -0
- thailint-0.11.0/src/linters/stringly_typed/python/condition_extractor.py +131 -0
- thailint-0.11.0/src/linters/stringly_typed/python/conditional_detector.py +176 -0
- thailint-0.11.0/src/linters/stringly_typed/python/constants.py +21 -0
- thailint-0.11.0/src/linters/stringly_typed/python/match_analyzer.py +88 -0
- thailint-0.11.0/src/linters/stringly_typed/python/validation_detector.py +186 -0
- thailint-0.11.0/src/linters/stringly_typed/python/variable_extractor.py +96 -0
- thailint-0.11.0/src/orchestrator/core.py +462 -0
- thailint-0.10.0/src/cli.py +0 -2141
- thailint-0.10.0/src/linters/dry/python_analyzer.py +0 -684
- thailint-0.10.0/src/linters/dry/typescript_analyzer.py +0 -622
- thailint-0.10.0/src/orchestrator/core.py +0 -233
- {thailint-0.10.0 → thailint-0.11.0}/CHANGELOG.md +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/LICENSE +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/analyzers/__init__.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/analyzers/typescript_base.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/api.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/config.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/core/__init__.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/core/base.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/core/cli_utils.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/core/config_parser.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/core/linter_utils.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/core/registry.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/core/rule_discovery.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/core/violation_builder.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/formatters/__init__.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/formatters/sarif.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linter_config/__init__.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linter_config/loader.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/__init__.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/collection_pipeline/__init__.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/collection_pipeline/config.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/collection_pipeline/continue_analyzer.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/collection_pipeline/detector.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/collection_pipeline/suggestion_builder.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/dry/__init__.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/dry/base_token_analyzer.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/dry/block_grouper.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/dry/cache_query.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/dry/config_loader.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/dry/deduplicator.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/dry/duplicate_storage.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/dry/file_analyzer.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/dry/inline_ignore.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/dry/storage_initializer.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/dry/violation_builder.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/dry/violation_filter.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/dry/violation_generator.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_header/__init__.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_header/atemporal_detector.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_header/base_parser.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_header/bash_parser.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_header/config.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_header/css_parser.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_header/field_validator.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_header/markdown_parser.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_header/python_parser.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_header/typescript_parser.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_header/violation_builder.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_placement/__init__.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_placement/config_loader.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_placement/directory_matcher.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_placement/path_resolver.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_placement/pattern_validator.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_placement/rule_checker.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/file_placement/violation_factory.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/magic_numbers/__init__.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/magic_numbers/config.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/magic_numbers/context_analyzer.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/magic_numbers/python_analyzer.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/magic_numbers/typescript_analyzer.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/magic_numbers/violation_builder.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/method_property/__init__.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/method_property/config.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/method_property/linter.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/method_property/python_analyzer.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/method_property/violation_builder.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/nesting/__init__.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/nesting/config.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/nesting/python_analyzer.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/nesting/typescript_analyzer.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/nesting/typescript_function_extractor.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/nesting/violation_builder.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/print_statements/__init__.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/print_statements/config.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/print_statements/python_analyzer.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/print_statements/typescript_analyzer.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/print_statements/violation_builder.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/srp/__init__.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/srp/config.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/srp/metrics_evaluator.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/srp/python_analyzer.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/srp/typescript_analyzer.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/srp/typescript_metrics_calculator.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/srp/violation_builder.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/stateless_class/__init__.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/stateless_class/config.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/linters/stateless_class/python_analyzer.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/orchestrator/__init__.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/orchestrator/language_detector.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/templates/thailint_config_template.yaml +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/utils/__init__.py +0 -0
- {thailint-0.10.0 → thailint-0.11.0}/src/utils/project_root.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thailint
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0
|
|
4
4
|
Summary: The AI Linter - Enterprise-grade linting and governance for AI-generated code across multiple languages
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -37,7 +37,7 @@ Description-Content-Type: text/markdown
|
|
|
37
37
|
|
|
38
38
|
[](https://opensource.org/licenses/MIT)
|
|
39
39
|
[](https://www.python.org/downloads/)
|
|
40
|
-
[](tests/)
|
|
41
41
|
[](htmlcov/)
|
|
42
42
|
[](https://thai-lint.readthedocs.io/en/latest/?badge=latest)
|
|
43
43
|
[](docs/sarif-output.md)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://opensource.org/licenses/MIT)
|
|
4
4
|
[](https://www.python.org/downloads/)
|
|
5
|
-
[](tests/)
|
|
6
6
|
[](htmlcov/)
|
|
7
7
|
[](https://thai-lint.readthedocs.io/en/latest/?badge=latest)
|
|
8
8
|
[](docs/sarif-output.md)
|
|
@@ -17,7 +17,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
17
17
|
|
|
18
18
|
[tool.poetry]
|
|
19
19
|
name = "thailint"
|
|
20
|
-
version = "0.
|
|
20
|
+
version = "0.11.0"
|
|
21
21
|
description = "The AI Linter - Enterprise-grade linting and governance for AI-generated code across multiple languages"
|
|
22
22
|
authors = ["Steve Jackson"]
|
|
23
23
|
license = "MIT"
|
|
@@ -104,8 +104,8 @@ loguru = "^0.7.3"
|
|
|
104
104
|
pytest-xdist = "^3.8.0"
|
|
105
105
|
|
|
106
106
|
[tool.poetry.scripts]
|
|
107
|
-
thailint = "src.
|
|
108
|
-
thai-lint = "src.
|
|
107
|
+
thailint = "src.cli_main:cli"
|
|
108
|
+
thai-lint = "src.cli_main:cli"
|
|
109
109
|
|
|
110
110
|
# Ruff configuration
|
|
111
111
|
[tool.ruff]
|
|
@@ -228,6 +228,7 @@ disable = [
|
|
|
228
228
|
|
|
229
229
|
[tool.pylint.format]
|
|
230
230
|
max-line-length = 120
|
|
231
|
+
max-module-lines = 500
|
|
231
232
|
|
|
232
233
|
# Flake8 configuration (in .flake8 file, not pyproject.toml)
|
|
233
234
|
# Note: Flake8 doesn't support pyproject.toml natively
|
|
@@ -19,6 +19,7 @@ Exports: __version__, Linter (high-level API), cli (CLI entry point), load_confi
|
|
|
19
19
|
Interfaces: Package version string, Linter class API, CLI command group, configuration functions
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
+
__version__: str
|
|
22
23
|
try:
|
|
23
24
|
from importlib.metadata import version
|
|
24
25
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: CLI package entry point and public API for thai-lint command-line interface
|
|
3
|
+
|
|
4
|
+
Scope: Re-export fully configured CLI with all commands registered
|
|
5
|
+
|
|
6
|
+
Overview: Provides the public API for the modular CLI package by re-exporting the CLI group from
|
|
7
|
+
src.cli.main and triggering command registration by importing submodules. Importing from this
|
|
8
|
+
module (src.cli) gives access to the complete CLI with all commands. Maintains backward
|
|
9
|
+
compatibility with code that imports from src.cli while enabling modular organization.
|
|
10
|
+
|
|
11
|
+
Dependencies: src.cli.main for CLI group, src.cli.config for config commands, src.cli.linters
|
|
12
|
+
for linter commands
|
|
13
|
+
|
|
14
|
+
Exports: cli (main Click command group with all commands registered)
|
|
15
|
+
|
|
16
|
+
Interfaces: Single import point for CLI access via 'from src.cli import cli'
|
|
17
|
+
|
|
18
|
+
Implementation: Imports submodules to trigger command registration via Click decorators
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# Import the CLI group from main module
|
|
22
|
+
# Import config and linters to register their commands with the CLI group
|
|
23
|
+
from src.cli import config as _config_module # noqa: F401
|
|
24
|
+
from src.cli import linters as _linters_module # noqa: F401
|
|
25
|
+
from src.cli.main import cli # noqa: F401
|
|
26
|
+
|
|
27
|
+
__all__ = ["cli"]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Entry point for running thai-lint CLI as a module (python -m src.cli)
|
|
3
|
+
|
|
4
|
+
Scope: Module execution support for direct CLI invocation
|
|
5
|
+
|
|
6
|
+
Overview: Enables running the CLI via 'python -m src.cli' by invoking the main cli group.
|
|
7
|
+
This file is executed when the package is run as a module, providing an alternative
|
|
8
|
+
entry point to the installed 'thailint' command.
|
|
9
|
+
|
|
10
|
+
Dependencies: src.cli for fully configured CLI
|
|
11
|
+
|
|
12
|
+
Exports: None (execution entry point only)
|
|
13
|
+
|
|
14
|
+
Interfaces: Command-line invocation via 'python -m src.cli [command] [args]'
|
|
15
|
+
|
|
16
|
+
Implementation: Imports and invokes cli() from the package
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from src.cli import cli
|
|
20
|
+
|
|
21
|
+
if __name__ == "__main__":
|
|
22
|
+
cli()
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Configuration management commands for thai-lint CLI
|
|
3
|
+
|
|
4
|
+
Scope: Commands for viewing, modifying, and initializing thai-lint configuration
|
|
5
|
+
|
|
6
|
+
Overview: Provides CLI commands for managing thai-lint configuration including show (display
|
|
7
|
+
configuration in text/json/yaml), get (retrieve specific value), set (modify value with
|
|
8
|
+
validation), reset (restore defaults), and init-config (generate new .thailint.yaml with
|
|
9
|
+
presets). Supports both interactive and non-interactive modes for human and AI agent
|
|
10
|
+
workflows. Integrates with the config module for loading, saving, and validation.
|
|
11
|
+
|
|
12
|
+
Dependencies: click for CLI framework, src.config for config operations, pathlib for file paths,
|
|
13
|
+
json and yaml for output formatting
|
|
14
|
+
|
|
15
|
+
Exports: config_group (Click command group), init_config command, show_config, get_config,
|
|
16
|
+
set_config, reset_config commands
|
|
17
|
+
|
|
18
|
+
Interfaces: Click commands registered to main CLI group, config presets (strict/standard/lenient)
|
|
19
|
+
|
|
20
|
+
Implementation: Uses Click decorators for command definition, supports multiple output formats,
|
|
21
|
+
validates configuration changes before saving, uses template file for init-config generation
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import logging
|
|
25
|
+
import sys
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
import click
|
|
29
|
+
|
|
30
|
+
from src.config import ConfigError, save_config, validate_config
|
|
31
|
+
|
|
32
|
+
from .main import cli
|
|
33
|
+
|
|
34
|
+
# Configure module logger
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# =============================================================================
|
|
39
|
+
# Config Command Group
|
|
40
|
+
# =============================================================================
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@cli.group()
|
|
44
|
+
def config() -> None:
|
|
45
|
+
"""Configuration management commands."""
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# =============================================================================
|
|
50
|
+
# Config Show Command
|
|
51
|
+
# =============================================================================
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@config.command("show")
|
|
55
|
+
@click.option(
|
|
56
|
+
"--format",
|
|
57
|
+
"-f",
|
|
58
|
+
type=click.Choice(["text", "json", "yaml"]),
|
|
59
|
+
default="text",
|
|
60
|
+
help="Output format",
|
|
61
|
+
)
|
|
62
|
+
@click.pass_context
|
|
63
|
+
def config_show(ctx: click.Context, format: str) -> None:
|
|
64
|
+
"""Display current configuration.
|
|
65
|
+
|
|
66
|
+
Shows all configuration values in the specified format.
|
|
67
|
+
|
|
68
|
+
Examples:
|
|
69
|
+
|
|
70
|
+
\b
|
|
71
|
+
# Show as text
|
|
72
|
+
thai-lint config show
|
|
73
|
+
|
|
74
|
+
\b
|
|
75
|
+
# Show as JSON
|
|
76
|
+
thai-lint config show --format json
|
|
77
|
+
|
|
78
|
+
\b
|
|
79
|
+
# Show as YAML
|
|
80
|
+
thai-lint config show --format yaml
|
|
81
|
+
"""
|
|
82
|
+
cfg = ctx.obj["config"]
|
|
83
|
+
|
|
84
|
+
formatters = {
|
|
85
|
+
"json": _format_config_json,
|
|
86
|
+
"yaml": _format_config_yaml,
|
|
87
|
+
"text": _format_config_text,
|
|
88
|
+
}
|
|
89
|
+
formatters[format](cfg)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _format_config_json(cfg: dict) -> None:
|
|
93
|
+
"""Format configuration as JSON."""
|
|
94
|
+
import json
|
|
95
|
+
|
|
96
|
+
click.echo(json.dumps(cfg, indent=2))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _format_config_yaml(cfg: dict) -> None:
|
|
100
|
+
"""Format configuration as YAML."""
|
|
101
|
+
import yaml
|
|
102
|
+
|
|
103
|
+
click.echo(yaml.dump(cfg, default_flow_style=False, sort_keys=False))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _format_config_text(cfg: dict) -> None:
|
|
107
|
+
"""Format configuration as text."""
|
|
108
|
+
click.echo("Current Configuration:")
|
|
109
|
+
click.echo("-" * 40)
|
|
110
|
+
for key, value in cfg.items():
|
|
111
|
+
click.echo(f"{key:20} : {value}")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# =============================================================================
|
|
115
|
+
# Config Get Command
|
|
116
|
+
# =============================================================================
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@config.command("get")
|
|
120
|
+
@click.argument("key")
|
|
121
|
+
@click.pass_context
|
|
122
|
+
def config_get(ctx: click.Context, key: str) -> None:
|
|
123
|
+
"""Get specific configuration value.
|
|
124
|
+
|
|
125
|
+
KEY: Configuration key to retrieve
|
|
126
|
+
|
|
127
|
+
Examples:
|
|
128
|
+
|
|
129
|
+
\b
|
|
130
|
+
# Get log level
|
|
131
|
+
thai-lint config get log_level
|
|
132
|
+
|
|
133
|
+
\b
|
|
134
|
+
# Get greeting template
|
|
135
|
+
thai-lint config get greeting
|
|
136
|
+
"""
|
|
137
|
+
cfg = ctx.obj["config"]
|
|
138
|
+
|
|
139
|
+
if key not in cfg:
|
|
140
|
+
click.echo(f"Configuration key not found: {key}", err=True)
|
|
141
|
+
sys.exit(1)
|
|
142
|
+
|
|
143
|
+
click.echo(cfg[key])
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# =============================================================================
|
|
147
|
+
# Config Set Command
|
|
148
|
+
# =============================================================================
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _convert_value_type(value: str) -> bool | int | float | str:
|
|
152
|
+
"""Convert string value to appropriate type."""
|
|
153
|
+
if value.lower() in ["true", "false"]:
|
|
154
|
+
return value.lower() == "true"
|
|
155
|
+
if value.isdigit():
|
|
156
|
+
return int(value)
|
|
157
|
+
if value.replace(".", "", 1).isdigit() and value.count(".") == 1:
|
|
158
|
+
return float(value)
|
|
159
|
+
return value
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _validate_and_report_errors(cfg: dict) -> None:
|
|
163
|
+
"""Validate configuration and report errors."""
|
|
164
|
+
is_valid, errors = validate_config(cfg)
|
|
165
|
+
if not is_valid:
|
|
166
|
+
click.echo("Invalid configuration:", err=True)
|
|
167
|
+
for error in errors:
|
|
168
|
+
click.echo(f" - {error}", err=True)
|
|
169
|
+
sys.exit(1)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _save_and_report_success(
|
|
173
|
+
cfg: dict, key: str, value: bool | int | float | str, config_path: Path | None, verbose: bool
|
|
174
|
+
) -> None:
|
|
175
|
+
"""Save configuration and report success."""
|
|
176
|
+
save_config(cfg, config_path)
|
|
177
|
+
click.echo(f"Set {key} = {value}")
|
|
178
|
+
if verbose:
|
|
179
|
+
logger.info(f"Configuration updated: {key}={value}")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@config.command("set")
|
|
183
|
+
@click.argument("key")
|
|
184
|
+
@click.argument("value")
|
|
185
|
+
@click.pass_context
|
|
186
|
+
def config_set(ctx: click.Context, key: str, value: str) -> None:
|
|
187
|
+
"""Set configuration value.
|
|
188
|
+
|
|
189
|
+
KEY: Configuration key to set
|
|
190
|
+
|
|
191
|
+
VALUE: New value for the key
|
|
192
|
+
|
|
193
|
+
Examples:
|
|
194
|
+
|
|
195
|
+
\b
|
|
196
|
+
# Set log level
|
|
197
|
+
thai-lint config set log_level DEBUG
|
|
198
|
+
|
|
199
|
+
\b
|
|
200
|
+
# Set greeting template
|
|
201
|
+
thai-lint config set greeting "Hi"
|
|
202
|
+
|
|
203
|
+
\b
|
|
204
|
+
# Set numeric value
|
|
205
|
+
thai-lint config set max_retries 5
|
|
206
|
+
"""
|
|
207
|
+
cfg = ctx.obj["config"]
|
|
208
|
+
converted_value = _convert_value_type(value)
|
|
209
|
+
cfg[key] = converted_value
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
_validate_and_report_errors(cfg)
|
|
213
|
+
except Exception as e:
|
|
214
|
+
click.echo(f"Validation error: {e}", err=True)
|
|
215
|
+
sys.exit(1)
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
config_path = ctx.obj.get("config_path")
|
|
219
|
+
verbose = ctx.obj.get("verbose", False)
|
|
220
|
+
_save_and_report_success(cfg, key, converted_value, config_path, verbose)
|
|
221
|
+
except ConfigError as e:
|
|
222
|
+
click.echo(f"Error saving configuration: {e}", err=True)
|
|
223
|
+
sys.exit(1)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# =============================================================================
|
|
227
|
+
# Config Reset Command
|
|
228
|
+
# =============================================================================
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@config.command("reset")
|
|
232
|
+
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
|
|
233
|
+
@click.pass_context
|
|
234
|
+
def config_reset(ctx: click.Context, yes: bool) -> None:
|
|
235
|
+
"""Reset configuration to defaults.
|
|
236
|
+
|
|
237
|
+
Examples:
|
|
238
|
+
|
|
239
|
+
\b
|
|
240
|
+
# Reset with confirmation
|
|
241
|
+
thai-lint config reset
|
|
242
|
+
|
|
243
|
+
\b
|
|
244
|
+
# Reset without confirmation
|
|
245
|
+
thai-lint config reset --yes
|
|
246
|
+
"""
|
|
247
|
+
if not yes:
|
|
248
|
+
click.confirm("Reset configuration to defaults?", abort=True)
|
|
249
|
+
|
|
250
|
+
from src.config import DEFAULT_CONFIG
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
config_path = ctx.obj.get("config_path")
|
|
254
|
+
save_config(DEFAULT_CONFIG.copy(), config_path)
|
|
255
|
+
click.echo("Configuration reset to defaults")
|
|
256
|
+
|
|
257
|
+
if ctx.obj.get("verbose"):
|
|
258
|
+
logger.info("Configuration reset to defaults")
|
|
259
|
+
except ConfigError as e:
|
|
260
|
+
click.echo(f"Error resetting configuration: {e}", err=True)
|
|
261
|
+
sys.exit(1)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
# =============================================================================
|
|
265
|
+
# Init Config Command
|
|
266
|
+
# =============================================================================
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@cli.command("init-config")
|
|
270
|
+
@click.option(
|
|
271
|
+
"--preset",
|
|
272
|
+
"-p",
|
|
273
|
+
type=click.Choice(["strict", "standard", "lenient"]),
|
|
274
|
+
default="standard",
|
|
275
|
+
help="Configuration preset",
|
|
276
|
+
)
|
|
277
|
+
@click.option("--non-interactive", is_flag=True, help="Skip interactive prompts (for AI agents)")
|
|
278
|
+
@click.option("--force", is_flag=True, help="Overwrite existing .thailint.yaml file")
|
|
279
|
+
@click.option(
|
|
280
|
+
"--output", "-o", type=click.Path(), default=".thailint.yaml", help="Output file path"
|
|
281
|
+
)
|
|
282
|
+
def init_config(preset: str, non_interactive: bool, force: bool, output: str) -> None:
|
|
283
|
+
"""Generate a .thailint.yaml configuration file with preset values.
|
|
284
|
+
|
|
285
|
+
Creates a richly-commented configuration file with sensible defaults
|
|
286
|
+
and optional customizations for different strictness levels.
|
|
287
|
+
|
|
288
|
+
For AI agents, use --non-interactive mode:
|
|
289
|
+
thailint init-config --non-interactive --preset lenient
|
|
290
|
+
|
|
291
|
+
Presets:
|
|
292
|
+
strict: Minimal allowed numbers (only -1, 0, 1)
|
|
293
|
+
standard: Balanced defaults (includes 2, 3, 4, 5, 10, 100, 1000)
|
|
294
|
+
lenient: Includes time conversions (adds 60, 3600)
|
|
295
|
+
|
|
296
|
+
Examples:
|
|
297
|
+
|
|
298
|
+
\\b
|
|
299
|
+
# Interactive mode (default, for humans)
|
|
300
|
+
thailint init-config
|
|
301
|
+
|
|
302
|
+
\\b
|
|
303
|
+
# Non-interactive mode (for AI agents)
|
|
304
|
+
thailint init-config --non-interactive
|
|
305
|
+
|
|
306
|
+
\\b
|
|
307
|
+
# Generate with lenient preset
|
|
308
|
+
thailint init-config --preset lenient
|
|
309
|
+
|
|
310
|
+
\\b
|
|
311
|
+
# Overwrite existing config
|
|
312
|
+
thailint init-config --force
|
|
313
|
+
|
|
314
|
+
\\b
|
|
315
|
+
# Custom output path
|
|
316
|
+
thailint init-config --output my-config.yaml
|
|
317
|
+
"""
|
|
318
|
+
output_path = Path(output)
|
|
319
|
+
|
|
320
|
+
# Check if file exists (unless --force)
|
|
321
|
+
if output_path.exists() and not force:
|
|
322
|
+
click.echo(f"Error: {output} already exists", err=True)
|
|
323
|
+
click.echo("", err=True)
|
|
324
|
+
click.echo("Use --force to overwrite:", err=True)
|
|
325
|
+
click.echo(" thailint init-config --force", err=True)
|
|
326
|
+
sys.exit(1)
|
|
327
|
+
|
|
328
|
+
# Interactive mode: Ask user for preferences
|
|
329
|
+
if not non_interactive:
|
|
330
|
+
preset = _run_interactive_preset_selection(preset)
|
|
331
|
+
|
|
332
|
+
# Generate config based on preset
|
|
333
|
+
config_content = _generate_config_content(preset)
|
|
334
|
+
|
|
335
|
+
# Write config file
|
|
336
|
+
_write_config_file(output_path, config_content, preset, output)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _run_interactive_preset_selection(default_preset: str) -> str:
|
|
340
|
+
"""Run interactive preset selection.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
default_preset: Default preset to use if user accepts default
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Selected preset name
|
|
347
|
+
"""
|
|
348
|
+
click.echo("thai-lint Configuration Generator")
|
|
349
|
+
click.echo("=" * 50)
|
|
350
|
+
click.echo("")
|
|
351
|
+
click.echo("This will create a .thailint.yaml configuration file.")
|
|
352
|
+
click.echo("For non-interactive mode (AI agents), use:")
|
|
353
|
+
click.echo(" thailint init-config --non-interactive")
|
|
354
|
+
click.echo("")
|
|
355
|
+
|
|
356
|
+
# Show preset options
|
|
357
|
+
click.echo("Available presets:")
|
|
358
|
+
click.echo(" strict: Only -1, 0, 1 allowed (strictest)")
|
|
359
|
+
click.echo(" standard: -1, 0, 1, 2, 3, 4, 5, 10, 100, 1000 (balanced)")
|
|
360
|
+
click.echo(" lenient: Includes time conversions 60, 3600 (most permissive)")
|
|
361
|
+
click.echo("")
|
|
362
|
+
|
|
363
|
+
preset_choices = click.Choice(["strict", "standard", "lenient"])
|
|
364
|
+
result: str = click.prompt("Choose preset", type=preset_choices, default=default_preset)
|
|
365
|
+
return result
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _generate_config_content(preset: str) -> str:
|
|
369
|
+
"""Generate config file content based on preset.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
preset: Preset name (strict, standard, or lenient)
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
Generated configuration file content
|
|
376
|
+
"""
|
|
377
|
+
# Preset configurations
|
|
378
|
+
presets = {
|
|
379
|
+
"strict": {
|
|
380
|
+
"allowed_numbers": "[-1, 0, 1]",
|
|
381
|
+
"max_small_integer": "3",
|
|
382
|
+
"description": "Strict (only universal values)",
|
|
383
|
+
},
|
|
384
|
+
"standard": {
|
|
385
|
+
"allowed_numbers": "[-1, 0, 1, 2, 3, 4, 5, 10, 100, 1000]",
|
|
386
|
+
"max_small_integer": "10",
|
|
387
|
+
"description": "Standard (balanced defaults)",
|
|
388
|
+
},
|
|
389
|
+
"lenient": {
|
|
390
|
+
"allowed_numbers": "[-1, 0, 1, 2, 3, 4, 5, 10, 60, 100, 1000, 3600]",
|
|
391
|
+
"max_small_integer": "10",
|
|
392
|
+
"description": "Lenient (includes time conversions)",
|
|
393
|
+
},
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
config = presets[preset]
|
|
397
|
+
|
|
398
|
+
# Read template - use parent of parent since we're in src/cli/
|
|
399
|
+
template_path = Path(__file__).parent.parent / "templates" / "thailint_config_template.yaml"
|
|
400
|
+
template = template_path.read_text(encoding="utf-8")
|
|
401
|
+
|
|
402
|
+
# Replace placeholders
|
|
403
|
+
content = template.replace("{{PRESET}}", config["description"])
|
|
404
|
+
content = content.replace("{{ALLOWED_NUMBERS}}", config["allowed_numbers"])
|
|
405
|
+
content = content.replace("{{MAX_SMALL_INTEGER}}", config["max_small_integer"])
|
|
406
|
+
|
|
407
|
+
return content
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _write_config_file(output_path: Path, content: str, preset: str, output: str) -> None:
|
|
411
|
+
"""Write configuration file and show success message.
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
output_path: Path to write file to
|
|
415
|
+
content: File content to write
|
|
416
|
+
preset: Selected preset name
|
|
417
|
+
output: Output filename for display
|
|
418
|
+
"""
|
|
419
|
+
try:
|
|
420
|
+
output_path.write_text(content, encoding="utf-8")
|
|
421
|
+
click.echo("")
|
|
422
|
+
click.echo(f"Created {output}")
|
|
423
|
+
click.echo(f"Preset: {preset}")
|
|
424
|
+
click.echo("")
|
|
425
|
+
click.echo("Next steps:")
|
|
426
|
+
click.echo(f" 1. Review and customize {output}")
|
|
427
|
+
click.echo(" 2. Run: thailint magic-numbers .")
|
|
428
|
+
click.echo(" 3. See docs: https://github.com/your-org/thai-lint")
|
|
429
|
+
except OSError as e:
|
|
430
|
+
click.echo(f"Error writing config file: {e}", err=True)
|
|
431
|
+
sys.exit(1)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
# =============================================================================
|
|
435
|
+
# Hello Command (Example Command)
|
|
436
|
+
# =============================================================================
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
@cli.command()
|
|
440
|
+
@click.option("--name", "-n", default="World", help="Name to greet")
|
|
441
|
+
@click.option("--uppercase", "-u", is_flag=True, help="Convert greeting to uppercase")
|
|
442
|
+
@click.pass_context
|
|
443
|
+
def hello(ctx: click.Context, name: str, uppercase: bool) -> None:
|
|
444
|
+
"""Print a greeting message.
|
|
445
|
+
|
|
446
|
+
This is a simple example command demonstrating CLI basics.
|
|
447
|
+
|
|
448
|
+
Examples:
|
|
449
|
+
|
|
450
|
+
\b
|
|
451
|
+
# Basic greeting
|
|
452
|
+
thai-lint hello
|
|
453
|
+
|
|
454
|
+
\b
|
|
455
|
+
# Custom name
|
|
456
|
+
thai-lint hello --name Alice
|
|
457
|
+
|
|
458
|
+
\b
|
|
459
|
+
# Uppercase output
|
|
460
|
+
thai-lint hello --name Bob --uppercase
|
|
461
|
+
"""
|
|
462
|
+
config = ctx.obj["config"]
|
|
463
|
+
verbose = ctx.obj.get("verbose", False)
|
|
464
|
+
|
|
465
|
+
# Get greeting from config or use default
|
|
466
|
+
greeting_template = config.get("greeting", "Hello")
|
|
467
|
+
|
|
468
|
+
# Build greeting message
|
|
469
|
+
message = f"{greeting_template}, {name}!"
|
|
470
|
+
|
|
471
|
+
if uppercase:
|
|
472
|
+
message = message.upper()
|
|
473
|
+
|
|
474
|
+
# Output greeting
|
|
475
|
+
click.echo(message)
|
|
476
|
+
|
|
477
|
+
if verbose:
|
|
478
|
+
logger.info(f"Greeted {name} with template '{greeting_template}'")
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: CLI linters package that registers all linter commands to the main CLI group
|
|
3
|
+
|
|
4
|
+
Scope: Export and registration of all linter CLI commands (nesting, srp, dry, magic-numbers, etc.)
|
|
5
|
+
|
|
6
|
+
Overview: Package initialization that imports all linter command modules to trigger their registration
|
|
7
|
+
with the main CLI group via Click decorators. Each submodule defines commands using @cli.command()
|
|
8
|
+
decorators that automatically register with the CLI when imported. Organized by logical grouping:
|
|
9
|
+
structure_quality (nesting, srp), code_smells (dry, magic-numbers), code_patterns (print-statements,
|
|
10
|
+
method-property, stateless-class), structure (file-placement, pipeline), documentation (file-header).
|
|
11
|
+
|
|
12
|
+
Dependencies: Click for CLI framework, src.cli.main for CLI group, individual linter modules
|
|
13
|
+
|
|
14
|
+
Exports: All linter command functions for reference and testing
|
|
15
|
+
|
|
16
|
+
Interfaces: Click command decorators, integration with main CLI group
|
|
17
|
+
|
|
18
|
+
Implementation: Module imports trigger command registration via Click decorator side effects
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# Import all linter command modules to register them with the CLI
|
|
22
|
+
# Each module uses @cli.command() decorators that register on import
|
|
23
|
+
from src.cli.linters import ( # noqa: F401
|
|
24
|
+
code_patterns,
|
|
25
|
+
code_smells,
|
|
26
|
+
documentation,
|
|
27
|
+
structure,
|
|
28
|
+
structure_quality,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Re-export command functions for testing and reference
|
|
32
|
+
from src.cli.linters.code_patterns import (
|
|
33
|
+
method_property,
|
|
34
|
+
print_statements,
|
|
35
|
+
stateless_class,
|
|
36
|
+
)
|
|
37
|
+
from src.cli.linters.code_smells import dry, magic_numbers
|
|
38
|
+
from src.cli.linters.documentation import file_header
|
|
39
|
+
from src.cli.linters.structure import file_placement, pipeline
|
|
40
|
+
from src.cli.linters.structure_quality import nesting, srp
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
# Structure quality commands
|
|
44
|
+
"nesting",
|
|
45
|
+
"srp",
|
|
46
|
+
# Code smell commands
|
|
47
|
+
"dry",
|
|
48
|
+
"magic_numbers",
|
|
49
|
+
# Code pattern commands
|
|
50
|
+
"print_statements",
|
|
51
|
+
"method_property",
|
|
52
|
+
"stateless_class",
|
|
53
|
+
# Structure commands
|
|
54
|
+
"file_placement",
|
|
55
|
+
"pipeline",
|
|
56
|
+
# Documentation commands
|
|
57
|
+
"file_header",
|
|
58
|
+
]
|