codeplain 0.2.14__tar.gz → 0.2.16__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 (205) hide show
  1. codeplain-0.2.16/.claude/settings.local.json +7 -0
  2. {codeplain-0.2.14 → codeplain-0.2.16}/PKG-INFO +1 -1
  3. codeplain-0.2.16/_version.py +1 -0
  4. {codeplain-0.2.14 → codeplain-0.2.16}/codeplain_REST_api.py +8 -8
  5. codeplain-0.2.16/docs/generate_cli.py +25 -0
  6. {codeplain-0.2.14 → codeplain-0.2.16}/docs/plain2code_cli.md +38 -25
  7. {codeplain-0.2.14 → codeplain-0.2.16}/event_bus.py +2 -14
  8. {codeplain-0.2.14 → codeplain-0.2.16}/file_utils.py +7 -4
  9. {codeplain-0.2.14 → codeplain-0.2.16}/git_utils.py +6 -8
  10. {codeplain-0.2.14 → codeplain-0.2.16}/install/bash/install.sh +1 -1
  11. {codeplain-0.2.14 → codeplain-0.2.16}/install/powershell/install.ps1 +1 -1
  12. {codeplain-0.2.14 → codeplain-0.2.16}/memory_management.py +1 -1
  13. {codeplain-0.2.14 → codeplain-0.2.16}/module_renderer.py +8 -1
  14. {codeplain-0.2.14 → codeplain-0.2.16}/plain2code.py +34 -36
  15. {codeplain-0.2.14 → codeplain-0.2.16}/plain2code_arguments.py +18 -14
  16. {codeplain-0.2.14 → codeplain-0.2.16}/plain2code_console.py +15 -11
  17. {codeplain-0.2.14 → codeplain-0.2.16}/plain2code_events.py +6 -1
  18. {codeplain-0.2.14 → codeplain-0.2.16}/plain2code_exceptions.py +7 -1
  19. {codeplain-0.2.14 → codeplain-0.2.16}/plain2code_logger.py +2 -19
  20. {codeplain-0.2.14 → codeplain-0.2.16}/plain2code_utils.py +3 -3
  21. {codeplain-0.2.14 → codeplain-0.2.16}/plain_file.py +11 -11
  22. {codeplain-0.2.14 → codeplain-0.2.16}/plain_spec.py +3 -3
  23. {codeplain-0.2.14 → codeplain-0.2.16}/pyproject.toml +0 -5
  24. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/actions/analyze_specification_ambiguity.py +2 -4
  25. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/actions/create_dist.py +1 -1
  26. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/actions/exit_with_error.py +1 -1
  27. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/actions/fix_conformance_test.py +3 -3
  28. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/actions/fix_unit_tests.py +2 -2
  29. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/actions/prepare_repositories.py +3 -3
  30. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/actions/prepare_testing_environment.py +1 -0
  31. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/actions/refactor_code.py +3 -3
  32. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/actions/render_conformance_tests.py +4 -4
  33. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/actions/render_functional_requirement.py +4 -4
  34. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/actions/run_conformance_tests.py +11 -7
  35. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/actions/run_unit_tests.py +9 -4
  36. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/actions/summarize_conformance_tests.py +2 -2
  37. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/code_renderer.py +11 -1
  38. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/conformance_tests.py +3 -3
  39. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/render_context.py +13 -8
  40. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/render_types.py +5 -1
  41. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/render_utils.py +102 -22
  42. {codeplain-0.2.14 → codeplain-0.2.16}/test_scripts/run_unittests_golang.sh +1 -0
  43. {codeplain-0.2.14 → codeplain-0.2.16}/test_scripts/run_unittests_python.sh +1 -0
  44. {codeplain-0.2.14 → codeplain-0.2.16}/test_scripts/run_unittests_react.sh +1 -0
  45. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfile/invalid_specification_order.plain +1 -1
  46. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfile/missing_non_functional_requirements.plain +1 -1
  47. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfile/without_non_functional_requirement.plain +1 -1
  48. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/plain_file_parser_with_comments.plain +2 -2
  49. codeplain-0.2.16/tests/data/plainfileparser/plain_file_with_comments_indented.plain +10 -0
  50. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/regular_plain_source.plain +2 -2
  51. codeplain-0.2.16/tests/data/requires/diamond_requires_common.plain +7 -0
  52. codeplain-0.2.16/tests/data/requires/normal_requires_common.plain +7 -0
  53. codeplain-0.2.16/tests/data/simple.plain +7 -0
  54. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/templates/code_variables.plain +2 -2
  55. codeplain-0.2.16/tests/data/templates/header.plain +7 -0
  56. {codeplain-0.2.14 → codeplain-0.2.16}/tests/test_git_utils.py +3 -3
  57. {codeplain-0.2.14 → codeplain-0.2.16}/tests/test_plainfile.py +8 -8
  58. {codeplain-0.2.14 → codeplain-0.2.16}/tests/test_plainfileparser.py +23 -23
  59. {codeplain-0.2.14 → codeplain-0.2.16}/tests/test_plainspec.py +5 -5
  60. {codeplain-0.2.14 → codeplain-0.2.16}/tui/components.py +60 -27
  61. {codeplain-0.2.14 → codeplain-0.2.16}/tui/plain2code_tui.py +33 -5
  62. {codeplain-0.2.14 → codeplain-0.2.16}/tui/state_handlers.py +5 -5
  63. {codeplain-0.2.14 → codeplain-0.2.16}/tui/styles.css +21 -3
  64. {codeplain-0.2.14 → codeplain-0.2.16}/tui/widget_helpers.py +17 -11
  65. codeplain-0.2.14/_version.py +0 -1
  66. codeplain-0.2.14/docs/generate_cli.py +0 -20
  67. codeplain-0.2.14/tests/data/plainfileparser/plain_file_with_comments_indented.plain +0 -10
  68. codeplain-0.2.14/tests/data/requires/diamond_requires_common.plain +0 -7
  69. codeplain-0.2.14/tests/data/requires/normal_requires_common.plain +0 -7
  70. codeplain-0.2.14/tests/data/simple.plain +0 -7
  71. codeplain-0.2.14/tests/data/templates/header.plain +0 -7
  72. {codeplain-0.2.14 → codeplain-0.2.16}/.flake8 +0 -0
  73. {codeplain-0.2.14 → codeplain-0.2.16}/.github/workflows/lint-and-test.yml +0 -0
  74. {codeplain-0.2.14 → codeplain-0.2.16}/.github/workflows/nofity-slack-on-main-merge.yml +0 -0
  75. {codeplain-0.2.14 → codeplain-0.2.16}/.github/workflows/publish-install-script.yml +0 -0
  76. {codeplain-0.2.14 → codeplain-0.2.16}/.github/workflows/publish-to-pypi.yml +0 -0
  77. {codeplain-0.2.14 → codeplain-0.2.16}/.gitignore +0 -0
  78. {codeplain-0.2.14 → codeplain-0.2.16}/.vscode/launch.json +0 -0
  79. {codeplain-0.2.14 → codeplain-0.2.16}/.vscode/settings.json +0 -0
  80. {codeplain-0.2.14 → codeplain-0.2.16}/LICENSE +0 -0
  81. {codeplain-0.2.14 → codeplain-0.2.16}/README.md +0 -0
  82. {codeplain-0.2.14 → codeplain-0.2.16}/concept_utils.py +0 -0
  83. {codeplain-0.2.14 → codeplain-0.2.16}/config/__init__.py +0 -0
  84. {codeplain-0.2.14 → codeplain-0.2.16}/config/system_config.yaml +0 -0
  85. {codeplain-0.2.14 → codeplain-0.2.16}/diff_utils.py +0 -0
  86. {codeplain-0.2.14 → codeplain-0.2.16}/docs/starting_a_plain_project_from_scratch.md +0 -0
  87. {codeplain-0.2.14 → codeplain-0.2.16}/examples/example_hello_world_golang/config.yaml +0 -0
  88. {codeplain-0.2.14 → codeplain-0.2.16}/examples/example_hello_world_golang/harness_tests/hello_world_test.go +0 -0
  89. {codeplain-0.2.14 → codeplain-0.2.16}/examples/example_hello_world_golang/hello_world_golang.plain +0 -0
  90. {codeplain-0.2.14 → codeplain-0.2.16}/examples/example_hello_world_golang/run.sh +0 -0
  91. {codeplain-0.2.14 → codeplain-0.2.16}/examples/example_hello_world_python/config.yaml +0 -0
  92. {codeplain-0.2.14 → codeplain-0.2.16}/examples/example_hello_world_python/harness_tests/hello_world_display/test_hello_world.py +0 -0
  93. {codeplain-0.2.14 → codeplain-0.2.16}/examples/example_hello_world_python/hello_world_python.plain +0 -0
  94. {codeplain-0.2.14 → codeplain-0.2.16}/examples/example_hello_world_python/python-console-app-boilerplate.plain +0 -0
  95. {codeplain-0.2.14 → codeplain-0.2.16}/examples/example_hello_world_python/run.sh +0 -0
  96. {codeplain-0.2.14 → codeplain-0.2.16}/examples/example_hello_world_react/config.yaml +0 -0
  97. {codeplain-0.2.14 → codeplain-0.2.16}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress/e2e/hello_world.cy.ts +0 -0
  98. {codeplain-0.2.14 → codeplain-0.2.16}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress/support/e2e.ts +0 -0
  99. {codeplain-0.2.14 → codeplain-0.2.16}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress.config.ts +0 -0
  100. {codeplain-0.2.14 → codeplain-0.2.16}/examples/example_hello_world_react/harness_tests/hello_world_display/package.json +0 -0
  101. {codeplain-0.2.14 → codeplain-0.2.16}/examples/example_hello_world_react/harness_tests/hello_world_display/tsconfig.json +0 -0
  102. {codeplain-0.2.14 → codeplain-0.2.16}/examples/example_hello_world_react/hello_world_react.plain +0 -0
  103. {codeplain-0.2.14 → codeplain-0.2.16}/examples/example_hello_world_react/run.sh +0 -0
  104. {codeplain-0.2.14 → codeplain-0.2.16}/examples/run.sh +0 -0
  105. {codeplain-0.2.14 → codeplain-0.2.16}/hash_key.py +0 -0
  106. {codeplain-0.2.14 → codeplain-0.2.16}/install/bash/examples.sh +0 -0
  107. {codeplain-0.2.14 → codeplain-0.2.16}/install/bash/walkthrough.sh +0 -0
  108. {codeplain-0.2.14 → codeplain-0.2.16}/install/powershell/examples.ps1 +0 -0
  109. {codeplain-0.2.14 → codeplain-0.2.16}/install/powershell/walkthrough.ps1 +0 -0
  110. {codeplain-0.2.14 → codeplain-0.2.16}/plain2code_nodes.py +0 -0
  111. {codeplain-0.2.14 → codeplain-0.2.16}/plain2code_read_config.py +0 -0
  112. {codeplain-0.2.14 → codeplain-0.2.16}/plain2code_state.py +0 -0
  113. {codeplain-0.2.14 → codeplain-0.2.16}/plain_modules.py +0 -0
  114. {codeplain-0.2.14 → codeplain-0.2.16}/pytest.ini +0 -0
  115. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/__init__.py +0 -0
  116. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/actions/base_action.py +0 -0
  117. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/actions/commit_conformance_tests_changes.py +0 -0
  118. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/actions/commit_implementation_code_changes.py +0 -0
  119. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/actions/finish_functional_requirement.py +0 -0
  120. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/conformance_test_helpers.py +0 -0
  121. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/implementation_code_helpers.py +0 -0
  122. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/state_machine_config.py +0 -0
  123. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/states.py +0 -0
  124. {codeplain-0.2.14 → codeplain-0.2.16}/render_machine/triggers.py +0 -0
  125. {codeplain-0.2.14 → codeplain-0.2.16}/requirements.txt +0 -0
  126. {codeplain-0.2.14 → codeplain-0.2.16}/resources/codeplain_overview.png +0 -0
  127. {codeplain-0.2.14 → codeplain-0.2.16}/resources/plain_example.png +0 -0
  128. {codeplain-0.2.14 → codeplain-0.2.16}/standard_template_library/__init__.py +0 -0
  129. {codeplain-0.2.14 → codeplain-0.2.16}/standard_template_library/golang-console-app-template.plain +0 -0
  130. {codeplain-0.2.14 → codeplain-0.2.16}/standard_template_library/python-console-app-template.plain +0 -0
  131. {codeplain-0.2.14 → codeplain-0.2.16}/standard_template_library/typescript-react-app-boilerplate.plain +0 -0
  132. {codeplain-0.2.14 → codeplain-0.2.16}/standard_template_library/typescript-react-app-template.plain +0 -0
  133. {codeplain-0.2.14 → codeplain-0.2.16}/system_config.py +0 -0
  134. {codeplain-0.2.14 → codeplain-0.2.16}/test_scripts/run_conformance_tests_cypress.sh +0 -0
  135. {codeplain-0.2.14 → codeplain-0.2.16}/test_scripts/run_conformance_tests_golang.sh +0 -0
  136. {codeplain-0.2.14 → codeplain-0.2.16}/test_scripts/run_conformance_tests_python.sh +0 -0
  137. {codeplain-0.2.14 → codeplain-0.2.16}/tests/__init__.py +0 -0
  138. {codeplain-0.2.14 → codeplain-0.2.16}/tests/conftest.py +0 -0
  139. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/imports/circular_imports_1.plain +0 -0
  140. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/imports/circular_imports_2.plain +0 -0
  141. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/imports/circular_imports_main.plain +0 -0
  142. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/imports/diamond_import_1.plain +0 -0
  143. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/imports/diamond_import_2.plain +0 -0
  144. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/imports/diamond_import_common.plain +0 -0
  145. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/imports/diamond_imports_main.plain +0 -0
  146. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/imports/non_existent_import.plain +0 -0
  147. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfile/duplicate_specification_heading.plain +0 -0
  148. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfile/plain_source_with_absolute_link.plain +0 -0
  149. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfile/plain_source_with_url_link.plain +0 -0
  150. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfile/task_manager_with_reference_links.plain +0 -0
  151. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/concept_validation_acceptance_tests.plain +0 -0
  152. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/concept_validation_acceptance_tests_nondefined.plain +0 -0
  153. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/concept_validation_defined_nondefined.plain +0 -0
  154. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/concept_validation_defined_nondefined_2.plain +0 -0
  155. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/concept_validation_definition.plain +0 -0
  156. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/concept_validation_noconcepts.plain +0 -0
  157. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/concept_validation_nonconcept.plain +0 -0
  158. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/concept_validation_nondefined.plain +0 -0
  159. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/concept_validation_redefinition.plain +0 -0
  160. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/concept_validation_several_concepts.plain +0 -0
  161. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/concept_validation_valid.plain +0 -0
  162. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/exported_concepts.plain +0 -0
  163. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/exported_concepts_base.plain +0 -0
  164. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/exported_concepts_example.plain +0 -0
  165. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/exported_concepts_missing.plain +0 -0
  166. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/exported_concepts_missing_example.plain +0 -0
  167. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/exported_concepts_nested.plain +0 -0
  168. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/exported_concepts_nested_example.plain +0 -0
  169. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/exported_concepts_transitive_example.plain +0 -0
  170. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/exported_concepts_transitive_l1.plain +0 -0
  171. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/exported_concepts_transitive_l2.plain +0 -0
  172. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/required_concepts.plain +0 -0
  173. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/required_concepts_defs.plain +0 -0
  174. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/required_concepts_example.plain +0 -0
  175. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/required_concepts_l1.plain +0 -0
  176. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/required_concepts_l2.plain +0 -0
  177. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/required_concepts_missing.plain +0 -0
  178. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/required_concepts_module.plain +0 -0
  179. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/required_concepts_partial.plain +0 -0
  180. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/required_concepts_partial_duplicate.plain +0 -0
  181. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/topological_sort.plain +0 -0
  182. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/plainfileparser/topological_sort_not_referenced.plain +0 -0
  183. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/requires/circular_requires_main.plain +0 -0
  184. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/requires/circular_requires_sub.plain +0 -0
  185. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/requires/diamond_requires_1.plain +0 -0
  186. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/requires/diamond_requires_2.plain +0 -0
  187. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/requires/diamond_requires_main.plain +0 -0
  188. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/requires/independent_requires_1.plain +0 -0
  189. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/requires/independent_requires_2.plain +0 -0
  190. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/requires/independent_requires_main.plain +0 -0
  191. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/requires/non_existent_require.plain +0 -0
  192. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/requires/normal_requires_1.plain +0 -0
  193. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/requires/normal_requires_2.plain +0 -0
  194. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/requires/normal_requires_main.plain +0 -0
  195. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/templates/block_level_include.plain +0 -0
  196. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/templates/implement.plain +0 -0
  197. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/templates/implement_2.plain +0 -0
  198. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/templates/template_include.plain +0 -0
  199. {codeplain-0.2.14 → codeplain-0.2.16}/tests/data/templates/test_hardest_problem.plain +0 -0
  200. {codeplain-0.2.14 → codeplain-0.2.16}/tests/test_imports.py +0 -0
  201. {codeplain-0.2.14 → codeplain-0.2.16}/tests/test_requires.py +0 -0
  202. {codeplain-0.2.14 → codeplain-0.2.16}/tests/test_resolve_config_file.py +0 -0
  203. {codeplain-0.2.14 → codeplain-0.2.16}/tui/__init__.py +0 -0
  204. {codeplain-0.2.14 → codeplain-0.2.16}/tui/models.py +0 -0
  205. {codeplain-0.2.14 → codeplain-0.2.16}/tui/spinner.py +0 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(gh pr:*)"
5
+ ]
6
+ }
7
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeplain
3
- Version: 0.2.14
3
+ Version: 0.2.16
4
4
  Summary: Transform plain language specifications into working code
5
5
  License-File: LICENSE
6
6
  Classifier: Environment :: Console
@@ -0,0 +1 @@
1
+ __version__ = "0.2.16"
@@ -18,7 +18,7 @@ RETRY_ERROR_CODES = [
18
18
  ERROR_CODE_EXCEPTIONS = {
19
19
  "FunctionalRequirementTooComplex": plain2code_exceptions.FunctionalRequirementTooComplex,
20
20
  "ConflictingRequirements": plain2code_exceptions.ConflictingRequirements,
21
- "CreditBalanceTooLow": plain2code_exceptions.CreditBalanceTooLow,
21
+ "CreditBalanceTooLow": plain2code_exceptions.RenderingCreditBalanceTooLow,
22
22
  "LLMInternalError": plain2code_exceptions.LLMInternalError,
23
23
  "MissingResource": plain2code_exceptions.MissingResource,
24
24
  "PlainSyntaxError": plain2code_exceptions.PlainSyntaxError,
@@ -67,8 +67,8 @@ class CodeplainAPI:
67
67
  connection_error_type = "Network error" if is_connection_error else "Error"
68
68
  if attempt < num_retries:
69
69
  if not silent:
70
- self.console.info(f"{connection_error_type} on attempt {attempt + 1}/{num_retries + 1}: {error}")
71
- self.console.info(f"Retrying in {retry_delay} seconds...")
70
+ self.console.debug(f"{connection_error_type} on attempt {attempt + 1}/{num_retries + 1}: {error}")
71
+ self.console.debug(f"Retrying in {retry_delay} seconds...")
72
72
  time.sleep(retry_delay)
73
73
  # Exponential backoff
74
74
  return retry_delay * 2
@@ -117,7 +117,7 @@ class CodeplainAPI:
117
117
  try:
118
118
  response_json = response.json()
119
119
  except requests.exceptions.JSONDecodeError as e:
120
- print(f"Failed to decode JSON response: {e}. Response text: {response.text}")
120
+ self.console.debug(f"Failed to decode JSON response: {e}. Response text: {response.text}")
121
121
  raise
122
122
 
123
123
  if response.status_code == requests.codes.bad_request and "error_code" in response_json:
@@ -156,11 +156,11 @@ class CodeplainAPI:
156
156
  run_state: RunState,
157
157
  ) -> dict[str, str]:
158
158
  """
159
- Renders the content of a functional requirement based on the provided ID,
159
+ Renders the content of a functionality based on the provided ID,
160
160
  corresponding sections from a Plain document, and existing files' content.
161
161
 
162
162
  Args:
163
- frid (str): The unique identifier for the functional requirement to be rendered.
163
+ frid (str): The unique identifier for the functionality to be rendered.
164
164
  plain_source_tree (dict): A dictionary containing the plain source tree.
165
165
  linked_resources (dict): A dictionary where the keys represent filenames of linked
166
166
  resources and the values are dictionaries containing
@@ -170,7 +170,7 @@ class CodeplainAPI:
170
170
  and the values are the content of those files.
171
171
  memory_files_content (dict): A dictionary where the keys represent memory filenames
172
172
  and the values are the content of those files.
173
- module_name (str): The name of the module to render the functional requirement for.
173
+ module_name (str): The name of the module to render the functionality for.
174
174
  required_modules (dict): A dictionary where the keys represent module names
175
175
  and the values are lists of functionalities implemented in those modules.
176
176
  run_state (RunState): The current state of the rendering process.
@@ -390,7 +390,7 @@ class CodeplainAPI:
390
390
  Renders acceptance tests based on the provided parameters.
391
391
 
392
392
  Args:
393
- frid (str): The unique identifier for the functional requirement.
393
+ frid (str): The unique identifier for the functionality.
394
394
  plain_source_tree (dict): A dictionary containing the plain source tree.
395
395
  linked_resources (dict): A dictionary where the keys represent resource names
396
396
  and the values are the content of those resources.
@@ -0,0 +1,25 @@
1
+ import os
2
+ import sys
3
+
4
+ # Add the parent directory to the path so we can import plain2code_arguments
5
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
6
+
7
+
8
+ def main():
9
+ from plain2code_arguments import create_parser
10
+
11
+ # Get the parser and generate help text
12
+ parser = create_parser() # Disable color for markdown output
13
+ help_text = parser.format_help()
14
+
15
+ # Create markdown
16
+ md = "# Plain2Code CLI Reference\n\n```text\n" + help_text + "\n```"
17
+
18
+ # Run generate_cli.py in the docs folder
19
+
20
+ with open("plain2code_cli.md", "w", encoding="utf-8") as f:
21
+ f.write(md)
22
+
23
+
24
+ if __name__ == "__main__":
25
+ main()
@@ -1,18 +1,22 @@
1
1
  # Plain2Code CLI Reference
2
2
 
3
3
  ```text
4
- usage: generate_cli.py [-h] [--verbose] [--base-folder BASE_FOLDER] [--build-folder BUILD_FOLDER] [--log-to-file | --no-log-to-file] [--log-file-name LOG_FILE_NAME] [--config-name CONFIG_NAME]
5
- [--render-range RENDER_RANGE | --render-from RENDER_FROM] [--force-render] [--unittests-script UNITTESTS_SCRIPT] [--conformance-tests-folder CONFORMANCE_TESTS_FOLDER]
6
- [--conformance-tests-script CONFORMANCE_TESTS_SCRIPT] [--prepare-environment-script PREPARE_ENVIRONMENT_SCRIPT] [--test-script-timeout TEST_SCRIPT_TIMEOUT] [--api [API]] [--api-key API_KEY]
7
- [--full-plain] [--dry-run] [--replay-with REPLAY_WITH] [--template-dir TEMPLATE_DIR] [--copy-build] [--build-dest BUILD_DEST] [--copy-conformance-tests]
8
- [--conformance-tests-dest CONFORMANCE_TESTS_DEST] [--render-machine-graph] [--logging-config-path] [--headless]
4
+ usage: generate_cli.py [-h] [--verbose] [--base-folder BASE_FOLDER] [--build-folder BUILD_FOLDER] [--log-to-file | --no-log-to-file]
5
+ [--log-file-name LOG_FILE_NAME] [--config-name CONFIG_NAME] [--render-range RENDER_RANGE | --render-from RENDER_FROM]
6
+ [--force-render] [--unittests-script UNITTESTS_SCRIPT] [--conformance-tests-folder CONFORMANCE_TESTS_FOLDER]
7
+ [--conformance-tests-script CONFORMANCE_TESTS_SCRIPT] [--prepare-environment-script PREPARE_ENVIRONMENT_SCRIPT]
8
+ [--test-script-timeout TEST_SCRIPT_TIMEOUT] [--api [API]] [--api-key API_KEY] [--full-plain] [--dry-run]
9
+ [--replay-with REPLAY_WITH] [--template-dir TEMPLATE_DIR] [--copy-build] [--build-dest BUILD_DEST]
10
+ [--copy-conformance-tests] [--conformance-tests-dest CONFORMANCE_TESTS_DEST] [--render-machine-graph]
11
+ [--logging-config-path LOGGING_CONFIG_PATH] [--headless]
9
12
  filename
10
13
 
11
14
  Render plain code to target code.
12
15
 
13
16
  positional arguments:
14
- filename Path to the plain file to render. The directory containing this file has highest precedence for template loading, so you can place custom templates here to override the defaults. See --template-dir
15
- for more details about template loading.
17
+ filename Path to the plain file to render. The directory containing this file has highest precedence for template loading, so
18
+ you can place custom templates here to override the defaults. See --template-dir for more details about template
19
+ loading.
16
20
 
17
21
  options:
18
22
  -h, --help show this help message and exit
@@ -24,49 +28,58 @@ options:
24
28
  --log-to-file, --no-log-to-file
25
29
  Enable logging to a file. Defaults to True. Set to False to disable.
26
30
  --log-file-name LOG_FILE_NAME
27
- Name of the log file. Defaults to 'codeplain.log'.Always resolved relative to the plain file directory.If file on this path already exists, the already existing log file will be overwritten by the
28
- current logs.
31
+ Name of the log file. Defaults to 'codeplain.log'.Always resolved relative to the plain file directory.If file on
32
+ this path already exists, the already existing log file will be overwritten by the current logs.
29
33
  --render-range RENDER_RANGE
30
- Specify a range of functional requirements to render (e.g. `1` , `2`, `3`). Use comma to separate start and end IDs. If only one ID is provided, only that requirement is rendered. Range is
31
- inclusive of both start and end IDs.
34
+ Specify a range of functionalities to render (e.g. `1` , `2`, `3`). Use comma to separate start and end IDs. If only
35
+ one functionality ID is provided, only that functionality is rendered. Range is inclusive of both start and end IDs.
32
36
  --render-from RENDER_FROM
33
- Continue generation starting from this specific functional requirement (e.g. `2`). The requirement with this ID will be included in the output. The ID must match one of the functional requirements
34
- in your plain file.
37
+ Continue generation starting from this specific functionality (e.g. `2`). The functionality with this ID will be
38
+ included in the output. The functionality ID must match one of the functionalities in your plain file.
35
39
  --force-render Force re-render of all the required modules.
36
40
  --unittests-script UNITTESTS_SCRIPT
37
- Shell script to run unit tests on generated code. Receives the build folder path as its first argument (default: 'plain_modules').
41
+ Shell script to run unit tests on generated code. Receives the build folder path as its first argument (default:
42
+ 'plain_modules').
38
43
  --conformance-tests-folder CONFORMANCE_TESTS_FOLDER
39
44
  Folder for conformance test files
40
45
  --conformance-tests-script CONFORMANCE_TESTS_SCRIPT
41
- Path to conformance tests shell script. Every conformance test script should accept two arguments: 1) Path to a folder (e.g. `plain_modules/module_name`) containing generated source code, 2) Path
42
- to a subfolder of the conformance tests folder (e.g. `conformance_tests/subfoldername`) containing test files.
46
+ Path to conformance tests shell script. Every conformance test script should accept two arguments: 1) Path to a
47
+ folder (e.g. `plain_modules/module_name`) containing generated source code, 2) Path to a subfolder of the conformance
48
+ tests folder (e.g. `conformance_tests/subfoldername`) containing test files.
43
49
  --prepare-environment-script PREPARE_ENVIRONMENT_SCRIPT
44
- Path to a shell script that prepares the testing environment. The script should accept the source code folder path as its first argument.
50
+ Path to a shell script that prepares the testing environment. The script should accept the source code folder path as
51
+ its first argument.
45
52
  --test-script-timeout TEST_SCRIPT_TIMEOUT
46
53
  Timeout for test scripts in seconds. If not provided, the default timeout of 120 seconds is used.
47
54
  --api [API] Alternative base URL for the API. Default: `https://api.codeplain.ai`
48
55
  --api-key API_KEY API key used to access the API. If not provided, the `CODEPLAIN_API_KEY` environment variable is used.
49
- --full-plain Full preview ***plain specification before code generation.Use when you want to preview context of all ***plain primitives that are going to be included in order to render the given module.
56
+ --full-plain Full preview ***plain specification before code generation.Use when you want to preview context of all ***plain
57
+ primitives that are going to be included in order to render the given module.
50
58
  --dry-run Dry run preview of the code generation (without actually making any changes).
51
59
  --replay-with REPLAY_WITH
52
60
  --template-dir TEMPLATE_DIR
53
- Path to a custom template directory. Templates are searched in the following order: 1) Directory containing the plain file, 2) Custom template directory (if provided through this argument), 3)
54
- Built-in standard_template_library directory
55
- --copy-build If set, copy the rendered contents of code in `--base-folder` folder to `--build-dest` folder after successful rendering.
61
+ Path to a custom template directory. Templates are searched in the following order: 1) Directory containing the plain
62
+ file, 2) Custom template directory (if provided through this argument), 3) Built-in standard_template_library
63
+ directory
64
+ --copy-build If set, copy the rendered contents of code in `--base-folder` folder to `--build-dest` folder after successful
65
+ rendering.
56
66
  --build-dest BUILD_DEST
57
67
  Target folder to copy rendered contents of code to (used only if --copy-build is set).
58
68
  --copy-conformance-tests
59
- If set, copy the conformance tests of code in `--conformance-tests-folder` folder to `--conformance-tests-dest` folder successful rendering. Requires --conformance-tests-script.
69
+ If set, copy the conformance tests of code in `--conformance-tests-folder` folder to `--conformance-tests-dest`
70
+ folder successful rendering. Requires --conformance-tests-script.
60
71
  --conformance-tests-dest CONFORMANCE_TESTS_DEST
61
72
  Target folder to copy conformance tests of code to (used only if --copy-conformance-tests is set).
62
73
  --render-machine-graph
63
74
  If set, render the state machine graph.
64
- --logging-config-path
75
+ --logging-config-path LOGGING_CONFIG_PATH
65
76
  Path to the logging configuration file.
66
- --headless Run in headless mode: no TUI, no terminal output except a single render-started message. All logs are written to the log file.
77
+ --headless Run in headless mode: no TUI, no terminal output except a single render-started message. All logs are written to the
78
+ log file.
67
79
 
68
80
  configuration:
69
81
  --config-name CONFIG_NAME
70
- Path to the config file, defaults to config.yaml
82
+ Name of the config file to look for. Looked up in the plain file directory and the current working directory.
83
+ Defaults to config.yaml.
71
84
 
72
85
  ```
@@ -7,11 +7,6 @@ from plain2code_events import BaseEvent
7
7
  class EventBus:
8
8
  def __init__(self):
9
9
  self._listeners: defaultdict[Type[BaseEvent], list[Callable[[Any], None]]] = defaultdict(list)
10
- self._dispatch_wrapper: Callable[[Callable], None] | None = None
11
-
12
- def register_dispatch_wrapper(self, fn: Callable[[Callable], None]):
13
- """Set a wrapper for dispatching listeners (e.g., Textual's app.call_from_thread)."""
14
- self._dispatch_wrapper = fn
15
10
 
16
11
  def subscribe(self, event_type: Type[BaseEvent], listener: Callable[[Any], None]):
17
12
  """Registers a listener for a specific event type."""
@@ -19,12 +14,5 @@ class EventBus:
19
14
 
20
15
  def publish(self, event: BaseEvent):
21
16
  """Publishes an event to all registered listeners."""
22
-
23
- def _dispatch():
24
- for listener in self._listeners[type(event)]:
25
- listener(event)
26
-
27
- if self._dispatch_wrapper:
28
- self._dispatch_wrapper(_dispatch)
29
- else:
30
- _dispatch()
17
+ for listener in self._listeners[type(event)]:
18
+ listener(event)
@@ -7,6 +7,7 @@ from liquid2 import Environment, FileSystemLoader, StrictUndefined
7
7
  from liquid2.exceptions import UndefinedError
8
8
 
9
9
  import plain_spec
10
+ from plain2code_console import console
10
11
  from plain2code_nodes import Plain2CodeIncludeTag, Plain2CodeLoaderMixin
11
12
  from plain_modules import CODEPLAIN_MEMORY_SUBFOLDER, CODEPLAIN_METADATA_FOLDER
12
13
 
@@ -79,7 +80,7 @@ def list_all_text_files(directory):
79
80
  with open(os.path.join(root, filename), "rb") as f:
80
81
  f.read().decode("utf-8")
81
82
  except UnicodeDecodeError:
82
- print(f"WARNING! Not listing {filename} in {root}. File is not a text file. Skipping it.")
83
+ console.debug(f"WARNING! Not listing {filename} in {root}. File is not a text file. Skipping it.")
83
84
  continue
84
85
 
85
86
  all_files.append(os.path.join(modified_root, filename))
@@ -178,7 +179,7 @@ def get_existing_files_content(build_folder, existing_files):
178
179
  try:
179
180
  existing_files_content[file_name] = content.decode("utf-8")
180
181
  except UnicodeDecodeError:
181
- print(f"WARNING! Error loading {file_name}. File is not a text file. Skipping it.")
182
+ console.debug(f"WARNING! Error loading {file_name}. File is not a text file. Skipping it.")
182
183
 
183
184
  return existing_files_content
184
185
 
@@ -193,7 +194,7 @@ def store_response_files(target_folder, response_files, existing_files):
193
194
  os.remove(full_file_name)
194
195
  existing_files.remove(file_name)
195
196
  else:
196
- print(f"WARNING! Cannot delete file! File {full_file_name} does not exist.")
197
+ console.debug(f"WARNING! Cannot delete file! File {full_file_name} does not exist.")
197
198
 
198
199
  continue
199
200
 
@@ -219,7 +220,9 @@ def open_from(dirs, file_name):
219
220
  try:
220
221
  content_text = content.decode("utf-8")
221
222
  except UnicodeDecodeError:
222
- print(f"WARNING! Error loading {file_name} ({file_name}). File is not a text file. Skipping it.")
223
+ console.debug(
224
+ f"WARNING! Error loading {file_name} ({file_name}). File is not a text file. Skipping it."
225
+ )
223
226
  return content_text
224
227
 
225
228
  return None
@@ -7,22 +7,20 @@ from git import Repo
7
7
  import file_utils
8
8
  from plain2code_exceptions import InvalidGitRepositoryError
9
9
 
10
- FUNCTIONAL_REQUIREMENT_IMPLEMENTED_COMMIT_MESSAGE = (
11
- "[Codeplain] Implemented code and unit tests for functional requirement {}"
12
- )
13
- REFACTORED_CODE_COMMIT_MESSAGE = "[Codeplain] Refactored code after implementing functional requirement {}"
10
+ FUNCTIONAL_REQUIREMENT_IMPLEMENTED_COMMIT_MESSAGE = "[Codeplain] Implemented code and unit tests for functionality {}"
11
+ REFACTORED_CODE_COMMIT_MESSAGE = "[Codeplain] Refactored code after implementing functionality {}"
14
12
  CONFORMANCE_TESTS_PASSED_COMMIT_MESSAGE = (
15
13
  "[Codeplain] Fixed issues in the implementation code identified during conformance testing"
16
14
  )
17
15
 
18
16
  # Following messages are used as checkpoints in the git history
19
17
  # Changing them will break backwards compatibility so change them with care
20
- FUNCTIONAL_REQUIREMENT_FINISHED_COMMIT_MESSAGE = "[Codeplain] Functional requirement ID (FRID):{} fully implemented"
18
+ FUNCTIONAL_REQUIREMENT_FINISHED_COMMIT_MESSAGE = "[Codeplain] functionality ID (FRID):{} fully implemented"
21
19
  INITIAL_COMMIT_MESSAGE = "[Codeplain] Initial module commit"
22
20
  BASE_FOLDER_COMMIT_MESSAGE = "[Codeplain] Initialize build with Base Folder content"
23
21
 
24
22
 
25
- RENDERED_FRID_MESSAGE = "Changes related to Functional requirement ID (FRID): {}"
23
+ RENDERED_FRID_MESSAGE = "Changes related to functionality ID (FRID): {}"
26
24
  MODULE_NAME_MESSAGE = "Module name: {}"
27
25
  RENDER_ID_MESSAGE = "Render ID: {}"
28
26
 
@@ -244,7 +242,7 @@ def diff(repo_path: Union[str, os.PathLike], previous_frid: str = None) -> dict:
244
242
 
245
243
  Args:
246
244
  repo_path (str | os.PathLike): Path to the git repository
247
- previous_frid (str): Functional requirement ID (FRID) of the previous commit
245
+ previous_frid (str): functionality ID (FRID) of the previous commit
248
246
 
249
247
  Returns:
250
248
  dict: Dictionary with file names as keys and their clean diff strings as values
@@ -285,7 +283,7 @@ def _get_commit_with_frid(repo: Repo, frid: str, module_name: Optional[str] = No
285
283
 
286
284
  Args:
287
285
  repo (Repo): Git repository object
288
- frid (str): Functional requirement ID
286
+ frid (str): functionality ID
289
287
  module_name (Optional[str]): Module name to filter by. If provided, only returns
290
288
  commits that have both the FRID and module name.
291
289
 
@@ -223,7 +223,7 @@ echo -e " thank you for using ${YELLOW}*codeplain!${NC}"
223
223
  echo ""
224
224
  echo -e " ${BOLD}next steps:${NC}"
225
225
  echo ""
226
- echo -e " join our Discord community: ${YELLOW}https://discord.gg/4qQJaMu7Y${NC}"
226
+ echo -e " join our Discord community: ${YELLOW}https://discord.gg/cgbynb9hFq${NC}"
227
227
  echo ""
228
228
  echo -e " learn more about ${YELLOW}***plain${NC} at ${YELLOW}https://plainlang.org/${NC}"
229
229
  echo ""
@@ -255,7 +255,7 @@ Write-Host " thank you for using ${YELLOW}*codeplain!${NC}"
255
255
  Write-Host ""
256
256
  Write-Host " ${BOLD}next steps:${NC}"
257
257
  Write-Host ""
258
- Write-Host " join our Discord community: ${YELLOW}https://discord.gg/4qQJaMu7Y${NC}"
258
+ Write-Host " join our Discord community: ${YELLOW}https://discord.gg/cgbynb9hFq${NC}"
259
259
  Write-Host ""
260
260
  Write-Host " learn more about ${YELLOW}***plain${NC} at ${YELLOW}https://plainlang.org/${NC}"
261
261
  Write-Host ""
@@ -21,7 +21,7 @@ class MemoryManager:
21
21
  return [], {}
22
22
  memory_files = file_utils.list_all_text_files(memory_path)
23
23
  memory_files_content = file_utils.get_existing_files_content(memory_path, memory_files)
24
- console.info(f"Loaded {len(memory_files_content)} memory files.")
24
+ console.debug(f"Loaded {len(memory_files_content)} memory files.")
25
25
  return memory_files, memory_files_content
26
26
 
27
27
  def __init__(self, codeplain_api, module_build_folder: str):
@@ -1,5 +1,6 @@
1
1
  import argparse
2
2
  import os
3
+ import threading
3
4
 
4
5
  import git_utils
5
6
  import plain_file
@@ -28,6 +29,8 @@ class ModuleRenderer:
28
29
  args: argparse.Namespace,
29
30
  run_state: RunState,
30
31
  event_bus: EventBus,
32
+ stop_event: threading.Event | None = None,
33
+ enter_pause_event: threading.Event | None = None,
31
34
  ):
32
35
  self.codeplainAPI = codeplainAPI
33
36
  self.filename = filename
@@ -36,6 +39,8 @@ class ModuleRenderer:
36
39
  self.args = args
37
40
  self.run_state = run_state
38
41
  self.event_bus = event_bus
42
+ self.stop_event = stop_event
43
+ self.enter_pause_event = enter_pause_event
39
44
 
40
45
  def _ensure_module_folders_exist(self, module_name: str, first_render_frid: str) -> tuple[str, str]:
41
46
  """
@@ -177,6 +182,8 @@ class ModuleRenderer:
177
182
  run_state=self.run_state,
178
183
  event_bus=self.event_bus,
179
184
  test_script_timeout=self.args.test_script_timeout,
185
+ stop_event=self.stop_event,
186
+ enter_pause_event=self.enter_pause_event,
180
187
  )
181
188
 
182
189
  def _render_module(
@@ -199,7 +206,7 @@ class ModuleRenderer:
199
206
  required_modules = []
200
207
  has_any_required_module_changed = False
201
208
  if not self.args.render_machine_graph and required_modules_list:
202
- console.info(f"Analyzing required modules of module {module_name}...")
209
+ console.debug(f"Analyzing required modules of module {module_name}...")
203
210
  for required_module_name in required_modules_list:
204
211
  required_module_filename = required_module_name + plain_file.PLAIN_SOURCE_FILE_EXTENSION
205
212
  has_module_changed, sub_required_modules, rendering_failed = self._render_module(
@@ -2,6 +2,7 @@ import importlib.resources
2
2
  import logging
3
3
  import logging.config
4
4
  import os
5
+ import signal
5
6
  import sys
6
7
  import threading
7
8
  from pathlib import Path
@@ -22,7 +23,6 @@ from plain2code_console import console
22
23
  from plain2code_events import RenderFailed
23
24
  from plain2code_exceptions import (
24
25
  ConflictingRequirements,
25
- CreditBalanceTooLow,
26
26
  InternalClientError,
27
27
  InternalServerError,
28
28
  InvalidAPIKey,
@@ -35,11 +35,13 @@ from plain2code_exceptions import (
35
35
  NetworkConnectionError,
36
36
  OutdatedClientVersion,
37
37
  PlainSyntaxError,
38
+ RenderCancelledError,
39
+ RenderingCreditBalanceTooLow,
38
40
  )
39
41
  from plain2code_logger import (
42
+ LOGGER_NAME,
40
43
  CrashLogHandler,
41
44
  IndentedFormatter,
42
- RetryOnlyFilter,
43
45
  TuiLoggingHandler,
44
46
  dump_crash_logs,
45
47
  get_log_file_path,
@@ -50,6 +52,7 @@ from system_config import system_config
50
52
  from tui.plain2code_tui import Plain2CodeTUI
51
53
 
52
54
  DEFAULT_TEMPLATE_DIRS = importlib.resources.files("standard_template_library")
55
+ RENDER_THREAD_SHUTDOWN_TIMEOUT = 0.7
53
56
 
54
57
 
55
58
  def get_render_range(render_range, plain_source):
@@ -86,12 +89,12 @@ def _get_frids_range(plain_source, start, end=None):
86
89
  start = str(start)
87
90
 
88
91
  if start not in frids:
89
- raise InvalidFridArgument(f"Invalid start functional requirement ID: {start}. Valid IDs are: {frids}.")
92
+ raise InvalidFridArgument(f"Invalid start functionality ID: {start}. Valid IDs are: {frids}.")
90
93
 
91
94
  if end is not None:
92
95
  end = str(end)
93
96
  if end not in frids:
94
- raise InvalidFridArgument(f"Invalid end functional requirement ID: {end}. Valid IDs are: {frids}.")
97
+ raise InvalidFridArgument(f"Invalid end functionality ID: {end}. Valid IDs are: {frids}.")
95
98
 
96
99
  end_idx = frids.index(end) + 1
97
100
  else:
@@ -99,9 +102,7 @@ def _get_frids_range(plain_source, start, end=None):
99
102
 
100
103
  start_idx = frids.index(start)
101
104
  if start_idx >= end_idx:
102
- raise InvalidFridArgument(
103
- f"Start functional requirement ID: {start} must be before end functional requirement ID: {end}."
104
- )
105
+ raise InvalidFridArgument(f"Start functionality ID: {start} must be before end functionality ID: {end}.")
105
106
 
106
107
  return frids[start_idx:end_idx]
107
108
 
@@ -112,20 +113,12 @@ def setup_logging(
112
113
  log_to_file: bool,
113
114
  log_file_name: str,
114
115
  plain_file_path: Optional[str],
115
- render_id: str,
116
116
  headless: bool = False,
117
117
  ):
118
118
  # Set default level to INFO for everything not explicitly configured
119
119
  logging.getLogger().setLevel(logging.INFO)
120
- logging.getLogger("urllib3").setLevel(logging.WARNING)
121
- logging.getLogger("httpx").setLevel(logging.WARNING)
122
- logging.getLogger("httpcore").setLevel(logging.WARNING)
123
- logging.getLogger("anthropic").setLevel(logging.WARNING)
124
- logging.getLogger("langsmith").setLevel(logging.WARNING)
120
+ logging.getLogger(LOGGER_NAME).setLevel(logging.INFO)
125
121
  logging.getLogger("git").setLevel(logging.WARNING)
126
- logging.getLogger("anthropic._base_client").setLevel(logging.WARNING)
127
- logging.getLogger("services.langsmith.langsmith_service").setLevel(logging.WARNING)
128
- logging.getLogger("repositories").setLevel(logging.WARNING)
129
122
  logging.getLogger("transitions").setLevel(logging.ERROR)
130
123
  logging.getLogger("transitions.extensions.diagrams").setLevel(logging.ERROR)
131
124
 
@@ -141,18 +134,11 @@ def setup_logging(
141
134
  except Exception as e:
142
135
  console.warning(f"Failed to load logging configuration from {args.logging_config_path}: {str(e)}")
143
136
 
144
- # Allow detailed retry logs for anthropic if needed
145
- logging.getLogger("anthropic._base_client").setLevel(logging.DEBUG)
146
- if logging.getLogger("anthropic._base_client").level == logging.DEBUG:
147
- logging.getLogger("anthropic._base_client").addFilter(RetryOnlyFilter())
148
-
149
137
  # The IndentedFormatter provides better multiline log readability.
150
138
  # We add the TuiLoggingHandler to the root logger.
151
- # CRITICAL: We must remove existing handlers (like StreamHandler) to prevent double-logging
152
- # that spills into the TUI dashboard.
153
- root_logger = logging.getLogger()
154
- for h in root_logger.handlers[:]:
155
- root_logger.removeHandler(h)
139
+ root_logger = logging.getLogger(LOGGER_NAME)
140
+ configured_log_level = root_logger.level
141
+ root_logger.setLevel(logging.DEBUG) # Capture all logs; handlers will filter levels as needed
156
142
 
157
143
  formatter = IndentedFormatter("%(levelname)s:%(name)s:%(message)s")
158
144
 
@@ -165,6 +151,7 @@ def setup_logging(
165
151
  try:
166
152
  file_handler = logging.FileHandler(log_file_path, mode="w")
167
153
  file_handler.setFormatter(formatter)
154
+ file_handler.setLevel(configured_log_level)
168
155
  root_logger.addHandler(file_handler)
169
156
  except Exception as e:
170
157
  console.warning(f"Failed to setup file logging to {log_file_path}: {str(e)}")
@@ -173,10 +160,9 @@ def setup_logging(
173
160
  # in case we need to dump them on crash.
174
161
  crash_handler = CrashLogHandler()
175
162
  crash_handler.setFormatter(formatter)
163
+ crash_handler.setLevel(configured_log_level)
176
164
  root_logger.addHandler(crash_handler)
177
165
 
178
- root_logger.info(f"Render ID: {render_id}") # Ensure render ID is logged in to codeplain.log file
179
-
180
166
 
181
167
  def _check_connection(codeplainAPI: codeplain_api.CodeplainAPI):
182
168
  """Check API connectivity and validate API key and client version."""
@@ -216,6 +202,10 @@ def render(args, run_state: RunState, event_bus: EventBus): # noqa: C901
216
202
 
217
203
  _check_connection(codeplainAPI)
218
204
 
205
+ stop_event = threading.Event()
206
+ enter_pause_event = threading.Event()
207
+ signal.signal(signal.SIGTERM, lambda _signum, _frame: stop_event.set())
208
+
219
209
  module_renderer = ModuleRenderer(
220
210
  codeplainAPI,
221
211
  args.filename,
@@ -224,6 +214,8 @@ def render(args, run_state: RunState, event_bus: EventBus): # noqa: C901
224
214
  args,
225
215
  run_state,
226
216
  event_bus,
217
+ stop_event=stop_event,
218
+ enter_pause_event=enter_pause_event,
227
219
  )
228
220
 
229
221
  render_error: list[Exception] = []
@@ -231,13 +223,19 @@ def render(args, run_state: RunState, event_bus: EventBus): # noqa: C901
231
223
  def run_render():
232
224
  try:
233
225
  module_renderer.render_module()
226
+ console.info(f"[#79FC96]Render {run_state.render_id} completed successfully.[/#79FC96]")
227
+ except RenderCancelledError:
228
+ pass # TUI already closed, nothing to report
234
229
  except Exception as e:
235
230
  render_error.append(e)
236
231
  event_bus.publish(RenderFailed(error_message=str(e)))
237
232
 
238
233
  if args.headless:
239
- print(f"Render started. Render ID: {run_state.render_id}")
240
- module_renderer.render_module()
234
+ console.info(f"Render started. Render ID: {run_state.render_id}")
235
+ try:
236
+ module_renderer.render_module()
237
+ except RenderCancelledError:
238
+ pass
241
239
  return
242
240
  else:
243
241
  render_thread = threading.Thread(target=run_render, daemon=True)
@@ -249,10 +247,12 @@ def render(args, run_state: RunState, event_bus: EventBus): # noqa: C901
249
247
  conformance_tests_script=args.conformance_tests_script,
250
248
  prepare_environment_script=args.prepare_environment_script,
251
249
  state_machine_version=system_config.client_version,
250
+ enter_pause_event=enter_pause_event,
252
251
  css_path="styles.css",
253
252
  )
254
253
  app.run()
255
- render_thread.join(timeout=1)
254
+ stop_event.set()
255
+ render_thread.join(timeout=RENDER_THREAD_SHUTDOWN_TIMEOUT)
256
256
 
257
257
  if render_error:
258
258
  raise render_error[0]
@@ -295,9 +295,7 @@ def main(): # noqa: C901
295
295
  # Suppress Rich console output.
296
296
  console.quiet = True
297
297
 
298
- setup_logging(
299
- args, event_bus, args.log_to_file, args.log_file_name, args.filename, run_state.render_id, args.headless
300
- )
298
+ setup_logging(args, event_bus, args.log_to_file, args.log_file_name, args.filename, args.headless)
301
299
 
302
300
  exc_info = None
303
301
  try:
@@ -307,7 +305,7 @@ def main(): # noqa: C901
307
305
  "API key is required. Please set the CODEPLAIN_API_KEY environment variable or provide it with the --api-key argument."
308
306
  )
309
307
 
310
- console.debug(f"Render ID: {run_state.render_id}") # Ensure render ID is logged to the console
308
+ console.info(f"Render ID: {run_state.render_id}")
311
309
  render(args, run_state, event_bus)
312
310
  except InvalidFridArgument as e:
313
311
  exc_info = sys.exc_info()
@@ -351,7 +349,7 @@ def main(): # noqa: C901
351
349
  exc_info = sys.exc_info()
352
350
  console.error(f"Conflicting requirements: {str(e)}\n")
353
351
  console.debug(f"Render ID: {run_state.render_id}")
354
- except CreditBalanceTooLow as e:
352
+ except RenderingCreditBalanceTooLow as e:
355
353
  exc_info = sys.exc_info()
356
354
  console.error(f"Credit balance too low: {str(e)}\n")
357
355
  console.debug(f"Render ID: {run_state.render_id}")