Skip to content

Commit

Permalink
Merge pull request ipython#1855 from minrk/moremagics
Browse files Browse the repository at this point in the history
%%script and %%file magics

* `%%file` writes to a file (-f to force overwrite)
* `%%script` runs a cell with a particular script

The ScriptMagics also defines a few common magics that wrap `%%script` with common interpreters, such as `%%bash` by default, and this list, as well as the full path for each, is configurable.

For fun, the `%%script` magic is also presented as `%%!`.
  • Loading branch information
fperez committed Jun 11, 2012
2 parents 61821e3 + d8fce62 commit 88a82b5
Show file tree
Hide file tree
Showing 7 changed files with 826 additions and 16 deletions.
3 changes: 2 additions & 1 deletion IPython/core/interactiveshell.py
Expand Up @@ -2017,7 +2017,8 @@ def init_magics(self):
self.register_magics(m.AutoMagics, m.BasicMagics, m.CodeMagics,
m.ConfigMagics, m.DeprecatedMagics, m.ExecutionMagics,
m.ExtensionMagics, m.HistoryMagics, m.LoggingMagics,
m.NamespaceMagics, m.OSMagics, m.PylabMagics )
m.NamespaceMagics, m.OSMagics, m.PylabMagics, m.ScriptMagics,
)

# FIXME: Move the color initialization to the DisplayHook, which
# should be split into a prompt manager and displayhook. We probably
Expand Down
8 changes: 4 additions & 4 deletions IPython/core/magic.py
Expand Up @@ -30,7 +30,7 @@
from IPython.utils.ipstruct import Struct
from IPython.utils.process import arg_split
from IPython.utils.text import dedent
from IPython.utils.traitlets import Bool, Dict, Instance
from IPython.utils.traitlets import Bool, Dict, Instance, MetaHasTraits
from IPython.utils.warn import error, warn

#-----------------------------------------------------------------------------
Expand Down Expand Up @@ -365,10 +365,10 @@ def register(self, *magic_objects):
for m in magic_objects:
if not m.registered:
raise ValueError("Class of magics %r was constructed without "
"the @register_macics class decorator")
if type(m) is type:
"the @register_magics class decorator")
if type(m) in (type, MetaHasTraits):
# If we're given an uninstantiated class
m = m(self.shell)
m = m(shell=self.shell)

# Now that we have an instance, we can register it and update the
# table of callables
Expand Down
1 change: 1 addition & 0 deletions IPython/core/magics/__init__.py
Expand Up @@ -25,6 +25,7 @@
from .namespace import NamespaceMagics
from .osm import OSMagics
from .pylab import PylabMagics
from .script import ScriptMagics

#-----------------------------------------------------------------------------
# Magic implementation classes
Expand Down
64 changes: 54 additions & 10 deletions IPython/core/magics/osm.py
Expand Up @@ -16,17 +16,20 @@
#-----------------------------------------------------------------------------

# Stdlib
import io
import os
import re
import sys
from pprint import pformat

# Our own packages
from IPython.core import magic_arguments
from IPython.core import oinspect
from IPython.core import page
from IPython.core.error import UsageError
from IPython.core.magic import (Magics, compress_dhist, magics_class,
line_magic)
from IPython.core.error import UsageError, StdinNotImplementedError
from IPython.core.magic import (
Magics, compress_dhist, magics_class, line_magic, cell_magic, line_cell_magic
)
from IPython.testing.skipdoctest import skip_doctest
from IPython.utils.io import file_read, nlprint
from IPython.utils.path import get_py_filename, unquote_filename
Expand Down Expand Up @@ -432,7 +435,7 @@ def dhist(self, parameter_s=''):
@skip_doctest
@line_magic
def sc(self, parameter_s=''):
"""Shell capture - execute a shell command and capture its output.
"""Shell capture - run shell command and capture output (DEPRECATED use !).
DEPRECATED. Suboptimal, retained for backwards compatibility.
Expand Down Expand Up @@ -545,9 +548,9 @@ def sc(self, parameter_s=''):
else:
return out

