Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Magic pastebin #330

Merged
merged 7 commits into from

2 participants

Thomas Kluyver Fernando Perez
Thomas Kluyver
Owner

This started off with the simple idea of adding a %pastebin magic command. See http://paste.pocoo.org/show/364199/ for an example using that. You can select ranges from history, files, and string or macro objects. The URL is returned.

I've opted to call the magic command "pastebin" - it could be shorter, but all the shorter ones (pbin, lodgeit, post) are unintuitive, at least to me.

I've gone for the 'Lodge it' paste bin, run by Pocoo. It's got a convenient API (the API docs for gist are very incomplete) and a clean web interface (plus for bonus points it's open source and in Python). Of course, there could be multiple options, but I think that sort of complexity defeats the aim of a pastebin, and it would be more work to maintain, so I think it makes sense to pick a good default.

Along the way, I abstracted some of the logic to grab the code, so now macros can be saved with %save mm.py mymacro and reloaded with %macro mymacro mm.py.

IPython/core/magic.py
((16 lines not shown))
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
Fernando Perez Owner
fperez added a note

Docstring should honor the format

one-line summary
#BLANK LINE
a
longer
explanation
...

Parameters
--------------
foo : type
  explain foo
...
Thomas Kluyver Owner

OK, will do. Hadn't realised this format was needed for 'private' functions as well.

Fernando Perez Owner
fperez added a note

Well, if the function takes arguments and flags, even if it's private we are likely to want to use it at some point later, and without a docstring that explains what those flags mean we'll be stuck. Documenting the functions properly will help us know what our own functions do as time goes on. Furthermore, this function could be a good candidate for making public at some point (I'm actually thinking about that just now), case in which a proper docstring will be even more important.

We certainly haven't been perfect with that in the past, and with large swaths of code that get written quickly that tends to get a little lost, but when feasible we should try to remember this.

One last question: do you think this could have a better name? 'get some code' sounds just a bit vague and generic. This seems like a useful function to expose, but it might be good to find a more descriptive name for it.

Fernando Perez Owner
fperez added a note

name idea: find_user_code? or some variation thereof?

Thomas Kluyver Owner

I'm not sure that any (reasonably short) name will really make it clear what it's for, but I'll call it find_user_code if you prefer. Do you think I should lose the initial _?

Fernando Perez Owner
fperez added a note
Thomas Kluyver Owner

I think the use case would be defining new magics which can grab code in the same manner. So yes.

Fernando Perez Owner
fperez added a note

Then go for it! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/core/magic.py
((16 lines not shown))
print 'The following commands were written to file `%s`:' % fname
print cmds
+
+ def _get_some_code(self, target, raw=True):
Fernando Perez Owner
fperez added a note

Shouldn't this be rather a method of the main object? Eventually we want to move the main Magic class to not inherit from the main shell class (that's an artifact of history), so magics should all access what they need via the self.shell attribute. But this kind of method for code extraction seems better suited to the shell object itself.

Thomas Kluyver Owner

OK, I'll move it. I stuck it in here simply because it seemed most useful for magics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Thomas Kluyver
Owner

OK, I've made those changes.

Thomas Kluyver
Owner

@fperez: made that change. Any more points?

Fernando Perez
Owner

Go for it, thanks!

Thomas Kluyver takluyver merged commit ecd4bc3 into from
Damián Avila damianavila referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
2  IPython/core/history.py
View
@@ -454,7 +454,7 @@ def writeout_cache(self):
((?P<sep>[\-:])
((?P<endsess>~?\d+)/)?
(?P<end>\d+))?
-""", re.VERBOSE)
+$""", re.VERBOSE)
def extract_hist_ranges(ranges_str):
"""Turn a string of history ranges into 3-tuples of (session, start, stop).
43 IPython/core/interactiveshell.py
View
@@ -50,6 +50,7 @@
from IPython.core.history import HistoryManager
from IPython.core.inputsplitter import IPythonInputSplitter
from IPython.core.logger import Logger
+from IPython.core.macro import Macro
from IPython.core.magic import Magic
from IPython.core.payload import PayloadManager
from IPython.core.plugin import PluginManager
@@ -2503,6 +2504,48 @@ def ask_yes_no(self,prompt,default=True):
def show_usage(self):
"""Show a usage message"""
page.page(IPython.core.usage.interactive_usage)
+
+ def find_user_code(self, target, raw=True):
+ """Get a code string from history, file, or a string or macro.
+
+ This is mainly used by magic functions.
+
+ Parameters
+ ----------
+ target : str
+ A string specifying code to retrieve. This will be tried respectively
+ as: ranges of input history (see %history for syntax), a filename, or
+ an expression evaluating to a string or Macro in the user namespace.
+ raw : bool
+ If true (default), retrieve raw history. Has no effect on the other
+ retrieval mechanisms.
+
+ Returns
+ -------
+ A string of code.
+
+ ValueError is raised if nothing is 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.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)
#-------------------------------------------------------------------------
# Things related to IPython exiting
31 IPython/core/macro.py
View
@@ -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):
@@ -31,3 +51,10 @@ def __repr__(self):
def __getstate__(self):
""" needed for safe pickling via %store """
return {'value': self.value}
+
+ def __add__(self, other):
+ if isinstance(other, Macro):
+ return Macro(self.value + other.value)
+ elif isinstance(other, basestring):
+ return Macro(self.value + other)
+ raise TypeError
47 IPython/core/magic.py
View
@@ -28,6 +28,7 @@
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 @@ def magic_time(self,parameter_s = ''):
@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 accepts ranges of history,
+ filenames or string objects.
Usage:\\
%macro [options] name n1-n2 n3-n4 ... n5 .. n6 ...
@@ -2014,12 +2016,8 @@ def magic_macro(self,parameter_s = ''):
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 @@ def magic_macro(self,parameter_s = ''):
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.shell.find_user_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 @@ def magic_save(self,parameter_s = ''):
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,12 +2068,29 @@ def magic_save(self,parameter_s = ''):
if ans.lower() not in ['y','yes']:
print 'Operation cancelled.'
return
- cmds = self.extract_input_lines(ranges, 'r' in opts)
+ try:
+ cmds = self.shell.find_user_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 magic_pastebin(self, parameter_s = ''):
+ """Upload code to the 'Lodge it' paste bin, returning the URL."""
+ try:
+ code = self.shell.find_user_code(parameter_s)
+ except (ValueError, TypeError) as e:
+ print e.args[0]
+ return
+ pbserver = ServerProxy('http://paste.pocoo.org/xmlrpc/')
+ id = pbserver.pastes.newPaste("python", code)
+ return "http://paste.pocoo.org/show/" + id
def _edit_macro(self,mname,macro):
"""open an editor with the macro data in a file"""
Something went wrong with that request. Please try again.