Skip to content

Commit

Permalink
feat: wizard with Ctrl-C menu to go back, and more
Browse files Browse the repository at this point in the history
  • Loading branch information
joanise committed Sep 23, 2024
1 parent b4e79f7 commit cf4f10f
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 0 deletions.
47 changes: 47 additions & 0 deletions everyvoice/tests/test_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -1731,6 +1731,53 @@ def test_multilingual_multispeaker_false_config(self):
self.assertIn("multilingual: false", text_to_spec_config)
self.assertIn("multispeaker: false", text_to_spec_config)

def test_control_c(self):
# Three Ctrl-C exits
tour = make_trivial_tour()
with patch_input(KeyboardInterrupt()), patch_menu_prompt(KeyboardInterrupt()):
with self.assertRaises(SystemExit):
tour.run()

# Ctrl-C plus option 4 (Exit) exits
tour = make_trivial_tour()
with patch_input(KeyboardInterrupt()):
with patch_menu_prompt(4): # 4 is "Exit" in keyboard interrupt handling
with self.assertRaises(SystemExit):
tour.run()

resulting_state = {
SN.name_step.value: "project_name",
SN.contact_name_step.value: "user name",
SN.contact_email_step.value: "[email protected]",
}

tour = make_trivial_tour()
with patch_input(
["project_name", KeyboardInterrupt(), "user name", "[email protected]"],
multi=True,
):
# Ctrl-C once, then hit 1 to continue
with patch_menu_prompt([KeyboardInterrupt(), 1], multi=True):
tour.run()
self.assertEqual(tour.state, resulting_state)

tour = make_trivial_tour()
with patch_input(
[
"bad_name",
KeyboardInterrupt(),
"project_name",
"bad user name",
KeyboardInterrupt(),
"user name",
"[email protected]",
],
multi=True,
):
with patch_menu_prompt(0): # say 0==go back each time
tour.run()
self.assertEqual(tour.state, resulting_state)

def test_trace(self):
tour = make_trivial_tour(trace=True)
tour.trace = True
Expand Down
59 changes: 59 additions & 0 deletions everyvoice/wizard/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from rich import print as rich_print
from rich.panel import Panel

from .prompts import get_response_from_menu_prompt
from .utils import EnumDict as State
from .utils import NodeMixinWithNavigation

Expand Down Expand Up @@ -122,6 +123,23 @@ def run(self):
sys.exit(1)
self.run()

def is_reversible(self):
"""Return True if the step's effects can be reversed, False otherwise.
Also implement undo if you return True and undoing the step requires state changes.
"""
return False

def undo(self):
"""Undo the effects of the step.
If you implement undo, also implement is_reversible with return True
"""
self.response = None
self.completed = False
if self.state is not None:
del self.state[self.name]


class RootStep(Step):
"""Dummy step sitting at the root of the tour"""
Expand Down Expand Up @@ -186,6 +204,47 @@ def add_step(self, step: Step, parent: Step):
children.insert(0, step)
parent.children = children

def keyboard_interrupt_action(self, node):
"""Handle a keyboard interrupt by asking the user what to do"""
action = None
# Three Ctrl-C in a row, we just exist, but two in a row might be an
# accident so ask again.
for _ in range(2):
try:
action = get_response_from_menu_prompt(
"What would you like to do?",
[
"Go back one step",
"Continue",
"View the question tree",
"Save progress",
"Exit",
],
return_indices=True,
)
break
except KeyboardInterrupt:
continue
if action == 0:
prev = node.prev()
if prev.is_reversible():
prev.undo()
return prev
else:
rich_print(
f"Sorry, the effects of the {prev.name} cannot be undone, continuing. If you need to go back, you'll have to restart the wizard."
)
return node
elif action == 1:
return node
elif action == 2:
self.visualize(highlight=node)
return node
elif action == 3:
return node
else: # still None, or the highest value in the choice list.
sys.exit(1)

def run(self):
"""Run the tour by traversing the tree depth-first"""
node = self.root
Expand Down
12 changes: 12 additions & 0 deletions everyvoice/wizard/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ def effect(self):
f"Great! Launching Configuration Wizard 🧙 for project named '{self.response}'."
)

def is_reversible(self):
return True


class ContactNameStep(Step):
DEFAULT_NAME = StepNames.contact_name_step
Expand All @@ -84,6 +87,9 @@ def validate(self, response):
def effect(self):
rich_print(f"Great! Nice to meet you, '{self.response}'.")

def is_reversible(self):
return True


class ContactEmailStep(Step):
DEFAULT_NAME = StepNames.contact_email_step
Expand Down Expand Up @@ -125,6 +131,9 @@ def effect(self):
f"Great! Your contact email '{self.response}' will be saved to your models."
)

def is_reversible(self):
return True


class OutputPathStep(Step):
DEFAULT_NAME = StepNames.output_step
Expand Down Expand Up @@ -189,6 +198,9 @@ def effect(self):
f"The Configuration Wizard 🧙 will put your files here: '{self.output_path}'"
)

def is_reversible(self):
return True


class ConfigFormatStep(Step):
DEFAULT_NAME = StepNames.config_format_step
Expand Down

0 comments on commit cf4f10f

Please sign in to comment.