r"""
Command line user interface
This module provides :class:`CmdLineInterface`, an implementation of a
:class:`sage.dev.user_interface.UserInterface` on the command line.
AUTHORS:
- David Roe, Julian Rueth: initial version
"""
from __future__ import print_function
from subprocess import check_call, CalledProcessError
from getpass import getpass
import os
import textwrap
import itertools
from user_interface import UserInterface
from user_interface import ERROR, WARNING, NORMAL, INFO, DEBUG
try:
from sage.doctest import DOCTEST_MODE
except ImportError:
DOCTEST_MODE = False
class CmdLineInterface(UserInterface):
r"""
An implementation of a :class:`sage.dev.user_interface.UserInterface` on
the command line.
EXAMPLES::
sage: from sage.dev.test.config import DoctestConfig
sage: from sage.dev.cmd_line_interface import CmdLineInterface
sage: CmdLineInterface(DoctestConfig()["UI"])
CmdLineInterface()
"""
def __repr__(self):
r"""
Return a printable representation of this object.
EXAMPLES::
sage: from sage.dev.test.config import DoctestConfig
sage: from sage.dev.cmd_line_interface import CmdLineInterface
sage: CmdLineInterface(DoctestConfig()["UI"])
CmdLineInterface()
"""
return "CmdLineInterface()"
def _std_values(self, prompt, options, default):
r"""
Helper method to generate prompts.
TESTS::
sage: from sage.dev.test.config import DoctestConfig
sage: from sage.dev.cmd_line_interface import CmdLineInterface
sage: UI = CmdLineInterface(DoctestConfig()["UI"])
sage: UI._std_values("Should I delete your home directory?",
....: ("yes", "no", "maybe"), default=1)
('Should I delete your home directory? [yes/No/maybe] ', ('yes', 'no', 'maybe'), 'no')
"""
if options is not None:
options = tuple(opt.lower() for opt in options)
if options:
prompt += " ["
prompt += "/".join(opt if i != default else opt.capitalize()
for i, opt in enumerate(options))
prompt += "]"
if default is not None:
default = options[default]
else:
options = None
prompt = self._color_code('prompt') + prompt + self._color_code() + " "
return prompt, options, default
def _get_input(self, prompt, options=None, default=None, input_func=raw_input):
r"""
Helper method for :meth:`switch`, :meth:`get_input`, and :meth:`get_password`.
INPUT:
- ``prompt`` -- a string
- ``options`` -- a list of strings or ``None`` (default: ``None``)
- ``default`` -- an integer or ``None`` (deault: ``None``), the default
option as an index into ``options``
- ``input_func`` -- a function to get input from user (default:
``raw_input``)
TESTS::
sage: from sage.dev.test.config import DoctestConfig
sage: from sage.dev.cmd_line_interface import CmdLineInterface
sage: UI = CmdLineInterface(DoctestConfig()["UI"])
sage: def input_func(prompt):
....: print(prompt)
....: return "no"
sage: UI._get_input("Should I delete your home directory?",
....: ("yes","no","maybe"), default=0, input_func = input_func)
Should I delete your home directory? [Yes/no/maybe]
'no'
"""
try:
prompt, options, default = self._std_values(prompt, options, default)
while True:
s = input_func(prompt)
if options is None:
return s
if len(s.strip()) == 0:
if default is None:
self.show("Please enter an option.")
continue
else:
return default
itr = (opt for opt in options if opt.lower().startswith(s.lower()))
try:
ret = itr.next()
except StopIteration:
self.show("Please specify an allowable option.")
continue
try:
ret = itr.next()
self.show("Please disambiguate between options.")
except StopIteration:
return ret
except KeyboardInterrupt:
from user_interface_error import OperationCancelledError
raise OperationCancelledError("cancelled by keyboard interrupt")
def select(self, prompt, options, default=None):
r"""
Ask user to select from ``options`` and return selected.
INPUT:
- ``prompt`` -- a string, the prompt to display
- ``options`` -- an iterable that contains the options
- ``default`` -- an integer or ``None`` (default: ``None``), the
default option as an index into ``options``
.. SEEALSO::
:meth:`sage.dev.user_interface.UserInterface.select`
TESTS::
sage: from sage.dev.test.config import DoctestConfig
sage: from sage.dev.cmd_line_interface import CmdLineInterface
sage: UI = CmdLineInterface(DoctestConfig()["UI"])
sage: UI.select("Should I delete your home directory?", # not tested
....: ("yes", "no", "maybe"), default=2)
Should I delete your home directory? [yes/no/Maybe] m
'maybe'
sage: from sage.dev.test.user_interface import DoctestUserInterface
sage: UI = DoctestUserInterface(DoctestConfig()["UI"])
sage: UI.append("n")
sage: UI.select("Should I delete your home directory?", # indirect doctest
....: ("yes","no","maybe"), default=2)
Should I delete your home directory? [yes/no/Maybe] n
'no'
"""
return self._get_input(prompt, options, default)
def get_input(self, prompt):
r"""
Prompt for input.
INPUT:
- ``prompt`` -- a string, the prompt to display
.. SEEALSO::
:meth:`sage.dev.user_interface.UserInterface.get_input`
TESTS::
sage: from sage.dev.test.config import DoctestConfig
sage: from sage.dev.cmd_line_interface import CmdLineInterface
sage: UI = CmdLineInterface(DoctestConfig()["UI"])
sage: UI.get_input("What do you want for dinner?") # not tested
What do you want for dinner? filet mignon
'filet mignon'
sage: from sage.dev.test.user_interface import DoctestUserInterface
sage: UI = DoctestUserInterface(DoctestConfig()["UI"])
sage: UI.append("filet mignon")
sage: UI.get_input("What do you want for dinner?") # indirect doctest
What do you want for dinner? filet mignon
'filet mignon'
"""
return self._get_input(prompt)
def get_password(self, prompt):
r"""
Prompt for password.
INPUT:
- ``prompt`` -- a string, the prompt to display
.. SEEALSO::
:meth:`sage.dev.user_interface.UserInterface.get_password`
TESTS::
sage: from sage.dev.test.config import DoctestConfig
sage: from sage.dev.cmd_line_interface import CmdLineInterface
sage: UI = CmdLineInterface(DoctestConfig()["UI"])
sage: UI.get_password("What is the key combo for your safe?") # not tested
What is the key combo for your safe?
'9247'
sage: from sage.dev.test.user_interface import DoctestUserInterface
sage: UI = DoctestUserInterface(DoctestConfig()["UI"])
sage: UI.append('9247')
sage: UI.get_password("What is the key combo for your safe?") # indirect doctest
What is the key combo for your safe?
'9247'
"""
return self._get_input(prompt, input_func=getpass)
def _get_dimensions(self):
r"""
Return the dimensions of the terminal.
OUTPUT:
A pair of ints, the number of rows and columns of the terminal.
TESTS::
sage: from sage.dev.test.config import DoctestConfig
sage: from sage.dev.cmd_line_interface import CmdLineInterface
sage: UI = CmdLineInterface(DoctestConfig()["UI"])
sage: UI._get_dimensions()
(25, 80)
"""
dim = self._ioctl_GWINSZ(0) or self._ioctl_GWINSZ(1) or self._ioctl_GWINSZ(2)
if dim is None:
fd = os.open(os.ctermid(), os.O_RDONLY)
try:
dim = self._ioctl_GWINSZ(fd)
finally:
os.close(fd)
if dim is None:
ret = (0,)
else:
ret = tuple(int(x) for x in dim)
if all(ret):
return ret
else:
return (25, 80)
def _ioctl_GWINSZ(self, fd):
r"""
Return the window size of the terminal at the file descriptor ``fd``.
OUTPUT:
A pair of ints, the terminal's rows and columns. If the size could not
be determined, returns ``None``.
TESTS::
sage: import os
sage: from sage.dev.test.config import DoctestConfig
sage: from sage.dev.cmd_line_interface import CmdLineInterface
sage: CmdLineInterface(DoctestConfig()["UI"])._ioctl_GWINSZ(0)
(25, 80)
"""
if DOCTEST_MODE:
return (25, 80)
try:
import struct
import fcntl
import termios
except ImportError:
return None
try:
return struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
except IOError:
return None
def _color_code(self, color=None):
"""
Return an ansi color code.
INPUT:
- ``color`` -- ``None``, ``'prompt'``, or one of the constants
``ERROR``, ``WARNING``, ``NORMAL``, ``INFO``, or ``DEBUG``.
OUTPUT:
String, possibly containing a color code.
"""
if DOCTEST_MODE:
return ''
if color is None:
return '\033[0m'
elif color == ERROR:
return '\033[0;31m'
elif color == WARNING:
return '\033[0;33m'
elif color == INFO or color == DEBUG:
return '\033[0;36m'
elif color == 'prompt':
return '\033[0;34m'
else:
return ''
def _show(self, message, log_level, *args):
r"""
Display ``message``.
.. SEEALSO::
:meth:`sage.dev.user_interface.UserInterface.show`
TESTS::
sage: from sage.dev.test.config import DoctestConfig
sage: from sage.dev.cmd_line_interface import CmdLineInterface
sage: UI = CmdLineInterface(DoctestConfig()["UI"])
sage: UI.info("I ate {0} for dinner, a whole {1} for dessert, and then took a nice walk around the lake.", 'filet mignon', 'apple pie') # indirect doctest
# I ate filet mignon for dinner, a whole apple pie for dessert, and then took a
# nice walk around the lake.
"""
if len(args) > 0:
message = message.format(*args)
height, width = self._get_dimensions()
kwds = {'width': width}
if log_level == INFO:
kwds['initial_indent'] = kwds['subsequent_indent'] = '# '
wrap = textwrap.TextWrapper(**kwds).wrap
message = list(itertools.chain.from_iterable(
wrap(line) for line in message.splitlines()))
if len(message) <= height:
print(self._color_code(log_level) +
'\n'.join(message) +
self._color_code())
else:
message = '\n'.join(message)+'\n'
try:
self._pager(message)
except AttributeError:
import pydoc
self._pager = pydoc.getpager()
self._pager(message)
def edit(self, filename):
r"""
Drop user into editor with filename open.
.. SEEALSO::
:meth:`sage.dev.user_interface.UserInterface.edit`
TESTS::
sage: tmp = tmp_filename()
sage: from sage.dev.test.config import DoctestConfig
sage: from sage.dev.cmd_line_interface import CmdLineInterface
sage: UI = CmdLineInterface(DoctestConfig()["UI"])
sage: UI.edit(tmp) # not tested
sage: print open(tmp,'r').read() # not tested
Some
lines
<BLANKLINE>
sage: os.unlink(tmp)
"""
try:
editor = os.environ.get('EDITOR', 'nano')
check_call(['sage-native-execute', editor, filename])
except CalledProcessError:
from user_interface_error import OperationCancelledError
raise OperationCancelledError("Editor returned non-zero exit value")