Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,12 @@ Two logging modes:
### Code Generation Process
- Templates in `standard_template_library/` use Liquid2 syntax
- Template search order: (1) `.plain` file dir, (2) `--template-dir`, (3) built-in standard lib
- Generated code written to `build/` (configurable via `--build-folder`)
- Each FRID render produces a git commit in build folder for rollback capability
- Each module renders into its own tree under the build folder (configurable via `--build-folder`, default `plain_modules/`):
- `<build-folder>/<module>/code/` — implementation code (its own git repo)
- `<build-folder>/<module>/tests/` — conformance tests (its own git repo; only created when a conformance tests script is configured)
- `<build-folder>/<module>/.codeplain/` — module metadata (`module_metadata.json`), not tracked in git
- `<build-folder>/<module>/.memory/` — conformance test memory, not tracked in git
- Each FRID render produces a git commit in the `code/` and `tests/` repos for rollback capability

### Configuration
- `config.yaml` can be placed in `.plain` file dir or CWD
Expand Down
42 changes: 22 additions & 20 deletions docs/plain2code_cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ usage: generate_cli.py [-h] [--verbose] [--base-folder BASE_FOLDER]
[--render-range RENDER_RANGE |
--render-from RENDER_FROM] [--force-render]
[--unittests-script UNITTESTS_SCRIPT]
[--conformance-tests-folder CONFORMANCE_TESTS_FOLDER]
[--conformance-tests-script CONFORMANCE_TESTS_SCRIPT]
[--prepare-environment-script PREPARE_ENVIRONMENT_SCRIPT]
[--test-script-timeout TEST_SCRIPT_TIMEOUT]
Expand All @@ -20,8 +19,8 @@ usage: generate_cli.py [-h] [--verbose] [--base-folder BASE_FOLDER]
[--conformance-tests-dest CONFORMANCE_TESTS_DEST]
[--render-machine-graph]
[--logging-config-path LOGGING_CONFIG_PATH]
[--headless]
filename
[--headless] [--status] [--version]
[filename]

Render plain code to target code. Path arguments resolve based on where they
were written: values given on the command line are resolved against the
Expand All @@ -39,7 +38,7 @@ positional arguments:

options:
-h, --help show this help message and exit
--verbose, -v Enable verbose output
--verbose, -v Set default log level to DEBUG for TUI and file logs
--base-folder BASE_FOLDER
Base folder for the build files
--build-folder BUILD_FOLDER
Expand All @@ -66,18 +65,17 @@ options:
--force-render Force re-render of all the required modules.
--unittests-script UNITTESTS_SCRIPT
Shell script to run unit tests on generated code.
Receives the build folder path as its first argument
(default: 'plain_modules').
--conformance-tests-folder CONFORMANCE_TESTS_FOLDER
Folder for conformance test files
Receives the module's code folder path as its first
argument (e.g. `plain_modules/module_name/code`).
--conformance-tests-script CONFORMANCE_TESTS_SCRIPT
Path to conformance tests shell script. Every
conformance test script should accept two arguments:
1) Path to a folder (e.g. `plain_modules/module_name`)
containing generated source code, 2) Path to a
subfolder of the conformance tests folder (e.g.
`conformance_tests/subfoldername`) containing test
files.
1) Path to a folder (e.g.
`plain_modules/module_name/code`) containing generated
source code, 2) Path to a subfolder of the module's
tests folder (e.g.
`plain_modules/module_name/tests/subfoldername`)
containing test files.
--prepare-environment-script PREPARE_ENVIRONMENT_SCRIPT
Path to a shell script that prepares the testing
environment. The script should accept the source code
Expand All @@ -90,9 +88,9 @@ options:
--api-key API_KEY API key used to access the API. If not provided, the
`CODEPLAIN_API_KEY` environment variable is used.
--full-plain Full preview ***plain specification before code
generation.Use when you want to preview context of all
***plain primitives that are going to be included in
order to render the given module.
generation. Use when you want to preview context of
all ***plain primitives that are going to be included
in order to render the given module.
--dry-run Dry run preview of the code generation (without
actually making any changes).
--replay-with REPLAY_WITH
Expand All @@ -109,10 +107,10 @@ options:
Target folder to copy rendered contents of code to
(used only if --copy-build is set).
--copy-conformance-tests
If set, copy the conformance tests of code in
`--conformance-tests-folder` folder to `--conformance-
tests-dest` folder successful rendering. Requires
--conformance-tests-script.
If set, copy the module's conformance tests (from
`<build-folder>/<module>/tests`) to `--conformance-
tests-dest` folder after successful rendering.
Requires --conformance-tests-script.
--conformance-tests-dest CONFORMANCE_TESTS_DEST
Target folder to copy conformance tests of code to
(used only if --copy-conformance-tests is set).
Expand All @@ -123,6 +121,10 @@ options:
--headless Run in headless mode: no TUI, no terminal output
except a single render-started message. All logs are
written to the log file.
--status Display account status including user information, API
key label, and rendering credits. Does not render any
code.
--version Display the client version and exit.

