Skip to content

Commit 920abe2

Browse files
authored
Merge pull request #20 from Codeplain-ai/deploy/07-17
Add options to include destination folder for source and conformance test code
2 parents ebab816 + c7086df commit 920abe2

File tree

5 files changed

+93
-10
lines changed

5 files changed

+93
-10
lines changed

codeplain_REST_api.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ def fix_unittests_issue(
213213
"linked_resources": linked_resources,
214214
"existing_files_content": existing_files_content,
215215
"unittests_issue": unittests_issue,
216+
"unittest_batch_id": run_state.unittest_batch_id,
216217
}
217218

218219
return self.post_request(endpoint_url, headers, payload, run_state)

file_utils.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,12 +250,23 @@ def update_build_folder_with_rendered_files(build_folder, existing_files, respon
250250
return existing_files, changed_files
251251

252252

253-
def copy_folder_content(source_folder, destination_folder):
253+
def copy_folder_content(source_folder, destination_folder, ignore_folders=None):
254254
"""
255255
Recursively copy all files and folders from source_folder to destination_folder.
256256
Uses shutil.copytree which handles all edge cases including permissions and symlinks.
257+
258+
Args:
259+
source_folder: Source directory to copy from
260+
destination_folder: Destination directory to copy to
261+
ignore_folders: List of folder names to ignore during copy (default: empty list)
257262
"""
258-
shutil.copytree(source_folder, destination_folder, dirs_exist_ok=True)
263+
if ignore_folders is None:
264+
ignore_folders = []
265+
266+
ignore_func = (
267+
(lambda dir, files: [f for f in files if f in ignore_folders]) if ignore_folders else None # noqa: U100,U101
268+
)
269+
shutil.copytree(source_folder, destination_folder, dirs_exist_ok=True, ignore=ignore_func)
259270

260271

261272
def get_template_directories(plain_file_path, custom_template_dir=None, default_template_dir=None) -> list[str]:
@@ -281,3 +292,16 @@ def get_template_directories(plain_file_path, custom_template_dir=None, default_
281292
template_dirs.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), default_template_dir))
282293

283294
return template_dirs
295+
296+
297+
def copy_folder_to_output(source_folder, output_folder):
298+
"""Copy source folder contents directly to the specified output folder."""
299+
# Create output folder if it doesn't exist
300+
os.makedirs(output_folder, exist_ok=True)
301+
302+
# If output folder exists, clean it first to ensure clean copy
303+
if os.path.exists(output_folder):
304+
delete_files_and_subfolders(output_folder)
305+
306+
# Copy source folder contents directly to output folder (excluding .git)
307+
copy_folder_content(source_folder, output_folder, ignore_folders=[".git"])

plain2code.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
MAX_UNITTEST_FIX_ATTEMPTS = 20
3030
MAX_CONFORMANCE_TEST_FIX_ATTEMPTS = 20
31-
MAX_CONFORMANCE_TEST_RUNS = 10
31+
MAX_CONFORMANCE_TEST_RUNS = 20
3232
MAX_REFACTORING_ITERATIONS = 5
3333
MAX_UNIT_TEST_RENDER_RETRIES = 2
3434

