Skip to content

Commit da62d38

Browse files
committed
Refactoring partial_render_tui.py
1 parent 40eee27 commit da62d38

8 files changed

Lines changed: 242 additions & 153 deletions

git_utils.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -435,22 +435,51 @@ def get_repo_info(repo_path: Union[str, os.PathLike]) -> dict:
435435
return info
436436

437437

438-
def get_last_finished_frid(repo_path: Union[str, os.PathLike]) -> tuple[Optional[str], Optional[str]]:
438+
def get_last_rendered_functionality(repo_path: Union[str, os.PathLike]) -> tuple[Optional[str], Optional[str]]:
439439
if not os.path.exists(repo_path):
440440
return None, None
441441

442442
repo = Repo(repo_path)
443443
grep_pattern = FUNCTIONAL_REQUIREMENT_FINISHED_COMMIT_MESSAGE.format(".*")
444444
grep_pattern = grep_pattern.replace("[", "\\[").replace("]", "\\]")
445445
commit_sha = repo.git.rev_list(repo.active_branch.name, "--grep", grep_pattern, "-n", "1")
446+
446447
if not commit_sha:
447-
return None, None
448+
# Repo was interrupted during the first functionality, fallback to initial commit and provide only module name
449+
grep_pattern = INITIAL_COMMIT_MESSAGE
450+
grep_pattern = grep_pattern.replace("[", "\\[").replace("]", "\\]")
451+
commit_sha = repo.git.rev_list(repo.active_branch.name, "--grep", grep_pattern, "-n", "1")
452+
if not commit_sha:
453+
raise InvalidGitRepositoryError("Git repository is in an invalid state. Initial commit could not be found.")
454+
455+
commit_message = repo.commit(commit_sha).message
456+
if isinstance(commit_message, bytes):
457+
commit_message = commit_message.decode("utf-8")
458+
459+
match = re.search(r"Module name:\s*(\S+)\n", commit_message)
460+
if not match:
461+
raise InvalidGitRepositoryError(
462+
"Git repository is in an invalid state. Could not find module name in initial commit."
463+
)
464+
465+
module_name = match.group(1)
466+
return module_name, None
467+
448468
commit_message = repo.commit(commit_sha).message
449469
if isinstance(commit_message, bytes):
450470
commit_message = commit_message.decode("utf-8")
471+
451472
match = re.search(r"FRID\):(\S+) fully implemented", commit_message)
452-
frid = match.group(1) if match else None
473+
if not match:
474+
raise InvalidGitRepositoryError(
475+
"Git repository is in an invalid state. Could not find frid in finished commit."
476+
)
477+
frid = match.group(1)
453478

454479
match = re.search(r"Module name:\s*(\S+)\n", commit_message)
455-
module_name = match.group(1) if match else None
480+
if not match:
481+
raise InvalidGitRepositoryError(
482+
"Git repository is in an invalid state. Could not find module name in finished commit."
483+
)
484+
module_name = match.group(1)
456485
return module_name, frid

partial_rendering.py

Lines changed: 87 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from dataclasses import dataclass
2+
from typing import Literal
23

34
import plain_spec
45
from plain2code_exceptions import ModuleDoesNotExistError
@@ -10,7 +11,7 @@ class PartialRender:
1011
last_render_module: PlainModule
1112
last_render_frid: str | None
1213
change: PlainModule | None = None
13-
change_type: str | None = None
14+
change_type: Literal["spec_change", "code_change"] | None = None
1415

1516

1617
@dataclass
@@ -21,43 +22,27 @@ class PartialRenderChoice:
2122

2223

