##// END OF EJS Templates
Merge pull request #13669 from achhina/fix-issue-12967...
Matthias Bussonnier -
r27758:c2c6349f merge
parent child Browse files
Show More
@@ -1,854 +1,855
1 """Implementation of magic functions for interaction with the OS.
1 """Implementation of magic functions for interaction with the OS.
2
2
3 Note: this module is named 'osm' instead of 'os' to avoid a collision with the
3 Note: this module is named 'osm' instead of 'os' to avoid a collision with the
4 builtin.
4 builtin.
5 """
5 """
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 import io
9 import io
10 import os
10 import os
11 import pathlib
11 import re
12 import re
12 import sys
13 import sys
13 from pprint import pformat
14 from pprint import pformat
14
15
15 from IPython.core import magic_arguments
16 from IPython.core import magic_arguments
16 from IPython.core import oinspect
17 from IPython.core import oinspect
17 from IPython.core import page
18 from IPython.core import page
18 from IPython.core.alias import AliasError, Alias
19 from IPython.core.alias import AliasError, Alias
19 from IPython.core.error import UsageError
20 from IPython.core.error import UsageError
20 from IPython.core.magic import (
21 from IPython.core.magic import (
21 Magics, compress_dhist, magics_class, line_magic, cell_magic, line_cell_magic
22 Magics, compress_dhist, magics_class, line_magic, cell_magic, line_cell_magic
22 )
23 )
23 from IPython.testing.skipdoctest import skip_doctest
24 from IPython.testing.skipdoctest import skip_doctest
24 from IPython.utils.openpy import source_to_unicode
25 from IPython.utils.openpy import source_to_unicode
25 from IPython.utils.process import abbrev_cwd
26 from IPython.utils.process import abbrev_cwd
26 from IPython.utils.terminal import set_term_title
27 from IPython.utils.terminal import set_term_title
27 from traitlets import Bool
28 from traitlets import Bool
28 from warnings import warn
29 from warnings import warn
29
30
30
31
31 @magics_class
32 @magics_class
32 class OSMagics(Magics):
33 class OSMagics(Magics):
33 """Magics to interact with the underlying OS (shell-type functionality).
34 """Magics to interact with the underlying OS (shell-type functionality).
34 """
35 """
35
36
36 cd_force_quiet = Bool(False,
37 cd_force_quiet = Bool(False,
37 help="Force %cd magic to be quiet even if -q is not passed."
38 help="Force %cd magic to be quiet even if -q is not passed."
38 ).tag(config=True)
39 ).tag(config=True)
39
40
40 def __init__(self, shell=None, **kwargs):
41 def __init__(self, shell=None, **kwargs):
41
42
42 # Now define isexec in a cross platform manner.
43 # Now define isexec in a cross platform manner.
43 self.is_posix = False
44 self.is_posix = False
44 self.execre = None
45 self.execre = None
45 if os.name == 'posix':
46 if os.name == 'posix':
46 self.is_posix = True
47 self.is_posix = True
47 else:
48 else:
48 try:
49 try:
49 winext = os.environ['pathext'].replace(';','|').replace('.','')
50 winext = os.environ['pathext'].replace(';','|').replace('.','')
50 except KeyError:
51 except KeyError:
51 winext = 'exe|com|bat|py'
52 winext = 'exe|com|bat|py'
52 try:
53 try:
53 self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
54 self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
54 except re.error:
55 except re.error:
55 warn("Seems like your pathext environmental "
56 warn("Seems like your pathext environmental "
56 "variable is malformed. Please check it to "
57 "variable is malformed. Please check it to "
57 "enable a proper handle of file extensions "
58 "enable a proper handle of file extensions "
58 "managed for your system")
59 "managed for your system")
59 winext = 'exe|com|bat|py'
60 winext = 'exe|com|bat|py'
60 self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
61 self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
61
62
62 # call up the chain
63 # call up the chain
63 super().__init__(shell=shell, **kwargs)
64 super().__init__(shell=shell, **kwargs)
64
65
65
66
66 def _isexec_POSIX(self, file):
67 def _isexec_POSIX(self, file):
67 """
68 """
68 Test for executable on a POSIX system
69 Test for executable on a POSIX system
69 """
70 """
70 if os.access(file.path, os.X_OK):
71 if os.access(file.path, os.X_OK):
71 # will fail on maxOS if access is not X_OK
72 # will fail on maxOS if access is not X_OK
72 return file.is_file()
73 return file.is_file()
73 return False
74 return False
74
75
75
76
76
77
77 def _isexec_WIN(self, file):
78 def _isexec_WIN(self, file):
78 """
79 """
79 Test for executable file on non POSIX system
80 Test for executable file on non POSIX system
80 """
81 """
81 return file.is_file() and self.execre.match(file.name) is not None
82 return file.is_file() and self.execre.match(file.name) is not None
82
83
83 def isexec(self, file):
84 def isexec(self, file):
84 """
85 """
85 Test for executable file on non POSIX system
86 Test for executable file on non POSIX system
86 """
87 """
87 if self.is_posix:
88 if self.is_posix:
88 return self._isexec_POSIX(file)
89 return self._isexec_POSIX(file)
89 else:
90 else:
90 return self._isexec_WIN(file)
91 return self._isexec_WIN(file)
91
92
92
93
93 @skip_doctest
94 @skip_doctest
94 @line_magic
95 @line_magic
95 def alias(self, parameter_s=''):
96 def alias(self, parameter_s=''):
96 """Define an alias for a system command.
97 """Define an alias for a system command.
97
98
98 '%alias alias_name cmd' defines 'alias_name' as an alias for 'cmd'
99 '%alias alias_name cmd' defines 'alias_name' as an alias for 'cmd'
99
100
100 Then, typing 'alias_name params' will execute the system command 'cmd
101 Then, typing 'alias_name params' will execute the system command 'cmd
101 params' (from your underlying operating system).
102 params' (from your underlying operating system).
102
103
103 Aliases have lower precedence than magic functions and Python normal
104 Aliases have lower precedence than magic functions and Python normal
104 variables, so if 'foo' is both a Python variable and an alias, the
105 variables, so if 'foo' is both a Python variable and an alias, the
105 alias can not be executed until 'del foo' removes the Python variable.
106 alias can not be executed until 'del foo' removes the Python variable.
106
107
107 You can use the %l specifier in an alias definition to represent the
108 You can use the %l specifier in an alias definition to represent the
108 whole line when the alias is called. For example::
109 whole line when the alias is called. For example::
109
110
110 In [2]: alias bracket echo "Input in brackets: <%l>"
111 In [2]: alias bracket echo "Input in brackets: <%l>"
111 In [3]: bracket hello world
112 In [3]: bracket hello world
112 Input in brackets: <hello world>
113 Input in brackets: <hello world>
113
114
114 You can also define aliases with parameters using %s specifiers (one
115 You can also define aliases with parameters using %s specifiers (one
115 per parameter)::
116 per parameter)::
116
117
117 In [1]: alias parts echo first %s second %s
118 In [1]: alias parts echo first %s second %s
118 In [2]: %parts A B
119 In [2]: %parts A B
119 first A second B
120 first A second B
120 In [3]: %parts A
121 In [3]: %parts A
121 Incorrect number of arguments: 2 expected.
122 Incorrect number of arguments: 2 expected.
122 parts is an alias to: 'echo first %s second %s'
123 parts is an alias to: 'echo first %s second %s'
123
124
124 Note that %l and %s are mutually exclusive. You can only use one or
125 Note that %l and %s are mutually exclusive. You can only use one or
125 the other in your aliases.
126 the other in your aliases.
126
127
127 Aliases expand Python variables just like system calls using ! or !!
128 Aliases expand Python variables just like system calls using ! or !!
128 do: all expressions prefixed with '$' get expanded. For details of
129 do: all expressions prefixed with '$' get expanded. For details of
129 the semantic rules, see PEP-215:
130 the semantic rules, see PEP-215:
130 https://peps.python.org/pep-0215/. This is the library used by
131 https://peps.python.org/pep-0215/. This is the library used by
131 IPython for variable expansion. If you want to access a true shell
132 IPython for variable expansion. If you want to access a true shell
132 variable, an extra $ is necessary to prevent its expansion by
133 variable, an extra $ is necessary to prevent its expansion by
133 IPython::
134 IPython::
134
135
135 In [6]: alias show echo
136 In [6]: alias show echo
136 In [7]: PATH='A Python string'
137 In [7]: PATH='A Python string'
137 In [8]: show $PATH
138 In [8]: show $PATH
138 A Python string
139 A Python string
139 In [9]: show $$PATH
140 In [9]: show $$PATH
140 /usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:...
141 /usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:...
141
142
142 You can use the alias facility to access all of $PATH. See the %rehashx
143 You can use the alias facility to access all of $PATH. See the %rehashx
143 function, which automatically creates aliases for the contents of your
144 function, which automatically creates aliases for the contents of your
144 $PATH.
145 $PATH.
145
146
146 If called with no parameters, %alias prints the current alias table
147 If called with no parameters, %alias prints the current alias table
147 for your system. For posix systems, the default aliases are 'cat',
148 for your system. For posix systems, the default aliases are 'cat',
148 'cp', 'mv', 'rm', 'rmdir', and 'mkdir', and other platform-specific
149 'cp', 'mv', 'rm', 'rmdir', and 'mkdir', and other platform-specific
149 aliases are added. For windows-based systems, the default aliases are
150 aliases are added. For windows-based systems, the default aliases are
150 'copy', 'ddir', 'echo', 'ls', 'ldir', 'mkdir', 'ren', and 'rmdir'.
151 'copy', 'ddir', 'echo', 'ls', 'ldir', 'mkdir', 'ren', and 'rmdir'.
151
152
152 You can see the definition of alias by adding a question mark in the
153 You can see the definition of alias by adding a question mark in the
153 end::
154 end::
154
155
155 In [1]: cat?
156 In [1]: cat?
156 Repr: <alias cat for 'cat'>"""
157 Repr: <alias cat for 'cat'>"""
157
158
158 par = parameter_s.strip()
159 par = parameter_s.strip()
159 if not par:
160 if not par:
160 aliases = sorted(self.shell.alias_manager.aliases)
161 aliases = sorted(self.shell.alias_manager.aliases)
161 # stored = self.shell.db.get('stored_aliases', {} )
162 # stored = self.shell.db.get('stored_aliases', {} )
162 # for k, v in stored:
163 # for k, v in stored:
163 # atab.append(k, v[0])
164 # atab.append(k, v[0])
164
165
165 print("Total number of aliases:", len(aliases))
166 print("Total number of aliases:", len(aliases))
166 sys.stdout.flush()
167 sys.stdout.flush()
167 return aliases
168 return aliases
168
169
169 # Now try to define a new one
170 # Now try to define a new one
170 try:
171 try:
171 alias,cmd = par.split(None, 1)
172 alias,cmd = par.split(None, 1)
172 except TypeError:
173 except TypeError:
173 print(oinspect.getdoc(self.alias))
174 print(oinspect.getdoc(self.alias))
174 return
175 return
175
176
176 try:
177 try:
177 self.shell.alias_manager.define_alias(alias, cmd)
178 self.shell.alias_manager.define_alias(alias, cmd)
178 except AliasError as e:
179 except AliasError as e:
179 print(e)
180 print(e)
180 # end magic_alias
181 # end magic_alias
181
182
182 @line_magic
183 @line_magic
183 def unalias(self, parameter_s=''):
184 def unalias(self, parameter_s=''):
184 """Remove an alias"""
185 """Remove an alias"""
185
186
186 aname = parameter_s.strip()
187 aname = parameter_s.strip()
187 try:
188 try:
188 self.shell.alias_manager.undefine_alias(aname)
189 self.shell.alias_manager.undefine_alias(aname)
189 except ValueError as e:
190 except ValueError as e:
190 print(e)
191 print(e)
191 return
192 return
192
193
193 stored = self.shell.db.get('stored_aliases', {} )
194 stored = self.shell.db.get('stored_aliases', {} )
194 if aname in stored:
195 if aname in stored:
195 print("Removing %stored alias",aname)
196 print("Removing %stored alias",aname)
196 del stored[aname]
197 del stored[aname]
197 self.shell.db['stored_aliases'] = stored
198 self.shell.db['stored_aliases'] = stored
198
199
199 @line_magic
200 @line_magic
200 def rehashx(self, parameter_s=''):
201 def rehashx(self, parameter_s=''):
201 """Update the alias table with all executable files in $PATH.
202 """Update the alias table with all executable files in $PATH.
202
203
203 rehashx explicitly checks that every entry in $PATH is a file
204 rehashx explicitly checks that every entry in $PATH is a file
204 with execute access (os.X_OK).
205 with execute access (os.X_OK).
205
206
206 Under Windows, it checks executability as a match against a
207 Under Windows, it checks executability as a match against a
207 '|'-separated string of extensions, stored in the IPython config
208 '|'-separated string of extensions, stored in the IPython config
208 variable win_exec_ext. This defaults to 'exe|com|bat'.
209 variable win_exec_ext. This defaults to 'exe|com|bat'.
209
210
210 This function also resets the root module cache of module completer,
211 This function also resets the root module cache of module completer,
211 used on slow filesystems.
212 used on slow filesystems.
212 """
213 """
213 from IPython.core.alias import InvalidAliasError
214 from IPython.core.alias import InvalidAliasError
214
215
215 # for the benefit of module completer in ipy_completers.py
216 # for the benefit of module completer in ipy_completers.py
216 del self.shell.db['rootmodules_cache']
217 del self.shell.db['rootmodules_cache']
217
218
218 path = [os.path.abspath(os.path.expanduser(p)) for p in
219 path = [os.path.abspath(os.path.expanduser(p)) for p in
219 os.environ.get('PATH','').split(os.pathsep)]
220 os.environ.get('PATH','').split(os.pathsep)]
220
221
221 syscmdlist = []
222 syscmdlist = []
222 savedir = os.getcwd()
223 savedir = os.getcwd()
223
224
224 # Now walk the paths looking for executables to alias.
225 # Now walk the paths looking for executables to alias.
225 try:
226 try:
226 # write the whole loop for posix/Windows so we don't have an if in
227 # write the whole loop for posix/Windows so we don't have an if in
227 # the innermost part
228 # the innermost part
228 if self.is_posix:
229 if self.is_posix:
229 for pdir in path:
230 for pdir in path:
230 try:
231 try:
231 os.chdir(pdir)
232 os.chdir(pdir)
232 except OSError:
233 except OSError:
233 continue
234 continue
234
235
235 # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist:
236 # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist:
236 dirlist = os.scandir(path=pdir)
237 dirlist = os.scandir(path=pdir)
237 for ff in dirlist:
238 for ff in dirlist:
238 if self.isexec(ff):
239 if self.isexec(ff):
239 fname = ff.name
240 fname = ff.name
240 try:
241 try:
241 # Removes dots from the name since ipython
242 # Removes dots from the name since ipython
242 # will assume names with dots to be python.
243 # will assume names with dots to be python.
243 if not self.shell.alias_manager.is_alias(fname):
244 if not self.shell.alias_manager.is_alias(fname):
244 self.shell.alias_manager.define_alias(
245 self.shell.alias_manager.define_alias(
245 fname.replace('.',''), fname)
246 fname.replace('.',''), fname)
246 except InvalidAliasError:
247 except InvalidAliasError:
247 pass
248 pass
248 else:
249 else:
249 syscmdlist.append(fname)
250 syscmdlist.append(fname)
250 else:
251 else:
251 no_alias = Alias.blacklist
252 no_alias = Alias.blacklist
252 for pdir in path:
253 for pdir in path:
253 try:
254 try:
254 os.chdir(pdir)
255 os.chdir(pdir)
255 except OSError:
256 except OSError:
256 continue
257 continue
257
258
258 # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist:
259 # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist:
259 dirlist = os.scandir(pdir)
260 dirlist = os.scandir(pdir)
260 for ff in dirlist:
261 for ff in dirlist:
261 fname = ff.name
262 fname = ff.name
262 base, ext = os.path.splitext(fname)
263 base, ext = os.path.splitext(fname)
263 if self.isexec(ff) and base.lower() not in no_alias:
264 if self.isexec(ff) and base.lower() not in no_alias:
264 if ext.lower() == '.exe':
265 if ext.lower() == '.exe':
265 fname = base
266 fname = base
266 try:
267 try:
267 # Removes dots from the name since ipython
268 # Removes dots from the name since ipython
268 # will assume names with dots to be python.
269 # will assume names with dots to be python.
269 self.shell.alias_manager.define_alias(
270 self.shell.alias_manager.define_alias(
270 base.lower().replace('.',''), fname)
271 base.lower().replace('.',''), fname)
271 except InvalidAliasError:
272 except InvalidAliasError:
272 pass
273 pass
273 syscmdlist.append(fname)
274 syscmdlist.append(fname)
274
275
275 self.shell.db['syscmdlist'] = syscmdlist
276 self.shell.db['syscmdlist'] = syscmdlist
276 finally:
277 finally:
277 os.chdir(savedir)
278 os.chdir(savedir)
278
279
279 @skip_doctest
280 @skip_doctest
280 @line_magic
281 @line_magic
281 def pwd(self, parameter_s=''):
282 def pwd(self, parameter_s=''):
282 """Return the current working directory path.
283 """Return the current working directory path.
283
284
284 Examples
285 Examples
285 --------
286 --------
286 ::
287 ::
287
288
288 In [9]: pwd
289 In [9]: pwd
289 Out[9]: '/home/tsuser/sprint/ipython'
290 Out[9]: '/home/tsuser/sprint/ipython'
290 """
291 """
291 try:
292 try:
292 return os.getcwd()
293 return os.getcwd()
293 except FileNotFoundError as e:
294 except FileNotFoundError as e:
294 raise UsageError("CWD no longer exists - please use %cd to change directory.") from e
295 raise UsageError("CWD no longer exists - please use %cd to change directory.") from e
295
296
296 @skip_doctest
297 @skip_doctest
297 @line_magic
298 @line_magic
298 def cd(self, parameter_s=''):
299 def cd(self, parameter_s=''):
299 """Change the current working directory.
300 """Change the current working directory.
300
301
301 This command automatically maintains an internal list of directories
302 This command automatically maintains an internal list of directories
302 you visit during your IPython session, in the variable ``_dh``. The
303 you visit during your IPython session, in the variable ``_dh``. The
303 command :magic:`%dhist` shows this history nicely formatted. You can
304 command :magic:`%dhist` shows this history nicely formatted. You can
304 also do ``cd -<tab>`` to see directory history conveniently.
305 also do ``cd -<tab>`` to see directory history conveniently.
305 Usage:
306 Usage:
306
307
307 - ``cd 'dir'``: changes to directory 'dir'.
308 - ``cd 'dir'``: changes to directory 'dir'.
308 - ``cd -``: changes to the last visited directory.
309 - ``cd -``: changes to the last visited directory.
309 - ``cd -<n>``: changes to the n-th directory in the directory history.
310 - ``cd -<n>``: changes to the n-th directory in the directory history.
310 - ``cd --foo``: change to directory that matches 'foo' in history
311 - ``cd --foo``: change to directory that matches 'foo' in history
311 - ``cd -b <bookmark_name>``: jump to a bookmark set by %bookmark
312 - ``cd -b <bookmark_name>``: jump to a bookmark set by %bookmark
312 - Hitting a tab key after ``cd -b`` allows you to tab-complete
313 - Hitting a tab key after ``cd -b`` allows you to tab-complete
313 bookmark names.
314 bookmark names.
314
315
315 .. note::
316 .. note::
316 ``cd <bookmark_name>`` is enough if there is no directory
317 ``cd <bookmark_name>`` is enough if there is no directory
317 ``<bookmark_name>``, but a bookmark with the name exists.
318 ``<bookmark_name>``, but a bookmark with the name exists.
318
319
319 Options:
320 Options:
320
321
321 -q Be quiet. Do not print the working directory after the
322 -q Be quiet. Do not print the working directory after the
322 cd command is executed. By default IPython's cd
323 cd command is executed. By default IPython's cd
323 command does print this directory, since the default
324 command does print this directory, since the default
324 prompts do not display path information.
325 prompts do not display path information.
325
326
326 .. note::
327 .. note::
327 Note that ``!cd`` doesn't work for this purpose because the shell
328 Note that ``!cd`` doesn't work for this purpose because the shell
328 where ``!command`` runs is immediately discarded after executing
329 where ``!command`` runs is immediately discarded after executing
329 'command'.
330 'command'.
330
331
331 Examples
332 Examples
332 --------
333 --------
333 ::
334 ::
334
335
335 In [10]: cd parent/child
336 In [10]: cd parent/child
336 /home/tsuser/parent/child
337 /home/tsuser/parent/child
337 """
338 """
338
339
339 try:
340 try:
340 oldcwd = os.getcwd()
341 oldcwd = os.getcwd()
341 except FileNotFoundError:
342 except FileNotFoundError:
342 # Happens if the CWD has been deleted.
343 # Happens if the CWD has been deleted.
343 oldcwd = None
344 oldcwd = None
344
345
345 numcd = re.match(r'(-)(\d+)$',parameter_s)
346 numcd = re.match(r'(-)(\d+)$',parameter_s)
346 # jump in directory history by number
347 # jump in directory history by number
347 if numcd:
348 if numcd:
348 nn = int(numcd.group(2))
349 nn = int(numcd.group(2))
349 try:
350 try:
350 ps = self.shell.user_ns['_dh'][nn]
351 ps = self.shell.user_ns['_dh'][nn]
351 except IndexError:
352 except IndexError:
352 print('The requested directory does not exist in history.')
353 print('The requested directory does not exist in history.')
353 return
354 return
354 else:
355 else:
355 opts = {}
356 opts = {}
356 elif parameter_s.startswith('--'):
357 elif parameter_s.startswith('--'):
357 ps = None
358 ps = None
358 fallback = None
359 fallback = None
359 pat = parameter_s[2:]
360 pat = parameter_s[2:]
360 dh = self.shell.user_ns['_dh']
361 dh = self.shell.user_ns['_dh']
361 # first search only by basename (last component)
362 # first search only by basename (last component)
362 for ent in reversed(dh):
363 for ent in reversed(dh):
363 if pat in os.path.basename(ent) and os.path.isdir(ent):
364 if pat in os.path.basename(ent) and os.path.isdir(ent):
364 ps = ent
365 ps = ent
365 break
366 break
366
367
367 if fallback is None and pat in ent and os.path.isdir(ent):
368 if fallback is None and pat in ent and os.path.isdir(ent):
368 fallback = ent
369 fallback = ent
369
370
370 # if we have no last part match, pick the first full path match
371 # if we have no last part match, pick the first full path match
371 if ps is None:
372 if ps is None:
372 ps = fallback
373 ps = fallback
373
374
374 if ps is None:
375 if ps is None:
375 print("No matching entry in directory history")
376 print("No matching entry in directory history")
376 return
377 return
377 else:
378 else:
378 opts = {}
379 opts = {}
379
380
380
381
381 else:
382 else:
382 opts, ps = self.parse_options(parameter_s, 'qb', mode='string')
383 opts, ps = self.parse_options(parameter_s, 'qb', mode='string')
383 # jump to previous
384 # jump to previous
384 if ps == '-':
385 if ps == '-':
385 try:
386 try:
386 ps = self.shell.user_ns['_dh'][-2]
387 ps = self.shell.user_ns['_dh'][-2]
387 except IndexError as e:
388 except IndexError as e:
388 raise UsageError('%cd -: No previous directory to change to.') from e
389 raise UsageError('%cd -: No previous directory to change to.') from e
389 # jump to bookmark if needed
390 # jump to bookmark if needed
390 else:
391 else:
391 if not os.path.isdir(ps) or 'b' in opts:
392 if not os.path.isdir(ps) or 'b' in opts:
392 bkms = self.shell.db.get('bookmarks', {})
393 bkms = self.shell.db.get('bookmarks', {})
393
394
394 if ps in bkms:
395 if ps in bkms:
395 target = bkms[ps]
396 target = bkms[ps]
396 print('(bookmark:%s) -> %s' % (ps, target))
397 print('(bookmark:%s) -> %s' % (ps, target))
397 ps = target
398 ps = target
398 else:
399 else:
399 if 'b' in opts:
400 if 'b' in opts:
400 raise UsageError("Bookmark '%s' not found. "
401 raise UsageError("Bookmark '%s' not found. "
401 "Use '%%bookmark -l' to see your bookmarks." % ps)
402 "Use '%%bookmark -l' to see your bookmarks." % ps)
402
403
403 # at this point ps should point to the target dir
404 # at this point ps should point to the target dir
404 if ps:
405 if ps:
405 try:
406 try:
406 os.chdir(os.path.expanduser(ps))
407 os.chdir(os.path.expanduser(ps))
407 if hasattr(self.shell, 'term_title') and self.shell.term_title:
408 if hasattr(self.shell, 'term_title') and self.shell.term_title:
408 set_term_title(self.shell.term_title_format.format(cwd=abbrev_cwd()))
409 set_term_title(self.shell.term_title_format.format(cwd=abbrev_cwd()))
409 except OSError:
410 except OSError:
410 print(sys.exc_info()[1])
411 print(sys.exc_info()[1])
411 else:
412 else:
412 cwd = os.getcwd()
413 cwd = pathlib.Path.cwd()
413 dhist = self.shell.user_ns['_dh']
414 dhist = self.shell.user_ns['_dh']
414 if oldcwd != cwd:
415 if oldcwd != cwd:
415 dhist.append(cwd)
416 dhist.append(cwd)
416 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
417 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
417
418
418 else:
419 else:
419 os.chdir(self.shell.home_dir)
420 os.chdir(self.shell.home_dir)
420 if hasattr(self.shell, 'term_title') and self.shell.term_title:
421 if hasattr(self.shell, 'term_title') and self.shell.term_title:
421 set_term_title(self.shell.term_title_format.format(cwd="~"))
422 set_term_title(self.shell.term_title_format.format(cwd="~"))
422 cwd = os.getcwd()
423 cwd = pathlib.Path.cwd()
423 dhist = self.shell.user_ns['_dh']
424 dhist = self.shell.user_ns['_dh']
424
425
425 if oldcwd != cwd:
426 if oldcwd != cwd:
426 dhist.append(cwd)
427 dhist.append(cwd)
427 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
428 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
428 if not 'q' in opts and not self.cd_force_quiet and self.shell.user_ns['_dh']:
429 if not 'q' in opts and not self.cd_force_quiet and self.shell.user_ns['_dh']:
429 print(self.shell.user_ns['_dh'][-1])
430 print(self.shell.user_ns['_dh'][-1])
430
431
431 @line_magic
432 @line_magic
432 def env(self, parameter_s=''):
433 def env(self, parameter_s=''):
433 """Get, set, or list environment variables.
434 """Get, set, or list environment variables.
434
435
435 Usage:\\
436 Usage:\\
436
437
437 :``%env``: lists all environment variables/values
438 :``%env``: lists all environment variables/values
438 :``%env var``: get value for var
439 :``%env var``: get value for var
439 :``%env var val``: set value for var
440 :``%env var val``: set value for var
440 :``%env var=val``: set value for var
441 :``%env var=val``: set value for var
441 :``%env var=$val``: set value for var, using python expansion if possible
442 :``%env var=$val``: set value for var, using python expansion if possible
442 """
443 """
443 if parameter_s.strip():
444 if parameter_s.strip():
444 split = '=' if '=' in parameter_s else ' '
445 split = '=' if '=' in parameter_s else ' '
445 bits = parameter_s.split(split)
446 bits = parameter_s.split(split)
446 if len(bits) == 1:
447 if len(bits) == 1:
447 key = parameter_s.strip()
448 key = parameter_s.strip()
448 if key in os.environ:
449 if key in os.environ:
449 return os.environ[key]
450 return os.environ[key]
450 else:
451 else:
451 err = "Environment does not have key: {0}".format(key)
452 err = "Environment does not have key: {0}".format(key)
452 raise UsageError(err)
453 raise UsageError(err)
453 if len(bits) > 1:
454 if len(bits) > 1:
454 return self.set_env(parameter_s)
455 return self.set_env(parameter_s)
455 env = dict(os.environ)
456 env = dict(os.environ)
456 # hide likely secrets when printing the whole environment
457 # hide likely secrets when printing the whole environment
457 for key in list(env):
458 for key in list(env):
458 if any(s in key.lower() for s in ('key', 'token', 'secret')):
459 if any(s in key.lower() for s in ('key', 'token', 'secret')):
459 env[key] = '<hidden>'
460 env[key] = '<hidden>'
460
461
461 return env
462 return env
462
463
463 @line_magic
464 @line_magic
464 def set_env(self, parameter_s):
465 def set_env(self, parameter_s):
465 """Set environment variables. Assumptions are that either "val" is a
466 """Set environment variables. Assumptions are that either "val" is a
466 name in the user namespace, or val is something that evaluates to a
467 name in the user namespace, or val is something that evaluates to a
467 string.
468 string.
468
469
469 Usage:\\
470 Usage:\\
470 %set_env var val: set value for var
471 %set_env var val: set value for var
471 %set_env var=val: set value for var
472 %set_env var=val: set value for var
472 %set_env var=$val: set value for var, using python expansion if possible
473 %set_env var=$val: set value for var, using python expansion if possible
473 """
474 """
474 split = '=' if '=' in parameter_s else ' '
475 split = '=' if '=' in parameter_s else ' '
475 bits = parameter_s.split(split, 1)
476 bits = parameter_s.split(split, 1)
476 if not parameter_s.strip() or len(bits)<2:
477 if not parameter_s.strip() or len(bits)<2:
477 raise UsageError("usage is 'set_env var=val'")
478 raise UsageError("usage is 'set_env var=val'")
478 var = bits[0].strip()
479 var = bits[0].strip()
479 val = bits[1].strip()
480 val = bits[1].strip()
480 if re.match(r'.*\s.*', var):
481 if re.match(r'.*\s.*', var):
481 # an environment variable with whitespace is almost certainly
482 # an environment variable with whitespace is almost certainly
482 # not what the user intended. what's more likely is the wrong
483 # not what the user intended. what's more likely is the wrong
483 # split was chosen, ie for "set_env cmd_args A=B", we chose
484 # split was chosen, ie for "set_env cmd_args A=B", we chose
484 # '=' for the split and should have chosen ' '. to get around
485 # '=' for the split and should have chosen ' '. to get around
485 # this, users should just assign directly to os.environ or use
486 # this, users should just assign directly to os.environ or use
486 # standard magic {var} expansion.
487 # standard magic {var} expansion.
487 err = "refusing to set env var with whitespace: '{0}'"
488 err = "refusing to set env var with whitespace: '{0}'"
488 err = err.format(val)
489 err = err.format(val)
489 raise UsageError(err)
490 raise UsageError(err)
490 os.environ[var] = val
491 os.environ[var] = val
491 print('env: {0}={1}'.format(var,val))
492 print('env: {0}={1}'.format(var,val))
492
493
493 @line_magic
494 @line_magic
494 def pushd(self, parameter_s=''):
495 def pushd(self, parameter_s=''):
495 """Place the current dir on stack and change directory.
496 """Place the current dir on stack and change directory.
496
497
497 Usage:\\
498 Usage:\\
498 %pushd ['dirname']
499 %pushd ['dirname']
499 """
500 """
500
501
501 dir_s = self.shell.dir_stack
502 dir_s = self.shell.dir_stack
502 tgt = os.path.expanduser(parameter_s)
503 tgt = os.path.expanduser(parameter_s)
503 cwd = os.getcwd().replace(self.shell.home_dir,'~')
504 cwd = os.getcwd().replace(self.shell.home_dir,'~')
504 if tgt:
505 if tgt:
505 self.cd(parameter_s)
506 self.cd(parameter_s)
506 dir_s.insert(0,cwd)
507 dir_s.insert(0,cwd)
507 return self.shell.run_line_magic('dirs', '')
508 return self.shell.run_line_magic('dirs', '')
508
509
509 @line_magic
510 @line_magic
510 def popd(self, parameter_s=''):
511 def popd(self, parameter_s=''):
511 """Change to directory popped off the top of the stack.
512 """Change to directory popped off the top of the stack.
512 """
513 """
513 if not self.shell.dir_stack:
514 if not self.shell.dir_stack:
514 raise UsageError("%popd on empty stack")
515 raise UsageError("%popd on empty stack")
515 top = self.shell.dir_stack.pop(0)
516 top = self.shell.dir_stack.pop(0)
516 self.cd(top)
517 self.cd(top)
517 print("popd ->",top)
518 print("popd ->",top)
518
519
519 @line_magic
520 @line_magic
520 def dirs(self, parameter_s=''):
521 def dirs(self, parameter_s=''):
521 """Return the current directory stack."""
522 """Return the current directory stack."""
522
523
523 return self.shell.dir_stack
524 return self.shell.dir_stack
524
525
525 @line_magic
526 @line_magic
526 def dhist(self, parameter_s=''):
527 def dhist(self, parameter_s=''):
527 """Print your history of visited directories.
528 """Print your history of visited directories.
528
529
529 %dhist -> print full history\\
530 %dhist -> print full history\\
530 %dhist n -> print last n entries only\\
531 %dhist n -> print last n entries only\\
531 %dhist n1 n2 -> print entries between n1 and n2 (n2 not included)\\
532 %dhist n1 n2 -> print entries between n1 and n2 (n2 not included)\\
532
533
533 This history is automatically maintained by the %cd command, and
534 This history is automatically maintained by the %cd command, and
534 always available as the global list variable _dh. You can use %cd -<n>
535 always available as the global list variable _dh. You can use %cd -<n>
535 to go to directory number <n>.
536 to go to directory number <n>.
536
537
537 Note that most of time, you should view directory history by entering
538 Note that most of time, you should view directory history by entering
538 cd -<TAB>.
539 cd -<TAB>.
539
540
540 """
541 """
541
542
542 dh = self.shell.user_ns['_dh']
543 dh = self.shell.user_ns['_dh']
543 if parameter_s:
544 if parameter_s:
544 try:
545 try:
545 args = map(int,parameter_s.split())
546 args = map(int,parameter_s.split())
546 except:
547 except:
547 self.arg_err(self.dhist)
548 self.arg_err(self.dhist)
548 return
549 return
549 if len(args) == 1:
550 if len(args) == 1:
550 ini,fin = max(len(dh)-(args[0]),0),len(dh)
551 ini,fin = max(len(dh)-(args[0]),0),len(dh)
551 elif len(args) == 2:
552 elif len(args) == 2:
552 ini,fin = args
553 ini,fin = args
553 fin = min(fin, len(dh))
554 fin = min(fin, len(dh))
554 else:
555 else:
555 self.arg_err(self.dhist)
556 self.arg_err(self.dhist)
556 return
557 return
557 else:
558 else:
558 ini,fin = 0,len(dh)
559 ini,fin = 0,len(dh)
559 print('Directory history (kept in _dh)')
560 print('Directory history (kept in _dh)')
560 for i in range(ini, fin):
561 for i in range(ini, fin):
561 print("%d: %s" % (i, dh[i]))
562 print("%d: %s" % (i, dh[i]))
562
563
563 @skip_doctest
564 @skip_doctest
564 @line_magic
565 @line_magic
565 def sc(self, parameter_s=''):
566 def sc(self, parameter_s=''):
566 """Shell capture - run shell command and capture output (DEPRECATED use !).
567 """Shell capture - run shell command and capture output (DEPRECATED use !).
567
568
568 DEPRECATED. Suboptimal, retained for backwards compatibility.
569 DEPRECATED. Suboptimal, retained for backwards compatibility.
569
570
570 You should use the form 'var = !command' instead. Example:
571 You should use the form 'var = !command' instead. Example:
571
572
572 "%sc -l myfiles = ls ~" should now be written as
573 "%sc -l myfiles = ls ~" should now be written as
573
574
574 "myfiles = !ls ~"
575 "myfiles = !ls ~"
575
576
576 myfiles.s, myfiles.l and myfiles.n still apply as documented
577 myfiles.s, myfiles.l and myfiles.n still apply as documented
577 below.
578 below.
578
579
579 --
580 --
580 %sc [options] varname=command
581 %sc [options] varname=command
581
582
582 IPython will run the given command using commands.getoutput(), and
583 IPython will run the given command using commands.getoutput(), and
583 will then update the user's interactive namespace with a variable
584 will then update the user's interactive namespace with a variable
584 called varname, containing the value of the call. Your command can
585 called varname, containing the value of the call. Your command can
585 contain shell wildcards, pipes, etc.
586 contain shell wildcards, pipes, etc.
586
587
587 The '=' sign in the syntax is mandatory, and the variable name you
588 The '=' sign in the syntax is mandatory, and the variable name you
588 supply must follow Python's standard conventions for valid names.
589 supply must follow Python's standard conventions for valid names.
589
590
590 (A special format without variable name exists for internal use)
591 (A special format without variable name exists for internal use)
591
592
592 Options:
593 Options:
593
594
594 -l: list output. Split the output on newlines into a list before
595 -l: list output. Split the output on newlines into a list before
595 assigning it to the given variable. By default the output is stored
596 assigning it to the given variable. By default the output is stored
596 as a single string.
597 as a single string.
597
598
598 -v: verbose. Print the contents of the variable.
599 -v: verbose. Print the contents of the variable.
599
600
600 In most cases you should not need to split as a list, because the
601 In most cases you should not need to split as a list, because the
601 returned value is a special type of string which can automatically
602 returned value is a special type of string which can automatically
602 provide its contents either as a list (split on newlines) or as a
603 provide its contents either as a list (split on newlines) or as a
603 space-separated string. These are convenient, respectively, either
604 space-separated string. These are convenient, respectively, either
604 for sequential processing or to be passed to a shell command.
605 for sequential processing or to be passed to a shell command.
605
606
606 For example::
607 For example::
607
608
608 # Capture into variable a
609 # Capture into variable a
609 In [1]: sc a=ls *py
610 In [1]: sc a=ls *py
610
611
611 # a is a string with embedded newlines
612 # a is a string with embedded newlines
612 In [2]: a
613 In [2]: a
613 Out[2]: 'setup.py\\nwin32_manual_post_install.py'
614 Out[2]: 'setup.py\\nwin32_manual_post_install.py'
614
615
615 # which can be seen as a list:
616 # which can be seen as a list:
616 In [3]: a.l
617 In [3]: a.l
617 Out[3]: ['setup.py', 'win32_manual_post_install.py']
618 Out[3]: ['setup.py', 'win32_manual_post_install.py']
618
619
619 # or as a whitespace-separated string:
620 # or as a whitespace-separated string:
620 In [4]: a.s
621 In [4]: a.s
621 Out[4]: 'setup.py win32_manual_post_install.py'
622 Out[4]: 'setup.py win32_manual_post_install.py'
622
623
623 # a.s is useful to pass as a single command line:
624 # a.s is useful to pass as a single command line:
624 In [5]: !wc -l $a.s
625 In [5]: !wc -l $a.s
625 146 setup.py
626 146 setup.py
626 130 win32_manual_post_install.py
627 130 win32_manual_post_install.py
627 276 total
628 276 total
628
629
629 # while the list form is useful to loop over:
630 # while the list form is useful to loop over:
630 In [6]: for f in a.l:
631 In [6]: for f in a.l:
631 ...: !wc -l $f
632 ...: !wc -l $f
632 ...:
633 ...:
633 146 setup.py
634 146 setup.py
634 130 win32_manual_post_install.py
635 130 win32_manual_post_install.py
635
636
636 Similarly, the lists returned by the -l option are also special, in
637 Similarly, the lists returned by the -l option are also special, in
637 the sense that you can equally invoke the .s attribute on them to
638 the sense that you can equally invoke the .s attribute on them to
638 automatically get a whitespace-separated string from their contents::
639 automatically get a whitespace-separated string from their contents::
639
640
640 In [7]: sc -l b=ls *py
641 In [7]: sc -l b=ls *py
641
642
642 In [8]: b
643 In [8]: b
643 Out[8]: ['setup.py', 'win32_manual_post_install.py']
644 Out[8]: ['setup.py', 'win32_manual_post_install.py']
644
645
645 In [9]: b.s
646 In [9]: b.s
646 Out[9]: 'setup.py win32_manual_post_install.py'
647 Out[9]: 'setup.py win32_manual_post_install.py'
647
648
648 In summary, both the lists and strings used for output capture have
649 In summary, both the lists and strings used for output capture have
649 the following special attributes::
650 the following special attributes::
650
651
651 .l (or .list) : value as list.
652 .l (or .list) : value as list.
652 .n (or .nlstr): value as newline-separated string.
653 .n (or .nlstr): value as newline-separated string.
653 .s (or .spstr): value as space-separated string.
654 .s (or .spstr): value as space-separated string.
654 """
655 """
655
656
656 opts,args = self.parse_options(parameter_s, 'lv')
657 opts,args = self.parse_options(parameter_s, 'lv')
657 # Try to get a variable name and command to run
658 # Try to get a variable name and command to run
658 try:
659 try:
659 # the variable name must be obtained from the parse_options
660 # the variable name must be obtained from the parse_options
660 # output, which uses shlex.split to strip options out.
661 # output, which uses shlex.split to strip options out.
661 var,_ = args.split('=', 1)
662 var,_ = args.split('=', 1)
662 var = var.strip()
663 var = var.strip()
663 # But the command has to be extracted from the original input
664 # But the command has to be extracted from the original input
664 # parameter_s, not on what parse_options returns, to avoid the
665 # parameter_s, not on what parse_options returns, to avoid the
665 # quote stripping which shlex.split performs on it.
666 # quote stripping which shlex.split performs on it.
666 _,cmd = parameter_s.split('=', 1)
667 _,cmd = parameter_s.split('=', 1)
667 except ValueError:
668 except ValueError:
668 var,cmd = '',''
669 var,cmd = '',''
669 # If all looks ok, proceed
670 # If all looks ok, proceed
670 split = 'l' in opts
671 split = 'l' in opts
671 out = self.shell.getoutput(cmd, split=split)
672 out = self.shell.getoutput(cmd, split=split)
672 if 'v' in opts:
673 if 'v' in opts:
673 print('%s ==\n%s' % (var, pformat(out)))
674 print('%s ==\n%s' % (var, pformat(out)))
674 if var:
675 if var:
675 self.shell.user_ns.update({var:out})
676 self.shell.user_ns.update({var:out})
676 else:
677 else:
677 return out
678 return out
678
679
679 @line_cell_magic
680 @line_cell_magic
680 def sx(self, line='', cell=None):
681 def sx(self, line='', cell=None):
681 """Shell execute - run shell command and capture output (!! is short-hand).
682 """Shell execute - run shell command and capture output (!! is short-hand).
682
683
683 %sx command
684 %sx command
684
685
685 IPython will run the given command using commands.getoutput(), and
686 IPython will run the given command using commands.getoutput(), and
686 return the result formatted as a list (split on '\\n'). Since the
687 return the result formatted as a list (split on '\\n'). Since the
687 output is _returned_, it will be stored in ipython's regular output
688 output is _returned_, it will be stored in ipython's regular output
688 cache Out[N] and in the '_N' automatic variables.
689 cache Out[N] and in the '_N' automatic variables.
689
690
690 Notes:
691 Notes:
691
692
692 1) If an input line begins with '!!', then %sx is automatically
693 1) If an input line begins with '!!', then %sx is automatically
693 invoked. That is, while::
694 invoked. That is, while::
694
695
695 !ls
696 !ls
696
697
697 causes ipython to simply issue system('ls'), typing::
698 causes ipython to simply issue system('ls'), typing::
698
699
699 !!ls
700 !!ls
700
701
701 is a shorthand equivalent to::
702 is a shorthand equivalent to::
702
703
703 %sx ls
704 %sx ls
704
705
705 2) %sx differs from %sc in that %sx automatically splits into a list,
706 2) %sx differs from %sc in that %sx automatically splits into a list,
706 like '%sc -l'. The reason for this is to make it as easy as possible
707 like '%sc -l'. The reason for this is to make it as easy as possible
707 to process line-oriented shell output via further python commands.
708 to process line-oriented shell output via further python commands.
708 %sc is meant to provide much finer control, but requires more
709 %sc is meant to provide much finer control, but requires more
709 typing.
710 typing.
710
711
711 3) Just like %sc -l, this is a list with special attributes:
712 3) Just like %sc -l, this is a list with special attributes:
712 ::
713 ::
713
714
714 .l (or .list) : value as list.
715 .l (or .list) : value as list.
715 .n (or .nlstr): value as newline-separated string.
716 .n (or .nlstr): value as newline-separated string.
716 .s (or .spstr): value as whitespace-separated string.
717 .s (or .spstr): value as whitespace-separated string.
717
718
718 This is very useful when trying to use such lists as arguments to
719 This is very useful when trying to use such lists as arguments to
719 system commands."""
720 system commands."""
720
721
721 if cell is None:
722 if cell is None:
722 # line magic
723 # line magic
723 return self.shell.getoutput(line)
724 return self.shell.getoutput(line)
724 else:
725 else:
725 opts,args = self.parse_options(line, '', 'out=')
726 opts,args = self.parse_options(line, '', 'out=')
726 output = self.shell.getoutput(cell)
727 output = self.shell.getoutput(cell)
727 out_name = opts.get('out', opts.get('o'))
728 out_name = opts.get('out', opts.get('o'))
728 if out_name:
729 if out_name:
729 self.shell.user_ns[out_name] = output
730 self.shell.user_ns[out_name] = output
730 else:
731 else:
731 return output
732 return output
732
733
733 system = line_cell_magic('system')(sx)
734 system = line_cell_magic('system')(sx)
734 bang = cell_magic('!')(sx)
735 bang = cell_magic('!')(sx)
735
736
736 @line_magic
737 @line_magic
737 def bookmark(self, parameter_s=''):
738 def bookmark(self, parameter_s=''):
738 """Manage IPython's bookmark system.
739 """Manage IPython's bookmark system.
739
740
740 %bookmark <name> - set bookmark to current dir
741 %bookmark <name> - set bookmark to current dir
741 %bookmark <name> <dir> - set bookmark to <dir>
742 %bookmark <name> <dir> - set bookmark to <dir>
742 %bookmark -l - list all bookmarks
743 %bookmark -l - list all bookmarks
743 %bookmark -d <name> - remove bookmark
744 %bookmark -d <name> - remove bookmark
744 %bookmark -r - remove all bookmarks
745 %bookmark -r - remove all bookmarks
745
746
746 You can later on access a bookmarked folder with::
747 You can later on access a bookmarked folder with::
747
748
748 %cd -b <name>
749 %cd -b <name>
749
750
750 or simply '%cd <name>' if there is no directory called <name> AND
751 or simply '%cd <name>' if there is no directory called <name> AND
751 there is such a bookmark defined.
752 there is such a bookmark defined.
752
753
753 Your bookmarks persist through IPython sessions, but they are
754 Your bookmarks persist through IPython sessions, but they are
754 associated with each profile."""
755 associated with each profile."""
755
756
756 opts,args = self.parse_options(parameter_s,'drl',mode='list')
757 opts,args = self.parse_options(parameter_s,'drl',mode='list')
757 if len(args) > 2:
758 if len(args) > 2:
758 raise UsageError("%bookmark: too many arguments")
759 raise UsageError("%bookmark: too many arguments")
759
760
760 bkms = self.shell.db.get('bookmarks',{})
761 bkms = self.shell.db.get('bookmarks',{})
761
762
762 if 'd' in opts:
763 if 'd' in opts:
763 try:
764 try:
764 todel = args[0]
765 todel = args[0]
765 except IndexError as e:
766 except IndexError as e:
766 raise UsageError(
767 raise UsageError(
767 "%bookmark -d: must provide a bookmark to delete") from e
768 "%bookmark -d: must provide a bookmark to delete") from e
768 else:
769 else:
769 try:
770 try:
770 del bkms[todel]
771 del bkms[todel]
771 except KeyError as e:
772 except KeyError as e:
772 raise UsageError(
773 raise UsageError(
773 "%%bookmark -d: Can't delete bookmark '%s'" % todel) from e
774 "%%bookmark -d: Can't delete bookmark '%s'" % todel) from e
774
775
775 elif 'r' in opts:
776 elif 'r' in opts:
776 bkms = {}
777 bkms = {}
777 elif 'l' in opts:
778 elif 'l' in opts:
778 bks = sorted(bkms)
779 bks = sorted(bkms)
779 if bks:
780 if bks:
780 size = max(map(len, bks))
781 size = max(map(len, bks))
781 else:
782 else:
782 size = 0
783 size = 0
783 fmt = '%-'+str(size)+'s -> %s'
784 fmt = '%-'+str(size)+'s -> %s'
784 print('Current bookmarks:')
785 print('Current bookmarks:')
785 for bk in bks:
786 for bk in bks:
786 print(fmt % (bk, bkms[bk]))
787 print(fmt % (bk, bkms[bk]))
787 else:
788 else:
788 if not args:
789 if not args:
789 raise UsageError("%bookmark: You must specify the bookmark name")
790 raise UsageError("%bookmark: You must specify the bookmark name")
790 elif len(args)==1:
791 elif len(args)==1:
791 bkms[args[0]] = os.getcwd()
792 bkms[args[0]] = os.getcwd()
792 elif len(args)==2:
793 elif len(args)==2:
793 bkms[args[0]] = args[1]
794 bkms[args[0]] = args[1]
794 self.shell.db['bookmarks'] = bkms
795 self.shell.db['bookmarks'] = bkms
795
796
796 @line_magic
797 @line_magic
797 def pycat(self, parameter_s=''):
798 def pycat(self, parameter_s=''):
798 """Show a syntax-highlighted file through a pager.
799 """Show a syntax-highlighted file through a pager.
799
800
800 This magic is similar to the cat utility, but it will assume the file
801 This magic is similar to the cat utility, but it will assume the file
801 to be Python source and will show it with syntax highlighting.
802 to be Python source and will show it with syntax highlighting.
802
803
803 This magic command can either take a local filename, an url,
804 This magic command can either take a local filename, an url,
804 an history range (see %history) or a macro as argument.
805 an history range (see %history) or a macro as argument.
805
806
806 If no parameter is given, prints out history of current session up to
807 If no parameter is given, prints out history of current session up to
807 this point. ::
808 this point. ::
808
809
809 %pycat myscript.py
810 %pycat myscript.py
810 %pycat 7-27
811 %pycat 7-27
811 %pycat myMacro
812 %pycat myMacro
812 %pycat http://www.example.com/myscript.py
813 %pycat http://www.example.com/myscript.py
813 """
814 """
814 try:
815 try:
815 cont = self.shell.find_user_code(parameter_s, skip_encoding_cookie=False)
816 cont = self.shell.find_user_code(parameter_s, skip_encoding_cookie=False)
816 except (ValueError, IOError):
817 except (ValueError, IOError):
817 print("Error: no such file, variable, URL, history range or macro")
818 print("Error: no such file, variable, URL, history range or macro")
818 return
819 return
819
820
820 page.page(self.shell.pycolorize(source_to_unicode(cont)))
821 page.page(self.shell.pycolorize(source_to_unicode(cont)))
821
822
822 @magic_arguments.magic_arguments()
823 @magic_arguments.magic_arguments()
823 @magic_arguments.argument(
824 @magic_arguments.argument(
824 '-a', '--append', action='store_true', default=False,
825 '-a', '--append', action='store_true', default=False,
825 help='Append contents of the cell to an existing file. '
826 help='Append contents of the cell to an existing file. '
826 'The file will be created if it does not exist.'
827 'The file will be created if it does not exist.'
827 )
828 )
828 @magic_arguments.argument(
829 @magic_arguments.argument(
829 'filename', type=str,
830 'filename', type=str,
830 help='file to write'
831 help='file to write'
831 )
832 )
832 @cell_magic
833 @cell_magic
833 def writefile(self, line, cell):
834 def writefile(self, line, cell):
834 """Write the contents of the cell to a file.
835 """Write the contents of the cell to a file.
835
836
836 The file will be overwritten unless the -a (--append) flag is specified.
837 The file will be overwritten unless the -a (--append) flag is specified.
837 """
838 """
838 args = magic_arguments.parse_argstring(self.writefile, line)
839 args = magic_arguments.parse_argstring(self.writefile, line)
839 if re.match(r'^(\'.*\')|(".*")$', args.filename):
840 if re.match(r'^(\'.*\')|(".*")$', args.filename):
840 filename = os.path.expanduser(args.filename[1:-1])
841 filename = os.path.expanduser(args.filename[1:-1])
841 else:
842 else:
842 filename = os.path.expanduser(args.filename)
843 filename = os.path.expanduser(args.filename)
843
844
844 if os.path.exists(filename):
845 if os.path.exists(filename):
845 if args.append:
846 if args.append:
846 print("Appending to %s" % filename)
847 print("Appending to %s" % filename)
847 else:
848 else:
848 print("Overwriting %s" % filename)
849 print("Overwriting %s" % filename)
849 else:
850 else:
850 print("Writing %s" % filename)
851 print("Writing %s" % filename)
851
852
852 mode = 'a' if args.append else 'w'
853 mode = 'a' if args.append else 'w'
853 with io.open(filename, mode, encoding='utf-8') as f:
854 with io.open(filename, mode, encoding='utf-8') as f:
854 f.write(cell)
855 f.write(cell)
General Comments 0
You need to be logged in to leave comments. Login now