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
4 changes: 3 additions & 1 deletion docs/plain2code_cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ options:
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.
--force-render Render every module of the project again, from the
first one. The client does not ask where to start and
the code rendered before is replaced.
--unittests-script UNITTESTS_SCRIPT
Shell script to run unit tests on generated code.
Receives the module's code folder path as its first
Expand Down
21 changes: 17 additions & 4 deletions partial_rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,6 @@ def change_is_only_future_work(
def _resume_render_choice(
plain_module: PlainModule,
plain_module_render_state: PlainModuleRenderState,
force_render: bool,
) -> RenderChoice:
"""The natural next step from the last-rendered position when no decision is needed:
start an unrendered module, continue a partially-rendered one, or advance to the next.
Expand All @@ -261,8 +260,7 @@ def _resume_render_choice(
render_range = plain_spec.get_render_range_from(next_frid, next_module.plain_source)
return RenderChoice(
module=next_module,
render_range=None if force_render else render_range,
wipe_later_modules=force_render,
render_range=render_range,
choice_type="continue_from_frid",
)

Expand All @@ -280,6 +278,21 @@ def get_render_choices(
plain_module_render_state: PlainModuleRenderState,
force_render: bool = False,
) -> dict[str, RenderChoice]:
if force_render:
# --force-render re-renders the whole chain from the first module, whatever changed and
# wherever the last render stopped. There is nothing to decide, so a single choice is
# returned and the user is not asked.
first_module = plain_module.all_required_modules[0] if plain_module.all_required_modules else plain_module
return {
"1": RenderChoice(
module=first_module,
render_range=None,
choice_type="rerender_from_first",
wipe_later_modules=True,
is_destructive=True,
)
}

render_choices = list[RenderChoice]()
module_start_points = list[str]()

Expand All @@ -298,7 +311,7 @@ def get_render_choices(
# Continue / resume from the last-rendered position. The change-driven blocks below decide
# the start otherwise — once a change touches already-rendered work, resuming from the old
# position would build on stale code.
render_choices.append(_resume_render_choice(plain_module, plain_module_render_state, force_render))
render_choices.append(_resume_render_choice(plain_module, plain_module_render_state))

else:
# change is set here (otherwise treat_as_continue would be True), so change_type is always
Expand Down
7 changes: 4 additions & 3 deletions plain2code.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,10 +375,11 @@ def render( # noqa: C901
render_choices = get_render_choices(plain_module, plain_module_render_state, args.force_render)
ask_user = True
render_choice = None
if len(render_choices) <= 2:
# Last choice is Quit, first choice is the only other actionable choice
if args.force_render or len(render_choices) <= 2:
# With --force-render there is a single choice. Otherwise the last choice is Quit,
# so the first choice is the only other actionable one.
render_choice = render_choices[list(render_choices.keys())[0]]
ask_user = render_choice.is_destructive
ask_user = render_choice.is_destructive and not args.force_render

if ask_user and not args.headless:
app = PlainModuleRenderChoiceTUI(
Expand Down
3 changes: 2 additions & 1 deletion plain2code_arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,8 @@ def create_parser():
"--force-render",
action="store_true",
default=False,
help="Force re-render of all the required modules.",
help="Render every module of the project again, from the first one. "
"The client does not ask where to start and the code rendered before is replaced.",
)

_add_arg(
Expand Down
44 changes: 44 additions & 0 deletions tests/test_partial_rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,50 @@ def test_render_choices_always_ends_with_quit():
assert choices[last_key].module is None


# --- Block 3: --force-render leaves nothing to choose ---


def test_render_choices_force_render_rerenders_whole_chain_without_a_choice():
"""--force-render renders the whole chain again from the first module, so a single,
non-interactive choice is returned (no Quit, because the user is never asked)."""
root, _, leaf = _build_choices_tree(fully_rendered=True)
pr = PlainModuleRenderState(last_render_module=root, last_render_frid="fr-e")

choices = get_render_choices(root, pr, force_render=True)

assert _choice_types(choices) == ["rerender_from_first"]
assert choices["1"].module is leaf
assert choices["1"].render_range is None
assert choices["1"].wipe_later_modules is True


def test_render_choices_force_render_ignores_a_detected_change(monkeypatch):
"""A change that would otherwise be offered as a partial render is ignored: the chain is
rendered again from the first module."""
root, middle, leaf = _build_choices_tree(fully_rendered=True)
monkeypatch.setattr(
partial_rendering, "determine_partial_render_start", lambda pm: PartialRenderStart(module=middle, frid="fr-c")
)
pr = PlainModuleRenderState(
last_render_module=root, last_render_frid="fr-e", change=middle, change_type="spec_change"
)

choices = get_render_choices(root, pr, force_render=True)

assert _choice_types(choices) == ["rerender_from_first"]
assert choices["1"].module is leaf


def test_render_choices_force_render_on_a_module_without_required_modules():
root = FakeModule("root", fully_rendered=True)
pr = PlainModuleRenderState(last_render_module=root, last_render_frid="fr-e")

choices = get_render_choices(root, pr, force_render=True)

assert _choice_types(choices) == ["rerender_from_first"]
assert choices["1"].module is root


# -------------------------
# change_is_only_future_work
# -------------------------
Expand Down
Loading