codeplain 0.2.4__tar.gz → 0.2.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. codeplain-0.2.5/.github/workflows/publish-install-script.yml +39 -0
  2. {codeplain-0.2.4 → codeplain-0.2.5}/.gitignore +3 -3
  3. {codeplain-0.2.4 → codeplain-0.2.5}/PKG-INFO +3 -3
  4. {codeplain-0.2.4 → codeplain-0.2.5}/codeplain_REST_api.py +57 -37
  5. codeplain-0.2.5/config/system_config.yaml +12 -0
  6. {codeplain-0.2.4 → codeplain-0.2.5}/examples/example_hello_world_golang/hello_world_golang.plain +1 -1
  7. {codeplain-0.2.4 → codeplain-0.2.5}/examples/example_hello_world_python/hello_world_python.plain +1 -1
  8. codeplain-0.2.5/examples/example_hello_world_react/config.yaml +2 -0
  9. {codeplain-0.2.4 → codeplain-0.2.5}/file_utils.py +5 -4
  10. {codeplain-0.2.4 → codeplain-0.2.5}/git_utils.py +42 -7
  11. codeplain-0.2.5/install/examples.sh +92 -0
  12. {codeplain-0.2.4 → codeplain-0.2.5/install}/install.sh +112 -17
  13. codeplain-0.2.5/install/walkthrough.sh +178 -0
  14. {codeplain-0.2.4 → codeplain-0.2.5}/memory_management.py +1 -1
  15. {codeplain-0.2.4 → codeplain-0.2.5}/module_renderer.py +114 -0
  16. {codeplain-0.2.4 → codeplain-0.2.5}/plain2code.py +87 -19
  17. {codeplain-0.2.4 → codeplain-0.2.5}/plain2code_console.py +3 -5
  18. {codeplain-0.2.4 → codeplain-0.2.5}/plain2code_exceptions.py +14 -4
  19. {codeplain-0.2.4 → codeplain-0.2.5}/plain2code_logger.py +11 -5
  20. {codeplain-0.2.4 → codeplain-0.2.5}/plain2code_utils.py +7 -5
  21. {codeplain-0.2.4 → codeplain-0.2.5}/plain_file.py +1 -1
  22. {codeplain-0.2.4 → codeplain-0.2.5}/plain_spec.py +22 -2
  23. {codeplain-0.2.4 → codeplain-0.2.5}/pyproject.toml +3 -3
  24. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/actions/create_dist.py +1 -1
  25. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/actions/exit_with_error.py +1 -1
  26. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/actions/prepare_testing_environment.py +1 -1
  27. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/actions/render_conformance_tests.py +2 -4
  28. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/actions/render_functional_requirement.py +6 -6
  29. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/actions/run_conformance_tests.py +3 -2
  30. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/actions/run_unit_tests.py +1 -1
  31. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/render_context.py +3 -3
  32. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/render_utils.py +14 -6
  33. {codeplain-0.2.4 → codeplain-0.2.5}/requirements.txt +2 -2
  34. {codeplain-0.2.4 → codeplain-0.2.5}/standard_template_library/golang-console-app-template.plain +2 -2
  35. {codeplain-0.2.4 → codeplain-0.2.5}/standard_template_library/python-console-app-template.plain +2 -2
  36. {codeplain-0.2.4 → codeplain-0.2.5}/standard_template_library/typescript-react-app-template.plain +2 -2
  37. {codeplain-0.2.4 → codeplain-0.2.5}/system_config.py +3 -11
  38. {codeplain-0.2.4 → codeplain-0.2.5}/test_scripts/run_unittests_python.sh +1 -1
  39. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/imports/circular_imports_2.plain +1 -1
  40. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/imports/circular_imports_main.plain +1 -1
  41. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/imports/diamond_import_1.plain +2 -2
  42. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/imports/diamond_import_2.plain +2 -2
  43. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/imports/diamond_import_common.plain +2 -2
  44. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/imports/diamond_imports_main.plain +1 -1
  45. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/imports/non_existent_import.plain +1 -1
  46. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfile/invalid_specification_order.plain +1 -1
  47. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfile/plain_source_with_absolute_link.plain +1 -1
  48. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfile/plain_source_with_url_link.plain +1 -1
  49. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfile/task_manager_with_reference_links.plain +2 -2
  50. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfile/without_non_functional_requirement.plain +1 -1
  51. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/concept_validation_acceptance_tests.plain +2 -2
  52. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/concept_validation_acceptance_tests_nondefined.plain +2 -2
  53. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/concept_validation_defined_nondefined.plain +2 -2
  54. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/concept_validation_defined_nondefined_2.plain +2 -2
  55. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/concept_validation_definition.plain +2 -2
  56. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/concept_validation_noconcepts.plain +2 -2
  57. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/concept_validation_nonconcept.plain +2 -2
  58. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/concept_validation_nondefined.plain +2 -2
  59. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/concept_validation_redefinition.plain +2 -2
  60. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/concept_validation_several_concepts.plain +1 -1
  61. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/concept_validation_valid.plain +2 -2
  62. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/exported_concepts.plain +1 -1
  63. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/exported_concepts_example.plain +1 -1
  64. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/exported_concepts_missing.plain +1 -1
  65. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/exported_concepts_missing_example.plain +1 -1
  66. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/exported_concepts_nested.plain +1 -1
  67. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/exported_concepts_nested_example.plain +1 -1
  68. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/exported_concepts_transitive_example.plain +1 -1
  69. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/exported_concepts_transitive_l1.plain +1 -1
  70. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/exported_concepts_transitive_l2.plain +1 -1
  71. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/plain_file_parser_with_comments.plain +1 -1
  72. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/plain_file_with_comments_indented.plain +1 -1
  73. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/regular_plain_source.plain +1 -1
  74. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/required_concepts_example.plain +1 -1
  75. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/required_concepts_missing.plain +1 -1
  76. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/required_concepts_module.plain +1 -1
  77. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/required_concepts_partial.plain +1 -1
  78. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/required_concepts_partial_duplicate.plain +1 -1
  79. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/topological_sort.plain +1 -1
  80. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/topological_sort_not_referenced.plain +1 -1
  81. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/requires/circular_requires_main.plain +1 -1
  82. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/requires/diamond_requires_common.plain +1 -1
  83. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/requires/diamond_requires_main.plain +1 -1
  84. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/requires/independent_requires_main.plain +1 -1
  85. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/requires/non_existent_require.plain +1 -1
  86. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/requires/normal_requires_common.plain +1 -1
  87. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/requires/normal_requires_main.plain +1 -1
  88. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/simple.plain +1 -1
  89. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/templates/block_level_include.plain +1 -1
  90. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/templates/code_variables.plain +1 -1
  91. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/templates/header.plain +1 -1
  92. {codeplain-0.2.4 → codeplain-0.2.5}/tests/test_imports.py +2 -2
  93. {codeplain-0.2.4 → codeplain-0.2.5}/tests/test_plainfile.py +1 -1
  94. {codeplain-0.2.4 → codeplain-0.2.5}/tests/test_plainfileparser.py +10 -10
  95. {codeplain-0.2.4 → codeplain-0.2.5}/tests/test_plainspec.py +2 -2
  96. codeplain-0.2.5/tui/components.py +580 -0
  97. {codeplain-0.2.4 → codeplain-0.2.5}/tui/plain2code_tui.py +101 -52
  98. {codeplain-0.2.4 → codeplain-0.2.5}/tui/state_handlers.py +94 -47
  99. codeplain-0.2.5/tui/styles.css +401 -0
  100. {codeplain-0.2.4 → codeplain-0.2.5}/tui/widget_helpers.py +43 -47
  101. codeplain-0.2.4/.github/workflows/publish-install-script.yml +0 -30
  102. codeplain-0.2.4/config/system_config.yaml +0 -27
  103. codeplain-0.2.4/tui/components.py +0 -372
  104. codeplain-0.2.4/tui/styles.css +0 -213
  105. {codeplain-0.2.4 → codeplain-0.2.5}/.flake8 +0 -0
  106. {codeplain-0.2.4 → codeplain-0.2.5}/.github/workflows/lint-and-test.yml +0 -0
  107. {codeplain-0.2.4 → codeplain-0.2.5}/.github/workflows/nofity-slack-on-main-merge.yml +0 -0
  108. {codeplain-0.2.4 → codeplain-0.2.5}/.github/workflows/publish-to-pypi.yml +0 -0
  109. {codeplain-0.2.4 → codeplain-0.2.5}/.vscode/launch.json +0 -0
  110. {codeplain-0.2.4 → codeplain-0.2.5}/.vscode/settings.json +0 -0
  111. {codeplain-0.2.4 → codeplain-0.2.5}/LICENSE +0 -0
  112. {codeplain-0.2.4 → codeplain-0.2.5}/README.md +0 -0
  113. {codeplain-0.2.4 → codeplain-0.2.5}/concept_utils.py +0 -0
  114. {codeplain-0.2.4 → codeplain-0.2.5}/config/__init__.py +0 -0
  115. {codeplain-0.2.4 → codeplain-0.2.5}/diff_utils.py +0 -0
  116. {codeplain-0.2.4 → codeplain-0.2.5}/docs/generate_cli.py +0 -0
  117. {codeplain-0.2.4 → codeplain-0.2.5}/docs/plain2code_cli.md +0 -0
  118. {codeplain-0.2.4 → codeplain-0.2.5}/docs/plain_language_specification.md +0 -0
  119. {codeplain-0.2.4 → codeplain-0.2.5}/docs/starting_a_plain_project_from_scratch.md +0 -0
  120. {codeplain-0.2.4 → codeplain-0.2.5}/event_bus.py +0 -0
  121. {codeplain-0.2.4 → codeplain-0.2.5}/examples/example_hello_world_golang/config.yaml +0 -0
  122. {codeplain-0.2.4 → codeplain-0.2.5}/examples/example_hello_world_golang/harness_tests/hello_world_test.go +0 -0
  123. {codeplain-0.2.4 → codeplain-0.2.5}/examples/example_hello_world_golang/run.sh +0 -0
  124. {codeplain-0.2.4 → codeplain-0.2.5}/examples/example_hello_world_python/config.yaml +0 -0
  125. {codeplain-0.2.4 → codeplain-0.2.5}/examples/example_hello_world_python/harness_tests/hello_world_display/test_hello_world.py +0 -0
  126. {codeplain-0.2.4 → codeplain-0.2.5}/examples/example_hello_world_python/run.sh +0 -0
  127. {codeplain-0.2.4 → codeplain-0.2.5}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress/e2e/hello_world.cy.ts +0 -0
  128. {codeplain-0.2.4 → codeplain-0.2.5}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress/support/e2e.ts +0 -0
  129. {codeplain-0.2.4 → codeplain-0.2.5}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress.config.ts +0 -0
  130. {codeplain-0.2.4 → codeplain-0.2.5}/examples/example_hello_world_react/harness_tests/hello_world_display/package.json +0 -0
  131. {codeplain-0.2.4 → codeplain-0.2.5}/examples/example_hello_world_react/harness_tests/hello_world_display/tsconfig.json +0 -0
  132. {codeplain-0.2.4 → codeplain-0.2.5}/examples/example_hello_world_react/hello_world_react.plain +0 -0
  133. {codeplain-0.2.4 → codeplain-0.2.5}/examples/example_hello_world_react/run.sh +0 -0
  134. {codeplain-0.2.4 → codeplain-0.2.5}/examples/run.sh +0 -0
  135. {codeplain-0.2.4 → codeplain-0.2.5}/hash_key.py +0 -0
  136. {codeplain-0.2.4 → codeplain-0.2.5}/plain2code_arguments.py +0 -0
  137. {codeplain-0.2.4 → codeplain-0.2.5}/plain2code_events.py +0 -0
  138. {codeplain-0.2.4 → codeplain-0.2.5}/plain2code_nodes.py +0 -0
  139. {codeplain-0.2.4 → codeplain-0.2.5}/plain2code_read_config.py +0 -0
  140. {codeplain-0.2.4 → codeplain-0.2.5}/plain2code_state.py +0 -0
  141. {codeplain-0.2.4 → codeplain-0.2.5}/plain_modules.py +0 -0
  142. {codeplain-0.2.4 → codeplain-0.2.5}/pytest.ini +0 -0
  143. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/__init__.py +0 -0
  144. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/actions/analyze_specification_ambiguity.py +0 -0
  145. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/actions/base_action.py +0 -0
  146. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/actions/commit_conformance_tests_changes.py +0 -0
  147. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/actions/commit_implementation_code_changes.py +0 -0
  148. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/actions/finish_functional_requirement.py +0 -0
  149. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/actions/fix_conformance_test.py +0 -0
  150. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/actions/fix_unit_tests.py +0 -0
  151. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/actions/prepare_repositories.py +0 -0
  152. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/actions/refactor_code.py +0 -0
  153. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/actions/summarize_conformance_tests.py +0 -0
  154. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/code_renderer.py +0 -0
  155. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/conformance_test_helpers.py +0 -0
  156. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/conformance_tests.py +0 -0
  157. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/implementation_code_helpers.py +0 -0
  158. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/render_types.py +0 -0
  159. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/state_machine_config.py +0 -0
  160. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/states.py +0 -0
  161. {codeplain-0.2.4 → codeplain-0.2.5}/render_machine/triggers.py +0 -0
  162. {codeplain-0.2.4 → codeplain-0.2.5}/resources/codeplain_overview.png +0 -0
  163. {codeplain-0.2.4 → codeplain-0.2.5}/resources/plain_example.png +0 -0
  164. {codeplain-0.2.4 → codeplain-0.2.5}/standard_template_library/__init__.py +0 -0
  165. {codeplain-0.2.4 → codeplain-0.2.5}/standard_template_library/typescript-react-app-boilerplate.plain +0 -0
  166. {codeplain-0.2.4 → codeplain-0.2.5}/test_scripts/run_conformance_tests_cypress.sh +0 -0
  167. {codeplain-0.2.4 → codeplain-0.2.5}/test_scripts/run_conformance_tests_golang.sh +0 -0
  168. {codeplain-0.2.4 → codeplain-0.2.5}/test_scripts/run_conformance_tests_python.sh +0 -0
  169. {codeplain-0.2.4 → codeplain-0.2.5}/test_scripts/run_unittests_golang.sh +0 -0
  170. {codeplain-0.2.4 → codeplain-0.2.5}/test_scripts/run_unittests_react.sh +0 -0
  171. {codeplain-0.2.4 → codeplain-0.2.5}/tests/__init__.py +0 -0
  172. {codeplain-0.2.4 → codeplain-0.2.5}/tests/conftest.py +0 -0
  173. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/imports/circular_imports_1.plain +0 -0
  174. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfile/duplicate_specification_heading.plain +0 -0
  175. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfile/missing_non_functional_requirements.plain +0 -0
  176. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/exported_concepts_base.plain +0 -0
  177. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/required_concepts.plain +0 -0
  178. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/required_concepts_defs.plain +0 -0
  179. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/required_concepts_l1.plain +0 -0
  180. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/plainfileparser/required_concepts_l2.plain +0 -0
  181. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/requires/circular_requires_sub.plain +0 -0
  182. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/requires/diamond_requires_1.plain +0 -0
  183. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/requires/diamond_requires_2.plain +0 -0
  184. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/requires/independent_requires_1.plain +0 -0
  185. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/requires/independent_requires_2.plain +0 -0
  186. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/requires/normal_requires_1.plain +0 -0
  187. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/requires/normal_requires_2.plain +0 -0
  188. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/templates/implement.plain +0 -0
  189. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/templates/implement_2.plain +0 -0
  190. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/templates/template_include.plain +0 -0
  191. {codeplain-0.2.4 → codeplain-0.2.5}/tests/data/templates/test_hardest_problem.plain +0 -0
  192. {codeplain-0.2.4 → codeplain-0.2.5}/tests/test_git_utils.py +0 -0
  193. {codeplain-0.2.4 → codeplain-0.2.5}/tests/test_requires.py +0 -0
  194. {codeplain-0.2.4 → codeplain-0.2.5}/tui/__init__.py +0 -0
  195. {codeplain-0.2.4 → codeplain-0.2.5}/tui/models.py +0 -0
  196. {codeplain-0.2.4 → codeplain-0.2.5/tui}/spinner.py +0 -0
