Skip to content
Merged
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
6 changes: 3 additions & 3 deletions codeplain_REST_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ def _handle_retry_logic(
connection_error_type = "Network error" if is_connection_error else "Error"
if attempt < num_retries:
if not silent:
self.console.info(f"{connection_error_type} on attempt {attempt + 1}/{num_retries + 1}: {error}")
self.console.info(f"Retrying in {retry_delay} seconds...")
self.console.debug(f"{connection_error_type} on attempt {attempt + 1}/{num_retries + 1}: {error}")
self.console.debug(f"Retrying in {retry_delay} seconds...")
time.sleep(retry_delay)
# Exponential backoff
return retry_delay * 2
Expand Down Expand Up @@ -117,7 +117,7 @@ def post_request(
try:
response_json = response.json()
except requests.exceptions.JSONDecodeError as e:
print(f"Failed to decode JSON response: {e}. Response text: {response.text}")
self.console.debug(f"Failed to decode JSON response: {e}. Response text: {response.text}")
raise

if response.status_code == requests.codes.bad_request and "error_code" in response_json:
Expand Down
25 changes: 15 additions & 10 deletions docs/generate_cli.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import os
import sys

from plain2code_arguments import create_parser

# Add the parent directory to the path so we can import plain2code_arguments
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))


# Get the parser and generate help text
parser = create_parser()
help_text = parser.format_help()
def main():
from plain2code_arguments import create_parser

# Get the parser and generate help text
parser = create_parser(color=False) # Disable color for markdown output
help_text = parser.format_help()

# Create markdown
md = "# Plain2Code CLI Reference\n\n```text\n" + help_text + "\n```"

# Run generate_cli.py in the docs folder

# Create markdown
md = "# Plain2Code CLI Reference\n\n```text\n" + help_text + "\n```"
with open("plain2code_cli.md", "w", encoding="utf-8") as f:
f.write(md)

# Run generate_cli.py in the docs folder

with open("plain2code_cli.md", "w", encoding="utf-8") as f:
f.write(md)
if __name__ == "__main__":
main()
63 changes: 38 additions & 25 deletions docs/plain2code_cli.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
# Plain2Code CLI Reference

```text
usage: generate_cli.py [-h] [--verbose] [--base-folder BASE_FOLDER] [--build-folder BUILD_FOLDER] [--log-to-file | --no-log-to-file] [--log-file-name LOG_FILE_NAME] [--config-name CONFIG_NAME]
[--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] [--api [API]] [--api-key API_KEY]
[--full-plain] [--dry-run] [--replay-with REPLAY_WITH] [--template-dir TEMPLATE_DIR] [--copy-build] [--build-dest BUILD_DEST] [--copy-conformance-tests]
[--conformance-tests-dest CONFORMANCE_TESTS_DEST] [--render-machine-graph] [--logging-config-path] [--headless]
usage: generate_cli.py [-h] [--verbose] [--base-folder BASE_FOLDER] [--build-folder BUILD_FOLDER] [--log-to-file | --no-log-to-file]
[--log-file-name LOG_FILE_NAME] [--config-name CONFIG_NAME] [--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] [--api [API]] [--api-key API_KEY] [--full-plain] [--dry-run]
[--replay-with REPLAY_WITH] [--template-dir TEMPLATE_DIR] [--copy-build] [--build-dest BUILD_DEST]
[--copy-conformance-tests] [--conformance-tests-dest CONFORMANCE_TESTS_DEST] [--render-machine-graph]
[--logging-config-path LOGGING_CONFIG_PATH] [--headless]
filename

Render plain code to target code.

positional arguments:
filename Path to the plain file to render. The directory containing this file has highest precedence for template loading, so you can place custom templates here to override the defaults. See --template-dir
for more details about template loading.
filename Path to the plain file to render. The directory containing this file has highest precedence for template loading, so
you can place custom templates here to override the defaults. See --template-dir for more details about template
loading.

options:
-h, --help show this help message and exit
Expand All @@ -24,49 +28,58 @@ options:
--log-to-file, --no-log-to-file
Enable logging to a file. Defaults to True. Set to False to disable.
--log-file-name LOG_FILE_NAME
Name of the log file. Defaults to 'codeplain.log'.Always resolved relative to the plain file directory.If file on this path already exists, the already existing log file will be overwritten by the
current logs.
Name of the log file. Defaults to 'codeplain.log'.Always resolved relative to the plain file directory.If file on
this path already exists, the already existing log file will be overwritten by the current logs.
--render-range RENDER_RANGE
Specify a range of functionalities to render (e.g. `1` , `2`, `3`). Use comma to separate start and end IDs. If only one ID is provided, only that requirement is rendered. Range is
inclusive of both start and end IDs.
Specify a range of functionalities to render (e.g. `1` , `2`, `3`). Use comma to separate start and end IDs. If only
one functionality ID is provided, only that functionality is rendered. Range is inclusive of both start and end IDs.
--render-from RENDER_FROM
Continue generation starting from this specific functionality (e.g. `2`). The requirement with this ID will be included in the output. The ID must match one of the functionalities
in your plain file.
Continue generation starting from this specific functionality (e.g. `2`). The functionality with this ID will be
included in the output. The functionality ID must match one of the functionalities in your plain file.
--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').
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
--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.
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.
--prepare-environment-script PREPARE_ENVIRONMENT_SCRIPT
Path to a shell script that prepares the testing environment. The script should accept the source code folder path as its first argument.
Path to a shell script that prepares the testing environment. The script should accept the source code folder path as
its first argument.
--test-script-timeout TEST_SCRIPT_TIMEOUT
Timeout for test scripts in seconds. If not provided, the default timeout of 120 seconds is used.
--api [API] Alternative base URL for the API. Default: `https://api.codeplain.ai`
--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.
--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.
--dry-run Dry run preview of the code generation (without actually making any changes).
--replay-with REPLAY_WITH
--template-dir TEMPLATE_DIR
Path to a custom template directory. Templates are searched in the following order: 1) Directory containing the plain file, 2) Custom template directory (if provided through this argument), 3)
Built-in standard_template_library directory
--copy-build If set, copy the rendered contents of code in `--base-folder` folder to `--build-dest` folder after successful rendering.
Path to a custom template directory. Templates are searched in the following order: 1) Directory containing the plain
file, 2) Custom template directory (if provided through this argument), 3) Built-in standard_template_library
directory
--copy-build If set, copy the rendered contents of code in `--base-folder` folder to `--build-dest` folder after successful
rendering.
--build-dest BUILD_DEST
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 conformance tests of code in `--conformance-tests-folder` folder to `--conformance-tests-dest`
folder 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).
--render-machine-graph
If set, render the state machine graph.
--logging-config-path
--logging-config-path LOGGING_CONFIG_PATH
Path to the logging configuration file.
--headless Run in headless mode: no TUI, no terminal output except a single render-started message. All logs are written to the log file.
--headless Run in headless mode: no TUI, no terminal output except a single render-started message. All logs are written to the
log file.

