Skip to content

Commit

Permalink
Merge pull request #5728 from kyleknap/improved-wizard-init
Browse files Browse the repository at this point in the history
[v2] Initial groundwork for wizard improvements
  • Loading branch information
kyleknap committed Nov 21, 2020
2 parents 4d8769d + cbde978 commit 2885771
Show file tree
Hide file tree
Showing 12 changed files with 674 additions and 195 deletions.
2 changes: 1 addition & 1 deletion awscli/customizations/configure/sso.py
Expand Up @@ -28,7 +28,7 @@
from awscli.customizations.commands import BasicCommand
from awscli.customizations.configure import profile_to_section
from awscli.customizations.configure.writer import ConfigFileWriter
from awscli.customizations.wizard.selectmenu import select_menu
from awscli.customizations.wizard.ui.selectmenu import select_menu
from awscli.customizations.sso.utils import do_sso_login
from awscli.formatter import CLI_OUTPUT_FORMATS

Expand Down
30 changes: 30 additions & 0 deletions awscli/customizations/wizard/app.py
@@ -0,0 +1,30 @@
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from prompt_toolkit.application import Application
from prompt_toolkit.key_binding import KeyBindings

from awscli.customizations.wizard.ui.keybindings import get_default_keybindings
from awscli.customizations.wizard.ui.layout import WizardLayoutFactory
from awscli.customizations.wizard.ui.style import get_default_style


def create_wizard_app(definition):
layout_factory = WizardLayoutFactory()
app = Application(
key_bindings=get_default_keybindings(),
style=get_default_style(),
layout=layout_factory.create_wizard_layout(definition),
full_screen=True,
)
app.values = {}
return app
11 changes: 5 additions & 6 deletions awscli/customizations/wizard/devcommands.py
Expand Up @@ -12,7 +12,7 @@
# language governing permissions and limitations under the License.
import ruamel.yaml as yaml
from awscli.customizations.commands import BasicCommand
from awscli.customizations.wizard import factory
from awscli.customizations.wizard.app import create_wizard_app


def register_dev_commands(event_handlers):
Expand All @@ -21,10 +21,8 @@ def register_dev_commands(event_handlers):


def create_default_wizard_dev_runner(session):
runner = factory.create_default_wizard_runner(session)
return WizardDevRunner(
wizard_loader=WizardLoader(),
wizard_runner=runner
)


Expand All @@ -35,14 +33,15 @@ def load(self, contents):


class WizardDevRunner(object):
def __init__(self, wizard_loader, wizard_runner):
def __init__(self, wizard_loader):
self._wizard_loader = wizard_loader
self._wizard_runner = wizard_runner

def run_wizard(self, wizard_contents):
"""Run a single wizard given the contents as a string."""
loaded = self._wizard_loader.load(wizard_contents)
self._wizard_runner.run(loaded)
app = create_wizard_app(loaded)
app.run()
print(f'Collected values: {app.values}')


class WizardDev(BasicCommand):
Expand Down
Expand Up @@ -16,7 +16,7 @@
import prompt_toolkit
from prompt_toolkit.completion import Completer, Completion

from awscli.customizations.wizard import selectmenu
from awscli.customizations.wizard.ui import selectmenu


class Prompter(object):
Expand Down
38 changes: 38 additions & 0 deletions awscli/customizations/wizard/ui/keybindings.py
@@ -0,0 +1,38 @@
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from prompt_toolkit.key_binding import KeyBindings


def get_default_keybindings():
kb = KeyBindings()

@kb.add('c-c')
def exit_(event):
event.app.exit()

def submit_current_answer(event):
current_buffer = event.app.current_buffer
event.app.values[current_buffer.name] = current_buffer.text

@kb.add('tab')
@kb.add('enter')
def next_prompt(event):
submit_current_answer(event)
event.app.layout.focus_next()

@kb.add('s-tab')
def last_prompt(event):
submit_current_answer(event)
event.app.layout.focus_last()

return kb
50 changes: 50 additions & 0 deletions awscli/customizations/wizard/ui/layout.py
@@ -0,0 +1,50 @@
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from prompt_toolkit.layout import Layout
from prompt_toolkit.layout.containers import WindowAlign, HSplit
from prompt_toolkit.widgets import Label

from awscli.customizations.wizard.ui.prompt import WizardPrompt


class WizardLayoutFactory:
def create_wizard_layout(self, defintion):
return Layout(
container=HSplit(
[
self._create_title(defintion),
self._create_all_prompts(defintion)
],
padding=1,
)
)

def _create_title(self, definition):
title = Label(f'{definition["title"]}', style='class:wizard.title')
title.window.align = WindowAlign.CENTER
return title

def _create_all_prompts(self, definition):
prompts = []
for step_definition in definition['plan'].values():
prompts.extend(
self._create_prompts_from_step_definition(step_definition)
)
return HSplit(prompts, padding=0)

def _create_prompts_from_step_definition(self, step_definition):
prompts = []
for value_name, value_definition in step_definition['values'].items():
if value_definition['type'] == 'prompt':
prompts.append(WizardPrompt(value_name, value_definition))
return prompts
93 changes: 93 additions & 0 deletions awscli/customizations/wizard/ui/prompt.py
@@ -0,0 +1,93 @@
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from prompt_toolkit.application import get_app
from prompt_toolkit.buffer import Buffer
from prompt_toolkit.document import Document
from prompt_toolkit.layout.containers import Window, VSplit, Dimension
from prompt_toolkit.layout.controls import BufferControl


class WizardPrompt:
def __init__(self, value_name, value_definition):
self._value_name = value_name
self._value_definition = value_definition
self.container = self._get_container()

def _get_container(self):
return VSplit(
[
WizardPromptDescription(
self._value_name,
self._value_definition['description']
),
WizardPromptAnswer(self._value_name)
]
)

def __pt_container__(self):
return self.container


class WizardPromptDescription:
def __init__(self, value_name, value_description):
self._value_name = value_name
self._value_description = value_description
self.container = self._get_container()

def _get_container(self):
content = f'{self._value_description}:'
buffer = Buffer(
document=Document(content),
read_only=True
)
return Window(
content=BufferControl(
buffer=buffer, focusable=False
),
style=self._get_style,
width=Dimension.exact(len(content) + 1),
dont_extend_height=True,
)

def _get_style(self):
if get_app().layout.has_focus(self._value_name):
return 'class:wizard.prompt.description.current'
else:
return 'class:wizard.prompt.description'

def __pt_container__(self):
return self.container


class WizardPromptAnswer:
def __init__(self, value_name):
self._value_name = value_name
self.container = self._get_answer_container()

def _get_answer_container(self):
return Window(
content=BufferControl(
buffer=Buffer(name=self._value_name)
),
style=self._get_style,
dont_extend_height=True,
)

def _get_style(self):
if get_app().layout.has_focus(self._value_name):
return 'class:wizard.prompt.answer.current'
else:
return 'class:wizard.prompt.answer'

def __pt_container__(self):
return self.container
File renamed without changes.
26 changes: 26 additions & 0 deletions awscli/customizations/wizard/ui/style.py
@@ -0,0 +1,26 @@
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from prompt_toolkit.styles import Style


def get_default_style():
return Style(
[
('wizard', ''),
('wizard.title', 'underline bold'),
('wizard.prompt.description', 'bold'),
('wizard.prompt.description.current', 'white'),
('wizard.prompt.answer', ''),
('wizard.prompt.answer.current', 'white'),
]
)

0 comments on commit 2885771

Please sign in to comment.