@@ -0,0 +1,39 @@
1
+ name: Publish Install Scripts to R2
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ paths:
8
+ - 'install/*.sh'
9
+ workflow_dispatch:
10
+
11
+ jobs:
12
+ publish:
13
+ name: Upload to Cloudflare R2
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Upload install scripts to R2
20
+ env:
21
+ AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
22
+ AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
23
+ R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
24
+ R2_BUCKET: ${{ secrets.R2_BUCKET }}
25
+ run: |
26
+ aws s3 cp install/install.sh s3://${R2_BUCKET}/install/install.sh \
27
+ --endpoint-url "${R2_ENDPOINT}" \
28
+ --content-type "text/plain"
29
+ echo "✓ install.sh uploaded to R2"
30
+
31
+ aws s3 cp install/walkthrough.sh s3://${R2_BUCKET}/install/walkthrough.sh \
32
+ --endpoint-url "${R2_ENDPOINT}" \
33
+ --content-type "text/plain"
34
+ echo "✓ walkthrough.sh uploaded to R2"
35
+
36
+ aws s3 cp install/examples.sh s3://${R2_BUCKET}/install/examples.sh \
37
+ --endpoint-url "${R2_ENDPOINT}" \
38
+ --content-type "text/plain"
39
+ echo "✓ examples.sh uploaded to R2"
@@ -1,7 +1,7 @@
1
1
  __pycache__
