codeplain 0.3.12.dev25__tar.gz → 0.3.12.dev26__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.12.dev25 → codeplain-0.3.12.dev26}/PKG-INFO +1 -1
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/docs/plain2code_cli.md +2 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/plain2code.py +20 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/plain2code_arguments.py +2 -1
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_render_finished_report.py +3 -0
- codeplain-0.3.12.dev26/tests/test_terminal_attachment.py +295 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/.claude/settings.json +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/.flake8 +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/.github/dependabot.yml +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/.github/workflows/e2e.yml +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/.github/workflows/lint-and-test.yml +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/.github/workflows/lint-powershell.yml +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/.github/workflows/nofity-slack-on-main-merge.yml +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/.github/workflows/publish-dev-to-pypi.yml +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/.github/workflows/publish-install-script.yml +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/.github/workflows/publish-to-pypi.yml +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/.gitignore +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/.vscode/launch.json +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/.vscode/settings.json +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/CLAUDE.md +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/CONTRIBUTING.md +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/LICENSE +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/README.md +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/change_detection.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/cli_output/__init__.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/cli_output/dry_run.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/cli_output/render_summary.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/cli_output/status.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/codeplain_REST_api.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/concept_utils.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/config/__init__.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/config/system_config.yaml +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/conformance_fix_journal.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/diff_utils.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/docs/generate_cli.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/docs/starting_a_plain_project_from_scratch.md +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/event_bus.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_golang/config.yaml +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_golang/harness_tests/hello_world_test.go +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_golang/hello_world_golang.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_golang/run.sh +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_python/config.yaml +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_python/harness_tests/hello_world_display/test_hello_world.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_python/hello_world_python.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_python/run.sh +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_react/config.yaml +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress/e2e/hello_world.cy.ts +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress/support/e2e.ts +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress.config.ts +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_react/harness_tests/hello_world_display/package.json +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_react/harness_tests/hello_world_display/tsconfig.json +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_react/hello_world_react.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_react/run.sh +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/run.sh +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/file_utils.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/git_preflight.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/git_utils.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/install/bash/examples.sh +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/install/bash/install.sh +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/install/bash/walkthrough.sh +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/install/powershell/examples.ps1 +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/install/powershell/install.ps1 +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/install/powershell/walkthrough.ps1 +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/memory_management.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/metadata_utils.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/module_renderer.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/partial_rendering.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/path_resolution.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/plain2code_cli.md +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/plain2code_console.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/plain2code_events.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/plain2code_exceptions.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/plain2code_logger.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/plain2code_nodes.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/plain2code_read_config.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/plain2code_state.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/plain2code_telemetry.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/plain2code_utils.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/plain_file.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/plain_modules.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/plain_spec.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/pyproject.toml +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/pytest.ini +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/__init__.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/analyze_specification_ambiguity.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/base_action.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/commit_conformance_tests_changes.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/commit_implementation_code_changes.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/create_dist.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/distill_conformance_test_memory.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/exit_with_error.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/finish_functional_requirement.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/fix_conformance_test.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/fix_unit_tests.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/prepare_repositories.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/prepare_testing_environment.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/refactor_code.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/render_conformance_tests.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/render_functional_requirement.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/run_conformance_tests.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/run_unit_tests.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/summarize_conformance_tests.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/code_renderer.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/conformance_tests.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/implementation_code_helpers.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/render_context.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/render_types.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/render_utils.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/state_machine_config.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/states.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/triggers.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/requirements.txt +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/resources/codeplain_overview.png +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/resources/plain_example.png +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/standard_template_library/golang-console-app-template.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/standard_template_library/python-console-app-template.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/standard_template_library/typescript-react-app-boilerplate.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/standard_template_library/typescript-react-app-template.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/system_config.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/test_scripts/run_conformance_tests_cypress.ps1 +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/test_scripts/run_conformance_tests_cypress.sh +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/test_scripts/run_conformance_tests_golang.ps1 +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/test_scripts/run_conformance_tests_golang.sh +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/test_scripts/run_conformance_tests_python.ps1 +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/test_scripts/run_conformance_tests_python.sh +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/test_scripts/run_unittests_flutter.ps1 +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/test_scripts/run_unittests_flutter.sh +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/test_scripts/run_unittests_golang.ps1 +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/test_scripts/run_unittests_golang.sh +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/test_scripts/run_unittests_python.ps1 +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/test_scripts/run_unittests_python.sh +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/test_scripts/run_unittests_react.ps1 +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/test_scripts/run_unittests_react.sh +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/__init__.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/conftest.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/acceptance_tests_warning/main_requiring_acceptance_tests.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/acceptance_tests_warning/required_with_acceptance_tests.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/imports/circular_imports_1.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/imports/circular_imports_2.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/imports/circular_imports_main.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/imports/diamond_import_1.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/imports/diamond_import_2.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/imports/diamond_import_common.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/imports/diamond_imports_main.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/imports/import_with_functionalities.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/imports/imports_module_with_functionalities.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/imports/non_existent_import.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/partial_rendering/pr_code_var.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/partial_rendering/pr_implement.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/partial_rendering/pr_leaf.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/partial_rendering/pr_middle.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/partial_rendering/pr_root.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/partial_rendering/pr_solo.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfile/add_new_task_modal_specification.yaml +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfile/duplicate_specification_heading.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfile/empty_functional_specs_section.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfile/invalid_specification_order.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfile/missing_non_functional_requirements.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfile/no_functional_specs_section.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfile/plain_source_with_absolute_link.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfile/plain_source_with_missing_link.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfile/plain_source_with_url_link.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfile/task_list_ui_specification.yaml +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfile/task_manager_with_reference_links.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfile/without_non_functional_requirement.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/concept_validation_acceptance_tests.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/concept_validation_acceptance_tests_nondefined.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/concept_validation_defined_nondefined.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/concept_validation_defined_nondefined_2.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/concept_validation_definition.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/concept_validation_noconcepts.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/concept_validation_nonconcept.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/concept_validation_nondefined.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/concept_validation_redefinition.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/concept_validation_several_concepts.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/concept_validation_valid.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/cyclic_definitions.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/exported_concepts.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/exported_concepts_base.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/exported_concepts_defaults_exported.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/exported_concepts_example.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/exported_concepts_imported_exported.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/exported_concepts_inherited_reexported.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/exported_concepts_malformed.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/exported_concepts_missing_definition.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/exported_concepts_missing_definition_example.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/exported_concepts_multiple_per_bullet.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/exported_concepts_multiple_per_bullet_undefined.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/exported_concepts_nested.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/exported_concepts_nested_example.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/exported_concepts_no_definitions.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/exported_concepts_not_declared.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/exported_concepts_not_declared_example.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/exported_concepts_transitive_example.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/exported_concepts_transitive_l1.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/exported_concepts_transitive_l2.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/plain_file_parser_with_comments.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/plain_file_with_comments_indented.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/regular_plain_source.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/required_concepts.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/required_concepts_defs.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/required_concepts_example.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/required_concepts_l1.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/required_concepts_l2.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/required_concepts_missing.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/required_concepts_module.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/required_concepts_partial.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/required_concepts_partial_duplicate.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/requires_without_definitions_example.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/topological_sort.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/plainfileparser/topological_sort_not_referenced.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/requires/circular_requires_main.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/requires/circular_requires_sub.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/requires/diamond_requires_1.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/requires/diamond_requires_2.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/requires/diamond_requires_common.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/requires/diamond_requires_main.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/requires/independent_requires_1.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/requires/independent_requires_2.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/requires/independent_requires_main.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/requires/non_existent_require.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/requires/normal_requires_1.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/requires/normal_requires_2.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/requires/normal_requires_common.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/requires/normal_requires_main.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/sample_base64_image.txt +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/simple.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/templates/block_level_include.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/templates/code_variables.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/templates/header.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/templates/implement.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/templates/implement_2.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/templates/template_include.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/data/templates/test_hardest_problem.plain +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/e2e/Dockerfile +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/e2e/conftest.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/e2e/test_hello_world_python.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/e2e/test_hello_world_python_windows.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_arg_provenance.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_change_detection.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_cli_output.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_conformance_fix_journal.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_console_log_styles.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_distill_conformance_test_memory.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_dry_run_link_validation.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_file_utils.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_fix_unit_tests_conformance_context.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_folder_path_resolution.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_git_preflight.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_git_utils.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_headless_logging.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_imports.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_log_file_name_resolution.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_logging_config_path_resolution.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_partial_rendering.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_path_resolution.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_plain2code.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_plain2code_state.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_plain2code_utils.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_plain_modules.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_plainfile.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_plainfileparser.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_plainspec.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_prepare_repositories_layout.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_requires.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_resolve_config_file.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_script_path_resolution.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_system_config.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_telemetry.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_tui_components.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_tui_usage.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tests/test_usage_summary.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tui/__init__.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tui/components.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tui/models.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tui/plain2code_tui.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tui/plain_module_render_choice_tui.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tui/spinner.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tui/state_handlers.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tui/styles.css +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/tui/widget_helpers.py +0 -0
- {codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/usage_summary.py +0 -0
|
@@ -121,6 +121,8 @@ options:
|
|
|
121
121
|
--headless Run in headless mode: no TUI. Progress is written to
|
|
122
122
|
the log file and streamed to stdout as it happens; the
|
|
123
123
|
specification text itself stays in the log file only.
|
|
124
|
+
Used automatically when stdin, stdout or stderr is not
|
|
125
|
+
a terminal.
|
|
124
126
|
--status Display account status including user information, API
|
|
125
127
|
key label, and rendering credits. Does not render any
|
|
126
128
|
code.
|
|
@@ -161,6 +161,17 @@ def setup_logging(
|
|
|
161
161
|
return logging.getLevelName(configured_log_level)
|
|
162
162
|
|
|
163
163
|
|
|
164
|
+
def attached_to_terminal() -> bool:
|
|
165
|
+
"""Whether a person at a terminal can see and drive the TUI.
|
|
166
|
+
|
|
167
|
+
The TUI reads keys from stdin and draws on stderr; the exit summary goes to
|
|
168
|
+
stdout. Started from a script or an agent, stdin is usually at end-of-file,
|
|
169
|
+
and Textual's input thread then reads EOF in a tight loop at 100% CPU while
|
|
170
|
+
a stopped render waits for an Enter nobody can press.
|
|
171
|
+
"""
|
|
172
|
+
return all(stream is not None and stream.isatty() for stream in (sys.stdin, sys.stdout, sys.stderr))
|
|
173
|
+
|
|
174
|
+
|
|
164
175
|
def _check_connection(codeplainAPI: codeplain_api.CodeplainAPI) -> Optional[str]:
|
|
165
176
|
"""Check API connectivity, validate API key and client version, and return the user email."""
|
|
166
177
|
response = codeplainAPI.connection_check(system_config.client_version)
|
|
@@ -473,12 +484,21 @@ def main(): # noqa: C901
|
|
|
473
484
|
|
|
474
485
|
run_state = RunState(spec_filename=args.filename, replay_with=args.replay_with)
|
|
475
486
|
|
|
487
|
+
# Without a terminal nobody can watch or drive the TUI, so render as --headless does:
|
|
488
|
+
# progress on stdout, and a non-zero exit status when the render stops.
|
|
489
|
+
no_terminal = not args.headless and not attached_to_terminal()
|
|
490
|
+
if no_terminal:
|
|
491
|
+
args.headless = True
|
|
492
|
+
|
|
476
493
|
if args.headless:
|
|
477
494
|
# Suppress Rich console output.
|
|
478
495
|
console.quiet = True
|
|
479
496
|
|
|
480
497
|
default_log_level = setup_logging(args, event_bus, run_state, args.log_to_file, args.log_file_name, args.headless)
|
|
481
498
|
|
|
499
|
+
if no_terminal:
|
|
500
|
+
console.info("No terminal attached, so rendering without the TUI, as with --headless.")
|
|
501
|
+
|
|
482
502
|
initialize_telemetry()
|
|
483
503
|
|
|
484
504
|
exc_info = None
|
|
@@ -428,7 +428,8 @@ def create_parser():
|
|
|
428
428
|
action="store_true",
|
|
429
429
|
default=False,
|
|
430
430
|
help="Run in headless mode: no TUI. Progress is written to the log file and streamed "
|
|
431
|
-
"to stdout as it happens; the specification text itself stays in the log file only."
|
|
431
|
+
"to stdout as it happens; the specification text itself stays in the log file only. "
|
|
432
|
+
"Used automatically when stdin, stdout or stderr is not a terminal.",
|
|
432
433
|
)
|
|
433
434
|
|
|
434
435
|
parser.add_argument(
|
|
@@ -122,6 +122,9 @@ def run_main(render_side_effect=None, api_key="test-key"):
|
|
|
122
122
|
render_side_effect(run_state)
|
|
123
123
|
|
|
124
124
|
with (
|
|
125
|
+
# The args ask for the TUI. Under pytest there is no terminal, which would
|
|
126
|
+
# otherwise switch the render to headless.
|
|
127
|
+
patch("plain2code.attached_to_terminal", return_value=True),
|
|
125
128
|
patch("plain2code.parse_arguments", return_value=make_args(api_key=api_key)),
|
|
126
129
|
patch("plain2code.file_utils.get_template_directories", return_value=["templates"]),
|
|
127
130
|
patch("plain2code.plain_modules.PlainModule", return_value=MagicMock(all_required_modules=[])),
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""How a render behaves with and without a terminal attached.
|
|
2
|
+
|
|
3
|
+
The TUI needs a person at a terminal: it reads keys from stdin, draws on stderr,
|
|
4
|
+
and the console summary goes to stdout. Started from a script or an agent
|
|
5
|
+
(`codeplain module.plain > render.log 2>&1 &`), stdin is at end-of-file.
|
|
6
|
+
Textual's input thread then reads EOF in a tight loop - millions of reads a
|
|
7
|
+
second - so the process pins a CPU core for the whole render, and a stopped
|
|
8
|
+
render waits for an Enter nobody can press. One such render stayed alive for
|
|
9
|
+
an hour and a half at ~100% CPU until it was killed.
|
|
10
|
+
|
|
11
|
+
So without a terminal codeplain runs as --headless does, and a stopped render
|
|
12
|
+
exits non-zero. With a terminal the TUI is kept, and waiting for Enter must
|
|
13
|
+
stay idle.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import struct
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
import textwrap
|
|
21
|
+
import threading
|
|
22
|
+
import time
|
|
23
|
+
from argparse import Namespace
|
|
24
|
+
from contextlib import ExitStack
|
|
25
|
+
from unittest.mock import MagicMock, patch
|
|
26
|
+
|
|
27
|
+
import pytest
|
|
28
|
+
|
|
29
|
+
import plain2code
|
|
30
|
+
|
|
31
|
+
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class FakeStream:
|
|
35
|
+
def __init__(self, is_terminal: bool):
|
|
36
|
+
self._is_terminal = is_terminal
|
|
37
|
+
|
|
38
|
+
def isatty(self) -> bool:
|
|
39
|
+
return self._is_terminal
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TestAttachedToTerminal:
|
|
43
|
+
@pytest.fixture
|
|
44
|
+
def streams(self, monkeypatch):
|
|
45
|
+
def set_streams(stdin=True, stdout=True, stderr=True):
|
|
46
|
+
for name, value in (("stdin", stdin), ("stdout", stdout), ("stderr", stderr)):
|
|
47
|
+
monkeypatch.setattr(sys, name, value if value is None else FakeStream(value))
|
|
48
|
+
|
|
49
|
+
return set_streams
|
|
50
|
+
|
|
51
|
+
def test_all_three_streams_on_a_terminal(self, streams):
|
|
52
|
+
streams()
|
|
53
|
+
|
|
54
|
+
assert plain2code.attached_to_terminal() is True
|
|
55
|
+
|
|
56
|
+
@pytest.mark.parametrize("stream", ["stdin", "stdout", "stderr"])
|
|
57
|
+
def test_any_stream_off_the_terminal(self, streams, stream):
|
|
58
|
+
"""stdin carries the keys, stderr the TUI, stdout the summary. Losing any
|
|
59
|
+
of them leaves a TUI nobody can drive or see."""
|
|
60
|
+
streams(**{stream: False})
|
|
61
|
+
|
|
62
|
+
assert plain2code.attached_to_terminal() is False
|
|
63
|
+
|
|
64
|
+
def test_a_closed_stream(self, streams):
|
|
65
|
+
"""sys.stdin is None when the process has no stdin at all."""
|
|
66
|
+
streams(stdin=None)
|
|
67
|
+
|
|
68
|
+
assert plain2code.attached_to_terminal() is False
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def make_args():
|
|
72
|
+
return Namespace(
|
|
73
|
+
version=False,
|
|
74
|
+
status=False,
|
|
75
|
+
full_plain=False,
|
|
76
|
+
dry_run=False,
|
|
77
|
+
api_key="test-key",
|
|
78
|
+
api="https://api.example.com",
|
|
79
|
+
filename="project.plain",
|
|
80
|
+
template_dir=None,
|
|
81
|
+
build_folder="build",
|
|
82
|
+
render_range=None,
|
|
83
|
+
render_from=None,
|
|
84
|
+
force_render=False,
|
|
85
|
+
headless=False,
|
|
86
|
+
replay_with=None,
|
|
87
|
+
log_to_file=False,
|
|
88
|
+
log_file_name="codeplain.log",
|
|
89
|
+
render_conformance_tests=False,
|
|
90
|
+
conformance_tests_script=None,
|
|
91
|
+
unittests_script=None,
|
|
92
|
+
prepare_environment_script=None,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def run_render(succeeded, terminal, render_state=None, render_choices=None):
|
|
97
|
+
"""Drive the real main() and render() up to the module renderer.
|
|
98
|
+
|
|
99
|
+
The module renderer stands in for the state machine: it decides how the
|
|
100
|
+
render ended. Everything the decision under test does not touch - the API,
|
|
101
|
+
parsing, logging, telemetry - is stubbed.
|
|
102
|
+
|
|
103
|
+
Returns the mocks for the two TUIs and the module renderer, the args main()
|
|
104
|
+
rendered with, and the exit status it asked for (None when it returned
|
|
105
|
+
normally).
|
|
106
|
+
"""
|
|
107
|
+
args = make_args()
|
|
108
|
+
module_renderer = MagicMock()
|
|
109
|
+
|
|
110
|
+
def make_module_renderer(codeplainAPI, plain_module, render_choice, render_range, args, run_state, *rest, **kw):
|
|
111
|
+
module_renderer.render_module.side_effect = lambda: run_state.set_render_succeeded(succeeded)
|
|
112
|
+
return module_renderer
|
|
113
|
+
|
|
114
|
+
progress_tui = MagicMock()
|
|
115
|
+
progress_tui.return_value.run.side_effect = lambda: progress_tui.call_args.kwargs["on_ready"]()
|
|
116
|
+
choice_tui = MagicMock()
|
|
117
|
+
|
|
118
|
+
patches = [
|
|
119
|
+
patch("plain2code.attached_to_terminal", return_value=terminal),
|
|
120
|
+
patch("plain2code.parse_arguments", return_value=args),
|
|
121
|
+
patch("plain2code.file_utils.get_template_directories", return_value=["templates"]),
|
|
122
|
+
patch("plain2code.plain_modules.PlainModule", return_value=MagicMock(all_required_modules=[])),
|
|
123
|
+
patch("plain2code.setup_logging", return_value="INFO"),
|
|
124
|
+
patch("plain2code.initialize_telemetry"),
|
|
125
|
+
patch("plain2code.stop_narrating"),
|
|
126
|
+
patch("plain2code.print_exit_summary"),
|
|
127
|
+
patch("plain2code.dump_crash_logs"),
|
|
128
|
+
patch("plain2code.capture_crash"),
|
|
129
|
+
patch("plain2code.codeplain_api.CodeplainAPI"),
|
|
130
|
+
patch("plain2code._check_connection", return_value="user@codeplain.ai"),
|
|
131
|
+
patch("plain2code.signal.signal"),
|
|
132
|
+
patch("plain2code.warn_if_acceptance_tests_without_conformance_script"),
|
|
133
|
+
patch("plain2code.get_plain_module_render_state", return_value=render_state),
|
|
134
|
+
patch("plain2code.get_render_choices", return_value=render_choices),
|
|
135
|
+
patch("plain2code.ModuleRenderer", side_effect=make_module_renderer),
|
|
136
|
+
patch("plain2code.Plain2CodeTUI", progress_tui),
|
|
137
|
+
patch("plain2code.PlainModuleRenderChoiceTUI", choice_tui),
|
|
138
|
+
patch.object(plain2code.console, "quiet", plain2code.console.quiet),
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
exit_status = None
|
|
142
|
+
with ExitStack() as stack:
|
|
143
|
+
for each in patches:
|
|
144
|
+
stack.enter_context(each)
|
|
145
|
+
try:
|
|
146
|
+
plain2code.main()
|
|
147
|
+
except SystemExit as e:
|
|
148
|
+
exit_status = e.code
|
|
149
|
+
|
|
150
|
+
return Namespace(
|
|
151
|
+
progress_tui=progress_tui,
|
|
152
|
+
choice_tui=choice_tui,
|
|
153
|
+
module_renderer=module_renderer,
|
|
154
|
+
exit_status=exit_status,
|
|
155
|
+
args=args,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class TestRenderWithoutATerminal:
|
|
160
|
+
def test_the_tui_never_starts(self):
|
|
161
|
+
result = run_render(succeeded=False, terminal=False)
|
|
162
|
+
|
|
163
|
+
result.progress_tui.assert_not_called()
|
|
164
|
+
result.module_renderer.render_module.assert_called_once()
|
|
165
|
+
|
|
166
|
+
def test_a_stopped_render_exits_non_zero(self):
|
|
167
|
+
"""The status --headless gives, so a script can tell it stopped."""
|
|
168
|
+
result = run_render(succeeded=False, terminal=False)
|
|
169
|
+
|
|
170
|
+
assert result.exit_status == 1
|
|
171
|
+
|
|
172
|
+
def test_a_completed_render_exits_cleanly(self):
|
|
173
|
+
result = run_render(succeeded=True, terminal=False)
|
|
174
|
+
|
|
175
|
+
assert result.exit_status is None
|
|
176
|
+
|
|
177
|
+
def test_says_why_it_runs_without_the_tui(self, caplog):
|
|
178
|
+
caplog.set_level("INFO", logger="codeplain")
|
|
179
|
+
|
|
180
|
+
run_render(succeeded=True, terminal=False)
|
|
181
|
+
|
|
182
|
+
assert "No terminal attached" in caplog.text
|
|
183
|
+
|
|
184
|
+
def test_never_asks_where_to_resume(self):
|
|
185
|
+
"""The choice prompt is a TUI too, and would wait for a key just the same."""
|
|
186
|
+
destructive = MagicMock(is_destructive=True)
|
|
187
|
+
choices = {"restart": destructive, "other": MagicMock(), "quit": MagicMock()}
|
|
188
|
+
|
|
189
|
+
result = run_render(succeeded=True, terminal=False, render_state=MagicMock(), render_choices=choices)
|
|
190
|
+
|
|
191
|
+
result.choice_tui.assert_not_called()
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class TestRenderWithATerminal:
|
|
195
|
+
def test_the_tui_still_runs(self):
|
|
196
|
+
result = run_render(succeeded=True, terminal=True)
|
|
197
|
+
|
|
198
|
+
result.progress_tui.assert_called_once()
|
|
199
|
+
assert result.args.headless is False
|
|
200
|
+
|
|
201
|
+
def test_says_nothing_about_a_missing_terminal(self, caplog):
|
|
202
|
+
caplog.set_level("INFO", logger="codeplain")
|
|
203
|
+
|
|
204
|
+
run_render(succeeded=True, terminal=True)
|
|
205
|
+
|
|
206
|
+
assert "No terminal attached" not in caplog.text
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
IDLE_SECONDS = 2.0
|
|
210
|
+
|
|
211
|
+
# Drives the real TUI to the stopped state, then measures the CPU the process
|
|
212
|
+
# spends over IDLE_SECONDS while it waits for Enter.
|
|
213
|
+
STOPPED_TUI = textwrap.dedent("""
|
|
214
|
+
import os, sys, threading, time
|
|
215
|
+
|
|
216
|
+
from event_bus import EventBus
|
|
217
|
+
from plain2code_events import RenderFailed
|
|
218
|
+
from plain2code_state import RunState
|
|
219
|
+
from tui.plain2code_tui import Plain2CodeTUI
|
|
220
|
+
|
|
221
|
+
report_path, idle_seconds = sys.argv[1], float(sys.argv[2])
|
|
222
|
+
event_bus = EventBus()
|
|
223
|
+
|
|
224
|
+
def stop_then_measure():
|
|
225
|
+
event_bus.publish(RenderFailed(error_message="unit tests still failing"))
|
|
226
|
+
start = time.process_time()
|
|
227
|
+
time.sleep(idle_seconds)
|
|
228
|
+
with open(report_path + ".tmp", "w") as report:
|
|
229
|
+
report.write(str(time.process_time() - start))
|
|
230
|
+
os.replace(report_path + ".tmp", report_path)
|
|
231
|
+
|
|
232
|
+
Plain2CodeTUI(
|
|
233
|
+
event_bus=event_bus,
|
|
234
|
+
run_state=RunState(spec_filename="x.plain"),
|
|
235
|
+
on_ready=threading.Thread(target=stop_then_measure, daemon=True).start,
|
|
236
|
+
render_id="test",
|
|
237
|
+
unittests_script=None,
|
|
238
|
+
conformance_tests_script=None,
|
|
239
|
+
prepare_environment_script=None,
|
|
240
|
+
state_machine_version="0.0.0",
|
|
241
|
+
css_path="styles.css",
|
|
242
|
+
).run()
|
|
243
|
+
""")
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@pytest.mark.skipif(sys.platform == "win32", reason="needs a POSIX pseudo-terminal")
|
|
247
|
+
def test_a_stopped_render_on_a_terminal_waits_for_enter_without_spinning(tmp_path):
|
|
248
|
+
# Unix-only modules: imported here so Windows can still collect the other tests in this file.
|
|
249
|
+
import fcntl
|
|
250
|
+
import termios
|
|
251
|
+
|
|
252
|
+
controller, terminal = os.openpty()
|
|
253
|
+
fcntl.ioctl(terminal, termios.TIOCSWINSZ, struct.pack("HHHH", 30, 100, 0, 0))
|
|
254
|
+
report = tmp_path / "cpu_seconds"
|
|
255
|
+
|
|
256
|
+
process = subprocess.Popen(
|
|
257
|
+
[sys.executable, "-c", STOPPED_TUI, str(report), str(IDLE_SECONDS)],
|
|
258
|
+
stdin=terminal,
|
|
259
|
+
stdout=terminal,
|
|
260
|
+
stderr=terminal,
|
|
261
|
+
cwd=REPO_ROOT,
|
|
262
|
+
env={**os.environ, "TERM": "xterm-256color"},
|
|
263
|
+
start_new_session=True,
|
|
264
|
+
)
|
|
265
|
+
os.close(terminal)
|
|
266
|
+
|
|
267
|
+
def drain():
|
|
268
|
+
# The TUI blocks once the terminal buffer fills, so keep reading it.
|
|
269
|
+
try:
|
|
270
|
+
while os.read(controller, 65536):
|
|
271
|
+
pass
|
|
272
|
+
except OSError:
|
|
273
|
+
pass
|
|
274
|
+
|
|
275
|
+
threading.Thread(target=drain, daemon=True).start()
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
deadline = time.monotonic() + 60
|
|
279
|
+
while not report.exists():
|
|
280
|
+
assert process.poll() is None, "the TUI exited before Enter was pressed"
|
|
281
|
+
assert time.monotonic() < deadline, "the TUI never reached the stopped state"
|
|
282
|
+
time.sleep(0.1)
|
|
283
|
+
cpu_seconds = float(report.read_text())
|
|
284
|
+
|
|
285
|
+
os.write(controller, b"\r")
|
|
286
|
+
exit_status = process.wait(timeout=15)
|
|
287
|
+
finally:
|
|
288
|
+
if process.poll() is None:
|
|
289
|
+
process.kill()
|
|
290
|
+
process.wait()
|
|
291
|
+
os.close(controller)
|
|
292
|
+
|
|
293
|
+
# A busy loop burns about one CPU-second per second of waiting.
|
|
294
|
+
assert cpu_seconds < IDLE_SECONDS / 4
|
|
295
|
+
assert exit_status == 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/.github/workflows/nofity-slack-on-main-merge.yml
RENAMED
|
File without changes
|
|
File without changes
|
{codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/.github/workflows/publish-install-script.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/docs/starting_a_plain_project_from_scratch.md
RENAMED
|
File without changes
|
|
File without changes
|
{codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_golang/config.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_golang/run.sh
RENAMED
|
File without changes
|
{codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_python/config.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_python/run.sh
RENAMED
|
File without changes
|
{codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/examples/example_hello_world_react/config.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/fix_conformance_test.py
RENAMED
|
File without changes
|
|
File without changes
|
{codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/prepare_repositories.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/render_conformance_tests.py
RENAMED
|
File without changes
|
|
File without changes
|
{codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/actions/run_conformance_tests.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codeplain-0.3.12.dev25 → codeplain-0.3.12.dev26}/render_machine/implementation_code_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|