Skip to content

Commit 40a6e42

Browse files
Resolve output paths relative to plain file
1 parent 35cd8ad commit 40a6e42

2 files changed

Lines changed: 104 additions & 0 deletions

File tree

plain2code_arguments.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
DEFAULT_LOG_FILE_NAME = "codeplain.log"
2020
PREPARE_ENVIRONMENT_SCRIPT_NAME = "prepare_environment_script"
2121

22+
FOLDER_ARGS = ["build_folder", "conformance_tests_folder", "build_dest", "conformance_tests_dest", "base_folder"]
23+
2224

2325
def process_test_script_path(script_arg_name, config):
2426
"""Resolve script paths in config."""
@@ -119,6 +121,22 @@ def resolve_config_file(config_name: str, plain_file_path: str):
119121
return None
120122

121123

124+
def resolve_output_paths(args):
125+
"""Resolve relative output folder paths to be relative to the plain file directory.
126+
127+
All relative paths (whether from command line, config file, or defaults) are anchored
128+
to the directory of the plain file being rendered, not the current working directory.
129+
"""
130+
plain_file_dir = os.path.dirname(os.path.abspath(args.filename))
131+
132+
for arg_name in FOLDER_ARGS:
133+
value = getattr(args, arg_name, None)
134+
if value and not os.path.isabs(value):
135+
setattr(args, arg_name, os.path.normpath(os.path.join(plain_file_dir, value)))
136+
137+
return args
138+
139+
122140
def update_args_with_config(args, parser):
123141
try:
124142
resolved_config = resolve_config_file(args.config_name, args.filename)
@@ -350,6 +368,7 @@ def parse_arguments():
350368

351369
args = parser.parse_args()
352370
args = update_args_with_config(args, parser)
371+
args = resolve_output_paths(args)
353372

354373
if args.build_folder == args.build_dest:
355374
parser.error("--build-folder and --build-dest cannot be the same")

tests/test_resolve_output_paths.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import os
2+
import tempfile
3+
from argparse import Namespace
4+
5+
import pytest
6+
7+
from plain2code_arguments import (
8+
DEFAULT_BUILD_DEST,
9+
DEFAULT_BUILD_FOLDER,
10+
DEFAULT_CONFORMANCE_TESTS_DEST,
11+
DEFAULT_CONFORMANCE_TESTS_FOLDER,
12+
resolve_output_paths,
13+
)
14+
15+
16+
def _make_args(plain_file, **kwargs):
17+
"""Build a minimal Namespace mimicking parsed args."""
18+
defaults = dict(
19+
filename=plain_file,
20+
build_folder=DEFAULT_BUILD_FOLDER,
21+
conformance_tests_folder=DEFAULT_CONFORMANCE_TESTS_FOLDER,
22+
build_dest=DEFAULT_BUILD_DEST,
23+
conformance_tests_dest=DEFAULT_CONFORMANCE_TESTS_DEST,
24+
base_folder=None,
25+
)
26+
defaults.update(kwargs)
27+
return Namespace(**defaults)
28+
29+
30+
def test_default_folders_resolve_to_plain_file_dir():
31+
with tempfile.TemporaryDirectory() as plain_dir:
32+
plain_file = os.path.join(plain_dir, "module.plain")
33+
args = resolve_output_paths(_make_args(plain_file))
34+
35+
assert args.build_folder == os.path.join(plain_dir, DEFAULT_BUILD_FOLDER)
36+
assert args.conformance_tests_folder == os.path.join(plain_dir, DEFAULT_CONFORMANCE_TESTS_FOLDER)
37+
assert args.build_dest == os.path.join(plain_dir, DEFAULT_BUILD_DEST)
38+
assert args.conformance_tests_dest == os.path.join(plain_dir, DEFAULT_CONFORMANCE_TESTS_DEST)
39+
40+
41+
def test_relative_path_from_config_resolves_to_plain_file_dir():
42+
"""A relative build_folder set in config (e.g. '../build') resolves relative to the plain file."""
43+
with tempfile.TemporaryDirectory() as plain_dir:
44+
plain_file = os.path.join(plain_dir, "module.plain")
45+
args = resolve_output_paths(_make_args(plain_file, build_folder="../build"))
46+
47+
assert args.build_folder == os.path.normpath(os.path.join(plain_dir, "../build"))
48+
assert os.path.isabs(args.build_folder)
49+
50+
51+
def test_absolute_path_left_unchanged():
52+
with tempfile.TemporaryDirectory() as plain_dir:
53+
plain_file = os.path.join(plain_dir, "module.plain")
54+
abs_path = "/some/absolute/path"
55+
args = resolve_output_paths(_make_args(plain_file, build_folder=abs_path))
56+
57+
assert args.build_folder == abs_path
58+
59+
60+
def test_base_folder_none_stays_none():
61+
with tempfile.TemporaryDirectory() as plain_dir:
62+
plain_file = os.path.join(plain_dir, "module.plain")
63+
args = resolve_output_paths(_make_args(plain_file, base_folder=None))
64+
65+
assert args.base_folder is None
66+
67+
68+
def test_base_folder_relative_resolves_to_plain_file_dir():
69+
with tempfile.TemporaryDirectory() as plain_dir:
70+
plain_file = os.path.join(plain_dir, "module.plain")
71+
args = resolve_output_paths(_make_args(plain_file, base_folder="base"))
72+
73+
assert args.build_folder == os.path.join(plain_dir, DEFAULT_BUILD_FOLDER)
74+
assert args.base_folder == os.path.join(plain_dir, "base")
75+
76+
77+
def test_plain_file_in_subdirectory():
78+
"""Plain file in a subdirectory — all defaults land next to it, not at CWD."""
79+
with tempfile.TemporaryDirectory() as root:
80+
sub = os.path.join(root, "b", "c")
81+
os.makedirs(sub)
82+
plain_file = os.path.join(sub, "example.plain")
83+
args = resolve_output_paths(_make_args(plain_file))
84+
85+
assert args.build_folder == os.path.join(sub, DEFAULT_BUILD_FOLDER)

0 commit comments

Comments
 (0)