2
2
  .DS_Store
3
3
 
4
- # Plain
4
+
5
5
  examples/**/build*/
6
6
  examples/**/conformance_tests/
7
7
  examples/**/conformance_tests.backup/
@@ -23,10 +23,10 @@ examples/**/node_plain_modules/
23
23
  *.log
24
24
 
25
25
  .venv
26
- build
27
26
  dist
28
27
  *.egg-info
29
28
 
30
29
  .env
31
30
 
32
- .coverage
31
+ .coverage
32
+ logging_config.yaml
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeplain
3
- Version: 0.2.4
3
+ Version: 0.2.5
4
4
  Summary: Transform plain language specifications into working code
5
5
  License-File: LICENSE
6
6
  Classifier: Environment :: Console
@@ -14,13 +14,13 @@ Requires-Dist: networkx==3.6.1
14
14
  Requires-Dist: python-frontmatter==1.1.0
15
15
  Requires-Dist: python-liquid2==0.3.0
16
16
  Requires-Dist: pyyaml==6.0.2
17
- Requires-Dist: requests==2.32.3
17
+ Requires-Dist: requests==2.32.4
18
18
  Requires-Dist: rich==14.2.0
19
19
  Requires-Dist: textual==1.0.0
20
20
  Requires-Dist: tiktoken==0.12.0
