Complete rewrite and removed legacy bash version
This commit is contained in:
29
src/libs/PyInquirer/__init__.py
Normal file
29
src/libs/PyInquirer/__init__.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import absolute_import, print_function
|
||||
import os
|
||||
|
||||
from libs.prompt_toolkit.token import Token
|
||||
from libs.prompt_toolkit.styles import style_from_dict
|
||||
from libs.prompt_toolkit.validation import Validator, ValidationError
|
||||
|
||||
from .utils import print_json, format_json
|
||||
|
||||
|
||||
__version__ = '1.0.2'
|
||||
|
||||
|
||||
def here(p):
|
||||
return os.path.abspath(os.path.join(os.path.dirname(__file__), p))
|
||||
|
||||
|
||||
class PromptParameterException(ValueError):
|
||||
def __init__(self, message, errors=None):
|
||||
|
||||
# Call the base class constructor with the parameters it needs
|
||||
super(PromptParameterException, self).__init__(
|
||||
'You must provide a `%s` value' % message, errors)
|
||||
|
||||
from .prompt import prompt
|
||||
from .separator import Separator
|
||||
from .prompts.common import default_style
|
41
src/libs/PyInquirer/color_print.py
Normal file
41
src/libs/PyInquirer/color_print.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
provide colorized output
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals
|
||||
import sys
|
||||
from libs.prompt_toolkit.shortcuts import print_tokens, style_from_dict, Token
|
||||
|
||||
|
||||
def _print_token_factory(col):
|
||||
"""Internal helper to provide color names."""
|
||||
def _helper(msg):
|
||||
style = style_from_dict({
|
||||
Token.Color: col,
|
||||
})
|
||||
tokens = [
|
||||
(Token.Color, msg)
|
||||
]
|
||||
print_tokens(tokens, style=style)
|
||||
|
||||
def _helper_no_terminal(msg):
|
||||
# workaround if we have no terminal
|
||||
print(msg)
|
||||
if sys.stdout.isatty():
|
||||
return _helper
|
||||
else:
|
||||
return _helper_no_terminal
|
||||
|
||||
# used this for color source:
|
||||
# http://unix.stackexchange.com/questions/105568/how-can-i-list-the-available-color-names
|
||||
yellow = _print_token_factory('#dfaf00')
|
||||
blue = _print_token_factory('#0087ff')
|
||||
gray = _print_token_factory('#6c6c6c')
|
||||
|
||||
# TODO
|
||||
#black
|
||||
#red
|
||||
#green
|
||||
#magenta
|
||||
#cyan
|
||||
#white
|
98
src/libs/PyInquirer/prompt.py
Normal file
98
src/libs/PyInquirer/prompt.py
Normal file
@@ -0,0 +1,98 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
from libs.prompt_toolkit.shortcuts import run_application
|
||||
|
||||
from . import PromptParameterException, prompts
|
||||
from .prompts import list, confirm, input, password, checkbox, rawlist, expand, editor
|
||||
|
||||
|
||||
def prompt(questions, answers=None, **kwargs):
|
||||
if isinstance(questions, dict):
|
||||
questions = [questions]
|
||||
answers = answers or {}
|
||||
|
||||
patch_stdout = kwargs.pop('patch_stdout', False)
|
||||
return_asyncio_coroutine = kwargs.pop('return_asyncio_coroutine', False)
|
||||
true_color = kwargs.pop('true_color', False)
|
||||
refresh_interval = kwargs.pop('refresh_interval', 0)
|
||||
eventloop = kwargs.pop('eventloop', None)
|
||||
kbi_msg = kwargs.pop('keyboard_interrupt_msg', 'Cancelled by user')
|
||||
|
||||
for question in questions:
|
||||
# import the question
|
||||
if 'type' not in question:
|
||||
raise PromptParameterException('type')
|
||||
if 'name' not in question:
|
||||
raise PromptParameterException('name')
|
||||
if 'message' not in question:
|
||||
raise PromptParameterException('message')
|
||||
try:
|
||||
choices = question.get('choices')
|
||||
if choices is not None and callable(choices):
|
||||
question['choices'] = choices(answers)
|
||||
|
||||
_kwargs = {}
|
||||
_kwargs.update(kwargs)
|
||||
_kwargs.update(question)
|
||||
type = _kwargs.pop('type')
|
||||
name = _kwargs.pop('name')
|
||||
message = _kwargs.pop('message')
|
||||
when = _kwargs.pop('when', None)
|
||||
filter = _kwargs.pop('filter', None)
|
||||
|
||||
if when:
|
||||
# at least a little sanity check!
|
||||
if callable(question['when']):
|
||||
try:
|
||||
if not question['when'](answers):
|
||||
continue
|
||||
except Exception as e:
|
||||
raise ValueError(
|
||||
'Problem in \'when\' check of %s question: %s' %
|
||||
(name, e))
|
||||
else:
|
||||
raise ValueError('\'when\' needs to be function that ' \
|
||||
'accepts a dict argument')
|
||||
if filter:
|
||||
# at least a little sanity check!
|
||||
if not callable(question['filter']):
|
||||
raise ValueError('\'filter\' needs to be function that ' \
|
||||
'accepts an argument')
|
||||
|
||||
if callable(question.get('default')):
|
||||
_kwargs['default'] = question['default'](answers)
|
||||
|
||||
application = getattr(prompts, type).question(message, **_kwargs)
|
||||
|
||||
answer = run_application(
|
||||
application,
|
||||
patch_stdout=patch_stdout,
|
||||
return_asyncio_coroutine=return_asyncio_coroutine,
|
||||
true_color=true_color,
|
||||
refresh_interval=refresh_interval,
|
||||
eventloop=eventloop)
|
||||
|
||||
if answer is not None:
|
||||
if filter:
|
||||
try:
|
||||
answer = question['filter'](answer)
|
||||
except Exception as e:
|
||||
raise ValueError(
|
||||
'Problem processing \'filter\' of %s question: %s' %
|
||||
(name, e))
|
||||
answers[name] = answer
|
||||
except AttributeError as e:
|
||||
print(e)
|
||||
raise ValueError('No question type \'%s\'' % type)
|
||||
except KeyboardInterrupt:
|
||||
print('')
|
||||
print(kbi_msg)
|
||||
print('')
|
||||
return {}
|
||||
return answers
|
||||
|
||||
|
||||
# TODO:
|
||||
# Bottom Bar - inquirer.ui.BottomBar
|
0
src/libs/PyInquirer/prompts/__init__.py
Normal file
0
src/libs/PyInquirer/prompts/__init__.py
Normal file
233
src/libs/PyInquirer/prompts/checkbox.py
Normal file
233
src/libs/PyInquirer/prompts/checkbox.py
Normal file
@@ -0,0 +1,233 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
`checkbox` type question
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals
|
||||
from libs.prompt_toolkit.application import Application
|
||||
from libs.prompt_toolkit.key_binding.manager import KeyBindingManager
|
||||
from libs.prompt_toolkit.keys import Keys
|
||||
from libs.prompt_toolkit.layout.containers import Window
|
||||
from libs.prompt_toolkit.filters import IsDone
|
||||
from libs.prompt_toolkit.layout.controls import TokenListControl
|
||||
from libs.prompt_toolkit.layout.containers import ConditionalContainer, \
|
||||
ScrollOffsets, HSplit
|
||||
from libs.prompt_toolkit.layout.dimension import LayoutDimension as D
|
||||
from libs.prompt_toolkit.token import Token
|
||||
|
||||
from .. import PromptParameterException
|
||||
from ..separator import Separator
|
||||
from .common import setup_simple_validator, default_style, if_mousedown
|
||||
|
||||
|
||||
# custom control based on TokenListControl
|
||||
|
||||
|
||||
class InquirerControl(TokenListControl):
|
||||
def __init__(self, choices, **kwargs):
|
||||
self.pointer_index = 0
|
||||
self.selected_options = [] # list of names
|
||||
self.answered = False
|
||||
self._init_choices(choices)
|
||||
super(InquirerControl, self).__init__(self._get_choice_tokens,
|
||||
**kwargs)
|
||||
|
||||
def _init_choices(self, choices):
|
||||
# helper to convert from question format to internal format
|
||||
self.choices = [] # list (name, value)
|
||||
searching_first_choice = True
|
||||
for i, c in enumerate(choices):
|
||||
if isinstance(c, Separator):
|
||||
self.choices.append(c)
|
||||
else:
|
||||
name = c['name']
|
||||
value = c.get('value', name)
|
||||
disabled = c.get('disabled', None)
|
||||
if 'checked' in c and c['checked'] and not disabled:
|
||||
self.selected_options.append(c['name'])
|
||||
self.choices.append((name, value, disabled))
|
||||
if searching_first_choice and not disabled: # find the first (available) choice
|
||||
self.pointer_index = i
|
||||
searching_first_choice = False
|
||||
|
||||
@property
|
||||
def choice_count(self):
|
||||
return len(self.choices)
|
||||
|
||||
def _get_choice_tokens(self, cli):
|
||||
tokens = []
|
||||
T = Token
|
||||
|
||||
def append(index, line):
|
||||
if isinstance(line, Separator):
|
||||
tokens.append((T.Separator, ' %s\n' % line))
|
||||
else:
|
||||
line_name = line[0]
|
||||
line_value = line[1]
|
||||
selected = (line_value in self.selected_options) # use value to check if option has been selected
|
||||
pointed_at = (index == self.pointer_index)
|
||||
|
||||
@if_mousedown
|
||||
def select_item(cli, mouse_event):
|
||||
# bind option with this index to mouse event
|
||||
if line_value in self.selected_options:
|
||||
self.selected_options.remove(line_value)
|
||||
else:
|
||||
self.selected_options.append(line_value)
|
||||
|
||||
if pointed_at:
|
||||
tokens.append((T.Pointer, ' \u276f', select_item)) # ' >'
|
||||
else:
|
||||
tokens.append((T, ' ', select_item))
|
||||
# 'o ' - FISHEYE
|
||||
if choice[2]: # disabled
|
||||
tokens.append((T, '- %s (%s)' % (choice[0], choice[2])))
|
||||
else:
|
||||
if selected:
|
||||
tokens.append((T.Selected, '\u25cf ', select_item))
|
||||
else:
|
||||
tokens.append((T, '\u25cb ', select_item))
|
||||
|
||||
if pointed_at:
|
||||
tokens.append((Token.SetCursorPosition, ''))
|
||||
|
||||
tokens.append((T, line_name, select_item))
|
||||
tokens.append((T, '\n'))
|
||||
|
||||
# prepare the select choices
|
||||
for i, choice in enumerate(self.choices):
|
||||
append(i, choice)
|
||||
tokens.pop() # Remove last newline.
|
||||
return tokens
|
||||
|
||||
def get_selected_values(self):
|
||||
# get values not labels
|
||||
return [c[1] for c in self.choices if not isinstance(c, Separator) and
|
||||
c[1] in self.selected_options]
|
||||
|
||||
@property
|
||||
def line_count(self):
|
||||
return len(self.choices)
|
||||
|
||||
|
||||
def question(message, **kwargs):
|
||||
# TODO add bottom-bar (Move up and down to reveal more choices)
|
||||
# TODO extract common parts for list, checkbox, rawlist, expand
|
||||
# TODO validate
|
||||
if not 'choices' in kwargs:
|
||||
raise PromptParameterException('choices')
|
||||
# this does not implement default, use checked...
|
||||
if 'default' in kwargs:
|
||||
raise ValueError('Checkbox does not implement \'default\' '
|
||||
'use \'checked\':True\' in choice!')
|
||||
|
||||
choices = kwargs.pop('choices', None)
|
||||
validator = setup_simple_validator(kwargs)
|
||||
|
||||
# TODO style defaults on detail level
|
||||
style = kwargs.pop('style', default_style)
|
||||
|
||||
ic = InquirerControl(choices)
|
||||
qmark = kwargs.pop('qmark', '?')
|
||||
|
||||
def get_prompt_tokens(cli):
|
||||
tokens = []
|
||||
|
||||
tokens.append((Token.QuestionMark, qmark))
|
||||
tokens.append((Token.Question, ' %s ' % message))
|
||||
if ic.answered:
|
||||
nbr_selected = len(ic.selected_options)
|
||||
if nbr_selected == 0:
|
||||
tokens.append((Token.Answer, ' done'))
|
||||
elif nbr_selected == 1:
|
||||
tokens.append((Token.Answer, ' [%s]' % ic.selected_options[0]))
|
||||
else:
|
||||
tokens.append((Token.Answer,
|
||||
' done (%d selections)' % nbr_selected))
|
||||
else:
|
||||
tokens.append((Token.Instruction,
|
||||
' (<up>, <down> to move, <space> to select, <a> '
|
||||
'to toggle, <i> to invert)'))
|
||||
return tokens
|
||||
|
||||
# assemble layout
|
||||
layout = HSplit([
|
||||
Window(height=D.exact(1),
|
||||
content=TokenListControl(get_prompt_tokens, align_center=False)
|
||||
),
|
||||
ConditionalContainer(
|
||||
Window(
|
||||
ic,
|
||||
width=D.exact(43),
|
||||
height=D(min=3),
|
||||
scroll_offsets=ScrollOffsets(top=1, bottom=1)
|
||||
),
|
||||
filter=~IsDone()
|
||||
)
|
||||
])
|
||||
|
||||
# key bindings
|
||||
manager = KeyBindingManager.for_prompt()
|
||||
|
||||
@manager.registry.add_binding(Keys.ControlQ, eager=True)
|
||||
@manager.registry.add_binding(Keys.ControlC, eager=True)
|
||||
def _(event):
|
||||
raise KeyboardInterrupt()
|
||||
# event.cli.set_return_value(None)
|
||||
|
||||
@manager.registry.add_binding(' ', eager=True)
|
||||
def toggle(event):
|
||||
pointed_choice = ic.choices[ic.pointer_index][1] # value
|
||||
if pointed_choice in ic.selected_options:
|
||||
ic.selected_options.remove(pointed_choice)
|
||||
else:
|
||||
ic.selected_options.append(pointed_choice)
|
||||
|
||||
@manager.registry.add_binding('i', eager=True)
|
||||
def invert(event):
|
||||
inverted_selection = [c[1] for c in ic.choices if
|
||||
not isinstance(c, Separator) and
|
||||
c[1] not in ic.selected_options and
|
||||
not c[2]]
|
||||
ic.selected_options = inverted_selection
|
||||
|
||||
@manager.registry.add_binding('a', eager=True)
|
||||
def all(event):
|
||||
all_selected = True # all choices have been selected
|
||||
for c in ic.choices:
|
||||
if not isinstance(c, Separator) and c[1] not in ic.selected_options and not c[2]:
|
||||
# add missing ones
|
||||
ic.selected_options.append(c[1])
|
||||
all_selected = False
|
||||
if all_selected:
|
||||
ic.selected_options = []
|
||||
|
||||
@manager.registry.add_binding(Keys.Down, eager=True)
|
||||
def move_cursor_down(event):
|
||||
def _next():
|
||||
ic.pointer_index = ((ic.pointer_index + 1) % ic.line_count)
|
||||
_next()
|
||||
while isinstance(ic.choices[ic.pointer_index], Separator) or \
|
||||
ic.choices[ic.pointer_index][2]:
|
||||
_next()
|
||||
|
||||
@manager.registry.add_binding(Keys.Up, eager=True)
|
||||
def move_cursor_up(event):
|
||||
def _prev():
|
||||
ic.pointer_index = ((ic.pointer_index - 1) % ic.line_count)
|
||||
_prev()
|
||||
while isinstance(ic.choices[ic.pointer_index], Separator) or \
|
||||
ic.choices[ic.pointer_index][2]:
|
||||
_prev()
|
||||
|
||||
@manager.registry.add_binding(Keys.Enter, eager=True)
|
||||
def set_answer(event):
|
||||
ic.answered = True
|
||||
# TODO use validator
|
||||
event.cli.set_return_value(ic.get_selected_values())
|
||||
|
||||
return Application(
|
||||
layout=layout,
|
||||
key_bindings_registry=manager.registry,
|
||||
mouse_support=True,
|
||||
style=style
|
||||
)
|
92
src/libs/PyInquirer/prompts/common.py
Normal file
92
src/libs/PyInquirer/prompts/common.py
Normal file
@@ -0,0 +1,92 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
common prompt functionality
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from libs.prompt_toolkit.validation import Validator, ValidationError
|
||||
from libs.prompt_toolkit.styles import style_from_dict
|
||||
from libs.prompt_toolkit.token import Token
|
||||
from libs.prompt_toolkit.mouse_events import MouseEventTypes
|
||||
|
||||
|
||||
PY3 = sys.version_info[0] >= 3
|
||||
|
||||
if PY3:
|
||||
basestring = str
|
||||
|
||||
|
||||
def if_mousedown(handler):
|
||||
def handle_if_mouse_down(cli, mouse_event):
|
||||
if mouse_event.event_type == MouseEventTypes.MOUSE_DOWN:
|
||||
return handler(cli, mouse_event)
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
return handle_if_mouse_down
|
||||
|
||||
|
||||
# TODO probably better to use base.Condition
|
||||
def setup_validator(kwargs):
|
||||
# this is an internal helper not meant for public consumption!
|
||||
# note this works on a dictionary
|
||||
validate_prompt = kwargs.pop('validate', None)
|
||||
if validate_prompt:
|
||||
if issubclass(validate_prompt, Validator):
|
||||
kwargs['validator'] = validate_prompt()
|
||||
elif callable(validate_prompt):
|
||||
class _InputValidator(Validator):
|
||||
def validate(self, document):
|
||||
#print('validation!!')
|
||||
verdict = validate_prompt(document.text)
|
||||
if isinstance(verdict, basestring):
|
||||
raise ValidationError(
|
||||
message=verdict,
|
||||
cursor_position=len(document.text))
|
||||
elif verdict is not True:
|
||||
raise ValidationError(
|
||||
message='invalid input',
|
||||
cursor_position=len(document.text))
|
||||
kwargs['validator'] = _InputValidator()
|
||||
return kwargs['validator']
|
||||
|
||||
|
||||
def setup_simple_validator(kwargs):
|
||||
# this is an internal helper not meant for public consumption!
|
||||
# note this works on a dictionary
|
||||
# this validates the answer not a buffer
|
||||
# TODO
|
||||
# not sure yet how to deal with the validation result:
|
||||
# https://github.com/jonathanslenders/python-prompt-toolkit/issues/430
|
||||
validate = kwargs.pop('validate', None)
|
||||
if validate is None:
|
||||
def _always(answer):
|
||||
return True
|
||||
return _always
|
||||
elif not callable(validate):
|
||||
raise ValueError('Here a simple validate function is expected, no class')
|
||||
|
||||
def _validator(answer):
|
||||
verdict = validate(answer)
|
||||
if isinstance(verdict, basestring):
|
||||
raise ValidationError(
|
||||
message=verdict
|
||||
)
|
||||
elif verdict is not True:
|
||||
raise ValidationError(
|
||||
message='invalid input'
|
||||
)
|
||||
return _validator
|
||||
|
||||
|
||||
# FIXME style defaults on detail level
|
||||
default_style = style_from_dict({
|
||||
Token.Separator: '#6C6C6C',
|
||||
Token.QuestionMark: '#5F819D',
|
||||
Token.Selected: '', # default
|
||||
Token.Pointer: '#FF9D00 bold', # AWS orange
|
||||
Token.Instruction: '', # default
|
||||
Token.Answer: '#FF9D00 bold', # AWS orange
|
||||
Token.Question: 'bold',
|
||||
})
|
82
src/libs/PyInquirer/prompts/confirm.py
Normal file
82
src/libs/PyInquirer/prompts/confirm.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
confirm type question
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals
|
||||
from libs.prompt_toolkit.application import Application
|
||||
from libs.prompt_toolkit.key_binding.manager import KeyBindingManager
|
||||
from libs.prompt_toolkit.keys import Keys
|
||||
from libs.prompt_toolkit.layout.containers import Window, HSplit
|
||||
from libs.prompt_toolkit.layout.controls import TokenListControl
|
||||
from libs.prompt_toolkit.layout.dimension import LayoutDimension as D
|
||||
from libs.prompt_toolkit.token import Token
|
||||
from libs.prompt_toolkit.shortcuts import create_prompt_application
|
||||
from libs.prompt_toolkit.styles import style_from_dict
|
||||
|
||||
|
||||
# custom control based on TokenListControl
|
||||
|
||||
|
||||
def question(message, **kwargs):
|
||||
# TODO need ENTER confirmation
|
||||
default = kwargs.pop('default', True)
|
||||
|
||||
# TODO style defaults on detail level
|
||||
style = kwargs.pop('style', style_from_dict({
|
||||
Token.QuestionMark: '#5F819D',
|
||||
#Token.Selected: '#FF9D00', # AWS orange
|
||||
Token.Instruction: '', # default
|
||||
Token.Answer: '#FF9D00 bold', # AWS orange
|
||||
Token.Question: 'bold',
|
||||
}))
|
||||
status = {'answer': None}
|
||||
|
||||
qmark = kwargs.pop('qmark', '?')
|
||||
|
||||
def get_prompt_tokens(cli):
|
||||
tokens = []
|
||||
|
||||
tokens.append((Token.QuestionMark, qmark))
|
||||
tokens.append((Token.Question, ' %s ' % message))
|
||||
if isinstance(status['answer'], bool):
|
||||
tokens.append((Token.Answer, ' Yes' if status['answer'] else ' No'))
|
||||
else:
|
||||
if default:
|
||||
instruction = ' (Y/n)'
|
||||
else:
|
||||
instruction = ' (y/N)'
|
||||
tokens.append((Token.Instruction, instruction))
|
||||
return tokens
|
||||
|
||||
# key bindings
|
||||
manager = KeyBindingManager.for_prompt()
|
||||
|
||||
@manager.registry.add_binding(Keys.ControlQ, eager=True)
|
||||
@manager.registry.add_binding(Keys.ControlC, eager=True)
|
||||
def _(event):
|
||||
raise KeyboardInterrupt()
|
||||
|
||||
@manager.registry.add_binding('n')
|
||||
@manager.registry.add_binding('N')
|
||||
def key_n(event):
|
||||
status['answer'] = False
|
||||
event.cli.set_return_value(False)
|
||||
|
||||
@manager.registry.add_binding('y')
|
||||
@manager.registry.add_binding('Y')
|
||||
def key_y(event):
|
||||
status['answer'] = True
|
||||
event.cli.set_return_value(True)
|
||||
|
||||
@manager.registry.add_binding(Keys.Enter, eager=True)
|
||||
def set_answer(event):
|
||||
status['answer'] = default
|
||||
event.cli.set_return_value(default)
|
||||
|
||||
return create_prompt_application(
|
||||
get_prompt_tokens=get_prompt_tokens,
|
||||
key_bindings_registry=manager.registry,
|
||||
mouse_support=False,
|
||||
style=style,
|
||||
erase_when_done=False,
|
||||
)
|
197
src/libs/PyInquirer/prompts/editor.py
Normal file
197
src/libs/PyInquirer/prompts/editor.py
Normal file
@@ -0,0 +1,197 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
`editor` type question
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals
|
||||
import os
|
||||
import sys
|
||||
from libs.prompt_toolkit.token import Token
|
||||
from libs.prompt_toolkit.shortcuts import create_prompt_application
|
||||
from libs.prompt_toolkit.validation import Validator, ValidationError
|
||||
from libs.prompt_toolkit.layout.lexers import SimpleLexer
|
||||
|
||||
from .common import default_style
|
||||
|
||||
|
||||
# use std prompt-toolkit control
|
||||
|
||||
WIN = sys.platform.startswith('win')
|
||||
|
||||
class EditorArgumentsError(Exception):
|
||||
pass
|
||||
|
||||
class Editor(object):
|
||||
|
||||
def __init__(self, editor=None, env=None, require_save=True, extension='.txt'):
|
||||
self.editor = editor
|
||||
self.env = env
|
||||
self.require_save = require_save
|
||||
self.extension = extension
|
||||
|
||||
def get_editor(self):
|
||||
if self.editor is not None and self.editor.lower() != "default":
|
||||
return self.editor
|
||||
for key in 'VISUAL', 'EDITOR':
|
||||
rv = os.environ.get(key)
|
||||
if rv:
|
||||
return rv
|
||||
if WIN:
|
||||
return 'notepad'
|
||||
for editor in 'vim', 'nano':
|
||||
if os.system('which %s >/dev/null 2>&1' % editor) == 0:
|
||||
return editor
|
||||
return 'vi'
|
||||
|
||||
def edit_file(self, filename):
|
||||
import subprocess
|
||||
editor = self.get_editor()
|
||||
if self.env:
|
||||
environ = os.environ.copy()
|
||||
environ.update(self.env)
|
||||
else:
|
||||
environ = None
|
||||
try:
|
||||
c = subprocess.Popen('%s "%s"' % (editor, filename),
|
||||
env=environ, shell=True)
|
||||
exit_code = c.wait()
|
||||
if exit_code != 0:
|
||||
raise Exception('%s: Editing failed!' % editor)
|
||||
except OSError as e:
|
||||
raise Exception('%s: Editing failed: %s' % (editor, e))
|
||||
|
||||
def edit(self, text):
|
||||
import tempfile
|
||||
|
||||
text = text or ''
|
||||
if text and not text.endswith('\n'):
|
||||
text += '\n'
|
||||
|
||||
fd, name = tempfile.mkstemp(prefix='editor-', suffix=self.extension)
|
||||
try:
|
||||
if WIN:
|
||||
encoding = 'utf-8-sig'
|
||||
text = text.replace('\n', '\r\n')
|
||||
else:
|
||||
encoding = 'utf-8'
|
||||
text = text.encode(encoding)
|
||||
|
||||
f = os.fdopen(fd, 'wb')
|
||||
f.write(text)
|
||||
f.close()
|
||||
timestamp = os.path.getmtime(name)
|
||||
|
||||
self.edit_file(name)
|
||||
|
||||
if self.require_save \
|
||||
and os.path.getmtime(name) == timestamp:
|
||||
return None
|
||||
|
||||
f = open(name, 'rb')
|
||||
try:
|
||||
rv = f.read()
|
||||
finally:
|
||||
f.close()
|
||||
return rv.decode('utf-8-sig').replace('\r\n', '\n')
|
||||
finally:
|
||||
os.unlink(name)
|
||||
|
||||
def edit(text=None, editor=None, env=None, require_save=True,
|
||||
extension='.txt', filename=None):
|
||||
r"""Edits the given text in the defined editor. If an editor is given
|
||||
(should be the full path to the executable but the regular operating
|
||||
system search path is used for finding the executable) it overrides
|
||||
the detected editor. Optionally, some environment variables can be
|
||||
used. If the editor is closed without changes, `None` is returned. In
|
||||
case a file is edited directly the return value is always `None` and
|
||||
`require_save` and `extension` are ignored.
|
||||
|
||||
If the editor cannot be opened a :exc:`UsageError` is raised.
|
||||
|
||||
Note for Windows: to simplify cross-platform usage, the newlines are
|
||||
automatically converted from POSIX to Windows and vice versa. As such,
|
||||
the message here will have ``\n`` as newline markers.
|
||||
|
||||
:param text: the text to edit.
|
||||
:param editor: optionally the editor to use. Defaults to automatic
|
||||
detection.
|
||||
:param env: environment variables to forward to the editor.
|
||||
:param require_save: if this is true, then not saving in the editor
|
||||
will make the return value become `None`.
|
||||
:param extension: the extension to tell the editor about. This defaults
|
||||
to `.txt` but changing this might change syntax
|
||||
highlighting.
|
||||
:param filename: if provided it will edit this file instead of the
|
||||
provided text contents. It will not use a temporary
|
||||
file as an indirection in that case.
|
||||
"""
|
||||
|
||||
editor = Editor(editor=editor, env=env, require_save=require_save,
|
||||
extension=extension)
|
||||
if filename is None:
|
||||
return editor.edit(text)
|
||||
editor.edit_file(filename)
|
||||
|
||||
def question(message, **kwargs):
|
||||
default = kwargs.pop('default', '')
|
||||
eargs = kwargs.pop('eargs', {})
|
||||
validate_prompt = kwargs.pop('validate', None)
|
||||
if validate_prompt:
|
||||
if issubclass(validate_prompt, Validator):
|
||||
kwargs['validator'] = validate_prompt()
|
||||
elif callable(validate_prompt):
|
||||
class _InputValidator(Validator):
|
||||
def validate(self, document):
|
||||
verdict = validate_prompt(document.text)
|
||||
if not verdict == True:
|
||||
if verdict == False:
|
||||
verdict = 'invalid input'
|
||||
raise ValidationError(
|
||||
message=verdict,
|
||||
cursor_position=len(document.text))
|
||||
kwargs['validator'] = _InputValidator()
|
||||
|
||||
for k, v in eargs.items():
|
||||
if v == "" or v == " ":
|
||||
raise EditorArgumentsError(
|
||||
"Args '{}' value should not be empty".format(k)
|
||||
)
|
||||
|
||||
editor = eargs.get("editor", None)
|
||||
ext = eargs.get("ext", ".txt")
|
||||
env = eargs.get("env", None)
|
||||
text = default
|
||||
filename = eargs.get("filename", None)
|
||||
multiline = True if not editor else False
|
||||
save = eargs.get("save", None)
|
||||
|
||||
if editor:
|
||||
_text = edit(
|
||||
editor=editor,
|
||||
extension=ext,
|
||||
text=text,
|
||||
env=env,
|
||||
filename=filename,
|
||||
require_save=save
|
||||
)
|
||||
if filename:
|
||||
default = filename
|
||||
else:
|
||||
default = _text
|
||||
|
||||
# TODO style defaults on detail level
|
||||
kwargs['style'] = kwargs.pop('style', default_style)
|
||||
qmark = kwargs.pop('qmark', '?')
|
||||
|
||||
def _get_prompt_tokens(cli):
|
||||
return [
|
||||
(Token.QuestionMark, qmark),
|
||||
(Token.Question, ' %s ' % message)
|
||||
]
|
||||
|
||||
return create_prompt_application(
|
||||
get_prompt_tokens=_get_prompt_tokens,
|
||||
lexer=SimpleLexer(Token.Answer),
|
||||
default=default,
|
||||
multiline=multiline,
|
||||
**kwargs
|
||||
)
|
195
src/libs/PyInquirer/prompts/expand.py
Normal file
195
src/libs/PyInquirer/prompts/expand.py
Normal file
@@ -0,0 +1,195 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
`expand` type question
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import sys
|
||||
|
||||
from libs.prompt_toolkit.application import Application
|
||||
from libs.prompt_toolkit.key_binding.manager import KeyBindingManager
|
||||
from libs.prompt_toolkit.keys import Keys
|
||||
from libs.prompt_toolkit.layout.containers import Window
|
||||
from libs.prompt_toolkit.filters import IsDone
|
||||
from libs.prompt_toolkit.layout.controls import TokenListControl
|
||||
from libs.prompt_toolkit.layout.containers import ConditionalContainer, HSplit
|
||||
from libs.prompt_toolkit.layout.dimension import LayoutDimension as D
|
||||
from libs.prompt_toolkit.token import Token
|
||||
|
||||
from .. import PromptParameterException
|
||||
from ..separator import Separator
|
||||
from .common import default_style
|
||||
from .common import if_mousedown
|
||||
|
||||
|
||||
PY3 = sys.version_info[0] >= 3
|
||||
|
||||
if PY3:
|
||||
basestring = str
|
||||
|
||||
|
||||
# custom control based on TokenListControl
|
||||
|
||||
class InquirerControl(TokenListControl):
|
||||
def __init__(self, choices, default=None, **kwargs):
|
||||
self.pointer_index = 0
|
||||
self.answered = False
|
||||
self._init_choices(choices, default)
|
||||
self._help_active = False # help is activated via 'h' key
|
||||
super(InquirerControl, self).__init__(self._get_choice_tokens,
|
||||
**kwargs)
|
||||
|
||||
def _init_choices(self, choices, default=None):
|
||||
# helper to convert from question format to internal format
|
||||
|
||||
self.choices = [] # list (key, name, value)
|
||||
|
||||
if not default:
|
||||
default = 'h'
|
||||
for i, c in enumerate(choices):
|
||||
if isinstance(c, Separator):
|
||||
self.choices.append(c)
|
||||
else:
|
||||
if isinstance(c, basestring):
|
||||
self.choices.append((key, c, c))
|
||||
else:
|
||||
key = c.get('key')
|
||||
name = c.get('name')
|
||||
value = c.get('value', name)
|
||||
if default and default == key:
|
||||
self.pointer_index = i
|
||||
key = key.upper() # default key is in uppercase
|
||||
self.choices.append((key, name, value))
|
||||
# append the help choice
|
||||
key = 'h'
|
||||
if not default:
|
||||
self.pointer_index = len(self.choices)
|
||||
key = key.upper() # default key is in uppercase
|
||||
self.choices.append((key, 'Help, list all options', None))
|
||||
|
||||
@property
|
||||
def choice_count(self):
|
||||
return len(self.choices)
|
||||
|
||||
def _get_choice_tokens(self, cli):
|
||||
tokens = []
|
||||
T = Token
|
||||
|
||||
def _append(index, line):
|
||||
if isinstance(line, Separator):
|
||||
tokens.append((T.Separator, ' %s\n' % line))
|
||||
else:
|
||||
key = line[0]
|
||||
line = line[1]
|
||||
pointed_at = (index == self.pointer_index)
|
||||
|
||||
@if_mousedown
|
||||
def select_item(cli, mouse_event):
|
||||
# bind option with this index to mouse event
|
||||
self.pointer_index = index
|
||||
|
||||
if pointed_at:
|
||||
tokens.append((T.Selected, ' %s) %s' % (key, line),
|
||||
select_item))
|
||||
else:
|
||||
tokens.append((T, ' %s) %s' % (key, line),
|
||||
select_item))
|
||||
tokens.append((T, '\n'))
|
||||
|
||||
if self._help_active:
|
||||
# prepare the select choices
|
||||
for i, choice in enumerate(self.choices):
|
||||
_append(i, choice)
|
||||
tokens.append((T, ' Answer: %s' %
|
||||
self.choices[self.pointer_index][0]))
|
||||
else:
|
||||
tokens.append((T.Pointer, '>> '))
|
||||
tokens.append((T, self.choices[self.pointer_index][1]))
|
||||
return tokens
|
||||
|
||||
def get_selected_value(self):
|
||||
# get value not label
|
||||
return self.choices[self.pointer_index][2]
|
||||
|
||||
|
||||
def question(message, **kwargs):
|
||||
# TODO extract common parts for list, checkbox, rawlist, expand
|
||||
# TODO up, down navigation
|
||||
if not 'choices' in kwargs:
|
||||
raise PromptParameterException('choices')
|
||||
|
||||
choices = kwargs.pop('choices', None)
|
||||
default = kwargs.pop('default', None)
|
||||
qmark = kwargs.pop('qmark', '?')
|
||||
# TODO style defaults on detail level
|
||||
style = kwargs.pop('style', default_style)
|
||||
|
||||
ic = InquirerControl(choices, default)
|
||||
|
||||
def get_prompt_tokens(cli):
|
||||
tokens = []
|
||||
T = Token
|
||||
|
||||
tokens.append((T.QuestionMark, qmark))
|
||||
tokens.append((T.Question, ' %s ' % message))
|
||||
if not ic.answered:
|
||||
tokens.append((T.Instruction, ' (%s)' % ''.join(
|
||||
[k[0] for k in ic.choices if not isinstance(k, Separator)])))
|
||||
else:
|
||||
tokens.append((T.Answer, ' %s' % ic.get_selected_value()))
|
||||
return tokens
|
||||
|
||||
#@Condition
|
||||
#def is_help_active(cli):
|
||||
# return ic._help_active
|
||||
|
||||
# assemble layout
|
||||
layout = HSplit([
|
||||
Window(height=D.exact(1),
|
||||
content=TokenListControl(get_prompt_tokens)
|
||||
),
|
||||
ConditionalContainer(
|
||||
Window(ic),
|
||||
#filter=is_help_active & ~IsDone() # ~ bitwise inverse
|
||||
filter=~IsDone() # ~ bitwise inverse
|
||||
)
|
||||
])
|
||||
|
||||
# key bindings
|
||||
manager = KeyBindingManager.for_prompt()
|
||||
|
||||
@manager.registry.add_binding(Keys.ControlQ, eager=True)
|
||||
@manager.registry.add_binding(Keys.ControlC, eager=True)
|
||||
def _(event):
|
||||
raise KeyboardInterrupt()
|
||||
|
||||
# add key bindings for choices
|
||||
for i, c in enumerate(ic.choices):
|
||||
if not isinstance(c, Separator):
|
||||
def _reg_binding(i, keys):
|
||||
# trick out late evaluation with a "function factory":
|
||||
# http://stackoverflow.com/questions/3431676/creating-functions-in-a-loop
|
||||
@manager.registry.add_binding(keys, eager=True)
|
||||
def select_choice(event):
|
||||
ic.pointer_index = i
|
||||
if c[0] not in ['h', 'H']:
|
||||
_reg_binding(i, c[0])
|
||||
if c[0].isupper():
|
||||
_reg_binding(i, c[0].lower())
|
||||
|
||||
@manager.registry.add_binding('H', eager=True)
|
||||
@manager.registry.add_binding('h', eager=True)
|
||||
def help_choice(event):
|
||||
ic._help_active = True
|
||||
|
||||
@manager.registry.add_binding(Keys.Enter, eager=True)
|
||||
def set_answer(event):
|
||||
ic.answered = True
|
||||
event.cli.set_return_value(ic.get_selected_value())
|
||||
|
||||
return Application(
|
||||
layout=layout,
|
||||
key_bindings_registry=manager.registry,
|
||||
mouse_support=True,
|
||||
style=style
|
||||
)
|
51
src/libs/PyInquirer/prompts/input.py
Normal file
51
src/libs/PyInquirer/prompts/input.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
`input` type question
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals
|
||||
import inspect
|
||||
from libs.prompt_toolkit.token import Token
|
||||
from libs.prompt_toolkit.shortcuts import create_prompt_application
|
||||
from libs.prompt_toolkit.validation import Validator, ValidationError
|
||||
from libs.prompt_toolkit.layout.lexers import SimpleLexer
|
||||
|
||||
from .common import default_style
|
||||
|
||||
# use std prompt-toolkit control
|
||||
|
||||
|
||||
def question(message, **kwargs):
|
||||
default = kwargs.pop('default', '')
|
||||
validate_prompt = kwargs.pop('validate', None)
|
||||
if validate_prompt:
|
||||
if inspect.isclass(validate_prompt) and issubclass(validate_prompt, Validator):
|
||||
kwargs['validator'] = validate_prompt()
|
||||
elif callable(validate_prompt):
|
||||
class _InputValidator(Validator):
|
||||
def validate(self, document):
|
||||
verdict = validate_prompt(document.text)
|
||||
if not verdict == True:
|
||||
if verdict == False:
|
||||
verdict = 'invalid input'
|
||||
raise ValidationError(
|
||||
message=verdict,
|
||||
cursor_position=len(document.text))
|
||||
kwargs['validator'] = _InputValidator()
|
||||
|
||||
# TODO style defaults on detail level
|
||||
kwargs['style'] = kwargs.pop('style', default_style)
|
||||
qmark = kwargs.pop('qmark', '?')
|
||||
|
||||
|
||||
def _get_prompt_tokens(cli):
|
||||
return [
|
||||
(Token.QuestionMark, qmark),
|
||||
(Token.Question, ' %s ' % message)
|
||||
]
|
||||
|
||||
return create_prompt_application(
|
||||
get_prompt_tokens=_get_prompt_tokens,
|
||||
lexer=SimpleLexer(Token.Answer),
|
||||
default=default,
|
||||
**kwargs
|
||||
)
|
184
src/libs/PyInquirer/prompts/list.py
Normal file
184
src/libs/PyInquirer/prompts/list.py
Normal file
@@ -0,0 +1,184 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
`list` type question
|
||||
"""
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
|
||||
from libs.prompt_toolkit.application import Application
|
||||
from libs.prompt_toolkit.key_binding.manager import KeyBindingManager
|
||||
from libs.prompt_toolkit.keys import Keys
|
||||
from libs.prompt_toolkit.layout.containers import Window
|
||||
from libs.prompt_toolkit.filters import IsDone
|
||||
from libs.prompt_toolkit.layout.controls import TokenListControl
|
||||
from libs.prompt_toolkit.layout.containers import ConditionalContainer, \
|
||||
ScrollOffsets, HSplit
|
||||
from libs.prompt_toolkit.layout.dimension import LayoutDimension as D
|
||||
from libs.prompt_toolkit.token import Token
|
||||
|
||||
from .. import PromptParameterException
|
||||
from ..separator import Separator
|
||||
from .common import if_mousedown, default_style
|
||||
|
||||
# custom control based on TokenListControl
|
||||
# docu here:
|
||||
# https://github.com/jonathanslenders/python-prompt-toolkit/issues/281
|
||||
# https://github.com/jonathanslenders/python-prompt-toolkit/blob/master/examples/full-screen-layout.py
|
||||
# https://github.com/jonathanslenders/python-prompt-toolkit/blob/master/docs/pages/full_screen_apps.rst
|
||||
|
||||
|
||||
PY3 = sys.version_info[0] >= 3
|
||||
|
||||
if PY3:
|
||||
basestring = str
|
||||
|
||||
|
||||
class InquirerControl(TokenListControl):
|
||||
def __init__(self, choices, **kwargs):
|
||||
self.selected_option_index = 0
|
||||
self.answered = False
|
||||
self.choices = choices
|
||||
self._init_choices(choices)
|
||||
super(InquirerControl, self).__init__(self._get_choice_tokens,
|
||||
**kwargs)
|
||||
|
||||
def _init_choices(self, choices, default=None):
|
||||
# helper to convert from question format to internal format
|
||||
self.choices = [] # list (name, value, disabled)
|
||||
searching_first_choice = True
|
||||
for i, c in enumerate(choices):
|
||||
if isinstance(c, Separator):
|
||||
self.choices.append((c, None, None))
|
||||
else:
|
||||
if isinstance(c, basestring):
|
||||
self.choices.append((c, c, None))
|
||||
else:
|
||||
name = c.get('name')
|
||||
value = c.get('value', name)
|
||||
disabled = c.get('disabled', None)
|
||||
self.choices.append((name, value, disabled))
|
||||
if searching_first_choice:
|
||||
self.selected_option_index = i # found the first choice
|
||||
searching_first_choice = False
|
||||
|
||||
@property
|
||||
def choice_count(self):
|
||||
return len(self.choices)
|
||||
|
||||
def _get_choice_tokens(self, cli):
|
||||
tokens = []
|
||||
T = Token
|
||||
|
||||
def append(index, choice):
|
||||
selected = (index == self.selected_option_index)
|
||||
|
||||
@if_mousedown
|
||||
def select_item(cli, mouse_event):
|
||||
# bind option with this index to mouse event
|
||||
self.selected_option_index = index
|
||||
self.answered = True
|
||||
cli.set_return_value(None)
|
||||
|
||||
tokens.append((T.Pointer if selected else T, ' \u276f ' if selected
|
||||
else ' '))
|
||||
if selected:
|
||||
tokens.append((Token.SetCursorPosition, ''))
|
||||
if choice[2]: # disabled
|
||||
tokens.append((T.Selected if selected else T,
|
||||
'- %s (%s)' % (choice[0], choice[2])))
|
||||
else:
|
||||
try:
|
||||
tokens.append((T.Selected if selected else T, str(choice[0]),
|
||||
select_item))
|
||||
except:
|
||||
tokens.append((T.Selected if selected else T, choice[0],
|
||||
select_item))
|
||||
tokens.append((T, '\n'))
|
||||
|
||||
# prepare the select choices
|
||||
for i, choice in enumerate(self.choices):
|
||||
append(i, choice)
|
||||
tokens.pop() # Remove last newline.
|
||||
return tokens
|
||||
|
||||
def get_selection(self):
|
||||
return self.choices[self.selected_option_index]
|
||||
|
||||
|
||||
def question(message, **kwargs):
|
||||
# TODO disabled, dict choices
|
||||
if not 'choices' in kwargs:
|
||||
raise PromptParameterException('choices')
|
||||
|
||||
choices = kwargs.pop('choices', None)
|
||||
default = kwargs.pop('default', 0) # TODO
|
||||
qmark = kwargs.pop('qmark', '?')
|
||||
# TODO style defaults on detail level
|
||||
style = kwargs.pop('style', default_style)
|
||||
|
||||
ic = InquirerControl(choices)
|
||||
|
||||
def get_prompt_tokens(cli):
|
||||
tokens = []
|
||||
|
||||
tokens.append((Token.QuestionMark, qmark))
|
||||
tokens.append((Token.Question, ' %s ' % message))
|
||||
if ic.answered:
|
||||
tokens.append((Token.Answer, ' ' + ic.get_selection()[0]))
|
||||
else:
|
||||
tokens.append((Token.Instruction, ' (Use arrow keys)'))
|
||||
return tokens
|
||||
|
||||
# assemble layout
|
||||
layout = HSplit([
|
||||
Window(height=D.exact(1),
|
||||
content=TokenListControl(get_prompt_tokens)
|
||||
),
|
||||
ConditionalContainer(
|
||||
Window(ic),
|
||||
filter=~IsDone()
|
||||
)
|
||||
])
|
||||
|
||||
# key bindings
|
||||
manager = KeyBindingManager.for_prompt()
|
||||
|
||||
@manager.registry.add_binding(Keys.ControlQ, eager=True)
|
||||
@manager.registry.add_binding(Keys.ControlC, eager=True)
|
||||
def _(event):
|
||||
raise KeyboardInterrupt()
|
||||
# event.cli.set_return_value(None)
|
||||
|
||||
@manager.registry.add_binding(Keys.Down, eager=True)
|
||||
def move_cursor_down(event):
|
||||
def _next():
|
||||
ic.selected_option_index = (
|
||||
(ic.selected_option_index + 1) % ic.choice_count)
|
||||
_next()
|
||||
while isinstance(ic.choices[ic.selected_option_index][0], Separator) or\
|
||||
ic.choices[ic.selected_option_index][2]:
|
||||
_next()
|
||||
|
||||
@manager.registry.add_binding(Keys.Up, eager=True)
|
||||
def move_cursor_up(event):
|
||||
def _prev():
|
||||
ic.selected_option_index = (
|
||||
(ic.selected_option_index - 1) % ic.choice_count)
|
||||
_prev()
|
||||
while isinstance(ic.choices[ic.selected_option_index][0], Separator) or \
|
||||
ic.choices[ic.selected_option_index][2]:
|
||||
_prev()
|
||||
|
||||
@manager.registry.add_binding(Keys.Enter, eager=True)
|
||||
def set_answer(event):
|
||||
ic.answered = True
|
||||
event.cli.set_return_value(ic.get_selection()[1])
|
||||
|
||||
return Application(
|
||||
layout=layout,
|
||||
key_bindings_registry=manager.registry,
|
||||
mouse_support=True,
|
||||
style=style
|
||||
)
|
14
src/libs/PyInquirer/prompts/password.py
Normal file
14
src/libs/PyInquirer/prompts/password.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
`password` type question
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
from . import input
|
||||
|
||||
# use std prompt-toolkit control
|
||||
|
||||
|
||||
def question(message, **kwargs):
|
||||
kwargs['is_password'] = True
|
||||
return input.question(message, **kwargs)
|
166
src/libs/PyInquirer/prompts/rawlist.py
Normal file
166
src/libs/PyInquirer/prompts/rawlist.py
Normal file
@@ -0,0 +1,166 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
`rawlist` type question
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals
|
||||
import sys
|
||||
|
||||
from libs.prompt_toolkit.application import Application
|
||||
from libs.prompt_toolkit.key_binding.manager import KeyBindingManager
|
||||
from libs.prompt_toolkit.keys import Keys
|
||||
from libs.prompt_toolkit.layout.containers import Window
|
||||
from libs.prompt_toolkit.filters import IsDone
|
||||
from libs.prompt_toolkit.layout.controls import TokenListControl
|
||||
from libs.prompt_toolkit.layout.containers import ConditionalContainer, HSplit
|
||||
from libs.prompt_toolkit.layout.dimension import LayoutDimension as D
|
||||
from libs.prompt_toolkit.token import Token
|
||||
|
||||
from .. import PromptParameterException
|
||||
from ..separator import Separator
|
||||
from .common import default_style
|
||||
from .common import if_mousedown
|
||||
|
||||
|
||||
PY3 = sys.version_info[0] >= 3
|
||||
|
||||
if PY3:
|
||||
basestring = str
|
||||
|
||||
|
||||
# custom control based on TokenListControl
|
||||
|
||||
class InquirerControl(TokenListControl):
|
||||
def __init__(self, choices, **kwargs):
|
||||
self.pointer_index = 0
|
||||
self.answered = False
|
||||
self._init_choices(choices)
|
||||
super(InquirerControl, self).__init__(self._get_choice_tokens,
|
||||
**kwargs)
|
||||
|
||||
def _init_choices(self, choices):
|
||||
# helper to convert from question format to internal format
|
||||
self.choices = [] # list (key, name, value)
|
||||
searching_first_choice = True
|
||||
key = 1 # used for numeric keys
|
||||
for i, c in enumerate(choices):
|
||||
if isinstance(c, Separator):
|
||||
self.choices.append(c)
|
||||
else:
|
||||
if isinstance(c, basestring):
|
||||
self.choices.append((key, c, c))
|
||||
key += 1
|
||||
if searching_first_choice:
|
||||
self.pointer_index = i # found the first choice
|
||||
searching_first_choice = False
|
||||
|
||||
@property
|
||||
def choice_count(self):
|
||||
return len(self.choices)
|
||||
|
||||
def _get_choice_tokens(self, cli):
|
||||
tokens = []
|
||||
T = Token
|
||||
|
||||
def _append(index, line):
|
||||
if isinstance(line, Separator):
|
||||
tokens.append((T.Separator, ' %s\n' % line))
|
||||
else:
|
||||
key = line[0]
|
||||
line = line[1]
|
||||
pointed_at = (index == self.pointer_index)
|
||||
|
||||
@if_mousedown
|
||||
def select_item(cli, mouse_event):
|
||||
# bind option with this index to mouse event
|
||||
self.pointer_index = index
|
||||
|
||||
if pointed_at:
|
||||
tokens.append((T.Selected, ' %d) %s' % (key, line),
|
||||
select_item))
|
||||
else:
|
||||
tokens.append((T, ' %d) %s' % (key, line),
|
||||
select_item))
|
||||
|
||||
tokens.append((T, '\n'))
|
||||
|
||||
# prepare the select choices
|
||||
for i, choice in enumerate(self.choices):
|
||||
_append(i, choice)
|
||||
tokens.append((T, ' Answer: %d' % self.choices[self.pointer_index][0]))
|
||||
return tokens
|
||||
|
||||
def get_selected_value(self):
|
||||
# get value not label
|
||||
return self.choices[self.pointer_index][2]
|
||||
|
||||
|
||||
def question(message, **kwargs):
|
||||
# TODO extract common parts for list, checkbox, rawlist, expand
|
||||
if not 'choices' in kwargs:
|
||||
raise PromptParameterException('choices')
|
||||
# this does not implement default, use checked...
|
||||
# TODO
|
||||
#if 'default' in kwargs:
|
||||
# raise ValueError('rawlist does not implement \'default\' '
|
||||
# 'use \'checked\':True\' in choice!')
|
||||
qmark = kwargs.pop('qmark', '?')
|
||||
choices = kwargs.pop('choices', None)
|
||||
if len(choices) > 9:
|
||||
raise ValueError('rawlist supports only a maximum of 9 choices!')
|
||||
|
||||
# TODO style defaults on detail level
|
||||
style = kwargs.pop('style', default_style)
|
||||
|
||||
ic = InquirerControl(choices)
|
||||
|
||||
def get_prompt_tokens(cli):
|
||||
tokens = []
|
||||
T = Token
|
||||
|
||||
tokens.append((T.QuestionMark, qmark))
|
||||
tokens.append((T.Question, ' %s ' % message))
|
||||
if ic.answered:
|
||||
tokens.append((T.Answer, ' %s' % ic.get_selected_value()))
|
||||
return tokens
|
||||
|
||||
# assemble layout
|
||||
layout = HSplit([
|
||||
Window(height=D.exact(1),
|
||||
content=TokenListControl(get_prompt_tokens)
|
||||
),
|
||||
ConditionalContainer(
|
||||
Window(ic),
|
||||
filter=~IsDone()
|
||||
)
|
||||
])
|
||||
|
||||
# key bindings
|
||||
manager = KeyBindingManager.for_prompt()
|
||||
|
||||
@manager.registry.add_binding(Keys.ControlQ, eager=True)
|
||||
@manager.registry.add_binding(Keys.ControlC, eager=True)
|
||||
def _(event):
|
||||
raise KeyboardInterrupt()
|
||||
|
||||
# add key bindings for choices
|
||||
for i, c in enumerate(ic.choices):
|
||||
if not isinstance(c, Separator):
|
||||
def _reg_binding(i, keys):
|
||||
# trick out late evaluation with a "function factory":
|
||||
# http://stackoverflow.com/questions/3431676/creating-functions-in-a-loop
|
||||
@manager.registry.add_binding(keys, eager=True)
|
||||
def select_choice(event):
|
||||
ic.pointer_index = i
|
||||
_reg_binding(i, '%d' % c[0])
|
||||
|
||||
@manager.registry.add_binding(Keys.Enter, eager=True)
|
||||
def set_answer(event):
|
||||
ic.answered = True
|
||||
event.cli.set_return_value(ic.get_selected_value())
|
||||
|
||||
return Application(
|
||||
layout=layout,
|
||||
key_bindings_registry=manager.registry,
|
||||
mouse_support=True,
|
||||
style=style
|
||||
)
|
15
src/libs/PyInquirer/separator.py
Normal file
15
src/libs/PyInquirer/separator.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Used to space/separate choices group
|
||||
"""
|
||||
|
||||
|
||||
class Separator(object):
|
||||
line = '-' * 15
|
||||
|
||||
def __init__(self, line=None):
|
||||
if line:
|
||||
self.line = line
|
||||
|
||||
def __str__(self):
|
||||
return self.line
|
35
src/libs/PyInquirer/utils.py
Normal file
35
src/libs/PyInquirer/utils.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function
|
||||
import json
|
||||
import sys
|
||||
from pprint import pprint
|
||||
|
||||
from pygments import highlight, lexers, formatters
|
||||
|
||||
__version__ = '0.1.2'
|
||||
|
||||
PY3 = sys.version_info[0] >= 3
|
||||
|
||||
|
||||
def format_json(data):
|
||||
return json.dumps(data, sort_keys=True, indent=4)
|
||||
|
||||
|
||||
def colorize_json(data):
|
||||
if PY3:
|
||||
if isinstance(data, bytes):
|
||||
data = data.decode('UTF-8')
|
||||
else:
|
||||
if not isinstance(data, unicode):
|
||||
data = unicode(data, 'UTF-8')
|
||||
colorful_json = highlight(data,
|
||||
lexers.JsonLexer(),
|
||||
formatters.TerminalFormatter())
|
||||
return colorful_json
|
||||
|
||||
|
||||
def print_json(data):
|
||||
#colorful_json = highlight(unicode(format_json(data), 'UTF-8'),
|
||||
# lexers.JsonLexer(),
|
||||
# formatters.TerminalFormatter())
|
||||
pprint(colorize_json(format_json(data)))
|
Reference in New Issue
Block a user