precis-cli 0.1.3__py3-none-any.whl
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.
- app/__init__.py +16 -0
- app/api/__init__.py +32 -0
- app/api/dependencies.py +185 -0
- app/api/main.py +301 -0
- app/api/middleware/__init__.py +34 -0
- app/api/middleware/exception_handler.py +126 -0
- app/api/middleware/request_logging.py +97 -0
- app/api/middleware/token_auth.py +215 -0
- app/api/models/__init__.py +151 -0
- app/api/models/connection_rules.py +98 -0
- app/api/models/files.py +99 -0
- app/api/models/full_validation.py +554 -0
- app/api/models/project.py +117 -0
- app/api/models/projects.py +80 -0
- app/api/models/schema.py +121 -0
- app/api/models/v2_responses.py +46 -0
- app/api/models/validation.py +303 -0
- app/api/models/workspace.py +118 -0
- app/api/routers/__init__.py +56 -0
- app/api/routers/ai/__init__.py +36 -0
- app/api/routers/ai/chat.py +243 -0
- app/api/routers/ai/generate.py +156 -0
- app/api/routers/ai/hardware.py +199 -0
- app/api/routers/ai/jobs.py +544 -0
- app/api/routers/ai/migrate.py +471 -0
- app/api/routers/ai/models.py +352 -0
- app/api/routers/ai/ollama.py +108 -0
- app/api/routers/ai/providers.py +491 -0
- app/api/routers/ai/router.py +44 -0
- app/api/routers/ai/stream.py +281 -0
- app/api/routers/ai/utils.py +182 -0
- app/api/routers/core/__init__.py +34 -0
- app/api/routers/core/connection_rules.py +188 -0
- app/api/routers/core/data_sources.py +374 -0
- app/api/routers/core/regex.py +333 -0
- app/api/routers/core/reporting.py +263 -0
- app/api/routers/files/__init__.py +24 -0
- app/api/routers/files/ops.py +179 -0
- app/api/routers/files/transfer.py +101 -0
- app/api/routers/preview/__init__.py +35 -0
- app/api/routers/preview/content_mode.py +264 -0
- app/api/routers/preview/header_row.py +154 -0
- app/api/routers/preview/models.py +81 -0
- app/api/routers/preview/path_mode.py +440 -0
- app/api/routers/preview/router.py +30 -0
- app/api/routers/project/__init__.py +61 -0
- app/api/routers/project/base.py +111 -0
- app/api/routers/project/constraint.py +345 -0
- app/api/routers/project/full_config.py +411 -0
- app/api/routers/project/full_config_writer.py +327 -0
- app/api/routers/project/helpers.py +167 -0
- app/api/routers/project/inspection_fix.py +330 -0
- app/api/routers/project/manifest.py +605 -0
- app/api/routers/project/models.py +177 -0
- app/api/routers/project/pattern.py +268 -0
- app/api/routers/project/regex.py +358 -0
- app/api/routers/project/scanner.py +157 -0
- app/api/routers/project/schema.py +567 -0
- app/api/routers/project/schema_helpers.py +149 -0
- app/api/routers/project/settings.py +399 -0
- app/api/routers/project/template.py +325 -0
- app/api/routers/project/validation.py +227 -0
- app/api/routers/project/view.py +177 -0
- app/api/routers/project/workspaces.py +167 -0
- app/api/routers/projects/__init__.py +25 -0
- app/api/routers/projects/create.py +121 -0
- app/api/routers/projects/open.py +103 -0
- app/api/routers/projects/scan.py +100 -0
- app/api/routers/validation/__init__.py +39 -0
- app/api/routers/validation/common.py +263 -0
- app/api/routers/validation/content_mode.py +401 -0
- app/api/routers/validation/history.py +235 -0
- app/api/routers/validation/inline_mode.py +196 -0
- app/api/routers/validation/path_mode.py +314 -0
- app/api/routers/validation/router.py +51 -0
- app/api/services/full_validation_response_builder.py +412 -0
- app/api/services/io_error_messages.py +41 -0
- app/api/services/preview_service.py +248 -0
- app/cli/__init__.py +18 -0
- app/cli/__main__.py +30 -0
- app/cli/shared_services/__init__.py +42 -0
- app/cli/shared_services/config_ops.py +581 -0
- app/cli/shared_services/generation_ops.py +160 -0
- app/cli/shared_services/project_ops.py +288 -0
- app/cli/shell/__init__.py +48 -0
- app/cli/shell/commands/__init__.py +63 -0
- app/cli/shell/commands/ai/__init__.py +260 -0
- app/cli/shell/commands/ai/chat.py +331 -0
- app/cli/shell/commands/ai/delete.py +165 -0
- app/cli/shell/commands/ai/diff.py +70 -0
- app/cli/shell/commands/ai/display.py +428 -0
- app/cli/shell/commands/ai/executor.py +225 -0
- app/cli/shell/commands/ai/executor_utils.py +271 -0
- app/cli/shell/commands/ai/generate.py +285 -0
- app/cli/shell/commands/ai/interaction.py +262 -0
- app/cli/shell/commands/ai/migrate.py +281 -0
- app/cli/shell/commands/ai/resolver.py +224 -0
- app/cli/shell/commands/ai/status.py +101 -0
- app/cli/shell/commands/ai/switch.py +158 -0
- app/cli/shell/commands/ai/utils.py +52 -0
- app/cli/shell/commands/base.py +343 -0
- app/cli/shell/commands/config/__init__.py +131 -0
- app/cli/shell/commands/config/base.py +80 -0
- app/cli/shell/commands/config/check.py +246 -0
- app/cli/shell/commands/config/edit.py +148 -0
- app/cli/shell/commands/config/get.py +111 -0
- app/cli/shell/commands/config/init.py +123 -0
- app/cli/shell/commands/config/inspect.py +164 -0
- app/cli/shell/commands/config/list.py +104 -0
- app/cli/shell/commands/config/set.py +119 -0
- app/cli/shell/commands/config/show.py +150 -0
- app/cli/shell/commands/exit.py +67 -0
- app/cli/shell/commands/help.py +104 -0
- app/cli/shell/commands/infer_schema.py +156 -0
- app/cli/shell/commands/open.py +201 -0
- app/cli/shell/commands/project.py +215 -0
- app/cli/shell/commands/provider.py +619 -0
- app/cli/shell/commands/system.py +170 -0
- app/cli/shell/commands/validate.py +455 -0
- app/cli/shell/completer.py +231 -0
- app/cli/shell/config_storage.py +191 -0
- app/cli/shell/exceptions.py +90 -0
- app/cli/shell/formatter.py +371 -0
- app/cli/shell/interactive_menu.py +348 -0
- app/cli/shell/main.py +286 -0
- app/cli/shell/parser.py +266 -0
- app/cli/start.py +180 -0
- app/cli_main.py +52 -0
- app/mcp_server.py +311 -0
- app/shared/core/__init__.py +18 -0
- app/shared/core/app_version.py +59 -0
- app/shared/core/config/__init__.py +119 -0
- app/shared/core/config/server.py +193 -0
- app/shared/core/data_source/__init__.py +106 -0
- app/shared/core/data_source/loader.py +348 -0
- app/shared/core/data_source/loaders/__init__.py +213 -0
- app/shared/core/data_source/loaders/base.py +215 -0
- app/shared/core/data_source/loaders/converter.py +337 -0
- app/shared/core/data_source/loaders/csv_loader.py +252 -0
- app/shared/core/data_source/loaders/excel_loader.py +666 -0
- app/shared/core/data_source/loaders/extractor.py +268 -0
- app/shared/core/data_source/loaders/json_loader.py +354 -0
- app/shared/core/data_source/loaders/registry.py +88 -0
- app/shared/core/data_source/loaders/sql_loader.py +313 -0
- app/shared/core/data_source/loaders/strategies/__init__.py +100 -0
- app/shared/core/data_source/loaders/strategies/array_parser.py +277 -0
- app/shared/core/data_source/loaders/strategies/lines_parser.py +355 -0
- app/shared/core/data_source/loaders/strategies/object_parser.py +278 -0
- app/shared/core/data_source/schema_info.py +50 -0
- app/shared/core/data_source/specs/__init__.py +128 -0
- app/shared/core/data_source/specs/base.py +258 -0
- app/shared/core/data_source/specs/csv_source.py +137 -0
- app/shared/core/data_source/specs/excel_source.py +141 -0
- app/shared/core/data_source/specs/file_base.py +252 -0
- app/shared/core/data_source/specs/json_source.py +226 -0
- app/shared/core/data_source/specs/sql_source.py +161 -0
- app/shared/core/encoding.py +36 -0
- app/shared/core/io/__init__.py +24 -0
- app/shared/core/io/yaml.py +379 -0
- app/shared/core/manifest_schema/__init__.py +52 -0
- app/shared/core/manifest_schema/types.py +99 -0
- app/shared/core/manifest_schema/version.py +175 -0
- app/shared/core/patterns/__init__.py +24 -0
- app/shared/core/patterns/loader.py +134 -0
- app/shared/core/patterns/writer.py +244 -0
- app/shared/core/project/__init__.py +47 -0
- app/shared/core/project/constraint/builders/__init__.py +50 -0
- app/shared/core/project/constraint/builders/base.py +100 -0
- app/shared/core/project/constraint/builders/composite.py +77 -0
- app/shared/core/project/constraint/builders/conditional.py +67 -0
- app/shared/core/project/constraint/builders/foreign_key.py +53 -0
- app/shared/core/project/constraint/builders/registry.py +64 -0
- app/shared/core/project/constraint/builders/scripted.py +51 -0
- app/shared/core/project/constraint/builders/single_column.py +86 -0
- app/shared/core/project/constraint/builders/unique.py +53 -0
- app/shared/core/project/constraint/factory.py +170 -0
- app/shared/core/project/constraint/reader.py +214 -0
- app/shared/core/project/constraint/registry.py +233 -0
- app/shared/core/project/constraint/types/__init__.py +63 -0
- app/shared/core/project/constraint/types/constraint_file.py +261 -0
- app/shared/core/project/constraint/types/refs.py +460 -0
- app/shared/core/project/constraint/types.py +28 -0
- app/shared/core/project/constraint/writer.py +181 -0
- app/shared/core/project/loader/__init__.py +56 -0
- app/shared/core/project/loader/loader.py +30 -0
- app/shared/core/project/loader/loader_parts/config_inspector.py +137 -0
- app/shared/core/project/loader/loader_parts/embedded_constraints.py +224 -0
- app/shared/core/project/loader/loader_parts/file_loaders.py +58 -0
- app/shared/core/project/loader/loader_parts/inspection_ids.py +85 -0
- app/shared/core/project/loader/loader_parts/inspector_helpers.py +312 -0
- app/shared/core/project/loader/loader_parts/inspector_id_checks.py +274 -0
- app/shared/core/project/loader/loader_parts/inspector_reference_checks.py +690 -0
- app/shared/core/project/loader/loader_parts/inspector_uniqueness_checks.py +286 -0
- app/shared/core/project/loader/loader_parts/loading_error_messages.py +298 -0
- app/shared/core/project/loader/loader_parts/main.py +432 -0
- app/shared/core/project/loader/loader_parts/path_validation.py +117 -0
- app/shared/core/project/loader/loader_parts/runtime.py +125 -0
- app/shared/core/project/loader/types.py +246 -0
- app/shared/core/project/manifest/coverage.py +391 -0
- app/shared/core/project/manifest/reader.py +260 -0
- app/shared/core/project/manifest/types.py +91 -0
- app/shared/core/project/manifest/types_parts/__init__.py +60 -0
- app/shared/core/project/manifest/types_parts/constants.py +40 -0
- app/shared/core/project/manifest/types_parts/data_source.py +68 -0
- app/shared/core/project/manifest/types_parts/info.py +59 -0
- app/shared/core/project/manifest/types_parts/manifest.py +294 -0
- app/shared/core/project/manifest/types_parts/refs.py +153 -0
- app/shared/core/project/manifest/types_parts/settings.py +92 -0
- app/shared/core/project/manifest/types_parts/settings_file_processing.py +64 -0
- app/shared/core/project/manifest/types_parts/settings_script_security.py +78 -0
- app/shared/core/project/manifest/types_parts/settings_validation.py +83 -0
- app/shared/core/project/manifest/types_parts/template.py +66 -0
- app/shared/core/project/manifest/writer.py +262 -0
- app/shared/core/project/manual_data/__init__.py +27 -0
- app/shared/core/project/manual_data/types.py +75 -0
- app/shared/core/project/regex/reader.py +197 -0
- app/shared/core/project/regex/types.py +405 -0
- app/shared/core/project/regex/writer.py +123 -0
- app/shared/core/project/schema/reader.py +170 -0
- app/shared/core/project/schema/types.py +47 -0
- app/shared/core/project/schema/types_parts/__init__.py +36 -0
- app/shared/core/project/schema/types_parts/column.py +174 -0
- app/shared/core/project/schema/types_parts/column_utils.py +72 -0
- app/shared/core/project/schema/types_parts/constraint.py +165 -0
- app/shared/core/project/schema/types_parts/schema_id.py +66 -0
- app/shared/core/project/schema/types_parts/source.py +255 -0
- app/shared/core/project/schema/types_parts/source_options.py +347 -0
- app/shared/core/project/schema/types_parts/table.py +230 -0
- app/shared/core/project/schema/writer.py +139 -0
- app/shared/core/project/schema_ref_check.py +95 -0
- app/shared/core/project/template/__init__.py +27 -0
- app/shared/core/project/template/expander.py +263 -0
- app/shared/core/project/template/reader.py +120 -0
- app/shared/core/project/template/types.py +114 -0
- app/shared/core/project/transform/reader.py +76 -0
- app/shared/core/project/transform/types.py +116 -0
- app/shared/core/project/transform/writer.py +84 -0
- app/shared/core/pydantic_messages.py +59 -0
- app/shared/core/reporter/__init__.py +46 -0
- app/shared/core/reporter/reporter.py +220 -0
- app/shared/core/reporter/reporters/__init__.py +65 -0
- app/shared/core/reporter/reporters/base.py +188 -0
- app/shared/core/reporter/reporters/dingtalk_app_reporter.py +274 -0
- app/shared/core/reporter/reporters/email_reporter.py +271 -0
- app/shared/core/reporter/reporters/feishu_app_reporter.py +467 -0
- app/shared/core/reporter/reporters/local_file_reporter.py +208 -0
- app/shared/core/reporter/reporters/wecom_app_reporter.py +268 -0
- app/shared/core/utils/__init__.py +18 -0
- app/shared/core/utils/path_utils.py +60 -0
- app/shared/core/utils/regex_extract.py +113 -0
- app/shared/domain/__init__.py +114 -0
- app/shared/domain/constraints/__init__.py +76 -0
- app/shared/domain/constraints/allowed_values.py +211 -0
- app/shared/domain/constraints/base.py +141 -0
- app/shared/domain/constraints/charset.py +337 -0
- app/shared/domain/constraints/composite.py +174 -0
- app/shared/domain/constraints/condition_registry.py +130 -0
- app/shared/domain/constraints/conditional.py +629 -0
- app/shared/domain/constraints/date_logic.py +731 -0
- app/shared/domain/constraints/foreign_key.py +261 -0
- app/shared/domain/constraints/key_normalization.py +56 -0
- app/shared/domain/constraints/not_null.py +185 -0
- app/shared/domain/constraints/range.py +360 -0
- app/shared/domain/constraints/regex.py +218 -0
- app/shared/domain/constraints/scripted.py +276 -0
- app/shared/domain/constraints/unique.py +233 -0
- app/shared/domain/data_engine.py +384 -0
- app/shared/domain/data_types.py +113 -0
- app/shared/domain/data_types_parts/__init__.py +67 -0
- app/shared/domain/data_types_parts/base.py +204 -0
- app/shared/domain/data_types_parts/composite.py +250 -0
- app/shared/domain/data_types_parts/expression.py +202 -0
- app/shared/domain/data_types_parts/extracted.py +106 -0
- app/shared/domain/data_types_parts/json_types.py +234 -0
- app/shared/domain/data_types_parts/scalars.py +719 -0
- app/shared/domain/data_types_parts/sequence.py +129 -0
- app/shared/domain/dataset_schema.py +48 -0
- app/shared/domain/eval_sandbox.py +63 -0
- app/shared/domain/expression_system.py +366 -0
- app/shared/domain/regex_flags.py +53 -0
- app/shared/domain/schema/builder.py +223 -0
- app/shared/domain/schema/models.py +339 -0
- app/shared/domain/transforms/__init__.py +28 -0
- app/shared/domain/transforms/aggregate.py +141 -0
- app/shared/domain/transforms/base.py +171 -0
- app/shared/domain/transforms/cast_type.py +120 -0
- app/shared/domain/transforms/concat.py +103 -0
- app/shared/domain/transforms/conditional_assign.py +114 -0
- app/shared/domain/transforms/date_format.py +81 -0
- app/shared/domain/transforms/digits.py +77 -0
- app/shared/domain/transforms/drop_duplicates.py +94 -0
- app/shared/domain/transforms/fill_na.py +94 -0
- app/shared/domain/transforms/filter_rows.py +97 -0
- app/shared/domain/transforms/lookup.py +78 -0
- app/shared/domain/transforms/lower_case.py +70 -0
- app/shared/domain/transforms/map_value.py +90 -0
- app/shared/domain/transforms/math_expr.py +112 -0
- app/shared/domain/transforms/modulo.py +82 -0
- app/shared/domain/transforms/regex_extract.py +103 -0
- app/shared/domain/transforms/registry.py +95 -0
- app/shared/domain/transforms/replace.py +88 -0
- app/shared/domain/transforms/sort_rows.py +94 -0
- app/shared/domain/transforms/string_split.py +84 -0
- app/shared/domain/transforms/strip.py +73 -0
- app/shared/domain/transforms/substring.py +92 -0
- app/shared/domain/transforms/upper_case.py +70 -0
- app/shared/domain/transforms/weighted_sum.py +104 -0
- app/shared/domain/validation_constraints.py +69 -0
- app/shared/services/__init__.py +62 -0
- app/shared/services/ai/__init__.py +48 -0
- app/shared/services/ai/agent/__init__.py +40 -0
- app/shared/services/ai/agent/chat_tools/__init__.py +47 -0
- app/shared/services/ai/agent/chat_tools/apply_actions.py +592 -0
- app/shared/services/ai/agent/chat_tools/ask_user.py +237 -0
- app/shared/services/ai/agent/chat_tools/read_canvas.py +140 -0
- app/shared/services/ai/agent/chat_tools/read_project.py +160 -0
- app/shared/services/ai/agent/chat_tools/read_table.py +301 -0
- app/shared/services/ai/agent/chat_tools/schemas.py +105 -0
- app/shared/services/ai/agent/chat_tools/validate_table.py +131 -0
- app/shared/services/ai/agent/executor.py +554 -0
- app/shared/services/ai/agent/memory.py +256 -0
- app/shared/services/ai/agent/planner.py +282 -0
- app/shared/services/ai/agent/tool_registry.py +296 -0
- app/shared/services/ai/agent/tools/__init__.py +32 -0
- app/shared/services/ai/agent/tools/config_generate.py +133 -0
- app/shared/services/ai/agent/tools/config_refine.py +109 -0
- app/shared/services/ai/agent/tools/config_validate.py +403 -0
- app/shared/services/ai/agent/tools/merge_results.py +136 -0
- app/shared/services/ai/agent/tools/plan_chunks.py +79 -0
- app/shared/services/ai/agent/tools/script_parse.py +359 -0
- app/shared/services/ai/agent/types.py +138 -0
- app/shared/services/ai/chat_agent_runner.py +596 -0
- app/shared/services/ai/chat_orchestrator.py +725 -0
- app/shared/services/ai/failure_messages.py +65 -0
- app/shared/services/ai/job_storage.py +274 -0
- app/shared/services/ai/migrate_service.py +550 -0
- app/shared/services/ai/streaming/__init__.py +65 -0
- app/shared/services/ai/streaming/event_journal.py +197 -0
- app/shared/services/ai/streaming/orchestrator.py +262 -0
- app/shared/services/ai/streaming/pending_interaction_store.py +227 -0
- app/shared/services/ai/streaming/sse_response.py +148 -0
- app/shared/services/ai/streaming/types.py +73 -0
- app/shared/services/ai/types.py +213 -0
- app/shared/services/ai/utils.py +322 -0
- app/shared/services/diff/config_diff.py +320 -0
- app/shared/services/hardware.py +237 -0
- app/shared/services/llm/__init__.py +62 -0
- app/shared/services/llm/actions/__init__.py +42 -0
- app/shared/services/llm/actions/_canvas_validator.py +147 -0
- app/shared/services/llm/actions/_constraint_validator.py +401 -0
- app/shared/services/llm/actions/_regex_validator.py +85 -0
- app/shared/services/llm/actions/_schema_validator.py +82 -0
- app/shared/services/llm/actions/_settings_validator.py +76 -0
- app/shared/services/llm/actions/_transform_validator.py +78 -0
- app/shared/services/llm/actions/action_handlers.py +400 -0
- app/shared/services/llm/actions/action_parser.py +63 -0
- app/shared/services/llm/actions/action_processor.py +468 -0
- app/shared/services/llm/actions/action_validator.py +311 -0
- app/shared/services/llm/actions/diff_compute.py +195 -0
- app/shared/services/llm/actions/regex_handlers.py +284 -0
- app/shared/services/llm/actions/registry.py +336 -0
- app/shared/services/llm/actions/schema_handlers.py +335 -0
- app/shared/services/llm/actions/settings_handlers.py +180 -0
- app/shared/services/llm/actions/specs.py +259 -0
- app/shared/services/llm/actions/transform_handlers.py +279 -0
- app/shared/services/llm/actions/validation_types.py +147 -0
- app/shared/services/llm/cache/__init__.py +18 -0
- app/shared/services/llm/cache/response_cache.py +80 -0
- app/shared/services/llm/chat/__init__.py +35 -0
- app/shared/services/llm/chat/chat_system_prompt.py +561 -0
- app/shared/services/llm/chat/response_parser.py +296 -0
- app/shared/services/llm/config/__init__.py +45 -0
- app/shared/services/llm/config/crypto.py +135 -0
- app/shared/services/llm/config/loader.py +258 -0
- app/shared/services/llm/config/models.py +202 -0
- app/shared/services/llm/config/presets.py +128 -0
- app/shared/services/llm/config_generator.py +163 -0
- app/shared/services/llm/constraints/__init__.py +41 -0
- app/shared/services/llm/constraints/constraint_builder.py +177 -0
- app/shared/services/llm/constraints/constraint_deletion.py +84 -0
- app/shared/services/llm/constraints/constraint_id.py +174 -0
- app/shared/services/llm/constraints/frontend_instructions.py +333 -0
- app/shared/services/llm/constraints/inline_batch.py +279 -0
- app/shared/services/llm/discovery/__init__.py +35 -0
- app/shared/services/llm/discovery/scanner.py +181 -0
- app/shared/services/llm/generation/__init__.py +42 -0
- app/shared/services/llm/generation/agent_wiring.py +156 -0
- app/shared/services/llm/generation/config_builder.py +386 -0
- app/shared/services/llm/generation/errors.py +36 -0
- app/shared/services/llm/generation/existing_config.py +99 -0
- app/shared/services/llm/generation/profiler.py +279 -0
- app/shared/services/llm/generation/prompt_builder.py +199 -0
- app/shared/services/llm/generation/response_parser.py +144 -0
- app/shared/services/llm/generation/service.py +622 -0
- app/shared/services/llm/models.py +104 -0
- app/shared/services/llm/providers/__init__.py +48 -0
- app/shared/services/llm/providers/base.py +310 -0
- app/shared/services/llm/providers/cached_provider.py +90 -0
- app/shared/services/llm/providers/ollama.py +498 -0
- app/shared/services/llm/providers/openai.py +334 -0
- app/shared/services/llm/providers/registry.py +108 -0
- app/shared/services/llm/schema_resolver.py +141 -0
- app/shared/services/llm/suggestion_utils.py +219 -0
- app/shared/services/llm/validate_executor.py +163 -0
- app/shared/services/llm/yaml_io.py +406 -0
- app/shared/services/preview/__init__.py +18 -0
- app/shared/services/preview/loader.py +145 -0
- app/shared/services/preview/path_validation.py +138 -0
- app/shared/services/project_loader.py +57 -0
- app/shared/services/schema_inference.py +260 -0
- app/shared/services/schema_runtime_builder.py +120 -0
- app/shared/services/validation/__init__.py +52 -0
- app/shared/services/validation/chunked_loader.py +509 -0
- app/shared/services/validation/dag/__init__.py +35 -0
- app/shared/services/validation/dag/builder.py +141 -0
- app/shared/services/validation/dag/executor.py +225 -0
- app/shared/services/validation/dag/sorter.py +77 -0
- app/shared/services/validation/data_loader.py +231 -0
- app/shared/services/validation/engine.py +446 -0
- app/shared/services/validation/executor.py +1030 -0
- app/shared/services/validation/extractors.py +266 -0
- app/shared/services/validation/history.py +209 -0
- app/shared/services/validation/json_payload.py +136 -0
- app/shared/services/validation/loader.py +152 -0
- app/shared/services/validation/memory_monitor.py +179 -0
- app/shared/services/validation/postprocess.py +208 -0
- app/shared/services/validation/progress.py +64 -0
- app/shared/services/validation/report_export.py +222 -0
- app/shared/services/validation/resolver.py +180 -0
- app/shared/services/validation/service.py +418 -0
- app/shared/services/validation/types.py +238 -0
- app/shared/services/validation/validators/__init__.py +36 -0
- app/shared/services/validation/validators/adapter.py +182 -0
- app/shared/services/validation/validators/base.py +332 -0
- app/shared/services/validation/validators/composite.py +233 -0
- app/shared/services/validation/validators/date_logic.py +276 -0
- app/start_server.py +133 -0
- precis_cli-0.1.3.dist-info/METADATA +180 -0
- precis_cli-0.1.3.dist-info/RECORD +442 -0
- precis_cli-0.1.3.dist-info/WHEEL +5 -0
- precis_cli-0.1.3.dist-info/entry_points.txt +4 -0
- precis_cli-0.1.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
# backend/app/cli/shell/commands/config/init.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview 配置初始化命令模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 提供 config init 子命令基于模板创建新配置文件
|
|
22
|
+
- 支持 project、constraint、pattern 三种模板类型
|
|
23
|
+
- 自动使用项目名称填充模板变量
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- ConfigInitCommand 继承 Command 基类
|
|
27
|
+
- 使用字典映射模板类型到 (默认文件名, 模板内容)
|
|
28
|
+
- 检查文件是否已存在,避免意外覆盖
|
|
29
|
+
|
|
30
|
+
输入示例:
|
|
31
|
+
config init project
|
|
32
|
+
config init constraint my_constraints.yaml
|
|
33
|
+
|
|
34
|
+
输出示例:
|
|
35
|
+
CommandResult.ok("已创建配置文件: project.precis.yaml")
|
|
36
|
+
CommandResult.error("文件已存在: project.precis.yaml")
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
import os
|
|
40
|
+
|
|
41
|
+
from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
|
|
42
|
+
from app.cli.shell.commands.config.base import CONSTRAINT_TEMPLATE, PATTERNS_TEMPLATE, PROJECT_TEMPLATE
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ConfigInitCommand(Command):
|
|
46
|
+
"""初始化配置命令。
|
|
47
|
+
|
|
48
|
+
基于内置模板创建新的配置文件。
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self) -> None:
|
|
52
|
+
super().__init__("init")
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def description(self) -> str:
|
|
56
|
+
return "初始化新的配置文件"
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def usage(self) -> str:
|
|
60
|
+
return "config init <project|constraint|pattern> [filename]"
|
|
61
|
+
|
|
62
|
+
def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
|
|
63
|
+
"""执行初始化配置命令。
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
args: 命令参数列表,第一个为模板类型,第二个可选为文件名
|
|
67
|
+
context: 项目上下文
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
创建成功或失败的结果
|
|
71
|
+
"""
|
|
72
|
+
project_path = context.project_path
|
|
73
|
+
if project_path is None:
|
|
74
|
+
return CommandResult.error("未打开项目,请先使用 'open <path>' 命令打开项目")
|
|
75
|
+
|
|
76
|
+
if not args:
|
|
77
|
+
return CommandResult.error(
|
|
78
|
+
"请指定模板类型: project, constraint, pattern\n用法: config init <type> [filename]"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
template_type = args[0].lower()
|
|
82
|
+
|
|
83
|
+
# 模板类型映射:类型 -> (默认文件名, 模板内容)
|
|
84
|
+
templates = {
|
|
85
|
+
"project": ("project.precis.yaml", PROJECT_TEMPLATE),
|
|
86
|
+
"constraint": ("constraints.yaml", CONSTRAINT_TEMPLATE),
|
|
87
|
+
"pattern": ("patterns.yaml", PATTERNS_TEMPLATE),
|
|
88
|
+
"patterns": ("patterns.yaml", PATTERNS_TEMPLATE),
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if template_type not in templates:
|
|
92
|
+
return CommandResult.error(f"未知模板类型: {template_type}\n可用类型: project, constraint, pattern")
|
|
93
|
+
|
|
94
|
+
default_filename, template = templates[template_type]
|
|
95
|
+
|
|
96
|
+
# 使用指定的文件名或默认文件名
|
|
97
|
+
filename = args[1] if len(args) > 1 else default_filename
|
|
98
|
+
# 4.7: 路径穿越防护——对齐 config set 的防护语义(禁止绝对路径/../~
|
|
99
|
+
# 前缀),否则 config init ../../x.yaml 可写项目外
|
|
100
|
+
from pathlib import Path as _Path
|
|
101
|
+
|
|
102
|
+
if os.path.isabs(filename) or any(seg == ".." for seg in _Path(filename).parts) or filename.startswith("~"):
|
|
103
|
+
return CommandResult.error(f"非法的文件名(不允许绝对路径或 .. 引用): {filename}")
|
|
104
|
+
filepath = os.path.join(project_path, filename)
|
|
105
|
+
if not os.path.abspath(filepath).startswith(os.path.abspath(project_path) + os.sep):
|
|
106
|
+
return CommandResult.error(f"文件路径超出项目目录: {filename}")
|
|
107
|
+
|
|
108
|
+
# 检查文件是否已存在
|
|
109
|
+
if os.path.exists(filepath):
|
|
110
|
+
return CommandResult.error(f"文件已存在: {filename}\n如需覆盖,请先删除原文件")
|
|
111
|
+
|
|
112
|
+
# 生成文件内容(使用项目名称填充模板变量)。
|
|
113
|
+
# 用 replace 而非 str.format:模板内含正则量词 {2,} 等花括号字面量,
|
|
114
|
+
# format 会把它们当替换字段抛 KeyError(config init pattern 必崩)
|
|
115
|
+
project_name = os.path.basename(project_path)
|
|
116
|
+
content = template.replace("{project_name}", project_name)
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
with open(filepath, "w", encoding="utf-8") as f:
|
|
120
|
+
f.write(content)
|
|
121
|
+
return CommandResult.ok(f"已创建配置文件: {filename}")
|
|
122
|
+
except Exception as e:
|
|
123
|
+
return CommandResult.error(f"创建文件失败: {e}")
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
# backend/app/cli/shell/commands/config/inspect.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview 配置自检命令模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 提供 config inspect 子命令,执行项目配置的跨文件一致性自检
|
|
22
|
+
- 复用后端 inspect_config 检查引擎(ID 一致性、引用完整性、数据源冲突等)
|
|
23
|
+
- 将检查结果格式化为友好的 CLI 输出(按严重度分组、显示修复建议)
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- ConfigInspectCommand 继承 Command 基类
|
|
27
|
+
- 通过 load_project() 加载项目(内部已执行 inspect_config,结果在 loading_errors)
|
|
28
|
+
- _format_report(): 将 LoadingError 列表格式化为可读文本
|
|
29
|
+
|
|
30
|
+
输入示例:
|
|
31
|
+
config inspect
|
|
32
|
+
|
|
33
|
+
输出示例:
|
|
34
|
+
CommandResult.ok("✓ 配置自检通过,未发现问题")
|
|
35
|
+
CommandResult.error("配置自检发现 3 个问题:\n...")
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
|
|
41
|
+
from app.cli.shell.formatter import _supports_unicode
|
|
42
|
+
from app.shared.core.project.loader.loader_parts.main import load_project
|
|
43
|
+
from app.shared.core.project.loader.types import LoadingError
|
|
44
|
+
|
|
45
|
+
_CHECK_MARK = "\u2713" if _supports_unicode() else "[OK]"
|
|
46
|
+
_CROSS_MARK = "\u2717" if _supports_unicode() else "[FAIL]"
|
|
47
|
+
|
|
48
|
+
# 严重度展示符号与标签
|
|
49
|
+
_SEVERITY_DISPLAY = {
|
|
50
|
+
"blocker": ("🔴", "阻塞"),
|
|
51
|
+
"warning": ("⚠️", "警告"),
|
|
52
|
+
"info": ("ℹ️", "提示"),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ConfigInspectCommand(Command):
|
|
57
|
+
"""配置自检命令。
|
|
58
|
+
|
|
59
|
+
执行项目配置的跨文件一致性检查,复用后端 inspect_config 引擎。
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self) -> None:
|
|
63
|
+
super().__init__("inspect")
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def description(self) -> str:
|
|
67
|
+
return "执行配置文件跨文件一致性自检(ID 一致性、引用完整性、数据源冲突)"
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def usage(self) -> str:
|
|
71
|
+
return "config inspect"
|
|
72
|
+
|
|
73
|
+
def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
|
|
74
|
+
"""执行配置自检命令。
|
|
75
|
+
|
|
76
|
+
通过 load_project 加载项目,复用其内置的 inspect_config 检查逻辑,
|
|
77
|
+
将收集到的 loading_errors 格式化为友好的 CLI 报告。
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
args: 命令参数列表(本命令不接收参数)
|
|
81
|
+
context: 项目上下文
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
自检结果,成功表示无问题,失败表示发现问题
|
|
85
|
+
"""
|
|
86
|
+
project_path = context.project_path
|
|
87
|
+
if project_path is None:
|
|
88
|
+
return CommandResult.error("未打开项目,请先使用 'open <path>' 命令打开项目")
|
|
89
|
+
|
|
90
|
+
# 定位 manifest 文件
|
|
91
|
+
import os
|
|
92
|
+
|
|
93
|
+
manifest_path = os.path.join(project_path, "project.precis.yaml")
|
|
94
|
+
if not os.path.isfile(manifest_path):
|
|
95
|
+
# 兼容 .yml 后缀
|
|
96
|
+
alt = os.path.join(project_path, "project.precis.yml")
|
|
97
|
+
if os.path.isfile(alt):
|
|
98
|
+
manifest_path = alt
|
|
99
|
+
else:
|
|
100
|
+
return CommandResult.error(f"未找到项目清单文件 project.precis.yaml(在 {project_path} 下)")
|
|
101
|
+
|
|
102
|
+
# 复用 load_project:其内部已执行 inspect_config,结果在 loading_errors
|
|
103
|
+
try:
|
|
104
|
+
loaded = load_project(manifest_path)
|
|
105
|
+
except Exception as e:
|
|
106
|
+
return CommandResult.error(f"加载项目失败: {e}")
|
|
107
|
+
|
|
108
|
+
errors: list[LoadingError] = loaded.loading_errors or []
|
|
109
|
+
warnings: list[str] = loaded.warnings or []
|
|
110
|
+
|
|
111
|
+
if not errors:
|
|
112
|
+
extra = f"\n(另有 {len(warnings)} 条加载警告)" if warnings else ""
|
|
113
|
+
return CommandResult.ok(f"{_CHECK_MARK} 配置自检通过,未发现问题{extra}")
|
|
114
|
+
|
|
115
|
+
report = self._format_report(errors)
|
|
116
|
+
# 有 blocker 时整体视为失败,否则视为成功附带警告
|
|
117
|
+
has_blocker = any(e.severity == "blocker" for e in errors)
|
|
118
|
+
summary = f"配置自检发现 {len(errors)} 个问题"
|
|
119
|
+
if has_blocker:
|
|
120
|
+
return CommandResult.error(f"{summary}:\n{report}")
|
|
121
|
+
return CommandResult.ok(f"{summary}(均为非阻塞,建议处理):\n{report}")
|
|
122
|
+
|
|
123
|
+
def _format_report(self, errors: list[LoadingError]) -> str:
|
|
124
|
+
"""将 LoadingError 列表格式化为可读文本报告。
|
|
125
|
+
|
|
126
|
+
按 severity 分组展示,每组下列出各问题的标题、文件路径与修复建议。
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
errors: 自检收集的错误列表
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
格式化的多行文本报告
|
|
133
|
+
"""
|
|
134
|
+
lines: list[str] = []
|
|
135
|
+
|
|
136
|
+
# 按严重度分组:blocker → warning → info
|
|
137
|
+
severity_order = ["blocker", "warning", "info"]
|
|
138
|
+
grouped: dict[str, list[LoadingError]] = {s: [] for s in severity_order}
|
|
139
|
+
for err in errors:
|
|
140
|
+
grouped.setdefault(err.severity, []).append(err)
|
|
141
|
+
|
|
142
|
+
for sev in severity_order:
|
|
143
|
+
group = grouped.get(sev, [])
|
|
144
|
+
if not group:
|
|
145
|
+
continue
|
|
146
|
+
icon, label = _SEVERITY_DISPLAY.get(sev, ("•", sev))
|
|
147
|
+
lines.append(f"\n{icon} {label}({len(group)})")
|
|
148
|
+
|
|
149
|
+
for err in group:
|
|
150
|
+
title = err.title or err.error_type or "未知问题"
|
|
151
|
+
lines.append(f" {_CROSS_MARK} {title}")
|
|
152
|
+
if err.file_path:
|
|
153
|
+
lines.append(f" 文件: {err.file_path}")
|
|
154
|
+
if err.ref_id:
|
|
155
|
+
lines.append(f" 编号: {err.ref_id}")
|
|
156
|
+
if err.description:
|
|
157
|
+
lines.append(f" 说明: {err.description}")
|
|
158
|
+
if err.fix_hint:
|
|
159
|
+
lines.append(f" 建议: {err.fix_hint}")
|
|
160
|
+
# 若有一键修复 API,提示用户(CLI 暂不自动修复,但告知可修复)
|
|
161
|
+
if err.fix_api:
|
|
162
|
+
lines.append(f" 可修复: {err.fix_api.get('method', 'POST')} {err.fix_api.get('path', '')}")
|
|
163
|
+
|
|
164
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
# backend/app/cli/shell/commands/config/list.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview 配置列表命令模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 提供 config list 子命令列出项目中所有 YAML 配置文件
|
|
22
|
+
- 递归扫描 schemas、constraints、patterns、regex 目录
|
|
23
|
+
- 格式化显示文件名与大小
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- ConfigListCommand 继承 Command 基类
|
|
27
|
+
- 先扫描项目根目录,再递归扫描子目录
|
|
28
|
+
- _format_size(): 将字节转换为人类可读格式(B/KB/MB)
|
|
29
|
+
|
|
30
|
+
输入示例:
|
|
31
|
+
config list
|
|
32
|
+
|
|
33
|
+
输出示例:
|
|
34
|
+
配置文件列表表格
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from app.cli.shared_services.config_ops import list_config_files
|
|
38
|
+
from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
|
|
39
|
+
from app.cli.shell.formatter import Formatter
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ConfigListCommand(Command):
|
|
43
|
+
"""列出配置命令。
|
|
44
|
+
|
|
45
|
+
显示项目中所有 YAML 配置文件及其大小。
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self) -> None:
|
|
49
|
+
super().__init__("list")
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def description(self) -> str:
|
|
53
|
+
return "列出项目中的所有配置文件"
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def usage(self) -> str:
|
|
57
|
+
return "config list"
|
|
58
|
+
|
|
59
|
+
def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
|
|
60
|
+
"""执行列出配置命令。
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
args: 命令参数列表(此命令不需要参数)
|
|
64
|
+
context: 项目上下文
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
配置文件列表或提示信息
|
|
68
|
+
"""
|
|
69
|
+
project_path = context.project_path
|
|
70
|
+
if project_path is None:
|
|
71
|
+
return CommandResult.error("未打开项目,请先使用 'open <path>' 命令打开项目")
|
|
72
|
+
|
|
73
|
+
# 扫描配置文件(委托 shared_services 纯逻辑,CLI/TUI 同源)
|
|
74
|
+
config_files = list_config_files(project_path)
|
|
75
|
+
|
|
76
|
+
if not config_files:
|
|
77
|
+
return CommandResult.ok("暂无配置文件")
|
|
78
|
+
|
|
79
|
+
output_lines = [Formatter.header("\n配置文件列表:")]
|
|
80
|
+
output_lines.append(f"{'文件名':<40} {'大小':>10}")
|
|
81
|
+
output_lines.append("-" * 52)
|
|
82
|
+
|
|
83
|
+
for cf in config_files:
|
|
84
|
+
size_str = self._format_size(cf.size)
|
|
85
|
+
output_lines.append(f"{cf.name:<40} {size_str:>10}")
|
|
86
|
+
|
|
87
|
+
output_lines.append(f"\n共 {len(config_files)} 个配置文件")
|
|
88
|
+
return CommandResult.ok("\n".join(output_lines))
|
|
89
|
+
|
|
90
|
+
def _format_size(self, size: int) -> str:
|
|
91
|
+
"""格式化文件大小。
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
size: 文件大小(字节)
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
人类可读的字符串,如 "1.5KB"
|
|
98
|
+
"""
|
|
99
|
+
if size < 1024:
|
|
100
|
+
return f"{size}B"
|
|
101
|
+
elif size < 1024 * 1024:
|
|
102
|
+
return f"{size / 1024:.1f}KB"
|
|
103
|
+
else:
|
|
104
|
+
return f"{size / (1024 * 1024):.1f}MB"
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
# backend/app/cli/shell/commands/config/set.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview 配置设置命令模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 提供 config set 子命令按点号路径设置配置项值
|
|
22
|
+
- 支持布尔值、整数、浮点数、列表、字典与字符串的自动类型解析
|
|
23
|
+
- 保留 YAML 注释与格式,原子写回,路径解析含穿越防护
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- ConfigSetCommand 继承 Command 基类
|
|
27
|
+
- _parse_value(): 自动推断值的类型(bool -> int -> float -> yaml -> str),已收敛至
|
|
28
|
+
shared_services 的 parse_config_value(CLI/TUI 同源)
|
|
29
|
+
- 文件定位/写入统一委托 shared_services 的 set_config_value_in_file:
|
|
30
|
+
find_config_file 防路径穿越 + ruamel round-trip 仅替换目标键的值(保留注释)
|
|
31
|
+
+ 临时文件原子替换
|
|
32
|
+
|
|
33
|
+
输入示例:
|
|
34
|
+
config set project.precis.yaml project.name "My Project"
|
|
35
|
+
config set project.precis.yaml validation.auto_validate true
|
|
36
|
+
|
|
37
|
+
输出示例:
|
|
38
|
+
CommandResult.ok("已设置: project.name = My Project")
|
|
39
|
+
CommandResult.error("配置文件不存在: project.precis.yaml")
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
from app.cli.shared_services.config_ops import (
|
|
43
|
+
MATCH_BASENAME,
|
|
44
|
+
parse_config_value,
|
|
45
|
+
resolve_config_file,
|
|
46
|
+
set_config_value_in_file,
|
|
47
|
+
)
|
|
48
|
+
from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ConfigSetCommand(Command):
|
|
52
|
+
"""设置配置项命令。
|
|
53
|
+
|
|
54
|
+
按点号路径设置 YAML 配置文件中指定项的值。
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self) -> None:
|
|
58
|
+
super().__init__("set")
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def description(self) -> str:
|
|
62
|
+
return "设置配置项的值(支持点号路径)"
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def usage(self) -> str:
|
|
66
|
+
return "config set [--string] <config_file> <key_path> <value>"
|
|
67
|
+
|
|
68
|
+
def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
|
|
69
|
+
"""执行设置配置项命令。
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
args: 命令参数列表,需要包含文件名、路径和值
|
|
73
|
+
context: 项目上下文
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
设置成功或失败的结果
|
|
77
|
+
"""
|
|
78
|
+
project_path = context.project_path
|
|
79
|
+
if project_path is None:
|
|
80
|
+
return CommandResult.error("未打开项目,请先使用 'open <path>' 命令打开项目")
|
|
81
|
+
|
|
82
|
+
# §4.3: --string 旗标——身份型字段(编号 007/版本 1.10)跳过类型推断按原字面量写入
|
|
83
|
+
raw_args = args
|
|
84
|
+
as_string = False
|
|
85
|
+
if raw_args and raw_args[0] == "--string":
|
|
86
|
+
as_string = True
|
|
87
|
+
raw_args = raw_args[1:]
|
|
88
|
+
|
|
89
|
+
if len(raw_args) < 3:
|
|
90
|
+
return CommandResult.error(
|
|
91
|
+
"用法: config set [--string] <config_file> <key_path> <value>\n"
|
|
92
|
+
'示例: config set project.precis.yaml project.name "My Project"\n'
|
|
93
|
+
" config set --string users.schema.yaml col.code 007 # 保留前导零"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
config_file = raw_args[0]
|
|
97
|
+
key_path = raw_args[1]
|
|
98
|
+
value_str = raw_args[2]
|
|
99
|
+
|
|
100
|
+
# 解析值(委托 shared_services 纯逻辑,CLI/TUI 同源)
|
|
101
|
+
# parse_config_value 始终成功返回三元组,行为与原 _parse_value 一致
|
|
102
|
+
value = value_str if as_string else parse_config_value(value_str)[1]
|
|
103
|
+
|
|
104
|
+
# 定位结果(用于文件名模糊回退命中时披露实际写入路径)
|
|
105
|
+
resolved_path, match_kind = resolve_config_file(project_path, config_file)
|
|
106
|
+
|
|
107
|
+
# 定位文件并写入(委托 shared_services,CLI/TUI 同源):
|
|
108
|
+
# - find_config_file 统一路径解析,含 .. / 绝对路径等穿越防护与递归回退
|
|
109
|
+
# - ruamel round-trip 仅替换目标键的值,保留注释与格式
|
|
110
|
+
# - 临时文件 + os.replace 原子写回,失败时不损坏原文件
|
|
111
|
+
ok, error_message = set_config_value_in_file(project_path, config_file, key_path, value)
|
|
112
|
+
if not ok:
|
|
113
|
+
return CommandResult.error(error_message)
|
|
114
|
+
|
|
115
|
+
message = f"已设置: {key_path} = {value}"
|
|
116
|
+
if match_kind == MATCH_BASENAME and resolved_path:
|
|
117
|
+
# 模糊回退命中的是同名文件而非用户输入路径,必须披露实际写入位置
|
|
118
|
+
message += f"(按文件名回退匹配,实际写入: {resolved_path})"
|
|
119
|
+
return CommandResult.ok(message)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
# backend/app/cli/shell/commands/config/show.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview 配置显示命令模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 提供 config show 子命令显示项目配置文件内容
|
|
22
|
+
- 支持单文件显示与默认配置文件批量显示
|
|
23
|
+
- 使用 YAML 格式化输出
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- ConfigShowCommand 继承 Command 基类
|
|
27
|
+
- 指定文件时调用 _show_single_file() 显示单个文件
|
|
28
|
+
- 未指定时显示一组默认配置文件的内容
|
|
29
|
+
|
|
30
|
+
输入示例:
|
|
31
|
+
config show
|
|
32
|
+
config show project.precis.yaml
|
|
33
|
+
|
|
34
|
+
输出示例:
|
|
35
|
+
格式化的 YAML 配置内容
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
import os
|
|
39
|
+
|
|
40
|
+
import yaml
|
|
41
|
+
|
|
42
|
+
from app.cli.shared_services.config_ops import find_config_file
|
|
43
|
+
from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
|
|
44
|
+
from app.cli.shell.formatter import Formatter
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ConfigShowCommand(Command):
|
|
48
|
+
"""显示配置命令。
|
|
49
|
+
|
|
50
|
+
显示项目配置文件的内容,支持单文件或批量显示。
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(self) -> None:
|
|
54
|
+
super().__init__("show")
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def description(self) -> str:
|
|
58
|
+
return "显示当前项目的配置文件内容"
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def usage(self) -> str:
|
|
62
|
+
return "config show [config_file]"
|
|
63
|
+
|
|
64
|
+
def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
|
|
65
|
+
"""执行显示配置命令。
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
args: 命令参数列表,可能包含要显示的文件名
|
|
69
|
+
context: 项目上下文
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
配置文件内容或错误提示
|
|
73
|
+
"""
|
|
74
|
+
project_path = context.project_path
|
|
75
|
+
if project_path is None:
|
|
76
|
+
return CommandResult.error("未打开项目,请先使用 'open <path>' 命令打开项目")
|
|
77
|
+
|
|
78
|
+
# 如果指定了具体文件,只显示该文件
|
|
79
|
+
if args:
|
|
80
|
+
config_file = args[0]
|
|
81
|
+
config_path = find_config_file(project_path, config_file)
|
|
82
|
+
if not config_path:
|
|
83
|
+
return CommandResult.error(f"配置文件不存在: {config_file}")
|
|
84
|
+
# 显示相对路径
|
|
85
|
+
rel_path = os.path.relpath(config_path, project_path)
|
|
86
|
+
return self._show_single_file(config_path, rel_path)
|
|
87
|
+
|
|
88
|
+
# 否则显示所有配置文件。
|
|
89
|
+
# §4.26: 无参 show 按 V2 目录结构遍历(schemas/constraints/regex/transforms/
|
|
90
|
+
# templates 分目录 + 根目录两文件)——原硬编码 V1 根目录文件清单,对 V2
|
|
91
|
+
# 项目几乎只显示 manifest 一个文件,其余配置全看不到(不做 V1 兼容层)
|
|
92
|
+
from pathlib import Path as _Path
|
|
93
|
+
|
|
94
|
+
output_lines = [Formatter.header("\n项目配置文件:")]
|
|
95
|
+
|
|
96
|
+
def _show_file(rel_path: str, abs_path: str) -> None:
|
|
97
|
+
output_lines.append(f"\n{Formatter.info('--- ' + rel_path + ' ---')}")
|
|
98
|
+
try:
|
|
99
|
+
output_lines.append(_Path(abs_path).read_text(encoding="utf-8"))
|
|
100
|
+
except Exception as e:
|
|
101
|
+
output_lines.append(f"(读取失败: {e})")
|
|
102
|
+
|
|
103
|
+
# 分目录收集(保持稳定的目录顺序)
|
|
104
|
+
for subdir, pattern in (
|
|
105
|
+
("schemas", "*.schema.yaml"),
|
|
106
|
+
("constraints", "*.constraint.yaml"),
|
|
107
|
+
("regex", "*.regex.yaml"),
|
|
108
|
+
("transforms", "*.transform.yaml"),
|
|
109
|
+
("templates", "*.template.yaml"),
|
|
110
|
+
):
|
|
111
|
+
dir_path = _Path(project_path) / subdir
|
|
112
|
+
if not dir_path.is_dir():
|
|
113
|
+
continue
|
|
114
|
+
for fp in sorted(dir_path.glob(pattern)):
|
|
115
|
+
_show_file(f"{subdir}/{fp.name}", str(fp))
|
|
116
|
+
|
|
117
|
+
# 根目录核心文件
|
|
118
|
+
for root_file in ("project.precis.yaml", "patterns.precis.yaml"):
|
|
119
|
+
root_path = os.path.join(project_path, root_file)
|
|
120
|
+
if os.path.isfile(root_path):
|
|
121
|
+
_show_file(root_file, root_path)
|
|
122
|
+
|
|
123
|
+
if len(output_lines) == 1:
|
|
124
|
+
return CommandResult.ok("项目中没有找到任何配置文件。")
|
|
125
|
+
|
|
126
|
+
return CommandResult.ok("\n".join(output_lines))
|
|
127
|
+
|
|
128
|
+
def _show_single_file(self, config_path: str, config_file: str) -> CommandResult:
|
|
129
|
+
"""显示单个配置文件。
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
config_path: 文件的完整路径
|
|
133
|
+
config_file: 用于显示的相对路径
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
文件内容或错误提示
|
|
137
|
+
"""
|
|
138
|
+
try:
|
|
139
|
+
with open(config_path, encoding="utf-8") as f:
|
|
140
|
+
content = yaml.safe_load(f)
|
|
141
|
+
|
|
142
|
+
output_lines = [
|
|
143
|
+
Formatter.header(f"\n{config_file}"),
|
|
144
|
+
yaml.dump(content, allow_unicode=True, default_flow_style=False),
|
|
145
|
+
]
|
|
146
|
+
return CommandResult.ok("\n".join(output_lines))
|
|
147
|
+
except yaml.YAMLError as e:
|
|
148
|
+
return CommandResult.error(f"YAML 解析失败: {e}")
|
|
149
|
+
except Exception as e:
|
|
150
|
+
return CommandResult.error(f"读取失败: {e}")
|