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