Show More
@@ -1,412 +1,26 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """Classes for handling input/output prompts.""" |
|
2 | """Being removed | |
|
3 | """ | |||
3 |
|
4 | |||
4 | # Copyright (c) 2001-2007 Fernando Perez <fperez@colorado.edu> |
|
5 | from IPython.utils import py3compat | |
5 | # Copyright (c) IPython Development Team. |
|
|||
6 | # Distributed under the terms of the Modified BSD License. |
|
|||
7 |
|
||||
8 | import os |
|
|||
9 | import re |
|
|||
10 | import socket |
|
|||
11 | import sys |
|
|||
12 | import time |
|
|||
13 |
|
||||
14 | from string import Formatter |
|
|||
15 |
|
||||
16 | from traitlets.config.configurable import Configurable |
|
|||
17 | from IPython.core import release |
|
|||
18 | from IPython.utils import coloransi, py3compat |
|
|||
19 | from traitlets import Unicode, Instance, Dict, Bool, Int, observe, default |
|
|||
20 |
|
||||
21 | from IPython.utils.PyColorize import LightBGColors, LinuxColors, NoColor |
|
|||
22 |
|
||||
23 | #----------------------------------------------------------------------------- |
|
|||
24 | # Color schemes for prompts |
|
|||
25 | #----------------------------------------------------------------------------- |
|
|||
26 |
|
||||
27 | InputColors = coloransi.InputTermColors # just a shorthand |
|
|||
28 | Colors = coloransi.TermColors # just a shorthand |
|
|||
29 |
|
||||
30 | color_lists = dict(normal=Colors(), inp=InputColors(), nocolor=coloransi.NoColors()) |
|
|||
31 |
|
||||
32 | #----------------------------------------------------------------------------- |
|
|||
33 | # Utilities |
|
|||
34 | #----------------------------------------------------------------------------- |
|
|||
35 |
|
6 | |||
36 | class LazyEvaluate(object): |
|
7 | class LazyEvaluate(object): | |
37 | """This is used for formatting strings with values that need to be updated |
|
8 | """This is used for formatting strings with values that need to be updated | |
38 | at that time, such as the current time or working directory.""" |
|
9 | at that time, such as the current time or working directory.""" | |
39 | def __init__(self, func, *args, **kwargs): |
|
10 | def __init__(self, func, *args, **kwargs): | |
40 | self.func = func |
|
11 | self.func = func | |
41 | self.args = args |
|
12 | self.args = args | |
42 | self.kwargs = kwargs |
|
13 | self.kwargs = kwargs | |
43 |
|
14 | |||
44 | def __call__(self, **kwargs): |
|
15 | def __call__(self, **kwargs): | |
45 | self.kwargs.update(kwargs) |
|
16 | self.kwargs.update(kwargs) | |
46 | return self.func(*self.args, **self.kwargs) |
|
17 | return self.func(*self.args, **self.kwargs) | |
47 |
|
18 | |||
48 | def __str__(self): |
|
19 | def __str__(self): | |
49 | return str(self()) |
|
20 | return str(self()) | |
50 |
|
21 | |||
51 | def __unicode__(self): |
|
22 | def __unicode__(self): | |
52 | return py3compat.unicode_type(self()) |
|
23 | return py3compat.unicode_type(self()) | |
53 |
|
24 | |||
54 | def __format__(self, format_spec): |
|
25 | def __format__(self, format_spec): | |
55 | return format(self(), format_spec) |
|
26 | return format(self(), format_spec) | |
56 |
|
||||
57 | def multiple_replace(dict, text): |
|
|||
58 | """ Replace in 'text' all occurrences of any key in the given |
|
|||
59 | dictionary by its corresponding value. Returns the new string.""" |
|
|||
60 |
|
||||
61 | # Function by Xavier Defrang, originally found at: |
|
|||
62 | # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330 |
|
|||
63 |
|
||||
64 | # Create a regular expression from the dictionary keys |
|
|||
65 | regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys()))) |
|
|||
66 | # For each match, look-up corresponding value in dictionary |
|
|||
67 | return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text) |
|
|||
68 |
|
||||
69 | #----------------------------------------------------------------------------- |
|
|||
70 | # Special characters that can be used in prompt templates, mainly bash-like |
|
|||
71 | #----------------------------------------------------------------------------- |
|
|||
72 |
|
||||
73 | # If $HOME isn't defined (Windows), make it an absurd string so that it can |
|
|||
74 | # never be expanded out into '~'. Basically anything which can never be a |
|
|||
75 | # reasonable directory name will do, we just want the $HOME -> '~' operation |
|
|||
76 | # to become a no-op. We pre-compute $HOME here so it's not done on every |
|
|||
77 | # prompt call. |
|
|||
78 |
|
||||
79 | # FIXME: |
|
|||
80 |
|
||||
81 | # - This should be turned into a class which does proper namespace management, |
|
|||
82 | # since the prompt specials need to be evaluated in a certain namespace. |
|
|||
83 | # Currently it's just globals, which need to be managed manually by code |
|
|||
84 | # below. |
|
|||
85 |
|
||||
86 | # - I also need to split up the color schemes from the prompt specials |
|
|||
87 | # somehow. I don't have a clean design for that quite yet. |
|
|||
88 |
|
||||
89 | HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~")) |
|
|||
90 |
|
||||
91 | # This is needed on FreeBSD, and maybe other systems which symlink /home to |
|
|||
92 | # /usr/home, but retain the $HOME variable as pointing to /home |
|
|||
93 | HOME = os.path.realpath(HOME) |
|
|||
94 |
|
||||
95 | # We precompute a few more strings here for the prompt_specials, which are |
|
|||
96 | # fixed once ipython starts. This reduces the runtime overhead of computing |
|
|||
97 | # prompt strings. |
|
|||
98 | USER = py3compat.str_to_unicode(os.environ.get("USER",'')) |
|
|||
99 | HOSTNAME = py3compat.str_to_unicode(socket.gethostname()) |
|
|||
100 | HOSTNAME_SHORT = HOSTNAME.split(".")[0] |
|
|||
101 |
|
||||
102 | # IronPython doesn't currently have os.getuid() even if |
|
|||
103 | # os.name == 'posix'; 2/8/2014 |
|
|||
104 | ROOT_SYMBOL = "#" if (os.name=='nt' or sys.platform=='cli' or os.getuid()==0) else "$" |
|
|||
105 |
|
||||
106 | prompt_abbreviations = { |
|
|||
107 | # Prompt/history count |
|
|||
108 | '%n' : '{color.number}' '{count}' '{color.prompt}', |
|
|||
109 | r'\#': '{color.number}' '{count}' '{color.prompt}', |
|
|||
110 | # Just the prompt counter number, WITHOUT any coloring wrappers, so users |
|
|||
111 | # can get numbers displayed in whatever color they want. |
|
|||
112 | r'\N': '{count}', |
|
|||
113 |
|
||||
114 | # Prompt/history count, with the actual digits replaced by dots or |
|
|||
115 | # spaces. Used mainly in continuation prompts (prompt_in2). |
|
|||
116 | r'\D': '{dots}', |
|
|||
117 | r'\S': '{spaces}', |
|
|||
118 |
|
||||
119 | # Current time |
|
|||
120 | r'\T' : '{time}', |
|
|||
121 | # Current working directory |
|
|||
122 | r'\w': '{cwd}', |
|
|||
123 | # Basename of current working directory. |
|
|||
124 | # (use os.sep to make this portable across OSes) |
|
|||
125 | r'\W' : '{cwd_last}', |
|
|||
126 | # These X<N> are an extension to the normal bash prompts. They return |
|
|||
127 | # N terms of the path, after replacing $HOME with '~' |
|
|||
128 | r'\X0': '{cwd_x[0]}', |
|
|||
129 | r'\X1': '{cwd_x[1]}', |
|
|||
130 | r'\X2': '{cwd_x[2]}', |
|
|||
131 | r'\X3': '{cwd_x[3]}', |
|
|||
132 | r'\X4': '{cwd_x[4]}', |
|
|||
133 | r'\X5': '{cwd_x[5]}', |
|
|||
134 | # Y<N> are similar to X<N>, but they show '~' if it's the directory |
|
|||
135 | # N+1 in the list. Somewhat like %cN in tcsh. |
|
|||
136 | r'\Y0': '{cwd_y[0]}', |
|
|||
137 | r'\Y1': '{cwd_y[1]}', |
|
|||
138 | r'\Y2': '{cwd_y[2]}', |
|
|||
139 | r'\Y3': '{cwd_y[3]}', |
|
|||
140 | r'\Y4': '{cwd_y[4]}', |
|
|||
141 | r'\Y5': '{cwd_y[5]}', |
|
|||
142 | # Hostname up to first . |
|
|||
143 | r'\h': HOSTNAME_SHORT, |
|
|||
144 | # Full hostname |
|
|||
145 | r'\H': HOSTNAME, |
|
|||
146 | # Username of current user |
|
|||
147 | r'\u': USER, |
|
|||
148 | # Escaped '\' |
|
|||
149 | '\\\\': '\\', |
|
|||
150 | # Newline |
|
|||
151 | r'\n': '\n', |
|
|||
152 | # Carriage return |
|
|||
153 | r'\r': '\r', |
|
|||
154 | # Release version |
|
|||
155 | r'\v': release.version, |
|
|||
156 | # Root symbol ($ or #) |
|
|||
157 | r'\$': ROOT_SYMBOL, |
|
|||
158 | } |
|
|||
159 |
|
||||
160 | #----------------------------------------------------------------------------- |
|
|||
161 | # More utilities |
|
|||
162 | #----------------------------------------------------------------------------- |
|
|||
163 |
|
||||
164 | def cwd_filt(depth): |
|
|||
165 | """Return the last depth elements of the current working directory. |
|
|||
166 |
|
||||
167 | $HOME is always replaced with '~'. |
|
|||
168 | If depth==0, the full path is returned.""" |
|
|||
169 |
|
||||
170 | cwd = py3compat.getcwd().replace(HOME,"~") |
|
|||
171 | out = os.sep.join(cwd.split(os.sep)[-depth:]) |
|
|||
172 | return out or os.sep |
|
|||
173 |
|
||||
174 | def cwd_filt2(depth): |
|
|||
175 | """Return the last depth elements of the current working directory. |
|
|||
176 |
|
||||
177 | $HOME is always replaced with '~'. |
|
|||
178 | If depth==0, the full path is returned.""" |
|
|||
179 |
|
||||
180 | full_cwd = py3compat.getcwd() |
|
|||
181 | cwd = full_cwd.replace(HOME,"~").split(os.sep) |
|
|||
182 | if '~' in cwd and len(cwd) == depth+1: |
|
|||
183 | depth += 1 |
|
|||
184 | drivepart = '' |
|
|||
185 | if sys.platform == 'win32' and len(cwd) > depth: |
|
|||
186 | drivepart = os.path.splitdrive(full_cwd)[0] |
|
|||
187 | out = drivepart + '/'.join(cwd[-depth:]) |
|
|||
188 |
|
||||
189 | return out or os.sep |
|
|||
190 |
|
||||
191 | #----------------------------------------------------------------------------- |
|
|||
192 | # Prompt classes |
|
|||
193 | #----------------------------------------------------------------------------- |
|
|||
194 |
|
||||
195 | lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"), |
|
|||
196 | 'cwd': LazyEvaluate(py3compat.getcwd), |
|
|||
197 | 'cwd_last': LazyEvaluate(lambda: py3compat.getcwd().split(os.sep)[-1]), |
|
|||
198 | 'cwd_x': [LazyEvaluate(lambda: py3compat.getcwd().replace(HOME,"~"))] +\ |
|
|||
199 | [LazyEvaluate(cwd_filt, x) for x in range(1,6)], |
|
|||
200 | 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)] |
|
|||
201 | } |
|
|||
202 |
|
||||
203 | def _lenlastline(s): |
|
|||
204 | """Get the length of the last line. More intelligent than |
|
|||
205 | len(s.splitlines()[-1]). |
|
|||
206 | """ |
|
|||
207 | if not s or s.endswith(('\n', '\r')): |
|
|||
208 | return 0 |
|
|||
209 | return len(s.splitlines()[-1]) |
|
|||
210 |
|
||||
211 |
|
||||
212 | invisible_chars_re = re.compile('\001[^\001\002]*\002') |
|
|||
213 | def _invisible_characters(s): |
|
|||
214 | """ |
|
|||
215 | Get the number of invisible ANSI characters in s. Invisible characters |
|
|||
216 | must be delimited by \001 and \002. |
|
|||
217 | """ |
|
|||
218 | return _lenlastline(s) - _lenlastline(invisible_chars_re.sub('', s)) |
|
|||
219 |
|
||||
220 | class UserNSFormatter(Formatter): |
|
|||
221 | """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution""" |
|
|||
222 | def __init__(self, shell): |
|
|||
223 | self.shell = shell |
|
|||
224 |
|
||||
225 | def get_value(self, key, args, kwargs): |
|
|||
226 | # try regular formatting first: |
|
|||
227 | try: |
|
|||
228 | return Formatter.get_value(self, key, args, kwargs) |
|
|||
229 | except Exception: |
|
|||
230 | pass |
|
|||
231 | # next, look in user_ns and builtins: |
|
|||
232 | for container in (self.shell.user_ns, __builtins__): |
|
|||
233 | if key in container: |
|
|||
234 | return container[key] |
|
|||
235 | # nothing found, put error message in its place |
|
|||
236 | return "<ERROR: '%s' not found>" % key |
|
|||
237 |
|
||||
238 |
|
||||
239 | class PromptManager(Configurable): |
|
|||
240 | """This is the primary interface for producing IPython's prompts.""" |
|
|||
241 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True) |
|
|||
242 |
|
||||
243 | color_scheme_table = Instance(coloransi.ColorSchemeTable, allow_none=True) |
|
|||
244 | color_scheme = Unicode('Linux').tag(config=True) |
|
|||
245 |
|
||||
246 | @observe('color_scheme') |
|
|||
247 | def _color_scheme_changed(self, change): |
|
|||
248 | self.color_scheme_table.set_active_scheme(change['new']) |
|
|||
249 | for pname in ['in', 'in2', 'out', 'rewrite']: |
|
|||
250 | # We need to recalculate the number of invisible characters |
|
|||
251 | self.update_prompt(pname) |
|
|||
252 |
|
||||
253 | lazy_evaluate_fields = Dict(help=""" |
|
|||
254 | This maps field names used in the prompt templates to functions which |
|
|||
255 | will be called when the prompt is rendered. This allows us to include |
|
|||
256 | things like the current time in the prompts. Functions are only called |
|
|||
257 | if they are used in the prompt. |
|
|||
258 | """) |
|
|||
259 |
|
||||
260 | in_template = Unicode('In [\\#]: ', |
|
|||
261 | help="Input prompt. '\\#' will be transformed to the prompt number" |
|
|||
262 | ).tag(config=True) |
|
|||
263 | in2_template = Unicode(' .\\D.: ', |
|
|||
264 | help="Continuation prompt.").tag(config=True) |
|
|||
265 | out_template = Unicode('Out[\\#]: ', |
|
|||
266 | help="Output prompt. '\\#' will be transformed to the prompt number" |
|
|||
267 | ).tag(config=True) |
|
|||
268 |
|
||||
269 | @default('lazy_evaluate_fields') |
|
|||
270 | def _lazy_evaluate_fields_default(self): |
|
|||
271 | return lazily_evaluate.copy() |
|
|||
272 |
|
||||
273 | justify = Bool(True, help=""" |
|
|||
274 | If True (default), each prompt will be right-aligned with the |
|
|||
275 | preceding one. |
|
|||
276 | """).tag(config=True) |
|
|||
277 |
|
||||
278 | # We actually store the expanded templates here: |
|
|||
279 | templates = Dict() |
|
|||
280 |
|
||||
281 | # The number of characters in the last prompt rendered, not including |
|
|||
282 | # colour characters. |
|
|||
283 | width = Int() |
|
|||
284 | txtwidth = Int() # Not including right-justification |
|
|||
285 |
|
||||
286 | # The number of characters in each prompt which don't contribute to width |
|
|||
287 | invisible_chars = Dict() |
|
|||
288 |
|
||||
289 | @default('invisible_chars') |
|
|||
290 | def _invisible_chars_default(self): |
|
|||
291 | return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0} |
|
|||
292 |
|
||||
293 | def __init__(self, shell, **kwargs): |
|
|||
294 | super(PromptManager, self).__init__(shell=shell, **kwargs) |
|
|||
295 |
|
||||
296 | # Prepare colour scheme table |
|
|||
297 | self.color_scheme_table = coloransi.ColorSchemeTable([NoColor, |
|
|||
298 | LinuxColors, LightBGColors], self.color_scheme) |
|
|||
299 |
|
||||
300 | self._formatter = UserNSFormatter(shell) |
|
|||
301 | # Prepare templates & numbers of invisible characters |
|
|||
302 | self.update_prompt('in', self.in_template) |
|
|||
303 | self.update_prompt('in2', self.in2_template) |
|
|||
304 | self.update_prompt('out', self.out_template) |
|
|||
305 | self.update_prompt('rewrite') |
|
|||
306 | self.observe(self._update_prompt_trait, |
|
|||
307 | names=['in_template', 'in2_template', 'out_template']) |
|
|||
308 |
|
||||
309 | def update_prompt(self, name, new_template=None): |
|
|||
310 | """This is called when a prompt template is updated. It processes |
|
|||
311 | abbreviations used in the prompt template (like \#) and calculates how |
|
|||
312 | many invisible characters (ANSI colour escapes) the resulting prompt |
|
|||
313 | contains. |
|
|||
314 |
|
||||
315 | It is also called for each prompt on changing the colour scheme. In both |
|
|||
316 | cases, traitlets should take care of calling this automatically. |
|
|||
317 | """ |
|
|||
318 | if new_template is not None: |
|
|||
319 | self.templates[name] = multiple_replace(prompt_abbreviations, new_template) |
|
|||
320 | # We count invisible characters (colour escapes) on the last line of the |
|
|||
321 | # prompt, to calculate the width for lining up subsequent prompts. |
|
|||
322 | invis_chars = _invisible_characters(self._render(name, color=True)) |
|
|||
323 | self.invisible_chars[name] = invis_chars |
|
|||
324 |
|
||||
325 | def _update_prompt_trait(self, changes): |
|
|||
326 | traitname = changes['name'] |
|
|||
327 | new_template = changes['new'] |
|
|||
328 | name = traitname[:-9] # Cut off '_template' |
|
|||
329 | self.update_prompt(name, new_template) |
|
|||
330 |
|
||||
331 | def _render(self, name, color=True, **kwargs): |
|
|||
332 | """Render but don't justify, or update the width or txtwidth attributes. |
|
|||
333 | """ |
|
|||
334 | if name == 'rewrite': |
|
|||
335 | return self._render_rewrite(color=color) |
|
|||
336 |
|
||||
337 | if color: |
|
|||
338 | scheme = self.color_scheme_table.active_colors |
|
|||
339 | if name=='out': |
|
|||
340 | colors = color_lists['normal'] |
|
|||
341 | colors.number, colors.prompt, colors.normal = \ |
|
|||
342 | scheme.out_number, scheme.out_prompt, scheme.normal |
|
|||
343 | else: |
|
|||
344 | colors = color_lists['inp'] |
|
|||
345 | colors.number, colors.prompt, colors.normal = \ |
|
|||
346 | scheme.in_number, scheme.in_prompt, scheme.in_normal |
|
|||
347 | if name=='in2': |
|
|||
348 | colors.prompt = scheme.in_prompt2 |
|
|||
349 | else: |
|
|||
350 | # No color |
|
|||
351 | colors = color_lists['nocolor'] |
|
|||
352 | colors.number, colors.prompt, colors.normal = '', '', '' |
|
|||
353 |
|
||||
354 | count = self.shell.execution_count # Shorthand |
|
|||
355 | # Build the dictionary to be passed to string formatting |
|
|||
356 | fmtargs = dict(color=colors, count=count, |
|
|||
357 | dots="."*len(str(count)), spaces=" "*len(str(count)), |
|
|||
358 | width=self.width, txtwidth=self.txtwidth) |
|
|||
359 | fmtargs.update(self.lazy_evaluate_fields) |
|
|||
360 | fmtargs.update(kwargs) |
|
|||
361 |
|
||||
362 | # Prepare the prompt |
|
|||
363 | prompt = colors.prompt + self.templates[name] + colors.normal |
|
|||
364 |
|
||||
365 | # Fill in required fields |
|
|||
366 | return self._formatter.format(prompt, **fmtargs) |
|
|||
367 |
|
||||
368 | def _render_rewrite(self, color=True): |
|
|||
369 | """Render the ---> rewrite prompt.""" |
|
|||
370 | if color: |
|
|||
371 | scheme = self.color_scheme_table.active_colors |
|
|||
372 | # We need a non-input version of these escapes |
|
|||
373 | color_prompt = scheme.in_prompt.replace("\001","").replace("\002","") |
|
|||
374 | color_normal = scheme.normal |
|
|||
375 | else: |
|
|||
376 | color_prompt, color_normal = '', '' |
|
|||
377 |
|
||||
378 | return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal |
|
|||
379 |
|
||||
380 | def render(self, name, color=True, just=None, **kwargs): |
|
|||
381 | """ |
|
|||
382 | Render the selected prompt. |
|
|||
383 |
|
||||
384 | Parameters |
|
|||
385 | ---------- |
|
|||
386 | name : str |
|
|||
387 | Which prompt to render. One of 'in', 'in2', 'out', 'rewrite' |
|
|||
388 | color : bool |
|
|||
389 | If True (default), include ANSI escape sequences for a coloured prompt. |
|
|||
390 | just : bool |
|
|||
391 | If True, justify the prompt to the width of the last prompt. The |
|
|||
392 | default is stored in self.justify. |
|
|||
393 | **kwargs : |
|
|||
394 | Additional arguments will be passed to the string formatting operation, |
|
|||
395 | so they can override the values that would otherwise fill in the |
|
|||
396 | template. |
|
|||
397 |
|
||||
398 | Returns |
|
|||
399 | ------- |
|
|||
400 | A string containing the rendered prompt. |
|
|||
401 | """ |
|
|||
402 | res = self._render(name, color=color, **kwargs) |
|
|||
403 |
|
||||
404 | # Handle justification of prompt |
|
|||
405 | invis_chars = self.invisible_chars[name] if color else 0 |
|
|||
406 | # self.txtwidth = _lenlastline(res) - invis_chars |
|
|||
407 | just = self.justify if (just is None) else just |
|
|||
408 | # If the prompt spans more than one line, don't try to justify it: |
|
|||
409 | if just and name != 'in' and ('\n' not in res) and ('\r' not in res): |
|
|||
410 | res = res.rjust(self.width + invis_chars) |
|
|||
411 | # self.width = _lenlastline(res) - invis_chars |
|
|||
412 | return res |
|
@@ -1,129 +1,37 b'' | |||||
1 | # -*- coding: utf-8 |
|
1 | # -*- coding: utf-8 | |
2 | """Tests for prompt generation.""" |
|
2 | """Tests for prompt generation.""" | |
3 |
|
3 | |||
4 | import unittest |
|
4 | import unittest | |
5 |
|
5 | |||
6 | import os |
|
6 | from IPython.core.prompts import LazyEvaluate | |
7 |
|
||||
8 | from IPython.testing import tools as tt, decorators as dec |
|
|||
9 | from IPython.core.prompts import PromptManager, LazyEvaluate, _invisible_characters |
|
|||
10 | from IPython.testing.globalipapp import get_ipython |
|
7 | from IPython.testing.globalipapp import get_ipython | |
11 | from IPython.utils.tempdir import TemporaryWorkingDirectory |
|
|||
12 | from IPython.utils import py3compat |
|
|||
13 | from IPython.utils.py3compat import unicode_type |
|
8 | from IPython.utils.py3compat import unicode_type | |
14 |
|
9 | |||
15 | ip = get_ipython() |
|
10 | ip = get_ipython() | |
16 |
|
11 | |||
17 |
|
12 | |||
18 | class PromptTests(unittest.TestCase): |
|
13 | class PromptTests(unittest.TestCase): | |
19 | def setUp(self): |
|
|||
20 | self.pm = PromptManager(shell=ip, config=ip.config) |
|
|||
21 |
|
||||
22 | def test_multiline_prompt(self): |
|
|||
23 | self.pm.in_template = "[In]\n>>>" |
|
|||
24 | self.pm.render('in') |
|
|||
25 | self.assertEqual(self.pm.width, 3) |
|
|||
26 | self.assertEqual(self.pm.txtwidth, 3) |
|
|||
27 |
|
||||
28 | self.pm.in_template = '[In]\n' |
|
|||
29 | self.pm.render('in') |
|
|||
30 | self.assertEqual(self.pm.width, 0) |
|
|||
31 | self.assertEqual(self.pm.txtwidth, 0) |
|
|||
32 |
|
||||
33 | def test_translate_abbreviations(self): |
|
|||
34 | def do_translate(template): |
|
|||
35 | self.pm.in_template = template |
|
|||
36 | return self.pm.templates['in'] |
|
|||
37 |
|
||||
38 | pairs = [(r'%n>', '{color.number}{count}{color.prompt}>'), |
|
|||
39 | (r'\T', '{time}'), |
|
|||
40 | (r'\n', '\n') |
|
|||
41 | ] |
|
|||
42 |
|
||||
43 | tt.check_pairs(do_translate, pairs) |
|
|||
44 |
|
||||
45 | def test_user_ns(self): |
|
|||
46 | self.pm.color_scheme = 'NoColor' |
|
|||
47 | ip.ex("foo='bar'") |
|
|||
48 | self.pm.in_template = "In [{foo}]" |
|
|||
49 | prompt = self.pm.render('in') |
|
|||
50 | self.assertEqual(prompt, u'In [bar]') |
|
|||
51 |
|
||||
52 | def test_builtins(self): |
|
|||
53 | self.pm.color_scheme = 'NoColor' |
|
|||
54 | self.pm.in_template = "In [{int}]" |
|
|||
55 | prompt = self.pm.render('in') |
|
|||
56 | self.assertEqual(prompt, u"In [%r]" % int) |
|
|||
57 |
|
||||
58 | def test_undefined(self): |
|
|||
59 | self.pm.color_scheme = 'NoColor' |
|
|||
60 | self.pm.in_template = "In [{foo_dne}]" |
|
|||
61 | prompt = self.pm.render('in') |
|
|||
62 | self.assertEqual(prompt, u"In [<ERROR: 'foo_dne' not found>]") |
|
|||
63 |
|
||||
64 | def test_render(self): |
|
|||
65 | self.pm.in_template = r'\#>' |
|
|||
66 | self.assertEqual(self.pm.render('in',color=False), '%d>' % ip.execution_count) |
|
|||
67 |
|
||||
68 | @dec.onlyif_unicode_paths |
|
|||
69 | def test_render_unicode_cwd(self): |
|
|||
70 | with TemporaryWorkingDirectory(u'ΓΌnicΓΈdΓ©'): |
|
|||
71 | self.pm.in_template = r'\w [\#]' |
|
|||
72 | p = self.pm.render('in', color=False) |
|
|||
73 | self.assertEqual(p, u"%s [%i]" % (py3compat.getcwd(), ip.execution_count)) |
|
|||
74 |
|
||||
75 | def test_lazy_eval_unicode(self): |
|
14 | def test_lazy_eval_unicode(self): | |
76 | u = u'ΓΌnicΓΈdΓ©' |
|
15 | u = u'ΓΌnicΓΈdΓ©' | |
77 | lz = LazyEvaluate(lambda : u) |
|
16 | lz = LazyEvaluate(lambda : u) | |
78 | # str(lz) would fail |
|
17 | # str(lz) would fail | |
79 | self.assertEqual(unicode_type(lz), u) |
|
18 | self.assertEqual(unicode_type(lz), u) | |
80 | self.assertEqual(format(lz), u) |
|
19 | self.assertEqual(format(lz), u) | |
81 |
|
20 | |||
82 | def test_lazy_eval_nonascii_bytes(self): |
|
21 | def test_lazy_eval_nonascii_bytes(self): | |
83 | u = u'ΓΌnicΓΈdΓ©' |
|
22 | u = u'ΓΌnicΓΈdΓ©' | |
84 | b = u.encode('utf8') |
|
23 | b = u.encode('utf8') | |
85 | lz = LazyEvaluate(lambda : b) |
|
24 | lz = LazyEvaluate(lambda : b) | |
86 | # unicode(lz) would fail |
|
25 | # unicode(lz) would fail | |
87 | self.assertEqual(str(lz), str(b)) |
|
26 | self.assertEqual(str(lz), str(b)) | |
88 | self.assertEqual(format(lz), str(b)) |
|
27 | self.assertEqual(format(lz), str(b)) | |
89 |
|
28 | |||
90 | def test_lazy_eval_float(self): |
|
29 | def test_lazy_eval_float(self): | |
91 | f = 0.503 |
|
30 | f = 0.503 | |
92 | lz = LazyEvaluate(lambda : f) |
|
31 | lz = LazyEvaluate(lambda : f) | |
93 |
|
32 | |||
94 | self.assertEqual(str(lz), str(f)) |
|
33 | self.assertEqual(str(lz), str(f)) | |
95 | self.assertEqual(unicode_type(lz), unicode_type(f)) |
|
34 | self.assertEqual(unicode_type(lz), unicode_type(f)) | |
96 | self.assertEqual(format(lz), str(f)) |
|
35 | self.assertEqual(format(lz), str(f)) | |
97 | self.assertEqual(format(lz, '.1'), '0.5') |
|
36 | self.assertEqual(format(lz, '.1'), '0.5') | |
98 |
|
||||
99 | @dec.skip_win32 |
|
|||
100 | def test_cwd_x(self): |
|
|||
101 | self.pm.in_template = r"\X0" |
|
|||
102 | save = py3compat.getcwd() |
|
|||
103 | os.chdir(os.path.expanduser('~')) |
|
|||
104 | p = self.pm.render('in', color=False) |
|
|||
105 | try: |
|
|||
106 | self.assertEqual(p, '~') |
|
|||
107 | finally: |
|
|||
108 | os.chdir(save) |
|
|||
109 |
|
||||
110 | def test_invisible_chars(self): |
|
|||
111 | self.assertEqual(_invisible_characters('abc'), 0) |
|
|||
112 | self.assertEqual(_invisible_characters('\001\033[1;37m\002'), 9) |
|
|||
113 | # Sequences must be between \001 and \002 to be counted |
|
|||
114 | self.assertEqual(_invisible_characters('\033[1;37m'), 0) |
|
|||
115 | # Test custom escape sequences |
|
|||
116 | self.assertEqual(_invisible_characters('\001\033]133;A\a\002'), 10) |
|
|||
117 |
|
||||
118 | def test_width(self): |
|
|||
119 | default_in = '\x01\x1b]133;A\x07\x02In [1]: \x01\x1b]133;B\x07\x02' |
|
|||
120 | self.pm.in_template = default_in |
|
|||
121 | self.pm.render('in') |
|
|||
122 | self.assertEqual(self.pm.width, 8) |
|
|||
123 | self.assertEqual(self.pm.txtwidth, 8) |
|
|||
124 |
|
37 | |||
125 | # Test custom escape sequences |
|
|||
126 | self.pm.in_template = '\001\033]133;A\a\002' + default_in + '\001\033]133;B\a\002' |
|
|||
127 | self.pm.render('in') |
|
|||
128 | self.assertEqual(self.pm.width, 8) |
|
|||
129 | self.assertEqual(self.pm.txtwidth, 8) |
|
General Comments 0
You need to be logged in to leave comments.
Login now