From: <jd...@us...> - 2010-12-13 15:27:58
|
Revision: 8830 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=8830&view=rev Author: jdh2358 Date: 2010-12-13 15:27:48 +0000 (Mon, 13 Dec 2010) Log Message: ----------- added ipython directive Added Paths: ----------- branches/v1_0_maint/lib/matplotlib/sphinxext/ipython_directive.py Added: branches/v1_0_maint/lib/matplotlib/sphinxext/ipython_directive.py =================================================================== --- branches/v1_0_maint/lib/matplotlib/sphinxext/ipython_directive.py (rev 0) +++ branches/v1_0_maint/lib/matplotlib/sphinxext/ipython_directive.py 2010-12-13 15:27:48 UTC (rev 8830) @@ -0,0 +1,567 @@ +import sys, os, shutil, imp, warnings, cStringIO, re + +import IPython +from IPython.Shell import MatplotlibShell + +try: + from hashlib import md5 +except ImportError: + from md5 import md5 + +from docutils.parsers.rst import directives +import sphinx + + +sphinx_version = sphinx.__version__.split(".") +# The split is necessary for sphinx beta versions where the string is +# '6b1' +sphinx_version = tuple([int(re.split('[a-z]', x)[0]) + for x in sphinx_version[:2]]) + + + +COMMENT, INPUT, OUTPUT = range(3) +rgxin = re.compile('In \[(\d+)\]:\s?(.*)\s*') +rgxout = re.compile('Out\[(\d+)\]:\s?(.*)\s*') +fmtin = 'In [%d]:' +fmtout = 'Out[%d]:' + +def block_parser(part): + """ + part is a string of ipython text, comprised of at most one + input, one ouput, comments, and blank lines. The block parser + parses the text into a list of:: + + blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...] + where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and + data is, depending on the type of token:: + + COMMENT : the comment string + + INPUT: the (DECORATOR, INPUT_LINE, REST) where + DECORATOR: the input decorator (or None) + INPUT_LINE: the input as string (possibly multi-line) + REST : any stdout generated by the input line (not OUTPUT) + + + OUTPUT: the output string, possibly multi-line + + """ + + block = [] + lines = part.split('\n') + #print 'PARSE', lines + N = len(lines) + i = 0 + decorator = None + while 1: + + if i==N: + # nothing left to parse -- the last line + break + + line = lines[i] + i += 1 + line_stripped = line.strip() + if line_stripped.startswith('#'): + block.append((COMMENT, line)) + continue + + + if line_stripped.startswith('@'): + # we're assuming at most one decorator -- may need to + # rethink + decorator = line_stripped + continue + + # does this look like an input line? + matchin = rgxin.match(line) + if matchin: + lineno, inputline = int(matchin.group(1)), matchin.group(2) + + # the ....: continuation string + continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2)) + Nc = len(continuation) + # input lines can continue on for more than one line, if + # we have a '\' line continuation char or a function call + # echo line 'print'. The input line can only be + # terminated by the end of the block or an output line, so + # we parse out the rest of the input line if it is + # multiline as well as any echo text + + rest = [] + while i<N: + + # look ahead; if the next line is blank, or a comment, or + # an output line, we're done + + nextline = lines[i] + matchout = rgxout.match(nextline) + #print "nextline=%s, continuation=%s, starts=%s"%(nextline, continuation, nextline.startswith(continuation)) + if matchout or nextline.startswith('#'): + break + elif nextline.startswith(continuation): + inputline += '\n' + nextline[Nc:] + else: + rest.append(nextline) + i+= 1 + + block.append((INPUT, (decorator, inputline, '\n'.join(rest)))) + continue + + # if it looks like an output line grab all the text to the end + # of the block + matchout = rgxout.match(line) + if matchout: + lineno, output = int(matchout.group(1)), matchout.group(2) + if i<N-1: + output = '\n'.join([output] + lines[i:]) + + #print 'OUTPUT', output + block.append((OUTPUT, output)) + break + + #print 'returning block', block + return block + + + +import matplotlib +matplotlib.use('Agg') + + +class EmbeddedSphinxShell: + + def __init__(self): + + self.cout = cStringIO.StringIO() + + IPython.Shell.Term.cout = self.cout + IPython.Shell.Term.cerr = self.cout + argv = ['-autocall', '0'] + self.user_ns = {} + self.user_glocal_ns = {} + + self.IP = IPython.ipmaker.make_IPython( + argv, self.user_ns, self.user_glocal_ns, embedded=True, + #shell_class=IPython.Shell.InteractiveShell, + shell_class=MatplotlibShell, + rc_override=dict(colors = 'NoColor')) + + self.input = '' + self.output = '' + + self.is_verbatim = False + self.is_doctest = False + self.is_suppress = False + + # on the first call to the savefig decorator, we'll import + # pyplot as plt so we can make a call to the plt.gcf().savefig + self._pyplot_imported = False + + # we need bookmark the current dir first so we can save + # relative to it + self.process_input('bookmark ipy_basedir') + self.cout.seek(0) + self.cout.truncate(0) + + def process_input(self, line): + 'process the input, capturing stdout' + #print "input='%s'"%self.input + stdout = sys.stdout + sys.stdout = self.cout + #self.IP.resetbuffer() + self.IP.push(self.IP.prefilter(line, 0)) + #self.IP.runlines(line) + sys.stdout = stdout + + + def process_block(self, block): + """ + process block from the block_parser and return a list of processed lines + """ + + #print 'BLOCK', block + ret = [] + + output = None + input_lines = None + + m = rgxin.match(str(self.IP.outputcache.prompt1).strip()) + lineno = int(m.group(1)) + + input_prompt = fmtin%lineno + output_prompt = fmtout%lineno + image_file = None + image_directive = None + for token, data in block: + + if token==COMMENT: + if not self.is_suppress: + ret.append(data) + + elif token==INPUT: + + decorator, input, rest = data + #print 'INPUT:', data + is_verbatim = decorator=='@verbatim' or self.is_verbatim + is_doctest = decorator=='@doctest' or self.is_doctest + is_suppress = decorator=='@suppress' or self.is_suppress + is_savefig = decorator is not None and decorator.startswith('@savefig') + #print 'is_verbatim=%s, is_doctest=%s, is_suppress=%s, is_savefig=%s'%(is_verbatim, is_doctest, is_suppress, is_savefig) + input_lines = input.split('\n') + + + continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2)) + Nc = len(continuation) + + if is_savefig: + saveargs = decorator.split(' ') + filename = saveargs[1] + outfile = os.path.join('_static/%s'%filename) + # build out an image directive like + # .. image:: somefile.png + # :width 4in + # + # from an input like + # savefig somefile.png width=4in + imagerows = ['.. image:: %s'%outfile] + + for kwarg in saveargs[2:]: + arg, val = kwarg.split('=') + arg = arg.strip() + val = val.strip() + imagerows.append(' :%s: %s'%(arg, val)) + + + image_file = outfile + image_directive = '\n'.join(imagerows) + + + + # TODO: can we get "rest" from ipython + #self.process_input('\n'.join(input_lines)) + + + is_semicolon = False + for i, line in enumerate(input_lines): + if line.endswith(';'): + is_semicolon = True + + if i==0: + # process the first input line + if is_verbatim: + self.process_input('') + else: + # only submit the line in non-verbatim mode + self.process_input(line) + formatted_line = '%s %s'%(input_prompt, line) + else: + # process a continuation line + if not is_verbatim: + self.process_input(line) + + formatted_line = '%s %s'%(continuation, line) + + + if not is_suppress: + ret.append(formatted_line) + + if not is_suppress: + if len(rest.strip()): + if is_verbatim: + # the "rest" is the standard output of the + # input, which needs to be added in + # verbatim mode + ret.append("%s"%rest) + ret.append('') + + self.cout.seek(0) + output = self.cout.read() + if not is_suppress and not is_semicolon and not is_verbatim: + ret.append(output) + + self.cout.truncate(0) + + + + + elif token==OUTPUT: + #print 'token==OUTPUT is_verbatim=%s'%is_verbatim + if is_verbatim: + # construct a mock output prompt + output = '%s %s\n'%(fmtout%lineno, data) + ret.append(output) + + #print 'token==OUTPUT', output + if is_doctest: + submitted = data.strip() + found = output + if found is not None: + ind = found.find(output_prompt) + if ind<0: + raise RuntimeError('output prompt="%s" does not match out line=%s'%(output_prompt, found)) + found = found[len(output_prompt):].strip() + + if found!=submitted: + raise RuntimeError('doctest failure for input_lines="%s" with found_output="%s" and submitted output="%s"'%(input_lines, found, submitted)) + #print 'doctest PASSED for input_lines="%s" with found_output="%s" and submitted output="%s"'%(input_lines, found, submitted) + + + if image_file is not None: + self.insure_pyplot() + command = 'plt.gcf().savefig("%s")'%image_file + #print 'SAVEFIG', command + self.process_input('bookmark ipy_thisdir') + self.process_input('cd -b ipy_basedir') + self.process_input(command) + self.process_input('cd -b ipy_thisdir') + self.cout.seek(0) + self.cout.truncate(0) + + #print 'returning', ret, figure + return ret, image_directive + + + def insure_pyplot(self): + if self._pyplot_imported: + return + self.process_input('import matplotlib.pyplot as plt') + + + +shell = EmbeddedSphinxShell() + + +def ipython_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine, + ): + + debug = ipython_directive.DEBUG + shell.is_suppress = options.has_key('suppress') + shell.is_doctest = options.has_key('doctest') + shell.is_verbatim = options.has_key('verbatim') + + #print 'ipy', shell.is_suppress, options + parts = '\n'.join(content).split('\n\n') + lines = ['.. sourcecode:: ipython', ''] + + figures = [] + for part in parts: + block = block_parser(part) + + if len(block): + rows, figure = shell.process_block(block) + for row in rows: + lines.extend([' %s'%line for line in row.split('\n')]) + + if figure is not None: + figures.append(figure) + + for figure in figures: + lines.append('') + lines.extend(figure.split('\n')) + lines.append('') + + #print lines + if len(lines)>2: + if debug: + print '\n'.join(lines) + else: + #print 'INSERTING %d lines'%len(lines) + state_machine.insert_input( + lines, state_machine.input_lines.source(0)) + + return [] + +ipython_directive.DEBUG = False + +def setup(app): + setup.app = app + options = { + 'suppress': directives.flag, + 'doctest': directives.flag, + 'verbatim': directives.flag, + } + + + app.add_directive('ipython', ipython_directive, True, (0, 2, 0), **options) + + +def test(): + + examples = [ + r""" +In [9]: pwd +Out[9]: '/home/jdhunter/py4science/book' + +In [10]: cd bookdata/ +/home/jdhunter/py4science/book/bookdata + +In [2]: from pylab import * + +In [2]: ion() + +In [3]: im = imread('stinkbug.png') + +@savefig mystinkbug.png width=4in +In [4]: imshow(im) +Out[4]: <matplotlib.image.AxesImage object at 0x39ea850> + +""", + r""" + +In [1]: x = 'hello world' + +# string methods can be +# used to alter the string +@doctest +In [2]: x.upper() +Out[2]: 'HELLO WORLD' + +@verbatim +In [3]: x.st<TAB> +x.startswith x.strip +""", + r""" + +In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\ + .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv' + +In [131]: print url.split('&') +--------> print(url.split('&')) +['http://ichart.finance.yahoo.com/table.csv?s=CROX', 'd=9', 'e=22', 'f=2009', 'g=d', 'a=1', 'b=8', 'c=2006', 'ignore=.csv'] + +In [60]: import urllib + +""", + r"""\ + +In [133]: import numpy.random + +@suppress +In [134]: numpy.random.seed(2358) + +@doctest +In [135]: np.random.rand(10,2) +Out[135]: +array([[ 0.64524308, 0.59943846], + [ 0.47102322, 0.8715456 ], + [ 0.29370834, 0.74776844], + [ 0.99539577, 0.1313423 ], + [ 0.16250302, 0.21103583], + [ 0.81626524, 0.1312433 ], + [ 0.67338089, 0.72302393], + [ 0.7566368 , 0.07033696], + [ 0.22591016, 0.77731835], + [ 0.0072729 , 0.34273127]]) + +""", + + r""" +In [106]: print x +--------> print(x) +jdh + +In [109]: for i in range(10): + .....: print i + .....: + .....: +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 + + +""", + + r""" + +In [144]: from pylab import * + +In [145]: ion() + +# use a semicolon to suppress the output +@savefig test_hist.png width=4in +In [151]: hist(np.random.randn(10000), 100); + + +@savefig test_plot.png width=4in +In [151]: plot(np.random.randn(10000), 'o'); + """, + + r""" +# use a semicolon to suppress the output +In [151]: plt.clf() + +@savefig plot_simple.png width=4in +In [151]: plot([1,2,3]) + +@savefig hist_simple.png width=4in +In [151]: hist(np.random.randn(10000), 100); + +""", + r""" +# update the current fig +In [151]: ylabel('number') + +In [152]: title('normal distribution') + + +@savefig hist_with_text.png +In [153]: grid(True) + + """, + + + r""" + +In [239]: 1/2 +@verbatim +Out[239]: 0 + +In [240]: 1.0/2.0 +Out[240]: 0.5 +""", + + r""" +@verbatim +In [6]: pwd +Out[6]: '/home/jdhunter/mypy' +""", + + r""" +@verbatim +In [151]: myfile.upper? +Type: builtin_function_or_method +Base Class: <type 'builtin_function_or_method'> +String Form: <built-in method upper of str object at 0x980e2f0> +Namespace: Interactive +Docstring: + S.upper() -> string + Return a copy of the string S converted to uppercase. + """ + ] + + + + ipython_directive.DEBUG = True + #options = dict(suppress=True) + options = dict() + for example in examples: + content = example.split('\n') + ipython_directive('debug', arguments=None, options=options, + content=content, lineno=0, + content_offset=None, block_text=None, + state=None, state_machine=None, + ) + + +if __name__=='__main__': + test() This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |