codeplain 0.3.0__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codeplain-0.3.1/.claude/settings.json +27 -0
- codeplain-0.3.1/.github/workflows/e2e.yml +62 -0
- codeplain-0.3.1/CLAUDE.md +238 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/PKG-INFO +29 -4
- {codeplain-0.3.0 → codeplain-0.3.1}/README.md +25 -0
- codeplain-0.3.1/_version.py +1 -0
- codeplain-0.3.1/cli_output/__init__.py +7 -0
- codeplain-0.3.1/cli_output/dry_run.py +35 -0
- codeplain-0.3.1/cli_output/render_summary.py +27 -0
- codeplain-0.3.1/cli_output/status.py +160 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/codeplain_REST_api.py +8 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/install/bash/install.sh +68 -37
- {codeplain-0.3.0 → codeplain-0.3.1}/install/powershell/install.ps1 +58 -34
- {codeplain-0.3.0 → codeplain-0.3.1}/memory_management.py +2 -2
- {codeplain-0.3.0 → codeplain-0.3.1}/module_renderer.py +2 -5
- {codeplain-0.3.0 → codeplain-0.3.1}/partial_rendering.py +10 -6
- {codeplain-0.3.0 → codeplain-0.3.1}/plain2code.py +56 -41
- {codeplain-0.3.0 → codeplain-0.3.1}/plain2code_arguments.py +32 -2
- {codeplain-0.3.0 → codeplain-0.3.1}/plain2code_console.py +2 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/plain2code_logger.py +37 -3
- {codeplain-0.3.0 → codeplain-0.3.1}/plain_modules.py +5 -6
- {codeplain-0.3.0 → codeplain-0.3.1}/pyproject.toml +4 -4
- {codeplain-0.3.0 → codeplain-0.3.1}/pytest.ini +3 -2
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/actions/fix_conformance_test.py +27 -29
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/actions/fix_unit_tests.py +2 -6
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/actions/prepare_repositories.py +3 -6
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/actions/prepare_testing_environment.py +3 -5
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/actions/refactor_code.py +11 -15
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/actions/render_conformance_tests.py +34 -39
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/actions/render_functional_requirement.py +15 -20
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/actions/run_conformance_tests.py +7 -9
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/actions/run_unit_tests.py +3 -5
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/code_renderer.py +1 -1
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/conformance_tests.py +9 -13
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/render_context.py +5 -13
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/render_utils.py +42 -47
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/state_machine_config.py +39 -33
- {codeplain-0.3.0 → codeplain-0.3.1}/requirements.txt +3 -3
- codeplain-0.3.1/tests/e2e/Dockerfile +31 -0
- codeplain-0.3.1/tests/e2e/conftest.py +145 -0
- codeplain-0.3.1/tests/e2e/test_hello_world_python.py +37 -0
- codeplain-0.3.1/tests/e2e/test_hello_world_python_windows.py +64 -0
- codeplain-0.3.1/tests/test_cli_output.py +411 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tui/plain2code_tui.py +11 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tui/plain_module_render_choice_tui.py +27 -9
- codeplain-0.3.0/.claude/docs/PLAIN_REFERENCE.md +0 -325
- codeplain-0.3.0/.zed/settings.json +0 -61
- codeplain-0.3.0/CLAUDE.md +0 -113
- codeplain-0.3.0/_version.py +0 -1
- {codeplain-0.3.0 → codeplain-0.3.1}/.claude/settings.local.json +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/.flake8 +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/.github/dependabot.yml +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/.github/workflows/lint-and-test.yml +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/.github/workflows/nofity-slack-on-main-merge.yml +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/.github/workflows/publish-install-script.yml +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/.github/workflows/publish-to-pypi.yml +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/.gitignore +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/.vscode/launch.json +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/.vscode/settings.json +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/LICENSE +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/concept_utils.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/config/__init__.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/config/system_config.yaml +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/diff_utils.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/docs/generate_cli.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/docs/plain2code_cli.md +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/docs/starting_a_plain_project_from_scratch.md +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/event_bus.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/examples/example_hello_world_golang/config.yaml +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/examples/example_hello_world_golang/harness_tests/hello_world_test.go +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/examples/example_hello_world_golang/hello_world_golang.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/examples/example_hello_world_golang/run.sh +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/examples/example_hello_world_python/config.yaml +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/examples/example_hello_world_python/harness_tests/hello_world_display/test_hello_world.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/examples/example_hello_world_python/hello_world_python.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/examples/example_hello_world_python/run.sh +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/examples/example_hello_world_react/config.yaml +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress/e2e/hello_world.cy.ts +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress/support/e2e.ts +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress.config.ts +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/examples/example_hello_world_react/harness_tests/hello_world_display/package.json +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/examples/example_hello_world_react/harness_tests/hello_world_display/tsconfig.json +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/examples/example_hello_world_react/hello_world_react.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/examples/example_hello_world_react/run.sh +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/examples/run.sh +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/file_utils.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/git_utils.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/hash_key.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/install/bash/examples.sh +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/install/bash/walkthrough.sh +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/install/powershell/examples.ps1 +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/install/powershell/walkthrough.ps1 +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/plain2code_events.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/plain2code_exceptions.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/plain2code_nodes.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/plain2code_read_config.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/plain2code_state.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/plain2code_utils.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/plain_file.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/plain_spec.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/__init__.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/actions/analyze_specification_ambiguity.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/actions/base_action.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/actions/commit_conformance_tests_changes.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/actions/commit_implementation_code_changes.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/actions/create_dist.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/actions/exit_with_error.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/actions/finish_functional_requirement.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/actions/summarize_conformance_tests.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/conformance_test_helpers.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/implementation_code_helpers.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/render_types.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/states.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/render_machine/triggers.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/resources/codeplain_overview.png +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/resources/plain_example.png +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/standard_template_library/__init__.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/standard_template_library/golang-console-app-template.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/standard_template_library/python-console-app-template.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/standard_template_library/typescript-react-app-boilerplate.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/standard_template_library/typescript-react-app-template.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/system_config.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/test_scripts/run_conformance_tests_cypress.ps1 +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/test_scripts/run_conformance_tests_cypress.sh +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/test_scripts/run_conformance_tests_golang.ps1 +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/test_scripts/run_conformance_tests_golang.sh +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/test_scripts/run_conformance_tests_python.ps1 +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/test_scripts/run_conformance_tests_python.sh +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/test_scripts/run_unittests_flutter.ps1 +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/test_scripts/run_unittests_golang.ps1 +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/test_scripts/run_unittests_golang.sh +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/test_scripts/run_unittests_python.ps1 +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/test_scripts/run_unittests_python.sh +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/test_scripts/run_unittests_react.ps1 +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/test_scripts/run_unittests_react.sh +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/__init__.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/conftest.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/imports/circular_imports_1.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/imports/circular_imports_2.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/imports/circular_imports_main.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/imports/diamond_import_1.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/imports/diamond_import_2.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/imports/diamond_import_common.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/imports/diamond_imports_main.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/imports/non_existent_import.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/partial_rendering/pr_leaf.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/partial_rendering/pr_middle.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/partial_rendering/pr_root.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/partial_rendering/pr_solo.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfile/duplicate_specification_heading.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfile/invalid_specification_order.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfile/missing_non_functional_requirements.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfile/plain_source_with_absolute_link.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfile/plain_source_with_url_link.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfile/task_manager_with_reference_links.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfile/without_non_functional_requirement.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/concept_validation_acceptance_tests.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/concept_validation_acceptance_tests_nondefined.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/concept_validation_defined_nondefined.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/concept_validation_defined_nondefined_2.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/concept_validation_definition.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/concept_validation_noconcepts.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/concept_validation_nonconcept.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/concept_validation_nondefined.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/concept_validation_redefinition.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/concept_validation_several_concepts.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/concept_validation_valid.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/exported_concepts.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/exported_concepts_base.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/exported_concepts_example.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/exported_concepts_missing.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/exported_concepts_missing_example.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/exported_concepts_nested.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/exported_concepts_nested_example.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/exported_concepts_transitive_example.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/exported_concepts_transitive_l1.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/exported_concepts_transitive_l2.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/plain_file_parser_with_comments.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/plain_file_with_comments_indented.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/regular_plain_source.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/required_concepts.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/required_concepts_defs.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/required_concepts_example.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/required_concepts_l1.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/required_concepts_l2.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/required_concepts_missing.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/required_concepts_module.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/required_concepts_partial.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/required_concepts_partial_duplicate.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/topological_sort.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/plainfileparser/topological_sort_not_referenced.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/requires/circular_requires_main.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/requires/circular_requires_sub.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/requires/diamond_requires_1.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/requires/diamond_requires_2.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/requires/diamond_requires_common.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/requires/diamond_requires_main.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/requires/independent_requires_1.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/requires/independent_requires_2.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/requires/independent_requires_main.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/requires/non_existent_require.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/requires/normal_requires_1.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/requires/normal_requires_2.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/requires/normal_requires_common.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/requires/normal_requires_main.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/simple.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/templates/block_level_include.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/templates/code_variables.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/templates/header.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/templates/implement.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/templates/implement_2.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/templates/template_include.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/data/templates/test_hardest_problem.plain +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/test_file_utils.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/test_git_utils.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/test_imports.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/test_partial_rendering.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/test_plain_modules.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/test_plainfile.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/test_plainfileparser.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/test_plainspec.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/test_requires.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tests/test_resolve_config_file.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tui/__init__.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tui/components.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tui/models.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tui/spinner.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tui/state_handlers.py +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tui/styles.css +0 -0
- {codeplain-0.3.0 → codeplain-0.3.1}/tui/widget_helpers.py +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"includeCoAuthoredBy": false,
|
|
3
|
+
"attribution": { "commit": "", "pr": "" },
|
|
4
|
+
"permissions": {
|
|
5
|
+
"allow": [
|
|
6
|
+
"Read",
|
|
7
|
+
"Bash(pytest *)",
|
|
8
|
+
"Bash(black *)",
|
|
9
|
+
"Bash(isort *)",
|
|
10
|
+
"Bash(flake8 *)",
|
|
11
|
+
"Bash(mypy *)",
|
|
12
|
+
"Bash(coverage *)",
|
|
13
|
+
"Bash(git status*)",
|
|
14
|
+
"Bash(git diff*)",
|
|
15
|
+
"Bash(git log*)",
|
|
16
|
+
"Bash(ls *)",
|
|
17
|
+
"Bash(cat *)",
|
|
18
|
+
"Bash(grep *)",
|
|
19
|
+
"Bash(find *)",
|
|
20
|
+
"Bash(head *)",
|
|
21
|
+
"Bash(tail *)",
|
|
22
|
+
"Bash(conda *)",
|
|
23
|
+
"Bash(.venv/bin/python *)",
|
|
24
|
+
"Bash(python plain2code.py *)"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# .github/workflows/e2e.yml
|
|
2
|
+
name: E2E
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
schedule:
|
|
6
|
+
- cron: '0 5 * * 1-5' # weekday nights to catch API drift
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
e2e-linux:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
timeout-minutes: 15
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: '3.12'
|
|
19
|
+
|
|
20
|
+
- run: pip install pytest
|
|
21
|
+
|
|
22
|
+
- name: Set up Docker Buildx
|
|
23
|
+
uses: docker/setup-buildx-action@v3
|
|
24
|
+
|
|
25
|
+
- name: Build E2E image (cached)
|
|
26
|
+
uses: docker/build-push-action@v5
|
|
27
|
+
with:
|
|
28
|
+
context: .
|
|
29
|
+
file: tests/e2e/Dockerfile
|
|
30
|
+
tags: codeplain-e2e:latest
|
|
31
|
+
load: true
|
|
32
|
+
cache-from: type=gha
|
|
33
|
+
cache-to: type=gha,mode=max
|
|
34
|
+
|
|
35
|
+
- name: Run E2E tests
|
|
36
|
+
env:
|
|
37
|
+
CODEPLAIN_API_KEY: ${{ secrets.CODEPLAIN_API_KEY }}
|
|
38
|
+
run: pytest tests/e2e/ -v --tb=short
|
|
39
|
+
|
|
40
|
+
- name: Upload generated outputs on failure
|
|
41
|
+
if: failure()
|
|
42
|
+
uses: actions/upload-artifact@v4
|
|
43
|
+
with:
|
|
44
|
+
name: e2e-outputs-linux
|
|
45
|
+
path: /tmp/pytest-of-runner/**/container_work*/**
|
|
46
|
+
|
|
47
|
+
e2e-windows:
|
|
48
|
+
runs-on: windows-latest
|
|
49
|
+
timeout-minutes: 15
|
|
50
|
+
steps:
|
|
51
|
+
- uses: actions/checkout@v4
|
|
52
|
+
|
|
53
|
+
- uses: actions/setup-python@v5
|
|
54
|
+
with:
|
|
55
|
+
python-version: '3.12'
|
|
56
|
+
|
|
57
|
+
- run: pip install pytest
|
|
58
|
+
|
|
59
|
+
- name: Run E2E tests
|
|
60
|
+
env:
|
|
61
|
+
CODEPLAIN_API_KEY: ${{ secrets.CODEPLAIN_API_KEY }}
|
|
62
|
+
run: pytest tests/e2e/ -v --tb=short
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This is **codeplain**, a Python CLI tool that renders code from `***plain` specification files using the Codeplain API. The tool acts as a client that:
|
|
8
|
+
1. Parses `***plain` spec files (a domain-specific language for software specifications)
|
|
9
|
+
2. Sends specs to the Codeplain API (LLM-based code generation service)
|
|
10
|
+
3. Manages an iterative state-machine-driven render process with testing and refactoring cycles
|
|
11
|
+
4. Outputs generated code to a build folder
|
|
12
|
+
|
|
13
|
+
The codebase serves two audiences:
|
|
14
|
+
- **End users**: developers who write `***plain` specs and run `plain2code.py` to generate code
|
|
15
|
+
- **Internal devs**: maintaining the renderer itself (this codebase)
|
|
16
|
+
|
|
17
|
+
### Related Repository: plain2code_rest_api
|
|
18
|
+
|
|
19
|
+
This repository has a sibling repository **`plain2code_rest_api`** which contains the backend API service that this client communicates with. The two repositories are typically cloned as siblings:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
parent-directory/
|
|
23
|
+
├── codeplain/ # This repository (client)
|
|
24
|
+
└── plain2code_rest_api/ # Backend API service
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**When to work across both repositories:**
|
|
28
|
+
- API contract changes (request/response formats)
|
|
29
|
+
- Adding new endpoints or modifying existing ones
|
|
30
|
+
- Debugging communication issues between client and server
|
|
31
|
+
- End-to-end feature development that requires both client and server changes
|
|
32
|
+
|
|
33
|
+
When working on features that span both repositories, coordinate changes carefully to maintain backward compatibility or plan coordinated deployments.
|
|
34
|
+
|
|
35
|
+
## Development Setup
|
|
36
|
+
|
|
37
|
+
### Prerequisites
|
|
38
|
+
- Python 3.11+
|
|
39
|
+
- `CODEPLAIN_API_KEY` environment variable set
|
|
40
|
+
|
|
41
|
+
### Installation
|
|
42
|
+
```bash
|
|
43
|
+
python -m venv .venv
|
|
44
|
+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
45
|
+
pip install -r requirements.txt
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Git Hooks
|
|
49
|
+
A pre-push hook runs automatically on `git push` and executes:
|
|
50
|
+
- Black formatting check
|
|
51
|
+
- isort import sorting check
|
|
52
|
+
- Flake8 linting
|
|
53
|
+
- Mypy type checking
|
|
54
|
+
- Full test suite with coverage
|
|
55
|
+
|
|
56
|
+
Hook is located at `.git/hooks/pre-push`.
|
|
57
|
+
|
|
58
|
+
## Common Commands
|
|
59
|
+
|
|
60
|
+
### Running the Tool
|
|
61
|
+
```bash
|
|
62
|
+
# Basic usage - render a .plain file
|
|
63
|
+
python plain2code.py path/to/file.plain
|
|
64
|
+
|
|
65
|
+
# Display account status (user info, credits, API key label)
|
|
66
|
+
python plain2code.py --status
|
|
67
|
+
|
|
68
|
+
# Dry run (parse and validate .plain files without rendering code)
|
|
69
|
+
# This parses the spec files, resolves imports/requires, but doesn't generate code
|
|
70
|
+
python plain2code.py path/to/file.plain --dry-run
|
|
71
|
+
|
|
72
|
+
# Enable file logging (writes to codeplain.log in same dir as .plain file)
|
|
73
|
+
python plain2code.py path/to/file.plain --log-to-file
|
|
74
|
+
|
|
75
|
+
# Headless mode (no TUI, logs to file only)
|
|
76
|
+
python plain2code.py path/to/file.plain --headless
|
|
77
|
+
|
|
78
|
+
# Render specific functionality range
|
|
79
|
+
python plain2code.py file.plain --render-range 1,3 # Render functionalities 1-3
|
|
80
|
+
python plain2code.py file.plain --render-from 2 # Resume from functionality 2
|
|
81
|
+
|
|
82
|
+
# Run with examples
|
|
83
|
+
cd examples/example_hello_world_python
|
|
84
|
+
python ../../plain2code.py hello_world_python.plain
|
|
85
|
+
cd build && python hello_world.py
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Testing
|
|
89
|
+
```bash
|
|
90
|
+
# Run all tests
|
|
91
|
+
pytest tests/ -v
|
|
92
|
+
|
|
93
|
+
# Run specific test file
|
|
94
|
+
pytest tests/test_plain_modules.py -v
|
|
95
|
+
|
|
96
|
+
# Run specific test
|
|
97
|
+
pytest tests/test_plain_modules.py::test_get_next_frid_within_same_module -v
|
|
98
|
+
|
|
99
|
+
# Run with coverage
|
|
100
|
+
coverage run -m pytest tests/ -v
|
|
101
|
+
coverage report
|
|
102
|
+
coverage html # Generates htmlcov/index.html
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Code Quality
|
|
106
|
+
```bash
|
|
107
|
+
# Format code
|
|
108
|
+
black .
|
|
109
|
+
|
|
110
|
+
# Sort imports
|
|
111
|
+
isort .
|
|
112
|
+
|
|
113
|
+
# Lint
|
|
114
|
+
flake8 .
|
|
115
|
+
|
|
116
|
+
# Type check
|
|
117
|
+
mypy . --check-untyped-defs
|
|
118
|
+
|
|
119
|
+
# Run all checks (same as pre-push hook)
|
|
120
|
+
black . --check && isort . --check-only && flake8 . && mypy . --check-untyped-defs && coverage run -m pytest tests/ -v && coverage report
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Architecture
|
|
124
|
+
|
|
125
|
+
### High-Level Flow
|
|
126
|
+
1. **Entry Point** (`plain2code.py`): CLI argument parsing, logging setup, TUI initialization
|
|
127
|
+
2. **Module Loading** (`plain_modules.py`, `plain_file.py`): Parse `.plain` files into `PlainModule` objects with dependency trees
|
|
128
|
+
3. **Rendering Orchestration** (`module_renderer.py`): Coordinates render process across modules
|
|
129
|
+
4. **State Machine** (`render_machine/`): Hierarchical state machine drives the render lifecycle
|
|
130
|
+
5. **API Communication** (`codeplain_REST_api.py`): HTTP client for Codeplain API
|
|
131
|
+
6. **Code Generation** (`render_machine/code_renderer.py`): Main code renderer class that orchestrates the code generation workflow using a hierarchical state machine
|
|
132
|
+
7. **TUI** (`tui/`): Textual-based terminal UI showing render progress
|
|
133
|
+
8. **CLI Output** (`cli_output/`): Non-interactive terminal output formatting for status, dry-run, and render summaries
|
|
134
|
+
|
|
135
|
+
### Key Concepts
|
|
136
|
+
|
|
137
|
+
**Plain Modules**: A `.plain` file can `import` other `.plain` files, creating a module dependency tree. The renderer processes required modules first (depth-first), then the top module.
|
|
138
|
+
|
|
139
|
+
**Functionalities (FRIDs)**: Each functional requirement in a `.plain` file has a unique ID (FRID). The renderer processes them sequentially. State is checkpointed after each FRID.
|
|
140
|
+
|
|
141
|
+
**Render State Machine** (`render_machine/states.py`): Hierarchical FSM with states like:
|
|
142
|
+
- `IMPLEMENTING_FRID` → `PROCESSING_UNIT_TESTS` → `REFACTORING_CODE` → `PROCESSING_CONFORMANCE_TESTS`
|
|
143
|
+
- Transitions handled by triggers in `render_machine/triggers.py`
|
|
144
|
+
- Config in `render_machine/state_machine_config.py`
|
|
145
|
+
|
|
146
|
+
**Memory Management** (`memory_management.py`): Tracks previously rendered code to provide context to the LLM for incremental changes. Uses git commits to persist checkpoints.
|
|
147
|
+
|
|
148
|
+
**Partial Rendering** (`partial_rendering.py`): Detects when specs or code changed and determines the optimal starting point to resume rendering (avoids full re-renders).
|
|
149
|
+
|
|
150
|
+
**Event Bus** (`event_bus.py`): Pub/sub for UI updates. Events defined in `plain2code_events.py` (e.g., `LogMessageEmitted`, `RenderCompleted`).
|
|
151
|
+
|
|
152
|
+
### Directory Structure
|
|
153
|
+
|
|
154
|
+
- `plain2code.py` - Main CLI entry point
|
|
155
|
+
- `plain2code_arguments.py` - CLI argument parsing and validation
|
|
156
|
+
- `plain2code_utils.py` - Pure utility functions (duration formatting, ambiguity messages)
|
|
157
|
+
- `plain_modules.py` - Module dependency resolution
|
|
158
|
+
- `plain_file.py` - `.plain` file parser
|
|
159
|
+
- `plain_spec.py` - Spec extraction and FRID utilities
|
|
160
|
+
- `module_renderer.py` - Orchestrates render for a module
|
|
161
|
+
- `render_machine/` - State machine implementation
|
|
162
|
+
- `code_renderer.py` - Generates code via templates
|
|
163
|
+
- `states.py` - State name constants
|
|
164
|
+
- `triggers.py` - State transition logic
|
|
165
|
+
- `render_context.py` - Shared context across states
|
|
166
|
+
- `conformance_tests.py` - Test generation and execution
|
|
167
|
+
- `tui/` - Interactive terminal UI (Textual framework)
|
|
168
|
+
- `plain2code_tui.py` - Main TUI app
|
|
169
|
+
- `components.py` - Custom UI widgets
|
|
170
|
+
- `cli_output/` - Non-interactive CLI output formatting
|
|
171
|
+
- `status.py` - Status display (`--status` flag)
|
|
172
|
+
- `dry_run.py` - Dry run output (`--dry-run` flag)
|
|
173
|
+
- `render_summary.py` - Render completion summary
|
|
174
|
+
- `codeplain_REST_api.py` - API client
|
|
175
|
+
- `memory_management.py` - Context tracking
|
|
176
|
+
- `git_utils.py` - Git operations for checkpointing
|
|
177
|
+
- `file_utils.py` - File I/O utilities
|
|
178
|
+
- `concept_utils.py` - Concept validation (***plain language feature)
|
|
179
|
+
- `plain2code_logger.py` - Custom logging with elapsed time timestamps
|
|
180
|
+
- `plain2code_console.py` - Rich console wrapper with custom styles
|
|
181
|
+
- `plain2code_state.py` - Runtime state (render ID, counters, timing)
|
|
182
|
+
- `standard_template_library/` - Built-in code templates
|
|
183
|
+
- `examples/` - Sample `.plain` projects
|
|
184
|
+
|
|
185
|
+
### Testing Architecture
|
|
186
|
+
|
|
187
|
+
Tests are in `tests/` and cover:
|
|
188
|
+
- `test_plain_modules.py` - Module loading, dependency resolution, metadata
|
|
189
|
+
- `test_plainfileparser.py` - `.plain` syntax parsing
|
|
190
|
+
- `test_partial_rendering.py` - Render resumption logic
|
|
191
|
+
- `test_git_utils.py` - Git checkpoint/restore operations
|
|
192
|
+
- `test_imports.py`, `test_requires.py` - Module dependency resolution
|
|
193
|
+
|
|
194
|
+
No integration tests hit the actual Codeplain API (would require API key + network). Testing focuses on parser, state management, and file operations.
|
|
195
|
+
|
|
196
|
+
### Logging
|
|
197
|
+
|
|
198
|
+
Two logging modes:
|
|
199
|
+
1. **Console (TUI)**: Logs displayed in TUI with relative timestamps `[HH:MM:SS]` (elapsed since render start, accounts for pauses)
|
|
200
|
+
2. **File**: Same format as TUI, written to `codeplain.log` in the `.plain` file's directory
|
|
201
|
+
|
|
202
|
+
`ElapsedTimeFormatter` (in `plain2code_logger.py`) uses `RunState` to calculate elapsed time. Format: `[HH:MM:SS] LEVEL logger_name: message`
|
|
203
|
+
|
|
204
|
+
## Important Notes
|
|
205
|
+
|
|
206
|
+
### Plain Specification Language
|
|
207
|
+
- `***plain` is a Markdown-like DSL for software specs
|
|
208
|
+
- Docs at https://www.plainlang.org/docs/
|
|
209
|
+
- Parser handles sections: definitions, functional specs, implementation reqs, test reqs, acceptance tests
|
|
210
|
+
- Concepts (domain terms) validated across modules via `concept_utils.py`
|
|
211
|
+
|
|
212
|
+
### Code Generation Process
|
|
213
|
+
- Templates in `standard_template_library/` use Liquid2 syntax
|
|
214
|
+
- Template search order: (1) `.plain` file dir, (2) `--template-dir`, (3) built-in standard lib
|
|
215
|
+
- Generated code written to `build/` (configurable via `--build-folder`)
|
|
216
|
+
- Each FRID render produces a git commit in build folder for rollback capability
|
|
217
|
+
|
|
218
|
+
### Configuration
|
|
219
|
+
- `config.yaml` can be placed in `.plain` file dir or CWD
|
|
220
|
+
- Specifies test scripts, template dirs, build paths
|
|
221
|
+
- CLI args override config file values
|
|
222
|
+
|
|
223
|
+
### Running Plain2Code from Another Directory
|
|
224
|
+
The tool can be run from any directory by providing an absolute or relative path to the `.plain` file. All paths (build folder, log file, templates) are resolved relative to the `.plain` file's directory, not the current working directory, unless explicitly overridden via CLI arguments.
|
|
225
|
+
|
|
226
|
+
### Windows Support
|
|
227
|
+
Windows users must use WSL (Windows Subsystem for Linux). The codebase has some platform-specific script handling (`.ps1` for Windows, `.sh` for Unix).
|
|
228
|
+
|
|
229
|
+
### CRITICAL: No User-Specific Paths in Version Control
|
|
230
|
+
**Never commit files containing user-specific absolute paths** (e.g., `/Users/username/...`, `/home/username/...`, `C:\Users\...`) to version-controlled files like:
|
|
231
|
+
- `.claude/settings.json` - Use relative paths (`.venv/bin/python`) instead of absolute paths
|
|
232
|
+
- `config.yaml` or any config files
|
|
233
|
+
- Any documentation or scripts
|
|
234
|
+
|
|
235
|
+
User-specific paths should only exist in:
|
|
236
|
+
- `.claude/settings.local.json` (gitignored)
|
|
237
|
+
- Personal `.env` files (gitignored)
|
|
238
|
+
- User's global `~/.claude/settings.json`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeplain
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Transform plain language specifications into working code
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Classifier: Environment :: Console
|
|
@@ -11,17 +11,17 @@ Requires-Python: ==3.11
|
|
|
11
11
|
Requires-Dist: gitpython==3.1.50
|
|
12
12
|
Requires-Dist: mistletoe==1.3.0
|
|
13
13
|
Requires-Dist: networkx==3.6.1
|
|
14
|
-
Requires-Dist: python-frontmatter==1.
|
|
14
|
+
Requires-Dist: python-frontmatter==1.3.0
|
|
15
15
|
Requires-Dist: python-liquid2==0.3.0
|
|
16
16
|
Requires-Dist: pyyaml==6.0.3
|
|
17
17
|
Requires-Dist: requests==2.34.0
|
|
18
|
-
Requires-Dist: rich==
|
|
18
|
+
Requires-Dist: rich==15.0.0
|
|
19
19
|
Requires-Dist: textual>=7.5.0
|
|
20
20
|
Requires-Dist: tiktoken==0.12.0
|
|
21
21
|
Requires-Dist: transitions==0.9.3
|
|
22
22
|
Provides-Extra: dev
|
|
23
23
|
Requires-Dist: black==26.3.1; extra == 'dev'
|
|
24
|
-
Requires-Dist: flake8==7.
|
|
24
|
+
Requires-Dist: flake8==7.3.0; extra == 'dev'
|
|
25
25
|
Requires-Dist: isort==5.13.2; extra == 'dev'
|
|
26
26
|
Requires-Dist: mypy==1.11.2; extra == 'dev'
|
|
27
27
|
Requires-Dist: pytest==8.3.5; extra == 'dev'
|
|
@@ -105,6 +105,31 @@ After completing the installation steps above, you can immediately test the syst
|
|
|
105
105
|
python hello_world.py
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
+
## Releasing
|
|
109
|
+
|
|
110
|
+
Releases are built and published with [uv](https://docs.astral.sh/uv/). The version is read from `_version.py`.
|
|
111
|
+
|
|
112
|
+
1. Bump the version in `_version.py` and commit/tag (`git tag v<version>`).
|
|
113
|
+
2. Build the distributions:
|
|
114
|
+
```bash
|
|
115
|
+
uv build
|
|
116
|
+
```
|
|
117
|
+
This produces `dist/codeplain-<version>-py3-none-any.whl` and `dist/codeplain-<version>.tar.gz`.
|
|
118
|
+
3. (Optional) Smoke-test the wheel:
|
|
119
|
+
```bash
|
|
120
|
+
uv run --with dist/codeplain-<version>-py3-none-any.whl --no-project -- codeplain --status
|
|
121
|
+
```
|
|
122
|
+
4. (Optional) Publish to TestPyPI first:
|
|
123
|
+
```bash
|
|
124
|
+
uv publish --publish-url https://test.pypi.org/legacy/ dist/codeplain-<version>*
|
|
125
|
+
```
|
|
126
|
+
5. Publish to PyPI:
|
|
127
|
+
```bash
|
|
128
|
+
uv publish dist/codeplain-<version>*
|
|
129
|
+
```
|
|
130
|
+
Authenticate with `UV_PUBLISH_TOKEN=pypi-...` (recommended), `--token pypi-...`, or `--username __token__ --password pypi-...`.
|
|
131
|
+
6. Push the tag: `git push origin v<version>`.
|
|
132
|
+
|
|
108
133
|
## Additional Resources
|
|
109
134
|
|
|
110
135
|
### Examples and Sample Projects
|
|
@@ -76,6 +76,31 @@ After completing the installation steps above, you can immediately test the syst
|
|
|
76
76
|
python hello_world.py
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
## Releasing
|
|
80
|
+
|
|
81
|
+
Releases are built and published with [uv](https://docs.astral.sh/uv/). The version is read from `_version.py`.
|
|
82
|
+
|
|
83
|
+
1. Bump the version in `_version.py` and commit/tag (`git tag v<version>`).
|
|
84
|
+
2. Build the distributions:
|
|
85
|
+
```bash
|
|
86
|
+
uv build
|
|
87
|
+
```
|
|
88
|
+
This produces `dist/codeplain-<version>-py3-none-any.whl` and `dist/codeplain-<version>.tar.gz`.
|
|
89
|
+
3. (Optional) Smoke-test the wheel:
|
|
90
|
+
```bash
|
|
91
|
+
uv run --with dist/codeplain-<version>-py3-none-any.whl --no-project -- codeplain --status
|
|
92
|
+
```
|
|
93
|
+
4. (Optional) Publish to TestPyPI first:
|
|
94
|
+
```bash
|
|
95
|
+
uv publish --publish-url https://test.pypi.org/legacy/ dist/codeplain-<version>*
|
|
96
|
+
```
|
|
97
|
+
5. Publish to PyPI:
|
|
98
|
+
```bash
|
|
99
|
+
uv publish dist/codeplain-<version>*
|
|
100
|
+
```
|
|
101
|
+
Authenticate with `UV_PUBLISH_TOKEN=pypi-...` (recommended), `--token pypi-...`, or `--username __token__ --password pypi-...`.
|
|
102
|
+
6. Push the tag: `git push origin v<version>`.
|
|
103
|
+
|
|
79
104
|
## Additional Resources
|
|
80
105
|
|
|
81
106
|
### Examples and Sample Projects
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.1"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""CLI output formatting for non-interactive display."""
|
|
2
|
+
|
|
3
|
+
from cli_output.dry_run import print_dry_run_output
|
|
4
|
+
from cli_output.render_summary import print_exit_summary
|
|
5
|
+
from cli_output.status import print_status
|
|
6
|
+
|
|
7
|
+
__all__ = ["print_dry_run_output", "print_exit_summary", "print_status"]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Dry run output display for --dry-run flag."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import plain_spec
|
|
6
|
+
from plain2code_console import console
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def print_dry_run_output(plain_source_tree: dict, render_range: Optional[list[str]]):
|
|
10
|
+
"""Print dry run output showing what would be rendered."""
|
|
11
|
+
frid = plain_spec.get_first_frid(plain_source_tree)
|
|
12
|
+
|
|
13
|
+
while frid is not None:
|
|
14
|
+
is_inside_range = render_range is None or frid in render_range
|
|
15
|
+
|
|
16
|
+
if is_inside_range:
|
|
17
|
+
specifications, _ = plain_spec.get_specifications_for_frid(plain_source_tree, frid)
|
|
18
|
+
functional_requirement_text = specifications[plain_spec.FUNCTIONAL_REQUIREMENTS][-1]
|
|
19
|
+
console.info(
|
|
20
|
+
"-------------------------------------\n"
|
|
21
|
+
f"Rendering functionality {frid}:\n"
|
|
22
|
+
f"{functional_requirement_text}\n"
|
|
23
|
+
"-------------------------------------\n"
|
|
24
|
+
)
|
|
25
|
+
if plain_spec.ACCEPTANCE_TESTS in specifications:
|
|
26
|
+
for i, acceptance_test in enumerate(specifications[plain_spec.ACCEPTANCE_TESTS], 1):
|
|
27
|
+
console.info(f"Generating acceptance test #{i}:\n\n{acceptance_test}\n")
|
|
28
|
+
else:
|
|
29
|
+
console.info(
|
|
30
|
+
"-------------------------------------\n"
|
|
31
|
+
f"Skipping rendering iteration: {frid}\n"
|
|
32
|
+
"-------------------------------------"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
frid = plain_spec.get_next_frid(plain_source_tree, frid)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Render completion summary display."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from plain2code_console import console
|
|
6
|
+
from plain2code_state import RunState
|
|
7
|
+
from plain2code_utils import format_duration_hms
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def print_exit_summary(
|
|
11
|
+
run_state: RunState,
|
|
12
|
+
spec_filename: str,
|
|
13
|
+
error_message: Optional[str] = None,
|
|
14
|
+
) -> None:
|
|
15
|
+
"""Print render outcome after the TUI exits (terminal restored)."""
|
|
16
|
+
console.quiet = False
|
|
17
|
+
|
|
18
|
+
msg = "\n[#79FC96]✓ rendering completed\n\n" if run_state.render_succeeded else "[#FF6B6B]✗ rendering failed\n\n"
|
|
19
|
+
msg += f" [#8E8F91]render id:\t\t\t[#FFFFFF]{run_state.render_id}\n"
|
|
20
|
+
msg += f" [#8E8F91]input file:\t\t\t[#FFFFFF]{spec_filename}\n"
|
|
21
|
+
msg += f" [#8E8F91]generated code folder:\t[#FFFFFF]{run_state.render_generated_code_path or '-'}\n\n"
|
|
22
|
+
msg += f"[#8E8F91]functionalities [#FFFFFF]{run_state.rendered_functionalities} [#8E8F91]used credits [#FFFFFF]{run_state.rendered_functionalities} [#8E8F91]render time [#FFFFFF]{format_duration_hms(run_state.render_time_accumulated)}\n"
|
|
23
|
+
console.print(msg)
|
|
24
|
+
|
|
25
|
+
if not run_state.render_succeeded and error_message:
|
|
26
|
+
console.error(error_message)
|
|
27
|
+
console.quiet = True
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Status display for --status flag."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import codeplain_REST_api as codeplain_api
|
|
7
|
+
from plain2code_console import console
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _create_progress_bar(remaining: int, total: int, width: int = 30) -> str:
|
|
11
|
+
"""Create a Unicode progress bar."""
|
|
12
|
+
if total == 0:
|
|
13
|
+
filled = 0
|
|
14
|
+
else:
|
|
15
|
+
filled = int((remaining / total) * width)
|
|
16
|
+
|
|
17
|
+
empty = width - filled
|
|
18
|
+
return "█" * filled + "░" * empty
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _display_credit_line(plan_credits: dict) -> None:
|
|
22
|
+
"""Display a plan credit line with progress bar."""
|
|
23
|
+
plan_type = plan_credits["type"]
|
|
24
|
+
remaining = plan_credits["remaining"]
|
|
25
|
+
total = plan_credits["total"]
|
|
26
|
+
period_end = plan_credits["period_end"]
|
|
27
|
+
|
|
28
|
+
# Parse ISO-8601 timestamp and format as "Jun 1, 2026"
|
|
29
|
+
# Handle both with and without timezone info
|
|
30
|
+
dt_str = period_end.replace("Z", "+00:00")
|
|
31
|
+
dt = datetime.fromisoformat(dt_str)
|
|
32
|
+
# If naive datetime, assume UTC
|
|
33
|
+
if dt.tzinfo is None:
|
|
34
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
35
|
+
formatted_date = dt.strftime("%b %-d, %Y")
|
|
36
|
+
|
|
37
|
+
# Check if expired
|
|
38
|
+
now = datetime.now(timezone.utc)
|
|
39
|
+
is_expired = dt < now
|
|
40
|
+
|
|
41
|
+
# Create progress bar (30 chars wide)
|
|
42
|
+
bar = _create_progress_bar(remaining, total, width=30)
|
|
43
|
+
|
|
44
|
+
# Format plan type label
|
|
45
|
+
plan_label = "Free trial" if plan_type == "free" else plan_type.upper() + " plan"
|
|
46
|
+
|
|
47
|
+
if is_expired:
|
|
48
|
+
console.print(f" {plan_label:10} {bar} expired {formatted_date}")
|
|
49
|
+
else:
|
|
50
|
+
console.print(f" {plan_label:10} {bar} {remaining:2} of {total} remaining expires {formatted_date}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _display_purchased_credit_line(bucket: dict) -> None:
|
|
54
|
+
"""Display a purchased credit line with progress bar."""
|
|
55
|
+
remaining = bucket["remaining"]
|
|
56
|
+
total = bucket["total"]
|
|
57
|
+
expiry_date = bucket["expiry_date"]
|
|
58
|
+
|
|
59
|
+
# Parse ISO-8601 timestamp and handle both with and without timezone info
|
|
60
|
+
dt_str = expiry_date.replace("Z", "+00:00")
|
|
61
|
+
dt = datetime.fromisoformat(dt_str)
|
|
62
|
+
# If naive datetime, assume UTC
|
|
63
|
+
if dt.tzinfo is None:
|
|
64
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
65
|
+
formatted_date = dt.strftime("%b %-d, %Y")
|
|
66
|
+
|
|
67
|
+
# Check if expired
|
|
68
|
+
now = datetime.now(timezone.utc)
|
|
69
|
+
is_expired = dt < now
|
|
70
|
+
|
|
71
|
+
bar = _create_progress_bar(remaining, total, width=30)
|
|
72
|
+
|
|
73
|
+
if is_expired:
|
|
74
|
+
console.print(f" Purchased {bar} expired {formatted_date}")
|
|
75
|
+
else:
|
|
76
|
+
console.print(f" Purchased {bar} {remaining:2} of {total} remaining expires {formatted_date}")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _display_status_message(plan_credits: Optional[dict], purchased_credits: list) -> None:
|
|
80
|
+
"""Display appropriate status message based on credit state."""
|
|
81
|
+
has_remaining = False
|
|
82
|
+
|
|
83
|
+
# Check if plan credits have remaining balance and are not expired
|
|
84
|
+
if plan_credits:
|
|
85
|
+
remaining = plan_credits["remaining"]
|
|
86
|
+
dt_str = plan_credits["period_end"].replace("Z", "+00:00")
|
|
87
|
+
period_end = datetime.fromisoformat(dt_str)
|
|
88
|
+
# If naive datetime, assume UTC
|
|
89
|
+
if period_end.tzinfo is None:
|
|
90
|
+
period_end = period_end.replace(tzinfo=timezone.utc)
|
|
91
|
+
now = datetime.now(timezone.utc)
|
|
92
|
+
|
|
93
|
+
if remaining > 0 and period_end > now:
|
|
94
|
+
has_remaining = True
|
|
95
|
+
|
|
96
|
+
# Check if any purchased credits have remaining balance and not expired
|
|
97
|
+
for bucket in purchased_credits:
|
|
98
|
+
dt_str = bucket["expiry_date"].replace("Z", "+00:00")
|
|
99
|
+
expiry_date = datetime.fromisoformat(dt_str)
|
|
100
|
+
# If naive datetime, assume UTC
|
|
101
|
+
if expiry_date.tzinfo is None:
|
|
102
|
+
expiry_date = expiry_date.replace(tzinfo=timezone.utc)
|
|
103
|
+
now = datetime.now(timezone.utc)
|
|
104
|
+
|
|
105
|
+
if bucket["remaining"] > 0 and expiry_date > now:
|
|
106
|
+
has_remaining = True
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
if not has_remaining:
|
|
110
|
+
console.print("\nNo rendering credits remaining. Upgrade to continue rendering.")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def print_status(api_key: str, api_url: str, client_version: str) -> None:
|
|
114
|
+
"""Display account status including user info and rendering credits."""
|
|
115
|
+
codeplainAPI = codeplain_api.CodeplainAPI(api_key, console)
|
|
116
|
+
codeplainAPI.api_url = api_url
|
|
117
|
+
|
|
118
|
+
# First check client version
|
|
119
|
+
version_check = codeplainAPI.connection_check(client_version)
|
|
120
|
+
client_version_valid = version_check.get("client_version_valid", False)
|
|
121
|
+
min_version = version_check.get("min_client_version", "unknown")
|
|
122
|
+
|
|
123
|
+
response = codeplainAPI.status()
|
|
124
|
+
|
|
125
|
+
user = response["user"]
|
|
126
|
+
api_key_label = response["api_key_label"]
|
|
127
|
+
org_owner = response.get("organization_owner_email")
|
|
128
|
+
plan_credits = response.get("plan_credits")
|
|
129
|
+
purchased_credits = response.get("purchased_credits", [])
|
|
130
|
+
|
|
131
|
+
# Display header information
|
|
132
|
+
if client_version_valid:
|
|
133
|
+
console.print(f"Version: {client_version}")
|
|
134
|
+
else:
|
|
135
|
+
from plain2code_console import Plain2CodeConsole
|
|
136
|
+
|
|
137
|
+
console.print(
|
|
138
|
+
f"Version: {client_version} (outdated — minimum required: {min_version})",
|
|
139
|
+
style=Plain2CodeConsole.ERROR_STYLE,
|
|
140
|
+
)
|
|
141
|
+
console.print("To update, run: uv tool upgrade codeplain\n", style=Plain2CodeConsole.ERROR_STYLE)
|
|
142
|
+
console.print(f"Name: {user['first_name']} {user['last_name']}")
|
|
143
|
+
console.print(f"User email: {user['email']}")
|
|
144
|
+
console.print(f"Organization owner: {org_owner or 'N/A'}")
|
|
145
|
+
console.print(f"API key label: {api_key_label}\n")
|
|
146
|
+
|
|
147
|
+
# Display rendering credits section
|
|
148
|
+
console.print("Rendering credits:")
|
|
149
|
+
|
|
150
|
+
# Display plan credits (free trial or subscription)
|
|
151
|
+
if plan_credits:
|
|
152
|
+
_display_credit_line(plan_credits)
|
|
153
|
+
|
|
154
|
+
# Display purchased credits
|
|
155
|
+
for bucket in purchased_credits:
|
|
156
|
+
_display_purchased_credit_line(bucket)
|
|
157
|
+
|
|
158
|
+
# Display status messages and management link
|
|
159
|
+
_display_status_message(plan_credits, purchased_credits)
|
|
160
|
+
console.print("\nTo manage your plan navigate to https://platform.codeplain.ai/plans")
|
|
@@ -161,6 +161,14 @@ class CodeplainAPI:
|
|
|
161
161
|
}
|
|
162
162
|
return self.post_request(endpoint_url, headers, payload, None, num_retries=0, silent=True)
|
|
163
163
|
|
|
164
|
+
def status(self):
|
|
165
|
+
endpoint_url = f"{self.api_url}/status"
|
|
166
|
+
headers = {"Content-Type": "application/json"}
|
|
167
|
+
payload = {
|
|
168
|
+
"api_key": self.api_key,
|
|
169
|
+
}
|
|
170
|
+
return self.post_request(endpoint_url, headers, payload, None, num_retries=0, silent=True)
|
|
171
|
+
|
|
164
172
|
def render_functional_requirement(
|
|
165
173
|
self,
|
|
166
174
|
frid: str,
|