21
21
  Requires-Dist: transitions==0.9.3
22
22
  Provides-Extra: dev
23
- Requires-Dist: black==24.2.0; extra == 'dev'
23
+ Requires-Dist: black==24.3.0; extra == 'dev'
24
24
  Requires-Dist: flake8==7.0.0; extra == 'dev'
25
25
  Requires-Dist: isort==5.13.2; extra == 'dev'
26
26
  Requires-Dist: mypy==1.11.2; extra == 'dev'
@@ -2,7 +2,6 @@ import time
2
2
  from typing import Optional
3
3
 
4
4
  import requests
5
- from requests.exceptions import ConnectionError, RequestException, Timeout
6
5
 
7
6
  import plain2code_exceptions
8
7
  from plain2code_state import RunState
@@ -10,6 +9,22 @@ from plain2code_state import RunState
10
9
  MAX_RETRIES = 4
11
10
  RETRY_DELAY = 3
12
11
 
12
+ # TODO: Handle connection errors
13
+ RETRY_ERROR_CODES = [
14
+ "LLMInternalError",
15
+ ]
16
+
17
+ # Mapping from API error codes to exception classes
18
+ ERROR_CODE_EXCEPTIONS = {
19
+ "FunctionalRequirementTooComplex": plain2code_exceptions.FunctionalRequirementTooComplex,
20
+ "ConflictingRequirements": plain2code_exceptions.ConflictingRequirements,
21
+ "CreditBalanceTooLow": plain2code_exceptions.CreditBalanceTooLow,
22
+ "LLMInternalError": plain2code_exceptions.LLMInternalError,
23
+ "MissingResource": plain2code_exceptions.MissingResource,
24
+ "PlainSyntaxError": plain2code_exceptions.PlainSyntaxError,
25
+ "InternalServerError": plain2code_exceptions.InternalServerError,
26
+ }
27
+
13
28
 
14
29
  class CodeplainAPI:
15
30
 
@@ -29,12 +44,31 @@ class CodeplainAPI:
29
44
  run_state.increment_call_count()
30
45
  payload["render_state"] = run_state.to_dict()
31
46
 
32
- def post_request(self, endpoint_url, headers, payload, run_state: Optional[RunState]): # noqa: C901
47
+ def _raise_for_error_code(self, response_json):
48
+ """Raise appropriate exception based on error code in response."""
49
+ error_code = response_json.get("error_code")
50
+ if error_code not in ERROR_CODE_EXCEPTIONS:
51
+ return
52
+
53
+ exception_class = ERROR_CODE_EXCEPTIONS[error_code]
54
+ message = response_json.get("message", "")
55
+
56
+ # FunctionalRequirementTooComplex has an extra parameter
57
+ if error_code == "FunctionalRequirementTooComplex":
58
+ raise exception_class(message, response_json.get("proposed_breakdown"))
59
+
60
+ raise exception_class(message)
61
+
62
+ def post_request(
63
+ self, endpoint_url, headers, payload, run_state: Optional[RunState], num_retries: int = MAX_RETRIES
64
+ ):
33
65
  if run_state is not None:
34
66
  self._extend_payload_with_run_state(payload, run_state)
35
67
 
36
68
  retry_delay = RETRY_DELAY
37
- for attempt in range(MAX_RETRIES + 1):
69
+ response_json = None
70
+
71
+ for attempt in range(num_retries + 1):
38
72
  try:
39
73
  response = requests.post(endpoint_url, headers=headers, json=payload)
40
74
 
@@ -45,47 +79,33 @@ class CodeplainAPI:
45
79
  raise
46
80
 
47
81
  if response.status_code == requests.codes.bad_request and "error_code" in response_json:
48
- if response_json["error_code"] == "FunctionalRequirementTooComplex":
49
- raise plain2code_exceptions.FunctionalRequirementTooComplex(
50
- response_json["message"], response_json.get("proposed_breakdown")
51
- )
52
-
53
- if response_json["error_code"] == "ConflictingRequirements":
54
- raise plain2code_exceptions.ConflictingRequirements(response_json["message"])
55
-
56
- if response_json["error_code"] == "CreditBalanceTooLow":
57
- raise plain2code_exceptions.CreditBalanceTooLow(response_json["message"])
58
-
59
- if response_json["error_code"] == "LLMInternalError":
60
- raise plain2code_exceptions.LLMInternalError(response_json["message"])
61
-
62
- if response_json["error_code"] == "MissingResource":
63
- raise plain2code_exceptions.MissingResource(response_json["message"])
64
-
65
- if response_json["error_code"] == "PlainSyntaxError":
66
- raise plain2code_exceptions.PlainSyntaxError(response_json["message"])
67
-
68
- if response_json["error_code"] == "NoRenderFound":
69
- raise plain2code_exceptions.NoRenderFound(response_json["message"])
70
-
71
- if response_json["error_code"] == "MultipleRendersFound":
72
- raise plain2code_exceptions.MultipleRendersFound(response_json["message"])
82
+ self._raise_for_error_code(response_json)
73
83
 
74
84
  response.raise_for_status()
75
85
  return response_json
76
86
 
77
- except (ConnectionError, Timeout, RequestException) as e:
78
- if attempt < MAX_RETRIES:
79
- self.console.info(f"Connection error on attempt {attempt + 1}/{MAX_RETRIES + 1}: {e}")
87
+ except Exception as e:
88
+ if response_json is not None and "error_code" in response_json:
89
+ if response_json["error_code"] not in RETRY_ERROR_CODES:
90
+ raise e
91
+
92
+ if attempt < num_retries:
93
+ self.console.info(f"Error on attempt {attempt + 1}/{num_retries + 1}: {e}")
80
94
  self.console.info(f"Retrying in {retry_delay} seconds...")
81
95
  time.sleep(retry_delay)
82
- # Exponential backoff
83
- retry_delay *= 2
96
+ retry_delay *= 2 # Exponential backoff
84
97
  else:
85
- self.console.error(f"Max retries ({MAX_RETRIES}) exceeded. Last error: {e}")
86
- raise RequestException(
87
- f"Connection error: Unable to reach the Codeplain API at {self.api_url}. Please try again or contact support."
88
- )
98
+ self.console.error(f"Max retries ({num_retries}) exceeded. Last error: {e}")
99
+ raise e
100
+
101
+ def connection_check(self, client_version):
102
+ endpoint_url = f"{self.api_url}/connection_check"
103
+ headers = {"Content-Type": "application/json"}
104
+ payload = {
105
+ "api_key": self.api_key,
106
+ "client_version": client_version,
107
+ }
108
+ return self.post_request(endpoint_url, headers, payload, None, num_retries=0)
89
109
 
90
110
  def render_functional_requirement(
91
111
  self,
@@ -0,0 +1,12 @@
1
+ client_version: "0.17.0"
2
+
3
+ error_messages:
4
+ template_not_found:
5
+ message: |
6
+ The required template could not be found. Templates are searched in the following order (highest to lowest precedence):
7
+
8
+ 1. The directory containing your .plain file
9
+ 2. The directory specified by --template-dir (if provided)
10
+ 3. The built-in 'standard_template_library' directory
11
+
12
+ Please ensure that the missing template exists in one of these locations, or specify the correct --template-dir if using custom templates.
@@ -4,7 +4,7 @@ import:
4
4
  - golang-console-app-template
5
5
  ---
6
6
 
7
- ***technical specs***
7
+ ***implementation reqs***
8
8
 
9
9
  - :MainExecutableFile: of :App: should be called "hello_world.go".
10
10
 
@@ -4,7 +4,7 @@ import:
4
4
  - python-console-app-template
5
5
  ---
6
6
 
7
- ***technical specs***
7
+ ***implementation reqs***
8
8
 
9
9
  - :MainExecutableFile: of :App: should be called "hello_world.py".
10
10
 
@@ -0,0 +1,2 @@
1
+ conformance-tests-script: ../../test_scripts/run_conformance_tests_cypress.sh
2
+ verbose: true
@@ -43,6 +43,8 @@ FILE_EXTENSION_MAPPING = {
43
43
  ".bat": "Batch File",
44
44
  }
45
45
 
46
+ SYSTEM_FOLDERS = [".git", CODEPLAIN_METADATA_FOLDER, CODEPLAIN_MEMORY_SUBFOLDER]
47
+
46
48
 
47
49
  def get_file_type(file_name):
48
50
 
@@ -55,10 +57,9 @@ def get_file_type(file_name):
55
57
 
56
58
  def list_all_text_files(directory):
57
59
  all_files = []
58
- skip_dirs = [".git", CODEPLAIN_METADATA_FOLDER, CODEPLAIN_MEMORY_SUBFOLDER]
59
60
  for root, dirs, files in os.walk(directory, topdown=True):
60
61
  # Skip directories that should not be traversed
61
- for skip_dir in skip_dirs:
62
+ for skip_dir in SYSTEM_FOLDERS:
62
63
  if skip_dir in dirs:
63
64
  dirs.remove(skip_dir)
64
65
 
@@ -322,5 +323,5 @@ def copy_folder_to_output(source_folder, output_folder):
322
323
  if os.path.exists(output_folder):
323
324
  delete_files_and_subfolders(output_folder)
324
325
 
325
- # Copy source folder contents directly to output folder (excluding .git)
326
- copy_folder_content(source_folder, output_folder, ignore_folders=[".git"])
326
+ # Copy source folder contents directly to output folder (excluding SYSTEM_FOLDERS)
327
+ copy_folder_content(source_folder, output_folder, ignore_folders=SYSTEM_FOLDERS)
@@ -247,7 +247,10 @@ def diff(repo_path: Union[str, os.PathLike], previous_frid: str = None) -> dict:
247
247
 
248
248
  def _get_commit(repo: Repo, frid: Optional[str]) -> str:
249
249
  if frid:
250
- return _get_commit_with_frid(repo, frid)
250
+ commit_with_frid = _get_commit_with_frid(repo, frid)
251
+ if not commit_with_frid:
252
+ raise InvalidGitRepositoryError(f"No commit with frid {frid} found.")
253
+ return commit_with_frid
251
254
 
252
255
  base_folder_commit = _get_base_folder_commit(repo)
253
256
  initial_commit = _get_initial_commit(repo)
@@ -256,12 +259,44 @@ def _get_commit(repo: Repo, frid: Optional[str]) -> str:
256
259
  return initial_commit
257
260
 
258
261
 
259
- def _get_commit_with_frid(repo: Repo, frid: str) -> str:
260
- """Finds commit with given frid mentioned in the commit message."""
261
- commit = _get_commit_with_message(repo, FUNCTIONAL_REQUIREMENT_FINISHED_COMMIT_MESSAGE.format(frid))
262
- if not commit:
263
- raise InvalidGitRepositoryError(f"No commit with frid {frid} found.")
264
- return commit
262
+ def _get_commit_with_frid(repo: Repo, frid: str, module_name: Optional[str] = None) -> str:
263
+ """
264
+ Finds commit with given frid mentioned in the commit message.
265
+
266
+ Args:
267
+ repo (Repo): Git repository object
268
+ frid (str): Functional requirement ID
269
+ module_name (Optional[str]): Module name to filter by. If provided, only returns
270
+ commits that have both the FRID and module name.
271
+
272
+ Returns:
273
+ str: Commit SHA if found, empty string otherwise
274
+ """
275
+ commit_message_pattern = FUNCTIONAL_REQUIREMENT_FINISHED_COMMIT_MESSAGE.format(frid)
276
+
277
+ # If no module name filtering is needed, use the original logic
278
+ if not module_name:
279
+ return _get_commit_with_message(repo, commit_message_pattern)
280
+
281
+ # Use multiple grep patterns with --all-match for AND condition
282
+ escaped_frid_message = commit_message_pattern.replace("[", "\\[").replace("]", "\\]")
283
+ module_name_pattern = MODULE_NAME_MESSAGE.format(module_name)
284
+ escaped_module_message = module_name_pattern.replace("[", "\\[").replace("]", "\\]")
285
+
286
+ return repo.git.rev_list(
287
+ repo.active_branch.name,
288
+ "--grep",
289
+ escaped_frid_message,
290
+ "--grep",
291
+ escaped_module_message,
292
+ "--all-match",
293
+ "-n",
294
+ "1",
295
+ )
296
+
297
+
298
+ def has_commit_for_frid(repo_path: Union[str, os.PathLike], frid: str, module_name: Optional[str] = None) -> bool:
299
+ return bool(_get_commit_with_frid(Repo(repo_path), frid, module_name))
265
300
 
266
301
 
267
302
  def _get_base_folder_commit(repo: Repo) -> str:
@@ -0,0 +1,92 @@
1
+ #!/bin/bash
2
+
3
+ set -euo pipefail
4
+
5
+ # Brand Colors (use exported colors if available, otherwise define them)
6
+ YELLOW="${YELLOW:-\033[38;2;224;255;110m}"
7
+ GREEN="${GREEN:-\033[38;2;121;252;150m}"
8
+ RED="${RED:-\033[38;2;239;68;68m}"
9
+ GRAY="${GRAY:-\033[38;2;128;128;128m}"
10
+ BOLD="${BOLD:-\033[1m}"
11
+ NC="${NC:-\033[0m}"
12
+
13
+ # Examples configuration
14
+ EXAMPLES_FOLDER_NAME="plainlang-examples"
15
+ EXAMPLES_DOWNLOAD_URL="https://github.com/Codeplain-ai/plainlang-examples/archive/refs/tags/0.1.zip"
16
+
17
+ # Show current directory and ask for extraction path
18
+ CURRENT_DIR=$(pwd)
19
+ echo -e " current folder: ${YELLOW}${CURRENT_DIR}${NC}"
20
+ echo ""
21
+ echo -e " extract examples here, or enter a different path:"
22
+ echo ""
23
+ read -r -p " [Enter for current, or type path]: " EXTRACT_PATH < /dev/tty
24
+ echo ""
25
+
26
+ # Use current directory if empty
27
+ if [ -z "${EXTRACT_PATH:-}" ]; then
28
+ EXTRACT_PATH="$CURRENT_DIR"
29
+ fi
30
+
31
+ # Expand ~ to home directory
32
+ EXTRACT_PATH="${EXTRACT_PATH/#\~/$HOME}"
33
+
34
+ SKIP_DOWNLOAD=false
35
+
36
+ # Check if directory exists, create if not
37
+ if [ ! -d "$EXTRACT_PATH" ]; then
38
+ echo -e " ${GRAY}creating directory...${NC}"
39
+ mkdir -p "$EXTRACT_PATH" 2>/dev/null
40
+ if [ $? -ne 0 ]; then
41
+ echo -e " ${RED}✗${NC} failed to create directory: ${EXTRACT_PATH}"
42
+ echo -e " ${GRAY}skipping example download.${NC}"
43
+ SKIP_DOWNLOAD=true
44
+ fi
45
+ fi
46
+
47
+ if [ "$SKIP_DOWNLOAD" = false ]; then
48
+ echo -e " ${GRAY}downloading examples...${NC}"
49
+
50
+ # Download the zip file
51
+ TEMP_ZIP=$(mktemp)
52
+ curl -L -s -o "$TEMP_ZIP" "$EXAMPLES_DOWNLOAD_URL"
53
+
54
+ if [ $? -eq 0 ] && [ -s "$TEMP_ZIP" ]; then
55
+ echo -e " ${GRAY}extracting to ${EXTRACT_PATH}...${NC}"
56
+
57
+ # Extract the zip file
58
+ unzip -q -o "$TEMP_ZIP" -d "$EXTRACT_PATH" 2>/dev/null
59
+
60
+ if [ $? -eq 0 ]; then
61
+ # Find and rename extracted directory to remove version number
62
+ EXTRACTED_DIR="${EXTRACT_PATH}/${EXAMPLES_FOLDER_NAME}"
63
+ VERSIONED_DIR=$(find "$EXTRACT_PATH" -maxdepth 1 -type d -name "${EXAMPLES_FOLDER_NAME}-*" | head -1)
64
+ if [ -n "$VERSIONED_DIR" ]; then
65
+ rm -rf "$EXTRACTED_DIR" 2>/dev/null # Remove existing if present
66
+ mv "$VERSIONED_DIR" "$EXTRACTED_DIR"
67
+ fi
68
+
69
+ # Remove the .gitignore file from the root of the extracted directory
70
+ if [ -f "${EXTRACTED_DIR}/.gitignore" ]; then
71
+ rm -f "${EXTRACTED_DIR}/.gitignore"
72
+ fi
73
+
74
+ echo ""
75
+ echo -e " ${GREEN}✓${NC} examples downloaded successfully!"
76
+ echo ""
77
+ echo -e " examples are in: ${YELLOW}${EXTRACTED_DIR}${NC}"
78
+ echo ""
79
+ else
80
+ echo -e " ${RED}✗${NC} failed to extract examples."
81
+ fi
82
+
83
+ # Clean up temp file
84
+ rm -f "$TEMP_ZIP"
85
+ else
86
+ echo -e " ${RED}✗${NC} failed to download examples."
87
+ rm -f "$TEMP_ZIP"
88
+ fi
89
+
90
+ echo ""
91
+ read -r -p " press [Enter] to continue..." < /dev/tty
92
+ fi
@@ -2,6 +2,9 @@
2
2
 
3
3
  set -euo pipefail
4
4
 
5
+ # Base URL for additional scripts
6
+ CODEPLAIN_SCRIPTS_BASE_URL="${CODEPLAIN_SCRIPTS_BASE_URL:-https://codeplain.ai}"
7
+
5
8
  # Brand Colors (True Color / 24-bit)
6
9
  YELLOW='\033[38;2;224;255;110m' # #E0FF6E
7
10
  GREEN='\033[38;2;121;252;150m' # #79FC96
@@ -16,6 +19,10 @@ GRAY_LIGHT='\033[38;2;211;211;211m' # #D3D3D3
16
19
  BOLD='\033[1m'
17
20
  NC='\033[0m' # No Color / Reset
18
21
 
22
+ # Export colors for child scripts
23
+ export YELLOW GREEN GREEN_LIGHT GREEN_DARK BLUE BLACK WHITE RED GRAY GRAY_LIGHT BOLD NC
24
+
25
+ clear
19
26
  echo -e "started ${YELLOW}${BOLD}*codeplain CLI${NC} installation..."
20
27
 
21
28
  # Install uv if not present
@@ -54,17 +61,36 @@ else
54
61
  echo -e "installing codeplain...${NC}"
55
62
  echo -e ""
56
63
  uv tool install codeplain
64
+ clear
57
65
  echo -e "${GREEN}✓ codeplain installed successfully!${NC}"
58
66
  fi
59
67
 
60
- echo -e "${GREEN}✓${NC} the latest version of *codeplain CLI is now installed."
61
- echo ""
62
- echo -e "go to ${YELLOW}https://platform.codeplain.ai${NC} and sign up to get your API key."
63
- echo ""
64
- read -r -p "paste your API key here: " API_KEY < /dev/tty
65
- echo ""
68
+ # Check if API key already exists
69
+ SKIP_API_KEY_SETUP=false
70
+ if [ -n "${CODEPLAIN_API_KEY:-}" ]; then
71
+ echo -e " you already have an API key configured."
72
+ echo ""
73
+ echo -e " would you like to log in and get a new one?"
74
+ echo ""
75
+ read -r -p " [y/N]: " GET_NEW_KEY < /dev/tty
76
+ echo ""
66
77
 
67
- if [ -z "$API_KEY" ]; then
78
+ if [[ ! "$GET_NEW_KEY" =~ ^[Yy]$ ]]; then
79
+ echo -e "${GREEN}✓${NC} using existing API key."
80
+ SKIP_API_KEY_SETUP=true
81
+ fi
82
+ fi
83
+
84
+ if [ "$SKIP_API_KEY_SETUP" = false ]; then
85
+ echo -e "go to ${YELLOW}https://platform.codeplain.ai${NC} and sign up to get your API key."
86
+ echo ""
87
+ read -r -p "paste your API key here: " API_KEY < /dev/tty
88
+ echo ""
89
+ fi
90
+
91
+ if [ "$SKIP_API_KEY_SETUP" = true ]; then
92
+ : # API key already set, nothing to do
93
+ elif [ -z "${API_KEY:-}" ]; then
68
94
  echo -e "${GRAY}no API key provided. you can set it later with:${NC}"
69
95
  echo -e " export CODEPLAIN_API_KEY=\"your_api_key\""
70
96
  else
@@ -74,15 +100,10 @@ else
74
100
  # Detect user's default shell from $SHELL (works even when script runs in different shell)
75
101
  case "$SHELL" in
76
102
  */zsh)
77
- SHELL_RC="$HOME/.zprofile"
103
+ SHELL_RC="$HOME/.zshrc"
78
104
  ;;
79
105
  */bash)
80
- if [[ "$OSTYPE" == "darwin"* ]]; then
81
- # macOS uses .bash_profile for login shells
82
- SHELL_RC="$HOME/.bash_profile"
83
- else
84
- SHELL_RC="$HOME/.bashrc"
85
- fi
106
+ SHELL_RC="$HOME/.bashrc"
86
107
  ;;
87
108
  *)
88
109
  SHELL_RC="$HOME/.profile"
@@ -105,12 +126,11 @@ else
105
126
  else
106
127
  sed -i "s|export CODEPLAIN_API_KEY=.*|export CODEPLAIN_API_KEY=\"$API_KEY\"|" "$SHELL_RC"
107
128
  fi
108
- echo -e "${GREEN}✓${NC} API key added to ${SHELL_RC}"
109
129
  fi
110
-
111
130
  fi
112
131
 
113
132
  # ASCII Art Welcome
133
+ clear
114
134
  echo ""
115
135
  echo -e "${NC}"
116
136
  echo -e "${GRAY}────────────────────────────────────────────${NC}"
@@ -124,6 +144,8 @@ cat << 'EOF'
124
144
  |_|
