codeplain 0.3.11.dev11__tar.gz → 0.3.11.dev13__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/.flake8 +2 -0
  2. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/PKG-INFO +1 -1
  3. codeplain-0.3.11.dev13/git_preflight.py +41 -0
  4. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/plain_modules.py +6 -6
  5. codeplain-0.3.11.dev13/tests/test_git_preflight.py +98 -0
  6. codeplain-0.3.11.dev13/tests/test_tui_components.py +88 -0
  7. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tui/components.py +13 -9
  8. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tui/widget_helpers.py +11 -4
  9. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/.claude/settings.json +0 -0
  10. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/.github/dependabot.yml +0 -0
  11. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/.github/workflows/e2e.yml +0 -0
  12. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/.github/workflows/lint-and-test.yml +0 -0
  13. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/.github/workflows/lint-powershell.yml +0 -0
  14. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/.github/workflows/nofity-slack-on-main-merge.yml +0 -0
  15. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/.github/workflows/publish-dev-to-pypi.yml +0 -0
  16. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/.github/workflows/publish-install-script.yml +0 -0
  17. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/.github/workflows/publish-to-pypi.yml +0 -0
  18. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/.gitignore +0 -0
  19. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/.vscode/launch.json +0 -0
  20. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/.vscode/settings.json +0 -0
  21. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/CLAUDE.md +0 -0
  22. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/CONTRIBUTING.md +0 -0
  23. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/LICENSE +0 -0
  24. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/README.md +0 -0
  25. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/change_detection.py +0 -0
  26. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/cli_output/__init__.py +0 -0
  27. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/cli_output/dry_run.py +0 -0
  28. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/cli_output/render_summary.py +0 -0
  29. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/cli_output/status.py +0 -0
  30. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/codeplain_REST_api.py +0 -0
  31. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/concept_utils.py +0 -0
  32. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/config/__init__.py +0 -0
  33. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/config/system_config.yaml +0 -0
  34. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/conformance_fix_journal.py +0 -0
  35. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/diff_utils.py +0 -0
  36. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/docs/generate_cli.py +0 -0
  37. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/docs/plain2code_cli.md +0 -0
  38. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/docs/starting_a_plain_project_from_scratch.md +0 -0
  39. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/event_bus.py +0 -0
  40. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/examples/example_hello_world_golang/config.yaml +0 -0
  41. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/examples/example_hello_world_golang/harness_tests/hello_world_test.go +0 -0
  42. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/examples/example_hello_world_golang/hello_world_golang.plain +0 -0
  43. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/examples/example_hello_world_golang/run.sh +0 -0
  44. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/examples/example_hello_world_python/config.yaml +0 -0
  45. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/examples/example_hello_world_python/harness_tests/hello_world_display/test_hello_world.py +0 -0
  46. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/examples/example_hello_world_python/hello_world_python.plain +0 -0
  47. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/examples/example_hello_world_python/run.sh +0 -0
  48. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/examples/example_hello_world_react/config.yaml +0 -0
  49. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress/e2e/hello_world.cy.ts +0 -0
  50. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress/support/e2e.ts +0 -0
  51. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress.config.ts +0 -0
  52. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/examples/example_hello_world_react/harness_tests/hello_world_display/package.json +0 -0
  53. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/examples/example_hello_world_react/harness_tests/hello_world_display/tsconfig.json +0 -0
  54. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/examples/example_hello_world_react/hello_world_react.plain +0 -0
  55. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/examples/example_hello_world_react/run.sh +0 -0
  56. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/examples/run.sh +0 -0
  57. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/file_utils.py +0 -0
  58. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/git_utils.py +0 -0
  59. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/install/bash/examples.sh +0 -0
  60. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/install/bash/install.sh +0 -0
  61. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/install/bash/walkthrough.sh +0 -0
  62. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/install/powershell/examples.ps1 +0 -0
  63. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/install/powershell/install.ps1 +0 -0
  64. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/install/powershell/walkthrough.ps1 +0 -0
  65. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/memory_management.py +0 -0
  66. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/metadata_utils.py +0 -0
  67. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/module_renderer.py +0 -0
  68. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/partial_rendering.py +0 -0
  69. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/path_resolution.py +0 -0
  70. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/plain2code.py +0 -0
  71. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/plain2code_arguments.py +0 -0
  72. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/plain2code_cli.md +0 -0
  73. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/plain2code_console.py +0 -0
  74. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/plain2code_events.py +0 -0
  75. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/plain2code_exceptions.py +0 -0
  76. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/plain2code_logger.py +0 -0
  77. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/plain2code_nodes.py +0 -0
  78. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/plain2code_read_config.py +0 -0
  79. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/plain2code_state.py +0 -0
  80. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/plain2code_telemetry.py +0 -0
  81. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/plain2code_utils.py +0 -0
  82. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/plain_file.py +0 -0
  83. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/plain_spec.py +0 -0
  84. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/pyproject.toml +0 -0
  85. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/pytest.ini +0 -0
  86. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/__init__.py +0 -0
  87. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/actions/analyze_specification_ambiguity.py +0 -0
  88. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/actions/base_action.py +0 -0
  89. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/actions/commit_conformance_tests_changes.py +0 -0
  90. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/actions/commit_implementation_code_changes.py +0 -0
  91. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/actions/create_dist.py +0 -0
  92. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/actions/distill_conformance_test_memory.py +0 -0
  93. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/actions/exit_with_error.py +0 -0
  94. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/actions/finish_functional_requirement.py +0 -0
  95. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/actions/fix_conformance_test.py +0 -0
  96. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/actions/fix_unit_tests.py +0 -0
  97. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/actions/prepare_repositories.py +0 -0
  98. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/actions/prepare_testing_environment.py +0 -0
  99. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/actions/refactor_code.py +0 -0
  100. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/actions/render_conformance_tests.py +0 -0
  101. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/actions/render_functional_requirement.py +0 -0
  102. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/actions/run_conformance_tests.py +0 -0
  103. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/actions/run_unit_tests.py +0 -0
  104. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/actions/summarize_conformance_tests.py +0 -0
  105. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/code_renderer.py +0 -0
  106. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/conformance_tests.py +0 -0
  107. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/implementation_code_helpers.py +0 -0
  108. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/render_context.py +0 -0
  109. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/render_types.py +0 -0
  110. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/render_utils.py +0 -0
  111. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/state_machine_config.py +0 -0
  112. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/states.py +0 -0
  113. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/render_machine/triggers.py +0 -0
  114. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/requirements.txt +0 -0
  115. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/resources/codeplain_overview.png +0 -0
  116. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/resources/plain_example.png +0 -0
  117. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/standard_template_library/golang-console-app-template.plain +0 -0
  118. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/standard_template_library/python-console-app-template.plain +0 -0
  119. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/standard_template_library/typescript-react-app-boilerplate.plain +0 -0
  120. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/standard_template_library/typescript-react-app-template.plain +0 -0
  121. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/system_config.py +0 -0
  122. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/test_scripts/run_conformance_tests_cypress.ps1 +0 -0
  123. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/test_scripts/run_conformance_tests_cypress.sh +0 -0
  124. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/test_scripts/run_conformance_tests_golang.ps1 +0 -0
  125. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/test_scripts/run_conformance_tests_golang.sh +0 -0
  126. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/test_scripts/run_conformance_tests_python.ps1 +0 -0
  127. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/test_scripts/run_conformance_tests_python.sh +0 -0
  128. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/test_scripts/run_unittests_flutter.ps1 +0 -0
  129. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/test_scripts/run_unittests_flutter.sh +0 -0
  130. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/test_scripts/run_unittests_golang.ps1 +0 -0
  131. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/test_scripts/run_unittests_golang.sh +0 -0
  132. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/test_scripts/run_unittests_python.ps1 +0 -0
  133. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/test_scripts/run_unittests_python.sh +0 -0
  134. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/test_scripts/run_unittests_react.ps1 +0 -0
  135. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/test_scripts/run_unittests_react.sh +0 -0
  136. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/__init__.py +0 -0
  137. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/conftest.py +0 -0
  138. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/acceptance_tests_warning/main_requiring_acceptance_tests.plain +0 -0
  139. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/acceptance_tests_warning/required_with_acceptance_tests.plain +0 -0
  140. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/imports/circular_imports_1.plain +0 -0
  141. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/imports/circular_imports_2.plain +0 -0
  142. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/imports/circular_imports_main.plain +0 -0
  143. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/imports/diamond_import_1.plain +0 -0
  144. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/imports/diamond_import_2.plain +0 -0
  145. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/imports/diamond_import_common.plain +0 -0
  146. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/imports/diamond_imports_main.plain +0 -0
  147. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/imports/import_with_functionalities.plain +0 -0
  148. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/imports/imports_module_with_functionalities.plain +0 -0
  149. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/imports/non_existent_import.plain +0 -0
  150. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/partial_rendering/pr_code_var.plain +0 -0
  151. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/partial_rendering/pr_implement.plain +0 -0
  152. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/partial_rendering/pr_leaf.plain +0 -0
  153. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/partial_rendering/pr_middle.plain +0 -0
  154. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/partial_rendering/pr_root.plain +0 -0
  155. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/partial_rendering/pr_solo.plain +0 -0
  156. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfile/add_new_task_modal_specification.yaml +0 -0
  157. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfile/duplicate_specification_heading.plain +0 -0
  158. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfile/empty_functional_specs_section.plain +0 -0
  159. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfile/invalid_specification_order.plain +0 -0
  160. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfile/missing_non_functional_requirements.plain +0 -0
  161. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfile/no_functional_specs_section.plain +0 -0
  162. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfile/plain_source_with_absolute_link.plain +0 -0
  163. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfile/plain_source_with_url_link.plain +0 -0
  164. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfile/task_list_ui_specification.yaml +0 -0
  165. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfile/task_manager_with_reference_links.plain +0 -0
  166. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfile/without_non_functional_requirement.plain +0 -0
  167. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/concept_validation_acceptance_tests.plain +0 -0
  168. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/concept_validation_acceptance_tests_nondefined.plain +0 -0
  169. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/concept_validation_defined_nondefined.plain +0 -0
  170. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/concept_validation_defined_nondefined_2.plain +0 -0
  171. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/concept_validation_definition.plain +0 -0
  172. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/concept_validation_noconcepts.plain +0 -0
  173. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/concept_validation_nonconcept.plain +0 -0
  174. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/concept_validation_nondefined.plain +0 -0
  175. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/concept_validation_redefinition.plain +0 -0
  176. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/concept_validation_several_concepts.plain +0 -0
  177. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/concept_validation_valid.plain +0 -0
  178. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/exported_concepts.plain +0 -0
  179. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/exported_concepts_base.plain +0 -0
  180. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/exported_concepts_example.plain +0 -0
  181. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/exported_concepts_missing.plain +0 -0
  182. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/exported_concepts_missing_example.plain +0 -0
  183. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/exported_concepts_nested.plain +0 -0
  184. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/exported_concepts_nested_example.plain +0 -0
  185. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/exported_concepts_transitive_example.plain +0 -0
  186. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/exported_concepts_transitive_l1.plain +0 -0
  187. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/exported_concepts_transitive_l2.plain +0 -0
  188. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/plain_file_parser_with_comments.plain +0 -0
  189. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/plain_file_with_comments_indented.plain +0 -0
  190. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/regular_plain_source.plain +0 -0
  191. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/required_concepts.plain +0 -0
  192. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/required_concepts_defs.plain +0 -0
  193. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/required_concepts_example.plain +0 -0
  194. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/required_concepts_l1.plain +0 -0
  195. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/required_concepts_l2.plain +0 -0
  196. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/required_concepts_missing.plain +0 -0
  197. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/required_concepts_module.plain +0 -0
  198. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/required_concepts_partial.plain +0 -0
  199. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/required_concepts_partial_duplicate.plain +0 -0
  200. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/topological_sort.plain +0 -0
  201. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/plainfileparser/topological_sort_not_referenced.plain +0 -0
  202. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/requires/circular_requires_main.plain +0 -0
  203. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/requires/circular_requires_sub.plain +0 -0
  204. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/requires/diamond_requires_1.plain +0 -0
  205. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/requires/diamond_requires_2.plain +0 -0
  206. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/requires/diamond_requires_common.plain +0 -0
  207. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/requires/diamond_requires_main.plain +0 -0
  208. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/requires/independent_requires_1.plain +0 -0
  209. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/requires/independent_requires_2.plain +0 -0
  210. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/requires/independent_requires_main.plain +0 -0
  211. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/requires/non_existent_require.plain +0 -0
  212. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/requires/normal_requires_1.plain +0 -0
  213. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/requires/normal_requires_2.plain +0 -0
  214. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/requires/normal_requires_common.plain +0 -0
  215. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/requires/normal_requires_main.plain +0 -0
  216. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/sample_base64_image.txt +0 -0
  217. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/simple.plain +0 -0
  218. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/templates/block_level_include.plain +0 -0
  219. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/templates/code_variables.plain +0 -0
  220. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/templates/header.plain +0 -0
  221. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/templates/implement.plain +0 -0
  222. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/templates/implement_2.plain +0 -0
  223. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/templates/template_include.plain +0 -0
  224. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/data/templates/test_hardest_problem.plain +0 -0
  225. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/e2e/Dockerfile +0 -0
  226. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/e2e/conftest.py +0 -0
  227. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/e2e/test_hello_world_python.py +0 -0
  228. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/e2e/test_hello_world_python_windows.py +0 -0
  229. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_arg_provenance.py +0 -0
  230. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_change_detection.py +0 -0
  231. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_cli_output.py +0 -0
  232. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_conformance_fix_journal.py +0 -0
  233. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_console_log_styles.py +0 -0
  234. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_distill_conformance_test_memory.py +0 -0
  235. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_dry_run_link_validation.py +0 -0
  236. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_file_utils.py +0 -0
  237. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_fix_unit_tests_conformance_context.py +0 -0
  238. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_folder_path_resolution.py +0 -0
  239. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_git_utils.py +0 -0
  240. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_headless_logging.py +0 -0
  241. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_imports.py +0 -0
  242. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_log_file_name_resolution.py +0 -0
  243. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_logging_config_path_resolution.py +0 -0
  244. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_partial_rendering.py +0 -0
  245. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_path_resolution.py +0 -0
  246. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_plain2code.py +0 -0
  247. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_plain2code_state.py +0 -0
  248. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_plain2code_utils.py +0 -0
  249. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_plain_modules.py +0 -0
  250. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_plainfile.py +0 -0
  251. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_plainfileparser.py +0 -0
  252. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_plainspec.py +0 -0
  253. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_prepare_repositories_layout.py +0 -0
  254. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_requires.py +0 -0
  255. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_resolve_config_file.py +0 -0
  256. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_script_path_resolution.py +0 -0
  257. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_system_config.py +0 -0
  258. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_telemetry.py +0 -0
  259. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_tui_usage.py +0 -0
  260. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tests/test_usage_summary.py +0 -0
  261. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tui/__init__.py +0 -0
  262. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tui/models.py +0 -0
  263. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tui/plain2code_tui.py +0 -0
  264. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tui/plain_module_render_choice_tui.py +0 -0
  265. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tui/spinner.py +0 -0
  266. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tui/state_handlers.py +0 -0
  267. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/tui/styles.css +0 -0
  268. {codeplain-0.3.11.dev11 → codeplain-0.3.11.dev13}/usage_summary.py +0 -0
