Shellmen/src/libs/PyInquirer/prompts/editor.py

198 lines
6.5 KiB
Python

# -*- 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
)