125
145
  EOF
126
146
  echo ""
147
+ echo -e "${GREEN}✓${NC} Sign in successful."
148
+ echo ""
127
149
  echo -e " ${YELLOW}welcome to *codeplain!${NC}"
128
150
  echo ""
129
151
  echo -e " spec-driven, production-ready code generation"
@@ -131,6 +153,79 @@ echo ""
131
153
  echo ""
132
154
  echo -e "${GRAY}────────────────────────────────────────────${NC}"
133
155
  echo ""
156
+ echo -e " would you like to get a quick intro to ***plain specification language?"
157
+ echo ""
158
+ read -r -p " [Y/n]: " WALKTHROUGH_CHOICE < /dev/tty
159
+ echo ""
160
+
161
+ # Determine script directory for local execution
162
+ SCRIPT_DIR=""
163
+ if [ -n "${BASH_SOURCE[0]:-}" ] && [ -f "${BASH_SOURCE[0]}" ]; then
164
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
165
+ fi
166
+
167
+ # Helper function to run a script (local or remote)
168
+ run_script() {
169
+ local script_name="$1"
170
+ local script_path=""
171
+
172
+ # Check possible local paths
173
+ if [ -n "$SCRIPT_DIR" ] && [ -f "${SCRIPT_DIR}/${script_name}" ]; then
174
+ script_path="${SCRIPT_DIR}/${script_name}"
175
+ elif [ -f "./install/${script_name}" ]; then
176
+ script_path="./install/${script_name}"
177
+ elif [ -f "./${script_name}" ]; then
178
+ script_path="./${script_name}"
179
+ fi
180
+
181
+ if [ -n "$script_path" ]; then
182
+ # Run locally
183
+ bash "$script_path" < /dev/tty
184
+ else
185
+ # Download and run
186
+ bash <(curl -fsSL "${CODEPLAIN_SCRIPTS_BASE_URL}/${script_name}") < /dev/tty
187
+ fi
188
+ }
189
+
190
+ # Run walkthrough if user agrees
191
+ if [[ ! "$WALKTHROUGH_CHOICE" =~ ^[Nn]$ ]]; then
192
+ run_script "walkthrough.sh"
193
+ fi
194
+
195
+ # Download examples step
196
+ clear
197
+ echo ""
198
+ echo -e "${GRAY}────────────────────────────────────────────${NC}"
199
+ echo -e " ${YELLOW}${BOLD}Example Projects${NC}"
200
+ echo -e "${GRAY}────────────────────────────────────────────${NC}"
201
+ echo ""
202
+ echo -e " we've prepared some example Plain projects for you"
203
+ echo -e " to explore and experiment with."
204
+ echo ""
205
+ echo -e " would you like to download them?"
206
+ echo ""
207
+ read -r -p " [Y/n]: " DOWNLOAD_EXAMPLES < /dev/tty
208
+ echo ""
209
+
210
+ # Run examples download if user agrees
211
+ if [[ ! "${DOWNLOAD_EXAMPLES:-}" =~ ^[Nn]$ ]]; then
212
+ run_script "examples.sh"
213
+ fi
214
+
215
+ # Final message
216
+ clear
217
+ echo ""
218
+ echo -e "${GRAY}────────────────────────────────────────────${NC}"
219
+ echo -e " ${YELLOW}${BOLD}You're all set!${NC}"
220
+ echo -e "${GRAY}────────────────────────────────────────────${NC}"
221
+ echo ""
134
222
  echo -e " thank you for using *codeplain!"
135
223
  echo ""
136
- echo -e " run '${YELLOW}${BOLD}codeplain <path_to_plain_file>${NC}' to get started."
224
+ echo -e " learn more at ${YELLOW}https://plainlang.org/${NC}"
225
+ echo ""
226
+ echo -e " ${GREEN}happy development!${NC} 🚀"
227
+ echo ""
228
+
229
+ # Replace this subshell with a fresh shell that has the new environment
230
+ # Reconnect stdin to terminal (needed when running via curl | bash)
231
+ exec "$SHELL" < /dev/tty