@@ -138,6 +138,7 @@ def run_unittests(
138138
console.info(f"\n[b]Running unit tests script:[/b] {args.unittests_script}")
139139

140140
unit_test_run_count = 0
141+
fix_unittest_called = False
141142
while unit_test_run_count < MAX_UNITTEST_FIX_ATTEMPTS:
142143
unit_test_run_count += 1
143144

@@ -185,6 +186,10 @@ def run_unittests(
185186
style=console.INPUT_STYLE,
186187
)
187188

189+
if not fix_unittest_called:
190+
run_state.increment_unittest_batch_id()
191+
fix_unittest_called = True
192+
188193
with console.status(f"[{console.INFO_STYLE}]Fixing unit tests issue for functional requirement {frid}...\n"):
189194
response_files = codeplainAPI.fix_unittests_issue(
190195
frid, plain_source_tree, linked_resources, existing_files_content, unittests_issue, run_state
@@ -1049,11 +1054,10 @@ def render_functional_requirement( # noqa: C901
10491054
None,
10501055
run_state.render_id,
10511056
)
1052-
10531057
return
10541058

10551059

1056-
def render(args, run_state: RunState):
1060+
def render(args, run_state: RunState): # noqa: C901
10571061
if args.verbose:
10581062

10591063
logging.basicConfig(level=logging.DEBUG)
@@ -1141,6 +1145,22 @@ def render(args, run_state: RunState):
11411145
)
11421146
frid = plain_spec.get_next_frid(plain_source_tree, frid)
11431147

1148+
# Copy build and conformance tests folders to output folders if specified
1149+
if args.copy_build:
1150+
file_utils.copy_folder_to_output(args.build_folder, args.build_dest)
1151+
if args.verbose:
1152+
console.info(f"Copied build folder to {args.build_dest}")
1153+
if args.copy_conformance_tests:
1154+
file_utils.copy_folder_to_output(args.conformance_tests_folder, args.conformance_tests_dest)
1155+
if args.verbose:
1156+
console.info(f"Copied conformance tests folder to {args.conformance_tests_dest}")
1157+
1158+
console.info(f"\n[bold blue]Code rendering {run_state.render_id} was successfully finished![/bold blue]")
1159+
if args.copy_build:
1160+
console.info(f"The generated source code is available in {args.build_dest}")
1161+
if args.copy_conformance_tests:
1162+
console.info(f"Related conformance tests are placed in {args.conformance_tests_dest}")
1163+
11441164
console.info(f"Render ID: {run_state.render_id}")
11451165
return
11461166

plain2code_arguments.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
CLAUDE_API_KEY = os.getenv("CLAUDE_API_KEY")
88
DEFAULT_BUILD_FOLDER = "build"
99
DEFAULT_CONFORMANCE_TESTS_FOLDER = "conformance_tests"
10+
DEFAULT_BUILD_DEST = "dist"
11+
DEFAULT_CONFORMANCE_TESTS_DEST = "dist_conformance_tests"
1012

1113
UNIT_TESTS_SCRIPT_NAME = "unittests_script"
1214
CONFORMANCE_TESTS_SCRIPT_NAME = "conformance_tests_script"
@@ -90,7 +92,7 @@ def update_args_with_config(args, parser):
9092
arg_action = action_types.get(key)
9193
if arg_action and isinstance(arg_action, argparse._StoreAction):
9294
# For regular arguments, only skip if explicitly provided
93-
if getattr(args, key) is not None:
95+
if getattr(args, key) is not None and (arg_action.default is None or value == arg_action.default):
9496
continue
9597
elif arg_action and isinstance(arg_action, argparse._StoreTrueAction):
9698
# For boolean flags, skip if True (explicitly set)
@@ -178,12 +180,44 @@ def parse_arguments():
178180
"2) this custom template directory (if provided), "
179181
"3) built-in standard_template_library directory",
180182
)
183+
parser.add_argument(
184+
"--copy-build",
185+
action="store_true",
186+
default=False,
187+
help="If set, copy the build folder to --build-dest after every successful functional requirement rendering.",
188+
)
189+
parser.add_argument(
190+
"--build-dest",
191+
type=non_empty_string,
192+
default=DEFAULT_BUILD_DEST,
193+
help="Target folder to copy build output to (used only if --copy-build is set).",
194+
)
195+
parser.add_argument(
196+
"--copy-conformance-tests",
197+
action="store_true",
198+
default=False,
199+
help="If set, copy the conformance tests folder to --conformance-tests-dest after every successful functional requirement rendering. Requires --conformance-tests-script.",
200+
)
201+
parser.add_argument(
202+
"--conformance-tests-dest",
203+
type=non_empty_string,
204+
default=DEFAULT_CONFORMANCE_TESTS_DEST,
205+
help="Target folder to copy conformance tests output to (used only if --copy-conformance-tests is set).",
206+
)
181207

182208
args = parser.parse_args()
183209
args = update_args_with_config(args, parser)
184210

211+
if args.build_folder == args.build_dest:
212+
parser.error("--build-folder and --build-dest cannot be the same")
213+
if args.conformance_tests_folder == args.conformance_tests_dest:
214+
parser.error("--conformance-tests-folder and --conformance-tests-dest cannot be the same")
215+
185216
args.render_conformance_tests = args.conformance_tests_script is not None
186217

218+
if not args.render_conformance_tests and args.copy_conformance_tests:
219+
parser.error("--copy-conformance-tests requires --conformance-tests-script to be set")
220+
187221
script_arg_names = [UNIT_TESTS_SCRIPT_NAME, CONFORMANCE_TESTS_SCRIPT_NAME]
188222
for script_name in script_arg_names:
189223
args = process_test_script_path(script_name, args)

plain2code_state.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,20 @@ class RunState:
6262
"""Contains information about the identifiable state of the rendering process."""
6363

6464
def __init__(self, replay_with: Optional[str] = None):
65-
self.replay = replay_with is not None
65+
self.replay: bool = replay_with is not None
6666
if replay_with:
67-
self.render_id = replay_with
67+
self.render_id: str = replay_with
6868
else:
69-
self.render_id = str(uuid.uuid4())
70-
self.call_count = 0
69+
self.render_id: str = str(uuid.uuid4())
70+
self.call_count: int = 0
71+
self.unittest_batch_id: int = 0
7172

7273
def increment_call_count(self):
7374
self.call_count += 1
7475

76+
def increment_unittest_batch_id(self):
77+
self.unittest_batch_id += 1
78+
7579
def to_dict(self):
7680
return {
7781
"render_id": self.render_id,

0 commit comments

Comments
 (0)