Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/dev/cmd_line_interface.py
8815 views
1
r"""
2
Command line user interface
3
4
This module provides :class:`CmdLineInterface`, an implementation of a
5
:class:`sage.dev.user_interface.UserInterface` on the command line.
6
7
AUTHORS:
8
9
- David Roe, Julian Rueth: initial version
10
11
"""
12
#*****************************************************************************
13
# Copyright (C) 2013 David Roe <[email protected]>
14
# Julian Rueth <[email protected]>
15
#
16
# Distributed under the terms of the GNU General Public License (GPL)
17
# as published by the Free Software Foundation; either version 2 of
18
# the License, or (at your option) any later version.
19
# http://www.gnu.org/licenses/
20
#*****************************************************************************
21
22
from __future__ import print_function
23
from subprocess import check_call, CalledProcessError
24
from getpass import getpass
25
26
import os
27
import textwrap
28
import itertools
29
30
from user_interface import UserInterface
31
from user_interface import ERROR, WARNING, NORMAL, INFO, DEBUG
32
33
try:
34
from sage.doctest import DOCTEST_MODE
35
except ImportError:
36
DOCTEST_MODE = False
37
38
39
class CmdLineInterface(UserInterface):
40
r"""
41
An implementation of a :class:`sage.dev.user_interface.UserInterface` on
42
the command line.
43
44
EXAMPLES::
45
46
sage: from sage.dev.test.config import DoctestConfig
47
sage: from sage.dev.cmd_line_interface import CmdLineInterface
48
sage: CmdLineInterface(DoctestConfig()["UI"])
49
CmdLineInterface()
50
"""
51
def __repr__(self):
52
r"""
53
Return a printable representation of this object.
54
55
EXAMPLES::
56
57
sage: from sage.dev.test.config import DoctestConfig
58
sage: from sage.dev.cmd_line_interface import CmdLineInterface
59
sage: CmdLineInterface(DoctestConfig()["UI"])
60
CmdLineInterface()
61
"""
62
return "CmdLineInterface()"
63
64
def _std_values(self, prompt, options, default):
65
r"""
66
Helper method to generate prompts.
67
68
TESTS::
69
70
sage: from sage.dev.test.config import DoctestConfig
71
sage: from sage.dev.cmd_line_interface import CmdLineInterface
72
sage: UI = CmdLineInterface(DoctestConfig()["UI"])
73
sage: UI._std_values("Should I delete your home directory?",
74
....: ("yes", "no", "maybe"), default=1)
75
('Should I delete your home directory? [yes/No/maybe] ', ('yes', 'no', 'maybe'), 'no')
76
"""
77
if options is not None:
78
options = tuple(opt.lower() for opt in options)
79
if options:
80
prompt += " ["
81
prompt += "/".join(opt if i != default else opt.capitalize()
82
for i, opt in enumerate(options))
83
prompt += "]"
84
if default is not None:
85
default = options[default]
86
else:
87
options = None
88
prompt = self._color_code('prompt') + prompt + self._color_code() + " "
89
return prompt, options, default
90
91
def _get_input(self, prompt, options=None, default=None, input_func=raw_input):
92
r"""
93
Helper method for :meth:`switch`, :meth:`get_input`, and :meth:`get_password`.
94
95
INPUT:
96
97
- ``prompt`` -- a string
98
99
- ``options`` -- a list of strings or ``None`` (default: ``None``)
100
101
- ``default`` -- an integer or ``None`` (deault: ``None``), the default
102
option as an index into ``options``
103
104
- ``input_func`` -- a function to get input from user (default:
105
``raw_input``)
106
107
TESTS::
108
109
sage: from sage.dev.test.config import DoctestConfig
110
sage: from sage.dev.cmd_line_interface import CmdLineInterface
111
sage: UI = CmdLineInterface(DoctestConfig()["UI"])
112
sage: def input_func(prompt):
113
....: print(prompt)
114
....: return "no"
115
sage: UI._get_input("Should I delete your home directory?",
116
....: ("yes","no","maybe"), default=0, input_func = input_func)
117
Should I delete your home directory? [Yes/no/maybe]
118
'no'
119
"""
120
try:
121
prompt, options, default = self._std_values(prompt, options, default)
122
while True:
123
s = input_func(prompt)
124
if options is None:
125
return s
126
if len(s.strip()) == 0:
127
if default is None:
128
self.show("Please enter an option.")
129
continue
130
else:
131
return default
132
133
itr = (opt for opt in options if opt.lower().startswith(s.lower()))
134
try:
135
ret = itr.next()
136
except StopIteration:
137
self.show("Please specify an allowable option.")
138
continue
139
140
try:
141
ret = itr.next()
142
self.show("Please disambiguate between options.")
143
except StopIteration:
144
return ret
145
except KeyboardInterrupt:
146
from user_interface_error import OperationCancelledError
147
raise OperationCancelledError("cancelled by keyboard interrupt")
148
149
def select(self, prompt, options, default=None):
150
r"""
151
Ask user to select from ``options`` and return selected.
152
153
INPUT:
154
155
- ``prompt`` -- a string, the prompt to display
156
157
- ``options`` -- an iterable that contains the options
158
159
- ``default`` -- an integer or ``None`` (default: ``None``), the
160
default option as an index into ``options``
161
162
.. SEEALSO::
163
164
:meth:`sage.dev.user_interface.UserInterface.select`
165
166
TESTS::
167
168
sage: from sage.dev.test.config import DoctestConfig
169
sage: from sage.dev.cmd_line_interface import CmdLineInterface
170
sage: UI = CmdLineInterface(DoctestConfig()["UI"])
171
sage: UI.select("Should I delete your home directory?", # not tested
172
....: ("yes", "no", "maybe"), default=2)
173
Should I delete your home directory? [yes/no/Maybe] m
174
'maybe'
175
176
sage: from sage.dev.test.user_interface import DoctestUserInterface
177
sage: UI = DoctestUserInterface(DoctestConfig()["UI"])
178
sage: UI.append("n")
179
sage: UI.select("Should I delete your home directory?", # indirect doctest
180
....: ("yes","no","maybe"), default=2)
181
Should I delete your home directory? [yes/no/Maybe] n
182
'no'
183
"""
184
return self._get_input(prompt, options, default)
185
186
def get_input(self, prompt):
187
r"""
188
Prompt for input.
189
190
INPUT:
191
192
- ``prompt`` -- a string, the prompt to display
193
194
.. SEEALSO::
195
196
:meth:`sage.dev.user_interface.UserInterface.get_input`
197
198
TESTS::
199
200
sage: from sage.dev.test.config import DoctestConfig
201
sage: from sage.dev.cmd_line_interface import CmdLineInterface
202
sage: UI = CmdLineInterface(DoctestConfig()["UI"])
203
sage: UI.get_input("What do you want for dinner?") # not tested
204
What do you want for dinner? filet mignon
205
'filet mignon'
206
207
sage: from sage.dev.test.user_interface import DoctestUserInterface
208
sage: UI = DoctestUserInterface(DoctestConfig()["UI"])
209
sage: UI.append("filet mignon")
210
sage: UI.get_input("What do you want for dinner?") # indirect doctest
211
What do you want for dinner? filet mignon
212
'filet mignon'
213
"""
214
return self._get_input(prompt)
215
216
def get_password(self, prompt):
217
r"""
218
Prompt for password.
219
220
INPUT:
221
222
- ``prompt`` -- a string, the prompt to display
223
224
.. SEEALSO::
225
226
:meth:`sage.dev.user_interface.UserInterface.get_password`
227
228
TESTS::
229
230
sage: from sage.dev.test.config import DoctestConfig
231
sage: from sage.dev.cmd_line_interface import CmdLineInterface
232
sage: UI = CmdLineInterface(DoctestConfig()["UI"])
233
sage: UI.get_password("What is the key combo for your safe?") # not tested
234
What is the key combo for your safe?
235
'9247'
236
237
sage: from sage.dev.test.user_interface import DoctestUserInterface
238
sage: UI = DoctestUserInterface(DoctestConfig()["UI"])
239
sage: UI.append('9247')
240
sage: UI.get_password("What is the key combo for your safe?") # indirect doctest
241
What is the key combo for your safe?
242
'9247'
243
"""
244
return self._get_input(prompt, input_func=getpass)
245
246
def _get_dimensions(self):
247
r"""
248
Return the dimensions of the terminal.
249
250
OUTPUT:
251
252
A pair of ints, the number of rows and columns of the terminal.
253
254
TESTS::
255
256
sage: from sage.dev.test.config import DoctestConfig
257
sage: from sage.dev.cmd_line_interface import CmdLineInterface
258
sage: UI = CmdLineInterface(DoctestConfig()["UI"])
259
sage: UI._get_dimensions()
260
(25, 80)
261
"""
262
dim = self._ioctl_GWINSZ(0) or self._ioctl_GWINSZ(1) or self._ioctl_GWINSZ(2)
263
if dim is None:
264
fd = os.open(os.ctermid(), os.O_RDONLY)
265
try:
266
dim = self._ioctl_GWINSZ(fd)
267
finally:
268
os.close(fd)
269
if dim is None:
270
ret = (0,)
271
else:
272
ret = tuple(int(x) for x in dim)
273
if all(ret):
274
return ret
275
else:
276
# fallback values
277
return (25, 80)
278
279
def _ioctl_GWINSZ(self, fd):
280
r"""
281
Return the window size of the terminal at the file descriptor ``fd``.
282
283
OUTPUT:
284
285
A pair of ints, the terminal's rows and columns. If the size could not
286
be determined, returns ``None``.
287
288
TESTS::
289
290
sage: import os
291
sage: from sage.dev.test.config import DoctestConfig
292
sage: from sage.dev.cmd_line_interface import CmdLineInterface
293
sage: CmdLineInterface(DoctestConfig()["UI"])._ioctl_GWINSZ(0)
294
(25, 80)
295
"""
296
if DOCTEST_MODE:
297
return (25, 80)
298
try:
299
import struct
300
import fcntl
301
import termios
302
except ImportError:
303
return None
304
305
try:
306
return struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
307
except IOError:
308
return None
309
310
def _color_code(self, color=None):
311
"""
312
Return an ansi color code.
313
314
INPUT:
315
316
- ``color`` -- ``None``, ``'prompt'``, or one of the constants
317
``ERROR``, ``WARNING``, ``NORMAL``, ``INFO``, or ``DEBUG``.
318
319
OUTPUT:
320
321
String, possibly containing a color code.
322
"""
323
if DOCTEST_MODE:
324
return ''
325
if color is None:
326
return '\033[0m'
327
elif color == ERROR:
328
return '\033[0;31m'
329
elif color == WARNING:
330
return '\033[0;33m'
331
elif color == INFO or color == DEBUG:
332
return '\033[0;36m'
333
elif color == 'prompt':
334
return '\033[0;34m'
335
else:
336
return ''
337
338
def _show(self, message, log_level, *args):
339
r"""
340
Display ``message``.
341
342
.. SEEALSO::
343
344
:meth:`sage.dev.user_interface.UserInterface.show`
345
346
TESTS::
347
348
sage: from sage.dev.test.config import DoctestConfig
349
sage: from sage.dev.cmd_line_interface import CmdLineInterface
350
sage: UI = CmdLineInterface(DoctestConfig()["UI"])
351
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
352
# I ate filet mignon for dinner, a whole apple pie for dessert, and then took a
353
# nice walk around the lake.
354
"""
355
if len(args) > 0:
356
message = message.format(*args)
357
height, width = self._get_dimensions()
358
kwds = {'width': width}
359
if log_level == INFO:
360
kwds['initial_indent'] = kwds['subsequent_indent'] = '# '
361
362
wrap = textwrap.TextWrapper(**kwds).wrap
363
message = list(itertools.chain.from_iterable(
364
wrap(line) for line in message.splitlines()))
365
366
if len(message) <= height:
367
print(self._color_code(log_level) +
368
'\n'.join(message) +
369
self._color_code())
370
else:
371
message = '\n'.join(message)+'\n'
372
try:
373
self._pager(message)
374
except AttributeError:
375
import pydoc
376
self._pager = pydoc.getpager()
377
self._pager(message)
378
379
def edit(self, filename):
380
r"""
381
Drop user into editor with filename open.
382
383
.. SEEALSO::
384
385
:meth:`sage.dev.user_interface.UserInterface.edit`
386
387
TESTS::
388
389
sage: tmp = tmp_filename()
390
sage: from sage.dev.test.config import DoctestConfig
391
sage: from sage.dev.cmd_line_interface import CmdLineInterface
392
sage: UI = CmdLineInterface(DoctestConfig()["UI"])
393
sage: UI.edit(tmp) # not tested
394
sage: print open(tmp,'r').read() # not tested
395
Some
396
lines
397
<BLANKLINE>
398
sage: os.unlink(tmp)
399
"""
400
try:
401
editor = os.environ.get('EDITOR', 'nano')
402
check_call(['sage-native-execute', editor, filename])
403
except CalledProcessError:
404
from user_interface_error import OperationCancelledError
405
raise OperationCancelledError("Editor returned non-zero exit value")
406
407