diff --git a/IPython/core/history.py b/IPython/core/history.py index 0c77a1c..c911a52 100644 --- a/IPython/core/history.py +++ b/IPython/core/history.py @@ -454,7 +454,7 @@ range_re = re.compile(r""" ((?P[\-:]) ((?P~?\d+)/)? (?P\d+))? -""", re.VERBOSE) +$""", re.VERBOSE) def extract_hist_ranges(ranges_str): """Turn a string of history ranges into 3-tuples of (session, start, stop). diff --git a/IPython/core/macro.py b/IPython/core/macro.py index f6af9f1..f017f09 100644 --- a/IPython/core/macro.py +++ b/IPython/core/macro.py @@ -7,8 +7,13 @@ # the file COPYING, distributed as part of this software. #***************************************************************************** +import re +import sys + import IPython.utils.io +coding_declaration = re.compile(r"#\s*coding[:=]\s*([-\w.]+)") + class Macro(object): """Simple class to store the value of macros as strings. @@ -20,9 +25,24 @@ class Macro(object): def __init__(self,code): """store the macro value, as a single string which can be executed""" - self.value = code.rstrip()+'\n' - + lines = [] + enc = None + for line in code.splitlines(): + coding_match = coding_declaration.match(line) + if coding_match: + enc = coding_match.group(1) + else: + lines.append(line) + code = "\n".join(lines) + if isinstance(code, bytes): + code = code.decode(enc or sys.getdefaultencoding()) + self.value = code + '\n' + def __str__(self): + enc = sys.stdin.encoding or sys.getdefaultencoding() + return self.value.encode(enc, "replace") + + def __unicode__(self): return self.value def __repr__(self): diff --git a/IPython/core/magic.py b/IPython/core/magic.py index f7096fb..5ef3b31 100644 --- a/IPython/core/magic.py +++ b/IPython/core/magic.py @@ -28,6 +28,7 @@ import textwrap from cStringIO import StringIO from getopt import getopt,GetoptError from pprint import pformat +from xmlrpclib import ServerProxy # cProfile was added in Python2.5 try: @@ -1962,7 +1963,8 @@ Currently the magic system has the following functions:\n""" @testdec.skip_doctest def magic_macro(self,parameter_s = ''): - """Define a set of input lines as a macro for future re-execution. + """Define a macro for future re-execution. It can take ranges of history, + filenames or string objects. Usage:\\ %macro [options] name n1-n2 n3-n4 ... n5 .. n6 ... @@ -2014,12 +2016,8 @@ Currently the magic system has the following functions:\n""" You can view a macro's contents by explicitly printing it with: 'print macro_name'. - - For one-off cases which DON'T contain magic function calls in them you - can obtain similar results by explicitly executing slices from your - input history with: - - In [60]: exec In[44:48]+In[49]""" + + """ opts,args = self.parse_options(parameter_s,'r',mode='list') if not args: # List existing macros @@ -2028,18 +2026,22 @@ Currently the magic system has the following functions:\n""" if len(args) == 1: raise UsageError( "%macro insufficient args; usage '%macro name n1-n2 n3-4...") - name, ranges = args[0], " ".join(args[1:]) + name, codefrom = args[0], " ".join(args[1:]) #print 'rng',ranges # dbg - lines = self.extract_input_lines(ranges,'r' in opts) + try: + lines = self._get_some_code(codefrom, 'r' in opts) + except (ValueError, TypeError) as e: + print e.args[0] + return macro = Macro(lines) self.shell.define_macro(name, macro) print 'Macro `%s` created. To execute, type its name (without quotes).' % name - print 'Macro contents:' + print '=== Macro contents: ===' print macro, def magic_save(self,parameter_s = ''): - """Save a set of lines to a given filename. + """Save a set of lines or a macro to a given filename. Usage:\\ %save [options] filename n1-n2 n3-n4 ... n5 .. n6 ... @@ -2058,7 +2060,7 @@ Currently the magic system has the following functions:\n""" it asks for confirmation before overwriting existing files.""" opts,args = self.parse_options(parameter_s,'r',mode='list') - fname,ranges = args[0], " ".join(args[1:]) + fname, codefrom = args[0], " ".join(args[1:]) if not fname.endswith('.py'): fname += '.py' if os.path.isfile(fname): @@ -2066,30 +2068,54 @@ Currently the magic system has the following functions:\n""" if ans.lower() not in ['y','yes']: print 'Operation cancelled.' return - cmds = self.extract_input_lines(ranges, 'r' in opts) + try: + cmds = self._get_some_code(codefrom, 'r' in opts) + except (TypeError, ValueError) as e: + print e.args[0] + return + if isinstance(cmds, unicode): + cmds = cmds.encode("utf-8") with open(fname,'w') as f: f.write("# coding: utf-8\n") - f.write(cmds.encode("utf-8")) + f.write(cmds) print 'The following commands were written to file `%s`:' % fname print cmds + + def _get_some_code(self, target, raw=True): + """Utility function to get a code string, either from a range of + history lines, a filename, or an expression evaluating to a string or a + Macro in the user namespace. + + ValueError is raised if none are found, and TypeError if it evaluates to + an object of another type. In each case, .args[0] is a printable + message.""" + code = self.extract_input_lines(target, raw=raw) # Grab history + if code: + return code + if os.path.isfile(target): # Read file + return open(target, "r").read() + + try: # User namespace + codeobj = eval(target, self.shell.user_ns) + except Exception: + raise ValueError(("'%s' was not found in history, as a file, nor in" + " the user namespace.") % target) + if isinstance(codeobj, basestring): + return codeobj + elif isinstance(codeobj, Macro): + return codeobj.value + + raise TypeError("%s is neither a string nor a macro." % target, + codeobj) def magic_pastebin(self, parameter_s = ''): """Upload code to the 'Lodge it' paste bin, returning the URL.""" + try: + code = self._get_some_code(parameter_s) + except (ValueError, TypeError) as e: + print e.args[0] + return pbserver = ServerProxy('http://paste.pocoo.org/xmlrpc/') - code = self.extract_input_lines(parameter_s) - if not code: - try: - codeobj = eval(parameter_s, self.shell.user_ns) - except Exception: - codeobj = None - if isinstance(codeobj, str): - code = codeobj - elif isinstance(codeobj, Macro): - code = codeobj.value - else: - print parameter_s, ("was not recognised as a history range, nor" - " as a string or macro.") - return id = pbserver.pastes.newPaste("python", code) return "http://paste.pocoo.org/show/" + id