1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3 4# 5# Copyright (c) 2020 Huawei Device Co., Ltd. 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18 19from __future__ import print_function 20from __future__ import unicode_literals 21import sys 22from prompt_toolkit.application import Application 23from prompt_toolkit.key_binding.manager import KeyBindingManager 24from prompt_toolkit.keys import Keys 25from prompt_toolkit.layout.containers import Window 26from prompt_toolkit.filters import IsDone 27from prompt_toolkit.layout.controls import TokenListControl 28from prompt_toolkit.layout.containers import ConditionalContainer 29from prompt_toolkit.layout.containers import HSplit 30from prompt_toolkit.layout.dimension import LayoutDimension as D 31from prompt_toolkit.token import Token 32from hb_internal.cts.common import Separator 33from hb_internal.cts.common import if_mousedown 34from hb_internal.cts.common import get_style 35from hb_internal.common.utils import OHOSException 36 37 38class InquirerControl(TokenListControl): 39 def __init__(self, choices, **kwargs): 40 self.selected_option_index = 0 41 self.answered = False 42 self.choices = choices 43 self._init_choices(choices) 44 super(InquirerControl, self).__init__(self._get_choice_tokens, 45 **kwargs) 46 47 def _init_choices(self, choices, default=None): 48 # helper to convert from question format to internal format 49 self.choices = [] # list (name, value, disabled) 50 searching_first_choice = True 51 for index, choice in enumerate(choices): 52 if isinstance(choice, Separator): 53 self.choices.append((choice, None, None)) 54 else: 55 base_string = str if sys.version_info[0] >= 3 else None 56 if isinstance(choice, base_string): 57 self.choices.append((choice, choice, None)) 58 else: 59 name = choice.get('name') 60 value = choice.get('value', name) 61 disabled = choice.get('disabled', None) 62 self.choices.append((name, value, disabled)) 63 if searching_first_choice: 64 self.selected_option_index = index 65 searching_first_choice = False 66 67 @property 68 def choice_count(self): 69 return len(self.choices) 70 71 def _get_choice_tokens(self, cli): 72 tokens = [] 73 token = Token 74 75 def append(index, choice): 76 selected = (index == self.selected_option_index) 77 78 @if_mousedown 79 def select_item(cli, mouse_event): 80 # bind option with this index to mouse event 81 self.selected_option_index = index 82 self.answered = True 83 84 tokens.append((token.Pointer if selected else token, ' \u276f ' 85 if selected else ' ')) 86 if selected: 87 tokens.append((Token.SetCursorPosition, '')) 88 if choice[2]: # disabled 89 tokens.append((token.Selected if selected else token, 90 '- %s (%s)' % (choice[0], choice[2]))) 91 else: 92 if isinstance(choice[0], Separator): 93 tokens.append((token.Separator, 94 str(choice[0]), 95 select_item)) 96 else: 97 try: 98 tokens.append((token.Selected if selected else token, 99 str(choice[0]), select_item)) 100 except Exception: 101 tokens.append((token.Selected if selected else 102 token, choice[0], select_item)) 103 tokens.append((token, '\n')) 104 105 # prepare the select choices 106 for i, choice in enumerate(self.choices): 107 append(i, choice) 108 tokens.pop() # Remove last newline. 109 return tokens 110 111 def get_selection(self): 112 return self.choices[self.selected_option_index] 113 114 115def question(message, **kwargs): 116 if 'choices' not in kwargs: 117 raise OHOSException("You must choose one platform.") 118 119 choices = kwargs.pop('choices', None) 120 qmark = kwargs.pop('qmark', '?') 121 style = kwargs.pop('style', get_style('terminal')) 122 123 inquirer_control = InquirerControl(choices) 124 125 def get_prompt_tokens(cli): 126 tokens = [] 127 128 tokens.append((Token.QuestionMark, qmark)) 129 tokens.append((Token.Question, ' %s ' % message)) 130 if inquirer_control.answered: 131 tokens.append((Token.Answer, ' ' + 132 inquirer_control.get_selection()[0])) 133 else: 134 tokens.append((Token.Instruction, ' (Use arrow keys)')) 135 return tokens 136 137 # assemble layout 138 layout = HSplit([ 139 Window(height=D.exact(1), 140 content=TokenListControl(get_prompt_tokens)), 141 ConditionalContainer( 142 Window(inquirer_control), 143 filter=~IsDone() 144 ) 145 ]) 146 147 # key bindings 148 manager = KeyBindingManager.for_prompt() 149 150 @manager.registry.add_binding(Keys.ControlQ, eager=True) 151 @manager.registry.add_binding(Keys.ControlC, eager=True) 152 def _(event): 153 raise KeyboardInterrupt() 154 155 @manager.registry.add_binding(Keys.Down, eager=True) 156 def move_cursor_down(event): 157 def _next(): 158 inquirer_control.selected_option_index = ( 159 (inquirer_control.selected_option_index + 1) % 160 inquirer_control.choice_count) 161 _next() 162 while isinstance(inquirer_control.choices[ 163 inquirer_control.selected_option_index][0], Separator) \ 164 or inquirer_control.choices[ 165 inquirer_control.selected_option_index][2]: 166 _next() 167 168 @manager.registry.add_binding(Keys.Up, eager=True) 169 def move_cursor_up(event): 170 def _prev(): 171 inquirer_control.selected_option_index = ( 172 (inquirer_control.selected_option_index - 1) % 173 inquirer_control.choice_count) 174 _prev() 175 while isinstance(inquirer_control.choices[ 176 inquirer_control.selected_option_index][0], Separator) \ 177 or inquirer_control.choices[ 178 inquirer_control.selected_option_index][2]: 179 _prev() 180 181 @manager.registry.add_binding(Keys.Enter, eager=True) 182 def set_answer(event): 183 inquirer_control.answered = True 184 event.cli.set_return_value(inquirer_control.get_selection()) 185 186 return Application( 187 layout=layout, 188 key_bindings_registry=manager.registry, 189 mouse_support=True, 190 style=style 191 ) 192