configuration:
--config-name CONFIG_NAME
Expand Down
10 changes: 8 additions & 2 deletions docs/starting_a_plain_project_from_scratch.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,16 @@ my-new-project/
├── run_unittests_[language].sh # Unit test script
├── run_conformance_tests_[language].sh # Conformance test script
├── build/ # Generated final code
├── plain_modules/ # Generated modules code
└── conformance_tests/ # Generated conformanece tests code
└── plain_modules/ # Generated modules
└── my_app/ # One folder per module
├── .codeplain/ # Module metadata
├── .memory/ # Conformance test memory
├── code/ # Generated code
└── tests/ # Generated conformance tests
```

The `tests/` folder is only created when a conformance test script is configured.

In this guide we will cover how to create each of these step by step.

## 1. Define Your .plain File
Expand Down
4 changes: 2 additions & 2 deletions examples/example_hello_world_golang/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ if [ $? -ne 0 ]; then
exit 1
fi

cd plain_modules/hello_world_golang
cd plain_modules/hello_world_golang/code

# We need to compile the tests so that we can execute them in the current folder
# (https://stackoverflow.com/questions/23847003/golang-tests-and-working-directory/29541248#29541248)
go test -c ../../harness_tests/hello_world_test.go
go test -c ../../../harness_tests/hello_world_test.go

# Check if test compilation has failed for the hello world example
if [ $? -ne 0 ]; then
Expand Down
4 changes: 2 additions & 2 deletions examples/example_hello_world_python/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ if [ $? -ne 0 ]; then
exit 1
fi

cd plain_modules/hello_world_python
cd plain_modules/hello_world_python/code

python ../../harness_tests/hello_world_display/test_hello_world.py
python ../../../harness_tests/hello_world_display/test_hello_world.py

# Check if the test harness has failed for the hello world example
if [ $? -ne 0 ]; then
Expand Down
2 changes: 1 addition & 1 deletion examples/example_hello_world_react/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ if [ $? -ne 0 ]; then
exit 1
fi

../../test_scripts/run_conformance_tests_cypress.sh plain_modules/hello_world_react harness_tests/hello_world_display ${VERBOSE:+-v}
../../test_scripts/run_conformance_tests_cypress.sh plain_modules/hello_world_react/code harness_tests/hello_world_display ${VERBOSE:+-v}

# Check if the test harness has failed for the hello world example
if [ $? -ne 0 ]; then
Expand Down
30 changes: 30 additions & 0 deletions examples/task-manager/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Two-module task manager example

This example is split into two modules to demonstrate a `requires` dependency chain:

- **`task-model.plain`** — the base module. Defines `:Task:`, `:TaskList:`, and `:User:`,
implements the entry point, adding a task, and showing the task list. It exports its
concepts so downstream modules can build on them.
- **`task-manager.plain`** — the top module. It `requires` `task-model`, inheriting its
generated code and functional specs as a starting point, then adds deleting, editing,
and completing tasks.

Rendering `task-manager.plain` builds `task-model` first, then continues on top of it.

# How to render the example

You can run the example with the `codeplain` command:

```bash
codeplain task-manager.plain
```

# How to run the generated code

After the rendering is finished, you can run the generated software code using the command:

```bash
python3 dist/taskmgr.py
```

See [top-level README](../../README.md) for additional information and help for troubleshooting the examples.
4 changes: 4 additions & 0 deletions examples/task-manager/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
unittests-script: ../../test_scripts/run_unittests_python.sh
conformance-tests-script: ../../test_scripts/run_conformance_tests_python.sh
copy-build: true
build-dest: dist
26 changes: 26 additions & 0 deletions examples/task-manager/task-manager.plain
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
description: 'A task manager application — extends the task model with editing features'
import:
- python-console-app-template # spec source available at https://github.com/Codeplain-ai/codeplain/blob/main/standard_template_library/
requires:
- task-model
---

***implementation reqs***

- :MainExecutableFile: of :App: should be called "taskmgr.py".

***test reqs***
> Test requirements are defined in 'python-console-app-template'.

***functional specs***

- :User: should be able to delete :Task:

- :User: should be able to edit :Task:

- :User: should be able to mark :Task: as completed.



> see https://www.plainlang.org/ for more information on ***plain
37 changes: 37 additions & 0 deletions examples/task-manager/task-model.plain
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
description: 'Core task model — base module providing tasks and a task list'
import:
- python-console-app-template # spec source available at https://github.com/Codeplain-ai/codeplain/blob/main/standard_template_library/
exported_concepts: [":User:", ":Task:", ":TaskList:"]
---

***definitions***

- :User: is the user of :App:

- :Task: describes an activity that needs to be done by :User:. :Task: has the following attributes
- Name - a short description of :Task:. This is a required attribute. The name must be at least 3 characters long.
- Notes - additional details about :Task:
- Due Date - optional date by which :User: is supposed to complete :Task:.

- :TaskList: is a list of :Task: items.
- Initially :TaskList: should be empty.

***implementation reqs***

- :MainExecutableFile: of :App: should be called "taskmgr.py".

***test reqs***
> Test requirements are defined in 'python-console-app-template'.

***functional specs***

- Implement the entry point for :App:.

- :User: should be able to add :Task:. Only valid :Task: items can be added.

- Show :TaskList:



> see https://www.plainlang.org/ for more information on ***plain
5 changes: 2 additions & 3 deletions memory_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import file_utils
from plain2code_console import console
from plain_modules import CODEPLAIN_MEMORY_SUBFOLDER
from render_machine.implementation_code_helpers import ImplementationCodeHelpers
from render_machine.render_context import RenderContext

Expand All @@ -24,9 +23,9 @@ def fetch_memory_files(memory_folder: str) -> tuple[list[str], dict[str, str]]:
console.debug(f"Loaded {len(memory_files_content)} memory files.")
return memory_files, memory_files_content

def __init__(self, codeplain_api, module_name: str, conformance_tests_folder: str):
def __init__(self, codeplain_api, memory_folder: str):
self.codeplain_api = codeplain_api
self.memory_folder = os.path.join(conformance_tests_folder, module_name, CODEPLAIN_MEMORY_SUBFOLDER)
self.memory_folder = memory_folder

def create_conformance_tests_memory(
self, render_context: RenderContext, exit_code: int, conformance_tests_issue: str
Expand Down
9 changes: 3 additions & 6 deletions module_renderer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import argparse
import os
import threading

from event_bus import EventBus
Expand Down Expand Up @@ -48,9 +47,8 @@ def _build_render_context_for_module(
self.codeplainAPI,
memory_manager,
plain_module,
build_folder=os.path.join(self.args.build_folder, plain_module.module_name),
build_folder=plain_module.module_build_folder,
build_dest=self.args.build_dest,
conformance_tests_folder=self.args.conformance_tests_folder,
conformance_tests_dest=self.args.conformance_tests_dest,
unittests_script=self.args.unittests_script,
conformance_tests_script=self.args.conformance_tests_script,
Expand Down Expand Up @@ -115,8 +113,7 @@ def _render_module(

memory_manager = MemoryManager(
self.codeplainAPI,
plain_module.module_name,
self.args.conformance_tests_folder,
plain_module.module_memory_folder,
)
render_context = self._build_render_context_for_module(
plain_module,
Expand Down Expand Up @@ -165,7 +162,7 @@ def render_module(self) -> None:
if self.args.copy_build:
rendered_code_path = f"{self.args.build_dest}/"
else:
rendered_code_path = self.args.build_folder
rendered_code_path = self.plain_module.module_build_folder

self.run_state.set_render_generated_code_path(rendered_code_path)
self.event_bus.publish(RenderCompleted(rendered_code_path=rendered_code_path))
1 change: 0 additions & 1 deletion plain2code.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,6 @@ def main(): # noqa: C901
plain_module = plain_modules.PlainModule(
os.path.basename(args.filename),
args.build_folder,
args.conformance_tests_folder,
template_dirs,
)
except Exception as e:
Expand Down
21 changes: 6 additions & 15 deletions plain2code_arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@


DEFAULT_BUILD_FOLDER = "plain_modules"
DEFAULT_CONFORMANCE_TESTS_FOLDER = "conformance_tests"
DEFAULT_BUILD_DEST = "dist"
DEFAULT_CONFORMANCE_TESTS_DEST = "dist_conformance_tests"

Expand Down Expand Up @@ -299,23 +298,15 @@ def create_parser():
parser,
"--unittests-script",
type=str,
help="Shell script to run unit tests on generated code. Receives the build folder path as its first argument (default: 'plain_modules').",
)
_add_arg(
parser,
"--conformance-tests-folder",
type=non_empty_string,
default=DEFAULT_CONFORMANCE_TESTS_FOLDER,
help="Folder for conformance test files",
path=True,
help="Shell script to run unit tests on generated code. Receives the module's code folder path as its first argument (e.g. `plain_modules/module_name/code`).",
)
_add_arg(
parser,
"--conformance-tests-script",
type=str,
help="Path to conformance tests shell script. Every conformance test script should accept two arguments: "
"1) Path to a folder (e.g. `plain_modules/module_name`) containing generated source code, "
"2) Path to a subfolder of the conformance tests folder (e.g. `conformance_tests/subfoldername`) containing test files.",
"1) Path to a folder (e.g. `plain_modules/module_name/code`) containing generated source code, "
"2) Path to a subfolder of the module's tests folder (e.g. `plain_modules/module_name/tests/subfoldername`) containing test files.",
)

_add_arg(
Expand Down Expand Up @@ -400,7 +391,7 @@ def create_parser():
"--copy-conformance-tests",
action="store_true",
default=False,
help="If set, copy the conformance tests of code in `--conformance-tests-folder` folder to `--conformance-tests-dest` folder successful rendering. Requires --conformance-tests-script.",
help="If set, copy the module's conformance tests (from `<build-folder>/<module>/tests`) to `--conformance-tests-dest` folder after successful rendering. Requires --conformance-tests-script.",
)
_add_arg(
parser,
Expand Down Expand Up @@ -490,8 +481,8 @@ def parse_arguments(command_line: Optional[Sequence[str]] = None):

if args.build_folder == args.build_dest:
parser.error("--build-folder and --build-dest cannot be the same")
if args.conformance_tests_folder == args.conformance_tests_dest:
parser.error("--conformance-tests-folder and --conformance-tests-dest cannot be the same")
if args.conformance_tests_dest == args.build_folder:
parser.error("--conformance-tests-dest and --build-folder cannot be the same")

args.render_conformance_tests = args.conformance_tests_script is not None

Expand Down
Loading
Loading