Skip to content
138 changes: 93 additions & 45 deletions docs/plain2code_cli.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,41 @@
# 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 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.
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
current working directory, values read from config.yaml are resolved against
the config file's directory, and defaults are resolved against the directory
containing the plain file. Absolute paths (and paths starting with '~') are
used as-is.

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 @@ -26,60 +45,89 @@ options:
--build-folder BUILD_FOLDER
Folder for build files
--log-to-file, --no-log-to-file
Enable logging to a file. Defaults to True. Set to False to disable.
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'. If
a file already exists at the resolved path, it 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 functionality ID is provided, only that functionality 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 functionality with this ID will be
included in the output. The functionality 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.
--dry-run Dry run preview of the code generation (without actually making any changes).
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.
--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).
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).
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
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
Name of the config file to look for. Looked up in the plain file directory and the current working directory.
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.

```
64 changes: 64 additions & 0 deletions path_resolution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""Path resolution for CLI / config / default path arguments.

The rule, in one sentence: the resolution base for a relative path is determined
by *where the value was written*, not by which option key it sets.

- Values supplied on the command line resolve against the current working
directory (so they match what shell tab completion just produced).
- Values read from ``config.yaml`` resolve against the directory containing
that config file.
- Values left at their default (not supplied anywhere) resolve against the
directory containing the spec file.

Paths are expanded for ``~`` and returned as absolute paths. Canonicalization
(symlink resolution) is left to the caller, to be done just before I/O that
needs it (writing output, executing scripts) so we don't accidentally
dereference links the user wanted preserved.
"""

import os
from typing import Literal, Optional

PathSource = Literal["cli", "config", "default"]


def resolve_path(
value: str,
source: PathSource,
*,
cwd: str,
config_dir: Optional[str] = None,
spec_dir: str,
) -> str:
"""Resolve *value* to an absolute path using the base anchor for *source*.

Args:
value: The path string as written by the user. May be absolute, relative,
or contain a leading ``~``.
source: Where the value came from -- ``"cli"``, ``"config"``, or
``"default"``.
cwd: Base directory for ``"cli"`` values.
config_dir: Base directory for ``"config"`` values. Must be provided
whenever ``source == "config"``.
spec_dir: Base directory for ``"default"`` values.

Returns:
Absolute path with ``~`` expanded. Symlinks are not resolved.
"""
expanded = os.path.expanduser(value)

if os.path.isabs(expanded):
return os.path.normpath(expanded)

if source == "cli":
base = cwd
elif source == "config":
if config_dir is None:
raise ValueError("config_dir must be provided when source == 'config'")
base = config_dir
elif source == "default":
base = spec_dir
else:
raise ValueError(f"Unknown path source: {source!r}")

return os.path.normpath(os.path.join(base, expanded))
19 changes: 4 additions & 15 deletions plain2code.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import sys
import threading
from pathlib import Path
from typing import Optional

import yaml
from liquid2.exceptions import TemplateNotFoundError
Expand Down Expand Up @@ -38,14 +37,7 @@
RenderCancelledError,
RenderingCreditBalanceTooLow,
)
from plain2code_logger import (
LOGGER_NAME,
CrashLogHandler,
IndentedFormatter,
TuiLoggingHandler,
dump_crash_logs,
get_log_file_path,
)
from plain2code_logger import LOGGER_NAME, CrashLogHandler, IndentedFormatter, TuiLoggingHandler, dump_crash_logs
from plain2code_state import RunState
from plain2code_utils import format_duration_hms, print_dry_run_output
from system_config import system_config
Expand Down Expand Up @@ -126,8 +118,7 @@ def setup_logging(
args,
event_bus: EventBus,
log_to_file: bool,
log_file_name: str,
plain_file_path: Optional[str],
log_file_path: str,
headless: bool = False,
):
# Set default level to INFO for everything not explicitly configured
Expand All @@ -137,8 +128,6 @@ def setup_logging(
logging.getLogger("transitions").setLevel(logging.ERROR)
logging.getLogger("transitions.extensions.diagrams").setLevel(logging.ERROR)

log_file_path = get_log_file_path(plain_file_path, log_file_name)

# Try to load logging configuration from YAML file
if args.logging_config_path and os.path.exists(args.logging_config_path):
try:
Expand All @@ -162,7 +151,7 @@ def setup_logging(
handler.setFormatter(formatter)
root_logger.addHandler(handler)

if log_to_file and log_file_path:
if log_to_file:
try:
file_handler = logging.FileHandler(log_file_path, mode="w")
file_handler.setFormatter(formatter)
Expand Down Expand Up @@ -313,7 +302,7 @@ def main(): # noqa: C901
# Suppress Rich console output.
console.quiet = True

setup_logging(args, event_bus, args.log_to_file, args.log_file_name, args.filename, args.headless)
setup_logging(args, event_bus, args.log_to_file, args.log_file_name, args.headless)

exc_info = None
try:
Expand Down
Loading
Loading