@@ -20,6 +20,8 @@ exclude =
20
20
  per-file-ignores =
21
21
  __init__.py: F401
22
22
  tests/*: U100
23
+ # require_git() runs between imports in plain_modules.py, so the imports below it trip E402
24
+ plain_modules.py: E402
23
25
  max-complexity = 15
24
26
  enable-extensions = U100
25
27
  # Plugin-specific configuration
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: codeplain
3
- Version: 0.3.11.dev11
3
+ Version: 0.3.11.dev13
4
4
  Summary: Transform plain language specifications into working code
5
5
  License-File: LICENSE
6
6
  Classifier: Environment :: Console
@@ -0,0 +1,41 @@
1
+ """Verify a working git is available before GitPython is imported.
2
+
3
+ GitPython probes for the git executable on import and raises from module scope
4
+ if it is missing or broken, so the CLI would crash with a traceback before
5
+ main() could explain it.
6
+ """
7
+
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+
12
+ from plain2code_console import console
13
+
14
+ GIT_MISSING_MESSAGE = "git is not installed. Please install git and try again."
15
+ GIT_BROKEN_MESSAGE = "git is installed but not working. Please repair your git installation and try again."
16
+ MACOS_BROKEN_GIT_HINT = (
17
+ "This usually means the Command Line Tools are missing; install them with 'xcode-select --install'."
18
+ )
19
+
20
+
21
+ def git_runs(git_path):
22
+ """Whether `git version` succeeds. A git on PATH can still be unusable: on
23
+ macOS without the Command Line Tools /usr/bin/git is a stub that exits with
24
+ an xcrun error, and GIT_PYTHON_REFRESH=quiet does not suppress that."""
25
+ try:
26
+ return subprocess.run([git_path, "version"], capture_output=True, timeout=10).returncode == 0
27
+ except (OSError, subprocess.SubprocessError):
28
+ return False
29
+
30
+
31
+ def require_git():
32
+ """Exit with a clean message unless a working git is available."""
33
+ git_path = shutil.which("git")
34
+ if git_path is None:
35
+ console.error(f"{GIT_MISSING_MESSAGE}\n")
36
+ sys.exit(1)
37
+
38
+ if not git_runs(git_path):
39
+ hint = f"\n{MACOS_BROKEN_GIT_HINT}" if sys.platform == "darwin" else ""
40
+ console.error(f"{GIT_BROKEN_MESSAGE}{hint}\n")
41
+ sys.exit(1)
@@ -6,18 +6,18 @@ import tempfile
6
6
  import zipfile
7
7
  from functools import cached_property
8
8
 
9
+ from git_preflight import require_git
9
10
  from plain2code_exceptions import (
10
- GitNotInstalledError,
11
11
  InvalidModuleArchiveError,
12
12
  MissingPreviousFunctionalitiesError,
13
13
  ModuleDoesNotExistError,
14
14
  )
15
15
 
16
- try:
17
- from git import Repo
18
- from git.exc import NoSuchPathError
19
- except ImportError:
20
- raise GitNotInstalledError("git is not installed. Please install git and try again.")
16
+ # Must run before GitPython is imported, which raises on a missing or broken git.
17
+ require_git()
18
+
19
+ from git import Repo
20
+ from git.exc import NoSuchPathError
21
21
 
22
22
  import file_utils
23
23
  import git_utils
@@ -0,0 +1,98 @@
1
+ """Regression tests for issue #133.
2
+
3
+ When git is unusable the CLI must report a clean message and exit 1, never dump
4
+ a traceback. The failure is at import time (GitPython raises from module scope
5
+ before main() runs), so the CLI is run in a subprocess and asserted on what a
6
+ user would actually see. sys.executable is launched by absolute path, so
7
+ replacing PATH only controls which git, if any, can be found.
8
+ """
9
+
10
+ import os
11
+ import re
12
+ import subprocess
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ import pytest
17
+
18
+ from git_preflight import GIT_BROKEN_MESSAGE, GIT_MISSING_MESSAGE
19
+
20
+ REPO_ROOT = Path(__file__).resolve().parents[1]
21
+ TRACEBACK_MARKER = "Traceback (most recent call last)"
22
+
23
+ # The crash was in the import chain, so it hit every entry point equally.
24
+ CLI_INVOCATIONS = [["--version"], ["--status"], ["does-not-exist.plain"]]
25
+
26
+
27
+ def run_cli(args, path_dir=None):
28
+ """Run the CLI and return its exit code and output, stripped of styling and
29
+ with whitespace collapsed, so assertions survive console line wrapping."""
30
+ env = os.environ.copy()
31
+ # Drop GitPython overrides so the PATH search decides whether git is found.
32
+ env.pop("GIT_PYTHON_GIT_EXECUTABLE", None)
33
+ env.pop("GIT_PYTHON_REFRESH", None)
34
+ env.setdefault("CODEPLAIN_API_KEY", "dummy")
35
+ if path_dir is not None:
36
+ env["PATH"] = str(path_dir)
37
+
38
+ result = subprocess.run(
39
+ [sys.executable, "plain2code.py", *args],
40
+ cwd=REPO_ROOT,
41
+ env=env,
42
+ capture_output=True,
43
+ text=True,
44
+ timeout=120,
45
+ )
46
+ output = re.sub(r"\x1b\[[0-9;]*m", "", result.stdout + result.stderr)
47
+ return result.returncode, " ".join(output.split())
48
+
49
+
50
+ @pytest.fixture
51
+ def no_git_dir(tmp_path):
52
+ """A directory containing no git at all, used as the whole PATH."""
53
+ d = tmp_path / "no-git"
54
+ d.mkdir()
55
+ return d
56
+
57
+
58
+ @pytest.fixture
59
+ def broken_git_dir(tmp_path):
60
+ """A directory whose git resolves but always fails, used as the whole PATH.
61
+ This is the shape of macOS without the Command Line Tools."""
62
+ d = tmp_path / "broken-git"
63
+ d.mkdir()
64
+ if sys.platform == "win32":
65
+ (d / "git.bat").write_text("@echo off\nexit /b 127\n")
66
+ else:
67
+ git = d / "git"
68
+ git.write_text("#!/bin/sh\nexit 127\n")
69
+ git.chmod(0o755)
70
+ return d
71
+
72
+
73
+ @pytest.mark.parametrize("args", CLI_INVOCATIONS)
74
+ def test_missing_git_reports_cleanly(args, no_git_dir):
75
+ rc, output = run_cli(args, no_git_dir)
76
+
77
+ assert GIT_MISSING_MESSAGE in output, output
78
+ assert TRACEBACK_MARKER not in output, output
79
+ # GitPython's own advice about GIT_PYTHON_REFRESH must not reach the user.
80
+ assert "GIT_PYTHON_REFRESH" not in output, output
81
+ assert rc == 1, output
82
+
83
+
84
+ @pytest.mark.parametrize("args", CLI_INVOCATIONS)
85
+ def test_broken_git_reports_cleanly(args, broken_git_dir):
86
+ rc, output = run_cli(args, broken_git_dir)
87
+
88
+ assert GIT_BROKEN_MESSAGE in output, output
89
+ assert GIT_MISSING_MESSAGE not in output, output
90
+ assert TRACEBACK_MARKER not in output, output
91
+ assert rc == 1, output
92
+
93
+
94
+ def test_cli_works_when_git_is_present():
95
+ rc, output = run_cli(["--version"])
96
+
97
+ assert rc == 0, output
98
+ assert "codeplain version" in output, output
@@ -0,0 +1,88 @@
1
+ """Regression tests: spec-derived text with square brackets must never be parsed as Textual markup."""
2
+
3
+ import asyncio
4
+
5
+ from textual.widgets import Static
6
+
7
+ from event_bus import EventBus
8
+ from plain2code_state import RunState
9
+ from tui.components import ProgressItem, SubstateLine, TUIComponents
10
+ from tui.models import Substate
11
+ from tui.plain2code_tui import Plain2CodeTUI
12
+ from tui.widget_helpers import display_error_message, display_success_message, update_progress_item_substates
13
+
14
+ # Exact acceptance-test text that crashed the TUI with
15
+ # "MarkupError: closing tag '[/#888888]' does not match any open tag":
16
+ # the unterminated "[b" swallowed the timer's opening tag.
17
+ UNTERMINATED_BRACKET_TEXT = (
18
+ "Rendering acceptance test: With a=1, b=2, c=3, d=4: ZRANK z c should reply 2, "
19
+ 'ZREVRANK z c WITHSCORE should reply [1, "3"], ZRANK z zz should reply null, '
20
+ "ZCOUNT z (1 3 should reply 2, and ZLEXCOUNT z [b + should reply 3."
21
+ )
22
+ # Terminated brackets used to be silently eaten as a markup tag ("LPOP key ").
23
+ TERMINATED_BRACKET_TEXT = "LPOP key [count] and RPOP key [count] reply one element"
24
+
25
+
26
+ def _make_app(run_state: RunState, event_bus: EventBus) -> Plain2CodeTUI:
27
+ return Plain2CodeTUI(
28
+ event_bus=event_bus,
29
+ run_state=run_state,
30
+ on_ready=lambda: None,
31
+ render_id="test-render-id",
32
+ unittests_script=None,
33
+ conformance_tests_script="run_conformance_tests.sh",
34
+ prepare_environment_script=None,
35
+ state_machine_version="0.0.0",
36
+ css_path="styles.css",
37
+ )
38
+
39
+
40
+ def test_substate_line_keeps_bracket_text_verbatim():
41
+ for text in (UNTERMINATED_BRACKET_TEXT, TERMINATED_BRACKET_TEXT):
42
+ line = SubstateLine(text, " ", ProgressItem.PROCESSING)._format_line()
43
+ assert text in line.plain
44
+ assert line.plain.endswith("(0s)")
45
+ # Only the timer carries the muted style; the text itself is unstyled.
46
+ assert len(line.spans) == 1
47
+ assert line.plain[line.spans[0].start : line.spans[0].end] == "(0s)"
48
+
49
+
50
+ def test_substate_with_brackets_renders_without_markup_error():
51
+ async def scenario():
52
+ event_bus = EventBus()
53
+ run_state = RunState(spec_filename="x.plain")
54
+ app = _make_app(run_state, event_bus)
55
+ async with app.run_test() as pilot:
56
+ widget_id = TUIComponents.FRID_PROGRESS_CONFORMANCE_TEST.value
57
+ update_progress_item_substates(
58
+ app, widget_id, [Substate(UNTERMINATED_BRACKET_TEXT), Substate(TERMINATED_BRACKET_TEXT)]
59
+ )
60
+ await pilot.pause()
61
+ await pilot.pause()
62
+
63
+ lines = app.query(".substate-line-text").results(Static)
64
+ rendered = [str(line.content) for line in lines]
65
+ assert any(UNTERMINATED_BRACKET_TEXT in text for text in rendered)
66
+ assert any(TERMINATED_BRACKET_TEXT in text for text in rendered)
67
+
68
+ asyncio.run(scenario())
69
+
70
+
71
+ def test_status_messages_keep_brackets():
72
+ async def scenario():
73
+ event_bus = EventBus()
74
+ run_state = RunState(spec_filename="x.plain")
75
+ app = _make_app(run_state, event_bus)
76
+ async with app.run_test() as pilot:
77
+ status = app.query_one(f"#{TUIComponents.RENDER_STATUS_WIDGET.value}", Static)
78
+
79
+ display_success_message(app, "out/[weird] path")
80
+ await pilot.pause()
81
+ assert "rendering completed!" in str(status.content)
82
+ assert "generated code folder: out/[weird] path" in str(status.content)
83
+
84
+ display_error_message(app, "Error: [Errno 2] No such file: run_tests.sh [b")
85
+ await pilot.pause()
86
+ assert "[Errno 2] No such file: run_tests.sh [b" in str(status.content)
87
+
88
+ asyncio.run(scenario())
@@ -3,6 +3,7 @@ from typing import Literal, Optional
3
3
 
4
4
  from rich.markup import escape
5
5
  from textual.containers import Horizontal, Vertical, VerticalScroll
6
+ from textual.content import Content
6
7
  from textual.message import Message
7
8
  from textual.timer import Timer
8
9
  from textual.widgets import Button, Static
@@ -71,7 +72,7 @@ class CustomFooter(Horizontal):
71
72
  self._footer_text_widget = Static(footer_rendering_text, classes="custom-footer-text")
72
73
  yield self._footer_text_widget
73
74
  if self.render_id:
74
- yield Static(f"render id: {self.render_id} ", classes="custom-footer-render-id")
75
+ yield Static(f"render id: {self.render_id} ", classes="custom-footer-render-id", markup=False)
75
76
 
76
77
  def update_footer_state(self, state: Literal["rendering", "pausing", "paused", "finished"]) -> None:
77
78
  self.remove_class("footer-state-default")
@@ -201,9 +202,12 @@ class SubstateLine(Horizontal):
201
202
  def _format_timer(self) -> str:
202
203
  return format_duration_hms(self._seconds_elapsed)
203
204
 
204
- def _format_line(self) -> str:
205
+ def _format_line(self) -> Content:
206
+ # The substate text is arbitrary (acceptance-test text from the spec, module names, ...)
207
+ # and may contain square brackets, so it must never go through the markup parser.
208
+ # Build the line as Content and style only the timer part.
205
209
  timer = self._format_timer()
206
- return f"{self.indent} └ {self.text} [#888888]({timer})[/#888888]"
210
+ return Content.assemble(f"{self.indent} └ {self.text} ", (f"({timer})", "#888888"))
207
211
 
208
212
  def _refresh_timer(self) -> None:
209
213
  try:
@@ -232,7 +236,7 @@ class ProgressItem(Vertical):
232
236
  # Main row with status and description
233
237
  with Horizontal(id=f"{self.id}-main-row", classes="progress-main-row"):
234
238
  yield Static(self._get_status_text(self.PENDING), classes=f"status {self.PENDING}")
235
- yield Static(self.initial_text, classes="description")
239
+ yield Static(self.initial_text, classes="description", markup=False)
236
240
  # Substates container (full width, aligned to left)
237
241
  yield Vertical(id=f"{self.id}-substates", classes="substates-container")
238
242
 
@@ -367,8 +371,8 @@ class RenderingInfoBox(Vertical):
367
371
  self._refresh_content()
368
372
 
369
373
  def compose(self):
370
- self.module_widget = Static(self.module_text, classes="rendering-info-row")
371
- self.functionality_widget = Static(self.functionality_text, classes="rendering-info-row")
374
+ self.module_widget = Static(self.module_text, classes="rendering-info-row", markup=False)
375
+ self.functionality_widget = Static(self.functionality_text, classes="rendering-info-row", markup=False)
372
376
  yield Static("module status", classes="rendering-info-title")
373
377
  with Vertical(classes="rendering-info-box"):
374
378
  yield self.module_widget
@@ -430,9 +434,9 @@ class TestScriptsContainer(Vertical):
430
434
  def compose(self):
431
435
  yield Static("testing status", classes="test-scripts-title")
432
436
  with Vertical(classes="test-scripts-box"):
433
- self.unit_widget = Static(self.unit_test_text, classes="test-script-row")
434
- self.conformance_widget = Static(self.conformance_test_text, classes="test-script-row")
435
- self.testing_widget = Static(self.testing_env_text, classes="test-script-row")
437
+ self.unit_widget = Static(self.unit_test_text, classes="test-script-row", markup=False)
438
+ self.conformance_widget = Static(self.conformance_test_text, classes="test-script-row", markup=False)
439
+ self.testing_widget = Static(self.testing_env_text, classes="test-script-row", markup=False)
436
440
  yield self.unit_widget
437
441
  yield self.conformance_widget
438
442
  yield self.testing_widget
@@ -2,6 +2,7 @@
2
2
 
3
3
  from datetime import datetime
4
4
 
5
+ from textual.content import Content
5
6
  from textual.css.query import NoMatches
6
7
  from textual.widgets import Static
7
8
 
@@ -89,9 +90,14 @@ def display_success_message(tui, rendered_code_path: str):
89
90
  rendered_code_path: The path to the rendered code
90
91
  """
91
92
 
92
- message = (
93
- f"[#79FC96]✓ rendering completed![/#79FC96] [#888888](press enter to exit)[/#888888]\n"
94
- f"[#888888]generated code folder: {rendered_code_path}[/#888888] "
93
+ # rendered_code_path is user-controlled and may contain square brackets, so build the
94
+ # message as Content instead of interpolating it into a markup string.
95
+ message = Content.assemble(
96
+ ("✓ rendering completed!", "#79FC96"),
97
+ " ",
98
+ ("(press enter to exit)", "#888888"),
99
+ "\n",
100
+ (f"generated code folder: {rendered_code_path} ", "#888888"),
95
101
  )
96
102
 
97
103
  widget: Static = tui.query_one(f"#{TUIComponents.RENDER_STATUS_WIDGET.value}", Static)
@@ -120,7 +126,8 @@ def transition_frid_progress(tui, from_status: str | None, to_status: str):
120
126
  def display_error_message(tui, error_message: str):
121
127
  widget: Static = tui.query_one(f"#{TUIComponents.RENDER_STATUS_WIDGET.value}", Static)
122
128
  widget.add_class("error")
123
- widget.update(error_message)
129
+ # Error text may contain square brackets (paths, tracebacks); keep it out of the markup parser.
130
+ widget.update(Content(error_message))
124
131
 
125
132
 
126
133
  def display_usage_summary(tui, functionalities: int, render_time_seconds: float) -> None: