codeplain 0.3.1__tar.gz → 0.3.3__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 (232) hide show
  1. {codeplain-0.3.1 → codeplain-0.3.3}/.claude/settings.json +5 -0
  2. {codeplain-0.3.1 → codeplain-0.3.3}/PKG-INFO +25 -23
  3. {codeplain-0.3.1 → codeplain-0.3.3}/README.md +23 -22
  4. codeplain-0.3.3/_version.py +1 -0
  5. {codeplain-0.3.1 → codeplain-0.3.3}/cli_output/render_summary.py +6 -1
  6. {codeplain-0.3.1 → codeplain-0.3.3}/cli_output/status.py +14 -9
  7. {codeplain-0.3.1 → codeplain-0.3.3}/codeplain_REST_api.py +1 -1
  8. {codeplain-0.3.1 → codeplain-0.3.3}/concept_utils.py +0 -1
  9. {codeplain-0.3.1 → codeplain-0.3.3}/file_utils.py +18 -62
  10. {codeplain-0.3.1 → codeplain-0.3.3}/plain2code.py +34 -25
  11. {codeplain-0.3.1 → codeplain-0.3.3}/plain2code_exceptions.py +6 -0
  12. {codeplain-0.3.1 → codeplain-0.3.3}/plain2code_state.py +8 -7
  13. codeplain-0.3.3/plain2code_telemetry.py +114 -0
  14. codeplain-0.3.3/plain2code_utils.py +22 -0
  15. {codeplain-0.3.1 → codeplain-0.3.3}/plain_file.py +6 -0
  16. {codeplain-0.3.1 → codeplain-0.3.3}/plain_modules.py +6 -2
  17. {codeplain-0.3.1 → codeplain-0.3.3}/pyproject.toml +1 -0
  18. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/actions/run_conformance_tests.py +4 -2
  19. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/actions/run_unit_tests.py +2 -2
  20. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/code_renderer.py +2 -0
  21. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/render_context.py +30 -46
  22. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/render_types.py +0 -10
  23. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/state_machine_config.py +22 -63
  24. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/triggers.py +1 -1
  25. {codeplain-0.3.1 → codeplain-0.3.3}/requirements.txt +1 -0
  26. {codeplain-0.3.1 → codeplain-0.3.3}/system_config.py +0 -6
  27. codeplain-0.3.3/tests/data/plainfile/add_new_task_modal_specification.yaml +1 -0
  28. codeplain-0.3.3/tests/data/plainfile/task_list_ui_specification.yaml +1 -0
  29. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfile/task_manager_with_reference_links.plain +3 -3
  30. {codeplain-0.3.1 → codeplain-0.3.3}/tests/test_cli_output.py +128 -26
  31. {codeplain-0.3.1 → codeplain-0.3.3}/tests/test_plainfileparser.py +4 -4
  32. codeplain-0.3.3/tests/test_telemetry.py +204 -0
  33. {codeplain-0.3.1 → codeplain-0.3.3}/tui/components.py +0 -6
  34. {codeplain-0.3.1 → codeplain-0.3.3}/tui/plain2code_tui.py +4 -0
  35. {codeplain-0.3.1 → codeplain-0.3.3}/tui/plain_module_render_choice_tui.py +9 -0
  36. {codeplain-0.3.1 → codeplain-0.3.3}/tui/styles.css +1 -1
  37. {codeplain-0.3.1 → codeplain-0.3.3}/tui/widget_helpers.py +0 -18
  38. codeplain-0.3.1/.claude/settings.local.json +0 -66
  39. codeplain-0.3.1/_version.py +0 -1
  40. codeplain-0.3.1/hash_key.py +0 -29
  41. codeplain-0.3.1/plain2code_utils.py +0 -56
  42. codeplain-0.3.1/render_machine/conformance_test_helpers.py +0 -68
  43. {codeplain-0.3.1 → codeplain-0.3.3}/.flake8 +0 -0
  44. {codeplain-0.3.1 → codeplain-0.3.3}/.github/dependabot.yml +0 -0
  45. {codeplain-0.3.1 → codeplain-0.3.3}/.github/workflows/e2e.yml +0 -0
  46. {codeplain-0.3.1 → codeplain-0.3.3}/.github/workflows/lint-and-test.yml +0 -0
  47. {codeplain-0.3.1 → codeplain-0.3.3}/.github/workflows/nofity-slack-on-main-merge.yml +0 -0
  48. {codeplain-0.3.1 → codeplain-0.3.3}/.github/workflows/publish-install-script.yml +0 -0
  49. {codeplain-0.3.1 → codeplain-0.3.3}/.github/workflows/publish-to-pypi.yml +0 -0
  50. {codeplain-0.3.1 → codeplain-0.3.3}/.gitignore +0 -0
  51. {codeplain-0.3.1 → codeplain-0.3.3}/.vscode/launch.json +0 -0
  52. {codeplain-0.3.1 → codeplain-0.3.3}/.vscode/settings.json +0 -0
  53. {codeplain-0.3.1 → codeplain-0.3.3}/CLAUDE.md +0 -0
  54. {codeplain-0.3.1 → codeplain-0.3.3}/LICENSE +0 -0
  55. {codeplain-0.3.1 → codeplain-0.3.3}/cli_output/__init__.py +0 -0
  56. {codeplain-0.3.1 → codeplain-0.3.3}/cli_output/dry_run.py +0 -0
  57. {codeplain-0.3.1 → codeplain-0.3.3}/config/__init__.py +0 -0
  58. {codeplain-0.3.1 → codeplain-0.3.3}/config/system_config.yaml +0 -0
  59. {codeplain-0.3.1 → codeplain-0.3.3}/diff_utils.py +0 -0
  60. {codeplain-0.3.1 → codeplain-0.3.3}/docs/generate_cli.py +0 -0
  61. {codeplain-0.3.1 → codeplain-0.3.3}/docs/plain2code_cli.md +0 -0
  62. {codeplain-0.3.1 → codeplain-0.3.3}/docs/starting_a_plain_project_from_scratch.md +0 -0
  63. {codeplain-0.3.1 → codeplain-0.3.3}/event_bus.py +0 -0
  64. {codeplain-0.3.1 → codeplain-0.3.3}/examples/example_hello_world_golang/config.yaml +0 -0
  65. {codeplain-0.3.1 → codeplain-0.3.3}/examples/example_hello_world_golang/harness_tests/hello_world_test.go +0 -0
  66. {codeplain-0.3.1 → codeplain-0.3.3}/examples/example_hello_world_golang/hello_world_golang.plain +0 -0
  67. {codeplain-0.3.1 → codeplain-0.3.3}/examples/example_hello_world_golang/run.sh +0 -0
  68. {codeplain-0.3.1 → codeplain-0.3.3}/examples/example_hello_world_python/config.yaml +0 -0
  69. {codeplain-0.3.1 → codeplain-0.3.3}/examples/example_hello_world_python/harness_tests/hello_world_display/test_hello_world.py +0 -0
  70. {codeplain-0.3.1 → codeplain-0.3.3}/examples/example_hello_world_python/hello_world_python.plain +0 -0
  71. {codeplain-0.3.1 → codeplain-0.3.3}/examples/example_hello_world_python/run.sh +0 -0
  72. {codeplain-0.3.1 → codeplain-0.3.3}/examples/example_hello_world_react/config.yaml +0 -0
  73. {codeplain-0.3.1 → codeplain-0.3.3}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress/e2e/hello_world.cy.ts +0 -0
  74. {codeplain-0.3.1 → codeplain-0.3.3}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress/support/e2e.ts +0 -0
  75. {codeplain-0.3.1 → codeplain-0.3.3}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress.config.ts +0 -0
  76. {codeplain-0.3.1 → codeplain-0.3.3}/examples/example_hello_world_react/harness_tests/hello_world_display/package.json +0 -0
  77. {codeplain-0.3.1 → codeplain-0.3.3}/examples/example_hello_world_react/harness_tests/hello_world_display/tsconfig.json +0 -0
  78. {codeplain-0.3.1 → codeplain-0.3.3}/examples/example_hello_world_react/hello_world_react.plain +0 -0
  79. {codeplain-0.3.1 → codeplain-0.3.3}/examples/example_hello_world_react/run.sh +0 -0
  80. {codeplain-0.3.1 → codeplain-0.3.3}/examples/run.sh +0 -0
  81. {codeplain-0.3.1 → codeplain-0.3.3}/git_utils.py +0 -0
  82. {codeplain-0.3.1 → codeplain-0.3.3}/install/bash/examples.sh +0 -0
  83. {codeplain-0.3.1 → codeplain-0.3.3}/install/bash/install.sh +0 -0
  84. {codeplain-0.3.1 → codeplain-0.3.3}/install/bash/walkthrough.sh +0 -0
  85. {codeplain-0.3.1 → codeplain-0.3.3}/install/powershell/examples.ps1 +0 -0
  86. {codeplain-0.3.1 → codeplain-0.3.3}/install/powershell/install.ps1 +0 -0
  87. {codeplain-0.3.1 → codeplain-0.3.3}/install/powershell/walkthrough.ps1 +0 -0
  88. {codeplain-0.3.1 → codeplain-0.3.3}/memory_management.py +0 -0
  89. {codeplain-0.3.1 → codeplain-0.3.3}/module_renderer.py +0 -0
  90. {codeplain-0.3.1 → codeplain-0.3.3}/partial_rendering.py +0 -0
  91. {codeplain-0.3.1 → codeplain-0.3.3}/plain2code_arguments.py +0 -0
  92. {codeplain-0.3.1 → codeplain-0.3.3}/plain2code_console.py +0 -0
  93. {codeplain-0.3.1 → codeplain-0.3.3}/plain2code_events.py +0 -0
  94. {codeplain-0.3.1 → codeplain-0.3.3}/plain2code_logger.py +0 -0
  95. {codeplain-0.3.1 → codeplain-0.3.3}/plain2code_nodes.py +0 -0
  96. {codeplain-0.3.1 → codeplain-0.3.3}/plain2code_read_config.py +0 -0
  97. {codeplain-0.3.1 → codeplain-0.3.3}/plain_spec.py +0 -0
  98. {codeplain-0.3.1 → codeplain-0.3.3}/pytest.ini +0 -0
  99. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/__init__.py +0 -0
  100. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/actions/analyze_specification_ambiguity.py +0 -0
  101. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/actions/base_action.py +0 -0
  102. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/actions/commit_conformance_tests_changes.py +0 -0
  103. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/actions/commit_implementation_code_changes.py +0 -0
  104. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/actions/create_dist.py +0 -0
  105. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/actions/exit_with_error.py +0 -0
  106. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/actions/finish_functional_requirement.py +0 -0
  107. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/actions/fix_conformance_test.py +0 -0
  108. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/actions/fix_unit_tests.py +0 -0
  109. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/actions/prepare_repositories.py +0 -0
  110. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/actions/prepare_testing_environment.py +0 -0
  111. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/actions/refactor_code.py +0 -0
  112. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/actions/render_conformance_tests.py +0 -0
  113. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/actions/render_functional_requirement.py +0 -0
  114. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/actions/summarize_conformance_tests.py +0 -0
  115. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/conformance_tests.py +0 -0
  116. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/implementation_code_helpers.py +0 -0
  117. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/render_utils.py +0 -0
  118. {codeplain-0.3.1 → codeplain-0.3.3}/render_machine/states.py +0 -0
  119. {codeplain-0.3.1 → codeplain-0.3.3}/resources/codeplain_overview.png +0 -0
  120. {codeplain-0.3.1 → codeplain-0.3.3}/resources/plain_example.png +0 -0
  121. {codeplain-0.3.1 → codeplain-0.3.3}/standard_template_library/__init__.py +0 -0
  122. {codeplain-0.3.1 → codeplain-0.3.3}/standard_template_library/golang-console-app-template.plain +0 -0
  123. {codeplain-0.3.1 → codeplain-0.3.3}/standard_template_library/python-console-app-template.plain +0 -0
  124. {codeplain-0.3.1 → codeplain-0.3.3}/standard_template_library/typescript-react-app-boilerplate.plain +0 -0
  125. {codeplain-0.3.1 → codeplain-0.3.3}/standard_template_library/typescript-react-app-template.plain +0 -0
  126. {codeplain-0.3.1 → codeplain-0.3.3}/test_scripts/run_conformance_tests_cypress.ps1 +0 -0
  127. {codeplain-0.3.1 → codeplain-0.3.3}/test_scripts/run_conformance_tests_cypress.sh +0 -0
  128. {codeplain-0.3.1 → codeplain-0.3.3}/test_scripts/run_conformance_tests_golang.ps1 +0 -0
  129. {codeplain-0.3.1 → codeplain-0.3.3}/test_scripts/run_conformance_tests_golang.sh +0 -0
  130. {codeplain-0.3.1 → codeplain-0.3.3}/test_scripts/run_conformance_tests_python.ps1 +0 -0
  131. {codeplain-0.3.1 → codeplain-0.3.3}/test_scripts/run_conformance_tests_python.sh +0 -0
  132. {codeplain-0.3.1 → codeplain-0.3.3}/test_scripts/run_unittests_flutter.ps1 +0 -0
  133. {codeplain-0.3.1 → codeplain-0.3.3}/test_scripts/run_unittests_golang.ps1 +0 -0
  134. {codeplain-0.3.1 → codeplain-0.3.3}/test_scripts/run_unittests_golang.sh +0 -0
  135. {codeplain-0.3.1 → codeplain-0.3.3}/test_scripts/run_unittests_python.ps1 +0 -0
  136. {codeplain-0.3.1 → codeplain-0.3.3}/test_scripts/run_unittests_python.sh +0 -0
  137. {codeplain-0.3.1 → codeplain-0.3.3}/test_scripts/run_unittests_react.ps1 +0 -0
  138. {codeplain-0.3.1 → codeplain-0.3.3}/test_scripts/run_unittests_react.sh +0 -0
  139. {codeplain-0.3.1 → codeplain-0.3.3}/tests/__init__.py +0 -0
  140. {codeplain-0.3.1 → codeplain-0.3.3}/tests/conftest.py +0 -0
  141. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/imports/circular_imports_1.plain +0 -0
  142. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/imports/circular_imports_2.plain +0 -0
  143. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/imports/circular_imports_main.plain +0 -0
  144. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/imports/diamond_import_1.plain +0 -0
  145. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/imports/diamond_import_2.plain +0 -0
  146. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/imports/diamond_import_common.plain +0 -0
  147. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/imports/diamond_imports_main.plain +0 -0
  148. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/imports/non_existent_import.plain +0 -0
  149. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/partial_rendering/pr_leaf.plain +0 -0
  150. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/partial_rendering/pr_middle.plain +0 -0
  151. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/partial_rendering/pr_root.plain +0 -0
  152. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/partial_rendering/pr_solo.plain +0 -0
  153. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfile/duplicate_specification_heading.plain +0 -0
  154. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfile/invalid_specification_order.plain +0 -0
  155. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfile/missing_non_functional_requirements.plain +0 -0
  156. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfile/plain_source_with_absolute_link.plain +0 -0
  157. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfile/plain_source_with_url_link.plain +0 -0
  158. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfile/without_non_functional_requirement.plain +0 -0
  159. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/concept_validation_acceptance_tests.plain +0 -0
  160. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/concept_validation_acceptance_tests_nondefined.plain +0 -0
  161. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/concept_validation_defined_nondefined.plain +0 -0
  162. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/concept_validation_defined_nondefined_2.plain +0 -0
  163. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/concept_validation_definition.plain +0 -0
  164. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/concept_validation_noconcepts.plain +0 -0
  165. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/concept_validation_nonconcept.plain +0 -0
  166. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/concept_validation_nondefined.plain +0 -0
  167. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/concept_validation_redefinition.plain +0 -0
  168. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/concept_validation_several_concepts.plain +0 -0
  169. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/concept_validation_valid.plain +0 -0
  170. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/exported_concepts.plain +0 -0
  171. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/exported_concepts_base.plain +0 -0
  172. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/exported_concepts_example.plain +0 -0
  173. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/exported_concepts_missing.plain +0 -0
  174. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/exported_concepts_missing_example.plain +0 -0
  175. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/exported_concepts_nested.plain +0 -0
  176. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/exported_concepts_nested_example.plain +0 -0
  177. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/exported_concepts_transitive_example.plain +0 -0
  178. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/exported_concepts_transitive_l1.plain +0 -0
  179. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/exported_concepts_transitive_l2.plain +0 -0
  180. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/plain_file_parser_with_comments.plain +0 -0
  181. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/plain_file_with_comments_indented.plain +0 -0
  182. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/regular_plain_source.plain +0 -0
  183. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/required_concepts.plain +0 -0
  184. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/required_concepts_defs.plain +0 -0
  185. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/required_concepts_example.plain +0 -0
  186. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/required_concepts_l1.plain +0 -0
  187. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/required_concepts_l2.plain +0 -0
  188. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/required_concepts_missing.plain +0 -0
  189. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/required_concepts_module.plain +0 -0
  190. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/required_concepts_partial.plain +0 -0
  191. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/required_concepts_partial_duplicate.plain +0 -0
  192. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/topological_sort.plain +0 -0
  193. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/plainfileparser/topological_sort_not_referenced.plain +0 -0
  194. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/requires/circular_requires_main.plain +0 -0
  195. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/requires/circular_requires_sub.plain +0 -0
  196. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/requires/diamond_requires_1.plain +0 -0
  197. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/requires/diamond_requires_2.plain +0 -0
  198. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/requires/diamond_requires_common.plain +0 -0
  199. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/requires/diamond_requires_main.plain +0 -0
  200. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/requires/independent_requires_1.plain +0 -0
  201. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/requires/independent_requires_2.plain +0 -0
  202. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/requires/independent_requires_main.plain +0 -0
  203. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/requires/non_existent_require.plain +0 -0
  204. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/requires/normal_requires_1.plain +0 -0
  205. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/requires/normal_requires_2.plain +0 -0
  206. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/requires/normal_requires_common.plain +0 -0
  207. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/requires/normal_requires_main.plain +0 -0
  208. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/simple.plain +0 -0
  209. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/templates/block_level_include.plain +0 -0
  210. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/templates/code_variables.plain +0 -0
  211. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/templates/header.plain +0 -0
  212. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/templates/implement.plain +0 -0
  213. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/templates/implement_2.plain +0 -0
  214. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/templates/template_include.plain +0 -0
  215. {codeplain-0.3.1 → codeplain-0.3.3}/tests/data/templates/test_hardest_problem.plain +0 -0
  216. {codeplain-0.3.1 → codeplain-0.3.3}/tests/e2e/Dockerfile +0 -0
  217. {codeplain-0.3.1 → codeplain-0.3.3}/tests/e2e/conftest.py +0 -0
  218. {codeplain-0.3.1 → codeplain-0.3.3}/tests/e2e/test_hello_world_python.py +0 -0
  219. {codeplain-0.3.1 → codeplain-0.3.3}/tests/e2e/test_hello_world_python_windows.py +0 -0
  220. {codeplain-0.3.1 → codeplain-0.3.3}/tests/test_file_utils.py +0 -0
  221. {codeplain-0.3.1 → codeplain-0.3.3}/tests/test_git_utils.py +0 -0
  222. {codeplain-0.3.1 → codeplain-0.3.3}/tests/test_imports.py +0 -0
  223. {codeplain-0.3.1 → codeplain-0.3.3}/tests/test_partial_rendering.py +0 -0
  224. {codeplain-0.3.1 → codeplain-0.3.3}/tests/test_plain_modules.py +0 -0
  225. {codeplain-0.3.1 → codeplain-0.3.3}/tests/test_plainfile.py +0 -0
  226. {codeplain-0.3.1 → codeplain-0.3.3}/tests/test_plainspec.py +0 -0
  227. {codeplain-0.3.1 → codeplain-0.3.3}/tests/test_requires.py +0 -0
  228. {codeplain-0.3.1 → codeplain-0.3.3}/tests/test_resolve_config_file.py +0 -0
  229. {codeplain-0.3.1 → codeplain-0.3.3}/tui/__init__.py +0 -0
  230. {codeplain-0.3.1 → codeplain-0.3.3}/tui/models.py +0 -0
  231. {codeplain-0.3.1 → codeplain-0.3.3}/tui/spinner.py +0 -0
  232. {codeplain-0.3.1 → codeplain-0.3.3}/tui/state_handlers.py +0 -0
@@ -13,6 +13,11 @@
13
13
  "Bash(git status*)",
14
14
  "Bash(git diff*)",
15
15
  "Bash(git log*)",
16
+ "Bash(gh issue *)",
17
+ "Bash(gh pr *)",
18
+ "Bash(gh api *)",
19
+ "Bash(gh repo *)",
20
+ "Bash(gh run *)",
16
21
  "Bash(ls *)",
17
22
  "Bash(cat *)",
18
23
  "Bash(grep *)",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeplain
3
- Version: 0.3.1
3
+ Version: 0.3.3
4
4
  Summary: Transform plain language specifications into working code
5
5
  License-File: LICENSE
6
6
  Classifier: Environment :: Console
@@ -16,6 +16,7 @@ Requires-Dist: python-liquid2==0.3.0
16
16
  Requires-Dist: pyyaml==6.0.3
17
17
  Requires-Dist: requests==2.34.0
18
18
  Requires-Dist: rich==15.0.0
19
+ Requires-Dist: sentry-sdk==2.62.0
19
20
  Requires-Dist: textual>=7.5.0
20
21
  Requires-Dist: tiktoken==0.12.0
21
22
  Requires-Dist: transitions==0.9.3
@@ -29,31 +30,28 @@ Description-Content-Type: text/markdown
29
30
 
30
31
  # Codeplain plain2code renderer
31
32
 
32
- Render ***plain source to software code using the Codeplain API.
33
+ Render \*\*\*plain source to software code using the Codeplain API.
33
34
 
34
35
  ## Codeplain.ai - Code Generation as a Service
35
36
 
36
- Codeplain is a platform that generates software code using large language models based on requirements you specify in ***plain specification language.
37
+ Codeplain is a platform that generates software code using large language models based on requirements you specify in \*\*\*plain specification language.
37
38
 
38
39
  Schematic overview of the Codeplain's code generation service
39
40
 
40
41
  <img src="https://raw.githubusercontent.com/Codeplain-ai/plain2code_client/main/resources/codeplain_overview.png">
41
42
 
42
- ### Abstracting Away Code Generation Complexity with ***plain
43
+ ### Abstracting Away Code Generation Complexity with \*\*\*plain
43
44
 
45
+ \*\*\*plain is a novel specification language that helps abstracting away complexity of using large language models for code generation.
44
46
 
45
- ***plain is a novel specification language that helps abstracting away complexity of using large language models for code generation.
46
-
47
- An example application in ***plain
47
+ An example application in \*\*\*plain
48
48
 
49
49
  <img src="resources/plain_example.png" width="70%" height="70%">
50
50
 
51
-
52
51
  ## Getting started
53
52
 
54
53
  ### Prerequisites
55
54
 
56
-
57
55
  #### System requirements
58
56
 
59
57
  To run the plain2code client, you need Python 3.11 or a later version.
@@ -92,18 +90,24 @@ export CODEPLAIN_API_KEY="your_actual_api_key_here"
92
90
  After completing the installation steps above, you can immediately test the system with a simple "Hello World" example:
93
91
 
94
92
  - Change to the example folder and run the example:
95
- ```
96
- cd examples/example_hello_world_python
97
- python ../../plain2code.py hello_world_python.plain
98
- ```
99
93
 
100
- *Note: Rendering will take a few minutes to complete.*
94
+ ```
95
+ cd examples/example_hello_world_python
96
+ python ../../plain2code.py hello_world_python.plain
97
+ ```
98
+
99
+ _Note: Rendering will take a few minutes to complete._
101
100
 
102
101
  - The system will generate a Python application in the `build` directory. You can run it with:
103
- ```
104
- cd build
105
- python hello_world.py
106
- ```
102
+ ```
103
+ cd build
104
+ python hello_world.py
105
+ ```
106
+
107
+ ### Crash reporting
108
+
109
+ If the plain2code client crashes unexpectedly, it sends an anonymous crash report to Codeplain to help improve the tool. To disable crash reporting, set the `CODEPLAIN_NO_TELEMETRY=1` flag.
110
+
107
111
 
108
112
  ## Releasing
109
113
 
@@ -112,7 +116,7 @@ Releases are built and published with [uv](https://docs.astral.sh/uv/). The vers
112
116
  1. Bump the version in `_version.py` and commit/tag (`git tag v<version>`).
113
117
  2. Build the distributions:
114
118
  ```bash
115
- uv build
119
+ uv build --python 3.11
116
120
  ```
117
121
  This produces `dist/codeplain-<version>-py3-none-any.whl` and `dist/codeplain-<version>.tar.gz`.
118
122
  3. (Optional) Smoke-test the wheel:
@@ -135,13 +139,11 @@ Releases are built and published with [uv](https://docs.astral.sh/uv/). The vers
135
139
  ### Examples and Sample Projects
136
140
 
137
141
  - See the [examples](examples) folder for sample projects in Golang, Python, and React.
138
- - For example application how to implement task manager in ***plain see [example-task-manager](https://github.com/Codeplain-ai/example-task-manager) repository.
139
- - For example application how to implement SaaS connectors in ***plain see [example-saas-connectors](https://github.com/Codeplain-ai/example-saas-connectors) repository.
142
+ - For example application how to implement task manager in \*\*\*plain see [example-task-manager](https://github.com/Codeplain-ai/example-task-manager) repository.
143
+ - For example application how to implement SaaS connectors in \*\*\*plain see [example-saas-connectors](https://github.com/Codeplain-ai/example-saas-connectors) repository.
140
144
 
141
145
  ### Documentation
142
146
 
143
147
  - For more details on the ***plain format, see the [***plain language specification](https://www.plainlang.org/docs/).
144
148
  - For step-by-step instructions for creating your first ***plain project see the [Kickstart your ***plain project](docs/starting_a_plain_project_from_scratch.md).
145
149
  - For complete CLI documentation and usage examples, see [plain2code CLI documentation](docs/plain2code_cli.md).
146
-
147
-
@@ -1,30 +1,27 @@
1
1
  # Codeplain plain2code renderer
2
2
 
3
- Render ***plain source to software code using the Codeplain API.
3
+ Render \*\*\*plain source to software code using the Codeplain API.
4
4
 
5
5
  ## Codeplain.ai - Code Generation as a Service
6
6
 
7
- Codeplain is a platform that generates software code using large language models based on requirements you specify in ***plain specification language.
7
+ Codeplain is a platform that generates software code using large language models based on requirements you specify in \*\*\*plain specification language.
8
8
 
9
9
  Schematic overview of the Codeplain's code generation service
10
10
 
11
11
  <img src="https://raw.githubusercontent.com/Codeplain-ai/plain2code_client/main/resources/codeplain_overview.png">
12
12
 
13
- ### Abstracting Away Code Generation Complexity with ***plain
13
+ ### Abstracting Away Code Generation Complexity with \*\*\*plain
14
14
 
15
+ \*\*\*plain is a novel specification language that helps abstracting away complexity of using large language models for code generation.
15
16
 
16
- ***plain is a novel specification language that helps abstracting away complexity of using large language models for code generation.
17
-
18
- An example application in ***plain
17
+ An example application in \*\*\*plain
19
18
 
20
19
  <img src="resources/plain_example.png" width="70%" height="70%">
21
20
 
22
-
23
21
  ## Getting started
24
22
 
25
23
  ### Prerequisites
26
24
 
27
-
28
25
  #### System requirements
29
26
 
30
27
  To run the plain2code client, you need Python 3.11 or a later version.
@@ -63,18 +60,24 @@ export CODEPLAIN_API_KEY="your_actual_api_key_here"
63
60
  After completing the installation steps above, you can immediately test the system with a simple "Hello World" example:
64
61
 
65
62
  - Change to the example folder and run the example:
66
- ```
67
- cd examples/example_hello_world_python
68
- python ../../plain2code.py hello_world_python.plain
69
- ```
70
63
 
71
- *Note: Rendering will take a few minutes to complete.*
64
+ ```
65
+ cd examples/example_hello_world_python
66
+ python ../../plain2code.py hello_world_python.plain
67
+ ```
68
+
69
+ _Note: Rendering will take a few minutes to complete._
72
70
 
73
71
  - The system will generate a Python application in the `build` directory. You can run it with:
74
- ```
75
- cd build
76
- python hello_world.py
77
- ```
72
+ ```
73
+ cd build
74
+ python hello_world.py
75
+ ```
76
+
77
+ ### Crash reporting
78
+
79
+ If the plain2code client crashes unexpectedly, it sends an anonymous crash report to Codeplain to help improve the tool. To disable crash reporting, set the `CODEPLAIN_NO_TELEMETRY=1` flag.
80
+
78
81
 
79
82
  ## Releasing
80
83
 
@@ -83,7 +86,7 @@ Releases are built and published with [uv](https://docs.astral.sh/uv/). The vers
83
86
  1. Bump the version in `_version.py` and commit/tag (`git tag v<version>`).
84
87
  2. Build the distributions:
85
88
  ```bash
86
- uv build
89
+ uv build --python 3.11
87
90
  ```
88
91
  This produces `dist/codeplain-<version>-py3-none-any.whl` and `dist/codeplain-<version>.tar.gz`.
89
92
  3. (Optional) Smoke-test the wheel:
@@ -106,13 +109,11 @@ Releases are built and published with [uv](https://docs.astral.sh/uv/). The vers
106
109
  ### Examples and Sample Projects
107
110
 
108
111
  - See the [examples](examples) folder for sample projects in Golang, Python, and React.
109
- - For example application how to implement task manager in ***plain see [example-task-manager](https://github.com/Codeplain-ai/example-task-manager) repository.
110
- - For example application how to implement SaaS connectors in ***plain see [example-saas-connectors](https://github.com/Codeplain-ai/example-saas-connectors) repository.
112
+ - For example application how to implement task manager in \*\*\*plain see [example-task-manager](https://github.com/Codeplain-ai/example-task-manager) repository.
113
+ - For example application how to implement SaaS connectors in \*\*\*plain see [example-saas-connectors](https://github.com/Codeplain-ai/example-saas-connectors) repository.
111
114
 
112
115
  ### Documentation
113
116
 
114
117
  - For more details on the ***plain format, see the [***plain language specification](https://www.plainlang.org/docs/).
115
118
  - For step-by-step instructions for creating your first ***plain project see the [Kickstart your ***plain project](docs/starting_a_plain_project_from_scratch.md).
116
119
  - For complete CLI documentation and usage examples, see [plain2code CLI documentation](docs/plain2code_cli.md).
117
-
118
-
@@ -0,0 +1 @@
1
+ __version__ = "0.3.3"
@@ -15,7 +15,12 @@ def print_exit_summary(
15
15
  """Print render outcome after the TUI exits (terminal restored)."""
16
16
  console.quiet = False
17
17
 
18
- msg = "\n[#79FC96]✓ rendering completed\n\n" if run_state.render_succeeded else "[#FF6B6B]✗ rendering failed\n\n"
18
+ if run_state.render_succeeded:
19
+ msg = "\n[#79FC96]✓ rendering completed\n\n"
20
+ elif run_state.render_cancelled:
21
+ msg = "\n[#FFFFFF]— rendering canceled\n\n"
22
+ else:
23
+ msg = "\n[#FF6B6B]✗ rendering failed\n\n"
19
24
  msg += f" [#8E8F91]render id:\t\t\t[#FFFFFF]{run_state.render_id}\n"
20
25
  msg += f" [#8E8F91]input file:\t\t\t[#FFFFFF]{spec_filename}\n"
21
26
  msg += f" [#8E8F91]generated code folder:\t[#FFFFFF]{run_state.render_generated_code_path or '-'}\n\n"
@@ -50,8 +50,8 @@ def _display_credit_line(plan_credits: dict) -> None:
50
50
  console.print(f" {plan_label:10} {bar} {remaining:2} of {total} remaining expires {formatted_date}")
51
51
 
52
52
 
53
- def _display_purchased_credit_line(bucket: dict) -> None:
54
- """Display a purchased credit line with progress bar."""
53
+ def _display_bucket_credit_line(bucket: dict, label: str) -> None:
54
+ """Display a credit bucket line (purchased or promo) with progress bar."""
55
55
  remaining = bucket["remaining"]
56
56
  total = bucket["total"]
57
57
  expiry_date = bucket["expiry_date"]
@@ -71,12 +71,12 @@ def _display_purchased_credit_line(bucket: dict) -> None:
71
71
  bar = _create_progress_bar(remaining, total, width=30)
72
72
 
73
73
  if is_expired:
74
- console.print(f" Purchased {bar} expired {formatted_date}")
74
+ console.print(f" {label:10} {bar} expired {formatted_date}")
75
75
  else:
76
- console.print(f" Purchased {bar} {remaining:2} of {total} remaining expires {formatted_date}")
76
+ console.print(f" {label:10} {bar} {remaining:2} of {total} remaining expires {formatted_date}")
77
77
 
78
78
 
79
- def _display_status_message(plan_credits: Optional[dict], purchased_credits: list) -> None:
79
+ def _display_status_message(plan_credits: Optional[dict], purchased_credits: list, promo_credits: list) -> None:
80
80
  """Display appropriate status message based on credit state."""
81
81
  has_remaining = False
82
82
 
@@ -93,8 +93,8 @@ def _display_status_message(plan_credits: Optional[dict], purchased_credits: lis
93
93
  if remaining > 0 and period_end > now:
94
94
  has_remaining = True
95
95
 
96
- # Check if any purchased credits have remaining balance and not expired
97
- for bucket in purchased_credits:
96
+ # Check if any purchased or promo credits have remaining balance and not expired
97
+ for bucket in [*purchased_credits, *promo_credits]:
98
98
  dt_str = bucket["expiry_date"].replace("Z", "+00:00")
99
99
  expiry_date = datetime.fromisoformat(dt_str)
100
100
  # If naive datetime, assume UTC
@@ -127,6 +127,7 @@ def print_status(api_key: str, api_url: str, client_version: str) -> None:
127
127
  org_owner = response.get("organization_owner_email")
128
128
  plan_credits = response.get("plan_credits")
129
129
  purchased_credits = response.get("purchased_credits", [])
130
+ promo_credits = response.get("promo_credits", [])
130
131
 
131
132
  # Display header information
132
133
  if client_version_valid:
@@ -153,8 +154,12 @@ def print_status(api_key: str, api_url: str, client_version: str) -> None:
153
154
 
154
155
  # Display purchased credits
155
156
  for bucket in purchased_credits:
156
- _display_purchased_credit_line(bucket)
157
+ _display_bucket_credit_line(bucket, "Purchased")
158
+
159
+ # Display promo credits
160
+ for bucket in promo_credits:
161
+ _display_bucket_credit_line(bucket, "Promo")
157
162
 
158
163
  # Display status messages and management link
159
- _display_status_message(plan_credits, purchased_credits)
164
+ _display_status_message(plan_credits, purchased_credits, promo_credits)
160
165
  console.print("\nTo manage your plan navigate to https://platform.codeplain.ai/plans")
@@ -108,7 +108,7 @@ class CodeplainAPI:
108
108
  if error_code == "InternalServerError":
109
109
  raise plain2code_exceptions.InternalServerError(
110
110
  "Internal server error.\n\n"
111
- f"Please report the error to support@codeplain.ai with the attached {self.log_file_name} file."
111
+ "Please report the error to support@codeplain.ai with the attached .log file."
112
112
  )
113
113
  exception_class = ERROR_CODE_EXCEPTIONS[error_code]
114
114
  raise exception_class(message)
@@ -2,7 +2,6 @@ import re
2
2
  from copy import deepcopy
3
3
  from typing import Optional
4
4
 
5
- import mistletoe
6
5
  import mistletoe.block_token
7
6
  import networkx as nx
8
7
 
@@ -54,15 +54,6 @@ def is_system_folder_path(file_path: str) -> bool:
54
54
  return bool(parts) and parts[0] in SYSTEM_FOLDERS
55
55
 
56
56
 
57
- def get_file_type(file_name):
58
-
59
- # Extract the file extension
60
- ext = Path(file_name).suffix.lower() # Convert to lowercase to handle case-insensitive matching
61
-
62
- # Use the dictionary to get the file type, defaulting to 'unknown' if the extension is not found
63
- return FILE_EXTENSION_MAPPING.get(ext, "unknown")
64
-
65
-
66
57
  def list_all_text_files(directory):
67
58
  all_files = []
68
59
  for root, dirs, files in os.walk(directory, topdown=True):
@@ -99,64 +90,29 @@ def list_folders_in_directory(directory):
99
90
  return folders
100
91
 
101
92
 
102
- # delete a folder and all its subfolders and files
103
- def delete_folder(folder_name):
104
- if os.path.exists(folder_name):
105
- # Use writable-aware deletion so read-only files (e.g. .git objects on Windows) don't cause PermissionError
106
- delete_files_and_subfolders(folder_name)
107
- _make_writable(folder_name)
108
- os.rmdir(folder_name)
93
+ def _on_rm_error(func, path, _exc_info):
94
+ """On Windows, clear read-only flag and retry the removal."""
95
+ os.chmod(path, stat.S_IWRITE)
96
+ func(path)
109
97
 
110
98
 
111
- def _make_writable(path: str) -> None:
112
- """On Windows, clear read-only so deletion can succeed."""
113
- # TODO - Check if this can be done in a cleaner way.
114
- is_windows = os.name == "nt"
115
- if is_windows:
116
- try:
117
- mode = os.stat(path).st_mode
118
- os.chmod(path, mode | stat.S_IWRITE)
119
- except OSError:
120
- pass
99
+ def delete_folder(folder_name):
100
+ """Delete a folder and all its subfolders and files."""
101
+ if os.path.exists(folder_name):
102
+ shutil.rmtree(folder_name, onerror=_on_rm_error)
121
103
 
122
104
 
123
105
  def delete_files_and_subfolders(directory):
124
- total_files_deleted = 0
125
- total_folders_deleted = 0
126
-
127
- # Walk the directory in reverse order (bottom-up)
128
- for root, dirs, files in os.walk(directory, topdown=False):
129
- # Delete files
130
- for file in files:
131
- file_path = os.path.join(root, file)
132
- _make_writable(file_path)
133
- os.remove(file_path)
134
- total_files_deleted += 1
135
-
136
- # Delete directories
137
- for dir_ in dirs:
138
- dir_path = os.path.join(root, dir_)
139
- _make_writable(dir_path)
140
- os.rmdir(dir_path)
141
- total_folders_deleted += 1
142
-
143
-
144
- def copy_file(source_path, destination_path):
145
- # Ensure the destination directory exists
146
- os.makedirs(os.path.dirname(destination_path), exist_ok=True)
147
-
148
- # Open the source file in read-binary ('rb') mode
149
- with open(source_path, "rb") as source_file:
150
- # Open the destination file in write-binary ('wb') mode
151
- with open(destination_path, "wb") as destination_file:
152
- # Copy the content from source to destination
153
- while True:
154
- # Read a chunk of the source file
155
- chunk = source_file.read(4096) # Reading in chunks of 4KB
156
- if not chunk:
157
- break # End of file reached
158
- # Write the chunk to the destination file
159
- destination_file.write(chunk)
106
+ """Delete all contents of a directory but keep the directory itself."""
107
+ for entry in os.scandir(directory):
108
+ if entry.is_dir(follow_symlinks=False):
109
+ shutil.rmtree(entry.path, onerror=_on_rm_error)
110
+ else:
111
+ try:
112
+ os.remove(entry.path)
113
+ except PermissionError:
114
+ os.chmod(entry.path, stat.S_IWRITE)
115
+ os.remove(entry.path)
160
116
 
161
117
 
162
118
  def add_current_path_if_no_path(filename):
@@ -25,6 +25,7 @@ from plain2code_console import console
25
25
  from plain2code_events import RenderFailed
26
26
  from plain2code_exceptions import (
27
27
  ConflictingRequirements,
28
+ GitNotInstalledError,
28
29
  InvalidAPIKey,
29
30
  InvalidFridArgument,
30
31
  MissingAPIKey,
@@ -48,6 +49,7 @@ from plain2code_logger import (
48
49
  get_log_file_path,
49
50
  )
50
51
  from plain2code_state import RunState
52
+ from plain2code_telemetry import capture_crash, initialize_telemetry
51
53
  from system_config import system_config
52
54
  from tui.plain2code_tui import Plain2CodeTUI
53
55
  from tui.plain_module_render_choice_tui import PlainModuleRenderChoiceTUI
@@ -55,6 +57,27 @@ from tui.plain_module_render_choice_tui import PlainModuleRenderChoiceTUI
55
57
  DEFAULT_TEMPLATE_DIRS = importlib.resources.files("standard_template_library")
56
58
  RENDER_THREAD_SHUTDOWN_TIMEOUT = 0.7
57
59
 
60
+ # Exceptions that represent expected, user-facing error conditions. They are
61
+ # reported to the user directly and must never be sent to Sentry as crashes.
62
+ EXPECTED_EXCEPTIONS = (
63
+ InvalidFridArgument,
64
+ FileNotFoundError,
65
+ MissingResource,
66
+ TemplateNotFoundError,
67
+ PlainSyntaxError,
68
+ MissingPreviousFunctionalitiesError,
69
+ MissingAPIKey,
70
+ InvalidAPIKey,
71
+ OutdatedClientVersion,
72
+ ConflictingRequirements,
73
+ RenderingCreditBalanceTooLow,
74
+ NetworkConnectionError,
75
+ ModuleDoesNotExistError,
76
+ UnsupportedResourceType,
77
+ GitNotInstalledError,
78
+ SystemExit,
79
+ )
80
+
58
81
 
59
82
  def setup_logging(
60
83
  args,
@@ -181,6 +204,7 @@ def render(args, run_state: RunState, event_bus: EventBus, default_log_level: st
181
204
  render_choices,
182
205
  system_config.client_version,
183
206
  run_state.render_id,
207
+ on_cancel=run_state.set_render_cancelled,
184
208
  css_path="styles.css",
185
209
  )
186
210
  render_choice = app.run()
@@ -189,6 +213,7 @@ def render(args, run_state: RunState, event_bus: EventBus, default_log_level: st
189
213
  and render_choice.render_range is None
190
214
  and render_choice.choice_type == "quit"
191
215
  ):
216
+ run_state.set_render_cancelled()
192
217
  sys.exit(0)
193
218
  elif ask_user and args.headless:
194
219
  # ignore the default choice if it requires user input due to headless mode
@@ -217,7 +242,7 @@ def render(args, run_state: RunState, event_bus: EventBus, default_log_level: st
217
242
  try:
218
243
  module_renderer.render_module()
219
244
  except RenderCancelledError:
220
- pass # TUI already closed, nothing to report
245
+ run_state.set_render_cancelled() # TUI already closed, nothing to report
221
246
  except Exception as e:
222
247
  run_state.set_render_succeeded(False)
223
248
  render_error.append(e)
@@ -228,8 +253,7 @@ def render(args, run_state: RunState, event_bus: EventBus, default_log_level: st
228
253
  try:
229
254
  module_renderer.render_module()
230
255
  except RenderCancelledError:
231
- run_state.set_render_succeeded(False)
232
- pass
256
+ run_state.set_render_cancelled()
233
257
  return
234
258
  else:
235
259
  render_thread = threading.Thread(target=run_render, daemon=True)
@@ -242,6 +266,7 @@ def render(args, run_state: RunState, event_bus: EventBus, default_log_level: st
242
266
  prepare_environment_script=args.prepare_environment_script,
243
267
  state_machine_version=system_config.client_version,
244
268
  enter_pause_event=enter_pause_event,
269
+ on_cancel=run_state.set_render_cancelled,
245
270
  default_log_level=default_log_level,
246
271
  css_path="styles.css",
247
272
  )
@@ -321,6 +346,8 @@ def main(): # noqa: C901
321
346
  args, event_bus, run_state, args.log_to_file, args.log_file_name, args.filename, args.headless
322
347
  )
323
348
 
349
+ initialize_telemetry()
350
+
324
351
  exc_info = None
325
352
  error_message = None
326
353
 
@@ -337,35 +364,17 @@ def main(): # noqa: C901
337
364
  else:
338
365
  error_message = str(e) if str(e) else repr(e)
339
366
 
340
- if not isinstance(
341
- e,
342
- (
343
- InvalidFridArgument,
344
- FileNotFoundError,
345
- MissingResource,
346
- TemplateNotFoundError,
347
- PlainSyntaxError,
348
- MissingPreviousFunctionalitiesError,
349
- MissingAPIKey,
350
- InvalidAPIKey,
351
- OutdatedClientVersion,
352
- ConflictingRequirements,
353
- RenderingCreditBalanceTooLow,
354
- NetworkConnectionError,
355
- ModuleDoesNotExistError,
356
- UnsupportedResourceType,
357
- ),
358
- ):
367
+ if not isinstance(e, EXPECTED_EXCEPTIONS):
359
368
  exc_info = sys.exc_info()
360
369
  finally:
370
+ if exc_info:
371
+ dump_crash_logs(args, run_state)
372
+ capture_crash(exc_info, run_state, args)
361
373
  print_exit_summary(
362
374
  run_state,
363
375
  args.filename,
364
376
  error_message=error_message,
365
377
  )
366
- if exc_info:
367
- # Log traceback
368
- dump_crash_logs(args, run_state)
369
378
 
370
379
  if args.headless and (exc_info is not None or not run_state.render_succeeded):
371
380
  sys.exit(1)
@@ -89,3 +89,9 @@ class RenderCancelledError(Exception):
89
89
  """Raised when the render is cancelled by the user closing the TUI."""
90
90
 
91
91
  pass
92
+
93
+
94
+ class GitNotInstalledError(Exception):
95
+ """Raised when git is not installed or not found on PATH."""
96
+
97
+ pass
@@ -11,6 +11,7 @@ class RunState:
11
11
  def __init__(self, spec_filename: str, replay_with: Optional[str] = None):
12
12
  self.replay: bool = replay_with is not None
13
13
  self.render_succeeded: bool = False
14
+ self.render_cancelled: bool = False
14
15
  self.render_generated_code_path: Optional[str] = None
15
16
  self.rendered_functionalities: int = 0
16
17
  if replay_with:
@@ -20,9 +21,12 @@ class RunState:
20
21
  self.spec_filename: str = spec_filename
21
22
  self.call_count: int = 0
22
23
  self.unittest_batch_id: int = 0
23
- self.frid_render_anaysis: dict[str, str] = {}
24
24
  self.render_time_accumulated: int = 0
25
25
  self.last_render_start_timestamp: float = time.monotonic()
26
+ # Mirrored from the render state machine for crash reporting; not authoritative.
27
+ self.current_module: Optional[str] = None
28
+ self.current_frid: Optional[str] = None
29
+ self.current_render_state: Optional[str] = None
26
30
 
27
31
  def increment_call_count(self):
28
32
  self.call_count += 1
@@ -30,12 +34,12 @@ class RunState:
30
34
  def increment_unittest_batch_id(self):
31
35
  self.unittest_batch_id += 1
32
36
 
33
- def add_rendering_analysis_for_frid(self, frid, rendering_analysis) -> None:
34
- self.frid_render_anaysis[frid] = rendering_analysis
35
-
36
37
  def set_render_succeeded(self, succeeded: bool):
37
38
  self.render_succeeded = succeeded
38
39
 
40
+ def set_render_cancelled(self):
41
+ self.render_cancelled = True
42
+
39
43
  def set_render_generated_code_path(self, generated_code_path: str):
40
44
  self.render_generated_code_path = generated_code_path
41
45
 
@@ -55,6 +59,3 @@ class RunState:
55
59
  "replay": self.replay,
56
60
  "spec_filename": self.spec_filename,
57
61
  }
58
-
59
- def get_render_func_id(self, frid: str) -> str:
60
- return f"{self.render_id}-{frid}"