2324
def spec_change(plain_module: PlainModule) -> PlainModule | None:
24-
for required_module in plain_module.all_required_modules:
25-
module_metadata = required_module.load_module_metadata()
25+
all_modules = plain_module.all_required_modules + [plain_module]
26+
for _module in all_modules:
27+
module_metadata = _module.load_module_metadata()
2628
if (
2729
module_metadata
2830
and "source_hash" in module_metadata
29-
and module_metadata["source_hash"] != required_module.get_module_source_hash()
31+
and module_metadata["source_hash"] != _module.get_module_source_hash()
3032
):
31-
return required_module
32-
33-
module_metadata = plain_module.load_module_metadata()
34-
if (
35-
module_metadata
36-
and "source_hash" in module_metadata
37-
and module_metadata["source_hash"] != plain_module.get_module_source_hash()
38-
):
39-
return plain_module
33+
return _module
4034

4135
return None
4236

4337

4438
def code_change(plain_module: PlainModule) -> PlainModule | None:
45-
for required_module in plain_module.all_required_modules:
46-
if len(required_module.required_modules) == 0:
39+
all_modules = plain_module.all_required_modules + [plain_module]
40+
for _module in all_modules:
41+
if len(_module.required_modules) == 0:
4742
continue
4843

49-
module_metadata = required_module.load_module_metadata()
50-
previous_module = required_module.required_modules[-1]
51-
if (
52-
module_metadata
53-
and "required_modules_code_hash" in module_metadata
54-
and module_metadata["required_modules_code_hash"] != previous_module.get_module_code_hash()
55-
):
56-
return previous_module
57-
58-
module_metadata = plain_module.load_module_metadata()
59-
if len(plain_module.required_modules) > 0:
60-
previous_module = plain_module.required_modules[-1]
44+
module_metadata = _module.load_module_metadata()
45+
previous_module = _module.required_modules[-1]
6146
if (
6247
module_metadata
6348
and "required_modules_code_hash" in module_metadata
@@ -68,7 +53,7 @@ def code_change(plain_module: PlainModule) -> PlainModule | None:
6853
return None
6954

7055

71-
def module_comes_before(
56+
def module_comes_before_or_equal(
7257
all_required_modules: list[PlainModule],
7358
module1: PlainModule,
7459
module2: PlainModule,
@@ -87,16 +72,11 @@ def detect_partial_rendering(plain_module: PlainModule) -> PartialRender | None:
8772
sc = spec_change(plain_module)
8873
cc = code_change(plain_module)
8974
all_required_modules = plain_module.all_required_modules
90-
last_rendered_module, last_rendered_frid = plain_module.get_last_rendered_frid()
91-
if last_rendered_module is None and last_rendered_frid is None:
75+
last_rendered_module_name, last_rendered_frid = plain_module.get_module_last_rendered_functionality()
76+
if last_rendered_module_name is None and last_rendered_frid is None:
9277
return None
9378

94-
module = None
95-
for required_module in all_required_modules:
96-
if required_module.module_name == last_rendered_module:
97-
module = required_module
98-
99-
if last_rendered_module == plain_module.module_name:
79+
if last_rendered_module_name == plain_module.module_name:
10080
if (
10181
last_rendered_frid is None
10282
or plain_spec.get_next_frid(plain_module.plain_source, last_rendered_frid) is not None
@@ -105,9 +85,14 @@ def detect_partial_rendering(plain_module: PlainModule) -> PartialRender | None:
10585
else:
10686
return None
10787

88+
module = None
89+
for required_module in all_required_modules:
90+
if required_module.module_name == last_rendered_module_name:
91+
module = required_module
92+
10893
if module is None:
10994
raise ModuleDoesNotExistError(
110-
f"Last rendered module {last_rendered_module} not found in {[rmodule.module_name for rmodule in all_required_modules]}"
95+
f"Last rendered module {last_rendered_module_name} not found in {[rmodule.module_name for rmodule in all_required_modules]}"
11196
)
11297

11398
pr = PartialRender(
@@ -125,9 +110,73 @@ def detect_partial_rendering(plain_module: PlainModule) -> PartialRender | None:
125110
pr.change_type = "spec_change"
126111

127112
if cc is not None and (
128-
pr.change is None or (pr.change is not None and module_comes_before(all_required_modules, cc, pr.change))
113+
pr.change is None
114+
or (pr.change is not None and module_comes_before_or_equal(all_required_modules, cc, pr.change))
129115
):
130116
pr.change = cc
131117
pr.change_type = "code_change"
132118

133119
return pr
120+
121+
122+
def get_choices(plain_module: PlainModule, partial_render: PartialRender) -> dict[str, PartialRenderChoice]:
123+
choices = dict[str, PartialRenderChoice]()
124+
choice_idx = 1
125+
if not partial_render.last_render_module.is_module_fully_rendered():
126+
choices[str(choice_idx)] = PartialRenderChoice(
127+
module=partial_render.last_render_module,
128+
render_range=None,
129+
msg=f"Start from module [#5593FF]{partial_render.last_render_module.module_name}[/]",
130+
)
131+
choice_idx += 1
132+
if not partial_render.last_render_module.is_initial_module():
133+
if not partial_render.last_render_frid:
134+
raise ValueError("Last render FRID is not set for a non-initial module")
135+
136+
next_frid, next_module = plain_module.get_next_frid(
137+
partial_render.last_render_frid, partial_render.last_render_module.module_name
138+
)
139+
render_range = plain_spec.get_render_range_from(next_frid, next_module.plain_source)
140+
msg = "Continue from"
141+
if next_frid != plain_spec.get_first_frid(next_module.plain_source):
142+
msg += f" functionality [#5593FF]{next_frid}[/]"
143+
else:
144+
msg += f" module [#5593FF]{next_module.module_name}[/]"
145+
146+
choices[str(choice_idx)] = PartialRenderChoice(
147+
module=next_module,
148+
render_range=render_range,
149+
msg=msg,
150+
)
151+
choice_idx += 1
152+
153+
if partial_render.change:
154+
all_affected_modules = list[str]()
155+
affected_module = False
156+
for module in plain_module.all_required_modules:
157+
if module.module_name == partial_render.change.module_name:
158+
affected_module = True
159+
if affected_module:
160+
all_affected_modules.append(module.module_name)
161+
162+
choices[str(choice_idx)] = PartialRenderChoice(
163+
module=partial_render.change,
164+
render_range=None,
165+
msg=f"Re-render all affected modules ([#5593FF]{', '.join(all_affected_modules)}[/])",
166+
)
167+
choice_idx += 1
168+
169+
first_module = plain_module.all_required_modules[0]
170+
if first_module.module_name != partial_render.last_render_module.module_name and (
171+
partial_render.change is not None and first_module.module_name != partial_render.change.module_name
172+
):
173+
choices[str(choice_idx)] = PartialRenderChoice(
174+
module=first_module,
175+
render_range=None,
176+
msg=f"Re-render from first module ([#5593FF]{first_module.module_name}[/])",
177+
)
178+
choice_idx += 1
179+
180+
choices[str(choice_idx)] = PartialRenderChoice(module=None, render_range=None, msg="Quit")
181+
182+
return choices

plain2code.py

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import plain_spec
2020
from event_bus import EventBus
2121
from module_renderer import ModuleRenderer
22-
from partial_rendering import detect_partial_rendering
22+
from partial_rendering import detect_partial_rendering, get_choices
2323
from plain2code_arguments import parse_arguments
2424
from plain2code_console import console
2525
from plain2code_events import RenderFailed
@@ -181,20 +181,26 @@ def render(args, run_state: RunState, event_bus: EventBus): # noqa: C901
181181
if render_range is None:
182182
partial_render = detect_partial_rendering(plain_module)
183183
if partial_render is not None:
184-
app = PartialRenderTUI(
185-
plain_module,
186-
partial_render,
187-
system_config.client_version,
188-
run_state.render_id,
189-
css_path="styles.css",
190-
)
191-
partial_render_choice = app.run()
192-
if partial_render_choice is None or (
193-
partial_render_choice.module is None
194-
and partial_render_choice.render_range is None
195-
and partial_render_choice.msg == "Quit"
196-
):
197-
exit()
184+
choices = get_choices(plain_module, partial_render)
185+
if len(choices) > 2:
186+
app = PartialRenderTUI(
187+
plain_module,
188+
partial_render,
189+
choices,
190+
system_config.client_version,
191+
run_state.render_id,
192+
css_path="styles.css",
193+
)
194+
partial_render_choice = app.run()
195+
if partial_render_choice is None or (
196+
partial_render_choice.module is None
197+
and partial_render_choice.render_range is None
198+
and partial_render_choice.msg == "Quit"
199+
):
200+
sys.exit(0)
201+
else:
202+
# Last choice is Quit, first choice is the only other actionable choice
203+
partial_render_choice = choices[list(choices.keys())[0]]
198204

199205
# We can't have both partial render choice and render range
200206
assert not (partial_render_choice is not None and render_range is not None)

plain_modules.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,18 +68,18 @@ def module_build_folder(self):
6868
def get_codeplain_folder(self):
6969
return os.path.join(self.module_build_folder, CODEPLAIN_METADATA_FOLDER)
7070

71-
def get_last_rendered_frid(self) -> tuple[str | None, str | None]:
71+
def get_module_last_rendered_functionality(self) -> tuple[str | None, str | None]:
7272
if len(self.required_modules) == 0:
73-
return git_utils.get_last_finished_frid(self.module_build_folder)
73+
return git_utils.get_last_rendered_functionality(self.module_build_folder)
7474

75-
module_name, frid = git_utils.get_last_finished_frid(self.module_build_folder)
76-
if module_name is not None and frid is not None and module_name == self.module_name:
75+
module_name, frid = git_utils.get_last_rendered_functionality(self.module_build_folder)
76+
if module_name is not None and module_name == self.module_name:
7777
return module_name, frid
7878

7979
for module in reversed(self.required_modules):
80-
last_rendered_module, last_rendered_frid = module.get_last_rendered_frid()
81-
if last_rendered_module is not None and last_rendered_frid is not None:
82-
return last_rendered_module, last_rendered_frid
80+
last_rendered_module_name, last_rendered_frid = module.get_module_last_rendered_functionality()
81+
if last_rendered_module_name is not None:
82+
return last_rendered_module_name, last_rendered_frid
8383

8484
return None, None
8585

@@ -310,8 +310,28 @@ def get_next_frid(self, frid: str, module_name: str) -> tuple[str, PlainModule]:
310310

311311
def is_module_fully_rendered(self) -> bool:
312312
frids = list(plain_spec.get_frids(self.plain_source))
313-
last_rendered_module, last_rendered_frid = git_utils.get_last_finished_frid(self.module_build_folder)
314-
if last_rendered_module is None or last_rendered_frid is None:
315-
return False
313+
last_rendered_module_name, last_rendered_frid = git_utils.get_last_rendered_functionality(
314+
self.module_build_folder
315+
)
316+
if (
317+
last_rendered_module_name is not None
318+
and last_rendered_module_name == self.module_name
319+
and last_rendered_frid is not None
320+
and last_rendered_frid == frids[-1]
321+
):
322+
return True
323+
324+
return False
325+
326+
def is_initial_module(self) -> bool:
327+
last_rendered_module_name, last_rendered_frid = git_utils.get_last_rendered_functionality(
328+
self.module_build_folder
329+
)
330+
if (
331+
last_rendered_module_name is not None
332+
and last_rendered_module_name == self.module_name
333+
and last_rendered_frid is None
334+
):
335+
return True
316336

317-
return last_rendered_frid == frids[-1]
337+
return False

0 commit comments

Comments
 (0)