codeplain 0.3.10.dev4__tar.gz → 0.3.11__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.10.dev4 → codeplain-0.3.11}/.flake8 +2 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/PKG-INFO +4 -4
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/cli_output/status.py +13 -8
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/codeplain_REST_api.py +32 -1
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/file_utils.py +14 -0
- codeplain-0.3.11/git_preflight.py +41 -0
- codeplain-0.3.11/install/bash/examples.sh +175 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/install/bash/install.sh +4 -2
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/install/powershell/examples.ps1 +65 -3
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/install/powershell/install.ps1 +9 -2
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/plain2code.py +76 -5
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/plain2code_console.py +5 -4
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/plain_file.py +51 -18
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/plain_modules.py +6 -6
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/pyproject.toml +3 -3
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/actions/fix_conformance_test.py +11 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/actions/fix_unit_tests.py +25 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/render_types.py +5 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/requirements.txt +3 -3
- codeplain-0.3.11/tests/data/plainfile/plain_source_with_missing_link.plain +9 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfile/task_manager_with_reference_links.plain +2 -2
- codeplain-0.3.11/tests/data/plainfileparser/cyclic_definitions.plain +9 -0
- codeplain-0.3.11/tests/data/plainfileparser/exported_concepts_defaults_exported.plain +13 -0
- codeplain-0.3.11/tests/data/plainfileparser/exported_concepts_imported_exported.plain +12 -0
- codeplain-0.3.11/tests/data/plainfileparser/exported_concepts_inherited_reexported.plain +15 -0
- codeplain-0.3.11/tests/data/plainfileparser/exported_concepts_malformed.plain +13 -0
- codeplain-0.3.11/tests/data/plainfileparser/exported_concepts_missing_definition.plain +14 -0
- codeplain-0.3.11/tests/data/plainfileparser/exported_concepts_missing_definition_example.plain +13 -0
- codeplain-0.3.11/tests/data/plainfileparser/exported_concepts_multiple_per_bullet.plain +14 -0
- codeplain-0.3.11/tests/data/plainfileparser/exported_concepts_multiple_per_bullet_undefined.plain +13 -0
- codeplain-0.3.11/tests/data/plainfileparser/exported_concepts_no_definitions.plain +10 -0
- codeplain-0.3.10.dev4/tests/data/plainfileparser/exported_concepts_missing_example.plain → codeplain-0.3.11/tests/data/plainfileparser/exported_concepts_not_declared_example.plain +1 -1
- codeplain-0.3.11/tests/data/plainfileparser/requires_without_definitions_example.plain +10 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_cli_output.py +15 -8
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_console_log_styles.py +61 -5
- codeplain-0.3.11/tests/test_dry_run_link_validation.py +60 -0
- codeplain-0.3.11/tests/test_fix_unit_tests_conformance_context.py +230 -0
- codeplain-0.3.11/tests/test_git_preflight.py +98 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_plainfileparser.py +82 -22
- codeplain-0.3.11/tests/test_render_finished_report.py +200 -0
- codeplain-0.3.11/tests/test_tui_components.py +147 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tui/components.py +37 -10
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tui/plain2code_tui.py +9 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tui/widget_helpers.py +11 -4
- codeplain-0.3.10.dev4/install/bash/examples.sh +0 -97
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/.claude/settings.json +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/.github/dependabot.yml +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/.github/workflows/e2e.yml +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/.github/workflows/lint-and-test.yml +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/.github/workflows/lint-powershell.yml +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/.github/workflows/nofity-slack-on-main-merge.yml +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/.github/workflows/publish-dev-to-pypi.yml +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/.github/workflows/publish-install-script.yml +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/.github/workflows/publish-to-pypi.yml +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/.gitignore +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/.vscode/launch.json +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/.vscode/settings.json +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/CLAUDE.md +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/CONTRIBUTING.md +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/LICENSE +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/README.md +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/change_detection.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/cli_output/__init__.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/cli_output/dry_run.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/cli_output/render_summary.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/concept_utils.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/config/__init__.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/config/system_config.yaml +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/conformance_fix_journal.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/diff_utils.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/docs/generate_cli.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/docs/plain2code_cli.md +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/docs/starting_a_plain_project_from_scratch.md +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/event_bus.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/examples/example_hello_world_golang/config.yaml +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/examples/example_hello_world_golang/harness_tests/hello_world_test.go +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/examples/example_hello_world_golang/hello_world_golang.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/examples/example_hello_world_golang/run.sh +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/examples/example_hello_world_python/config.yaml +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/examples/example_hello_world_python/harness_tests/hello_world_display/test_hello_world.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/examples/example_hello_world_python/hello_world_python.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/examples/example_hello_world_python/run.sh +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/examples/example_hello_world_react/config.yaml +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress/e2e/hello_world.cy.ts +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress/support/e2e.ts +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress.config.ts +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/examples/example_hello_world_react/harness_tests/hello_world_display/package.json +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/examples/example_hello_world_react/harness_tests/hello_world_display/tsconfig.json +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/examples/example_hello_world_react/hello_world_react.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/examples/example_hello_world_react/run.sh +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/examples/run.sh +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/git_utils.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/install/bash/walkthrough.sh +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/install/powershell/walkthrough.ps1 +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/memory_management.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/metadata_utils.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/module_renderer.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/partial_rendering.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/path_resolution.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/plain2code_arguments.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/plain2code_cli.md +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/plain2code_events.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/plain2code_exceptions.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/plain2code_logger.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/plain2code_nodes.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/plain2code_read_config.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/plain2code_state.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/plain2code_telemetry.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/plain2code_utils.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/plain_spec.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/pytest.ini +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/__init__.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/actions/analyze_specification_ambiguity.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/actions/base_action.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/actions/commit_conformance_tests_changes.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/actions/commit_implementation_code_changes.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/actions/create_dist.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/actions/distill_conformance_test_memory.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/actions/exit_with_error.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/actions/finish_functional_requirement.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/actions/prepare_repositories.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/actions/prepare_testing_environment.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/actions/refactor_code.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/actions/render_conformance_tests.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/actions/render_functional_requirement.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/actions/run_conformance_tests.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/actions/run_unit_tests.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/actions/summarize_conformance_tests.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/code_renderer.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/conformance_tests.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/implementation_code_helpers.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/render_context.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/render_utils.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/state_machine_config.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/states.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/render_machine/triggers.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/resources/codeplain_overview.png +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/resources/plain_example.png +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/standard_template_library/golang-console-app-template.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/standard_template_library/python-console-app-template.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/standard_template_library/typescript-react-app-boilerplate.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/standard_template_library/typescript-react-app-template.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/system_config.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/test_scripts/run_conformance_tests_cypress.ps1 +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/test_scripts/run_conformance_tests_cypress.sh +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/test_scripts/run_conformance_tests_golang.ps1 +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/test_scripts/run_conformance_tests_golang.sh +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/test_scripts/run_conformance_tests_python.ps1 +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/test_scripts/run_conformance_tests_python.sh +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/test_scripts/run_unittests_flutter.ps1 +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/test_scripts/run_unittests_flutter.sh +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/test_scripts/run_unittests_golang.ps1 +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/test_scripts/run_unittests_golang.sh +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/test_scripts/run_unittests_python.ps1 +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/test_scripts/run_unittests_python.sh +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/test_scripts/run_unittests_react.ps1 +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/test_scripts/run_unittests_react.sh +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/__init__.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/conftest.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/acceptance_tests_warning/main_requiring_acceptance_tests.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/acceptance_tests_warning/required_with_acceptance_tests.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/imports/circular_imports_1.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/imports/circular_imports_2.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/imports/circular_imports_main.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/imports/diamond_import_1.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/imports/diamond_import_2.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/imports/diamond_import_common.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/imports/diamond_imports_main.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/imports/import_with_functionalities.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/imports/imports_module_with_functionalities.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/imports/non_existent_import.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/partial_rendering/pr_code_var.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/partial_rendering/pr_implement.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/partial_rendering/pr_leaf.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/partial_rendering/pr_middle.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/partial_rendering/pr_root.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/partial_rendering/pr_solo.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfile/add_new_task_modal_specification.yaml +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfile/duplicate_specification_heading.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfile/empty_functional_specs_section.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfile/invalid_specification_order.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfile/missing_non_functional_requirements.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfile/no_functional_specs_section.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfile/plain_source_with_absolute_link.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfile/plain_source_with_url_link.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfile/task_list_ui_specification.yaml +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfile/without_non_functional_requirement.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/concept_validation_acceptance_tests.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/concept_validation_acceptance_tests_nondefined.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/concept_validation_defined_nondefined.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/concept_validation_defined_nondefined_2.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/concept_validation_definition.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/concept_validation_noconcepts.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/concept_validation_nonconcept.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/concept_validation_nondefined.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/concept_validation_redefinition.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/concept_validation_several_concepts.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/concept_validation_valid.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/exported_concepts.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/exported_concepts_base.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/exported_concepts_example.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/exported_concepts_nested.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/exported_concepts_nested_example.plain +0 -0
- /codeplain-0.3.10.dev4/tests/data/plainfileparser/exported_concepts_missing.plain → /codeplain-0.3.11/tests/data/plainfileparser/exported_concepts_not_declared.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/exported_concepts_transitive_example.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/exported_concepts_transitive_l1.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/exported_concepts_transitive_l2.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/plain_file_parser_with_comments.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/plain_file_with_comments_indented.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/regular_plain_source.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/required_concepts.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/required_concepts_defs.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/required_concepts_example.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/required_concepts_l1.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/required_concepts_l2.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/required_concepts_missing.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/required_concepts_module.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/required_concepts_partial.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/required_concepts_partial_duplicate.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/topological_sort.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/plainfileparser/topological_sort_not_referenced.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/requires/circular_requires_main.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/requires/circular_requires_sub.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/requires/diamond_requires_1.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/requires/diamond_requires_2.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/requires/diamond_requires_common.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/requires/diamond_requires_main.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/requires/independent_requires_1.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/requires/independent_requires_2.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/requires/independent_requires_main.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/requires/non_existent_require.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/requires/normal_requires_1.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/requires/normal_requires_2.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/requires/normal_requires_common.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/requires/normal_requires_main.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/sample_base64_image.txt +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/simple.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/templates/block_level_include.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/templates/code_variables.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/templates/header.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/templates/implement.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/templates/implement_2.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/templates/template_include.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/data/templates/test_hardest_problem.plain +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/e2e/Dockerfile +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/e2e/conftest.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/e2e/test_hello_world_python.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/e2e/test_hello_world_python_windows.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_arg_provenance.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_change_detection.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_conformance_fix_journal.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_distill_conformance_test_memory.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_file_utils.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_folder_path_resolution.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_git_utils.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_headless_logging.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_imports.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_log_file_name_resolution.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_logging_config_path_resolution.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_partial_rendering.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_path_resolution.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_plain2code.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_plain2code_state.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_plain2code_utils.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_plain_modules.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_plainfile.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_plainspec.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_prepare_repositories_layout.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_requires.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_resolve_config_file.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_script_path_resolution.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_system_config.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_telemetry.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_tui_usage.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tests/test_usage_summary.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tui/__init__.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tui/models.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tui/plain_module_render_choice_tui.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tui/spinner.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tui/state_handlers.py +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/tui/styles.css +0 -0
- {codeplain-0.3.10.dev4 → codeplain-0.3.11}/usage_summary.py +0 -0
|
@@ -20,6 +20,8 @@ exclude =
|
|
|
20
20
|
per-file-ignores =
|
|
21
21
|
__init__.py: F401
|
|
22
22
|
tests/*: U100
|
|
23
|
+
# require_git() runs between imports in plain_modules.py, so the imports below it trip E402
|
|
24
|
+
plain_modules.py: E402
|
|
23
25
|
max-complexity = 15
|
|
24
26
|
enable-extensions = U100
|
|
25
27
|
# Plugin-specific configuration
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: codeplain
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.11
|
|
4
4
|
Summary: Transform plain language specifications into working code
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Classifier: Environment :: Console
|
|
@@ -8,7 +8,7 @@ Classifier: Intended Audience :: Developers
|
|
|
8
8
|
Classifier: Operating System :: OS Independent
|
|
9
9
|
Classifier: Topic :: Software Development :: Code Generators
|
|
10
10
|
Requires-Python: ==3.11.*
|
|
11
|
-
Requires-Dist: gitpython==3.1.
|
|
11
|
+
Requires-Dist: gitpython==3.1.59
|
|
12
12
|
Requires-Dist: mistletoe==1.6.0
|
|
13
13
|
Requires-Dist: networkx==3.6.1
|
|
14
14
|
Requires-Dist: python-frontmatter==1.3.0
|
|
@@ -16,9 +16,9 @@ Requires-Dist: python-liquid2==0.4.0
|
|
|
16
16
|
Requires-Dist: pyyaml==6.0.3
|
|
17
17
|
Requires-Dist: requests==2.34.2
|
|
18
18
|
Requires-Dist: rich==15.0.0
|
|
19
|
-
Requires-Dist: sentry-sdk==2.
|
|
19
|
+
Requires-Dist: sentry-sdk==2.68.1
|
|
20
20
|
Requires-Dist: textual>=7.5.0
|
|
21
|
-
Requires-Dist: tiktoken==0.
|
|
21
|
+
Requires-Dist: tiktoken==0.14.0
|
|
22
22
|
Requires-Dist: transitions==0.9.3
|
|
23
23
|
Provides-Extra: dev
|
|
24
24
|
Requires-Dist: black==26.5.1; extra == 'dev'
|
|
@@ -6,6 +6,8 @@ from typing import Optional
|
|
|
6
6
|
import codeplain_REST_api as codeplain_api
|
|
7
7
|
from plain2code_console import console
|
|
8
8
|
|
|
9
|
+
PLANS_URL = "https://platform.codeplain.ai/plans"
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
def _create_progress_bar(remaining: int, total: int, width: int = 30) -> str:
|
|
11
13
|
"""Create a Unicode progress bar."""
|
|
@@ -42,12 +44,12 @@ def _display_credit_line(plan_credits: dict) -> None:
|
|
|
42
44
|
bar = _create_progress_bar(remaining, total, width=30)
|
|
43
45
|
|
|
44
46
|
# Format plan type label
|
|
45
|
-
plan_label = "Free
|
|
47
|
+
plan_label = "Free credits" if plan_type == "free" else plan_type.upper() + " plan"
|
|
46
48
|
|
|
47
49
|
if is_expired:
|
|
48
|
-
console.print(f" {plan_label:
|
|
50
|
+
console.print(f" {plan_label:12} {bar} expired {formatted_date}")
|
|
49
51
|
else:
|
|
50
|
-
console.print(f" {plan_label:
|
|
52
|
+
console.print(f" {plan_label:12} {bar} {remaining:2} of {total} remaining expires {formatted_date}")
|
|
51
53
|
|
|
52
54
|
|
|
53
55
|
def _display_bucket_credit_line(bucket: dict, label: str) -> None:
|
|
@@ -71,9 +73,9 @@ def _display_bucket_credit_line(bucket: dict, label: str) -> None:
|
|
|
71
73
|
bar = _create_progress_bar(remaining, total, width=30)
|
|
72
74
|
|
|
73
75
|
if is_expired:
|
|
74
|
-
console.print(f" {label:
|
|
76
|
+
console.print(f" {label:12} {bar} expired {formatted_date}")
|
|
75
77
|
else:
|
|
76
|
-
console.print(f" {label:
|
|
78
|
+
console.print(f" {label:12} {bar} {remaining:2} of {total} remaining expires {formatted_date}")
|
|
77
79
|
|
|
78
80
|
|
|
79
81
|
def _display_status_message(plan_credits: Optional[dict], purchased_credits: list, promo_credits: list) -> None:
|
|
@@ -107,7 +109,11 @@ def _display_status_message(plan_credits: Optional[dict], purchased_credits: lis
|
|
|
107
109
|
break
|
|
108
110
|
|
|
109
111
|
if not has_remaining:
|
|
110
|
-
console.print(
|
|
112
|
+
console.print(
|
|
113
|
+
f"\nNo rendering credits remaining. Upgrade to the PRO plan at {PLANS_URL} to continue rendering."
|
|
114
|
+
)
|
|
115
|
+
else:
|
|
116
|
+
console.print(f"\nTo manage your plan navigate to {PLANS_URL}")
|
|
111
117
|
|
|
112
118
|
|
|
113
119
|
def print_status(api_key: str, api_url: str, client_version: str) -> None:
|
|
@@ -148,7 +154,7 @@ def print_status(api_key: str, api_url: str, client_version: str) -> None:
|
|
|
148
154
|
# Display rendering credits section
|
|
149
155
|
console.print("Rendering credits:")
|
|
150
156
|
|
|
151
|
-
# Display plan credits (free
|
|
157
|
+
# Display plan credits (free credits or subscription)
|
|
152
158
|
if plan_credits:
|
|
153
159
|
_display_credit_line(plan_credits)
|
|
154
160
|
|
|
@@ -162,4 +168,3 @@ def print_status(api_key: str, api_url: str, client_version: str) -> None:
|
|
|
162
168
|
|
|
163
169
|
# Display status messages and management link
|
|
164
170
|
_display_status_message(plan_credits, purchased_credits, promo_credits)
|
|
165
|
-
console.print("\nTo manage your plan navigate to https://platform.codeplain.ai/plans")
|
|
@@ -11,6 +11,10 @@ from plain2code_state import RunState
|
|
|
11
11
|
MAX_RETRIES = 4
|
|
12
12
|
RETRY_DELAY = 3
|
|
13
13
|
|
|
14
|
+
# A report the render does not consume must not be able to hold the process open
|
|
15
|
+
# at exit, and requests waits indefinitely by default.
|
|
16
|
+
REPORT_TIMEOUT_SECONDS = 5
|
|
17
|
+
|
|
14
18
|
RETRY_ERROR_CODES = [
|
|
15
19
|
"LLMInternalError",
|
|
16
20
|
]
|
|
@@ -125,6 +129,7 @@ class CodeplainAPI:
|
|
|
125
129
|
run_state: Optional[RunState],
|
|
126
130
|
num_retries: int = MAX_RETRIES,
|
|
127
131
|
silent: bool = False,
|
|
132
|
+
timeout: Optional[int] = None,
|
|
128
133
|
):
|
|
129
134
|
if run_state is not None:
|
|
130
135
|
self._extend_payload_with_run_state(payload, run_state)
|
|
@@ -134,7 +139,7 @@ class CodeplainAPI:
|
|
|
134
139
|
|
|
135
140
|
for attempt in range(num_retries + 1):
|
|
136
141
|
try:
|
|
137
|
-
response = requests.post(endpoint_url, headers=headers, json=payload)
|
|
142
|
+
response = requests.post(endpoint_url, headers=headers, json=payload, timeout=timeout)
|
|
138
143
|
|
|
139
144
|
try:
|
|
140
145
|
response_json = response.json()
|
|
@@ -173,6 +178,27 @@ class CodeplainAPI:
|
|
|
173
178
|
}
|
|
174
179
|
return self.post_request(endpoint_url, headers, payload, None, num_retries=0, silent=True)
|
|
175
180
|
|
|
181
|
+
def render_finished(self, outcome: str, run_state: RunState):
|
|
182
|
+
"""Report the terminal state a render reached.
|
|
183
|
+
|
|
184
|
+
Called once per render, whatever the outcome. The render state machine
|
|
185
|
+
lives here, so the API cannot tell a finished render from one still
|
|
186
|
+
going; it has to be told.
|
|
187
|
+
|
|
188
|
+
No retries and no console output: a report has no result the render
|
|
189
|
+
needs, and must not add minutes to a render that has already ended nor
|
|
190
|
+
print retry noise over the exit summary.
|
|
191
|
+
"""
|
|
192
|
+
endpoint_url = f"{self.api_url}/render_finished"
|
|
193
|
+
headers = {"X-API-Key": self.api_key, "Content-Type": "application/json"}
|
|
194
|
+
payload = {
|
|
195
|
+
"outcome": outcome,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return self.post_request(
|
|
199
|
+
endpoint_url, headers, payload, run_state, num_retries=0, silent=True, timeout=REPORT_TIMEOUT_SECONDS
|
|
200
|
+
)
|
|
201
|
+
|
|
176
202
|
def render_functional_requirement(
|
|
177
203
|
self,
|
|
178
204
|
frid: str,
|
|
@@ -237,6 +263,7 @@ class CodeplainAPI:
|
|
|
237
263
|
required_modules,
|
|
238
264
|
unittests_issue,
|
|
239
265
|
run_state: RunState,
|
|
266
|
+
conformance_tests_fixes: list[dict] | None = None,
|
|
240
267
|
):
|
|
241
268
|
endpoint_url = f"{self.api_url}/fix_unittests_issue"
|
|
242
269
|
headers = {"X-API-Key": self.api_key, "Content-Type": "application/json"}
|
|
@@ -251,6 +278,10 @@ class CodeplainAPI:
|
|
|
251
278
|
"unittests_issue": unittests_issue,
|
|
252
279
|
"unittest_batch_id": run_state.unittest_batch_id,
|
|
253
280
|
}
|
|
281
|
+
# Implementation code changes made by the conformance tests fixer right before this unit tests run;
|
|
282
|
+
# only sent when unit tests are processed inside the conformance tests phase.
|
|
283
|
+
if conformance_tests_fixes is not None:
|
|
284
|
+
payload["conformance_tests_fixes"] = conformance_tests_fixes
|
|
254
285
|
|
|
255
286
|
return self.post_request(endpoint_url, headers, payload, run_state)
|
|
256
287
|
|
|
@@ -180,6 +180,20 @@ def open_from(dirs, file_name):
|
|
|
180
180
|
return None
|
|
181
181
|
|
|
182
182
|
|
|
183
|
+
def resolve_linked_resource(dirs, file_name):
|
|
184
|
+
"""Return the first existing path for file_name across dirs, or None if not found.
|
|
185
|
+
|
|
186
|
+
Mirrors the search order used by open_from so that validating a linked resource and
|
|
187
|
+
loading it can never disagree.
|
|
188
|
+
"""
|
|
189
|
+
for dir in dirs:
|
|
190
|
+
full_file_name = os.path.join(dir, file_name)
|
|
191
|
+
if os.path.exists(full_file_name):
|
|
192
|
+
return full_file_name
|
|
193
|
+
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
|
|
183
197
|
def load_linked_resources(template_dirs: list[str], resources_list, module_name: str):
|
|
184
198
|
linked_resources = {}
|
|
185
199
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Verify a working git is available before GitPython is imported.
|
|
2
|
+
|
|
3
|
+
GitPython probes for the git executable on import and raises from module scope
|
|
4
|
+
if it is missing or broken, so the CLI would crash with a traceback before
|
|
5
|
+
main() could explain it.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
from plain2code_console import console
|
|
13
|
+
|
|
14
|
+
GIT_MISSING_MESSAGE = "git is not installed. Please install git and try again."
|
|
15
|
+
GIT_BROKEN_MESSAGE = "git is installed but not working. Please repair your git installation and try again."
|
|
16
|
+
MACOS_BROKEN_GIT_HINT = (
|
|
17
|
+
"This usually means the Command Line Tools are missing; install them with 'xcode-select --install'."
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def git_runs(git_path):
|
|
22
|
+
"""Whether `git version` succeeds. A git on PATH can still be unusable: on
|
|
23
|
+
macOS without the Command Line Tools /usr/bin/git is a stub that exits with
|
|
24
|
+
an xcrun error, and GIT_PYTHON_REFRESH=quiet does not suppress that."""
|
|
25
|
+
try:
|
|
26
|
+
return subprocess.run([git_path, "version"], capture_output=True, timeout=10).returncode == 0
|
|
27
|
+
except (OSError, subprocess.SubprocessError):
|
|
28
|
+
return False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def require_git():
|
|
32
|
+
"""Exit with a clean message unless a working git is available."""
|
|
33
|
+
git_path = shutil.which("git")
|
|
34
|
+
if git_path is None:
|
|
35
|
+
console.error(f"{GIT_MISSING_MESSAGE}\n")
|
|
36
|
+
sys.exit(1)
|
|
37
|
+
|
|
38
|
+
if not git_runs(git_path):
|
|
39
|
+
hint = f"\n{MACOS_BROKEN_GIT_HINT}" if sys.platform == "darwin" else ""
|
|
40
|
+
console.error(f"{GIT_BROKEN_MESSAGE}{hint}\n")
|
|
41
|
+
sys.exit(1)
|
|
@@ -0,0 +1,175 @@
|
|
|
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
|
+
WHITE="${WHITE:-\033[38;2;255;255;255m}"
|
|
9
|
+
RED="${RED:-\033[38;2;239;68;68m}"
|
|
10
|
+
GRAY="${GRAY:-\033[38;2;128;128;128m}"
|
|
11
|
+
BOLD="${BOLD:-\033[1m}"
|
|
12
|
+
NC="${NC:-\033[0m}"
|
|
13
|
+
|
|
14
|
+
# Examples configuration
|
|
15
|
+
EXAMPLES_FOLDER_NAME="plainlang-examples"
|
|
16
|
+
EXAMPLES_DOWNLOAD_URL="https://codeplain.ai/examples/unix"
|
|
17
|
+
|
|
18
|
+
# Prompt the user, reading from the terminal when there is one. Falling back to
|
|
19
|
+
# stdin keeps the script usable in non-interactive environments instead of
|
|
20
|
+
# dying on an unreadable /dev/tty.
|
|
21
|
+
prompt_user() {
|
|
22
|
+
local prompt="$1"
|
|
23
|
+
local varname="$2"
|
|
24
|
+
|
|
25
|
+
# Test that /dev/tty can actually be opened: it exists but fails to open
|
|
26
|
+
# when the process has no controlling terminal.
|
|
27
|
+
if { : < /dev/tty; } 2>/dev/null; then
|
|
28
|
+
read -r -p "$prompt" "$varname" < /dev/tty || true
|
|
29
|
+
else
|
|
30
|
+
read -r -p "$prompt" "$varname" || true
|
|
31
|
+
fi
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Make the user acknowledge that the examples were not installed. The
|
|
35
|
+
# installation continues either way, but the failure must not scroll past
|
|
36
|
+
# unseen - the installer clears the screen in its next step.
|
|
37
|
+
confirm_continue_after_failure() {
|
|
38
|
+
echo ""
|
|
39
|
+
echo -e " ${YELLOW}The examples were not installed.${NC} ${GRAY}The installation will continue.${NC}"
|
|
40
|
+
echo ""
|
|
41
|
+
prompt_user "$(printf '%b' " Continue? Press ${WHITE}[Enter]${NC} to acknowledge: ")" _ACKNOWLEDGED
|
|
42
|
+
echo ""
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Tell the user how to get unzip on their system, then let the installer move on.
|
|
46
|
+
report_missing_unzip_tool() {
|
|
47
|
+
echo -e " ${RED}✗ Cannot extract the examples: no 'unzip' command found.${NC}"
|
|
48
|
+
echo ""
|
|
49
|
+
echo -e " ${GRAY}Install unzip and re-run the installer to get the examples:${NC}"
|
|
50
|
+
echo ""
|
|
51
|
+
case "$OSTYPE" in
|
|
52
|
+
darwin*)
|
|
53
|
+
echo -e " ${WHITE}${BOLD}brew install unzip${NC}"
|
|
54
|
+
;;
|
|
55
|
+
*)
|
|
56
|
+
echo -e " ${WHITE}${BOLD}apt-get install unzip${NC} ${GRAY}(Debian/Ubuntu)${NC}"
|
|
57
|
+
echo -e " ${WHITE}${BOLD}dnf install unzip${NC} ${GRAY}(Fedora/RHEL)${NC}"
|
|
58
|
+
echo -e " ${WHITE}${BOLD}apk add unzip${NC} ${GRAY}(Alpine)${NC}"
|
|
59
|
+
;;
|
|
60
|
+
esac
|
|
61
|
+
echo ""
|
|
62
|
+
echo -e " ${GRAY}The examples are also available at:${NC}"
|
|
63
|
+
echo -e " ${WHITE}https://github.com/Codeplain-ai/plainlang-examples${NC}"
|
|
64
|
+
echo ""
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# Checked before anything is downloaded, so a missing unzip is reported up
|
|
68
|
+
# front instead of leaving the user with an installer that just stops.
|
|
69
|
+
if ! command -v unzip &> /dev/null; then
|
|
70
|
+
report_missing_unzip_tool
|
|
71
|
+
confirm_continue_after_failure
|
|
72
|
+
exit 0
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# Show current directory and ask for extraction path
|
|
76
|
+
CURRENT_DIR=$(pwd)
|
|
77
|
+
echo -e " Current folder: ${WHITE}${CURRENT_DIR}${NC}"
|
|
78
|
+
echo ""
|
|
79
|
+
echo -e " Extract examples here, or enter a different path:"
|
|
80
|
+
echo ""
|
|
81
|
+
prompt_user " [Enter for current, or type path]: " EXTRACT_PATH
|
|
82
|
+
echo ""
|
|
83
|
+
|
|
84
|
+
# Use current directory if empty
|
|
85
|
+
if [ -z "${EXTRACT_PATH:-}" ]; then
|
|
86
|
+
EXTRACT_PATH="$CURRENT_DIR"
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# Expand ~ to home directory
|
|
90
|
+
EXTRACT_PATH="${EXTRACT_PATH/#\~/$HOME}"
|
|
91
|
+
|
|
92
|
+
SKIP_DOWNLOAD=false
|
|
93
|
+
|
|
94
|
+
# Check if directory exists, create if not
|
|
95
|
+
if [ ! -d "$EXTRACT_PATH" ]; then
|
|
96
|
+
echo -e " ${GRAY}Creating directory...${NC}"
|
|
97
|
+
if ! mkdir -p "$EXTRACT_PATH" 2>/dev/null; then
|
|
98
|
+
echo -e " ${RED}✗ Failed to create directory: ${EXTRACT_PATH}${NC}"
|
|
99
|
+
confirm_continue_after_failure
|
|
100
|
+
SKIP_DOWNLOAD=true
|
|
101
|
+
fi
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
EXAMPLES_INSTALLED=false
|
|
105
|
+
|
|
106
|
+
if [ "$SKIP_DOWNLOAD" = false ]; then
|
|
107
|
+
echo -e " ${GRAY}Downloading examples...${NC}"
|
|
108
|
+
|
|
109
|
+
# Download the zip file
|
|
110
|
+
TEMP_ZIP=$(mktemp)
|
|
111
|
+
DOWNLOAD_OK=false
|
|
112
|
+
DOWNLOAD_ERROR="Could not download ${EXAMPLES_DOWNLOAD_URL}"
|
|
113
|
+
if curl -L -s -o "$TEMP_ZIP" "$EXAMPLES_DOWNLOAD_URL" && [ -s "$TEMP_ZIP" ]; then
|
|
114
|
+
DOWNLOAD_OK=true
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
# A server error page is a successful download of the wrong thing, so check
|
|
118
|
+
# for the zip magic bytes rather than handing unzip an HTML page.
|
|
119
|
+
if [ "$DOWNLOAD_OK" = true ] && [ "$(head -c 2 "$TEMP_ZIP")" != "PK" ]; then
|
|
120
|
+
DOWNLOAD_OK=false
|
|
121
|
+
DOWNLOAD_ERROR="The downloaded file is not a zip archive."
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
if [ "$DOWNLOAD_OK" = true ]; then
|
|
125
|
+
echo -e " ${GRAY}Extracting to ${EXTRACT_PATH}...${NC}"
|
|
126
|
+
|
|
127
|
+
# Extract the zip file (contents are at the zip root, so extract into the target folder)
|
|
128
|
+
EXTRACTED_DIR="${EXTRACT_PATH}/${EXAMPLES_FOLDER_NAME}"
|
|
129
|
+
rm -rf "$EXTRACTED_DIR" 2>/dev/null || true # Remove existing if present
|
|
130
|
+
|
|
131
|
+
# Keep the extractor's own error output: it is the only clue about why
|
|
132
|
+
# extraction failed (corrupt download, no disk space, no permissions).
|
|
133
|
+
if EXTRACT_OUTPUT=$(unzip -q -o "$TEMP_ZIP" -d "$EXTRACTED_DIR" 2>&1); then
|
|
134
|
+
# Remove the .gitignore file from the root of the extracted directory
|
|
135
|
+
if [ -f "${EXTRACTED_DIR}/.gitignore" ]; then
|
|
136
|
+
rm -f "${EXTRACTED_DIR}/.gitignore"
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
clear || true
|
|
140
|
+
echo ""
|
|
141
|
+
echo -e " ${GREEN}✓ Examples downloaded successfully!${NC}"
|
|
142
|
+
echo ""
|
|
143
|
+
echo -e " ${GRAY}Examples are in: ${EXTRACTED_DIR}${NC}"
|
|
144
|
+
echo ""
|
|
145
|
+
echo -e " ${WHITE}${BOLD}Try the hello, world example:${NC}"
|
|
146
|
+
echo ""
|
|
147
|
+
echo -e " ${GRAY}Example folder:${NC} ${WHITE}cd ${EXTRACTED_DIR}/hello-world/python${NC}"
|
|
148
|
+
echo ""
|
|
149
|
+
echo -e " ${GRAY}Render the example:${NC} ${WHITE}codeplain hello-world-python.plain${NC}"
|
|
150
|
+
echo ""
|
|
151
|
+
echo -e " ${GRAY}See hello-world/python/README.md for details.${NC}"
|
|
152
|
+
echo ""
|
|
153
|
+
EXAMPLES_INSTALLED=true
|
|
154
|
+
else
|
|
155
|
+
echo -e " ${RED}✗ Failed to extract examples.${NC}"
|
|
156
|
+
if [ -n "$EXTRACT_OUTPUT" ]; then
|
|
157
|
+
echo -e " ${GRAY}$(echo "$EXTRACT_OUTPUT" | tail -n 3)${NC}"
|
|
158
|
+
fi
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
# Clean up temp file
|
|
162
|
+
rm -f "$TEMP_ZIP"
|
|
163
|
+
else
|
|
164
|
+
echo -e " ${RED}✗ Failed to download examples.${NC}"
|
|
165
|
+
echo -e " ${GRAY}${DOWNLOAD_ERROR}${NC}"
|
|
166
|
+
rm -f "$TEMP_ZIP"
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
if [ "$EXAMPLES_INSTALLED" = true ]; then
|
|
170
|
+
echo ""
|
|
171
|
+
prompt_user "$(printf '%b' " Press ${WHITE}[Enter]${NC} to continue...")" _CONTINUE
|
|
172
|
+
else
|
|
173
|
+
confirm_continue_after_failure
|
|
174
|
+
fi
|
|
175
|
+
fi
|
|
@@ -425,9 +425,11 @@ else
|
|
|
425
425
|
echo ""
|
|
426
426
|
fi
|
|
427
427
|
|
|
428
|
-
# Run examples download if user agrees
|
|
428
|
+
# Run examples download if user agrees. The examples are a nice-to-have, so a
|
|
429
|
+
# failure there must never abort the installation (set -e would otherwise take
|
|
430
|
+
# the whole installer down without printing anything).
|
|
429
431
|
if [[ ! "${DOWNLOAD_EXAMPLES:-}" =~ ^[Nn]$ ]]; then
|
|
430
|
-
run_script "examples.sh"
|
|
432
|
+
run_script "examples.sh" || echo -e "${GRAY}Example download did not complete. Continuing with the installation.${NC}"
|
|
431
433
|
fi
|
|
432
434
|
|
|
433
435
|
# Final verification: make sure the installed codeplain can actually run and
|
|
@@ -19,6 +19,39 @@ $CROSS = [char]0x2717
|
|
|
19
19
|
$EXAMPLES_FOLDER_NAME = "plainlang-examples"
|
|
20
20
|
$EXAMPLES_DOWNLOAD_URL = "https://codeplain.ai/examples/windows"
|
|
21
21
|
|
|
22
|
+
# Make the user acknowledge that the examples were not installed. The
|
|
23
|
+
# installation continues either way, but the failure must not scroll past
|
|
24
|
+
# unseen - the installer clears the screen in its next step.
|
|
25
|
+
function Confirm-ContinueAfterFailure {
|
|
26
|
+
Write-Host ""
|
|
27
|
+
Write-Host " ${YELLOW}The examples were not installed.${NC} ${GRAY}The installation will continue.${NC}"
|
|
28
|
+
Write-Host ""
|
|
29
|
+
Read-Host " Continue? Press ${WHITE}[Enter]${NC} to acknowledge"
|
|
30
|
+
Write-Host ""
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Expand-Archive lives in the Microsoft.PowerShell.Archive module, shipped with
|
|
34
|
+
# PowerShell 5.0 and later. Checked before anything is downloaded, so a machine
|
|
35
|
+
# without it is told what to do instead of just seeing an extraction failure.
|
|
36
|
+
function Show-MissingArchiveCmdletMessage {
|
|
37
|
+
Write-Host " ${RED}${CROSS} Cannot extract the examples: Expand-Archive is not available.${NC}"
|
|
38
|
+
Write-Host ""
|
|
39
|
+
Write-Host " ${GRAY}Expand-Archive requires PowerShell 5.0 or later. Install a newer${NC}"
|
|
40
|
+
Write-Host " ${GRAY}PowerShell and re-run the installer to get the examples:${NC}"
|
|
41
|
+
Write-Host ""
|
|
42
|
+
Write-Host " ${WHITE}${BOLD}https://aka.ms/powershell${NC}"
|
|
43
|
+
Write-Host ""
|
|
44
|
+
Write-Host " ${GRAY}The examples are also available at:${NC}"
|
|
45
|
+
Write-Host " ${WHITE}https://github.com/Codeplain-ai/plainlang-examples${NC}"
|
|
46
|
+
Write-Host ""
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (-not (Get-Command Expand-Archive -ErrorAction SilentlyContinue)) {
|
|
50
|
+
Show-MissingArchiveCmdletMessage
|
|
51
|
+
Confirm-ContinueAfterFailure
|
|
52
|
+
exit 0
|
|
53
|
+
}
|
|
54
|
+
|
|
22
55
|
# Show current directory and ask for extraction path
|
|
23
56
|
$CURRENT_DIR = Get-Location
|
|
24
57
|
Write-Host " Current folder: ${WHITE}${CURRENT_DIR}${NC}"
|
|
@@ -47,11 +80,13 @@ if (-not (Test-Path $EXTRACT_PATH -PathType Container)) {
|
|
|
47
80
|
New-Item -ItemType Directory -Path $EXTRACT_PATH -Force | Out-Null
|
|
48
81
|
} catch {
|
|
49
82
|
Write-Host " ${RED}${CROSS} Failed to create directory: ${EXTRACT_PATH}${NC}"
|
|
50
|
-
|
|
83
|
+
Confirm-ContinueAfterFailure
|
|
51
84
|
$SKIP_DOWNLOAD = $true
|
|
52
85
|
}
|
|
53
86
|
}
|
|
54
87
|
|
|
88
|
+
$EXAMPLES_INSTALLED = $false
|
|
89
|
+
|
|
55
90
|
if (-not $SKIP_DOWNLOAD) {
|
|
56
91
|
Write-Host " ${GRAY}Downloading examples...${NC}"
|
|
57
92
|
|
|
@@ -60,7 +95,26 @@ if (-not $SKIP_DOWNLOAD) {
|
|
|
60
95
|
try {
|
|
61
96
|
Invoke-WebRequest -Uri $EXAMPLES_DOWNLOAD_URL -OutFile $TEMP_ZIP -UseBasicParsing
|
|
62
97
|
|
|
98
|
+
# Check for the zip magic bytes rather than handing Expand-Archive a
|
|
99
|
+
# server error page. Read via a stream: 'Get-Content -Encoding Byte' is
|
|
100
|
+
# Windows PowerShell 5.1 only, '-AsByteStream' is PowerShell 6+ only.
|
|
101
|
+
$IS_ZIP = $false
|
|
63
102
|
if (Test-Path $TEMP_ZIP) {
|
|
103
|
+
$HEADER = New-Object byte[] 2
|
|
104
|
+
$STREAM = [System.IO.File]::OpenRead($TEMP_ZIP)
|
|
105
|
+
try {
|
|
106
|
+
$BYTES_READ = $STREAM.Read($HEADER, 0, 2)
|
|
107
|
+
} finally {
|
|
108
|
+
$STREAM.Close()
|
|
109
|
+
}
|
|
110
|
+
$IS_ZIP = ($BYTES_READ -eq 2 -and $HEADER[0] -eq 0x50 -and $HEADER[1] -eq 0x4B)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (-not $IS_ZIP -and (Test-Path $TEMP_ZIP)) {
|
|
114
|
+
Write-Host " ${RED}${CROSS} Failed to download examples.${NC}"
|
|
115
|
+
Write-Host " ${GRAY}The downloaded file is not a zip archive.${NC}"
|
|
116
|
+
Remove-Item -Path $TEMP_ZIP -Force -ErrorAction SilentlyContinue
|
|
117
|
+
} elseif ($IS_ZIP) {
|
|
64
118
|
Write-Host " ${GRAY}Extracting to ${EXTRACT_PATH}...${NC}"
|
|
65
119
|
|
|
66
120
|
try {
|
|
@@ -91,8 +145,12 @@ if (-not $SKIP_DOWNLOAD) {
|
|
|
91
145
|
Write-Host ""
|
|
92
146
|
Write-Host " ${GRAY}See hello-world/python/README.md for details.${NC}"
|
|
93
147
|
Write-Host ""
|
|
148
|
+
$EXAMPLES_INSTALLED = $true
|
|
94
149
|
} catch {
|
|
150
|
+
# Keep the cmdlet's own message: it is the only clue about why
|
|
151
|
+
# extraction failed (corrupt download, no space, no permissions).
|
|
95
152
|
Write-Host " ${RED}${CROSS} Failed to extract examples.${NC}"
|
|
153
|
+
Write-Host " ${GRAY}$($_.Exception.Message)${NC}"
|
|
96
154
|
}
|
|
97
155
|
|
|
98
156
|
Remove-Item -Path $TEMP_ZIP -Force -ErrorAction SilentlyContinue
|
|
@@ -104,6 +162,10 @@ if (-not $SKIP_DOWNLOAD) {
|
|
|
104
162
|
Remove-Item -Path $TEMP_ZIP -Force -ErrorAction SilentlyContinue
|
|
105
163
|
}
|
|
106
164
|
|
|
107
|
-
|
|
108
|
-
|
|
165
|
+
if ($EXAMPLES_INSTALLED) {
|
|
166
|
+
Write-Host ""
|
|
167
|
+
Read-Host " Press ${WHITE}[Enter]${NC} to continue..."
|
|
168
|
+
} else {
|
|
169
|
+
Confirm-ContinueAfterFailure
|
|
170
|
+
}
|
|
109
171
|
}
|
|
@@ -526,9 +526,16 @@ if ($nonInteractive) {
|
|
|
526
526
|
Write-Host ""
|
|
527
527
|
}
|
|
528
528
|
|
|
529
|
-
# Run examples download if user agrees
|
|
529
|
+
# Run examples download if user agrees. The examples are a nice-to-have, so a
|
|
530
|
+
# failure there must never abort the installation: examples.ps1 runs in this
|
|
531
|
+
# runspace via the call operator, so a terminating error inside it would
|
|
532
|
+
# otherwise propagate here and take the whole installer down.
|
|
530
533
|
if ($downloadExamples -notmatch '^[Nn]$') {
|
|
531
|
-
|
|
534
|
+
try {
|
|
535
|
+
Invoke-SubScript "examples.ps1"
|
|
536
|
+
} catch {
|
|
537
|
+
Write-Host "${GRAY}Example download did not complete. Continuing with the installation.${NC}"
|
|
538
|
+
}
|
|
532
539
|
}
|
|
533
540
|
|
|
534
541
|
# Final verification: make sure the installed codeplain can actually run and
|