##// END OF EJS Templates
Changes for demos and access to raw history in %macro, %save and %edit....
fperez -
Show More
@@ -1,7 +1,7 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Magic functions for InteractiveShell.
2 """Magic functions for InteractiveShell.
3
3
4 $Id: Magic.py 1121 2006-02-01 21:12:20Z vivainio $"""
4 $Id: Magic.py 1126 2006-02-06 02:31:40Z fperez $"""
5
5
6 #*****************************************************************************
6 #*****************************************************************************
7 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and
7 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and
@@ -98,7 +98,7 b' license. To use profiling, please install"python2.3-profiler" from non-free.""")'
98
98
99 def default_option(self,fn,optstr):
99 def default_option(self,fn,optstr):
100 """Make an entry in the options_table for fn, with value optstr"""
100 """Make an entry in the options_table for fn, with value optstr"""
101
101
102 if fn not in self.lsmagic():
102 if fn not in self.lsmagic():
103 error("%s is not a magic function" % fn)
103 error("%s is not a magic function" % fn)
104 self.options_table[fn] = optstr
104 self.options_table[fn] = optstr
@@ -129,12 +129,19 b' license. To use profiling, please install"python2.3-profiler" from non-free.""")'
129 out.sort()
129 out.sort()
130 return out
130 return out
131
131
132 def extract_input_slices(self,slices):
132 def extract_input_slices(self,slices,raw=False):
133 """Return as a string a set of input history slices.
133 """Return as a string a set of input history slices.
134
134
135 The set of slices is given as a list of strings (like ['1','4:8','9'],
135 Inputs:
136 since this function is for use by magic functions which get their
136
137 arguments as strings.
137 - slices: the set of slices is given as a list of strings (like
138 ['1','4:8','9'], since this function is for use by magic functions
139 which get their arguments as strings.
140
141 Optional inputs:
142
143 - raw(False): by default, the processed input is used. If this is
144 true, the raw input history is used instead.
138
145
139 Note that slices can be called with two notations:
146 Note that slices can be called with two notations:
140
147
@@ -142,6 +149,11 b' license. To use profiling, please install"python2.3-profiler" from non-free.""")'
142
149
143 N-M -> include items N..M (closed endpoint)."""
150 N-M -> include items N..M (closed endpoint)."""
144
151
152 if raw:
153 hist = self.shell.input_hist_raw
154 else:
155 hist = self.shell.input_hist
156
145 cmds = []
157 cmds = []
146 for chunk in slices:
158 for chunk in slices:
147 if ':' in chunk:
159 if ':' in chunk:
@@ -152,7 +164,7 b' license. To use profiling, please install"python2.3-profiler" from non-free.""")'
152 else:
164 else:
153 ini = int(chunk)
165 ini = int(chunk)
154 fin = ini+1
166 fin = ini+1
155 cmds.append(self.shell.input_hist[ini:fin])
167 cmds.append(hist[ini:fin])
156 return cmds
168 return cmds
157
169
158 def _ofind(self,oname):
170 def _ofind(self,oname):
@@ -1606,7 +1618,14 b' Currently the magic system has the following functions:\\n"""'
1606 """Define a set of input lines as a macro for future re-execution.
1618 """Define a set of input lines as a macro for future re-execution.
1607
1619
1608 Usage:\\
1620 Usage:\\
1609 %macro name n1-n2 n3-n4 ... n5 .. n6 ...
1621 %macro [options] name n1-n2 n3-n4 ... n5 .. n6 ...
1622
1623 Options:
1624
1625 -r: use 'raw' input. By default, the 'processed' history is used,
1626 so that magics are loaded in their transformed version to valid
1627 Python. If this option is given, the raw input as typed as the
1628 command line is used instead.
1610
1629
1611 This will define a global variable called `name` which is a string
1630 This will define a global variable called `name` which is a string
1612 made of joining the slices and lines you specify (n1,n2,... numbers
1631 made of joining the slices and lines you specify (n1,n2,... numbers
@@ -1657,10 +1676,10 b' Currently the magic system has the following functions:\\n"""'
1657
1676
1658 In [60]: exec In[44:48]+In[49]"""
1677 In [60]: exec In[44:48]+In[49]"""
1659
1678
1660 args = parameter_s.split()
1679 opts,args = self.parse_options(parameter_s,'r')
1661 name,ranges = args[0], args[1:]
1680 name,ranges = args[0], args[1:]
1662 #print 'rng',ranges # dbg
1681 #print 'rng',ranges # dbg
1663 lines = self.extract_input_slices(ranges)
1682 lines = self.extract_input_slices(ranges,opts.has_key('r'))
1664 macro = Macro(lines)
1683 macro = Macro(lines)
1665 self.shell.user_ns.update({name:macro})
1684 self.shell.user_ns.update({name:macro})
1666 print 'Macro `%s` created. To execute, type its name (without quotes).' % name
1685 print 'Macro `%s` created. To execute, type its name (without quotes).' % name
@@ -1671,7 +1690,14 b' Currently the magic system has the following functions:\\n"""'
1671 """Save a set of lines to a given filename.
1690 """Save a set of lines to a given filename.
1672
1691
1673 Usage:\\
1692 Usage:\\
1674 %save filename n1-n2 n3-n4 ... n5 .. n6 ...
1693 %save [options] filename n1-n2 n3-n4 ... n5 .. n6 ...
1694
1695 Options:
1696
1697 -r: use 'raw' input. By default, the 'processed' history is used,
1698 so that magics are loaded in their transformed version to valid
1699 Python. If this option is given, the raw input as typed as the
1700 command line is used instead.
1675
1701
1676 This function uses the same syntax as %macro for line extraction, but
1702 This function uses the same syntax as %macro for line extraction, but
1677 instead of creating a macro it saves the resulting string to the
1703 instead of creating a macro it saves the resulting string to the
@@ -1680,7 +1706,7 b' Currently the magic system has the following functions:\\n"""'
1680 It adds a '.py' extension to the file if you don't do so yourself, and
1706 It adds a '.py' extension to the file if you don't do so yourself, and
1681 it asks for confirmation before overwriting existing files."""
1707 it asks for confirmation before overwriting existing files."""
1682
1708
1683 args = parameter_s.split()
1709 opts,args = self.parse_options(parameter_s,'r')
1684 fname,ranges = args[0], args[1:]
1710 fname,ranges = args[0], args[1:]
1685 if not fname.endswith('.py'):
1711 if not fname.endswith('.py'):
1686 fname += '.py'
1712 fname += '.py'
@@ -1689,7 +1715,7 b' Currently the magic system has the following functions:\\n"""'
1689 if ans.lower() not in ['y','yes']:
1715 if ans.lower() not in ['y','yes']:
1690 print 'Operation cancelled.'
1716 print 'Operation cancelled.'
1691 return
1717 return
1692 cmds = ''.join(self.extract_input_slices(ranges))
1718 cmds = ''.join(self.extract_input_slices(ranges,opts.has_key('r')))
1693 f = file(fname,'w')
1719 f = file(fname,'w')
1694 f.write(cmds)
1720 f.write(cmds)
1695 f.close()
1721 f.close()
@@ -1742,6 +1768,13 b' Currently the magic system has the following functions:\\n"""'
1742 it was used, regardless of how long ago (in your current session) it
1768 it was used, regardless of how long ago (in your current session) it
1743 was.
1769 was.
1744
1770
1771 -r: use 'raw' input. This option only applies to input taken from the
1772 user's history. By default, the 'processed' history is used, so that
1773 magics are loaded in their transformed version to valid Python. If
1774 this option is given, the raw input as typed as the command line is
1775 used instead. When you exit the editor, it will be executed by
1776 IPython's own processor.
1777
1745 -x: do not execute the edited code immediately upon exit. This is
1778 -x: do not execute the edited code immediately upon exit. This is
1746 mainly useful if you are editing programs which need to be called with
1779 mainly useful if you are editing programs which need to be called with
1747 command line arguments, which you can then do using %run.
1780 command line arguments, which you can then do using %run.
@@ -1860,11 +1893,14 b' Currently the magic system has the following functions:\\n"""'
1860 # custom exceptions
1893 # custom exceptions
1861 class DataIsObject(Exception): pass
1894 class DataIsObject(Exception): pass
1862
1895
1863 opts,args = self.parse_options(parameter_s,'px')
1896 opts,args = self.parse_options(parameter_s,'prx')
1897 # Set a few locals from the options for convenience:
1898 opts_p = opts.has_key('p')
1899 opts_r = opts.has_key('r')
1864
1900
1865 # Default line number value
1901 # Default line number value
1866 lineno = None
1902 lineno = None
1867 if opts.has_key('p'):
1903 if opts_p:
1868 args = '_%s' % last_call[0]
1904 args = '_%s' % last_call[0]
1869 if not self.shell.user_ns.has_key(args):
1905 if not self.shell.user_ns.has_key(args):
1870 args = last_call[1]
1906 args = last_call[1]
@@ -1873,7 +1909,7 b' Currently the magic system has the following functions:\\n"""'
1873 # let it be clobbered by successive '-p' calls.
1909 # let it be clobbered by successive '-p' calls.
1874 try:
1910 try:
1875 last_call[0] = self.shell.outputcache.prompt_count
1911 last_call[0] = self.shell.outputcache.prompt_count
1876 if not opts.has_key('p'):
1912 if not opts_p:
1877 last_call[1] = parameter_s
1913 last_call[1] = parameter_s
1878 except:
1914 except:
1879 pass
1915 pass
@@ -1887,7 +1923,7 b' Currently the magic system has the following functions:\\n"""'
1887 # This means that you can't edit files whose names begin with
1923 # This means that you can't edit files whose names begin with
1888 # numbers this way. Tough.
1924 # numbers this way. Tough.
1889 ranges = args.split()
1925 ranges = args.split()
1890 data = ''.join(self.extract_input_slices(ranges))
1926 data = ''.join(self.extract_input_slices(ranges,opts_r))
1891 elif args.endswith('.py'):
1927 elif args.endswith('.py'):
1892 filename = make_filename(args)
1928 filename = make_filename(args)
1893 data = ''
1929 data = ''
@@ -1955,7 +1991,10 b' Currently the magic system has the following functions:\\n"""'
1955 print
1991 print
1956 else:
1992 else:
1957 print 'done. Executing edited code...'
1993 print 'done. Executing edited code...'
1958 self.shell.safe_execfile(filename,self.shell.user_ns)
1994 if opts_r:
1995 self.shell.runlines(file_read(filename))
1996 else:
1997 self.shell.safe_execfile(filename,self.shell.user_ns)
1959 if use_temp:
1998 if use_temp:
1960 try:
1999 try:
1961 return open(filename).read()
2000 return open(filename).read()
@@ -1,9 +1,24 b''
1 """Module for interactive demos using IPython.
1 """Module for interactive demos using IPython.
2
2
3 This module implements a single class, Demo, for running Python scripts
3 This module implements a few classes for running Python scripts interactively
4 interactively in IPython for demonstrations. With very simple markup (a few
4 in IPython for demonstrations. With very simple markup (a few tags in
5 tags in comments), you can control points where the script stops executing and
5 comments), you can control points where the script stops executing and returns
6 returns control to IPython.
6 control to IPython.
7
8 The classes are (see their docstrings for further details):
9
10 - Demo: pure python demos
11
12 - IPythonDemo: demos with input to be processed by IPython as if it had been
13 typed interactively (so magics work, as well as any other special syntax you
14 may have added via input prefilters).
15
16 - LineDemo: single-line version of the Demo class. These demos are executed
17 one line at a time, and require no markup.
18
19 - IPythonLineDemo: IPython version of the LineDemo class (the demo is
20 executed a line at a time, but processed via IPython).
21
7
22
8 The file is run in its own empty namespace (though you can pass it a string of
23 The file is run in its own empty namespace (though you can pass it a string of
9 arguments as if in a command line environment, and it will see those as
24 arguments as if in a command line environment, and it will see those as
@@ -54,12 +69,12 b' copy this into a file named ex_demo.py, and try running it via:'
54
69
55 from IPython.demo import Demo
70 from IPython.demo import Demo
56 d = Demo('ex_demo.py')
71 d = Demo('ex_demo.py')
57 d() <--- Call the d object (omit the parens if you have autocall on).
72 d() <--- Call the d object (omit the parens if you have autocall set to 2).
58
73
59 Each time you call the demo object, it runs the next block. The demo object
74 Each time you call the demo object, it runs the next block. The demo object
60 has a few useful methods for navigation, like again(), jump(), seek() and
75 has a few useful methods for navigation, like again(), edit(), jump(), seek()
61 back(). It can be reset for a new run via reset() or reloaded from disk (in
76 and back(). It can be reset for a new run via reset() or reloaded from disk
62 case you've edited the source) via reload(). See their docstrings below.
77 (in case you've edited the source) via reload(). See their docstrings below.
63
78
64 #################### EXAMPLE DEMO <ex_demo.py> ###############################
79 #################### EXAMPLE DEMO <ex_demo.py> ###############################
65 '''A simple interactive demo to illustrate the use of IPython's Demo class.'''
80 '''A simple interactive demo to illustrate the use of IPython's Demo class.'''
@@ -95,9 +110,6 b" print 'z is now:', z"
95
110
96 print 'bye!'
111 print 'bye!'
97 ################### END EXAMPLE DEMO <ex_demo.py> ############################
112 ################### END EXAMPLE DEMO <ex_demo.py> ############################
98
99 WARNING: this module uses Python 2.3 features, so it won't work in 2.2
100 environments.
101 """
113 """
102 #*****************************************************************************
114 #*****************************************************************************
103 # Copyright (C) 2005-2006 Fernando Perez. <Fernando.Perez@colorado.edu>
115 # Copyright (C) 2005-2006 Fernando Perez. <Fernando.Perez@colorado.edu>
@@ -108,13 +120,14 b' environments.'
108 #*****************************************************************************
120 #*****************************************************************************
109
121
110 import exceptions
122 import exceptions
123 import os
111 import re
124 import re
112 import sys
125 import sys
113
126
114 from IPython.PyColorize import Parser
127 from IPython.PyColorize import Parser
115 from IPython.genutils import marquee, shlex_split, file_read
128 from IPython.genutils import marquee, shlex_split, file_read, file_readlines
116
129
117 __all__ = ['Demo','DemoError']
130 __all__ = ['Demo','IPythonDemo','LineDemo','IPythonLineDemo','DemoError']
118
131
119 class DemoError(exceptions.Exception): pass
132 class DemoError(exceptions.Exception): pass
120
133
@@ -150,7 +163,7 b' class Demo:'
150 can be changed at runtime simply by reassigning it to a boolean
163 can be changed at runtime simply by reassigning it to a boolean
151 value.
164 value.
152 """
165 """
153
166
154 self.fname = fname
167 self.fname = fname
155 self.sys_argv = [fname] + shlex_split(arg_str)
168 self.sys_argv = [fname] + shlex_split(arg_str)
156 self.auto_all = auto_all
169 self.auto_all = auto_all
@@ -159,9 +172,11 b' class Demo:'
159 # it ensures that things like color scheme and the like are always in
172 # it ensures that things like color scheme and the like are always in
160 # sync with the ipython mode being used. This class is only meant to
173 # sync with the ipython mode being used. This class is only meant to
161 # be used inside ipython anyways, so it's OK.
174 # be used inside ipython anyways, so it's OK.
162 self.ip_showtb = __IPYTHON__.showtraceback
163 self.ip_ns = __IPYTHON__.user_ns
175 self.ip_ns = __IPYTHON__.user_ns
164 self.ip_colorize = __IPYTHON__.pycolorize
176 self.ip_colorize = __IPYTHON__.pycolorize
177 self.ip_showtb = __IPYTHON__.showtraceback
178 self.ip_runlines = __IPYTHON__.runlines
179 self.shell = __IPYTHON__
165
180
166 # load user data and initialize data structures
181 # load user data and initialize data structures
167 self.reload()
182 self.reload()
@@ -211,6 +226,20 b' class Demo:'
211 if index<0 or index>=self.nblocks:
226 if index<0 or index>=self.nblocks:
212 raise ValueError('invalid block index %s' % index)
227 raise ValueError('invalid block index %s' % index)
213
228
229 def _get_index(self,index):
230 """Get the current block index, validating and checking status.
231
232 Returns None if the demo is finished"""
233
234 if index is None:
235 if self.finished:
236 print 'Demo finished. Use reset() if you want to rerun it.'
237 return None
238 index = self.block_index
239 else:
240 self._validate_index(index)
241 return index
242
214 def seek(self,index):
243 def seek(self,index):
215 """Move the current seek pointer to the given block"""
244 """Move the current seek pointer to the given block"""
216 self._validate_index(index)
245 self._validate_index(index)
@@ -230,15 +259,42 b' class Demo:'
230 self.back(1)
259 self.back(1)
231 self()
260 self()
232
261
262 def edit(self,index=None):
263 """Edit a block.
264
265 If no number is given, use the last block executed.
266
267 This edits the in-memory copy of the demo, it does NOT modify the
268 original source file. If you want to do that, simply open the file in
269 an editor and use reload() when you make changes to the file. This
270 method is meant to let you change a block during a demonstration for
271 explanatory purposes, without damaging your original script."""
272
273 index = self._get_index(index)
274 if index is None:
275 return
276 # decrease the index by one (unless we're at the very beginning), so
277 # that the default demo.edit() call opens up the sblock we've last run
278 if index>0:
279 index -= 1
280
281 filename = self.shell.mktempfile(self.src_blocks[index])
282 self.shell.hooks.editor(filename,1)
283 new_block = file_read(filename)
284 # update the source and colored block
285 self.src_blocks[index] = new_block
286 self.src_blocks_colored[index] = self.ip_colorize(new_block)
287 self.block_index = index
288 # call to run with the newly edited index
289 self()
290
233 def show(self,index=None):
291 def show(self,index=None):
234 """Show a single block on screen"""
292 """Show a single block on screen"""
293
294 index = self._get_index(index)
235 if index is None:
295 if index is None:
236 if self.finished:
296 return
237 print 'Demo finished. Use reset() if you want to rerun it.'
297
238 return
239 index = self.block_index
240 else:
241 self._validate_index(index)
242 print marquee('<%s> block # %s (%s remaining)' %
298 print marquee('<%s> block # %s (%s remaining)' %
243 (self.fname,index,self.nblocks-index-1))
299 (self.fname,index,self.nblocks-index-1))
244 print self.src_blocks_colored[index],
300 print self.src_blocks_colored[index],
@@ -259,7 +315,12 b' class Demo:'
259 (fname,index,nblocks-index-1))
315 (fname,index,nblocks-index-1))
260 print block,
316 print block,
261 sys.stdout.flush()
317 sys.stdout.flush()
262
318
319 def runlines(self,source):
320 """Execute a string with one or more lines of code"""
321
322 exec source in self.user_ns
323
263 def __call__(self,index=None):
324 def __call__(self,index=None):
264 """run a block of the demo.
325 """run a block of the demo.
265
326
@@ -269,12 +330,9 b' class Demo:'
269 prints 'Block n/N, and N is the total, so it would be very odd to use
330 prints 'Block n/N, and N is the total, so it would be very odd to use
270 zero-indexing here."""
331 zero-indexing here."""
271
332
272 if index is None and self.finished:
333 index = self._get_index(index)
273 print 'Demo finished. Use reset() if you want to rerun it.'
274 return
275 if index is None:
334 if index is None:
276 index = self.block_index
335 return
277 self._validate_index(index)
278 try:
336 try:
279 next_block = self.src_blocks[index]
337 next_block = self.src_blocks[index]
280 self.block_index += 1
338 self.block_index += 1
@@ -294,7 +352,7 b' class Demo:'
294 try:
352 try:
295 save_argv = sys.argv
353 save_argv = sys.argv
296 sys.argv = self.sys_argv
354 sys.argv = self.sys_argv
297 exec next_block in self.user_ns
355 self.runlines(next_block)
298 finally:
356 finally:
299 sys.argv = save_argv
357 sys.argv = save_argv
300
358
@@ -309,3 +367,52 b' class Demo:'
309 print marquee('Use reset() if you want to rerun it.')
367 print marquee('Use reset() if you want to rerun it.')
310 self.finished = True
368 self.finished = True
311
369
370 class IPythonDemo(Demo):
371 """Class for interactive demos with IPython's input processing applied.
372
373 This subclasses Demo, but instead of executing each block by the Python
374 interpreter (via exec), it actually calls IPython on it, so that any input
375 filters which may be in place are applied to the input block.
376
377 If you have an interactive environment which exposes special input
378 processing, you can use this class instead to write demo scripts which
379 operate exactly as if you had typed them interactively. The default Demo
380 class requires the input to be valid, pure Python code.
381 """
382
383 def runlines(self,source):
384 """Execute a string with one or more lines of code"""
385
386 self.runlines(source)
387
388 class LineDemo(Demo):
389 """Demo where each line is executed as a separate block.
390
391 The input script should be valid Python code.
392
393 This class doesn't require any markup at all, and it's meant for simple
394 scripts (with no nesting or any kind of indentation) which consist of
395 multiple lines of input to be executed, one at a time, as if they had been
396 typed in the interactive prompt."""
397
398 def reload(self):
399 """Reload source from disk and initialize state."""
400 # read data and parse into blocks
401 src_b = [l for l in file_readlines(self.fname) if l.strip()]
402 nblocks = len(src_b)
403 self.src = os.linesep.join(file_readlines(self.fname))
404 self._silent = [False]*nblocks
405 self._auto = [True]*nblocks
406 self.auto_all = True
407 self.nblocks = nblocks
408 self.src_blocks = src_b
409
410 # also build syntax-highlighted source
411 self.src_blocks_colored = map(self.ip_colorize,self.src_blocks)
412
413 # ensure clean namespace and seek offset
414 self.reset()
415
416 class IPythonLineDemo(IPythonDemo,LineDemo):
417 """Variant of the LineDemo class whose input is processed by IPython."""
418 pass
@@ -5,7 +5,7 b' General purpose utilities.'
5 This is a grab-bag of stuff I find useful in most programs I write. Some of
5 This is a grab-bag of stuff I find useful in most programs I write. Some of
6 these things are also convenient when working at the command line.
6 these things are also convenient when working at the command line.
7
7
8 $Id: genutils.py 1110 2006-01-30 20:43:30Z vivainio $"""
8 $Id: genutils.py 1126 2006-02-06 02:31:40Z fperez $"""
9
9
10 #*****************************************************************************
10 #*****************************************************************************
11 # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu>
11 # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu>
@@ -529,11 +529,18 b' def filefind(fname,alt_dirs = None):'
529 #----------------------------------------------------------------------------
529 #----------------------------------------------------------------------------
530 def file_read(filename):
530 def file_read(filename):
531 """Read a file and close it. Returns the file source."""
531 """Read a file and close it. Returns the file source."""
532 fobj=open(filename,'r');
532 fobj = open(filename,'r');
533 source = fobj.read();
533 source = fobj.read();
534 fobj.close()
534 fobj.close()
535 return source
535 return source
536
536
537 def file_readlines(filename):
538 """Read a file and close it. Returns the file source using readlines()."""
539 fobj = open(filename,'r');
540 lines = fobj.readlines();
541 fobj.close()
542 return lines
543
537 #----------------------------------------------------------------------------
544 #----------------------------------------------------------------------------
538 def target_outdated(target,deps):
545 def target_outdated(target,deps):
539 """Determine whether a target is out of date.
546 """Determine whether a target is out of date.
@@ -1,3 +1,20 b''
1 2006-02-05 Fernando Perez <Fernando.Perez@colorado.edu>
2
3 * IPython/demo.py (IPythonDemo): Add new classes to the demo
4 facilities, for demos processed by the IPython input filter
5 (IPythonDemo), and for running a script one-line-at-a-time as a
6 demo, both for pure Python (LineDemo) and for IPython-processed
7 input (IPythonLineDemo). After a request by Dave Kohel, from the
8 SAGE team.
9 (Demo.edit): added and edit() method to the demo objects, to edit
10 the in-memory copy of the last executed block.
11
12 * IPython/Magic.py (magic_edit): add '-r' option for 'raw'
13 processing to %edit, %macro and %save. These commands can now be
14 invoked on the unprocessed input as it was typed by the user
15 (without any prefilters applied). After requests by the SAGE team
16 at SAGE days 2006: http://modular.ucsd.edu/sage/days1/schedule.html.
17
1 2006-02-01 Ville Vainio <vivainio@gmail.com>
18 2006-02-01 Ville Vainio <vivainio@gmail.com>
2
19
3 * setup.py, eggsetup.py: easy_install ipython==dev works
20 * setup.py, eggsetup.py: easy_install ipython==dev works
General Comments 0
You need to be logged in to leave comments. Login now