configuration:
--config-name CONFIG_NAME
Path to the config file, defaults to config.yaml
Name of the config file to look for. Looked up in the plain file directory and the current working directory.
Defaults to config.yaml.

```
11 changes: 7 additions & 4 deletions file_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from liquid2.exceptions import UndefinedError

import plain_spec
from plain2code_console import console
from plain2code_nodes import Plain2CodeIncludeTag, Plain2CodeLoaderMixin
from plain_modules import CODEPLAIN_MEMORY_SUBFOLDER, CODEPLAIN_METADATA_FOLDER

Expand Down Expand Up @@ -79,7 +80,7 @@ def list_all_text_files(directory):
with open(os.path.join(root, filename), "rb") as f:
f.read().decode("utf-8")
except UnicodeDecodeError:
print(f"WARNING! Not listing {filename} in {root}. File is not a text file. Skipping it.")
console.debug(f"WARNING! Not listing {filename} in {root}. File is not a text file. Skipping it.")
continue

all_files.append(os.path.join(modified_root, filename))
Expand Down Expand Up @@ -178,7 +179,7 @@ def get_existing_files_content(build_folder, existing_files):
try:
existing_files_content[file_name] = content.decode("utf-8")
except UnicodeDecodeError:
print(f"WARNING! Error loading {file_name}. File is not a text file. Skipping it.")
console.debug(f"WARNING! Error loading {file_name}. File is not a text file. Skipping it.")

return existing_files_content

Expand All @@ -193,7 +194,7 @@ def store_response_files(target_folder, response_files, existing_files):
os.remove(full_file_name)
existing_files.remove(file_name)
else:
print(f"WARNING! Cannot delete file! File {full_file_name} does not exist.")
console.debug(f"WARNING! Cannot delete file! File {full_file_name} does not exist.")

continue

Expand All @@ -219,7 +220,9 @@ def open_from(dirs, file_name):
try:
content_text = content.decode("utf-8")
except UnicodeDecodeError:
print(f"WARNING! Error loading {file_name} ({file_name}). File is not a text file. Skipping it.")
console.debug(
f"WARNING! Error loading {file_name} ({file_name}). File is not a text file. Skipping it."
)
return content_text

return None
Expand Down
2 changes: 1 addition & 1 deletion memory_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def fetch_memory_files(memory_folder: str) -> tuple[list[str], dict[str, str]]:
return [], {}
memory_files = file_utils.list_all_text_files(memory_path)
memory_files_content = file_utils.get_existing_files_content(memory_path, memory_files)
console.info(f"Loaded {len(memory_files_content)} memory files.")
console.debug(f"Loaded {len(memory_files_content)} memory files.")
return memory_files, memory_files_content

def __init__(self, codeplain_api, module_build_folder: str):
Expand Down
2 changes: 1 addition & 1 deletion module_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def _render_module(
required_modules = []
has_any_required_module_changed = False
if not self.args.render_machine_graph and required_modules_list:
console.info(f"Analyzing required modules of module {module_name}...")
console.debug(f"Analyzing required modules of module {module_name}...")
for required_module_name in required_modules_list:
required_module_filename = required_module_name + plain_file.PLAIN_SOURCE_FILE_EXTENSION
has_module_changed, sub_required_modules, rendering_failed = self._render_module(
Expand Down
3 changes: 2 additions & 1 deletion plain2code.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,14 +221,15 @@ def render(args, run_state: RunState, event_bus: EventBus): # noqa: C901
def run_render():
try:
module_renderer.render_module()
console.info(f"[#79FC96]Render {run_state.render_id} completed successfully.[/#79FC96]")
except RenderCancelledError:
pass # TUI already closed, nothing to report
except Exception as e:
render_error.append(e)
event_bus.publish(RenderFailed(error_message=str(e)))

if args.headless:
print(f"Render started. Render ID: {run_state.render_id}")
console.info(f"Render started. Render ID: {run_state.render_id}")
try:
module_renderer.render_module()
except RenderCancelledError:
Expand Down
17 changes: 13 additions & 4 deletions plain2code_arguments.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import argparse
import os
import sys
from typing import Any

from plain2code_console import console
from plain2code_exceptions import AmbiguousConfigFileError
Expand Down Expand Up @@ -160,9 +162,16 @@ def update_args_with_config(args, parser):
return args


def create_parser():
def create_parser(color: bool = False):
"""Create the argument parser without parsing arguments."""
parser = argparse.ArgumentParser(description="Render plain code to target code.")
parser_kwargs: dict[str, Any] = {
"description": "Render plain code to target code.",
}

if sys.version_info >= (3, 13):
parser_kwargs["color"] = color

parser = argparse.ArgumentParser(**parser_kwargs)

parser.add_argument(
"filename",
Expand Down Expand Up @@ -205,14 +214,14 @@ def create_parser():
"--render-range",
type=frid_range_string,
help="Specify a range of functionalities to render (e.g. `1` , `2`, `3`). "
"Use comma to separate start and end IDs. If only one ID is provided, only that functionality is rendered. "
"Use comma to separate start and end IDs. If only one functionality ID is provided, only that functionality is rendered. "
"Range is inclusive of both start and end IDs.",
)
render_range_group.add_argument(
"--render-from",
type=frid_string,
help="Continue generation starting from this specific functionality (e.g. `2`). "
"The requirement with this ID will be included in the output. The ID must match one of the functionalities in your plain file.",
"The functionality with this ID will be included in the output. The functionality ID must match one of the functionalities in your plain file.",
)

parser.add_argument(
Expand Down
4 changes: 2 additions & 2 deletions plain2code_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,11 @@ def print_resources(self, resources_list, linked_resources):
self.debug("Linked resources: None")
return

self.input("Linked resources:")
self.debug("Linked resources:")
for resource_name in resources_list:
if resource_name["target"] in linked_resources:
file_tokens = self._count_tokens(linked_resources[resource_name["target"]])
self.input(
self.debug(
f"- {resource_name['text']} [#4169E1]({resource_name['target']}, {file_tokens} tokens)[/#4169E1]"
)

Expand Down
2 changes: 1 addition & 1 deletion plain2code_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def print_dry_run_output(plain_source_tree: dict, render_range: Optional[list[st
functional_requirement_text = specifications[plain_spec.FUNCTIONAL_REQUIREMENTS][-1]
console.info(
"-------------------------------------\n"
f"Rendering functionality {frid}\n"
f"Rendering functionality {frid}:\n"
f"{functional_requirement_text}\n"
"-------------------------------------\n"
)
Expand Down
4 changes: 1 addition & 3 deletions render_machine/actions/analyze_specification_ambiguity.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,5 @@ def execute(self, render_context: RenderContext, _previous_action_payload: Any |
)
console.info(rendering_analysis["guidance"])
else:
console.warning(
f"No specification ambiguity detected for functionality {render_context.frid_context.frid}."
)
console.debug(f"No specification ambiguity detected for functionality {render_context.frid_context.frid}.")
return self.SUCCESSFUL_OUTCOME, None
2 changes: 1 addition & 1 deletion render_machine/actions/create_dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ def execute(self, render_context: RenderContext, _previous_action_payload: Any |
render_context.conformance_tests.get_module_conformance_tests_folder(render_context.module_name),
render_context.conformance_tests_dest,
)
console.info(f"[#79FC96]Render {render_context.run_state.render_id} completed successfully.[/#79FC96]")
console.info(f"[#79FC96]Render of module {render_context.module_name} completed successfully.[/#79FC96]")

return self.SUCCESSFUL_OUTCOME, None
Loading
Loading