diff --git a/awsshell/interaction.py b/awsshell/interaction.py index 3d7957f..4814a68 100644 --- a/awsshell/interaction.py +++ b/awsshell/interaction.py @@ -77,12 +77,14 @@ def __init__(self, model, prompt_msg, prompter=select_prompt): super(SimpleSelect, self).__init__(model, prompt_msg) self._prompter = prompter - def execute(self, data): + def execute(self, data, show_meta=False): if not isinstance(data, list) or len(data) < 1: raise InteractionException('SimpleSelect expects a non-empty list') if self._model.get('Path') is not None: display_data = jmespath.search(self._model['Path'], data) - result = self._prompter('%s ' % self.prompt, display_data) + options_meta = data if show_meta else None + result = self._prompter('%s ' % self.prompt, display_data, + options_meta=options_meta) (selected, index) = result return data[index] else: @@ -90,6 +92,17 @@ def execute(self, data): return selected +class InfoSelect(SimpleSelect): + """Display a list of options with meta information. + + Small extension of :class:`SimpleSelect` that turns the show_meta flag on + to display what the complete object looks like rendered as json in a pane + below the prompt. + """ + def execute(self, data): + return super(InfoSelect, self).execute(data, show_meta=True) + + class SimplePrompt(Interaction): """Prompt the user to type in responses for each field. @@ -174,6 +187,7 @@ class InteractionLoader(object): Interaction objects can be instantiated from their corresponding str. """ _INTERACTIONS = { + 'InfoSelect': InfoSelect, 'FuzzySelect': FuzzySelect, 'SimpleSelect': SimpleSelect, 'SimplePrompt': SimplePrompt, diff --git a/awsshell/selectmenu.py b/awsshell/selectmenu.py index 2f86a3d..895d7c0 100644 --- a/awsshell/selectmenu.py +++ b/awsshell/selectmenu.py @@ -1,4 +1,3 @@ -import json from pygments.lexers import find_lexer_class from prompt_toolkit.keys import Keys from prompt_toolkit.token import Token @@ -21,6 +20,7 @@ from prompt_toolkit.layout import Window, HSplit, FloatContainer, Float from prompt_toolkit.layout.containers import ScrollOffsets, \ ConditionalContainer +from awsshell.utils import format_json """An implementation of a selection menu using prompt toolkit. @@ -261,8 +261,7 @@ def return_selection(cli, buf): def selection_changed(cli): index = self.menu_control.get_index() info = options_meta[index] - formatted_info = json.dumps(info, indent=4, sort_keys=True, - ensure_ascii=False) + formatted_info = format_json(info) buffers['INFO'].text = formatted_info default_buf.on_text_changed += selection_changed diff --git a/awsshell/utils.py b/awsshell/utils.py index 5e9dab7..60c9324 100644 --- a/awsshell/utils.py +++ b/awsshell/utils.py @@ -6,9 +6,11 @@ import tempfile import uuid import logging +import json import awscli +from awscli.utils import json_encoder from awsshell.compat import HTMLParser @@ -142,3 +144,8 @@ def force_unicode(obj, encoding='utf8'): if not isinstance(obj, six.text_type): obj = _attempt_decode(obj, encoding) return obj + + +def format_json(response): + return json.dumps(response, indent=4, default=json_encoder, + ensure_ascii=False, sort_keys=True) diff --git a/awsshell/wizard.py b/awsshell/wizard.py index 01c3c9c..54c18b8 100644 --- a/awsshell/wizard.py +++ b/awsshell/wizard.py @@ -1,6 +1,5 @@ import sys import copy -import json import logging import jmespath @@ -9,7 +8,7 @@ from botocore.exceptions import BotoCoreError, ClientError from awsshell.resource import index -from awsshell.utils import force_unicode +from awsshell.utils import force_unicode, format_json from awsshell.selectmenu import select_prompt from awsshell.interaction import InteractionLoader, InteractionException @@ -348,7 +347,7 @@ def __init__(self): self._variables = {} def __str__(self): - return json.dumps(self._variables, indent=4, sort_keys=True) + return format_json(self._variables) def update(self, environment): assert isinstance(environment, Environment) diff --git a/tests/unit/test_interaction.py b/tests/unit/test_interaction.py index 722f3fe..853ad68 100644 --- a/tests/unit/test_interaction.py +++ b/tests/unit/test_interaction.py @@ -6,7 +6,7 @@ from prompt_toolkit.contrib.validators.base import Validator, ValidationError from awsshell.interaction import InteractionLoader, InteractionException from awsshell.interaction import SimpleSelect, SimplePrompt, FilePrompt -from awsshell.interaction import FuzzyCompleter, FuzzySelect +from awsshell.interaction import FuzzyCompleter, FuzzySelect, InfoSelect @pytest.fixture @@ -88,12 +88,13 @@ def test_simple_select(): assert xformed == options[1] -def test_simple_select_with_path(): +@pytest.mark.parametrize('selector', [SimpleSelect, InfoSelect]) +def test_simple_select_with_path(selector): # Verify that SimpleSelect calls prompt and it returns the corresponding # item derived from the path. prompt = mock.Mock() model = {'Path': '[].a'} - simple_selector = SimpleSelect(model, 'Promptingu', prompt) + simple_selector = selector(model, 'Promptingu', prompt) options = [{'a': '1', 'b': 'one'}, {'a': '2', 'b': 'two'}] prompt.return_value = ('2', 1) xformed = simple_selector.execute(options) diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index c243f1e..281a7b1 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -1,6 +1,7 @@ from tests import unittest import os import tempfile +import datetime import shutil import six import pytest @@ -10,6 +11,7 @@ from awsshell.utils import FileReadError from awsshell.utils import temporary_file from awsshell.utils import force_unicode +from awsshell.utils import format_json class TestFSLayer(unittest.TestCase): @@ -127,3 +129,8 @@ def test_force_unicode_recursion(): assert isinstance(clean_obj['b']['str'], six.text_type) assert clean_obj['c'] is obj['c'] assert obj == clean_obj + + +def test_format_json(): + data = {'Key': datetime.datetime(2016, 12, 12)} + assert format_json(data) == '{\n "Key": "2016-12-12T00:00:00"\n}'