codeplain 0.3.4__tar.gz → 0.3.5__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.
- codeplain-0.3.5/.claude/settings.local.json +10 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/CLAUDE.md +21 -1
- {codeplain-0.3.4 → codeplain-0.3.5}/PKG-INFO +7 -7
- codeplain-0.3.5/_version.py +1 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/file_utils.py +13 -2
- {codeplain-0.3.4 → codeplain-0.3.5}/plain2code.py +71 -35
- {codeplain-0.3.4 → codeplain-0.3.5}/plain2code_exceptions.py +5 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/plain2code_state.py +1 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/plain2code_telemetry.py +2 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/plain2code_utils.py +18 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/plain_file.py +53 -1
- {codeplain-0.3.4 → codeplain-0.3.5}/plain_spec.py +9 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/pyproject.toml +6 -6
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/actions/fix_conformance_test.py +23 -1
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/actions/prepare_testing_environment.py +6 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/actions/refactor_code.py +18 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/actions/render_functional_requirement.py +15 -1
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/render_context.py +0 -55
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/state_machine_config.py +7 -14
- {codeplain-0.3.4 → codeplain-0.3.5}/requirements.txt +6 -6
- codeplain-0.3.5/tests/data/acceptance_tests_warning/main_requiring_acceptance_tests.plain +16 -0
- codeplain-0.3.5/tests/data/acceptance_tests_warning/required_with_acceptance_tests.plain +15 -0
- codeplain-0.3.5/tests/data/sample_base64_image.txt +1 -0
- codeplain-0.3.5/tests/test_file_utils.py +92 -0
- codeplain-0.3.5/tests/test_plain2code.py +94 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/test_plainfileparser.py +108 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/test_plainspec.py +36 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/test_telemetry.py +16 -1
- codeplain-0.3.4/_version.py +0 -1
- codeplain-0.3.4/standard_template_library/__init__.py +0 -1
- codeplain-0.3.4/tests/test_file_utils.py +0 -41
- {codeplain-0.3.4 → codeplain-0.3.5}/.claude/settings.json +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/.flake8 +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/.github/dependabot.yml +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/.github/workflows/e2e.yml +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/.github/workflows/lint-and-test.yml +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/.github/workflows/nofity-slack-on-main-merge.yml +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/.github/workflows/publish-install-script.yml +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/.github/workflows/publish-to-pypi.yml +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/.gitignore +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/.vscode/launch.json +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/.vscode/settings.json +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/LICENSE +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/README.md +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/change_detection.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/cli_output/__init__.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/cli_output/dry_run.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/cli_output/render_summary.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/cli_output/status.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/codeplain_REST_api.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/concept_utils.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/config/__init__.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/config/system_config.yaml +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/diff_utils.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/docs/generate_cli.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/docs/plain2code_cli.md +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/docs/starting_a_plain_project_from_scratch.md +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/event_bus.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/examples/example_hello_world_golang/config.yaml +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/examples/example_hello_world_golang/harness_tests/hello_world_test.go +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/examples/example_hello_world_golang/hello_world_golang.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/examples/example_hello_world_golang/run.sh +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/examples/example_hello_world_python/config.yaml +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/examples/example_hello_world_python/harness_tests/hello_world_display/test_hello_world.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/examples/example_hello_world_python/hello_world_python.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/examples/example_hello_world_python/run.sh +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/examples/example_hello_world_react/config.yaml +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress/e2e/hello_world.cy.ts +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress/support/e2e.ts +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress.config.ts +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/examples/example_hello_world_react/harness_tests/hello_world_display/package.json +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/examples/example_hello_world_react/harness_tests/hello_world_display/tsconfig.json +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/examples/example_hello_world_react/hello_world_react.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/examples/example_hello_world_react/run.sh +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/examples/run.sh +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/git_utils.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/install/bash/examples.sh +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/install/bash/install.sh +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/install/bash/walkthrough.sh +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/install/powershell/examples.ps1 +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/install/powershell/install.ps1 +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/install/powershell/walkthrough.ps1 +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/memory_management.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/module_renderer.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/partial_rendering.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/plain2code_arguments.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/plain2code_console.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/plain2code_events.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/plain2code_logger.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/plain2code_nodes.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/plain2code_read_config.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/plain_modules.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/pytest.ini +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/__init__.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/actions/analyze_specification_ambiguity.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/actions/base_action.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/actions/commit_conformance_tests_changes.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/actions/commit_implementation_code_changes.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/actions/create_dist.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/actions/exit_with_error.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/actions/finish_functional_requirement.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/actions/fix_unit_tests.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/actions/prepare_repositories.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/actions/render_conformance_tests.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/actions/run_conformance_tests.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/actions/run_unit_tests.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/actions/summarize_conformance_tests.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/code_renderer.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/conformance_tests.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/implementation_code_helpers.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/render_types.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/render_utils.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/states.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/render_machine/triggers.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/resources/codeplain_overview.png +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/resources/plain_example.png +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/standard_template_library/golang-console-app-template.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/standard_template_library/python-console-app-template.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/standard_template_library/typescript-react-app-boilerplate.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/standard_template_library/typescript-react-app-template.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/system_config.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/test_scripts/run_conformance_tests_cypress.ps1 +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/test_scripts/run_conformance_tests_cypress.sh +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/test_scripts/run_conformance_tests_golang.ps1 +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/test_scripts/run_conformance_tests_golang.sh +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/test_scripts/run_conformance_tests_python.ps1 +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/test_scripts/run_conformance_tests_python.sh +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/test_scripts/run_unittests_flutter.ps1 +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/test_scripts/run_unittests_golang.ps1 +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/test_scripts/run_unittests_golang.sh +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/test_scripts/run_unittests_python.ps1 +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/test_scripts/run_unittests_python.sh +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/test_scripts/run_unittests_react.ps1 +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/test_scripts/run_unittests_react.sh +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/__init__.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/conftest.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/imports/circular_imports_1.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/imports/circular_imports_2.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/imports/circular_imports_main.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/imports/diamond_import_1.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/imports/diamond_import_2.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/imports/diamond_import_common.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/imports/diamond_imports_main.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/imports/non_existent_import.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/partial_rendering/pr_code_var.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/partial_rendering/pr_implement.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/partial_rendering/pr_leaf.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/partial_rendering/pr_middle.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/partial_rendering/pr_root.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/partial_rendering/pr_solo.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfile/add_new_task_modal_specification.yaml +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfile/duplicate_specification_heading.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfile/invalid_specification_order.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfile/missing_non_functional_requirements.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfile/plain_source_with_absolute_link.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfile/plain_source_with_url_link.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfile/task_list_ui_specification.yaml +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfile/task_manager_with_reference_links.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfile/without_non_functional_requirement.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/concept_validation_acceptance_tests.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/concept_validation_acceptance_tests_nondefined.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/concept_validation_defined_nondefined.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/concept_validation_defined_nondefined_2.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/concept_validation_definition.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/concept_validation_noconcepts.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/concept_validation_nonconcept.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/concept_validation_nondefined.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/concept_validation_redefinition.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/concept_validation_several_concepts.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/concept_validation_valid.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/exported_concepts.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/exported_concepts_base.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/exported_concepts_example.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/exported_concepts_missing.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/exported_concepts_missing_example.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/exported_concepts_nested.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/exported_concepts_nested_example.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/exported_concepts_transitive_example.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/exported_concepts_transitive_l1.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/exported_concepts_transitive_l2.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/plain_file_parser_with_comments.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/plain_file_with_comments_indented.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/regular_plain_source.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/required_concepts.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/required_concepts_defs.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/required_concepts_example.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/required_concepts_l1.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/required_concepts_l2.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/required_concepts_missing.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/required_concepts_module.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/required_concepts_partial.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/required_concepts_partial_duplicate.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/topological_sort.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/plainfileparser/topological_sort_not_referenced.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/requires/circular_requires_main.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/requires/circular_requires_sub.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/requires/diamond_requires_1.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/requires/diamond_requires_2.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/requires/diamond_requires_common.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/requires/diamond_requires_main.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/requires/independent_requires_1.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/requires/independent_requires_2.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/requires/independent_requires_main.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/requires/non_existent_require.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/requires/normal_requires_1.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/requires/normal_requires_2.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/requires/normal_requires_common.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/requires/normal_requires_main.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/simple.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/templates/block_level_include.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/templates/code_variables.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/templates/header.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/templates/implement.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/templates/implement_2.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/templates/template_include.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/data/templates/test_hardest_problem.plain +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/e2e/Dockerfile +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/e2e/conftest.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/e2e/test_hello_world_python.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/e2e/test_hello_world_python_windows.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/test_change_detection.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/test_cli_output.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/test_git_utils.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/test_imports.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/test_partial_rendering.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/test_plain_modules.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/test_plainfile.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/test_requires.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tests/test_resolve_config_file.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tui/__init__.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tui/components.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tui/models.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tui/plain2code_tui.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tui/plain_module_render_choice_tui.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tui/spinner.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tui/state_handlers.py +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tui/styles.css +0 -0
- {codeplain-0.3.4 → codeplain-0.3.5}/tui/widget_helpers.py +0 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Read(//Users/nero/DevelCodeplain/**)",
|
|
5
|
+
"Read(//Users/nero/DevelCodeplain/plain2code_rest_api/**)",
|
|
6
|
+
"Bash(/private/tmp/claude-501/-Users-nero-DevelCodeplain-codeplain/8382c3ca-47e9-4519-afd2-160102a63874/scratchpad/tvenv/bin/python /Users/nero/DevelCodeplain/codeplain/plain2code.py hello_world_python.plain --headless)",
|
|
7
|
+
"Bash(echo \"EXIT=$?\")"
|
|
8
|
+
]
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -179,7 +179,7 @@ black . --check && isort . --check-only && flake8 . && mypy . --check-untyped-de
|
|
|
179
179
|
- `plain2code_logger.py` - Custom logging with elapsed time timestamps
|
|
180
180
|
- `plain2code_console.py` - Rich console wrapper with custom styles
|
|
181
181
|
- `plain2code_state.py` - Runtime state (render ID, counters, timing)
|
|
182
|
-
- `standard_template_library/` - Built-in
|
|
182
|
+
- `standard_template_library/` - Built-in `.plain` templates (git subtree from `plainlang-examples`)
|
|
183
183
|
- `examples/` - Sample `.plain` projects
|
|
184
184
|
|
|
185
185
|
### Testing Architecture
|
|
@@ -223,6 +223,26 @@ Two logging modes:
|
|
|
223
223
|
### Running Plain2Code from Another Directory
|
|
224
224
|
The tool can be run from any directory by providing an absolute or relative path to the `.plain` file. All paths (build folder, log file, templates) are resolved relative to the `.plain` file's directory, not the current working directory, unless explicitly overridden via CLI arguments.
|
|
225
225
|
|
|
226
|
+
### Standard Template Library
|
|
227
|
+
|
|
228
|
+
`standard_template_library/` is a git subtree sourced from the `plainlang-examples` repository. It is **not** updated automatically — you must pull changes manually when the templates change upstream:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
git subtree pull --prefix=standard_template_library git@github.com:Codeplain-ai/plainlang-examples.git subtree/standard-template-library --squash
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The subtree does not track a branch pointer. The branch (`main` above) is just an argument passed at pull time — git uses the `git-subtree-split` hash embedded in the commit history to determine what's new since the last sync.
|
|
235
|
+
|
|
236
|
+
**Important:** You must pull from the `subtree/standard-template-library` branch, not `main`. This is a split branch containing only the `standard_template_library/` subdirectory contents at root level. Pulling from `main` would bring in the entire `plainlang-examples` repo.
|
|
237
|
+
|
|
238
|
+
If the split branch is outdated, regenerate it first in `plainlang-examples`:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
# In plainlang-examples/
|
|
242
|
+
git subtree split --prefix=standard_template_library -b subtree/standard-template-library
|
|
243
|
+
git push origin subtree/standard-template-library
|
|
244
|
+
```
|
|
245
|
+
|
|
226
246
|
### Windows Support
|
|
227
247
|
Windows users must use WSL (Windows Subsystem for Linux). The codebase has some platform-specific script handling (`.ps1` for Windows, `.sh` for Unix).
|
|
228
248
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeplain
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
4
4
|
Summary: Transform plain language specifications into working code
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Classifier: Environment :: Console
|
|
@@ -12,20 +12,20 @@ Requires-Dist: gitpython==3.1.50
|
|
|
12
12
|
Requires-Dist: mistletoe==1.3.0
|
|
13
13
|
Requires-Dist: networkx==3.6.1
|
|
14
14
|
Requires-Dist: python-frontmatter==1.3.0
|
|
15
|
-
Requires-Dist: python-liquid2==0.
|
|
15
|
+
Requires-Dist: python-liquid2==0.4.0
|
|
16
16
|
Requires-Dist: pyyaml==6.0.3
|
|
17
|
-
Requires-Dist: requests==2.34.
|
|
17
|
+
Requires-Dist: requests==2.34.2
|
|
18
18
|
Requires-Dist: rich==15.0.0
|
|
19
|
-
Requires-Dist: sentry-sdk==2.
|
|
19
|
+
Requires-Dist: sentry-sdk==2.63.0
|
|
20
20
|
Requires-Dist: textual>=7.5.0
|
|
21
|
-
Requires-Dist: tiktoken==0.
|
|
21
|
+
Requires-Dist: tiktoken==0.13.0
|
|
22
22
|
Requires-Dist: transitions==0.9.3
|
|
23
23
|
Provides-Extra: dev
|
|
24
24
|
Requires-Dist: black==26.3.1; extra == 'dev'
|
|
25
25
|
Requires-Dist: flake8==7.3.0; extra == 'dev'
|
|
26
26
|
Requires-Dist: isort==5.13.2; extra == 'dev'
|
|
27
|
-
Requires-Dist: mypy==1.
|
|
28
|
-
Requires-Dist: pytest==
|
|
27
|
+
Requires-Dist: mypy==2.1.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest==9.1.1; extra == 'dev'
|
|
29
29
|
Description-Content-Type: text/markdown
|
|
30
30
|
|
|
31
31
|
# Codeplain plain2code renderer
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.5"
|
|
@@ -8,8 +8,9 @@ from liquid2.exceptions import UndefinedError
|
|
|
8
8
|
|
|
9
9
|
import plain_spec
|
|
10
10
|
from plain2code_console import console
|
|
11
|
-
from plain2code_exceptions import UnsupportedResourceType
|
|
11
|
+
from plain2code_exceptions import UnsupportedBase64Content, UnsupportedResourceType
|
|
12
12
|
from plain2code_nodes import Plain2CodeIncludeTag, Plain2CodeLoaderMixin
|
|
13
|
+
from plain2code_utils import find_large_base64_blob
|
|
13
14
|
from plain_modules import CODEPLAIN_MEMORY_SUBFOLDER, CODEPLAIN_METADATA_FOLDER
|
|
14
15
|
|
|
15
16
|
BINARY_FILE_EXTENSIONS = [".pyc"]
|
|
@@ -157,7 +158,7 @@ def store_response_files(target_folder, response_files, existing_files):
|
|
|
157
158
|
|
|
158
159
|
os.makedirs(os.path.dirname(full_file_name), exist_ok=True)
|
|
159
160
|
|
|
160
|
-
with open(full_file_name, "w") as f:
|
|
161
|
+
with open(full_file_name, "w", encoding="utf-8") as f:
|
|
161
162
|
f.write(response_files[file_name])
|
|
162
163
|
|
|
163
164
|
if file_name not in existing_files:
|
|
@@ -206,6 +207,16 @@ def load_linked_resources(template_dirs: list[str], resources_list, module_name:
|
|
|
206
207
|
Please ensure that the resource exists in one of these locations, or specify the correct --template-dir if using custom templates.
|
|
207
208
|
""")
|
|
208
209
|
|
|
210
|
+
blob = find_large_base64_blob(content)
|
|
211
|
+
if blob is not None:
|
|
212
|
+
raise UnsupportedBase64Content(
|
|
213
|
+
f"Referenced resource '{file_name}' in module '{module_name}' contains a large "
|
|
214
|
+
f"base64-encoded blob ({len(blob)} characters), such as an embedded image. Inline "
|
|
215
|
+
"base64 data is not supported. Remove the data from the resource. "
|
|
216
|
+
"If the data should be used by the end software, "
|
|
217
|
+
"save the data to a separate file and include the file path in the specification without it being a reference file."
|
|
218
|
+
)
|
|
219
|
+
|
|
209
220
|
linked_resources[file_name] = content
|
|
210
221
|
|
|
211
222
|
return linked_resources
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import importlib.resources
|
|
2
1
|
import logging
|
|
3
2
|
import logging.config
|
|
4
3
|
import os
|
|
@@ -37,6 +36,7 @@ from plain2code_exceptions import (
|
|
|
37
36
|
PlainSyntaxError,
|
|
38
37
|
RenderCancelledError,
|
|
39
38
|
RenderingCreditBalanceTooLow,
|
|
39
|
+
UnsupportedBase64Content,
|
|
40
40
|
UnsupportedResourceType,
|
|
41
41
|
)
|
|
42
42
|
from plain2code_logger import (
|
|
@@ -54,7 +54,7 @@ from system_config import system_config
|
|
|
54
54
|
from tui.plain2code_tui import Plain2CodeTUI
|
|
55
55
|
from tui.plain_module_render_choice_tui import PlainModuleRenderChoiceTUI
|
|
56
56
|
|
|
57
|
-
DEFAULT_TEMPLATE_DIRS =
|
|
57
|
+
DEFAULT_TEMPLATE_DIRS = "standard_template_library"
|
|
58
58
|
RENDER_THREAD_SHUTDOWN_TIMEOUT = 0.7
|
|
59
59
|
|
|
60
60
|
# Exceptions that represent expected, user-facing error conditions. They are
|
|
@@ -74,6 +74,7 @@ EXPECTED_EXCEPTIONS = (
|
|
|
74
74
|
NetworkConnectionError,
|
|
75
75
|
ModuleDoesNotExistError,
|
|
76
76
|
UnsupportedResourceType,
|
|
77
|
+
UnsupportedBase64Content,
|
|
77
78
|
GitNotInstalledError,
|
|
78
79
|
SystemExit,
|
|
79
80
|
)
|
|
@@ -139,8 +140,8 @@ def setup_logging(
|
|
|
139
140
|
return logging.getLevelName(configured_log_level)
|
|
140
141
|
|
|
141
142
|
|
|
142
|
-
def _check_connection(codeplainAPI: codeplain_api.CodeplainAPI):
|
|
143
|
-
"""Check API connectivity
|
|
143
|
+
def _check_connection(codeplainAPI: codeplain_api.CodeplainAPI) -> Optional[str]:
|
|
144
|
+
"""Check API connectivity, validate API key and client version, and return the user email."""
|
|
144
145
|
response = codeplainAPI.connection_check(system_config.client_version)
|
|
145
146
|
|
|
146
147
|
if not response.get("api_key_valid", False):
|
|
@@ -157,33 +158,58 @@ def _check_connection(codeplainAPI: codeplain_api.CodeplainAPI):
|
|
|
157
158
|
"Please update using: uv tool upgrade codeplain"
|
|
158
159
|
)
|
|
159
160
|
|
|
161
|
+
return response.get("user_email")
|
|
160
162
|
|
|
161
|
-
def render(args, run_state: RunState, event_bus: EventBus, default_log_level: str = "INFO"): # noqa: C901
|
|
162
|
-
template_dirs = file_utils.get_template_directories(args.filename, args.template_dir, DEFAULT_TEMPLATE_DIRS)
|
|
163
163
|
|
|
164
|
+
def warn_if_acceptance_tests_without_conformance_script(plain_module, args) -> None:
|
|
165
|
+
"""Warn when any loaded module (including required modules) defines acceptance tests
|
|
166
|
+
but no conformance tests script is configured.
|
|
167
|
+
|
|
168
|
+
Acceptance tests are treated as conformance tests, so a conformance tests script is required
|
|
169
|
+
to actually run them. Without it, the acceptance tests cannot be executed.
|
|
170
|
+
"""
|
|
171
|
+
if args.conformance_tests_script:
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
module_names_with_acceptance_tests = [
|
|
175
|
+
module.module_name
|
|
176
|
+
for module in plain_module.all_required_modules + [plain_module]
|
|
177
|
+
if plain_spec.has_acceptance_tests(module.plain_source)
|
|
178
|
+
]
|
|
179
|
+
if not module_names_with_acceptance_tests:
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
module_names = ", ".join(module_names_with_acceptance_tests)
|
|
183
|
+
console.warning(
|
|
184
|
+
f"Acceptance tests were found ({module_names}) but no conformance tests script is configured. "
|
|
185
|
+
"Acceptance tests are treated as conformance tests and require a conformance tests script "
|
|
186
|
+
"(--conformance-tests-script or 'conformance_tests_script' in config) to be executed."
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def render( # noqa: C901
|
|
191
|
+
plain_module: plain_modules.PlainModule,
|
|
192
|
+
args,
|
|
193
|
+
run_state: RunState,
|
|
194
|
+
event_bus: EventBus,
|
|
195
|
+
default_log_level: str = "INFO",
|
|
196
|
+
):
|
|
164
197
|
# Compute render range from either --render-range or --render-from
|
|
165
198
|
render_range = None
|
|
166
199
|
if args.render_range or args.render_from:
|
|
167
|
-
|
|
168
|
-
_, plain_source, _ = plain_file.plain_file_parser(args.filename, template_dirs)
|
|
169
|
-
render_range = plain_spec.compute_render_range(args, plain_source)
|
|
200
|
+
render_range = plain_spec.compute_render_range(args, plain_module.plain_source)
|
|
170
201
|
|
|
171
202
|
codeplainAPI = codeplain_api.CodeplainAPI(args.api_key, console)
|
|
172
203
|
assert args.api is not None and args.api != "", "API URL is required"
|
|
173
204
|
codeplainAPI.api_url = args.api
|
|
174
205
|
|
|
175
|
-
_check_connection(codeplainAPI)
|
|
206
|
+
run_state.user_email = _check_connection(codeplainAPI)
|
|
176
207
|
|
|
177
208
|
stop_event = threading.Event()
|
|
178
209
|
enter_pause_event = threading.Event()
|
|
179
210
|
signal.signal(signal.SIGTERM, lambda _signum, _frame: stop_event.set())
|
|
180
211
|
|
|
181
|
-
plain_module
|
|
182
|
-
args.filename,
|
|
183
|
-
args.build_folder,
|
|
184
|
-
args.conformance_tests_folder,
|
|
185
|
-
template_dirs,
|
|
186
|
-
)
|
|
212
|
+
warn_if_acceptance_tests_without_conformance_script(plain_module, args)
|
|
187
213
|
|
|
188
214
|
render_choice = None
|
|
189
215
|
if render_range is None:
|
|
@@ -308,28 +334,38 @@ def main(): # noqa: C901
|
|
|
308
334
|
console.error(f"Error fetching status: {str(e)}")
|
|
309
335
|
return
|
|
310
336
|
|
|
311
|
-
|
|
312
|
-
if args.dry_run or args.full_plain:
|
|
313
|
-
template_dirs = file_utils.get_template_directories(args.filename, args.template_dir, DEFAULT_TEMPLATE_DIRS)
|
|
337
|
+
template_dirs = file_utils.get_template_directories(args.filename, args.template_dir, DEFAULT_TEMPLATE_DIRS)
|
|
314
338
|
|
|
339
|
+
# Handle full plain early-exit (raw text dump; does not require a parsed module).
|
|
340
|
+
if args.full_plain:
|
|
315
341
|
try:
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
console.info(full_plain_source)
|
|
322
|
-
return
|
|
323
|
-
|
|
324
|
-
if args.dry_run:
|
|
325
|
-
console.info("Printing dry run output...\n")
|
|
326
|
-
_, plain_source_tree, _ = plain_file.plain_file_parser(args.filename, template_dirs)
|
|
327
|
-
render_range = plain_spec.compute_render_range(args, plain_source_tree)
|
|
328
|
-
print_dry_run_output(plain_source_tree, render_range)
|
|
329
|
-
return
|
|
342
|
+
module_name = Path(args.filename).stem
|
|
343
|
+
plain_source = plain_file.read_module_plain_source(module_name, template_dirs)
|
|
344
|
+
[full_plain_source, _] = file_utils.get_loaded_templates(template_dirs, plain_source)
|
|
345
|
+
console.info("Full plain text:\n")
|
|
346
|
+
console.info(full_plain_source)
|
|
330
347
|
except Exception as e:
|
|
331
348
|
console.error(f"Error: {str(e)}")
|
|
332
|
-
|
|
349
|
+
return
|
|
350
|
+
|
|
351
|
+
# Parse the plain file (and its required modules) once; reused by dry-run and rendering.
|
|
352
|
+
try:
|
|
353
|
+
plain_module = plain_modules.PlainModule(
|
|
354
|
+
args.filename,
|
|
355
|
+
args.build_folder,
|
|
356
|
+
args.conformance_tests_folder,
|
|
357
|
+
template_dirs,
|
|
358
|
+
)
|
|
359
|
+
except Exception as e:
|
|
360
|
+
console.error(f"Error: {str(e)}")
|
|
361
|
+
return
|
|
362
|
+
|
|
363
|
+
if args.dry_run:
|
|
364
|
+
console.info("Printing dry run output...\n")
|
|
365
|
+
render_range = plain_spec.compute_render_range(args, plain_module.plain_source)
|
|
366
|
+
print_dry_run_output(plain_module.plain_source, render_range)
|
|
367
|
+
warn_if_acceptance_tests_without_conformance_script(plain_module, args)
|
|
368
|
+
return
|
|
333
369
|
|
|
334
370
|
event_bus = EventBus()
|
|
335
371
|
|
|
@@ -357,7 +393,7 @@ def main(): # noqa: C901
|
|
|
357
393
|
raise MissingAPIKey(
|
|
358
394
|
"Your API key is required. Please set the CODEPLAIN_API_KEY environment variable or provide it with the --api-key argument.\n"
|
|
359
395
|
)
|
|
360
|
-
render(args, run_state, event_bus, default_log_level)
|
|
396
|
+
render(plain_module, args, run_state, event_bus, default_log_level)
|
|
361
397
|
except BaseException as e:
|
|
362
398
|
if isinstance(e, KeyboardInterrupt):
|
|
363
399
|
error_message = "Keyboard interrupt"
|
|
@@ -98,6 +98,8 @@ def capture_crash(exc_info, run_state: Optional[RunState], args) -> bool:
|
|
|
98
98
|
scope.set_tag("render_state", run_state.current_render_state)
|
|
99
99
|
scope.set_tag("current_module", run_state.current_module)
|
|
100
100
|
scope.set_tag("current_frid", run_state.current_frid)
|
|
101
|
+
if run_state.user_email:
|
|
102
|
+
scope.set_user({"email": run_state.user_email})
|
|
101
103
|
scope.set_tag("headless", bool(getattr(args, "headless", False)))
|
|
102
104
|
scope.set_tag("unittests_script_provided", bool(getattr(args, "unittests_script", None)))
|
|
103
105
|
scope.set_tag("conformance_tests_script_provided", bool(getattr(args, "conformance_tests_script", None)))
|
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
MAX_BASE64_BLOB_LENGTH = 8192
|
|
6
|
+
|
|
7
|
+
# Matches a long contiguous base64 / base64url run, optionally preceded by a data: URI header.
|
|
8
|
+
_BASE64_BLOB_PATTERN = re.compile(
|
|
9
|
+
r"(?:data:[\w.+-]+/[\w.+-]+;base64,)?[A-Za-z0-9+/_-]{%d,}={0,2}" % MAX_BASE64_BLOB_LENGTH
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def find_large_base64_blob(text: str) -> Optional[str]:
|
|
14
|
+
"""Return the first contiguous base64 blob at or above the threshold, or None."""
|
|
15
|
+
match = _BASE64_BLOB_PATTERN.search(text)
|
|
16
|
+
return match.group(0) if match else None
|
|
17
|
+
|
|
18
|
+
|
|
1
19
|
def format_duration_hms(total_seconds: int) -> str:
|
|
2
20
|
"""Format a duration in seconds as hours, minutes, and seconds (e.g. ``1h 2m 3.45s``, ``45.67s``)."""
|
|
3
21
|
if total_seconds < 0:
|
|
@@ -17,8 +17,9 @@ from mistletoe.utils import traverse
|
|
|
17
17
|
import concept_utils
|
|
18
18
|
import file_utils
|
|
19
19
|
import plain_spec
|
|
20
|
-
from plain2code_exceptions import ModuleDoesNotExistError, PlainSyntaxError
|
|
20
|
+
from plain2code_exceptions import ModuleDoesNotExistError, PlainSyntaxError, UnsupportedBase64Content
|
|
21
21
|
from plain2code_nodes import Plain2CodeIncludeTag, Plain2CodeLoaderMixin
|
|
22
|
+
from plain2code_utils import find_large_base64_blob
|
|
22
23
|
|
|
23
24
|
RESOURCE_MARKER = "[resource]"
|
|
24
25
|
|
|
@@ -203,6 +204,26 @@ def _is_acceptance_test_heading(token) -> tuple[bool, str | None]:
|
|
|
203
204
|
return False, None
|
|
204
205
|
|
|
205
206
|
|
|
207
|
+
def _find_acceptance_test_heading(token):
|
|
208
|
+
"""
|
|
209
|
+
Recursively search a token tree for an `acceptance tests` heading.
|
|
210
|
+
|
|
211
|
+
Returns the heading token if one is found anywhere in the subtree, otherwise None.
|
|
212
|
+
Used to detect acceptance tests nested under sections other than functional specs.
|
|
213
|
+
"""
|
|
214
|
+
is_acceptance_test_heading, _ = _is_acceptance_test_heading(token)
|
|
215
|
+
if is_acceptance_test_heading:
|
|
216
|
+
return token
|
|
217
|
+
|
|
218
|
+
if getattr(token, "children", None):
|
|
219
|
+
for child in token.children:
|
|
220
|
+
found = _find_acceptance_test_heading(child)
|
|
221
|
+
if found is not None:
|
|
222
|
+
return found
|
|
223
|
+
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
|
|
206
227
|
def _process_single_acceptance_test_requirement(functional_requirement: mistletoe.block_token.ListItem):
|
|
207
228
|
"""
|
|
208
229
|
Process a single functionality to extract acceptance tests.
|
|
@@ -473,6 +494,11 @@ def parse_plain_source( # noqa: C901
|
|
|
473
494
|
|
|
474
495
|
specification_heading = token.children[0].children[0].children[0].content
|
|
475
496
|
|
|
497
|
+
if specification_heading == plain_spec.ACCEPTANCE_TEST_HEADING:
|
|
498
|
+
raise PlainSyntaxError(
|
|
499
|
+
f"Plain syntax error: Syntax error at line {token.line_number}: {plain_spec.ACCEPTANCE_TEST_HEADING} heading should be nested under specific functional spec."
|
|
500
|
+
)
|
|
501
|
+
|
|
476
502
|
if specification_heading not in plain_spec.ALLOWED_SPECIFICATION_HEADINGS:
|
|
477
503
|
raise PlainSyntaxError(
|
|
478
504
|
f"Plain syntax error: Syntax error at line {token.line_number}: Invalid specification heading (`{specification_heading}`). Allowed headings: {', '.join(plain_spec.ALLOWED_SPECIFICATION_HEADINGS)}"
|
|
@@ -511,6 +537,22 @@ def parse_plain_source( # noqa: C901
|
|
|
511
537
|
f"Plain syntax error: Syntax error at line {token.line_number}: Invalid source structure (`{token_text}`)"
|
|
512
538
|
)
|
|
513
539
|
|
|
540
|
+
# Acceptance tests are only allowed nested under functional specs. Reject them when
|
|
541
|
+
# they are nested under any other section (e.g. definitions, implementation reqs, test reqs).
|
|
542
|
+
for non_functional_heading in (
|
|
543
|
+
plain_spec.DEFINITIONS,
|
|
544
|
+
plain_spec.NON_FUNCTIONAL_REQUIREMENTS,
|
|
545
|
+
plain_spec.TEST_REQUIREMENTS,
|
|
546
|
+
):
|
|
547
|
+
section = plain_source.get(non_functional_heading)
|
|
548
|
+
if section is None:
|
|
549
|
+
continue
|
|
550
|
+
nested_acceptance_test_heading = _find_acceptance_test_heading(section)
|
|
551
|
+
if nested_acceptance_test_heading is not None:
|
|
552
|
+
raise PlainSyntaxError(
|
|
553
|
+
f"Plain syntax error: Syntax error at line {nested_acceptance_test_heading.line_number}: {plain_spec.ACCEPTANCE_TEST_HEADING} heading should be nested under specific functional spec."
|
|
554
|
+
)
|
|
555
|
+
|
|
514
556
|
if plain_source[plain_spec.DEFINITIONS] is not None:
|
|
515
557
|
with PlainRenderer() as renderer:
|
|
516
558
|
for token in plain_source[plain_spec.DEFINITIONS].children:
|
|
@@ -536,6 +578,16 @@ def read_module_plain_source(module_name: str, template_dirs: list[str]) -> str:
|
|
|
536
578
|
plain_source_text = file_utils.open_from(template_dirs, module_name + PLAIN_SOURCE_FILE_EXTENSION)
|
|
537
579
|
if plain_source_text is None:
|
|
538
580
|
raise ModuleDoesNotExistError(f"Module does not exist ({module_name}).")
|
|
581
|
+
|
|
582
|
+
blob = find_large_base64_blob(plain_source_text)
|
|
583
|
+
if blob is not None:
|
|
584
|
+
raise UnsupportedBase64Content(
|
|
585
|
+
f"Module '{module_name}' contains a base64-encoded blob ({len(blob)} characters) "
|
|
586
|
+
"inlined in the specification. This is not supported."
|
|
587
|
+
"Remove the base64 data from the .plain file or if necessary,"
|
|
588
|
+
"include the binary file path in the specification."
|
|
589
|
+
)
|
|
590
|
+
|
|
539
591
|
return plain_source_text
|
|
540
592
|
|
|
541
593
|
|
|
@@ -25,6 +25,15 @@ ALLOWED_SPECIFICATION_HEADINGS = [
|
|
|
25
25
|
ALLOWED_IMPORT_SPECIFICATION_HEADINGS = [DEFINITIONS, NON_FUNCTIONAL_REQUIREMENTS, TEST_REQUIREMENTS]
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
def has_acceptance_tests(plain_source_tree) -> bool:
|
|
29
|
+
"""Return True if any functional requirement in the plain source defines acceptance tests."""
|
|
30
|
+
functional_requirements = plain_source_tree.get(FUNCTIONAL_REQUIREMENTS) or []
|
|
31
|
+
for functional_requirement in functional_requirements:
|
|
32
|
+
if functional_requirement.get(ACCEPTANCE_TESTS):
|
|
33
|
+
return True
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
|
|
28
37
|
def collect_specification_linked_resources(specification, specification_heading, linked_resources_list):
|
|
29
38
|
linked_resources = []
|
|
30
39
|
if "linked_resources" in specification:
|
|
@@ -15,10 +15,10 @@ classifiers = [
|
|
|
15
15
|
"Topic :: Software Development :: Code Generators",
|
|
16
16
|
]
|
|
17
17
|
dependencies = [
|
|
18
|
-
"python-liquid2==0.
|
|
18
|
+
"python-liquid2==0.4.0",
|
|
19
19
|
"mistletoe==1.3.0",
|
|
20
|
-
"requests==2.34.
|
|
21
|
-
"tiktoken==0.
|
|
20
|
+
"requests==2.34.2",
|
|
21
|
+
"tiktoken==0.13.0",
|
|
22
22
|
"PyYAML==6.0.3",
|
|
23
23
|
"gitpython==3.1.50",
|
|
24
24
|
"transitions==0.9.3",
|
|
@@ -26,16 +26,16 @@ dependencies = [
|
|
|
26
26
|
"rich==15.0.0",
|
|
27
27
|
"python-frontmatter==1.3.0",
|
|
28
28
|
"networkx==3.6.1",
|
|
29
|
-
"sentry-sdk==2.
|
|
29
|
+
"sentry-sdk==2.63.0",
|
|
30
30
|
]
|
|
31
31
|
|
|
32
32
|
[project.optional-dependencies]
|
|
33
33
|
dev = [
|
|
34
|
-
"pytest==
|
|
34
|
+
"pytest==9.1.1",
|
|
35
35
|
"flake8==7.3.0",
|
|
36
36
|
"black==26.3.1",
|
|
37
37
|
"isort==5.13.2",
|
|
38
|
-
"mypy==1.
|
|
38
|
+
"mypy==2.1.0",
|
|
39
39
|
]
|
|
40
40
|
|
|
41
41
|
[project.scripts]
|
|
@@ -9,12 +9,17 @@ from plain2code_exceptions import InternalClientError
|
|
|
9
9
|
from render_machine.actions.base_action import BaseAction
|
|
10
10
|
from render_machine.implementation_code_helpers import ImplementationCodeHelpers
|
|
11
11
|
from render_machine.render_context import RenderContext
|
|
12
|
-
from render_machine.render_types import TestExecutionPhase
|
|
12
|
+
from render_machine.render_types import RenderError, TestExecutionPhase
|
|
13
|
+
|
|
14
|
+
MAX_CONFORMANCE_TEST_FIX_ATTEMPTS = 20
|
|
15
|
+
MAX_CONFORMANCE_TEST_RERENDER_ATTEMPTS = 1
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
class FixConformanceTest(BaseAction):
|
|
16
19
|
IMPLEMENTATION_CODE_NOT_UPDATED = "implementation_code_not_updated"
|
|
17
20
|
IMPLEMENTATION_CODE_UPDATED = "implementation_code_updated"
|
|
21
|
+
LIMIT_EXCEEDED_OUTCOME = "conformance_test_fix_limit_exceeded"
|
|
22
|
+
REGENERATE_CONFORMANCE_TESTS_OUTCOME = "regenerate_conformance_tests"
|
|
18
23
|
|
|
19
24
|
ISSUE_REASON_CODE_CONFORMANCE_TESTS = 0
|
|
20
25
|
ISSUE_REASON_CODE_IMPLEMENTATION_CODE = 1
|
|
@@ -22,6 +27,23 @@ class FixConformanceTest(BaseAction):
|
|
|
22
27
|
ISSUE_REASON_CODE_CONFLICTING_ACCEPTANCE_TESTS = 3
|
|
23
28
|
|
|
24
29
|
def execute(self, render_context: RenderContext, previous_action_payload: Any | None):
|
|
30
|
+
ctx = render_context.conformance_tests_running_context
|
|
31
|
+
ctx.fix_attempts += 1
|
|
32
|
+
|
|
33
|
+
if ctx.fix_attempts >= MAX_CONFORMANCE_TEST_FIX_ATTEMPTS:
|
|
34
|
+
if ctx.conformance_tests_render_attempts >= MAX_CONFORMANCE_TEST_RERENDER_ATTEMPTS:
|
|
35
|
+
error_msg = f"The renderer was unable to produce an implementation that passes conformance tests for functionality '{render_context.frid_context.frid}' after many attempts. Please review and rewrite the specification. (Render ID: {render_context.run_state.render_id})"
|
|
36
|
+
render_context.last_error_message = error_msg
|
|
37
|
+
return (
|
|
38
|
+
self.LIMIT_EXCEEDED_OUTCOME,
|
|
39
|
+
RenderError.encode(message=error_msg).to_payload(),
|
|
40
|
+
)
|
|
41
|
+
else:
|
|
42
|
+
ctx.regenerating_conformance_tests = True
|
|
43
|
+
return self.REGENERATE_CONFORMANCE_TESTS_OUTCOME, None
|
|
44
|
+
|
|
45
|
+
console.info(f"Running conformance tests attempt {ctx.fix_attempts + 1}.")
|
|
46
|
+
|
|
25
47
|
console.info(
|
|
26
48
|
f"Fixing conformance test for functionality {render_context.conformance_tests_running_context.current_testing_frid} in module {render_context.conformance_tests_running_context.current_testing_module_name}."
|
|
27
49
|
)
|
|
@@ -12,6 +12,12 @@ class PrepareTestingEnvironment(BaseAction):
|
|
|
12
12
|
FAILED_OUTCOME = "testing_environment_preparation_failed"
|
|
13
13
|
|
|
14
14
|
def execute(self, render_context: RenderContext, _previous_action_payload: Any | None):
|
|
15
|
+
if (
|
|
16
|
+
render_context.prepare_environment_script is None
|
|
17
|
+
or not render_context.conformance_tests_running_context.should_prepare_testing_environment
|
|
18
|
+
):
|
|
19
|
+
return self.SUCCESSFUL_OUTCOME, None
|
|
20
|
+
|
|
15
21
|
console.info(
|
|
16
22
|
f"Running testing environment preparation script {render_context.prepare_environment_script} for build folder {render_context.build_folder}."
|
|
17
23
|
)
|
|
@@ -5,13 +5,31 @@ from plain2code_console import console
|
|
|
5
5
|
from render_machine.actions.base_action import BaseAction
|
|
6
6
|
from render_machine.implementation_code_helpers import ImplementationCodeHelpers
|
|
7
7
|
from render_machine.render_context import RenderContext
|
|
8
|
+
from render_machine.render_types import RenderError
|
|
9
|
+
|
|
10
|
+
MAX_REFACTORING_ITERATIONS = 5
|
|
8
11
|
|
|
9
12
|
|
|
10
13
|
class RefactorCode(BaseAction):
|
|
11
14
|
SUCCESSFUL_OUTCOME = "refactoring_successful"
|
|
12
15
|
NO_FILES_REFACTORED_OUTCOME = "no_files_refactored"
|
|
16
|
+
ITERATION_LIMIT_EXCEEDED_OUTCOME = "refactoring_iteration_limit_exceeded"
|
|
13
17
|
|
|
14
18
|
def execute(self, render_context: RenderContext, _previous_action_payload: Any | None):
|
|
19
|
+
if render_context.frid_context.refactoring_iteration == 0:
|
|
20
|
+
console.info("Refactoring the generated code...")
|
|
21
|
+
|
|
22
|
+
render_context.frid_context.refactoring_iteration += 1
|
|
23
|
+
|
|
24
|
+
if render_context.frid_context.refactoring_iteration >= MAX_REFACTORING_ITERATIONS:
|
|
25
|
+
error_message = "Refactoring iterations limit of {MAX_REFACTORING_ITERATIONS} reached for functionality {render_context.frid_context.frid}."
|
|
26
|
+
render_context.last_error_message = error_message
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
self.ITERATION_LIMIT_EXCEEDED_OUTCOME,
|
|
30
|
+
RenderError.encode(message=error_message).to_payload(),
|
|
31
|
+
)
|
|
32
|
+
|
|
15
33
|
existing_files, existing_files_content = ImplementationCodeHelpers.fetch_existing_files(
|
|
16
34
|
render_context.build_folder
|
|
17
35
|
)
|
|
@@ -16,8 +16,22 @@ MAX_CODE_GENERATION_RETRIES = 2
|
|
|
16
16
|
class RenderFunctionalRequirement(BaseAction):
|
|
17
17
|
SUCCESSFUL_OUTCOME = "code_and_unit_tests_generated"
|
|
18
18
|
FUNCTIONAL_REQUIREMENT_TOO_COMPLEX_OUTCOME = "functional_requirement_too_complex"
|
|
19
|
+
ITERATION_LIMIT_EXCEEDED_OUTCOME = "frid_iteration_limit_exceeded"
|
|
19
20
|
|
|
20
21
|
def execute(self, render_context: RenderContext, _previous_action_payload: Any | None):
|
|
22
|
+
if render_context.frid_context.functional_requirement_render_attempts >= MAX_CODE_GENERATION_RETRIES:
|
|
23
|
+
error_msg = f"Unittests could not be fixed after rendering the functionality {render_context.frid_context.frid} for the {MAX_CODE_GENERATION_RETRIES} times."
|
|
24
|
+
render_context.last_error_message = error_msg
|
|
25
|
+
return self.ITERATION_LIMIT_EXCEEDED_OUTCOME, None
|
|
26
|
+
|
|
27
|
+
render_context.frid_context.functional_requirement_render_attempts += 1
|
|
28
|
+
|
|
29
|
+
if render_context.frid_context.functional_requirement_render_attempts > 1:
|
|
30
|
+
console.info(
|
|
31
|
+
f"Unittests could not be fixed after rendering the functionality. "
|
|
32
|
+
f"Restarting rendering functionality {render_context.frid_context.frid} from scratch."
|
|
33
|
+
)
|
|
34
|
+
|
|
21
35
|
render_utils.revert_changes_for_frid(render_context)
|
|
22
36
|
existing_files, existing_files_content = ImplementationCodeHelpers.fetch_existing_files(
|
|
23
37
|
render_context.build_folder
|
|
@@ -48,7 +62,7 @@ class RenderFunctionalRequirement(BaseAction):
|
|
|
48
62
|
render_context.run_state,
|
|
49
63
|
)
|
|
50
64
|
except FunctionalRequirementTooComplex as e:
|
|
51
|
-
error_message = f"The functionality:\n{render_context.frid_context.functional_requirement_text}\n is too complex to be implemented. Please break down the functionality into smaller parts
|
|
65
|
+
error_message = f"The functionality:\n{render_context.frid_context.functional_requirement_text}\n is too complex to be implemented. Please break down the functionality into smaller parts."
|
|
52
66
|
if e.proposed_breakdown:
|
|
53
67
|
error_message += "\nProposed breakdown:"
|
|
54
68
|
for _, part in e.proposed_breakdown["functional_requirements"].items():
|