@line_magic
def sx(self, parameter_s=''):
"""Shell execute - run a shell command and capture its output.
@line_cell_magic
def sx(self, line='', cell=None):
"""Shell execute - run shell command and capture output (!! is short-hand).
%sx command
Expand Down Expand Up @@ -586,10 +589,21 @@ def sx(self, parameter_s=''):
This is very useful when trying to use such lists as arguments to
system commands."""

if cell is None:
# line magic
return self.shell.getoutput(line)
else:
opts,args = self.parse_options(line, '', 'out=')
output = self.shell.getoutput(cell)
out_name = opts.get('out', opts.get('o'))
if out_name:
self.shell.user_ns[out_name] = output
else:
return output

if parameter_s:
return self.shell.getoutput(parameter_s)

system = line_cell_magic('system')(sx)
bang = cell_magic('!')(sx)

@line_magic
def bookmark(self, parameter_s=''):
Expand Down Expand Up @@ -675,3 +689,33 @@ def pycat(self, parameter_s=''):
return

page.page(self.shell.pycolorize(cont))

@magic_arguments.magic_arguments()
@magic_arguments.argument(
'-a', '--amend', action='store_true', default=False,
help='Open file for amending if it exists'
)
@magic_arguments.argument(
'filename', type=unicode,
help='file to write'
)
@cell_magic
def file(self, line, cell):
"""Write the contents of the cell to a file.
For frontends that do not support stdin (Notebook), -f is implied.
"""
args = magic_arguments.parse_argstring(self.file, line)
filename = unquote_filename(args.filename)

if os.path.exists(filename):
if args.amend:
print "Amending to %s" % filename
else:
print "Overwriting %s" % filename
else:
print "Writing %s" % filename

mode = 'a' if args.amend else 'w'
with io.open(filename, mode, encoding='utf-8') as f:
f.write(cell)
215 changes: 215 additions & 0 deletions IPython/core/magics/script.py
@@ -0,0 +1,215 @@
"""Magic functions for running cells in various scripts."""
#-----------------------------------------------------------------------------
# Copyright (c) 2012 The IPython Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file COPYING.txt, distributed with this software.
#-----------------------------------------------------------------------------

#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------

# Stdlib
import os
import re
import sys
from subprocess import Popen, PIPE

# Our own packages
from IPython.config.configurable import Configurable
from IPython.core import magic_arguments
from IPython.core.error import UsageError
from IPython.core.magic import (
Magics, magics_class, line_magic, cell_magic
)
from IPython.lib.backgroundjobs import BackgroundJobManager
from IPython.testing.skipdoctest import skip_doctest
from IPython.utils import py3compat
from IPython.utils.process import find_cmd, FindCmdError, arg_split
from IPython.utils.traitlets import List, Dict

#-----------------------------------------------------------------------------
# Magic implementation classes
#-----------------------------------------------------------------------------

def script_args(f):
"""single decorator for adding script args"""
args = [
magic_arguments.argument(
'--out', type=str,
help="""The variable in which to store stdout from the script.
If the script is backgrounded, this will be the stdout *pipe*,
instead of the stderr text itself.
"""
),
magic_arguments.argument(
'--err', type=str,
help="""The variable in which to store stderr from the script.
If the script is backgrounded, this will be the stderr *pipe*,
instead of the stderr text itself.
"""
),
magic_arguments.argument(
'--bg', action="store_true",
help="""Whether to run the script in the background.
If given, the only way to see the output of the command is
with --out/err.
"""
),
]
for arg in args:
f = arg(f)
return f

@magics_class
class ScriptMagics(Magics, Configurable):
"""Magics for talking to scripts
This defines a base `%%script` cell magic for running a cell
with a program in a subprocess, and registers a few top-level
magics that call %%script with common interpreters.
"""
script_magics = List(config=True,
help="""Extra script cell magics to define
This generates simple wrappers of `%%script foo` as `%%foo`.
If you want to add script magics that aren't on your path,
specify them in script_paths
""",
)
def _script_magics_default(self):
"""default to a common list of programs if we find them"""

defaults = []
to_try = []
if os.name == 'nt':
defaults.append('cmd')
to_try.append('powershell')
to_try.extend([
'sh',
'bash',
'perl',
'ruby',
'python3',
'pypy',
])

for cmd in to_try:
if cmd in self.script_paths:
defaults.append(cmd)
else:
try:
find_cmd(cmd)
except FindCmdError:
# command not found, ignore it
pass
except ImportError:
# Windows without pywin32, find_cmd doesn't work
pass
else:
defaults.append(cmd)
return defaults

script_paths = Dict(config=True,
help="""Dict mapping short 'ruby' names to full paths, such as '/opt/secret/bin/ruby'
Only necessary for items in script_magics where the default path will not
find the right interpreter.
"""
)

def __init__(self, shell=None):
Configurable.__init__(self, config=shell.config)
self._generate_script_magics()
Magics.__init__(self, shell=shell)
self.job_manager = BackgroundJobManager()

def _generate_script_magics(self):
cell_magics = self.magics['cell']
for name in self.script_magics:
cell_magics[name] = self._make_script_magic(name)

def _make_script_magic(self, name):
"""make a named magic, that calls %%script with a particular program"""
# expand to explicit path if necessary:
script = self.script_paths.get(name, name)

@magic_arguments.magic_arguments()
@script_args
def named_script_magic(line, cell):
# if line, add it as cl-flags
if line:
line = "%s %s" % (script, line)
else:
line = script
return self.shebang(line, cell)

# write a basic docstring:
named_script_magic.__doc__ = \
"""%%{name} script magic
Run cells with {script} in a subprocess.
This is a shortcut for `%%script {script}`
""".format(**locals())

return named_script_magic

@magic_arguments.magic_arguments()
@script_args
@cell_magic("script")
def shebang(self, line, cell):
"""Run a cell via a shell command
The `%%script` line is like the #! line of script,
specifying a program (bash, perl, ruby, etc.) with which to run.
The rest of the cell is run by that program.
Examples
--------
::
In [1]: %%script bash
...: for i in 1 2 3; do
...: echo $i
...: done
1
2
3
"""
argv = arg_split(line, posix = not sys.platform.startswith('win'))
args, cmd = self.shebang.parser.parse_known_args(argv)

p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE)

if args.bg:
if args.out:
self.shell.user_ns[args.out] = p.stdout
if args.err:
self.shell.user_ns[args.err] = p.stderr
self.job_manager.new(self._run_script, p, cell)
return

out, err = p.communicate(cell)
out = py3compat.bytes_to_str(out)
err = py3compat.bytes_to_str(err)
if args.out:
self.shell.user_ns[args.out] = out
else:
sys.stdout.write(out)
sys.stdout.flush()
if args.err:
self.shell.user_ns[args.err] = err
else:
sys.stderr.write(err)
sys.stderr.flush()

def _run_script(self, p, cell):
"""callback for running the script in the background"""
p.stdin.write(cell)
p.stdin.close()
p.wait()

0 comments on commit 88a82b5

Please sign in to comment.