##// END OF EJS Templates
crlf -> lf
Ville M. Vainio -
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,170 +1,170 b''
1 # -*- coding: utf-8 -*-
2 """Tools for coloring text in ANSI terminals.
3
4 $Id: ColorANSI.py 2167 2007-03-21 06:57:50Z fperez $"""
5
6 #*****************************************************************************
7 # Copyright (C) 2002-2006 Fernando Perez. <fperez@colorado.edu>
8 #
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
11 #*****************************************************************************
12
13 from IPython import Release
14 __author__ = '%s <%s>' % Release.authors['Fernando']
15 __license__ = Release.license
16
17 __all__ = ['TermColors','InputTermColors','ColorScheme','ColorSchemeTable']
18
19 import os
20
21 from IPython.ipstruct import Struct
22
23 def make_color_table(in_class):
24 """Build a set of color attributes in a class.
25
26 Helper function for building the *TermColors classes."""
27
28 color_templates = (
29 ("Black" , "0;30"),
30 ("Red" , "0;31"),
31 ("Green" , "0;32"),
32 ("Brown" , "0;33"),
33 ("Blue" , "0;34"),
34 ("Purple" , "0;35"),
35 ("Cyan" , "0;36"),
36 ("LightGray" , "0;37"),
37 ("DarkGray" , "1;30"),
38 ("LightRed" , "1;31"),
39 ("LightGreen" , "1;32"),
40 ("Yellow" , "1;33"),
41 ("LightBlue" , "1;34"),
42 ("LightPurple" , "1;35"),
43 ("LightCyan" , "1;36"),
44 ("White" , "1;37"), )
45
46 for name,value in color_templates:
47 setattr(in_class,name,in_class._base % value)
48
49 class TermColors:
50 """Color escape sequences.
51
52 This class defines the escape sequences for all the standard (ANSI?)
53 colors in terminals. Also defines a NoColor escape which is just the null
54 string, suitable for defining 'dummy' color schemes in terminals which get
55 confused by color escapes.
56
57 This class should be used as a mixin for building color schemes."""
58
59 NoColor = '' # for color schemes in color-less terminals.
60 Normal = '\033[0m' # Reset normal coloring
61 _base = '\033[%sm' # Template for all other colors
62
63 # Build the actual color table as a set of class attributes:
64 make_color_table(TermColors)
65
66 class InputTermColors:
67 """Color escape sequences for input prompts.
68
69 This class is similar to TermColors, but the escapes are wrapped in \001
70 and \002 so that readline can properly know the length of each line and
71 can wrap lines accordingly. Use this class for any colored text which
72 needs to be used in input prompts, such as in calls to raw_input().
73
74 This class defines the escape sequences for all the standard (ANSI?)
75 colors in terminals. Also defines a NoColor escape which is just the null
76 string, suitable for defining 'dummy' color schemes in terminals which get
77 confused by color escapes.
78
79 This class should be used as a mixin for building color schemes."""
80
81 NoColor = '' # for color schemes in color-less terminals.
82
83 if os.name == 'nt' and os.environ.get('TERM','dumb') == 'emacs':
84 # (X)emacs on W32 gets confused with \001 and \002 so we remove them
85 Normal = '\033[0m' # Reset normal coloring
86 _base = '\033[%sm' # Template for all other colors
87 else:
88 Normal = '\001\033[0m\002' # Reset normal coloring
89 _base = '\001\033[%sm\002' # Template for all other colors
90
91 # Build the actual color table as a set of class attributes:
92 make_color_table(InputTermColors)
93
94 class ColorScheme:
95 """Generic color scheme class. Just a name and a Struct."""
96 def __init__(self,__scheme_name_,colordict=None,**colormap):
97 self.name = __scheme_name_
98 if colordict is None:
99 self.colors = Struct(**colormap)
100 else:
101 self.colors = Struct(colordict)
102
103 def copy(self,name=None):
104 """Return a full copy of the object, optionally renaming it."""
105 if name is None:
106 name = self.name
107 return ColorScheme(name,self.colors.__dict__)
108
109 class ColorSchemeTable(dict):
110 """General class to handle tables of color schemes.
111
112 It's basically a dict of color schemes with a couple of shorthand
113 attributes and some convenient methods.
114
115 active_scheme_name -> obvious
116 active_colors -> actual color table of the active scheme"""
117
118 def __init__(self,scheme_list=None,default_scheme=''):
119 """Create a table of color schemes.
120
121 The table can be created empty and manually filled or it can be
122 created with a list of valid color schemes AND the specification for
123 the default active scheme.
124 """
125
126 # create object attributes to be set later
127 self.active_scheme_name = ''
128 self.active_colors = None
129
130 if scheme_list:
131 if default_scheme == '':
132 raise ValueError,'you must specify the default color scheme'
133 for scheme in scheme_list:
134 self.add_scheme(scheme)
135 self.set_active_scheme(default_scheme)
136
137 def copy(self):
138 """Return full copy of object"""
139 return ColorSchemeTable(self.values(),self.active_scheme_name)
140
141 def add_scheme(self,new_scheme):
142 """Add a new color scheme to the table."""
143 if not isinstance(new_scheme,ColorScheme):
144 raise ValueError,'ColorSchemeTable only accepts ColorScheme instances'
145 self[new_scheme.name] = new_scheme
146
147 def set_active_scheme(self,scheme,case_sensitive=0):
148 """Set the currently active scheme.
149
150 Names are by default compared in a case-insensitive way, but this can
151 be changed by setting the parameter case_sensitive to true."""
152
153 scheme_names = self.keys()
154 if case_sensitive:
155 valid_schemes = scheme_names
156 scheme_test = scheme
157 else:
158 valid_schemes = [s.lower() for s in scheme_names]
159 scheme_test = scheme.lower()
160 try:
161 scheme_idx = valid_schemes.index(scheme_test)
162 except ValueError:
163 raise ValueError,'Unrecognized color scheme: ' + scheme + \
164 '\nValid schemes: '+str(scheme_names).replace("'', ",'')
165 else:
166 active = scheme_names[scheme_idx]
167 self.active_scheme_name = active
168 self.active_colors = self[active].colors
169 # Now allow using '' as an index for the current active scheme
170 self[''] = self[active]
1 # -*- coding: utf-8 -*-
2 """Tools for coloring text in ANSI terminals.
3
4 $Id: ColorANSI.py 2167 2007-03-21 06:57:50Z fperez $"""
5
6 #*****************************************************************************
7 # Copyright (C) 2002-2006 Fernando Perez. <fperez@colorado.edu>
8 #
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
11 #*****************************************************************************
12
13 from IPython import Release
14 __author__ = '%s <%s>' % Release.authors['Fernando']
15 __license__ = Release.license
16
17 __all__ = ['TermColors','InputTermColors','ColorScheme','ColorSchemeTable']
18
19 import os
20
21 from IPython.ipstruct import Struct
22
23 def make_color_table(in_class):
24 """Build a set of color attributes in a class.
25
26 Helper function for building the *TermColors classes."""
27
28 color_templates = (
29 ("Black" , "0;30"),
30 ("Red" , "0;31"),
31 ("Green" , "0;32"),
32 ("Brown" , "0;33"),
33 ("Blue" , "0;34"),
34 ("Purple" , "0;35"),
35 ("Cyan" , "0;36"),
36 ("LightGray" , "0;37"),
37 ("DarkGray" , "1;30"),
38 ("LightRed" , "1;31"),
39 ("LightGreen" , "1;32"),
40 ("Yellow" , "1;33"),
41 ("LightBlue" , "1;34"),
42 ("LightPurple" , "1;35"),
43 ("LightCyan" , "1;36"),
44 ("White" , "1;37"), )
45
46 for name,value in color_templates:
47 setattr(in_class,name,in_class._base % value)
48
49 class TermColors:
50 """Color escape sequences.
51
52 This class defines the escape sequences for all the standard (ANSI?)
53 colors in terminals. Also defines a NoColor escape which is just the null
54 string, suitable for defining 'dummy' color schemes in terminals which get
55 confused by color escapes.
56
57 This class should be used as a mixin for building color schemes."""
58
59 NoColor = '' # for color schemes in color-less terminals.
60 Normal = '\033[0m' # Reset normal coloring
61 _base = '\033[%sm' # Template for all other colors
62
63 # Build the actual color table as a set of class attributes:
64 make_color_table(TermColors)
65
66 class InputTermColors:
67 """Color escape sequences for input prompts.
68
69 This class is similar to TermColors, but the escapes are wrapped in \001
70 and \002 so that readline can properly know the length of each line and
71 can wrap lines accordingly. Use this class for any colored text which
72 needs to be used in input prompts, such as in calls to raw_input().
73
74 This class defines the escape sequences for all the standard (ANSI?)
75 colors in terminals. Also defines a NoColor escape which is just the null
76 string, suitable for defining 'dummy' color schemes in terminals which get
77 confused by color escapes.
78
79 This class should be used as a mixin for building color schemes."""
80
81 NoColor = '' # for color schemes in color-less terminals.
82
83 if os.name == 'nt' and os.environ.get('TERM','dumb') == 'emacs':
84 # (X)emacs on W32 gets confused with \001 and \002 so we remove them
85 Normal = '\033[0m' # Reset normal coloring
86 _base = '\033[%sm' # Template for all other colors
87 else:
88 Normal = '\001\033[0m\002' # Reset normal coloring
89 _base = '\001\033[%sm\002' # Template for all other colors
90
91 # Build the actual color table as a set of class attributes:
92 make_color_table(InputTermColors)
93
94 class ColorScheme:
95 """Generic color scheme class. Just a name and a Struct."""
96 def __init__(self,__scheme_name_,colordict=None,**colormap):
97 self.name = __scheme_name_
98 if colordict is None:
99 self.colors = Struct(**colormap)
100 else:
101 self.colors = Struct(colordict)
102
103 def copy(self,name=None):
104 """Return a full copy of the object, optionally renaming it."""
105 if name is None:
106 name = self.name
107 return ColorScheme(name,self.colors.__dict__)
108
109 class ColorSchemeTable(dict):
110 """General class to handle tables of color schemes.
111
112 It's basically a dict of color schemes with a couple of shorthand
113 attributes and some convenient methods.
114
115 active_scheme_name -> obvious
116 active_colors -> actual color table of the active scheme"""
117
118 def __init__(self,scheme_list=None,default_scheme=''):
119 """Create a table of color schemes.
120
121 The table can be created empty and manually filled or it can be
122 created with a list of valid color schemes AND the specification for
123 the default active scheme.
124 """
125
126 # create object attributes to be set later
127 self.active_scheme_name = ''
128 self.active_colors = None
129
130 if scheme_list:
131 if default_scheme == '':
132 raise ValueError,'you must specify the default color scheme'
133 for scheme in scheme_list:
134 self.add_scheme(scheme)
135 self.set_active_scheme(default_scheme)
136
137 def copy(self):
138 """Return full copy of object"""
139 return ColorSchemeTable(self.values(),self.active_scheme_name)
140
141 def add_scheme(self,new_scheme):
142 """Add a new color scheme to the table."""
143 if not isinstance(new_scheme,ColorScheme):
144 raise ValueError,'ColorSchemeTable only accepts ColorScheme instances'
145 self[new_scheme.name] = new_scheme
146
147 def set_active_scheme(self,scheme,case_sensitive=0):
148 """Set the currently active scheme.
149
150 Names are by default compared in a case-insensitive way, but this can
151 be changed by setting the parameter case_sensitive to true."""
152
153 scheme_names = self.keys()
154 if case_sensitive:
155 valid_schemes = scheme_names
156 scheme_test = scheme
157 else:
158 valid_schemes = [s.lower() for s in scheme_names]
159 scheme_test = scheme.lower()
160 try:
161 scheme_idx = valid_schemes.index(scheme_test)
162 except ValueError:
163 raise ValueError,'Unrecognized color scheme: ' + scheme + \
164 '\nValid schemes: '+str(scheme_names).replace("'', ",'')
165 else:
166 active = scheme_names[scheme_idx]
167 self.active_scheme_name = active
168 self.active_colors = self[active].colors
169 # Now allow using '' as an index for the current active scheme
170 self[''] = self[active]
@@ -1,116 +1,116 b''
1 # -*- coding: utf-8 -*-
2 """Configuration loader
3
4 $Id: ConfigLoader.py 1005 2006-01-12 08:39:26Z fperez $"""
5
6 #*****************************************************************************
7 # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu>
8 #
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
11 #*****************************************************************************
12
13 from IPython import Release
14 __author__ = '%s <%s>' % Release.authors['Fernando']
15 __license__ = Release.license
16
17 import exceptions
18 import os
19 from pprint import pprint
20
21 from IPython import ultraTB
22 from IPython.ipstruct import Struct
23 from IPython.genutils import *
24
25 class ConfigLoaderError(exceptions.Exception):
26 """Exception for ConfigLoader class."""
27
28 def __init__(self,args=None):
29 self.args = args
30
31 class ConfigLoader:
32
33 """Configuration file loader capable of handling recursive inclusions and
34 with parametrized conflict resolution for multiply found keys."""
35
36 def __init__(self,conflict=None,field_sep=None,reclimit=15):
37
38 """The reclimit parameter controls the number of recursive
39 configuration file inclusions. This way we can stop early on (before
40 python's own recursion limit is hit) if there is a circular
41 inclusion.
42
43 - conflict: dictionary for conflict resolutions (see Struct.merge())
44
45 """
46 self.conflict = conflict
47 self.field_sep = field_sep
48 self.reset(reclimit)
49
50 def reset(self,reclimit=15):
51 self.reclimit = reclimit
52 self.recdepth = 0
53 self.included = []
54
55 def load(self,fname,convert=None,recurse_key='',incpath = '.',**kw):
56 """Load a configuration file, return the resulting Struct.
57
58 Call: load_config(fname,convert=None,conflict=None,recurse_key='')
59
60 - fname: file to load from.
61 - convert: dictionary of type conversions (see read_dict())
62 - recurse_key: keyword in dictionary to trigger recursive file
63 inclusions.
64 """
65
66 if self.recdepth > self.reclimit:
67 raise ConfigLoaderError, 'maximum recursive inclusion of rcfiles '+\
68 'exceeded: ' + `self.recdepth` + \
69 '.\nMaybe you have a circular chain of inclusions?'
70 self.recdepth += 1
71 fname = filefind(fname,incpath)
72 data = Struct()
73 # avoid including the same file more than once
74 if fname in self.included:
75 return data
76 Xinfo = ultraTB.AutoFormattedTB()
77 if convert==None and recurse_key : convert = {qwflat:recurse_key}
78 # for production, change warn to 0:
79 data.merge(read_dict(fname,convert,fs=self.field_sep,strip=1,
80 warn=0,no_empty=0,**kw))
81 # keep track of successfully loaded files
82 self.included.append(fname)
83 if recurse_key in data.keys():
84 for incfilename in data[recurse_key]:
85 found=0
86 try:
87 incfile = filefind(incfilename,incpath)
88 except IOError:
89 if os.name in ['nt','dos']:
90 try:
91 # Try again with '.ini' extension
92 incfilename += '.ini'
93 incfile = filefind(incfilename,incpath)
94 except IOError:
95 found = 0
96 else:
97 found = 1
98 else:
99 found = 0
100 else:
101 found = 1
102 if found:
103 try:
104 data.merge(self.load(incfile,convert,recurse_key,
105 incpath,**kw),
106 self.conflict)
107 except:
108 Xinfo()
109 warn('Problem loading included file: '+
110 `incfilename` + '. Ignoring it...')
111 else:
112 warn('File `%s` not found. Included by %s' % (incfilename,fname))
113
114 return data
115
116 # end ConfigLoader
1 # -*- coding: utf-8 -*-
2 """Configuration loader
3
4 $Id: ConfigLoader.py 1005 2006-01-12 08:39:26Z fperez $"""
5
6 #*****************************************************************************
7 # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu>
8 #
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
11 #*****************************************************************************
12
13 from IPython import Release
14 __author__ = '%s <%s>' % Release.authors['Fernando']
15 __license__ = Release.license
16
17 import exceptions
18 import os
19 from pprint import pprint
20
21 from IPython import ultraTB
22 from IPython.ipstruct import Struct
23 from IPython.genutils import *
24
25 class ConfigLoaderError(exceptions.Exception):
26 """Exception for ConfigLoader class."""
27
28 def __init__(self,args=None):
29 self.args = args
30
31 class ConfigLoader:
32
33 """Configuration file loader capable of handling recursive inclusions and
34 with parametrized conflict resolution for multiply found keys."""
35
36 def __init__(self,conflict=None,field_sep=None,reclimit=15):
37
38 """The reclimit parameter controls the number of recursive
39 configuration file inclusions. This way we can stop early on (before
40 python's own recursion limit is hit) if there is a circular
41 inclusion.
42
43 - conflict: dictionary for conflict resolutions (see Struct.merge())
44
45 """
46 self.conflict = conflict
47 self.field_sep = field_sep
48 self.reset(reclimit)
49
50 def reset(self,reclimit=15):
51 self.reclimit = reclimit
52 self.recdepth = 0
53 self.included = []
54
55 def load(self,fname,convert=None,recurse_key='',incpath = '.',**kw):
56 """Load a configuration file, return the resulting Struct.
57
58 Call: load_config(fname,convert=None,conflict=None,recurse_key='')
59
60 - fname: file to load from.
61 - convert: dictionary of type conversions (see read_dict())
62 - recurse_key: keyword in dictionary to trigger recursive file
63 inclusions.
64 """
65
66 if self.recdepth > self.reclimit:
67 raise ConfigLoaderError, 'maximum recursive inclusion of rcfiles '+\
68 'exceeded: ' + `self.recdepth` + \
69 '.\nMaybe you have a circular chain of inclusions?'
70 self.recdepth += 1
71 fname = filefind(fname,incpath)
72 data = Struct()
73 # avoid including the same file more than once
74 if fname in self.included:
75 return data
76 Xinfo = ultraTB.AutoFormattedTB()
77 if convert==None and recurse_key : convert = {qwflat:recurse_key}
78 # for production, change warn to 0:
79 data.merge(read_dict(fname,convert,fs=self.field_sep,strip=1,
80 warn=0,no_empty=0,**kw))
81 # keep track of successfully loaded files
82 self.included.append(fname)
83 if recurse_key in data.keys():
84 for incfilename in data[recurse_key]:
85 found=0
86 try:
87 incfile = filefind(incfilename,incpath)
88 except IOError:
89 if os.name in ['nt','dos']:
90 try:
91 # Try again with '.ini' extension
92 incfilename += '.ini'
93 incfile = filefind(incfilename,incpath)
94 except IOError:
95 found = 0
96 else:
97 found = 1
98 else:
99 found = 0
100 else:
101 found = 1
102 if found:
103 try:
104 data.merge(self.load(incfile,convert,recurse_key,
105 incpath,**kw),
106 self.conflict)
107 except:
108 Xinfo()
109 warn('Problem loading included file: '+
110 `incfilename` + '. Ignoring it...')
111 else:
112 warn('File `%s` not found. Included by %s' % (incfilename,fname))
113
114 return data
115
116 # end ConfigLoader
@@ -1,228 +1,228 b''
1 # -*- coding: utf-8 -*-
2 """sys.excepthook for IPython itself, leaves a detailed report on disk.
3
4 $Id: CrashHandler.py 2908 2007-12-30 21:07:46Z vivainio $"""
5
6 #*****************************************************************************
7 # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu>
8 #
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
11 #*****************************************************************************
12
13 from IPython import Release
14 __author__ = '%s <%s>' % Release.authors['Fernando']
15 __license__ = Release.license
16 __version__ = Release.version
17
18 #****************************************************************************
19 # Required modules
20
21 # From the standard library
22 import os
23 import sys
24 from pprint import pprint,pformat
25
26 # Homebrewed
27 from IPython.Itpl import Itpl,itpl,printpl
28 from IPython.ColorANSI import ColorScheme,ColorSchemeTable # too long names
29 from IPython import ultraTB
30 from IPython.genutils import *
31
32 #****************************************************************************
33 class CrashHandler:
34 """Customizable crash handlers for IPython-based systems.
35
36 Instances of this class provide a __call__ method which can be used as a
37 sys.excepthook, i.e., the __call__ signature is:
38
39 def __call__(self,etype, evalue, etb)
40
41 """
42
43 def __init__(self,IP,app_name,contact_name,contact_email,
44 bug_tracker,crash_report_fname,
45 show_crash_traceback=True):
46 """New crash handler.
47
48 Inputs:
49
50 - IP: a running IPython instance, which will be queried at crash time
51 for internal information.
52
53 - app_name: a string containing the name of your application.
54
55 - contact_name: a string with the name of the person to contact.
56
57 - contact_email: a string with the email address of the contact.
58
59 - bug_tracker: a string with the URL for your project's bug tracker.
60
61 - crash_report_fname: a string with the filename for the crash report
62 to be saved in. These reports are left in the ipython user directory
63 as determined by the running IPython instance.
64
65 Optional inputs:
66
67 - show_crash_traceback(True): if false, don't print the crash
68 traceback on stderr, only generate the on-disk report
69
70
71 Non-argument instance attributes:
72
73 These instances contain some non-argument attributes which allow for
74 further customization of the crash handler's behavior. Please see the
75 source for further details.
76 """
77
78 # apply args into instance
79 self.IP = IP # IPython instance
80 self.app_name = app_name
81 self.contact_name = contact_name
82 self.contact_email = contact_email
83 self.bug_tracker = bug_tracker
84 self.crash_report_fname = crash_report_fname
85 self.show_crash_traceback = show_crash_traceback
86
87 # Hardcoded defaults, which can be overridden either by subclasses or
88 # at runtime for the instance.
89
90 # Template for the user message. Subclasses which completely override
91 # this, or user apps, can modify it to suit their tastes. It gets
92 # expanded using itpl, so calls of the kind $self.foo are valid.
93 self.user_message_template = """
94 Oops, $self.app_name crashed. We do our best to make it stable, but...
95
96 A crash report was automatically generated with the following information:
97 - A verbatim copy of the crash traceback.
98 - A copy of your input history during this session.
99 - Data on your current $self.app_name configuration.
100
101 It was left in the file named:
102 \t'$self.crash_report_fname'
103 If you can email this file to the developers, the information in it will help
104 them in understanding and correcting the problem.
105
106 You can mail it to: $self.contact_name at $self.contact_email
107 with the subject '$self.app_name Crash Report'.
108
109 If you want to do it now, the following command will work (under Unix):
110 mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname
111
112 To ensure accurate tracking of this issue, please file a report about it at:
113 $self.bug_tracker
114 """
115
116 def __call__(self,etype, evalue, etb):
117 """Handle an exception, call for compatible with sys.excepthook"""
118
119 # Report tracebacks shouldn't use color in general (safer for users)
120 color_scheme = 'NoColor'
121
122 # Use this ONLY for developer debugging (keep commented out for release)
123 #color_scheme = 'Linux' # dbg
124
125 try:
126 rptdir = self.IP.rc.ipythondir
127 except:
128 rptdir = os.getcwd()
129 if not os.path.isdir(rptdir):
130 rptdir = os.getcwd()
131 report_name = os.path.join(rptdir,self.crash_report_fname)
132 # write the report filename into the instance dict so it can get
133 # properly expanded out in the user message template
134 self.crash_report_fname = report_name
135 TBhandler = ultraTB.VerboseTB(color_scheme=color_scheme,
136 long_header=1)
137 traceback = TBhandler.text(etype,evalue,etb,context=31)
138
139 # print traceback to screen
140 if self.show_crash_traceback:
141 print >> sys.stderr, traceback
142
143 # and generate a complete report on disk
144 try:
145 report = open(report_name,'w')
146 except:
147 print >> sys.stderr, 'Could not create crash report on disk.'
148 return
149
150 # Inform user on stderr of what happened
151 msg = itpl('\n'+'*'*70+'\n'+self.user_message_template)
152 print >> sys.stderr, msg
153
154 # Construct report on disk
155 report.write(self.make_report(traceback))
156 report.close()
157 raw_input("Press enter to exit:")
158
159 def make_report(self,traceback):
160 """Return a string containing a crash report."""
161
162 sec_sep = '\n\n'+'*'*75+'\n\n'
163
164 report = []
165 rpt_add = report.append
166
167 rpt_add('*'*75+'\n\n'+'IPython post-mortem report\n\n')
168 rpt_add('IPython version: %s \n\n' % Release.version)
169 rpt_add('SVN revision : %s \n\n' % Release.revision)
170 rpt_add('Platform info : os.name -> %s, sys.platform -> %s' %
171 (os.name,sys.platform) )
172 rpt_add(sec_sep+'Current user configuration structure:\n\n')
173 rpt_add(pformat(self.IP.rc.dict()))
174 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
175 try:
176 rpt_add(sec_sep+"History of session input:")
177 for line in self.IP.user_ns['_ih']:
178 rpt_add(line)
179 rpt_add('\n*** Last line of input (may not be in above history):\n')
180 rpt_add(self.IP._last_input_line+'\n')
181 except:
182 pass
183
184 return ''.join(report)
185
186 class IPythonCrashHandler(CrashHandler):
187 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
188
189 def __init__(self,IP):
190
191 # Set here which of the IPython authors should be listed as contact
192 AUTHOR_CONTACT = 'Ville'
193
194 # Set argument defaults
195 app_name = 'IPython'
196 bug_tracker = 'http://projects.scipy.org/ipython/ipython/report'
197 contact_name,contact_email = Release.authors[AUTHOR_CONTACT][:2]
198 crash_report_fname = 'IPython_crash_report.txt'
199 # Call parent constructor
200 CrashHandler.__init__(self,IP,app_name,contact_name,contact_email,
201 bug_tracker,crash_report_fname)
202
203 def make_report(self,traceback):
204 """Return a string containing a crash report."""
205
206 sec_sep = '\n\n'+'*'*75+'\n\n'
207
208 report = []
209 rpt_add = report.append
210
211 rpt_add('*'*75+'\n\n'+'IPython post-mortem report\n\n')
212 rpt_add('IPython version: %s \n\n' % Release.version)
213 rpt_add('SVN revision : %s \n\n' % Release.revision)
214 rpt_add('Platform info : os.name -> %s, sys.platform -> %s' %
215 (os.name,sys.platform) )
216 rpt_add(sec_sep+'Current user configuration structure:\n\n')
217 rpt_add(pformat(self.IP.rc.dict()))
218 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
219 try:
220 rpt_add(sec_sep+"History of session input:")
221 for line in self.IP.user_ns['_ih']:
222 rpt_add(line)
223 rpt_add('\n*** Last line of input (may not be in above history):\n')
224 rpt_add(self.IP._last_input_line+'\n')
225 except:
226 pass
227
228 return ''.join(report)
1 # -*- coding: utf-8 -*-
2 """sys.excepthook for IPython itself, leaves a detailed report on disk.
3
4 $Id: CrashHandler.py 2908 2007-12-30 21:07:46Z vivainio $"""
5
6 #*****************************************************************************
7 # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu>
8 #
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
11 #*****************************************************************************
12
13 from IPython import Release
14 __author__ = '%s <%s>' % Release.authors['Fernando']
15 __license__ = Release.license
16 __version__ = Release.version
17
18 #****************************************************************************
19 # Required modules
20
21 # From the standard library
22 import os
23 import sys
24 from pprint import pprint,pformat
25
26 # Homebrewed
27 from IPython.Itpl import Itpl,itpl,printpl
28 from IPython.ColorANSI import ColorScheme,ColorSchemeTable # too long names
29 from IPython import ultraTB
30 from IPython.genutils import *
31
32 #****************************************************************************
33 class CrashHandler:
34 """Customizable crash handlers for IPython-based systems.
35
36 Instances of this class provide a __call__ method which can be used as a
37 sys.excepthook, i.e., the __call__ signature is:
38
39 def __call__(self,etype, evalue, etb)
40
41 """
42
43 def __init__(self,IP,app_name,contact_name,contact_email,
44 bug_tracker,crash_report_fname,
45 show_crash_traceback=True):
46 """New crash handler.
47
48 Inputs:
49
50 - IP: a running IPython instance, which will be queried at crash time
51 for internal information.
52
53 - app_name: a string containing the name of your application.
54
55 - contact_name: a string with the name of the person to contact.
56
57 - contact_email: a string with the email address of the contact.
58
59 - bug_tracker: a string with the URL for your project's bug tracker.
60
61 - crash_report_fname: a string with the filename for the crash report
62 to be saved in. These reports are left in the ipython user directory
63 as determined by the running IPython instance.
64
65 Optional inputs:
66
67 - show_crash_traceback(True): if false, don't print the crash
68 traceback on stderr, only generate the on-disk report
69
70
71 Non-argument instance attributes:
72
73 These instances contain some non-argument attributes which allow for
74 further customization of the crash handler's behavior. Please see the
75 source for further details.
76 """
77
78 # apply args into instance
79 self.IP = IP # IPython instance
80 self.app_name = app_name
81 self.contact_name = contact_name
82 self.contact_email = contact_email
83 self.bug_tracker = bug_tracker
84 self.crash_report_fname = crash_report_fname
85 self.show_crash_traceback = show_crash_traceback
86
87 # Hardcoded defaults, which can be overridden either by subclasses or
88 # at runtime for the instance.
89
90 # Template for the user message. Subclasses which completely override
91 # this, or user apps, can modify it to suit their tastes. It gets
92 # expanded using itpl, so calls of the kind $self.foo are valid.
93 self.user_message_template = """
94 Oops, $self.app_name crashed. We do our best to make it stable, but...
95
96 A crash report was automatically generated with the following information:
97 - A verbatim copy of the crash traceback.
98 - A copy of your input history during this session.
99 - Data on your current $self.app_name configuration.
100
101 It was left in the file named:
102 \t'$self.crash_report_fname'
103 If you can email this file to the developers, the information in it will help
104 them in understanding and correcting the problem.
105
106 You can mail it to: $self.contact_name at $self.contact_email
107 with the subject '$self.app_name Crash Report'.
108
109 If you want to do it now, the following command will work (under Unix):
110 mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname
111
112 To ensure accurate tracking of this issue, please file a report about it at:
113 $self.bug_tracker
114 """
115
116 def __call__(self,etype, evalue, etb):
117 """Handle an exception, call for compatible with sys.excepthook"""
118
119 # Report tracebacks shouldn't use color in general (safer for users)
120 color_scheme = 'NoColor'
121
122 # Use this ONLY for developer debugging (keep commented out for release)
123 #color_scheme = 'Linux' # dbg
124
125 try:
126 rptdir = self.IP.rc.ipythondir
127 except:
128 rptdir = os.getcwd()
129 if not os.path.isdir(rptdir):
130 rptdir = os.getcwd()
131 report_name = os.path.join(rptdir,self.crash_report_fname)
132 # write the report filename into the instance dict so it can get
133 # properly expanded out in the user message template
134 self.crash_report_fname = report_name
135 TBhandler = ultraTB.VerboseTB(color_scheme=color_scheme,
136 long_header=1)
137 traceback = TBhandler.text(etype,evalue,etb,context=31)
138
139 # print traceback to screen
140 if self.show_crash_traceback:
141 print >> sys.stderr, traceback
142
143 # and generate a complete report on disk
144 try:
145 report = open(report_name,'w')
146 except:
147 print >> sys.stderr, 'Could not create crash report on disk.'
148 return
149
150 # Inform user on stderr of what happened
151 msg = itpl('\n'+'*'*70+'\n'+self.user_message_template)
152 print >> sys.stderr, msg
153
154 # Construct report on disk
155 report.write(self.make_report(traceback))
156 report.close()
157 raw_input("Press enter to exit:")
158
159 def make_report(self,traceback):
160 """Return a string containing a crash report."""
161
162 sec_sep = '\n\n'+'*'*75+'\n\n'
163
164 report = []
165 rpt_add = report.append
166
167 rpt_add('*'*75+'\n\n'+'IPython post-mortem report\n\n')
168 rpt_add('IPython version: %s \n\n' % Release.version)
169 rpt_add('SVN revision : %s \n\n' % Release.revision)
170 rpt_add('Platform info : os.name -> %s, sys.platform -> %s' %
171 (os.name,sys.platform) )
172 rpt_add(sec_sep+'Current user configuration structure:\n\n')
173 rpt_add(pformat(self.IP.rc.dict()))
174 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
175 try:
176 rpt_add(sec_sep+"History of session input:")
177 for line in self.IP.user_ns['_ih']:
178 rpt_add(line)
179 rpt_add('\n*** Last line of input (may not be in above history):\n')
180 rpt_add(self.IP._last_input_line+'\n')
181 except:
182 pass
183
184 return ''.join(report)
185
186 class IPythonCrashHandler(CrashHandler):
187 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
188
189 def __init__(self,IP):
190
191 # Set here which of the IPython authors should be listed as contact
192 AUTHOR_CONTACT = 'Ville'
193
194 # Set argument defaults
195 app_name = 'IPython'
196 bug_tracker = 'http://projects.scipy.org/ipython/ipython/report'
197 contact_name,contact_email = Release.authors[AUTHOR_CONTACT][:2]
198 crash_report_fname = 'IPython_crash_report.txt'
199 # Call parent constructor
200 CrashHandler.__init__(self,IP,app_name,contact_name,contact_email,
201 bug_tracker,crash_report_fname)
202
203 def make_report(self,traceback):
204 """Return a string containing a crash report."""
205
206 sec_sep = '\n\n'+'*'*75+'\n\n'
207
208 report = []
209 rpt_add = report.append
210
211 rpt_add('*'*75+'\n\n'+'IPython post-mortem report\n\n')
212 rpt_add('IPython version: %s \n\n' % Release.version)
213 rpt_add('SVN revision : %s \n\n' % Release.revision)
214 rpt_add('Platform info : os.name -> %s, sys.platform -> %s' %
215 (os.name,sys.platform) )
216 rpt_add(sec_sep+'Current user configuration structure:\n\n')
217 rpt_add(pformat(self.IP.rc.dict()))
218 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
219 try:
220 rpt_add(sec_sep+"History of session input:")
221 for line in self.IP.user_ns['_ih']:
222 rpt_add(line)
223 rpt_add('\n*** Last line of input (may not be in above history):\n')
224 rpt_add(self.IP._last_input_line+'\n')
225 except:
226 pass
227
228 return ''.join(report)
This diff has been collapsed as it changes many lines, (1386 lines changed) Show them Hide them
@@ -1,693 +1,693 b''
1 # -*- coding: utf-8 -*-
2 """DPyGetOpt -- Demiurge Python GetOptions Module
3
4 $Id: DPyGetOpt.py 2872 2007-11-25 17:58:05Z fperez $
5
6 This module is modeled after perl's Getopt::Long module-- which
7 is, in turn, modeled after GNU's extended getopt() function.
8
9 Upon instantiation, the option specification should be a sequence
10 (list) of option definitions.
11
12 Options that take no arguments should simply contain the name of
13 the option. If a ! is post-pended, the option can be negated by
14 prepending 'no'; ie 'debug!' specifies that -debug and -nodebug
15 should be accepted.
16
17 Mandatory arguments to options are specified using a postpended
18 '=' + a type specifier. '=s' specifies a mandatory string
19 argument, '=i' specifies a mandatory integer argument, and '=f'
20 specifies a mandatory real number. In all cases, the '=' can be
21 substituted with ':' to specify that the argument is optional.
22
23 Dashes '-' in option names are allowed.
24
25 If an option has the character '@' postpended (after the
26 argumentation specification), it can appear multiple times within
27 each argument list that is processed. The results will be stored
28 in a list.
29
30 The option name can actually be a list of names separated by '|'
31 characters; ie-- 'foo|bar|baz=f@' specifies that all -foo, -bar,
32 and -baz options that appear on within the parsed argument list
33 must have a real number argument and that the accumulated list
34 of values will be available under the name 'foo'
35
36 $Id: DPyGetOpt.py 2872 2007-11-25 17:58:05Z fperez $"""
37
38 #*****************************************************************************
39 #
40 # Copyright (c) 2001 Bill Bumgarner <bbum@friday.com>
41 #
42 #
43 # Published under the terms of the MIT license, hereby reproduced:
44 #
45 # Permission is hereby granted, free of charge, to any person obtaining a copy
46 # of this software and associated documentation files (the "Software"), to
47 # deal in the Software without restriction, including without limitation the
48 # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
49 # sell copies of the Software, and to permit persons to whom the Software is
50 # furnished to do so, subject to the following conditions:
51 #
52 # The above copyright notice and this permission notice shall be included in
53 # all copies or substantial portions of the Software.
54 #
55 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
56 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
57 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
58 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
59 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
60 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
61 # IN THE SOFTWARE.
62 #
63 #*****************************************************************************
64
65 __author__ = 'Bill Bumgarner <bbum@friday.com>'
66 __license__ = 'MIT'
67 __version__ = '1.2'
68
69 # Modified to use re instead of regex and regsub modules.
70 # 2001/5/7, Jonathan Hogg <jonathan@onegoodidea.com>
71
72 import re
73 import string
74 import sys
75 import types
76
77 class Error(Exception):
78 """Base class for exceptions in the DPyGetOpt module."""
79
80 class ArgumentError(Error):
81 """Exception indicating an error in the arguments passed to
82 DPyGetOpt.processArguments."""
83
84 class SpecificationError(Error):
85 """Exception indicating an error with an option specification."""
86
87 class TerminationError(Error):
88 """Exception indicating an error with an option processing terminator."""
89
90 specificationExpr = re.compile('(?P<required>.)(?P<type>.)(?P<multi>@?)')
91
92 ArgRequired = 'Requires an Argument'
93 ArgOptional = 'Argument Optional'
94
95 # The types modules is not used for these identifiers because there
96 # is no identifier for 'boolean' or 'generic'
97 StringArgType = 'String Argument Type'
98 IntegerArgType = 'Integer Argument Type'
99 RealArgType = 'Real Argument Type'
100 BooleanArgType = 'Boolean Argument Type'
101 GenericArgType = 'Generic Argument Type'
102
103 # dictionary of conversion functions-- boolean and generic options
104 # do not accept arguments and do not need conversion functions;
105 # the identity function is used purely for convenience.
106 ConversionFunctions = {
107 StringArgType : lambda x: x,
108 IntegerArgType : string.atoi,
109 RealArgType : string.atof,
110 BooleanArgType : lambda x: x,
111 GenericArgType : lambda x: x,
112 }
113
114 class DPyGetOpt:
115
116 def __init__(self, spec = None, terminators = ['--']):
117 """
118 Declare and intialize instance variables
119
120 Yes, declaration is not necessary... but one of the things
121 I sorely miss from C/Obj-C is the concept of having an
122 interface definition that clearly declares all instance
123 variables and methods without providing any implementation
124 details. it is a useful reference!
125
126 all instance variables are initialized to 0/Null/None of
127 the appropriate type-- not even the default value...
128 """
129
130 # sys.stderr.write(string.join(spec) + "\n")
131
132 self.allowAbbreviations = 1 # boolean, 1 if abbreviations will
133 # be expanded
134 self.freeValues = [] # list, contains free values
135 self.ignoreCase = 0 # boolean, YES if ignoring case
136 self.needsParse = 0 # boolean, YES if need to reparse parameter spec
137 self.optionNames = {} # dict, all option names-- value is index of tuple
138 self.optionStartExpr = None # regexp defining the start of an option (ie; '-', '--')
139 self.optionTuples = [] # list o' tuples containing defn of options AND aliases
140 self.optionValues = {} # dict, option names (after alias expansion) -> option value(s)
141 self.orderMixed = 0 # boolean, YES if options can be mixed with args
142 self.posixCompliance = 0 # boolean, YES indicates posix like behaviour
143 self.spec = [] # list, raw specs (in case it must be reparsed)
144 self.terminators = terminators # list, strings that terminate argument processing
145 self.termValues = [] # list, values after terminator
146 self.terminator = None # full name of terminator that ended
147 # option processing
148
149 # set up defaults
150 self.setPosixCompliance()
151 self.setIgnoreCase()
152 self.setAllowAbbreviations()
153
154 # parse spec-- if present
155 if spec:
156 self.parseConfiguration(spec)
157
158 def setPosixCompliance(self, aFlag = 0):
159 """
160 Enables and disables posix compliance.
161
162 When enabled, '+' can be used as an option prefix and free
163 values can be mixed with options.
164 """
165 self.posixCompliance = aFlag
166 self.needsParse = 1
167
168 if self.posixCompliance:
169 self.optionStartExpr = re.compile('(--|-)(?P<option>[A-Za-z0-9_-]+)(?P<arg>=.*)?')
170 self.orderMixed = 0
171 else:
172 self.optionStartExpr = re.compile('(--|-|\+)(?P<option>[A-Za-z0-9_-]+)(?P<arg>=.*)?')
173 self.orderMixed = 1
174
175 def isPosixCompliant(self):
176 """
177 Returns the value of the posix compliance flag.
178 """
179 return self.posixCompliance
180
181 def setIgnoreCase(self, aFlag = 1):
182 """
183 Enables and disables ignoring case during option processing.
184 """
185 self.needsParse = 1
186 self.ignoreCase = aFlag
187
188 def ignoreCase(self):
189 """
190 Returns 1 if the option processor will ignore case when
191 processing options.
192 """
193 return self.ignoreCase
194
195 def setAllowAbbreviations(self, aFlag = 1):
196 """
197 Enables and disables the expansion of abbreviations during
198 option processing.
199 """
200 self.allowAbbreviations = aFlag
201
202 def willAllowAbbreviations(self):
203 """
204 Returns 1 if abbreviated options will be automatically
205 expanded to the non-abbreviated form (instead of causing an
206 unrecognized option error).
207 """
208 return self.allowAbbreviations
209
210 def addTerminator(self, newTerm):
211 """
212 Adds newTerm as terminator of option processing.
213
214 Whenever the option processor encounters one of the terminators
215 during option processing, the processing of options terminates
216 immediately, all remaining options are stored in the termValues
217 instance variable and the full name of the terminator is stored
218 in the terminator instance variable.
219 """
220 self.terminators = self.terminators + [newTerm]
221
222 def _addOption(self, oTuple):
223 """
224 Adds the option described by oTuple (name, (type, mode,
225 default), alias) to optionTuples. Adds index keyed under name
226 to optionNames. Raises SpecificationError if name already in
227 optionNames
228 """
229 (name, (type, mode, default, multi), realName) = oTuple
230
231 # verify name and add to option names dictionary
232 if self.optionNames.has_key(name):
233 if realName:
234 raise SpecificationError('Alias \'' + name + '\' for \'' +
235 realName +
236 '\' already used for another option or alias.')
237 else:
238 raise SpecificationError('Option named \'' + name +
239 '\' specified more than once. Specification: '
240 + option)
241
242 # validated. add to optionNames
243 self.optionNames[name] = self.tupleIndex
244 self.tupleIndex = self.tupleIndex + 1
245
246 # add to optionTuples
247 self.optionTuples = self.optionTuples + [oTuple]
248
249 # if type is boolean, add negation
250 if type == BooleanArgType:
251 alias = 'no' + name
252 specTuple = (type, mode, 0, multi)
253 oTuple = (alias, specTuple, name)
254
255 # verify name and add to option names dictionary
256 if self.optionNames.has_key(alias):
257 if realName:
258 raise SpecificationError('Negated alias \'' + name +
259 '\' for \'' + realName +
260 '\' already used for another option or alias.')
261 else:
262 raise SpecificationError('Negated option named \'' + name +
263 '\' specified more than once. Specification: '
264 + option)
265
266 # validated. add to optionNames
267 self.optionNames[alias] = self.tupleIndex
268 self.tupleIndex = self.tupleIndex + 1
269
270 # add to optionTuples
271 self.optionTuples = self.optionTuples + [oTuple]
272
273 def addOptionConfigurationTuple(self, oTuple):
274 (name, argSpec, realName) = oTuple
275 if self.ignoreCase:
276 name = string.lower(name)
277 if realName:
278 realName = string.lower(realName)
279 else:
280 realName = name
281
282 oTuple = (name, argSpec, realName)
283
284 # add option
285 self._addOption(oTuple)
286
287 def addOptionConfigurationTuples(self, oTuple):
288 if type(oTuple) is ListType:
289 for t in oTuple:
290 self.addOptionConfigurationTuple(t)
291 else:
292 self.addOptionConfigurationTuple(oTuple)
293
294 def parseConfiguration(self, spec):
295 # destroy previous stored information + store raw spec
296 self.spec = spec
297 self.optionTuples = []
298 self.optionNames = {}
299 self.tupleIndex = 0
300
301 tupleIndex = 0
302
303 # create some regex's for parsing each spec
304 splitExpr = \
305 re.compile('(?P<names>\w+[-A-Za-z0-9|]*)?(?P<spec>!|[=:][infs]@?)?')
306 for option in spec:
307 # push to lower case (does not negatively affect
308 # specification)
309 if self.ignoreCase:
310 option = string.lower(option)
311
312 # break into names, specification
313 match = splitExpr.match(option)
314 if match is None:
315 raise SpecificationError('Invalid specification {' + option +
316 '}')
317
318 names = match.group('names')
319 specification = match.group('spec')
320
321 # break name into name, aliases
322 nlist = string.split(names, '|')
323
324 # get name
325 name = nlist[0]
326 aliases = nlist[1:]
327
328 # specificationExpr = regex.symcomp('\(<required>.\)\(<type>.\)\(<multi>@?\)')
329 if not specification:
330 #spec tuple is ('type', 'arg mode', 'default value', 'multiple')
331 argType = GenericArgType
332 argMode = None
333 argDefault = 1
334 argMultiple = 0
335 elif specification == '!':
336 argType = BooleanArgType
337 argMode = None
338 argDefault = 1
339 argMultiple = 0
340 else:
341 # parse
342 match = specificationExpr.match(specification)
343 if match is None:
344 # failed to parse, die
345 raise SpecificationError('Invalid configuration for option \''
346 + option + '\'')
347
348 # determine mode
349 required = match.group('required')
350 if required == '=':
351 argMode = ArgRequired
352 elif required == ':':
353 argMode = ArgOptional
354 else:
355 raise SpecificationError('Unknown requirement configuration \''
356 + required + '\'')
357
358 # determine type
359 type = match.group('type')
360 if type == 's':
361 argType = StringArgType
362 argDefault = ''
363 elif type == 'i':
364 argType = IntegerArgType
365 argDefault = 1
366 elif type == 'f' or type == 'n':
367 argType = RealArgType
368 argDefault = 1
369 else:
370 raise SpecificationError('Unknown type specifier \'' +
371 type + '\'')
372
373 # determine quantity
374 if match.group('multi') == '@':
375 argMultiple = 1
376 else:
377 argMultiple = 0
378 ## end else (of not specification)
379
380 # construct specification tuple
381 specTuple = (argType, argMode, argDefault, argMultiple)
382
383 # add the option-- option tuple is (name, specTuple, real name)
384 oTuple = (name, specTuple, name)
385 self._addOption(oTuple)
386
387 for alias in aliases:
388 # drop to all lower (if configured to do so)
389 if self.ignoreCase:
390 alias = string.lower(alias)
391 # create configuration tuple
392 oTuple = (alias, specTuple, name)
393 # add
394 self._addOption(oTuple)
395
396 # successfully parsed....
397 self.needsParse = 0
398
399 def _getArgTuple(self, argName):
400 """
401 Returns a list containing all the specification tuples that
402 match argName. If none match, None is returned. If one
403 matches, a list with one tuple is returned. If more than one
404 match, a list containing all the tuples that matched is
405 returned.
406
407 In other words, this function does not pass judgement upon the
408 validity of multiple matches.
409 """
410 # is it in the optionNames dict?
411
412 try:
413 # sys.stderr.write(argName + string.join(self.optionNames.keys()) + "\n")
414
415 # yes, get index
416 tupleIndex = self.optionNames[argName]
417 # and return tuple as element of list
418 return [self.optionTuples[tupleIndex]]
419 except KeyError:
420 # are abbreviations allowed?
421 if not self.allowAbbreviations:
422 # No! terefore, this cannot be valid argument-- nothing found
423 return None
424
425 # argName might be an abbreviation (and, abbreviations must
426 # be allowed... or this would not have been reached!)
427
428 # create regex for argName
429 argExpr = re.compile('^' + argName)
430
431 tuples = filter(lambda x, argExpr=argExpr: argExpr.search(x[0]) is not None,
432 self.optionTuples)
433
434 if not len(tuples):
435 return None
436 else:
437 return tuples
438
439 def _isTerminator(self, optionName):
440 """
441 Returns the full name of the terminator if optionName is a valid
442 terminator. If it is, sets self.terminator to the full name of
443 the terminator.
444
445 If more than one terminator matched, raises a TerminationError with a
446 string describing the ambiguity.
447 """
448
449 # sys.stderr.write(optionName + "\n")
450 # sys.stderr.write(repr(self.terminators))
451
452 if optionName in self.terminators:
453 self.terminator = optionName
454 elif not self.allowAbbreviations:
455 return None
456
457 # regex thing in bogus
458 # termExpr = regex.compile('^' + optionName)
459
460 terms = filter(lambda x, on=optionName: string.find(x,on) == 0, self.terminators)
461
462 if not len(terms):
463 return None
464 elif len(terms) > 1:
465 raise TerminationError('Ambiguous terminator \'' + optionName +
466 '\' matches ' + repr(terms))
467
468 self.terminator = terms[0]
469 return self.terminator
470
471 def processArguments(self, args = None):
472 """
473 Processes args, a list of arguments (including options).
474
475 If args is the same as sys.argv, automatically trims the first
476 argument (the executable name/path).
477
478 If an exception is not raised, the argument list was parsed
479 correctly.
480
481 Upon successful completion, the freeValues instance variable
482 will contain all the arguments that were not associated with an
483 option in the order they were encountered. optionValues is a
484 dictionary containing the value of each option-- the method
485 valueForOption() can be used to query this dictionary.
486 terminator will contain the argument encountered that terminated
487 option processing (or None, if a terminator was never
488 encountered) and termValues will contain all of the options that
489 appeared after the Terminator (or an empty list).
490 """
491
492 if hasattr(sys, "argv") and args == sys.argv:
493 args = sys.argv[1:]
494
495 max = len(args) # maximum index + 1
496 self.freeValues = [] # array to hold return values
497 self.optionValues= {}
498 index = 0 # initial index
499 self.terminator = None
500 self.termValues = []
501
502 while index < max:
503 # obtain argument
504 arg = args[index]
505 # increment index -- REMEMBER; it is NOW incremented
506 index = index + 1
507
508 # terminate immediately if option terminator encountered
509 if self._isTerminator(arg):
510 self.freeValues = self.freeValues + args[index:]
511 self.termValues = args[index:]
512 return
513
514 # is this possibly an option?
515 match = self.optionStartExpr.match(arg)
516 if match is None:
517 # not an option-- add to freeValues
518 self.freeValues = self.freeValues + [arg]
519 if not self.orderMixed:
520 # mixing not allowed; add rest of args as freeValues
521 self.freeValues = self.freeValues + args[index:]
522 # return to caller
523 return
524 else:
525 continue
526
527 # grab name
528 optName = match.group('option')
529
530 # obtain next argument-- index has already been incremented
531 nextArg = match.group('arg')
532 if nextArg:
533 nextArg = nextArg[1:]
534 index = index - 1 # put it back
535 else:
536 try:
537 nextArg = args[index]
538 except:
539 nextArg = None
540
541 # transpose to lower case, if necessary
542 if self.ignoreCase:
543 optName = string.lower(optName)
544
545 # obtain defining tuple
546 tuples = self._getArgTuple(optName)
547
548 if tuples == None:
549 raise ArgumentError('Illegal option \'' + arg + '\'')
550 elif len(tuples) > 1:
551 raise ArgumentError('Ambiguous option \'' + arg +
552 '\'; matches ' +
553 repr(map(lambda x: x[0], tuples)))
554 else:
555 config = tuples[0]
556
557 # config is now set to the configuration tuple for the
558 # argument
559 (fullName, spec, realName) = config
560 (optType, optMode, optDefault, optMultiple) = spec
561
562 # if opt mode required, but nextArg is none, raise an error
563 if (optMode == ArgRequired):
564 if (not nextArg) or self._isTerminator(nextArg):
565 # print nextArg
566 raise ArgumentError('Option \'' + arg +
567 '\' requires an argument of type ' +
568 optType)
569
570 if (not optMode == None) and nextArg and (not self._isTerminator(nextArg)):
571 # nextArg defined, option configured to possibly consume arg
572 try:
573 # grab conversion function-- the try is more for internal diagnostics
574 func = ConversionFunctions[optType]
575 try:
576 optionValue = func(nextArg)
577 index = index + 1
578 except:
579 # only raise conversion error if REQUIRED to consume argument
580 if optMode == ArgRequired:
581 raise ArgumentError('Invalid argument to option \''
582 + arg + '\'; should be \'' +
583 optType + '\'')
584 else:
585 optionValue = optDefault
586 except ArgumentError:
587 raise
588 except:
589 raise ArgumentError('(' + arg +
590 ') Conversion function for \'' +
591 optType + '\' not found.')
592 else:
593 optionValue = optDefault
594
595 # add value to options dictionary
596 if optMultiple:
597 # can be multiple values
598 try:
599 # try to append element
600 self.optionValues[realName] = self.optionValues[realName] + [optionValue]
601 except:
602 # failed-- must not exist; add it
603 self.optionValues[realName] = [optionValue]
604 else:
605 # only one value per
606 if self.isPosixCompliant and self.optionValues.has_key(realName):
607 raise ArgumentError('Argument \'' + arg +
608 '\' occurs multiple times.')
609
610 self.optionValues[realName] = optionValue
611
612 def valueForOption(self, optionName, defaultValue = None):
613 """
614 Return the value associated with optionName. If optionName was
615 not encountered during parsing of the arguments, returns the
616 defaultValue (which defaults to None).
617 """
618 try:
619 optionValue = self.optionValues[optionName]
620 except:
621 optionValue = defaultValue
622
623 return optionValue
624
625 ##
626 ## test/example section
627 ##
628 test_error = 'Test Run Amok!'
629 def _test():
630 """
631 A relatively complete test suite.
632 """
633 try:
634 DPyGetOpt(['foo', 'bar=s', 'foo'])
635 except Error, exc:
636 print 'EXCEPTION (should be \'foo\' already used..): %s' % exc
637
638 try:
639 DPyGetOpt(['foo|bar|apple=s@', 'baz|apple!'])
640 except Error, exc:
641 print 'EXCEPTION (should be duplicate alias/name error): %s' % exc
642
643 x = DPyGetOpt(['apple|atlas=i@', 'application|executable=f@'])
644 try:
645 x.processArguments(['-app', '29.3'])
646 except Error, exc:
647 print 'EXCEPTION (should be ambiguous argument): %s' % exc
648
649 x = DPyGetOpt(['foo'], ['antigravity', 'antithesis'])
650 try:
651 x.processArguments(['-foo', 'anti'])
652 except Error, exc:
653 print 'EXCEPTION (should be ambiguous terminator): %s' % exc
654
655 profile = ['plain-option',
656 'boolean-option!',
657 'list-of-integers=i@',
658 'list-real-option|list-real-alias|list-real-pseudonym=f@',
659 'optional-string-option:s',
660 'abbreviated-string-list=s@']
661
662 terminators = ['terminator']
663
664 args = ['-plain-option',
665 '+noboolean-option',
666 '--list-of-integers', '1',
667 '+list-of-integers', '2',
668 '-list-of-integers', '3',
669 'freeargone',
670 '-list-real-option', '1.1',
671 '+list-real-alias', '1.2',
672 '--list-real-pseudonym', '1.3',
673 'freeargtwo',
674 '-abbreviated-string-list', 'String1',
675 '--abbreviated-s', 'String2',
676 '-abbrev', 'String3',
677 '-a', 'String4',
678 '-optional-string-option',
679 'term',
680 'next option should look like an invalid arg',
681 '-a']
682
683
684 print 'Using profile: ' + repr(profile)
685 print 'With terminator: ' + repr(terminators)
686 print 'Processing arguments: ' + repr(args)
687
688 go = DPyGetOpt(profile, terminators)
689 go.processArguments(args)
690
691 print 'Options (and values): ' + repr(go.optionValues)
692 print 'free args: ' + repr(go.freeValues)
693 print 'term args: ' + repr(go.termValues)
1 # -*- coding: utf-8 -*-
2 """DPyGetOpt -- Demiurge Python GetOptions Module
3
4 $Id: DPyGetOpt.py 2872 2007-11-25 17:58:05Z fperez $
5
6 This module is modeled after perl's Getopt::Long module-- which
7 is, in turn, modeled after GNU's extended getopt() function.
8
9 Upon instantiation, the option specification should be a sequence
10 (list) of option definitions.
11
12 Options that take no arguments should simply contain the name of
13 the option. If a ! is post-pended, the option can be negated by
14 prepending 'no'; ie 'debug!' specifies that -debug and -nodebug
15 should be accepted.
16
17 Mandatory arguments to options are specified using a postpended
18 '=' + a type specifier. '=s' specifies a mandatory string
19 argument, '=i' specifies a mandatory integer argument, and '=f'
20 specifies a mandatory real number. In all cases, the '=' can be
21 substituted with ':' to specify that the argument is optional.
22
23 Dashes '-' in option names are allowed.
24
25 If an option has the character '@' postpended (after the
26 argumentation specification), it can appear multiple times within
27 each argument list that is processed. The results will be stored
28 in a list.
29
30 The option name can actually be a list of names separated by '|'
31 characters; ie-- 'foo|bar|baz=f@' specifies that all -foo, -bar,
32 and -baz options that appear on within the parsed argument list
33 must have a real number argument and that the accumulated list
34 of values will be available under the name 'foo'
35
36 $Id: DPyGetOpt.py 2872 2007-11-25 17:58:05Z fperez $"""
37
38 #*****************************************************************************
39 #
40 # Copyright (c) 2001 Bill Bumgarner <bbum@friday.com>
41 #
42 #
43 # Published under the terms of the MIT license, hereby reproduced:
44 #
45 # Permission is hereby granted, free of charge, to any person obtaining a copy
46 # of this software and associated documentation files (the "Software"), to
47 # deal in the Software without restriction, including without limitation the
48 # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
49 # sell copies of the Software, and to permit persons to whom the Software is
50 # furnished to do so, subject to the following conditions:
51 #
52 # The above copyright notice and this permission notice shall be included in
53 # all copies or substantial portions of the Software.
54 #
55 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
56 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
57 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
58 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
59 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
60 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
61 # IN THE SOFTWARE.
62 #
63 #*****************************************************************************
64
65 __author__ = 'Bill Bumgarner <bbum@friday.com>'
66 __license__ = 'MIT'
67 __version__ = '1.2'
68
69 # Modified to use re instead of regex and regsub modules.
70 # 2001/5/7, Jonathan Hogg <jonathan@onegoodidea.com>
71
72 import re
73 import string
74 import sys
75 import types
76
77 class Error(Exception):
78 """Base class for exceptions in the DPyGetOpt module."""
79
80 class ArgumentError(Error):
81 """Exception indicating an error in the arguments passed to
82 DPyGetOpt.processArguments."""
83
84 class SpecificationError(Error):
85 """Exception indicating an error with an option specification."""
86
87 class TerminationError(Error):
88 """Exception indicating an error with an option processing terminator."""
89
90 specificationExpr = re.compile('(?P<required>.)(?P<type>.)(?P<multi>@?)')
91
92 ArgRequired = 'Requires an Argument'
93 ArgOptional = 'Argument Optional'
94
95 # The types modules is not used for these identifiers because there
96 # is no identifier for 'boolean' or 'generic'
97 StringArgType = 'String Argument Type'
98 IntegerArgType = 'Integer Argument Type'
99 RealArgType = 'Real Argument Type'
100 BooleanArgType = 'Boolean Argument Type'
101 GenericArgType = 'Generic Argument Type'
102
103 # dictionary of conversion functions-- boolean and generic options
104 # do not accept arguments and do not need conversion functions;
105 # the identity function is used purely for convenience.
106 ConversionFunctions = {
107 StringArgType : lambda x: x,
108 IntegerArgType : string.atoi,
109 RealArgType : string.atof,
110 BooleanArgType : lambda x: x,
111 GenericArgType : lambda x: x,
112 }
113
114 class DPyGetOpt:
115
116 def __init__(self, spec = None, terminators = ['--']):
117 """
118 Declare and intialize instance variables
119
120 Yes, declaration is not necessary... but one of the things
121 I sorely miss from C/Obj-C is the concept of having an
122 interface definition that clearly declares all instance
123 variables and methods without providing any implementation
124 details. it is a useful reference!
125
126 all instance variables are initialized to 0/Null/None of
127 the appropriate type-- not even the default value...
128 """
129
130 # sys.stderr.write(string.join(spec) + "\n")
131
132 self.allowAbbreviations = 1 # boolean, 1 if abbreviations will
133 # be expanded
134 self.freeValues = [] # list, contains free values
135 self.ignoreCase = 0 # boolean, YES if ignoring case
136 self.needsParse = 0 # boolean, YES if need to reparse parameter spec
137 self.optionNames = {} # dict, all option names-- value is index of tuple
138 self.optionStartExpr = None # regexp defining the start of an option (ie; '-', '--')
139 self.optionTuples = [] # list o' tuples containing defn of options AND aliases
140 self.optionValues = {} # dict, option names (after alias expansion) -> option value(s)
141 self.orderMixed = 0 # boolean, YES if options can be mixed with args
142 self.posixCompliance = 0 # boolean, YES indicates posix like behaviour
143 self.spec = [] # list, raw specs (in case it must be reparsed)
144 self.terminators = terminators # list, strings that terminate argument processing
145 self.termValues = [] # list, values after terminator
146 self.terminator = None # full name of terminator that ended
147 # option processing
148
149 # set up defaults
150 self.setPosixCompliance()
151 self.setIgnoreCase()
152 self.setAllowAbbreviations()
153
154 # parse spec-- if present
155 if spec:
156 self.parseConfiguration(spec)
157
158 def setPosixCompliance(self, aFlag = 0):
159 """
160 Enables and disables posix compliance.
161
162 When enabled, '+' can be used as an option prefix and free
163 values can be mixed with options.
164 """
165 self.posixCompliance = aFlag
166 self.needsParse = 1
167
168 if self.posixCompliance:
169 self.optionStartExpr = re.compile('(--|-)(?P<option>[A-Za-z0-9_-]+)(?P<arg>=.*)?')
170 self.orderMixed = 0
171 else:
172 self.optionStartExpr = re.compile('(--|-|\+)(?P<option>[A-Za-z0-9_-]+)(?P<arg>=.*)?')
173 self.orderMixed = 1
174
175 def isPosixCompliant(self):
176 """
177 Returns the value of the posix compliance flag.
178 """
179 return self.posixCompliance
180
181 def setIgnoreCase(self, aFlag = 1):
182 """
183 Enables and disables ignoring case during option processing.
184 """
185 self.needsParse = 1
186 self.ignoreCase = aFlag
187
188 def ignoreCase(self):
189 """
190 Returns 1 if the option processor will ignore case when
191 processing options.
192 """
193 return self.ignoreCase
194
195 def setAllowAbbreviations(self, aFlag = 1):
196 """
197 Enables and disables the expansion of abbreviations during
198 option processing.
199 """
200 self.allowAbbreviations = aFlag
201
202 def willAllowAbbreviations(self):
203 """
204 Returns 1 if abbreviated options will be automatically
205 expanded to the non-abbreviated form (instead of causing an
206 unrecognized option error).
207 """
208 return self.allowAbbreviations
209
210 def addTerminator(self, newTerm):
211 """
212 Adds newTerm as terminator of option processing.
213
214 Whenever the option processor encounters one of the terminators
215 during option processing, the processing of options terminates
216 immediately, all remaining options are stored in the termValues
217 instance variable and the full name of the terminator is stored
218 in the terminator instance variable.
219 """
220 self.terminators = self.terminators + [newTerm]
221
222 def _addOption(self, oTuple):
223 """
224 Adds the option described by oTuple (name, (type, mode,
225 default), alias) to optionTuples. Adds index keyed under name
226 to optionNames. Raises SpecificationError if name already in
227 optionNames
228 """
229 (name, (type, mode, default, multi), realName) = oTuple
230
231 # verify name and add to option names dictionary
232 if self.optionNames.has_key(name):
233 if realName:
234 raise SpecificationError('Alias \'' + name + '\' for \'' +
235 realName +
236 '\' already used for another option or alias.')
237 else:
238 raise SpecificationError('Option named \'' + name +
239 '\' specified more than once. Specification: '
240 + option)
241
242 # validated. add to optionNames
243 self.optionNames[name] = self.tupleIndex
244 self.tupleIndex = self.tupleIndex + 1
245
246 # add to optionTuples
247 self.optionTuples = self.optionTuples + [oTuple]
248
249 # if type is boolean, add negation
250 if type == BooleanArgType:
251 alias = 'no' + name
252 specTuple = (type, mode, 0, multi)
253 oTuple = (alias, specTuple, name)
254
255 # verify name and add to option names dictionary
256 if self.optionNames.has_key(alias):
257 if realName:
258 raise SpecificationError('Negated alias \'' + name +
259 '\' for \'' + realName +
260 '\' already used for another option or alias.')
261 else:
262 raise SpecificationError('Negated option named \'' + name +
263 '\' specified more than once. Specification: '
264 + option)
265
266 # validated. add to optionNames
267 self.optionNames[alias] = self.tupleIndex
268 self.tupleIndex = self.tupleIndex + 1
269
270 # add to optionTuples
271 self.optionTuples = self.optionTuples + [oTuple]
272
273 def addOptionConfigurationTuple(self, oTuple):
274 (name, argSpec, realName) = oTuple
275 if self.ignoreCase:
276 name = string.lower(name)
277 if realName:
278 realName = string.lower(realName)
279 else:
280 realName = name
281
282 oTuple = (name, argSpec, realName)
283
284 # add option
285 self._addOption(oTuple)
286
287 def addOptionConfigurationTuples(self, oTuple):
288 if type(oTuple) is ListType:
289 for t in oTuple:
290 self.addOptionConfigurationTuple(t)
291 else:
292 self.addOptionConfigurationTuple(oTuple)
293
294 def parseConfiguration(self, spec):
295 # destroy previous stored information + store raw spec
296 self.spec = spec
297 self.optionTuples = []
298 self.optionNames = {}
299 self.tupleIndex = 0
300
301 tupleIndex = 0
302
303 # create some regex's for parsing each spec
304 splitExpr = \
305 re.compile('(?P<names>\w+[-A-Za-z0-9|]*)?(?P<spec>!|[=:][infs]@?)?')
306 for option in spec:
307 # push to lower case (does not negatively affect
308 # specification)
309 if self.ignoreCase:
310 option = string.lower(option)
311
312 # break into names, specification
313 match = splitExpr.match(option)
314 if match is None:
315 raise SpecificationError('Invalid specification {' + option +
316 '}')
317
318 names = match.group('names')
319 specification = match.group('spec')
320
321 # break name into name, aliases
322 nlist = string.split(names, '|')
323
324 # get name
325 name = nlist[0]
326 aliases = nlist[1:]
327
328 # specificationExpr = regex.symcomp('\(<required>.\)\(<type>.\)\(<multi>@?\)')
329 if not specification:
330 #spec tuple is ('type', 'arg mode', 'default value', 'multiple')
331 argType = GenericArgType
332 argMode = None
333 argDefault = 1
334 argMultiple = 0
335 elif specification == '!':
336 argType = BooleanArgType
337 argMode = None
338 argDefault = 1
339 argMultiple = 0
340 else:
341 # parse
342 match = specificationExpr.match(specification)
343 if match is None:
344 # failed to parse, die
345 raise SpecificationError('Invalid configuration for option \''
346 + option + '\'')
347
348 # determine mode
349 required = match.group('required')
350 if required == '=':
351 argMode = ArgRequired
352 elif required == ':':
353 argMode = ArgOptional
354 else:
355 raise SpecificationError('Unknown requirement configuration \''
356 + required + '\'')
357
358 # determine type
359 type = match.group('type')
360 if type == 's':
361 argType = StringArgType
362 argDefault = ''
363 elif type == 'i':
364 argType = IntegerArgType
365 argDefault = 1
366 elif type == 'f' or type == 'n':
367 argType = RealArgType
368 argDefault = 1
369 else:
370 raise SpecificationError('Unknown type specifier \'' +
371 type + '\'')
372
373 # determine quantity
374 if match.group('multi') == '@':
375 argMultiple = 1
376 else:
377 argMultiple = 0
378 ## end else (of not specification)
379
380 # construct specification tuple
381 specTuple = (argType, argMode, argDefault, argMultiple)
382
383 # add the option-- option tuple is (name, specTuple, real name)
384 oTuple = (name, specTuple, name)
385 self._addOption(oTuple)
386
387 for alias in aliases:
388 # drop to all lower (if configured to do so)
389 if self.ignoreCase:
390 alias = string.lower(alias)
391 # create configuration tuple
392 oTuple = (alias, specTuple, name)
393 # add
394 self._addOption(oTuple)
395
396 # successfully parsed....
397 self.needsParse = 0
398
399 def _getArgTuple(self, argName):
400 """
401 Returns a list containing all the specification tuples that
402 match argName. If none match, None is returned. If one
403 matches, a list with one tuple is returned. If more than one
404 match, a list containing all the tuples that matched is
405 returned.
406
407 In other words, this function does not pass judgement upon the
408 validity of multiple matches.
409 """
410 # is it in the optionNames dict?
411
412 try:
413 # sys.stderr.write(argName + string.join(self.optionNames.keys()) + "\n")
414
415 # yes, get index
416 tupleIndex = self.optionNames[argName]
417 # and return tuple as element of list
418 return [self.optionTuples[tupleIndex]]
419 except KeyError:
420 # are abbreviations allowed?
421 if not self.allowAbbreviations:
422 # No! terefore, this cannot be valid argument-- nothing found
423 return None
424
425 # argName might be an abbreviation (and, abbreviations must
426 # be allowed... or this would not have been reached!)
427
428 # create regex for argName
429 argExpr = re.compile('^' + argName)
430
431 tuples = filter(lambda x, argExpr=argExpr: argExpr.search(x[0]) is not None,
432 self.optionTuples)
433
434 if not len(tuples):
435 return None
436 else:
437 return tuples
438
439 def _isTerminator(self, optionName):
440 """
441 Returns the full name of the terminator if optionName is a valid
442 terminator. If it is, sets self.terminator to the full name of
443 the terminator.
444
445 If more than one terminator matched, raises a TerminationError with a
446 string describing the ambiguity.
447 """
448
449 # sys.stderr.write(optionName + "\n")
450 # sys.stderr.write(repr(self.terminators))
451
452 if optionName in self.terminators:
453 self.terminator = optionName
454 elif not self.allowAbbreviations:
455 return None
456
457 # regex thing in bogus
458 # termExpr = regex.compile('^' + optionName)
459
460 terms = filter(lambda x, on=optionName: string.find(x,on) == 0, self.terminators)
461
462 if not len(terms):
463 return None
464 elif len(terms) > 1:
465 raise TerminationError('Ambiguous terminator \'' + optionName +
466 '\' matches ' + repr(terms))
467
468 self.terminator = terms[0]
469 return self.terminator
470
471 def processArguments(self, args = None):
472 """
473 Processes args, a list of arguments (including options).
474
475 If args is the same as sys.argv, automatically trims the first
476 argument (the executable name/path).
477
478 If an exception is not raised, the argument list was parsed
479 correctly.
480
481 Upon successful completion, the freeValues instance variable
482 will contain all the arguments that were not associated with an
483 option in the order they were encountered. optionValues is a
484 dictionary containing the value of each option-- the method
485 valueForOption() can be used to query this dictionary.
486 terminator will contain the argument encountered that terminated
487 option processing (or None, if a terminator was never
488 encountered) and termValues will contain all of the options that
489 appeared after the Terminator (or an empty list).
490 """
491
492 if hasattr(sys, "argv") and args == sys.argv:
493 args = sys.argv[1:]
494
495 max = len(args) # maximum index + 1
496 self.freeValues = [] # array to hold return values
497 self.optionValues= {}
498 index = 0 # initial index
499 self.terminator = None
500 self.termValues = []
501
502 while index < max:
503 # obtain argument
504 arg = args[index]
505 # increment index -- REMEMBER; it is NOW incremented
506 index = index + 1
507
508 # terminate immediately if option terminator encountered
509 if self._isTerminator(arg):
510 self.freeValues = self.freeValues + args[index:]
511 self.termValues = args[index:]
512 return
513
514 # is this possibly an option?
515 match = self.optionStartExpr.match(arg)
516 if match is None:
517 # not an option-- add to freeValues
518 self.freeValues = self.freeValues + [arg]
519 if not self.orderMixed:
520 # mixing not allowed; add rest of args as freeValues
521 self.freeValues = self.freeValues + args[index:]
522 # return to caller
523 return
524 else:
525 continue
526
527 # grab name
528 optName = match.group('option')
529
530 # obtain next argument-- index has already been incremented
531 nextArg = match.group('arg')
532 if nextArg:
533 nextArg = nextArg[1:]
534 index = index - 1 # put it back
535 else:
536 try:
537 nextArg = args[index]
538 except:
539 nextArg = None
540
541 # transpose to lower case, if necessary
542 if self.ignoreCase:
543 optName = string.lower(optName)
544
545 # obtain defining tuple
546 tuples = self._getArgTuple(optName)
547
548 if tuples == None:
549 raise ArgumentError('Illegal option \'' + arg + '\'')
550 elif len(tuples) > 1:
551 raise ArgumentError('Ambiguous option \'' + arg +
552 '\'; matches ' +
553 repr(map(lambda x: x[0], tuples)))
554 else:
555 config = tuples[0]
556
557 # config is now set to the configuration tuple for the
558 # argument
559 (fullName, spec, realName) = config
560 (optType, optMode, optDefault, optMultiple) = spec
561
562 # if opt mode required, but nextArg is none, raise an error
563 if (optMode == ArgRequired):
564 if (not nextArg) or self._isTerminator(nextArg):
565 # print nextArg
566 raise ArgumentError('Option \'' + arg +
567 '\' requires an argument of type ' +
568 optType)
569
570 if (not optMode == None) and nextArg and (not self._isTerminator(nextArg)):
571 # nextArg defined, option configured to possibly consume arg
572 try:
573 # grab conversion function-- the try is more for internal diagnostics
574 func = ConversionFunctions[optType]
575 try:
576 optionValue = func(nextArg)
577 index = index + 1
578 except:
579 # only raise conversion error if REQUIRED to consume argument
580 if optMode == ArgRequired:
581 raise ArgumentError('Invalid argument to option \''
582 + arg + '\'; should be \'' +
583 optType + '\'')
584 else:
585 optionValue = optDefault
586 except ArgumentError:
587 raise
588 except:
589 raise ArgumentError('(' + arg +
590 ') Conversion function for \'' +
591 optType + '\' not found.')
592 else:
593 optionValue = optDefault
594
595 # add value to options dictionary
596 if optMultiple:
597 # can be multiple values
598 try:
599 # try to append element
600 self.optionValues[realName] = self.optionValues[realName] + [optionValue]
601 except:
602 # failed-- must not exist; add it
603 self.optionValues[realName] = [optionValue]
604 else:
605 # only one value per
606 if self.isPosixCompliant and self.optionValues.has_key(realName):
607 raise ArgumentError('Argument \'' + arg +
608 '\' occurs multiple times.')
609
610 self.optionValues[realName] = optionValue
611
612 def valueForOption(self, optionName, defaultValue = None):
613 """
614 Return the value associated with optionName. If optionName was
615 not encountered during parsing of the arguments, returns the
616 defaultValue (which defaults to None).
617 """
618 try:
619 optionValue = self.optionValues[optionName]
620 except:
621 optionValue = defaultValue
622
623 return optionValue
624
625 ##
626 ## test/example section
627 ##
628 test_error = 'Test Run Amok!'
629 def _test():
630 """
631 A relatively complete test suite.
632 """
633 try:
634 DPyGetOpt(['foo', 'bar=s', 'foo'])
635 except Error, exc:
636 print 'EXCEPTION (should be \'foo\' already used..): %s' % exc
637
638 try:
639 DPyGetOpt(['foo|bar|apple=s@', 'baz|apple!'])
640 except Error, exc:
641 print 'EXCEPTION (should be duplicate alias/name error): %s' % exc
642
643 x = DPyGetOpt(['apple|atlas=i@', 'application|executable=f@'])
644 try:
645 x.processArguments(['-app', '29.3'])
646 except Error, exc:
647 print 'EXCEPTION (should be ambiguous argument): %s' % exc
648
649 x = DPyGetOpt(['foo'], ['antigravity', 'antithesis'])
650 try:
651 x.processArguments(['-foo', 'anti'])
652 except Error, exc:
653 print 'EXCEPTION (should be ambiguous terminator): %s' % exc
654
655 profile = ['plain-option',
656 'boolean-option!',
657 'list-of-integers=i@',
658 'list-real-option|list-real-alias|list-real-pseudonym=f@',
659 'optional-string-option:s',
660 'abbreviated-string-list=s@']
661
662 terminators = ['terminator']
663
664 args = ['-plain-option',
665 '+noboolean-option',
666 '--list-of-integers', '1',
667 '+list-of-integers', '2',
668 '-list-of-integers', '3',
669 'freeargone',
670 '-list-real-option', '1.1',
671 '+list-real-alias', '1.2',
672 '--list-real-pseudonym', '1.3',
673 'freeargtwo',
674 '-abbreviated-string-list', 'String1',
675 '--abbreviated-s', 'String2',
676 '-abbrev', 'String3',
677 '-a', 'String4',
678 '-optional-string-option',
679 'term',
680 'next option should look like an invalid arg',
681 '-a']
682
683
684 print 'Using profile: ' + repr(profile)
685 print 'With terminator: ' + repr(terminators)
686 print 'Processing arguments: ' + repr(args)
687
688 go = DPyGetOpt(profile, terminators)
689 go.processArguments(args)
690
691 print 'Options (and values): ' + repr(go.optionValues)
692 print 'free args: ' + repr(go.freeValues)
693 print 'term args: ' + repr(go.termValues)
This diff has been collapsed as it changes many lines, (1034 lines changed) Show them Hide them
@@ -1,517 +1,517 b''
1 # -*- coding: utf-8 -*-
2 """
3 Pdb debugger class.
4
5 Modified from the standard pdb.Pdb class to avoid including readline, so that
6 the command line completion of other programs which include this isn't
7 damaged.
8
9 In the future, this class will be expanded with improvements over the standard
10 pdb.
11
12 The code in this file is mainly lifted out of cmd.py in Python 2.2, with minor
13 changes. Licensing should therefore be under the standard Python terms. For
14 details on the PSF (Python Software Foundation) standard license, see:
15
16 http://www.python.org/2.2.3/license.html
17
18 $Id: Debugger.py 2913 2007-12-31 12:42:14Z vivainio $"""
19
20 #*****************************************************************************
21 #
22 # This file is licensed under the PSF license.
23 #
24 # Copyright (C) 2001 Python Software Foundation, www.python.org
25 # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
26 #
27 #
28 #*****************************************************************************
29
30 from IPython import Release
31 __author__ = '%s <%s>' % Release.authors['Fernando']
32 __license__ = 'Python'
33
34 import bdb
35 import cmd
36 import linecache
37 import os
38 import sys
39
40 from IPython import PyColorize, ColorANSI, ipapi
41 from IPython.genutils import Term
42 from IPython.excolors import ExceptionColors
43
44 # See if we can use pydb.
45 has_pydb = False
46 prompt = 'ipdb> '
47 #We have to check this directly from sys.argv, config struct not yet available
48 if '-pydb' in sys.argv:
49 try:
50 import pydb
51 if hasattr(pydb.pydb, "runl") and pydb.version>'1.17':
52 # Version 1.17 is broken, and that's what ships with Ubuntu Edgy, so we
53 # better protect against it.
54 has_pydb = True
55 except ImportError:
56 print "Pydb (http://bashdb.sourceforge.net/pydb/) does not seem to be available"
57
58 if has_pydb:
59 from pydb import Pdb as OldPdb
60 #print "Using pydb for %run -d and post-mortem" #dbg
61 prompt = 'ipydb> '
62 else:
63 from pdb import Pdb as OldPdb
64
65 # Allow the set_trace code to operate outside of an ipython instance, even if
66 # it does so with some limitations. The rest of this support is implemented in
67 # the Tracer constructor.
68 def BdbQuit_excepthook(et,ev,tb):
69 if et==bdb.BdbQuit:
70 print 'Exiting Debugger.'
71 else:
72 BdbQuit_excepthook.excepthook_ori(et,ev,tb)
73
74 def BdbQuit_IPython_excepthook(self,et,ev,tb):
75 print 'Exiting Debugger.'
76
77 class Tracer(object):
78 """Class for local debugging, similar to pdb.set_trace.
79
80 Instances of this class, when called, behave like pdb.set_trace, but
81 providing IPython's enhanced capabilities.
82
83 This is implemented as a class which must be initialized in your own code
84 and not as a standalone function because we need to detect at runtime
85 whether IPython is already active or not. That detection is done in the
86 constructor, ensuring that this code plays nicely with a running IPython,
87 while functioning acceptably (though with limitations) if outside of it.
88 """
89
90 def __init__(self,colors=None):
91 """Create a local debugger instance.
92
93 :Parameters:
94
95 - `colors` (None): a string containing the name of the color scheme to
96 use, it must be one of IPython's valid color schemes. If not given, the
97 function will default to the current IPython scheme when running inside
98 IPython, and to 'NoColor' otherwise.
99
100 Usage example:
101
102 from IPython.Debugger import Tracer; debug_here = Tracer()
103
104 ... later in your code
105 debug_here() # -> will open up the debugger at that point.
106
107 Once the debugger activates, you can use all of its regular commands to
108 step through code, set breakpoints, etc. See the pdb documentation
109 from the Python standard library for usage details.
110 """
111
112 global __IPYTHON__
113 try:
114 __IPYTHON__
115 except NameError:
116 # Outside of ipython, we set our own exception hook manually
117 __IPYTHON__ = ipapi.get(True,False)
118 BdbQuit_excepthook.excepthook_ori = sys.excepthook
119 sys.excepthook = BdbQuit_excepthook
120 def_colors = 'NoColor'
121 try:
122 # Limited tab completion support
123 import rlcompleter,readline
124 readline.parse_and_bind('tab: complete')
125 except ImportError:
126 pass
127 else:
128 # In ipython, we use its custom exception handler mechanism
129 ip = ipapi.get()
130 def_colors = ip.options.colors
131 ip.set_custom_exc((bdb.BdbQuit,),BdbQuit_IPython_excepthook)
132
133 if colors is None:
134 colors = def_colors
135 self.debugger = Pdb(colors)
136
137 def __call__(self):
138 """Starts an interactive debugger at the point where called.
139
140 This is similar to the pdb.set_trace() function from the std lib, but
141 using IPython's enhanced debugger."""
142
143 self.debugger.set_trace(sys._getframe().f_back)
144
145 def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
146 """Make new_fn have old_fn's doc string. This is particularly useful
147 for the do_... commands that hook into the help system.
148 Adapted from from a comp.lang.python posting
149 by Duncan Booth."""
150 def wrapper(*args, **kw):
151 return new_fn(*args, **kw)
152 if old_fn.__doc__:
153 wrapper.__doc__ = old_fn.__doc__ + additional_text
154 return wrapper
155
156 def _file_lines(fname):
157 """Return the contents of a named file as a list of lines.
158
159 This function never raises an IOError exception: if the file can't be
160 read, it simply returns an empty list."""
161
162 try:
163 outfile = open(fname)
164 except IOError:
165 return []
166 else:
167 out = outfile.readlines()
168 outfile.close()
169 return out
170
171 class Pdb(OldPdb):
172 """Modified Pdb class, does not load readline."""
173
174 if sys.version[:3] >= '2.5' or has_pydb:
175 def __init__(self,color_scheme='NoColor',completekey=None,
176 stdin=None, stdout=None):
177
178 # Parent constructor:
179 if has_pydb and completekey is None:
180 OldPdb.__init__(self,stdin=stdin,stdout=Term.cout)
181 else:
182 OldPdb.__init__(self,completekey,stdin,stdout)
183
184 self.prompt = prompt # The default prompt is '(Pdb)'
185
186 # IPython changes...
187 self.is_pydb = has_pydb
188
189 if self.is_pydb:
190
191 # iplib.py's ipalias seems to want pdb's checkline
192 # which located in pydb.fn
193 import pydb.fns
194 self.checkline = lambda filename, lineno: \
195 pydb.fns.checkline(self, filename, lineno)
196
197 self.curframe = None
198 self.do_restart = self.new_do_restart
199
200 self.old_all_completions = __IPYTHON__.Completer.all_completions
201 __IPYTHON__.Completer.all_completions=self.all_completions
202
203 self.do_list = decorate_fn_with_doc(self.list_command_pydb,
204 OldPdb.do_list)
205 self.do_l = self.do_list
206 self.do_frame = decorate_fn_with_doc(self.new_do_frame,
207 OldPdb.do_frame)
208
209 self.aliases = {}
210
211 # Create color table: we copy the default one from the traceback
212 # module and add a few attributes needed for debugging
213 self.color_scheme_table = ExceptionColors.copy()
214
215 # shorthands
216 C = ColorANSI.TermColors
217 cst = self.color_scheme_table
218
219 cst['NoColor'].colors.breakpoint_enabled = C.NoColor
220 cst['NoColor'].colors.breakpoint_disabled = C.NoColor
221
222 cst['Linux'].colors.breakpoint_enabled = C.LightRed
223 cst['Linux'].colors.breakpoint_disabled = C.Red
224
225 cst['LightBG'].colors.breakpoint_enabled = C.LightRed
226 cst['LightBG'].colors.breakpoint_disabled = C.Red
227
228 self.set_colors(color_scheme)
229
230 # Add a python parser so we can syntax highlight source while
231 # debugging.
232 self.parser = PyColorize.Parser()
233
234
235 else:
236 # Ugly hack: for Python 2.3-2.4, we can't call the parent constructor,
237 # because it binds readline and breaks tab-completion. This means we
238 # have to COPY the constructor here.
239 def __init__(self,color_scheme='NoColor'):
240 bdb.Bdb.__init__(self)
241 cmd.Cmd.__init__(self,completekey=None) # don't load readline
242 self.prompt = 'ipdb> ' # The default prompt is '(Pdb)'
243 self.aliases = {}
244
245 # These two lines are part of the py2.4 constructor, let's put them
246 # unconditionally here as they won't cause any problems in 2.3.
247 self.mainpyfile = ''
248 self._wait_for_mainpyfile = 0
249
250 # Read $HOME/.pdbrc and ./.pdbrc
251 try:
252 self.rcLines = _file_lines(os.path.join(os.environ['HOME'],
253 ".pdbrc"))
254 except KeyError:
255 self.rcLines = []
256 self.rcLines.extend(_file_lines(".pdbrc"))
257
258 # Create color table: we copy the default one from the traceback
259 # module and add a few attributes needed for debugging
260 ExceptionColors.set_active_scheme(color_scheme)
261 self.color_scheme_table = ExceptionColors.copy()
262
263 # shorthands
264 C = ColorANSI.TermColors
265 cst = self.color_scheme_table
266
267 cst['NoColor'].colors.breakpoint_enabled = C.NoColor
268 cst['NoColor'].colors.breakpoint_disabled = C.NoColor
269
270 cst['Linux'].colors.breakpoint_enabled = C.LightRed
271 cst['Linux'].colors.breakpoint_disabled = C.Red
272
273 cst['LightBG'].colors.breakpoint_enabled = C.LightRed
274 cst['LightBG'].colors.breakpoint_disabled = C.Red
275
276 self.set_colors(color_scheme)
277
278 # Add a python parser so we can syntax highlight source while
279 # debugging.
280 self.parser = PyColorize.Parser()
281
282 def set_colors(self, scheme):
283 """Shorthand access to the color table scheme selector method."""
284 self.color_scheme_table.set_active_scheme(scheme)
285
286 def interaction(self, frame, traceback):
287 __IPYTHON__.set_completer_frame(frame)
288 OldPdb.interaction(self, frame, traceback)
289
290 def new_do_up(self, arg):
291 OldPdb.do_up(self, arg)
292 __IPYTHON__.set_completer_frame(self.curframe)
293 do_u = do_up = decorate_fn_with_doc(new_do_up, OldPdb.do_up)
294
295 def new_do_down(self, arg):
296 OldPdb.do_down(self, arg)
297 __IPYTHON__.set_completer_frame(self.curframe)
298
299 do_d = do_down = decorate_fn_with_doc(new_do_down, OldPdb.do_down)
300
301 def new_do_frame(self, arg):
302 OldPdb.do_frame(self, arg)
303 __IPYTHON__.set_completer_frame(self.curframe)
304
305 def new_do_quit(self, arg):
306
307 if hasattr(self, 'old_all_completions'):
308 __IPYTHON__.Completer.all_completions=self.old_all_completions
309
310
311 return OldPdb.do_quit(self, arg)
312
313 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
314
315 def new_do_restart(self, arg):
316 """Restart command. In the context of ipython this is exactly the same
317 thing as 'quit'."""
318 self.msg("Restart doesn't make sense here. Using 'quit' instead.")
319 return self.do_quit(arg)
320
321 def postloop(self):
322 __IPYTHON__.set_completer_frame(None)
323
324 def print_stack_trace(self):
325 try:
326 for frame_lineno in self.stack:
327 self.print_stack_entry(frame_lineno, context = 5)
328 except KeyboardInterrupt:
329 pass
330
331 def print_stack_entry(self,frame_lineno,prompt_prefix='\n-> ',
332 context = 3):
333 #frame, lineno = frame_lineno
334 print >>Term.cout, self.format_stack_entry(frame_lineno, '', context)
335
336 def format_stack_entry(self, frame_lineno, lprefix=': ', context = 3):
337 import linecache, repr
338
339 ret = []
340
341 Colors = self.color_scheme_table.active_colors
342 ColorsNormal = Colors.Normal
343 tpl_link = '%s%%s%s' % (Colors.filenameEm, ColorsNormal)
344 tpl_call = '%s%%s%s%%s%s' % (Colors.vName, Colors.valEm, ColorsNormal)
345 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
346 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line,
347 ColorsNormal)
348
349 frame, lineno = frame_lineno
350
351 return_value = ''
352 if '__return__' in frame.f_locals:
353 rv = frame.f_locals['__return__']
354 #return_value += '->'
355 return_value += repr.repr(rv) + '\n'
356 ret.append(return_value)
357
358 #s = filename + '(' + `lineno` + ')'
359 filename = self.canonic(frame.f_code.co_filename)
360 link = tpl_link % filename
361
362 if frame.f_code.co_name:
363 func = frame.f_code.co_name
364 else:
365 func = "<lambda>"
366
367 call = ''
368 if func != '?':
369 if '__args__' in frame.f_locals:
370 args = repr.repr(frame.f_locals['__args__'])
371 else:
372 args = '()'
373 call = tpl_call % (func, args)
374
375 # The level info should be generated in the same format pdb uses, to
376 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
377 if frame is self.curframe:
378 ret.append('> ')
379 else:
380 ret.append(' ')
381 ret.append('%s(%s)%s\n' % (link,lineno,call))
382
383 start = lineno - 1 - context//2
384 lines = linecache.getlines(filename)
385 start = max(start, 0)
386 start = min(start, len(lines) - context)
387 lines = lines[start : start + context]
388
389 for i,line in enumerate(lines):
390 show_arrow = (start + 1 + i == lineno)
391 linetpl = (frame is self.curframe or show_arrow) \
392 and tpl_line_em \
393 or tpl_line
394 ret.append(self.__format_line(linetpl, filename,
395 start + 1 + i, line,
396 arrow = show_arrow) )
397
398 return ''.join(ret)
399
400 def __format_line(self, tpl_line, filename, lineno, line, arrow = False):
401 bp_mark = ""
402 bp_mark_color = ""
403
404 scheme = self.color_scheme_table.active_scheme_name
405 new_line, err = self.parser.format2(line, 'str', scheme)
406 if not err: line = new_line
407
408 bp = None
409 if lineno in self.get_file_breaks(filename):
410 bps = self.get_breaks(filename, lineno)
411 bp = bps[-1]
412
413 if bp:
414 Colors = self.color_scheme_table.active_colors
415 bp_mark = str(bp.number)
416 bp_mark_color = Colors.breakpoint_enabled
417 if not bp.enabled:
418 bp_mark_color = Colors.breakpoint_disabled
419
420 numbers_width = 7
421 if arrow:
422 # This is the line with the error
423 pad = numbers_width - len(str(lineno)) - len(bp_mark)
424 if pad >= 3:
425 marker = '-'*(pad-3) + '-> '
426 elif pad == 2:
427 marker = '> '
428 elif pad == 1:
429 marker = '>'
430 else:
431 marker = ''
432 num = '%s%s' % (marker, str(lineno))
433 line = tpl_line % (bp_mark_color + bp_mark, num, line)
434 else:
435 num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
436 line = tpl_line % (bp_mark_color + bp_mark, num, line)
437
438 return line
439
440 def list_command_pydb(self, arg):
441 """List command to use if we have a newer pydb installed"""
442 filename, first, last = OldPdb.parse_list_cmd(self, arg)
443 if filename is not None:
444 self.print_list_lines(filename, first, last)
445
446 def print_list_lines(self, filename, first, last):
447 """The printing (as opposed to the parsing part of a 'list'
448 command."""
449 try:
450 Colors = self.color_scheme_table.active_colors
451 ColorsNormal = Colors.Normal
452 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
453 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
454 src = []
455 for lineno in range(first, last+1):
456 line = linecache.getline(filename, lineno)
457 if not line:
458 break
459
460 if lineno == self.curframe.f_lineno:
461 line = self.__format_line(tpl_line_em, filename, lineno, line, arrow = True)
462 else:
463 line = self.__format_line(tpl_line, filename, lineno, line, arrow = False)
464
465 src.append(line)
466 self.lineno = lineno
467
468 print >>Term.cout, ''.join(src)
469
470 except KeyboardInterrupt:
471 pass
472
473 def do_list(self, arg):
474 self.lastcmd = 'list'
475 last = None
476 if arg:
477 try:
478 x = eval(arg, {}, {})
479 if type(x) == type(()):
480 first, last = x
481 first = int(first)
482 last = int(last)
483 if last < first:
484 # Assume it's a count
485 last = first + last
486 else:
487 first = max(1, int(x) - 5)
488 except:
489 print '*** Error in argument:', `arg`
490 return
491 elif self.lineno is None:
492 first = max(1, self.curframe.f_lineno - 5)
493 else:
494 first = self.lineno + 1
495 if last is None:
496 last = first + 10
497 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
498
499 do_l = do_list
500
501 def do_pdef(self, arg):
502 """The debugger interface to magic_pdef"""
503 namespaces = [('Locals', self.curframe.f_locals),
504 ('Globals', self.curframe.f_globals)]
505 __IPYTHON__.magic_pdef(arg, namespaces=namespaces)
506
507 def do_pdoc(self, arg):
508 """The debugger interface to magic_pdoc"""
509 namespaces = [('Locals', self.curframe.f_locals),
510 ('Globals', self.curframe.f_globals)]
511 __IPYTHON__.magic_pdoc(arg, namespaces=namespaces)
512
513 def do_pinfo(self, arg):
514 """The debugger equivalant of ?obj"""
515 namespaces = [('Locals', self.curframe.f_locals),
516 ('Globals', self.curframe.f_globals)]
517 __IPYTHON__.magic_pinfo("pinfo %s" % arg, namespaces=namespaces)
1 # -*- coding: utf-8 -*-
2 """
3 Pdb debugger class.
4
5 Modified from the standard pdb.Pdb class to avoid including readline, so that
6 the command line completion of other programs which include this isn't
7 damaged.
8
9 In the future, this class will be expanded with improvements over the standard
10 pdb.
11
12 The code in this file is mainly lifted out of cmd.py in Python 2.2, with minor
13 changes. Licensing should therefore be under the standard Python terms. For
14 details on the PSF (Python Software Foundation) standard license, see:
15
16 http://www.python.org/2.2.3/license.html
17
18 $Id: Debugger.py 2913 2007-12-31 12:42:14Z vivainio $"""
19
20 #*****************************************************************************
21 #
22 # This file is licensed under the PSF license.
23 #
24 # Copyright (C) 2001 Python Software Foundation, www.python.org
25 # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
26 #
27 #
28 #*****************************************************************************
29
30 from IPython import Release
31 __author__ = '%s <%s>' % Release.authors['Fernando']
32 __license__ = 'Python'
33
34 import bdb
35 import cmd
36 import linecache
37 import os
38 import sys
39
40 from IPython import PyColorize, ColorANSI, ipapi
41 from IPython.genutils import Term
42 from IPython.excolors import ExceptionColors
43
44 # See if we can use pydb.
45 has_pydb = False
46 prompt = 'ipdb> '
47 #We have to check this directly from sys.argv, config struct not yet available
48 if '-pydb' in sys.argv:
49 try:
50 import pydb
51 if hasattr(pydb.pydb, "runl") and pydb.version>'1.17':
52 # Version 1.17 is broken, and that's what ships with Ubuntu Edgy, so we
53 # better protect against it.
54 has_pydb = True
55 except ImportError:
56 print "Pydb (http://bashdb.sourceforge.net/pydb/) does not seem to be available"
57
58 if has_pydb:
59 from pydb import Pdb as OldPdb
60 #print "Using pydb for %run -d and post-mortem" #dbg
61 prompt = 'ipydb> '
62 else:
63 from pdb import Pdb as OldPdb
64
65 # Allow the set_trace code to operate outside of an ipython instance, even if
66 # it does so with some limitations. The rest of this support is implemented in
67 # the Tracer constructor.
68 def BdbQuit_excepthook(et,ev,tb):
69 if et==bdb.BdbQuit:
70 print 'Exiting Debugger.'
71 else:
72 BdbQuit_excepthook.excepthook_ori(et,ev,tb)
73
74 def BdbQuit_IPython_excepthook(self,et,ev,tb):
75 print 'Exiting Debugger.'
76
77 class Tracer(object):
78 """Class for local debugging, similar to pdb.set_trace.
79
80 Instances of this class, when called, behave like pdb.set_trace, but
81 providing IPython's enhanced capabilities.
82
83 This is implemented as a class which must be initialized in your own code
84 and not as a standalone function because we need to detect at runtime
85 whether IPython is already active or not. That detection is done in the
86 constructor, ensuring that this code plays nicely with a running IPython,
87 while functioning acceptably (though with limitations) if outside of it.
88 """
89
90 def __init__(self,colors=None):
91 """Create a local debugger instance.
92
93 :Parameters:
94
95 - `colors` (None): a string containing the name of the color scheme to
96 use, it must be one of IPython's valid color schemes. If not given, the
97 function will default to the current IPython scheme when running inside
98 IPython, and to 'NoColor' otherwise.
99
100 Usage example:
101
102 from IPython.Debugger import Tracer; debug_here = Tracer()
103
104 ... later in your code
105 debug_here() # -> will open up the debugger at that point.
106
107 Once the debugger activates, you can use all of its regular commands to
108 step through code, set breakpoints, etc. See the pdb documentation
109 from the Python standard library for usage details.
110 """
111
112 global __IPYTHON__
113 try:
114 __IPYTHON__
115 except NameError:
116 # Outside of ipython, we set our own exception hook manually
117 __IPYTHON__ = ipapi.get(True,False)
118 BdbQuit_excepthook.excepthook_ori = sys.excepthook
119 sys.excepthook = BdbQuit_excepthook
120 def_colors = 'NoColor'
121 try:
122 # Limited tab completion support
123 import rlcompleter,readline
124 readline.parse_and_bind('tab: complete')
125 except ImportError:
126 pass
127 else:
128 # In ipython, we use its custom exception handler mechanism
129 ip = ipapi.get()
130 def_colors = ip.options.colors
131 ip.set_custom_exc((bdb.BdbQuit,),BdbQuit_IPython_excepthook)
132
133 if colors is None:
134 colors = def_colors
135 self.debugger = Pdb(colors)
136
137 def __call__(self):
138 """Starts an interactive debugger at the point where called.
139
140 This is similar to the pdb.set_trace() function from the std lib, but
141 using IPython's enhanced debugger."""
142
143 self.debugger.set_trace(sys._getframe().f_back)
144
145 def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
146 """Make new_fn have old_fn's doc string. This is particularly useful
147 for the do_... commands that hook into the help system.
148 Adapted from from a comp.lang.python posting
149 by Duncan Booth."""
150 def wrapper(*args, **kw):
151 return new_fn(*args, **kw)
152 if old_fn.__doc__:
153 wrapper.__doc__ = old_fn.__doc__ + additional_text
154 return wrapper
155
156 def _file_lines(fname):
157 """Return the contents of a named file as a list of lines.
158
159 This function never raises an IOError exception: if the file can't be
160 read, it simply returns an empty list."""
161
162 try:
163 outfile = open(fname)
164 except IOError:
165 return []
166 else:
167 out = outfile.readlines()
168 outfile.close()
169 return out
170
171 class Pdb(OldPdb):
172 """Modified Pdb class, does not load readline."""
173
174 if sys.version[:3] >= '2.5' or has_pydb:
175 def __init__(self,color_scheme='NoColor',completekey=None,
176 stdin=None, stdout=None):
177
178 # Parent constructor:
179 if has_pydb and completekey is None:
180 OldPdb.__init__(self,stdin=stdin,stdout=Term.cout)
181 else:
182 OldPdb.__init__(self,completekey,stdin,stdout)
183
184 self.prompt = prompt # The default prompt is '(Pdb)'
185
186 # IPython changes...
187 self.is_pydb = has_pydb
188
189 if self.is_pydb:
190
191 # iplib.py's ipalias seems to want pdb's checkline
192 # which located in pydb.fn
193 import pydb.fns
194 self.checkline = lambda filename, lineno: \
195 pydb.fns.checkline(self, filename, lineno)
196
197 self.curframe = None
198 self.do_restart = self.new_do_restart
199
200 self.old_all_completions = __IPYTHON__.Completer.all_completions
201 __IPYTHON__.Completer.all_completions=self.all_completions
202
203 self.do_list = decorate_fn_with_doc(self.list_command_pydb,
204 OldPdb.do_list)
205 self.do_l = self.do_list
206 self.do_frame = decorate_fn_with_doc(self.new_do_frame,
207 OldPdb.do_frame)
208
209 self.aliases = {}
210
211 # Create color table: we copy the default one from the traceback
212 # module and add a few attributes needed for debugging
213 self.color_scheme_table = ExceptionColors.copy()
214
215 # shorthands
216 C = ColorANSI.TermColors
217 cst = self.color_scheme_table
218
219 cst['NoColor'].colors.breakpoint_enabled = C.NoColor
220 cst['NoColor'].colors.breakpoint_disabled = C.NoColor
221
222 cst['Linux'].colors.breakpoint_enabled = C.LightRed
223 cst['Linux'].colors.breakpoint_disabled = C.Red
224
225 cst['LightBG'].colors.breakpoint_enabled = C.LightRed
226 cst['LightBG'].colors.breakpoint_disabled = C.Red
227
228 self.set_colors(color_scheme)
229
230 # Add a python parser so we can syntax highlight source while
231 # debugging.
232 self.parser = PyColorize.Parser()
233
234
235 else:
236 # Ugly hack: for Python 2.3-2.4, we can't call the parent constructor,
237 # because it binds readline and breaks tab-completion. This means we
238 # have to COPY the constructor here.
239 def __init__(self,color_scheme='NoColor'):
240 bdb.Bdb.__init__(self)
241 cmd.Cmd.__init__(self,completekey=None) # don't load readline
242 self.prompt = 'ipdb> ' # The default prompt is '(Pdb)'
243 self.aliases = {}
244
245 # These two lines are part of the py2.4 constructor, let's put them
246 # unconditionally here as they won't cause any problems in 2.3.
247 self.mainpyfile = ''
248 self._wait_for_mainpyfile = 0
249
250 # Read $HOME/.pdbrc and ./.pdbrc
251 try:
252 self.rcLines = _file_lines(os.path.join(os.environ['HOME'],
253 ".pdbrc"))
254 except KeyError:
255 self.rcLines = []
256 self.rcLines.extend(_file_lines(".pdbrc"))
257
258 # Create color table: we copy the default one from the traceback
259 # module and add a few attributes needed for debugging
260 ExceptionColors.set_active_scheme(color_scheme)
261 self.color_scheme_table = ExceptionColors.copy()
262
263 # shorthands
264 C = ColorANSI.TermColors
265 cst = self.color_scheme_table
266
267 cst['NoColor'].colors.breakpoint_enabled = C.NoColor
268 cst['NoColor'].colors.breakpoint_disabled = C.NoColor
269
270 cst['Linux'].colors.breakpoint_enabled = C.LightRed
271 cst['Linux'].colors.breakpoint_disabled = C.Red
272
273 cst['LightBG'].colors.breakpoint_enabled = C.LightRed
274 cst['LightBG'].colors.breakpoint_disabled = C.Red
275
276 self.set_colors(color_scheme)
277
278 # Add a python parser so we can syntax highlight source while
279 # debugging.
280 self.parser = PyColorize.Parser()
281
282 def set_colors(self, scheme):
283 """Shorthand access to the color table scheme selector method."""
284 self.color_scheme_table.set_active_scheme(scheme)
285
286 def interaction(self, frame, traceback):
287 __IPYTHON__.set_completer_frame(frame)
288 OldPdb.interaction(self, frame, traceback)
289
290 def new_do_up(self, arg):
291 OldPdb.do_up(self, arg)
292 __IPYTHON__.set_completer_frame(self.curframe)
293 do_u = do_up = decorate_fn_with_doc(new_do_up, OldPdb.do_up)
294
295 def new_do_down(self, arg):
296 OldPdb.do_down(self, arg)
297 __IPYTHON__.set_completer_frame(self.curframe)
298
299 do_d = do_down = decorate_fn_with_doc(new_do_down, OldPdb.do_down)
300
301 def new_do_frame(self, arg):
302 OldPdb.do_frame(self, arg)
303 __IPYTHON__.set_completer_frame(self.curframe)
304
305 def new_do_quit(self, arg):
306
307 if hasattr(self, 'old_all_completions'):
308 __IPYTHON__.Completer.all_completions=self.old_all_completions
309
310
311 return OldPdb.do_quit(self, arg)
312
313 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
314
315 def new_do_restart(self, arg):
316 """Restart command. In the context of ipython this is exactly the same
317 thing as 'quit'."""
318 self.msg("Restart doesn't make sense here. Using 'quit' instead.")
319 return self.do_quit(arg)
320
321 def postloop(self):
322 __IPYTHON__.set_completer_frame(None)
323
324 def print_stack_trace(self):
325 try:
326 for frame_lineno in self.stack:
327 self.print_stack_entry(frame_lineno, context = 5)
328 except KeyboardInterrupt:
329 pass
330
331 def print_stack_entry(self,frame_lineno,prompt_prefix='\n-> ',
332 context = 3):
333 #frame, lineno = frame_lineno
334 print >>Term.cout, self.format_stack_entry(frame_lineno, '', context)
335
336 def format_stack_entry(self, frame_lineno, lprefix=': ', context = 3):
337 import linecache, repr
338
339 ret = []
340
341 Colors = self.color_scheme_table.active_colors
342 ColorsNormal = Colors.Normal
343 tpl_link = '%s%%s%s' % (Colors.filenameEm, ColorsNormal)
344 tpl_call = '%s%%s%s%%s%s' % (Colors.vName, Colors.valEm, ColorsNormal)
345 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
346 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line,
347 ColorsNormal)
348
349 frame, lineno = frame_lineno
350
351 return_value = ''
352 if '__return__' in frame.f_locals:
353 rv = frame.f_locals['__return__']
354 #return_value += '->'
355 return_value += repr.repr(rv) + '\n'
356 ret.append(return_value)
357
358 #s = filename + '(' + `lineno` + ')'
359 filename = self.canonic(frame.f_code.co_filename)
360 link = tpl_link % filename
361
362 if frame.f_code.co_name:
363 func = frame.f_code.co_name
364 else:
365 func = "<lambda>"
366
367 call = ''
368 if func != '?':
369 if '__args__' in frame.f_locals:
370 args = repr.repr(frame.f_locals['__args__'])
371 else:
372 args = '()'
373 call = tpl_call % (func, args)
374
375 # The level info should be generated in the same format pdb uses, to
376 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
377 if frame is self.curframe:
378 ret.append('> ')
379 else:
380 ret.append(' ')
381 ret.append('%s(%s)%s\n' % (link,lineno,call))
382
383 start = lineno - 1 - context//2
384 lines = linecache.getlines(filename)
385 start = max(start, 0)
386 start = min(start, len(lines) - context)
387 lines = lines[start : start + context]
388
389 for i,line in enumerate(lines):
390 show_arrow = (start + 1 + i == lineno)
391 linetpl = (frame is self.curframe or show_arrow) \
392 and tpl_line_em \
393 or tpl_line
394 ret.append(self.__format_line(linetpl, filename,
395 start + 1 + i, line,
396 arrow = show_arrow) )
397
398 return ''.join(ret)
399
400 def __format_line(self, tpl_line, filename, lineno, line, arrow = False):
401 bp_mark = ""
402 bp_mark_color = ""
403
404 scheme = self.color_scheme_table.active_scheme_name
405 new_line, err = self.parser.format2(line, 'str', scheme)
406 if not err: line = new_line
407
408 bp = None
409 if lineno in self.get_file_breaks(filename):
410 bps = self.get_breaks(filename, lineno)
411 bp = bps[-1]
412
413 if bp:
414 Colors = self.color_scheme_table.active_colors
415 bp_mark = str(bp.number)
416 bp_mark_color = Colors.breakpoint_enabled
417 if not bp.enabled:
418 bp_mark_color = Colors.breakpoint_disabled
419
420 numbers_width = 7
421 if arrow:
422 # This is the line with the error
423 pad = numbers_width - len(str(lineno)) - len(bp_mark)
424 if pad >= 3:
425 marker = '-'*(pad-3) + '-> '
426 elif pad == 2:
427 marker = '> '
428 elif pad == 1:
429 marker = '>'
430 else:
431 marker = ''
432 num = '%s%s' % (marker, str(lineno))
433 line = tpl_line % (bp_mark_color + bp_mark, num, line)
434 else:
435 num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
436 line = tpl_line % (bp_mark_color + bp_mark, num, line)
437
438 return line
439
440 def list_command_pydb(self, arg):
441 """List command to use if we have a newer pydb installed"""
442 filename, first, last = OldPdb.parse_list_cmd(self, arg)
443 if filename is not None:
444 self.print_list_lines(filename, first, last)
445
446 def print_list_lines(self, filename, first, last):
447 """The printing (as opposed to the parsing part of a 'list'
448 command."""
449 try:
450 Colors = self.color_scheme_table.active_colors
451 ColorsNormal = Colors.Normal
452 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
453 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
454 src = []
455 for lineno in range(first, last+1):
456 line = linecache.getline(filename, lineno)
457 if not line:
458 break
459
460 if lineno == self.curframe.f_lineno:
461 line = self.__format_line(tpl_line_em, filename, lineno, line, arrow = True)
462 else:
463 line = self.__format_line(tpl_line, filename, lineno, line, arrow = False)
464
465 src.append(line)
466 self.lineno = lineno
467
468 print >>Term.cout, ''.join(src)
469
470 except KeyboardInterrupt:
471 pass
472
473 def do_list(self, arg):
474 self.lastcmd = 'list'
475 last = None
476 if arg:
477 try:
478 x = eval(arg, {}, {})
479 if type(x) == type(()):
480 first, last = x
481 first = int(first)
482 last = int(last)
483 if last < first:
484 # Assume it's a count
485 last = first + last
486 else:
487 first = max(1, int(x) - 5)
488 except:
489 print '*** Error in argument:', `arg`
490 return
491 elif self.lineno is None:
492 first = max(1, self.curframe.f_lineno - 5)
493 else:
494 first = self.lineno + 1
495 if last is None:
496 last = first + 10
497 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
498
499 do_l = do_list
500
501 def do_pdef(self, arg):
502 """The debugger interface to magic_pdef"""
503 namespaces = [('Locals', self.curframe.f_locals),
504 ('Globals', self.curframe.f_globals)]
505 __IPYTHON__.magic_pdef(arg, namespaces=namespaces)
506
507 def do_pdoc(self, arg):
508 """The debugger interface to magic_pdoc"""
509 namespaces = [('Locals', self.curframe.f_locals),
510 ('Globals', self.curframe.f_globals)]
511 __IPYTHON__.magic_pdoc(arg, namespaces=namespaces)
512
513 def do_pinfo(self, arg):
514 """The debugger equivalant of ?obj"""
515 namespaces = [('Locals', self.curframe.f_locals),
516 ('Globals', self.curframe.f_globals)]
517 __IPYTHON__.magic_pinfo("pinfo %s" % arg, namespaces=namespaces)
This diff has been collapsed as it changes many lines, (518 lines changed) Show them Hide them
@@ -1,259 +1,259 b''
1 # -*- coding: utf-8 -*-
2 """Modified input prompt for executing files.
3
4 We define a special input line filter to allow typing lines which begin with
5 '~', '/' or '.'. If one of those strings is encountered, it is automatically
6 executed.
7
8 $Id: InterpreterExec.py 2724 2007-09-07 08:05:38Z fperez $"""
9
10 #*****************************************************************************
11 # Copyright (C) 2004 W.J. van der Laan <gnufnork@hetdigitalegat.nl>
12 # Copyright (C) 2004-2006 Fernando Perez <fperez@colorado.edu>
13 #
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
16 #*****************************************************************************
17
18 from IPython import Release
19 __author__ = 'W.J. van der Laan <gnufnork@hetdigitalegat.nl>, '\
20 '%s <%s>' % Release.authors['Fernando']
21 __license__ = Release.license
22
23 # TODO: deprecated
24 def prefilter_shell(self,line,continuation):
25 """Alternate prefilter, modified for shell-like functionality.
26
27 - Execute all lines beginning with '~', '/' or '.'
28 - $var=cmd <=> %sc var=cmd
29 - $$var=cmd <=> %sc -l var=cmd
30 """
31
32 if line:
33 l0 = line[0]
34 if l0 in '~/.':
35 return self._prefilter("!%s"%line,continuation)
36 elif l0=='$':
37 lrest = line[1:]
38 if lrest.startswith('$'):
39 # $$var=cmd <=> %sc -l var=cmd
40 return self._prefilter("%ssc -l %s" % (self.ESC_MAGIC,lrest[1:]),
41 continuation)
42 else:
43 # $var=cmd <=> %sc var=cmd
44 return self._prefilter("%ssc %s" % (self.ESC_MAGIC,lrest),
45 continuation)
46 else:
47 return self._prefilter(line,continuation)
48 else:
49 return self._prefilter(line,continuation)
50
51 # Rebind this to be the new IPython prefilter:
52 from IPython.iplib import InteractiveShell
53 InteractiveShell.prefilter = prefilter_shell
54 # Clean up the namespace.
55 del InteractiveShell,prefilter_shell
56
57 # Provide pysh and further shell-oriented services
58 import os,sys,shutil
59 from IPython.genutils import system,shell,getoutput,getoutputerror
60
61 # Short aliases for getting shell output as a string and a list
62 sout = getoutput
63 lout = lambda cmd: getoutput(cmd,split=1)
64
65 # Empty function, meant as a docstring holder so help(pysh) works.
66 def pysh():
67 """Pysh is a set of modules and extensions to IPython which make shell-like
68 usage with Python syntax more convenient. Keep in mind that pysh is NOT a
69 full-blown shell, so don't try to make it your /etc/passwd entry!
70
71 In particular, it has no job control, so if you type Ctrl-Z (under Unix),
72 you'll suspend pysh itself, not the process you just started.
73
74 Since pysh is really nothing but a customized IPython, you should
75 familiarize yourself with IPython's features. This brief help mainly
76 documents areas in which pysh differs from the normal IPython.
77
78 ALIASES
79 -------
80 All of your $PATH has been loaded as IPython aliases, so you should be
81 able to type any normal system command and have it executed. See %alias?
82 and %unalias? for details on the alias facilities.
83
84 SPECIAL SYNTAX
85 --------------
86 Any lines which begin with '~', '/' and '.' will be executed as shell
87 commands instead of as Python code. The special escapes below are also
88 recognized. !cmd is valid in single or multi-line input, all others are
89 only valid in single-line input:
90
91 !cmd - pass 'cmd' directly to the shell
92 !!cmd - execute 'cmd' and return output as a list (split on '\\n')
93 $var=cmd - capture output of cmd into var, as a string
94 $$var=cmd - capture output of cmd into var, as a list (split on '\\n')
95
96 The $/$$ syntaxes make Python variables from system output, which you can
97 later use for further scripting. The converse is also possible: when
98 executing an alias or calling to the system via !/!!, you can expand any
99 python variable or expression by prepending it with $. Full details of
100 the allowed syntax can be found in Python's PEP 215.
101
102 A few brief examples will illustrate these:
103
104 fperez[~/test]|3> !ls *s.py
105 scopes.py strings.py
106
107 ls is an internal alias, so there's no need to use !:
108 fperez[~/test]|4> ls *s.py
109 scopes.py* strings.py
110
111 !!ls will return the output into a Python variable:
112 fperez[~/test]|5> !!ls *s.py
113 <5> ['scopes.py', 'strings.py']
114 fperez[~/test]|6> print _5
115 ['scopes.py', 'strings.py']
116
117 $ and $$ allow direct capture to named variables:
118 fperez[~/test]|7> $astr = ls *s.py
119 fperez[~/test]|8> astr
120 <8> 'scopes.py\\nstrings.py'
121
122 fperez[~/test]|9> $$alist = ls *s.py
123 fperez[~/test]|10> alist
124 <10> ['scopes.py', 'strings.py']
125
126 alist is now a normal python list you can loop over. Using $ will expand
127 back the python values when alias calls are made:
128 fperez[~/test]|11> for f in alist:
129 |..> print 'file',f,
130 |..> wc -l $f
131 |..>
132 file scopes.py 13 scopes.py
133 file strings.py 4 strings.py
134
135 Note that you may need to protect your variables with braces if you want
136 to append strings to their names. To copy all files in alist to .bak
137 extensions, you must use:
138 fperez[~/test]|12> for f in alist:
139 |..> cp $f ${f}.bak
140
141 If you try using $f.bak, you'll get an AttributeError exception saying
142 that your string object doesn't have a .bak attribute. This is because
143 the $ expansion mechanism allows you to expand full Python expressions:
144 fperez[~/test]|13> echo "sys.platform is: $sys.platform"
145 sys.platform is: linux2
146
147 IPython's input history handling is still active, which allows you to
148 rerun a single block of multi-line input by simply using exec:
149 fperez[~/test]|14> $$alist = ls *.eps
150 fperez[~/test]|15> exec _i11
151 file image2.eps 921 image2.eps
152 file image.eps 921 image.eps
153
154 While these are new special-case syntaxes, they are designed to allow very
155 efficient use of the shell with minimal typing. At an interactive shell
156 prompt, conciseness of expression wins over readability.
157
158 USEFUL FUNCTIONS AND MODULES
159 ----------------------------
160 The os, sys and shutil modules from the Python standard library are
161 automatically loaded. Some additional functions, useful for shell usage,
162 are listed below. You can request more help about them with '?'.
163
164 shell - execute a command in the underlying system shell
165 system - like shell(), but return the exit status of the command
166 sout - capture the output of a command as a string
167 lout - capture the output of a command as a list (split on '\\n')
168 getoutputerror - capture (output,error) of a shell command
169
170 sout/lout are the functional equivalents of $/$$. They are provided to
171 allow you to capture system output in the middle of true python code,
172 function definitions, etc (where $ and $$ are invalid).
173
174 DIRECTORY MANAGEMENT
175 --------------------
176 Since each command passed by pysh to the underlying system is executed in
177 a subshell which exits immediately, you can NOT use !cd to navigate the
178 filesystem.
179
180 Pysh provides its own builtin '%cd' magic command to move in the
181 filesystem (the % is not required with automagic on). It also maintains a
182 list of visited directories (use %dhist to see it) and allows direct
183 switching to any of them. Type 'cd?' for more details.
184
185 %pushd, %popd and %dirs are provided for directory stack handling.
186
187 PROMPT CUSTOMIZATION
188 --------------------
189
190 The supplied ipythonrc-pysh profile comes with an example of a very
191 colored and detailed prompt, mainly to serve as an illustration. The
192 valid escape sequences, besides color names, are:
193
194 \\# - Prompt number.
195 \\D - Dots, as many as there are digits in \\# (so they align).
196 \\w - Current working directory (cwd).
197 \\W - Basename of current working directory.
198 \\XN - Where N=0..5. N terms of the cwd, with $HOME written as ~.
199 \\YN - Where N=0..5. Like XN, but if ~ is term N+1 it's also shown.
200 \\u - Username.
201 \\H - Full hostname.
202 \\h - Hostname up to first '.'
203 \\$ - Root symbol ($ or #).
204 \\t - Current time, in H:M:S format.
205 \\v - IPython release version.
206 \\n - Newline.
207 \\r - Carriage return.
208 \\\\ - An explicitly escaped '\\'.
209
210 You can configure your prompt colors using any ANSI color escape. Each
211 color escape sets the color for any subsequent text, until another escape
212 comes in and changes things. The valid color escapes are:
213
214 \\C_Black
215 \\C_Blue
216 \\C_Brown
217 \\C_Cyan
218 \\C_DarkGray
219 \\C_Green
220 \\C_LightBlue
221 \\C_LightCyan
222 \\C_LightGray
223 \\C_LightGreen
224 \\C_LightPurple
225 \\C_LightRed
226 \\C_Purple
227 \\C_Red
228 \\C_White
229 \\C_Yellow
230 \\C_Normal - Stop coloring, defaults to your terminal settings.
231 """
232 pass
233
234 # Configure a few things. Much of this is fairly hackish, since IPython
235 # doesn't really expose a clean API for it. Be careful if you start making
236 # many modifications here.
237
238
239 # Set the 'cd' command to quiet mode, a more shell-like behavior
240 __IPYTHON__.default_option('cd','-q')
241
242 # This is redundant, ipy_user_conf.py will determine this
243 # Load all of $PATH as aliases
244 __IPYTHON__.magic_rehashx()
245
246 # Remove %sc,%sx if present as aliases
247 __IPYTHON__.magic_unalias('sc')
248 __IPYTHON__.magic_unalias('sx')
249
250 # We need different criteria for line-splitting, so that aliases such as
251 # 'gnome-terminal' are interpreted as a single alias instead of variable
252 # 'gnome' minus variable 'terminal'.
253 import re
254 __IPYTHON__.line_split = re.compile(r'^([\s*,;/])'
255 r'([\?\w\.\-\+]+\w*\s*)'
256 r'(\(?.*$)')
257
258 # Namespace cleanup
259 del re
1 # -*- coding: utf-8 -*-
2 """Modified input prompt for executing files.
3
4 We define a special input line filter to allow typing lines which begin with
5 '~', '/' or '.'. If one of those strings is encountered, it is automatically
6 executed.
7
8 $Id: InterpreterExec.py 2724 2007-09-07 08:05:38Z fperez $"""
9
10 #*****************************************************************************
11 # Copyright (C) 2004 W.J. van der Laan <gnufnork@hetdigitalegat.nl>
12 # Copyright (C) 2004-2006 Fernando Perez <fperez@colorado.edu>
13 #
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
16 #*****************************************************************************
17
18 from IPython import Release
19 __author__ = 'W.J. van der Laan <gnufnork@hetdigitalegat.nl>, '\
20 '%s <%s>' % Release.authors['Fernando']
21 __license__ = Release.license
22
23 # TODO: deprecated
24 def prefilter_shell(self,line,continuation):
25 """Alternate prefilter, modified for shell-like functionality.
26
27 - Execute all lines beginning with '~', '/' or '.'
28 - $var=cmd <=> %sc var=cmd
29 - $$var=cmd <=> %sc -l var=cmd
30 """
31
32 if line:
33 l0 = line[0]
34 if l0 in '~/.':
35 return self._prefilter("!%s"%line,continuation)
36 elif l0=='$':
37 lrest = line[1:]
38 if lrest.startswith('$'):
39 # $$var=cmd <=> %sc -l var=cmd
40 return self._prefilter("%ssc -l %s" % (self.ESC_MAGIC,lrest[1:]),
41 continuation)
42 else:
43 # $var=cmd <=> %sc var=cmd
44 return self._prefilter("%ssc %s" % (self.ESC_MAGIC,lrest),
45 continuation)
46 else:
47 return self._prefilter(line,continuation)
48 else:
49 return self._prefilter(line,continuation)
50
51 # Rebind this to be the new IPython prefilter:
52 from IPython.iplib import InteractiveShell
53 InteractiveShell.prefilter = prefilter_shell
54 # Clean up the namespace.
55 del InteractiveShell,prefilter_shell
56
57 # Provide pysh and further shell-oriented services
58 import os,sys,shutil
59 from IPython.genutils import system,shell,getoutput,getoutputerror
60
61 # Short aliases for getting shell output as a string and a list
62 sout = getoutput
63 lout = lambda cmd: getoutput(cmd,split=1)
64
65 # Empty function, meant as a docstring holder so help(pysh) works.
66 def pysh():
67 """Pysh is a set of modules and extensions to IPython which make shell-like
68 usage with Python syntax more convenient. Keep in mind that pysh is NOT a
69 full-blown shell, so don't try to make it your /etc/passwd entry!
70
71 In particular, it has no job control, so if you type Ctrl-Z (under Unix),
72 you'll suspend pysh itself, not the process you just started.
73
74 Since pysh is really nothing but a customized IPython, you should
75 familiarize yourself with IPython's features. This brief help mainly
76 documents areas in which pysh differs from the normal IPython.
77
78 ALIASES
79 -------
80 All of your $PATH has been loaded as IPython aliases, so you should be
81 able to type any normal system command and have it executed. See %alias?
82 and %unalias? for details on the alias facilities.
83
84 SPECIAL SYNTAX
85 --------------
86 Any lines which begin with '~', '/' and '.' will be executed as shell
87 commands instead of as Python code. The special escapes below are also
88 recognized. !cmd is valid in single or multi-line input, all others are
89 only valid in single-line input:
90
91 !cmd - pass 'cmd' directly to the shell
92 !!cmd - execute 'cmd' and return output as a list (split on '\\n')
93 $var=cmd - capture output of cmd into var, as a string
94 $$var=cmd - capture output of cmd into var, as a list (split on '\\n')
95
96 The $/$$ syntaxes make Python variables from system output, which you can
97 later use for further scripting. The converse is also possible: when
98 executing an alias or calling to the system via !/!!, you can expand any
99 python variable or expression by prepending it with $. Full details of
100 the allowed syntax can be found in Python's PEP 215.
101
102 A few brief examples will illustrate these:
103
104 fperez[~/test]|3> !ls *s.py
105 scopes.py strings.py
106
107 ls is an internal alias, so there's no need to use !:
108 fperez[~/test]|4> ls *s.py
109 scopes.py* strings.py
110
111 !!ls will return the output into a Python variable:
112 fperez[~/test]|5> !!ls *s.py
113 <5> ['scopes.py', 'strings.py']
114 fperez[~/test]|6> print _5
115 ['scopes.py', 'strings.py']
116
117 $ and $$ allow direct capture to named variables:
118 fperez[~/test]|7> $astr = ls *s.py
119 fperez[~/test]|8> astr
120 <8> 'scopes.py\\nstrings.py'
121
122 fperez[~/test]|9> $$alist = ls *s.py
123 fperez[~/test]|10> alist
124 <10> ['scopes.py', 'strings.py']
125
126 alist is now a normal python list you can loop over. Using $ will expand
127 back the python values when alias calls are made:
128 fperez[~/test]|11> for f in alist:
129 |..> print 'file',f,
130 |..> wc -l $f
131 |..>
132 file scopes.py 13 scopes.py
133 file strings.py 4 strings.py
134
135 Note that you may need to protect your variables with braces if you want
136 to append strings to their names. To copy all files in alist to .bak
137 extensions, you must use:
138 fperez[~/test]|12> for f in alist:
139 |..> cp $f ${f}.bak
140
141 If you try using $f.bak, you'll get an AttributeError exception saying
142 that your string object doesn't have a .bak attribute. This is because
143 the $ expansion mechanism allows you to expand full Python expressions:
144 fperez[~/test]|13> echo "sys.platform is: $sys.platform"
145 sys.platform is: linux2
146
147 IPython's input history handling is still active, which allows you to
148 rerun a single block of multi-line input by simply using exec:
149 fperez[~/test]|14> $$alist = ls *.eps
150 fperez[~/test]|15> exec _i11
151 file image2.eps 921 image2.eps
152 file image.eps 921 image.eps
153
154 While these are new special-case syntaxes, they are designed to allow very
155 efficient use of the shell with minimal typing. At an interactive shell
156 prompt, conciseness of expression wins over readability.
157
158 USEFUL FUNCTIONS AND MODULES
159 ----------------------------
160 The os, sys and shutil modules from the Python standard library are
161 automatically loaded. Some additional functions, useful for shell usage,
162 are listed below. You can request more help about them with '?'.
163
164 shell - execute a command in the underlying system shell
165 system - like shell(), but return the exit status of the command
166 sout - capture the output of a command as a string
167 lout - capture the output of a command as a list (split on '\\n')
168 getoutputerror - capture (output,error) of a shell command
169
170 sout/lout are the functional equivalents of $/$$. They are provided to
171 allow you to capture system output in the middle of true python code,
172 function definitions, etc (where $ and $$ are invalid).
173
174 DIRECTORY MANAGEMENT
175 --------------------
176 Since each command passed by pysh to the underlying system is executed in
177 a subshell which exits immediately, you can NOT use !cd to navigate the
178 filesystem.
179
180 Pysh provides its own builtin '%cd' magic command to move in the
181 filesystem (the % is not required with automagic on). It also maintains a
182 list of visited directories (use %dhist to see it) and allows direct
183 switching to any of them. Type 'cd?' for more details.
184
185 %pushd, %popd and %dirs are provided for directory stack handling.
186
187 PROMPT CUSTOMIZATION
188 --------------------
189
190 The supplied ipythonrc-pysh profile comes with an example of a very
191 colored and detailed prompt, mainly to serve as an illustration. The
192 valid escape sequences, besides color names, are:
193
194 \\# - Prompt number.
195 \\D - Dots, as many as there are digits in \\# (so they align).
196 \\w - Current working directory (cwd).
197 \\W - Basename of current working directory.
198 \\XN - Where N=0..5. N terms of the cwd, with $HOME written as ~.
199 \\YN - Where N=0..5. Like XN, but if ~ is term N+1 it's also shown.
200 \\u - Username.
201 \\H - Full hostname.
202 \\h - Hostname up to first '.'
203 \\$ - Root symbol ($ or #).
204 \\t - Current time, in H:M:S format.
205 \\v - IPython release version.
206 \\n - Newline.
207 \\r - Carriage return.
208 \\\\ - An explicitly escaped '\\'.
209
210 You can configure your prompt colors using any ANSI color escape. Each
211 color escape sets the color for any subsequent text, until another escape
212 comes in and changes things. The valid color escapes are:
213
214 \\C_Black
215 \\C_Blue
216 \\C_Brown
217 \\C_Cyan
218 \\C_DarkGray
219 \\C_Green
220 \\C_LightBlue
221 \\C_LightCyan
222 \\C_LightGray
223 \\C_LightGreen
224 \\C_LightPurple
225 \\C_LightRed
226 \\C_Purple
227 \\C_Red
228 \\C_White
229 \\C_Yellow
230 \\C_Normal - Stop coloring, defaults to your terminal settings.
231 """
232 pass
233
234 # Configure a few things. Much of this is fairly hackish, since IPython
235 # doesn't really expose a clean API for it. Be careful if you start making
236 # many modifications here.
237
238
239 # Set the 'cd' command to quiet mode, a more shell-like behavior
240 __IPYTHON__.default_option('cd','-q')
241
242 # This is redundant, ipy_user_conf.py will determine this
243 # Load all of $PATH as aliases
244 __IPYTHON__.magic_rehashx()
245
246 # Remove %sc,%sx if present as aliases
247 __IPYTHON__.magic_unalias('sc')
248 __IPYTHON__.magic_unalias('sx')
249
250 # We need different criteria for line-splitting, so that aliases such as
251 # 'gnome-terminal' are interpreted as a single alias instead of variable
252 # 'gnome' minus variable 'terminal'.
253 import re
254 __IPYTHON__.line_split = re.compile(r'^([\s*,;/])'
255 r'([\?\w\.\-\+]+\w*\s*)'
256 r'(\(?.*$)')
257
258 # Namespace cleanup
259 del re
@@ -1,122 +1,122 b''
1 # -*- coding: utf-8 -*-
2 """Modified input prompt for entering text with >>> or ... at the start.
3
4 We define a special input line filter to allow typing lines which begin with
5 '>>> ' or '... '. These two strings, if present at the start of the input
6 line, are stripped. This allows for direct pasting of code from examples such
7 as those available in the standard Python tutorial.
8
9 Normally pasting such code is one chunk is impossible because of the
10 extraneous >>> and ..., requiring one to do a line by line paste with careful
11 removal of those characters. This module allows pasting that kind of
12 multi-line examples in one pass.
13
14 Here is an 'screenshot' of a section of the tutorial pasted into IPython with
15 this feature enabled:
16
17 In [1]: >>> def fib2(n): # return Fibonacci series up to n
18 ...: ... '''Return a list containing the Fibonacci series up to n.'''
19 ...: ... result = []
20 ...: ... a, b = 0, 1
21 ...: ... while b < n:
22 ...: ... result.append(b) # see below
23 ...: ... a, b = b, a+b
24 ...: ... return result
25 ...:
26
27 In [2]: fib2(10)
28 Out[2]: [1, 1, 2, 3, 5, 8]
29
30 The >>> and ... are stripped from the input so that the python interpreter
31 only sees the real part of the code.
32
33 All other input is processed normally.
34
35 Notes
36 =====
37
38 * You can even paste code that has extra initial spaces, such as is common in
39 doctests:
40
41 In [3]: >>> a = ['Mary', 'had', 'a', 'little', 'lamb']
42
43 In [4]: >>> for i in range(len(a)):
44 ...: ... print i, a[i]
45 ...: ...
46 0 Mary
47 1 had
48 2 a
49 3 little
50 4 lamb
51 """
52
53 #*****************************************************************************
54 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
55 #
56 # Distributed under the terms of the BSD License. The full license is in
57 # the file COPYING, distributed as part of this software.
58 #*****************************************************************************
59
60 from IPython import Release
61 __author__ = '%s <%s>' % Release.authors['Fernando']
62 __license__ = Release.license
63
64 # This file is an example of how to modify IPython's line-processing behavior
65 # without touching the internal code. We'll define an alternate pre-processing
66 # stage which allows a special form of input (which is invalid Python syntax)
67 # for certain quantities, rewrites a line of proper Python in those cases, and
68 # then passes it off to IPython's normal processor for further work.
69
70 # With this kind of customization, IPython can be adapted for many
71 # special-purpose scenarios providing alternate input syntaxes.
72
73 # This file can be imported like a regular module.
74
75 # IPython has a prefilter() function that analyzes each input line. We redefine
76 # it here to first pre-process certain forms of input
77
78 # The prototype of any alternate prefilter must be like this one (the name
79 # doesn't matter):
80 # - line is a string containing the user input line.
81 # - continuation is a parameter which tells us if we are processing a first
82 # line of user input or the second or higher of a multi-line statement.
83
84 import re
85
86 from IPython.iplib import InteractiveShell
87
88 PROMPT_RE = re.compile(r'(^[ \t]*>>> |^[ \t]*\.\.\. )')
89
90 def prefilter_paste(self,line,continuation):
91 """Alternate prefilter for input of pasted code from an interpreter.
92 """
93 if not line:
94 return ''
95 m = PROMPT_RE.match(line)
96 if m:
97 # In the end, always call the default IPython _prefilter() function.
98 # Note that self must be passed explicitly, b/c we're calling the
99 # unbound class method (since this method will overwrite the instance
100 # prefilter())
101 return self._prefilter(line[len(m.group(0)):],continuation)
102 elif line.strip() == '...':
103 return self._prefilter('',continuation)
104 elif line.isspace():
105 # This allows us to recognize multiple input prompts separated by blank
106 # lines and pasted in a single chunk, very common when pasting doctests
107 # or long tutorial passages.
108 return ''
109 else:
110 return self._prefilter(line,continuation)
111
112 def activate_prefilter():
113 """Rebind the input-pasting filter to be the new IPython prefilter"""
114 InteractiveShell.prefilter = prefilter_paste
115
116 def deactivate_prefilter():
117 """Reset the filter."""
118 InteractiveShell.prefilter = InteractiveShell._prefilter
119
120 # Just a heads up at the console
121 activate_prefilter()
122 print '*** Pasting of code with ">>>" or "..." has been enabled.'
1 # -*- coding: utf-8 -*-
2 """Modified input prompt for entering text with >>> or ... at the start.
3
4 We define a special input line filter to allow typing lines which begin with
5 '>>> ' or '... '. These two strings, if present at the start of the input
6 line, are stripped. This allows for direct pasting of code from examples such
7 as those available in the standard Python tutorial.
8
9 Normally pasting such code is one chunk is impossible because of the
10 extraneous >>> and ..., requiring one to do a line by line paste with careful
11 removal of those characters. This module allows pasting that kind of
12 multi-line examples in one pass.
13
14 Here is an 'screenshot' of a section of the tutorial pasted into IPython with
15 this feature enabled:
16
17 In [1]: >>> def fib2(n): # return Fibonacci series up to n
18 ...: ... '''Return a list containing the Fibonacci series up to n.'''
19 ...: ... result = []
20 ...: ... a, b = 0, 1
21 ...: ... while b < n:
22 ...: ... result.append(b) # see below
23 ...: ... a, b = b, a+b
24 ...: ... return result
25 ...:
26
27 In [2]: fib2(10)
28 Out[2]: [1, 1, 2, 3, 5, 8]
29
30 The >>> and ... are stripped from the input so that the python interpreter
31 only sees the real part of the code.
32
33 All other input is processed normally.
34
35 Notes
36 =====
37
38 * You can even paste code that has extra initial spaces, such as is common in
39 doctests:
40
41 In [3]: >>> a = ['Mary', 'had', 'a', 'little', 'lamb']
42
43 In [4]: >>> for i in range(len(a)):
44 ...: ... print i, a[i]
45 ...: ...
46 0 Mary
47 1 had
48 2 a
49 3 little
50 4 lamb
51 """
52
53 #*****************************************************************************
54 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
55 #
56 # Distributed under the terms of the BSD License. The full license is in
57 # the file COPYING, distributed as part of this software.
58 #*****************************************************************************
59
60 from IPython import Release
61 __author__ = '%s <%s>' % Release.authors['Fernando']
62 __license__ = Release.license
63
64 # This file is an example of how to modify IPython's line-processing behavior
65 # without touching the internal code. We'll define an alternate pre-processing
66 # stage which allows a special form of input (which is invalid Python syntax)
67 # for certain quantities, rewrites a line of proper Python in those cases, and
68 # then passes it off to IPython's normal processor for further work.
69
70 # With this kind of customization, IPython can be adapted for many
71 # special-purpose scenarios providing alternate input syntaxes.
72
73 # This file can be imported like a regular module.
74
75 # IPython has a prefilter() function that analyzes each input line. We redefine
76 # it here to first pre-process certain forms of input
77
78 # The prototype of any alternate prefilter must be like this one (the name
79 # doesn't matter):
80 # - line is a string containing the user input line.
81 # - continuation is a parameter which tells us if we are processing a first
82 # line of user input or the second or higher of a multi-line statement.
83
84 import re
85
86 from IPython.iplib import InteractiveShell
87
88 PROMPT_RE = re.compile(r'(^[ \t]*>>> |^[ \t]*\.\.\. )')
89
90 def prefilter_paste(self,line,continuation):
91 """Alternate prefilter for input of pasted code from an interpreter.
92 """
93 if not line:
94 return ''
95 m = PROMPT_RE.match(line)
96 if m:
97 # In the end, always call the default IPython _prefilter() function.
98 # Note that self must be passed explicitly, b/c we're calling the
99 # unbound class method (since this method will overwrite the instance
100 # prefilter())
101 return self._prefilter(line[len(m.group(0)):],continuation)
102 elif line.strip() == '...':
103 return self._prefilter('',continuation)
104 elif line.isspace():
105 # This allows us to recognize multiple input prompts separated by blank
106 # lines and pasted in a single chunk, very common when pasting doctests
107 # or long tutorial passages.
108 return ''
109 else:
110 return self._prefilter(line,continuation)
111
112 def activate_prefilter():
113 """Rebind the input-pasting filter to be the new IPython prefilter"""
114 InteractiveShell.prefilter = prefilter_paste
115
116 def deactivate_prefilter():
117 """Reset the filter."""
118 InteractiveShell.prefilter = InteractiveShell._prefilter
119
120 # Just a heads up at the console
121 activate_prefilter()
122 print '*** Pasting of code with ">>>" or "..." has been enabled.'
@@ -1,83 +1,83 b''
1 # -*- coding: utf-8 -*-
2 """Modified input prompt for entering quantities with units.
3
4 Modify the behavior of the interactive interpreter to allow direct input of
5 quantities with units without having to make a function call.
6
7 Now the following forms are accepted:
8
9 x = 4 m
10 y = -.45e3 m/s
11 g = 9.8 m/s**2
12 a = 2.3 m/s^2 # ^ -> ** automatically
13
14 All other input is processed normally.
15 """
16 #*****************************************************************************
17 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
18 #
19 # Distributed under the terms of the BSD License. The full license is in
20 # the file COPYING, distributed as part of this software.
21 #*****************************************************************************
22
23 from IPython import Release
24 __author__ = '%s <%s>' % Release.authors['Fernando']
25 __license__ = Release.license
26
27 # This file is an example of how to modify IPython's line-processing behavior
28 # without touching the internal code. We'll define an alternate pre-processing
29 # stage which allows a special form of input (which is invalid Python syntax)
30 # for certain quantities, rewrites a line of proper Python in those cases, and
31 # then passes it off to IPython's normal processor for further work.
32
33 # With this kind of customization, IPython can be adapted for many
34 # special-purpose scenarios providing alternate input syntaxes.
35
36 # This file can be imported like a regular module.
37
38 # IPython has a prefilter() function that analyzes each input line. We redefine
39 # it here to first pre-process certain forms of input
40
41 # The prototype of any alternate prefilter must be like this one (the name
42 # doesn't matter):
43 # - line is a string containing the user input line.
44 # - continuation is a parameter which tells us if we are processing a first line of
45 # user input or the second or higher of a multi-line statement.
46
47 def prefilter_PQ(self,line,continuation):
48 """Alternate prefilter for input of PhysicalQuantityInteractive objects.
49
50 This assumes that the function PhysicalQuantityInteractive() has been
51 imported."""
52
53 from re import match
54 from IPython.iplib import InteractiveShell
55
56 # This regexp is what does the real work
57 unit_split = match(r'\s*(\w+)\s*=\s*(-?\d*\.?\d*[eE]?-?\d*)\s+([a-zA-Z].*)',
58 line)
59
60 # If special input was ecnountered, process it:
61 if unit_split:
62 var,val,units = unit_split.groups()
63 if var and val and units:
64 units = units.replace('^','**')
65 # Now a valid line needs to be constructed for IPython to process:
66 line = var +" = PhysicalQuantityInteractive(" + val + ", '" + \
67 units + "')"
68 #print 'New line:',line # dbg
69
70 # In the end, always call the default IPython _prefilter() function. Note
71 # that self must be passed explicitly, b/c we're calling the unbound class
72 # method (since this method will overwrite the instance prefilter())
73 return InteractiveShell._prefilter(self,line,continuation)
74
75 # Rebind this to be the new IPython prefilter:
76 from IPython.iplib import InteractiveShell
77 InteractiveShell.prefilter = prefilter_PQ
78
79 # Clean up the namespace.
80 del InteractiveShell,prefilter_PQ
81
82 # Just a heads up at the console
83 print '*** Simplified input for physical quantities enabled.'
1 # -*- coding: utf-8 -*-
2 """Modified input prompt for entering quantities with units.
3
4 Modify the behavior of the interactive interpreter to allow direct input of
5 quantities with units without having to make a function call.
6
7 Now the following forms are accepted:
8
9 x = 4 m
10 y = -.45e3 m/s
11 g = 9.8 m/s**2
12 a = 2.3 m/s^2 # ^ -> ** automatically
13
14 All other input is processed normally.
15 """
16 #*****************************************************************************
17 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
18 #
19 # Distributed under the terms of the BSD License. The full license is in
20 # the file COPYING, distributed as part of this software.
21 #*****************************************************************************
22
23 from IPython import Release
24 __author__ = '%s <%s>' % Release.authors['Fernando']
25 __license__ = Release.license
26
27 # This file is an example of how to modify IPython's line-processing behavior
28 # without touching the internal code. We'll define an alternate pre-processing
29 # stage which allows a special form of input (which is invalid Python syntax)
30 # for certain quantities, rewrites a line of proper Python in those cases, and
31 # then passes it off to IPython's normal processor for further work.
32
33 # With this kind of customization, IPython can be adapted for many
34 # special-purpose scenarios providing alternate input syntaxes.
35
36 # This file can be imported like a regular module.
37
38 # IPython has a prefilter() function that analyzes each input line. We redefine
39 # it here to first pre-process certain forms of input
40
41 # The prototype of any alternate prefilter must be like this one (the name
42 # doesn't matter):
43 # - line is a string containing the user input line.
44 # - continuation is a parameter which tells us if we are processing a first line of
45 # user input or the second or higher of a multi-line statement.
46
47 def prefilter_PQ(self,line,continuation):
48 """Alternate prefilter for input of PhysicalQuantityInteractive objects.
49
50 This assumes that the function PhysicalQuantityInteractive() has been
51 imported."""
52
53 from re import match
54 from IPython.iplib import InteractiveShell
55
56 # This regexp is what does the real work
57 unit_split = match(r'\s*(\w+)\s*=\s*(-?\d*\.?\d*[eE]?-?\d*)\s+([a-zA-Z].*)',
58 line)
59
60 # If special input was ecnountered, process it:
61 if unit_split:
62 var,val,units = unit_split.groups()
63 if var and val and units:
64 units = units.replace('^','**')
65 # Now a valid line needs to be constructed for IPython to process:
66 line = var +" = PhysicalQuantityInteractive(" + val + ", '" + \
67 units + "')"
68 #print 'New line:',line # dbg
69
70 # In the end, always call the default IPython _prefilter() function. Note
71 # that self must be passed explicitly, b/c we're calling the unbound class
72 # method (since this method will overwrite the instance prefilter())
73 return InteractiveShell._prefilter(self,line,continuation)
74
75 # Rebind this to be the new IPython prefilter:
76 from IPython.iplib import InteractiveShell
77 InteractiveShell.prefilter = prefilter_PQ
78
79 # Clean up the namespace.
80 del InteractiveShell,prefilter_PQ
81
82 # Just a heads up at the console
83 print '*** Simplified input for physical quantities enabled.'
@@ -1,88 +1,88 b''
1 # -*- coding: utf-8 -*-
2 """Modify the PhysicalQuantities class for more convenient interactive use.
3
4 Also redefine some math functions to operate on PhysQties with no need for
5 special method syntax. This just means moving them out to the global
6 namespace.
7
8 This module should always be loaded *after* math or Numeric, so it can
9 overwrite math functions with the versions that handle units."""
10
11 #*****************************************************************************
12 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
13 #
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
16 #*****************************************************************************
17
18 from IPython import Release
19 __author__ = '%s <%s>' % Release.authors['Fernando']
20 __license__ = Release.license
21
22 from Scientific.Physics.PhysicalQuantities import PhysicalQuantity
23
24 # This code can be set up to work with Numeric or with math for providing the
25 # mathematical functions. Uncomment the one you prefer to use below.
26
27 # If you use math, sin(x) won't work for x an array, only float or PhysQty
28 import math
29
30 # If you use Numeric, sin(x) works for x a float, PhysQty an array.
31 #import Numeric as math
32
33 class PhysicalQuantityFunction:
34 """Generic function wrapper for PhysicalQuantity instances.
35
36 Calls functions from either the math library or the instance's methods as
37 required. Allows using sin(theta) or sqrt(v**2) syntax irrespective of
38 whether theta is a pure number or a PhysicalQuantity.
39
40 This is *slow*. It's meant for convenient interactive use, not for
41 speed."""
42
43 def __init__(self,name):
44 self.name = name
45
46 def __call__(self,x):
47 if isinstance(x,PhysicalQuantity):
48 return PhysicalQuantity.__dict__[self.name](x)
49 else:
50 return math.__dict__[self.name](x)
51
52 class PhysicalQuantityInteractive(PhysicalQuantity):
53 """Physical quantity with units - modified for Interactive use.
54
55 Basically, the __str__ and __repr__ methods have been swapped for more
56 convenient interactive use. Powers are shown as ^ instead of ** and only 4
57 significant figures are shown.
58
59 Also adds the following aliases for commonly used methods:
60 b = PhysicalQuantity.inBaseUnits
61 u = PhysicalQuantity.inUnitsOf
62
63 These are useful when doing a lot of interactive calculations.
64 """
65
66 # shorthands for the most useful unit conversions
67 b = PhysicalQuantity.inBaseUnits # so you can just type x.b to get base units
68 u = PhysicalQuantity.inUnitsOf
69
70 # This can be done, but it can get dangerous when coupled with IPython's
71 # auto-calling. Everything ends up shown in baseunits and things like x*2
72 # get automatically converted to k(*2), which doesn't work.
73 # Probably not a good idea in general...
74 #__call__ = b
75
76 def __str__(self):
77 return PhysicalQuantity.__repr__(self)
78
79 def __repr__(self):
80 value = '%.4G' % self.value
81 units = self.unit.name().replace('**','^')
82 return value + ' ' + units
83
84 # implement the methods defined in PhysicalQuantity as PhysicalQuantityFunctions
85 sin = PhysicalQuantityFunction('sin')
86 cos = PhysicalQuantityFunction('cos')
87 tan = PhysicalQuantityFunction('tan')
88 sqrt = PhysicalQuantityFunction('sqrt')
1 # -*- coding: utf-8 -*-
2 """Modify the PhysicalQuantities class for more convenient interactive use.
3
4 Also redefine some math functions to operate on PhysQties with no need for
5 special method syntax. This just means moving them out to the global
6 namespace.
7
8 This module should always be loaded *after* math or Numeric, so it can
9 overwrite math functions with the versions that handle units."""
10
11 #*****************************************************************************
12 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
13 #
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
16 #*****************************************************************************
17
18 from IPython import Release
19 __author__ = '%s <%s>' % Release.authors['Fernando']
20 __license__ = Release.license
21
22 from Scientific.Physics.PhysicalQuantities import PhysicalQuantity
23
24 # This code can be set up to work with Numeric or with math for providing the
25 # mathematical functions. Uncomment the one you prefer to use below.
26
27 # If you use math, sin(x) won't work for x an array, only float or PhysQty
28 import math
29
30 # If you use Numeric, sin(x) works for x a float, PhysQty an array.
31 #import Numeric as math
32
33 class PhysicalQuantityFunction:
34 """Generic function wrapper for PhysicalQuantity instances.
35
36 Calls functions from either the math library or the instance's methods as
37 required. Allows using sin(theta) or sqrt(v**2) syntax irrespective of
38 whether theta is a pure number or a PhysicalQuantity.
39
40 This is *slow*. It's meant for convenient interactive use, not for
41 speed."""
42
43 def __init__(self,name):
44 self.name = name
45
46 def __call__(self,x):
47 if isinstance(x,PhysicalQuantity):
48 return PhysicalQuantity.__dict__[self.name](x)
49 else:
50 return math.__dict__[self.name](x)
51
52 class PhysicalQuantityInteractive(PhysicalQuantity):
53 """Physical quantity with units - modified for Interactive use.
54
55 Basically, the __str__ and __repr__ methods have been swapped for more
56 convenient interactive use. Powers are shown as ^ instead of ** and only 4
57 significant figures are shown.
58
59 Also adds the following aliases for commonly used methods:
60 b = PhysicalQuantity.inBaseUnits
61 u = PhysicalQuantity.inUnitsOf
62
63 These are useful when doing a lot of interactive calculations.
64 """
65
66 # shorthands for the most useful unit conversions
67 b = PhysicalQuantity.inBaseUnits # so you can just type x.b to get base units
68 u = PhysicalQuantity.inUnitsOf
69
70 # This can be done, but it can get dangerous when coupled with IPython's
71 # auto-calling. Everything ends up shown in baseunits and things like x*2
72 # get automatically converted to k(*2), which doesn't work.
73 # Probably not a good idea in general...
74 #__call__ = b
75
76 def __str__(self):
77 return PhysicalQuantity.__repr__(self)
78
79 def __repr__(self):
80 value = '%.4G' % self.value
81 units = self.unit.name().replace('**','^')
82 return value + ' ' + units
83
84 # implement the methods defined in PhysicalQuantity as PhysicalQuantityFunctions
85 sin = PhysicalQuantityFunction('sin')
86 cos = PhysicalQuantityFunction('cos')
87 tan = PhysicalQuantityFunction('tan')
88 sqrt = PhysicalQuantityFunction('sqrt')
@@ -1,13 +1,13 b''
1 # -*- coding: utf-8 -*-
2 """This directory is meant for special-purpose extensions to IPython.
3
4 This can include things which alter the syntax processing stage (see
5 PhysicalQ_Input for an example of how to do this).
6
7 Any file located here can be called with an 'execfile =' option as
8
9 execfile = Extensions/filename.py
10
11 since the IPython directory itself is already part of the search path for
12 files listed as 'execfile ='.
13 """
1 # -*- coding: utf-8 -*-
2 """This directory is meant for special-purpose extensions to IPython.
3
4 This can include things which alter the syntax processing stage (see
5 PhysicalQ_Input for an example of how to do this).
6
7 Any file located here can be called with an 'execfile =' option as
8
9 execfile = Extensions/filename.py
10
11 since the IPython directory itself is already part of the search path for
12 files listed as 'execfile ='.
13 """
@@ -1,86 +1,86 b''
1 <?xml version='1.0' encoding='iso-8859-1'?>
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3 <html>
4 <head>
5 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
6 <link rel="stylesheet" href="igrid_help.css" type="text/css" />
7 <title>igrid help</title>
8 </head>
9 <body>
10 <h1>igrid help</h1>
11
12
13 <h2>Commands</h2>
14
15
16 <h3>pick (P)</h3>
17 <p>Pick the whole row (object is available as "_")</p>
18
19 <h3>pickattr (Shift-P)</h3>
20 <p>Pick the attribute under the cursor</p>
21
22 <h3>pickallattrs (Shift-C)</h3>
23 <p>Pick' the complete column under the cursor (i.e. the attribute under the
24 cursor) from all currently fetched objects. These attributes will be returned
25 as a list.</p>
26
27 <h3>enter (E)</h3>
28 <p>Enter the object under the cursor. (what this mean depends on the object
29 itself, i.e. how it implements iteration). This opens a new browser 'level'.</p>
30
31 <h3>enterattr (Shift-E)</h3>
32 <p>Enter the attribute under the cursor.</p>
33
34 <h3>detail (D)</h3>
35 <p>Show a detail view of the object under the cursor. This shows the name,
36 type, doc string and value of the object attributes (and it might show more
37 attributes than in the list view, depending on the object).</p>
38
39 <h3>detailattr (Shift-D)</h3>
40 <p>Show a detail view of the attribute under the cursor.</p>
41
42 <h3>pickrows (M)</h3>
43 <p>Pick multiple selected rows (M)</p>
44
45 <h3>pickrowsattr (CTRL-M)</h3>
46 <p>From multiple selected rows pick the cells matching the attribute the cursor is in (CTRL-M)</p>
47
48 <h3>find (CTRL-F)</h3>
49 <p>Find text</p>
50
51 <h3>find_next (F3)</h3>
52 <p>Find next occurrence of the searchtext</p>
53
54 <h3>find_previous (Shift-F3)</h3>
55 <p>Find previous occurrence of the searchtext </p>
56
57 <h3>sortattrasc (V)</h3>
58 <p>Sort the objects (in ascending order) using the attribute under the cursor as the sort key.</p>
59
60 <h3>sortattrdesc (Shift-V)</h3>
61 <p>Sort the objects (in descending order) using the attribute under the cursor as the sort key.</p>
62
63 <h3>leave (Backspace, DEL, X)</h3>
64 <p>Close current tab (and all the tabs to the right of the current one).</h3>
65
66 <h3>quit (ESC,Q)</h3>
67 <p>Quit igrid and return to the IPython prompt.</p>
68
69
70 <h2>Navigation</h2>
71
72
73 <h3>Jump to the last column of the current row (END, CTRL-E, CTRL-Right)</h3>
74
75 <h3>Jump to the first column of the current row (HOME, CTRL-A, CTRL-Left)</h3>
76
77 <h3>Move the cursor one column to the left (&lt;)</h3>
78
79 <h3>Move the cursor one column to the right (&gt;)</h3>
80
81 <h3>Jump to the first row in the current column (CTRL-Up)</h3>
82
83 <h3>Jump to the last row in the current column (CTRL-Down)</h3>
84
85 </body>
86 </html>
1 <?xml version='1.0' encoding='iso-8859-1'?>
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3 <html>
4 <head>
5 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
6 <link rel="stylesheet" href="igrid_help.css" type="text/css" />
7 <title>igrid help</title>
8 </head>
9 <body>
10 <h1>igrid help</h1>
11
12
13 <h2>Commands</h2>
14
15
16 <h3>pick (P)</h3>
17 <p>Pick the whole row (object is available as "_")</p>
18
19 <h3>pickattr (Shift-P)</h3>
20 <p>Pick the attribute under the cursor</p>
21
22 <h3>pickallattrs (Shift-C)</h3>
23 <p>Pick' the complete column under the cursor (i.e. the attribute under the
24 cursor) from all currently fetched objects. These attributes will be returned
25 as a list.</p>
26
27 <h3>enter (E)</h3>
28 <p>Enter the object under the cursor. (what this mean depends on the object
29 itself, i.e. how it implements iteration). This opens a new browser 'level'.</p>
30
31 <h3>enterattr (Shift-E)</h3>
32 <p>Enter the attribute under the cursor.</p>
33
34 <h3>detail (D)</h3>
35 <p>Show a detail view of the object under the cursor. This shows the name,
36 type, doc string and value of the object attributes (and it might show more
37 attributes than in the list view, depending on the object).</p>
38
39 <h3>detailattr (Shift-D)</h3>
40 <p>Show a detail view of the attribute under the cursor.</p>
41
42 <h3>pickrows (M)</h3>
43 <p>Pick multiple selected rows (M)</p>
44
45 <h3>pickrowsattr (CTRL-M)</h3>
46 <p>From multiple selected rows pick the cells matching the attribute the cursor is in (CTRL-M)</p>
47
48 <h3>find (CTRL-F)</h3>
49 <p>Find text</p>
50
51 <h3>find_next (F3)</h3>
52 <p>Find next occurrence of the searchtext</p>
53
54 <h3>find_previous (Shift-F3)</h3>
55 <p>Find previous occurrence of the searchtext </p>
56
57 <h3>sortattrasc (V)</h3>
58 <p>Sort the objects (in ascending order) using the attribute under the cursor as the sort key.</p>
59
60 <h3>sortattrdesc (Shift-V)</h3>
61 <p>Sort the objects (in descending order) using the attribute under the cursor as the sort key.</p>
62
63 <h3>leave (Backspace, DEL, X)</h3>
64 <p>Close current tab (and all the tabs to the right of the current one).</h3>
65
66 <h3>quit (ESC,Q)</h3>
67 <p>Quit igrid and return to the IPython prompt.</p>
68
69
70 <h2>Navigation</h2>
71
72
73 <h3>Jump to the last column of the current row (END, CTRL-E, CTRL-Right)</h3>
74
75 <h3>Jump to the first column of the current row (HOME, CTRL-A, CTRL-Left)</h3>
76
77 <h3>Move the cursor one column to the left (&lt;)</h3>
78
79 <h3>Move the cursor one column to the right (&gt;)</h3>
80
81 <h3>Jump to the first row in the current column (CTRL-Up)</h3>
82
83 <h3>Jump to the last row in the current column (CTRL-Down)</h3>
84
85 </body>
86 </html>
This diff has been collapsed as it changes many lines, (1150 lines changed) Show them Hide them
@@ -1,575 +1,575 b''
1 """ ILeo - Leo plugin for IPython
2
3
4 """
5 import IPython.ipapi
6 import IPython.genutils
7 import IPython.generics
8 from IPython.hooks import CommandChainDispatcher
9 import re
10 import UserDict
11 from IPython.ipapi import TryNext
12 import IPython.macro
13
14 def init_ipython(ipy):
15 """ This will be run by _ip.load('ipy_leo')
16
17 Leo still needs to run update_commander() after this.
18
19 """
20 global ip
21 ip = ipy
22 ip.set_hook('complete_command', mb_completer, str_key = '%mb')
23 ip.expose_magic('mb',mb_f)
24 ip.expose_magic('lee',lee_f)
25 ip.expose_magic('leoref',leoref_f)
26 expose_ileo_push(push_cl_node,100)
27 # this should be the LAST one that will be executed, and it will never raise TryNext
28 expose_ileo_push(push_ipython_script, 1000)
29 expose_ileo_push(push_plain_python, 100)
30 expose_ileo_push(push_ev_node, 100)
31 global wb
32 wb = LeoWorkbook()
33 ip.user_ns['wb'] = wb
34
35 show_welcome()
36
37
38 def update_commander(new_leox):
39 """ Set the Leo commander to use
40
41 This will be run every time Leo does ipython-launch; basically,
42 when the user switches the document he is focusing on, he should do
43 ipython-launch to tell ILeo what document the commands apply to.
44
45 """
46
47 global c,g
48 c,g = new_leox.c, new_leox.g
49 print "Set Leo Commander:",c.frame.getTitle()
50
51 # will probably be overwritten by user, but handy for experimentation early on
52 ip.user_ns['c'] = c
53 ip.user_ns['g'] = g
54 ip.user_ns['_leo'] = new_leox
55
56 new_leox.push = push_position_from_leo
57 run_leo_startup_node()
58
59 from IPython.external.simplegeneric import generic
60 import pprint
61
62 def es(s):
63 g.es(s, tabName = 'IPython')
64 pass
65
66 @generic
67 def format_for_leo(obj):
68 """ Convert obj to string representiation (for editing in Leo)"""
69 return pprint.pformat(obj)
70
71 @format_for_leo.when_type(list)
72 def format_list(obj):
73 return "\n".join(str(s) for s in obj)
74
75
76 attribute_re = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$')
77 def valid_attribute(s):
78 return attribute_re.match(s)
79
80 _rootnode = None
81 def rootnode():
82 """ Get ileo root node (@ipy-root)
83
84 if node has become invalid or has not been set, return None
85
86 Note that the root is the *first* @ipy-root item found
87 """
88 global _rootnode
89 if _rootnode is None:
90 return None
91 if c.positionExists(_rootnode.p):
92 return _rootnode
93 _rootnode = None
94 return None
95
96 def all_cells():
97 global _rootnode
98 d = {}
99 r = rootnode()
100 if r is not None:
101 nodes = r.p.children_iter()
102 else:
103 nodes = c.allNodes_iter()
104
105 for p in nodes:
106 h = p.headString()
107 if h.strip() == '@ipy-root':
108 # update root node (found it for the first time)
109 _rootnode = LeoNode(p)
110 # the next recursive call will use the children of new root
111 return all_cells()
112
113 if h.startswith('@a '):
114 d[h.lstrip('@a ').strip()] = p.parent().copy()
115 elif not valid_attribute(h):
116 continue
117 d[h] = p.copy()
118 return d
119
120 def eval_node(n):
121 body = n.b
122 if not body.startswith('@cl'):
123 # plain python repr node, just eval it
124 return ip.ev(n.b)
125 # @cl nodes deserve special treatment - first eval the first line (minus cl), then use it to call the rest of body
126 first, rest = body.split('\n',1)
127 tup = first.split(None, 1)
128 # @cl alone SPECIAL USE-> dump var to user_ns
129 if len(tup) == 1:
130 val = ip.ev(rest)
131 ip.user_ns[n.h] = val
132 es("%s = %s" % (n.h, repr(val)[:20] ))
133 return val
134
135 cl, hd = tup
136
137 xformer = ip.ev(hd.strip())
138 es('Transform w/ %s' % repr(xformer))
139 return xformer(rest, n)
140
141 class LeoNode(object, UserDict.DictMixin):
142 """ Node in Leo outline
143
144 Most important attributes (getters/setters available:
145 .v - evaluate node, can also be alligned
146 .b, .h - body string, headline string
147 .l - value as string list
148
149 Also supports iteration,
150
151 setitem / getitem (indexing):
152 wb.foo['key'] = 12
153 assert wb.foo['key'].v == 12
154
155 Note the asymmetry on setitem and getitem! Also other
156 dict methods are available.
157
158 .ipush() - run push-to-ipython
159
160 Minibuffer command access (tab completion works):
161
162 mb save-to-file
163
164 """
165 def __init__(self,p):
166 self.p = p.copy()
167
168 def __str__(self):
169 return "<LeoNode %s>" % str(self.p)
170
171 __repr__ = __str__
172
173 def __get_h(self): return self.p.headString()
174 def __set_h(self,val):
175 print "set head",val
176 c.beginUpdate()
177 try:
178 c.setHeadString(self.p,val)
179 finally:
180 c.endUpdate()
181
182 h = property( __get_h, __set_h, doc = "Node headline string")
183
184 def __get_b(self): return self.p.bodyString()
185 def __set_b(self,val):
186 print "set body",val
187 c.beginUpdate()
188 try:
189 c.setBodyString(self.p, val)
190 finally:
191 c.endUpdate()
192
193 b = property(__get_b, __set_b, doc = "Nody body string")
194
195 def __set_val(self, val):
196 self.b = format_for_leo(val)
197
198 v = property(lambda self: eval_node(self), __set_val, doc = "Node evaluated value")
199
200 def __set_l(self,val):
201 self.b = '\n'.join(val )
202 l = property(lambda self : IPython.genutils.SList(self.b.splitlines()),
203 __set_l, doc = "Node value as string list")
204
205 def __iter__(self):
206 """ Iterate through nodes direct children """
207
208 return (LeoNode(p) for p in self.p.children_iter())
209
210 def __children(self):
211 d = {}
212 for child in self:
213 head = child.h
214 tup = head.split(None,1)
215 if len(tup) > 1 and tup[0] == '@k':
216 d[tup[1]] = child
217 continue
218
219 if not valid_attribute(head):
220 d[head] = child
221 continue
222 return d
223 def keys(self):
224 d = self.__children()
225 return d.keys()
226 def __getitem__(self, key):
227 """ wb.foo['Some stuff'] Return a child node with headline 'Some stuff'
228
229 If key is a valid python name (e.g. 'foo'), look for headline '@k foo' as well
230 """
231 key = str(key)
232 d = self.__children()
233 return d[key]
234 def __setitem__(self, key, val):
235 """ You can do wb.foo['My Stuff'] = 12 to create children
236
237 This will create 'My Stuff' as a child of foo (if it does not exist), and
238 do .v = 12 assignment.
239
240 Exception:
241
242 wb.foo['bar'] = 12
243
244 will create a child with headline '@k bar', because bar is a valid python name
245 and we don't want to crowd the WorkBook namespace with (possibly numerous) entries
246 """
247 key = str(key)
248 d = self.__children()
249 if key in d:
250 d[key].v = val
251 return
252
253 if not valid_attribute(key):
254 head = key
255 else:
256 head = '@k ' + key
257 p = c.createLastChildNode(self.p, head, '')
258 LeoNode(p).v = val
259
260 def ipush(self):
261 """ Does push-to-ipython on the node """
262 push_from_leo(self)
263
264 def go(self):
265 """ Set node as current node (to quickly see it in Outline) """
266 c.beginUpdate()
267 try:
268 c.setCurrentPosition(self.p)
269 finally:
270 c.endUpdate()
271
272 def script(self):
273 """ Method to get the 'tangled' contents of the node
274
275 (parse @others, << section >> references etc.)
276 """
277 return g.getScript(c,self.p,useSelectedText=False,useSentinels=False)
278
279 def __get_uA(self):
280 p = self.p
281 # Create the uA if necessary.
282 if not hasattr(p.v.t,'unknownAttributes'):
283 p.v.t.unknownAttributes = {}
284
285 d = p.v.t.unknownAttributes.setdefault('ipython', {})
286 return d
287
288 uA = property(__get_uA, doc = "Access persistent unknownAttributes of node")
289
290
291 class LeoWorkbook:
292 """ class for 'advanced' node access
293
294 Has attributes for all "discoverable" nodes. Node is discoverable if it
295 either
296
297 - has a valid python name (Foo, bar_12)
298 - is a parent of an anchor node (if it has a child '@a foo', it is visible as foo)
299
300 """
301 def __getattr__(self, key):
302 if key.startswith('_') or key == 'trait_names' or not valid_attribute(key):
303 raise AttributeError
304 cells = all_cells()
305 p = cells.get(key, None)
306 if p is None:
307 return add_var(key)
308
309 return LeoNode(p)
310
311 def __str__(self):
312 return "<LeoWorkbook>"
313 def __setattr__(self,key, val):
314 raise AttributeError("Direct assignment to workbook denied, try wb.%s.v = %s" % (key,val))
315
316 __repr__ = __str__
317
318 def __iter__(self):
319 """ Iterate all (even non-exposed) nodes """
320 cells = all_cells()
321 return (LeoNode(p) for p in c.allNodes_iter())
322
323 current = property(lambda self: LeoNode(c.currentPosition()), doc = "Currently selected node")
324
325 def match_h(self, regex):
326 cmp = re.compile(regex)
327 for node in self:
328 if re.match(cmp, node.h, re.IGNORECASE):
329 yield node
330 return
331
332 @IPython.generics.complete_object.when_type(LeoWorkbook)
333 def workbook_complete(obj, prev):
334 return all_cells().keys() + [s for s in prev if not s.startswith('_')]
335
336
337 def add_var(varname):
338 c.beginUpdate()
339 r = rootnode()
340 try:
341 if r is None:
342 p2 = g.findNodeAnywhere(c,varname)
343 else:
344 p2 = g.findNodeInChildren(c, r.p, varname)
345 if p2:
346 return LeoNode(p2)
347
348 if r is not None:
349 p2 = r.p.insertAsLastChild()
350
351 else:
352 p2 = c.currentPosition().insertAfter()
353
354 c.setHeadString(p2,varname)
355 return LeoNode(p2)
356 finally:
357 c.endUpdate()
358
359 def add_file(self,fname):
360 p2 = c.currentPosition().insertAfter()
361
362 push_from_leo = CommandChainDispatcher()
363
364 def expose_ileo_push(f, prio = 0):
365 push_from_leo.add(f, prio)
366
367 def push_ipython_script(node):
368 """ Execute the node body in IPython, as if it was entered in interactive prompt """
369 c.beginUpdate()
370 try:
371 ohist = ip.IP.output_hist
372 hstart = len(ip.IP.input_hist)
373 script = node.script()
374
375 script = g.splitLines(script + '\n')
376 ip.user_ns['_p'] = node
377 ip.runlines(script)
378 ip.user_ns.pop('_p',None)
379
380 has_output = False
381 for idx in range(hstart,len(ip.IP.input_hist)):
382 val = ohist.get(idx,None)
383 if val is None:
384 continue
385 has_output = True
386 inp = ip.IP.input_hist[idx]
387 if inp.strip():
388 es('In: %s' % (inp[:40], ))
389
390 es('<%d> %s' % (idx, pprint.pformat(ohist[idx],width = 40)))
391
392 if not has_output:
393 es('ipy run: %s (%d LL)' %( node.h,len(script)))
394 finally:
395 c.endUpdate()
396
397
398 def eval_body(body):
399 try:
400 val = ip.ev(body)
401 except:
402 # just use stringlist if it's not completely legal python expression
403 val = IPython.genutils.SList(body.splitlines())
404 return val
405
406 def push_plain_python(node):
407 if not node.h.endswith('P'):
408 raise TryNext
409 script = node.script()
410 lines = script.count('\n')
411 try:
412 exec script in ip.user_ns
413 except:
414 print " -- Exception in script:\n"+script + "\n --"
415 raise
416 es('ipy plain: %s (%d LL)' % (node.h,lines))
417
418
419 def push_cl_node(node):
420 """ If node starts with @cl, eval it
421
422 The result is put as last child of @ipy-results node, if it exists
423 """
424 if not node.b.startswith('@cl'):
425 raise TryNext
426
427 p2 = g.findNodeAnywhere(c,'@ipy-results')
428 val = node.v
429 if p2:
430 es("=> @ipy-results")
431 LeoNode(p2).v = val
432 es(val)
433
434 def push_ev_node(node):
435 """ If headline starts with @ev, eval it and put result in body """
436 if not node.h.startswith('@ev '):
437 raise TryNext
438 expr = node.h.lstrip('@ev ')
439 es('ipy eval ' + expr)
440 res = ip.ev(expr)
441 node.v = res
442
443
444 def push_position_from_leo(p):
445 push_from_leo(LeoNode(p))
446
447 @generic
448 def edit_object_in_leo(obj, varname):
449 """ Make it @cl node so it can be pushed back directly by alt+I """
450 node = add_var(varname)
451 formatted = format_for_leo(obj)
452 if not formatted.startswith('@cl'):
453 formatted = '@cl\n' + formatted
454 node.b = formatted
455 node.go()
456
457 @edit_object_in_leo.when_type(IPython.macro.Macro)
458 def edit_macro(obj,varname):
459 bod = '_ip.defmacro("""\\\n' + obj.value + '""")'
460 node = add_var('Macro_' + varname)
461 node.b = bod
462 node.go()
463
464 def get_history(hstart = 0):
465 res = []
466 ohist = ip.IP.output_hist
467
468 for idx in range(hstart, len(ip.IP.input_hist)):
469 val = ohist.get(idx,None)
470 has_output = True
471 inp = ip.IP.input_hist_raw[idx]
472 if inp.strip():
473 res.append('In [%d]: %s' % (idx, inp))
474 if val:
475 res.append(pprint.pformat(val))
476 res.append('\n')
477 return ''.join(res)
478
479
480 def lee_f(self,s):
481 """ Open file(s)/objects in Leo
482
483 - %lee hist -> open full session history in leo
484 - Takes an object
485 - Takes an mglob pattern, e.g. '%lee *.cpp' or %leo 'rec:*.cpp'
486 """
487 import os
488
489 c.beginUpdate()
490 try:
491 if s == 'hist':
492 wb.ipython_history.b = get_history()
493 wb.ipython_history.go()
494 return
495
496
497
498 # try editing the object directly
499 obj = ip.user_ns.get(s, None)
500 if obj is not None:
501 edit_object_in_leo(obj,s)
502 return
503
504 # if it's not object, it's a file name / mglob pattern
505 from IPython.external import mglob
506
507 files = (os.path.abspath(f) for f in mglob.expand(s))
508 for fname in files:
509 p = g.findNodeAnywhere(c,'@auto ' + fname)
510 if not p:
511 p = c.currentPosition().insertAfter()
512
513 p.setHeadString('@auto ' + fname)
514 if os.path.isfile(fname):
515 c.setBodyString(p,open(fname).read())
516 c.selectPosition(p)
517 print "Editing file(s), press ctrl+shift+w in Leo to write @auto nodes"
518 finally:
519 c.endUpdate()
520
521
522
523 def leoref_f(self,s):
524 """ Quick reference for ILeo """
525 import textwrap
526 print textwrap.dedent("""\
527 %leoe file/object - open file / object in leo
528 wb.foo.v - eval node foo (i.e. headstring is 'foo' or '@ipy foo')
529 wb.foo.v = 12 - assign to body of node foo
530 wb.foo.b - read or write the body of node foo
531 wb.foo.l - body of node foo as string list
532
533 for el in wb.foo:
534 print el.v
535
536 """
537 )
538
539
540
541 def mb_f(self, arg):
542 """ Execute leo minibuffer commands
543
544 Example:
545 mb save-to-file
546 """
547 c.executeMinibufferCommand(arg)
548
549 def mb_completer(self,event):
550 """ Custom completer for minibuffer """
551 cmd_param = event.line.split()
552 if event.line.endswith(' '):
553 cmd_param.append('')
554 if len(cmd_param) > 2:
555 return ip.IP.Completer.file_matches(event.symbol)
556 cmds = c.commandsDict.keys()
557 cmds.sort()
558 return cmds
559
560 def show_welcome():
561 print "------------------"
562 print "Welcome to Leo-enabled IPython session!"
563 print "Try %leoref for quick reference."
564 import IPython.platutils
565 IPython.platutils.set_term_title('ILeo')
566 IPython.platutils.freeze_term_title()
567
568 def run_leo_startup_node():
569 p = g.findNodeAnywhere(c,'@ipy-startup')
570 if p:
571 print "Running @ipy-startup nodes"
572 for n in LeoNode(p):
573 push_from_leo(n)
574
575
1 """ ILeo - Leo plugin for IPython
2
3
4 """
5 import IPython.ipapi
6 import IPython.genutils
7 import IPython.generics
8 from IPython.hooks import CommandChainDispatcher
9 import re
10 import UserDict
11 from IPython.ipapi import TryNext
12 import IPython.macro
13
14 def init_ipython(ipy):
15 """ This will be run by _ip.load('ipy_leo')
16
17 Leo still needs to run update_commander() after this.
18
19 """
20 global ip
21 ip = ipy
22 ip.set_hook('complete_command', mb_completer, str_key = '%mb')
23 ip.expose_magic('mb',mb_f)
24 ip.expose_magic('lee',lee_f)
25 ip.expose_magic('leoref',leoref_f)
26 expose_ileo_push(push_cl_node,100)
27 # this should be the LAST one that will be executed, and it will never raise TryNext
28 expose_ileo_push(push_ipython_script, 1000)
29 expose_ileo_push(push_plain_python, 100)
30 expose_ileo_push(push_ev_node, 100)
31 global wb
32 wb = LeoWorkbook()
33 ip.user_ns['wb'] = wb
34
35 show_welcome()
36
37
38 def update_commander(new_leox):
39 """ Set the Leo commander to use
40
41 This will be run every time Leo does ipython-launch; basically,
42 when the user switches the document he is focusing on, he should do
43 ipython-launch to tell ILeo what document the commands apply to.
44
45 """
46
47 global c,g
48 c,g = new_leox.c, new_leox.g
49 print "Set Leo Commander:",c.frame.getTitle()
50
51 # will probably be overwritten by user, but handy for experimentation early on
52 ip.user_ns['c'] = c
53 ip.user_ns['g'] = g
54 ip.user_ns['_leo'] = new_leox
55
56 new_leox.push = push_position_from_leo
57 run_leo_startup_node()
58
59 from IPython.external.simplegeneric import generic
60 import pprint
61
62 def es(s):
63 g.es(s, tabName = 'IPython')
64 pass
65
66 @generic
67 def format_for_leo(obj):
68 """ Convert obj to string representiation (for editing in Leo)"""
69 return pprint.pformat(obj)
70
71 @format_for_leo.when_type(list)
72 def format_list(obj):
73 return "\n".join(str(s) for s in obj)
74
75
76 attribute_re = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$')
77 def valid_attribute(s):
78 return attribute_re.match(s)
79
80 _rootnode = None
81 def rootnode():
82 """ Get ileo root node (@ipy-root)
83
84 if node has become invalid or has not been set, return None
85
86 Note that the root is the *first* @ipy-root item found
87 """
88 global _rootnode
89 if _rootnode is None:
90 return None
91 if c.positionExists(_rootnode.p):
92 return _rootnode
93 _rootnode = None
94 return None
95
96 def all_cells():
97 global _rootnode
98 d = {}
99 r = rootnode()
100 if r is not None:
101 nodes = r.p.children_iter()
102 else:
103 nodes = c.allNodes_iter()
104
105 for p in nodes:
106 h = p.headString()
107 if h.strip() == '@ipy-root':
108 # update root node (found it for the first time)
109 _rootnode = LeoNode(p)
110 # the next recursive call will use the children of new root
111 return all_cells()
112
113 if h.startswith('@a '):
114 d[h.lstrip('@a ').strip()] = p.parent().copy()
115 elif not valid_attribute(h):
116 continue
117 d[h] = p.copy()
118 return d
119
120 def eval_node(n):
121 body = n.b
122 if not body.startswith('@cl'):
123 # plain python repr node, just eval it
124 return ip.ev(n.b)
125 # @cl nodes deserve special treatment - first eval the first line (minus cl), then use it to call the rest of body
126 first, rest = body.split('\n',1)
127 tup = first.split(None, 1)
128 # @cl alone SPECIAL USE-> dump var to user_ns
129 if len(tup) == 1:
130 val = ip.ev(rest)
131 ip.user_ns[n.h] = val
132 es("%s = %s" % (n.h, repr(val)[:20] ))
133 return val
134
135 cl, hd = tup
136
137 xformer = ip.ev(hd.strip())
138 es('Transform w/ %s' % repr(xformer))
139 return xformer(rest, n)
140
141 class LeoNode(object, UserDict.DictMixin):
142 """ Node in Leo outline
143
144 Most important attributes (getters/setters available:
145 .v - evaluate node, can also be alligned
146 .b, .h - body string, headline string
147 .l - value as string list
148
149 Also supports iteration,
150
151 setitem / getitem (indexing):
152 wb.foo['key'] = 12
153 assert wb.foo['key'].v == 12
154
155 Note the asymmetry on setitem and getitem! Also other
156 dict methods are available.
157
158 .ipush() - run push-to-ipython
159
160 Minibuffer command access (tab completion works):
161
162 mb save-to-file
163
164 """
165 def __init__(self,p):
166 self.p = p.copy()
167
168 def __str__(self):
169 return "<LeoNode %s>" % str(self.p)
170
171 __repr__ = __str__
172
173 def __get_h(self): return self.p.headString()
174 def __set_h(self,val):
175 print "set head",val
176 c.beginUpdate()
177 try:
178 c.setHeadString(self.p,val)
179 finally:
180 c.endUpdate()
181
182 h = property( __get_h, __set_h, doc = "Node headline string")
183
184 def __get_b(self): return self.p.bodyString()
185 def __set_b(self,val):
186 print "set body",val
187 c.beginUpdate()
188 try:
189 c.setBodyString(self.p, val)
190 finally:
191 c.endUpdate()
192
193 b = property(__get_b, __set_b, doc = "Nody body string")
194
195 def __set_val(self, val):
196 self.b = format_for_leo(val)
197
198 v = property(lambda self: eval_node(self), __set_val, doc = "Node evaluated value")
199
200 def __set_l(self,val):
201 self.b = '\n'.join(val )
202 l = property(lambda self : IPython.genutils.SList(self.b.splitlines()),
203 __set_l, doc = "Node value as string list")
204
205 def __iter__(self):
206 """ Iterate through nodes direct children """
207
208 return (LeoNode(p) for p in self.p.children_iter())
209
210 def __children(self):
211 d = {}
212 for child in self:
213 head = child.h
214 tup = head.split(None,1)
215 if len(tup) > 1 and tup[0] == '@k':
216 d[tup[1]] = child
217 continue
218
219 if not valid_attribute(head):
220 d[head] = child
221 continue
222 return d
223 def keys(self):
224 d = self.__children()
225 return d.keys()
226 def __getitem__(self, key):
227 """ wb.foo['Some stuff'] Return a child node with headline 'Some stuff'
228
229 If key is a valid python name (e.g. 'foo'), look for headline '@k foo' as well
230 """
231 key = str(key)
232 d = self.__children()
233 return d[key]
234 def __setitem__(self, key, val):
235 """ You can do wb.foo['My Stuff'] = 12 to create children
236
237 This will create 'My Stuff' as a child of foo (if it does not exist), and
238 do .v = 12 assignment.
239
240 Exception:
241
242 wb.foo['bar'] = 12
243
244 will create a child with headline '@k bar', because bar is a valid python name
245 and we don't want to crowd the WorkBook namespace with (possibly numerous) entries
246 """
247 key = str(key)
248 d = self.__children()
249 if key in d:
250 d[key].v = val
251 return
252
253 if not valid_attribute(key):
254 head = key
255 else:
256 head = '@k ' + key
257 p = c.createLastChildNode(self.p, head, '')
258 LeoNode(p).v = val
259
260 def ipush(self):
261 """ Does push-to-ipython on the node """
262 push_from_leo(self)
263
264 def go(self):
265 """ Set node as current node (to quickly see it in Outline) """
266 c.beginUpdate()
267 try:
268 c.setCurrentPosition(self.p)
269 finally:
270 c.endUpdate()
271
272 def script(self):
273 """ Method to get the 'tangled' contents of the node
274
275 (parse @others, << section >> references etc.)
276 """
277 return g.getScript(c,self.p,useSelectedText=False,useSentinels=False)
278
279 def __get_uA(self):
280 p = self.p
281 # Create the uA if necessary.
282 if not hasattr(p.v.t,'unknownAttributes'):
283 p.v.t.unknownAttributes = {}
284
285 d = p.v.t.unknownAttributes.setdefault('ipython', {})
286 return d
287
288 uA = property(__get_uA, doc = "Access persistent unknownAttributes of node")
289
290
291 class LeoWorkbook:
292 """ class for 'advanced' node access
293
294 Has attributes for all "discoverable" nodes. Node is discoverable if it
295 either
296
297 - has a valid python name (Foo, bar_12)
298 - is a parent of an anchor node (if it has a child '@a foo', it is visible as foo)
299
300 """
301 def __getattr__(self, key):
302 if key.startswith('_') or key == 'trait_names' or not valid_attribute(key):
303 raise AttributeError
304 cells = all_cells()
305 p = cells.get(key, None)
306 if p is None:
307 return add_var(key)
308
309 return LeoNode(p)
310
311 def __str__(self):
312 return "<LeoWorkbook>"
313 def __setattr__(self,key, val):
314 raise AttributeError("Direct assignment to workbook denied, try wb.%s.v = %s" % (key,val))
315
316 __repr__ = __str__
317
318 def __iter__(self):
319 """ Iterate all (even non-exposed) nodes """
320 cells = all_cells()
321 return (LeoNode(p) for p in c.allNodes_iter())
322
323 current = property(lambda self: LeoNode(c.currentPosition()), doc = "Currently selected node")
324
325 def match_h(self, regex):
326 cmp = re.compile(regex)
327 for node in self:
328 if re.match(cmp, node.h, re.IGNORECASE):
329 yield node
330 return
331
332 @IPython.generics.complete_object.when_type(LeoWorkbook)
333 def workbook_complete(obj, prev):
334 return all_cells().keys() + [s for s in prev if not s.startswith('_')]
335
336
337 def add_var(varname):
338 c.beginUpdate()
339 r = rootnode()
340 try:
341 if r is None:
342 p2 = g.findNodeAnywhere(c,varname)
343 else:
344 p2 = g.findNodeInChildren(c, r.p, varname)
345 if p2:
346 return LeoNode(p2)
347
348 if r is not None:
349 p2 = r.p.insertAsLastChild()
350
351 else:
352 p2 = c.currentPosition().insertAfter()
353
354 c.setHeadString(p2,varname)
355 return LeoNode(p2)
356 finally:
357 c.endUpdate()
358
359 def add_file(self,fname):
360 p2 = c.currentPosition().insertAfter()
361
362 push_from_leo = CommandChainDispatcher()
363
364 def expose_ileo_push(f, prio = 0):
365 push_from_leo.add(f, prio)
366
367 def push_ipython_script(node):
368 """ Execute the node body in IPython, as if it was entered in interactive prompt """
369 c.beginUpdate()
370 try:
371 ohist = ip.IP.output_hist
372 hstart = len(ip.IP.input_hist)
373 script = node.script()
374
375 script = g.splitLines(script + '\n')
376 ip.user_ns['_p'] = node
377 ip.runlines(script)
378 ip.user_ns.pop('_p',None)
379
380 has_output = False
381 for idx in range(hstart,len(ip.IP.input_hist)):
382 val = ohist.get(idx,None)
383 if val is None:
384 continue
385 has_output = True
386 inp = ip.IP.input_hist[idx]
387 if inp.strip():
388 es('In: %s' % (inp[:40], ))
389
390 es('<%d> %s' % (idx, pprint.pformat(ohist[idx],width = 40)))
391
392 if not has_output:
393 es('ipy run: %s (%d LL)' %( node.h,len(script)))
394 finally:
395 c.endUpdate()
396
397
398 def eval_body(body):
399 try:
400 val = ip.ev(body)
401 except:
402 # just use stringlist if it's not completely legal python expression
403 val = IPython.genutils.SList(body.splitlines())
404 return val
405
406 def push_plain_python(node):
407 if not node.h.endswith('P'):
408 raise TryNext
409 script = node.script()
410 lines = script.count('\n')
411 try:
412 exec script in ip.user_ns
413 except:
414 print " -- Exception in script:\n"+script + "\n --"
415 raise
416 es('ipy plain: %s (%d LL)' % (node.h,lines))
417
418
419 def push_cl_node(node):
420 """ If node starts with @cl, eval it
421
422 The result is put as last child of @ipy-results node, if it exists
423 """
424 if not node.b.startswith('@cl'):
425 raise TryNext
426
427 p2 = g.findNodeAnywhere(c,'@ipy-results')
428 val = node.v
429 if p2:
430 es("=> @ipy-results")
431 LeoNode(p2).v = val
432 es(val)
433
434 def push_ev_node(node):
435 """ If headline starts with @ev, eval it and put result in body """
436 if not node.h.startswith('@ev '):
437 raise TryNext
438 expr = node.h.lstrip('@ev ')
439 es('ipy eval ' + expr)
440 res = ip.ev(expr)
441 node.v = res
442
443
444 def push_position_from_leo(p):
445 push_from_leo(LeoNode(p))
446
447 @generic
448 def edit_object_in_leo(obj, varname):
449 """ Make it @cl node so it can be pushed back directly by alt+I """
450 node = add_var(varname)
451 formatted = format_for_leo(obj)
452 if not formatted.startswith('@cl'):
453 formatted = '@cl\n' + formatted
454 node.b = formatted
455 node.go()
456
457 @edit_object_in_leo.when_type(IPython.macro.Macro)
458 def edit_macro(obj,varname):
459 bod = '_ip.defmacro("""\\\n' + obj.value + '""")'
460 node = add_var('Macro_' + varname)
461 node.b = bod
462 node.go()
463
464 def get_history(hstart = 0):
465 res = []
466 ohist = ip.IP.output_hist
467
468 for idx in range(hstart, len(ip.IP.input_hist)):
469 val = ohist.get(idx,None)
470 has_output = True
471 inp = ip.IP.input_hist_raw[idx]
472 if inp.strip():
473 res.append('In [%d]: %s' % (idx, inp))
474 if val:
475 res.append(pprint.pformat(val))
476 res.append('\n')
477 return ''.join(res)
478
479
480 def lee_f(self,s):
481 """ Open file(s)/objects in Leo
482
483 - %lee hist -> open full session history in leo
484 - Takes an object
485 - Takes an mglob pattern, e.g. '%lee *.cpp' or %leo 'rec:*.cpp'
486 """
487 import os
488
489 c.beginUpdate()
490 try:
491 if s == 'hist':
492 wb.ipython_history.b = get_history()
493 wb.ipython_history.go()
494 return
495
496
497
498 # try editing the object directly
499 obj = ip.user_ns.get(s, None)
500 if obj is not None:
501 edit_object_in_leo(obj,s)
502 return
503
504 # if it's not object, it's a file name / mglob pattern
505 from IPython.external import mglob
506
507 files = (os.path.abspath(f) for f in mglob.expand(s))
508 for fname in files:
509 p = g.findNodeAnywhere(c,'@auto ' + fname)
510 if not p:
511 p = c.currentPosition().insertAfter()
512
513 p.setHeadString('@auto ' + fname)
514 if os.path.isfile(fname):
515 c.setBodyString(p,open(fname).read())
516 c.selectPosition(p)
517 print "Editing file(s), press ctrl+shift+w in Leo to write @auto nodes"
518 finally:
519 c.endUpdate()
520
521
522
523 def leoref_f(self,s):
524 """ Quick reference for ILeo """
525 import textwrap
526 print textwrap.dedent("""\
527 %leoe file/object - open file / object in leo
528 wb.foo.v - eval node foo (i.e. headstring is 'foo' or '@ipy foo')
529 wb.foo.v = 12 - assign to body of node foo
530 wb.foo.b - read or write the body of node foo
531 wb.foo.l - body of node foo as string list
532
533 for el in wb.foo:
534 print el.v
535
536 """
537 )
538
539
540
541 def mb_f(self, arg):
542 """ Execute leo minibuffer commands
543
544 Example:
545 mb save-to-file
546 """
547 c.executeMinibufferCommand(arg)
548
549 def mb_completer(self,event):
550 """ Custom completer for minibuffer """
551 cmd_param = event.line.split()
552 if event.line.endswith(' '):
553 cmd_param.append('')
554 if len(cmd_param) > 2:
555 return ip.IP.Completer.file_matches(event.symbol)
556 cmds = c.commandsDict.keys()
557 cmds.sort()
558 return cmds
559
560 def show_welcome():
561 print "------------------"
562 print "Welcome to Leo-enabled IPython session!"
563 print "Try %leoref for quick reference."
564 import IPython.platutils
565 IPython.platutils.set_term_title('ILeo')
566 IPython.platutils.freeze_term_title()
567
568 def run_leo_startup_node():
569 p = g.findNodeAnywhere(c,'@ipy-startup')
570 if p:
571 print "Running @ipy-startup nodes"
572 for n in LeoNode(p):
573 push_from_leo(n)
574
575
@@ -1,43 +1,43 b''
1 # -*- coding: utf-8 -*-
2 """
3 Extension for printing Numeric Arrays in flexible ways.
4 """
5
6 from Numeric import ArrayType
7
8 def num_display(self,arg):
9 """Display method for printing which treats Numeric arrays specially.
10 """
11
12 # Non-numpy variables are printed using the system default
13 if type(arg) != ArrayType:
14 self._display(arg)
15 return
16 # Otherwise, we do work.
17 format = __IPYTHON__.runtime_rc.numarray_print_format
18 print 'NumPy array, format:',format
19 # Here is where all the printing logic needs to be implemented
20 print arg # nothing yet :)
21
22
23 def magic_format(self,parameter_s=''):
24 """Specifies format of numerical output.
25
26 This command is similar to Ocave's format command.
27 """
28
29 valid_formats = ['long','short']
30
31 if parameter_s in valid_formats:
32 self.runtime_rc.numarray_print_format = parameter_s
33 print 'Numeric output format is now:',parameter_s
34 else:
35 print 'Invalid format:',parameter_s
36 print 'Valid formats:',valid_formats
37
38 # setup default format
39 __IPYTHON__.runtime_rc.numarray_print_format = 'long'
40
41 # Bind our new functions to the interpreter
42 __IPYTHON__.__class__.magic_format = magic_format
43 __IPYTHON__.hooks.display = num_display
1 # -*- coding: utf-8 -*-
2 """
3 Extension for printing Numeric Arrays in flexible ways.
4 """
5
6 from Numeric import ArrayType
7
8 def num_display(self,arg):
9 """Display method for printing which treats Numeric arrays specially.
10 """
11
12 # Non-numpy variables are printed using the system default
13 if type(arg) != ArrayType:
14 self._display(arg)
15 return
16 # Otherwise, we do work.
17 format = __IPYTHON__.runtime_rc.numarray_print_format
18 print 'NumPy array, format:',format
19 # Here is where all the printing logic needs to be implemented
20 print arg # nothing yet :)
21
22
23 def magic_format(self,parameter_s=''):
24 """Specifies format of numerical output.
25
26 This command is similar to Ocave's format command.
27 """
28
29 valid_formats = ['long','short']
30
31 if parameter_s in valid_formats:
32 self.runtime_rc.numarray_print_format = parameter_s
33 print 'Numeric output format is now:',parameter_s
34 else:
35 print 'Invalid format:',parameter_s
36 print 'Valid formats:',valid_formats
37
38 # setup default format
39 __IPYTHON__.runtime_rc.numarray_print_format = 'long'
40
41 # Bind our new functions to the interpreter
42 __IPYTHON__.__class__.magic_format = magic_format
43 __IPYTHON__.hooks.display = num_display
@@ -1,43 +1,43 b''
1 # -*- coding: utf-8 -*-
2 """
3 Class which mimics a module.
4
5 Needed to allow pickle to correctly resolve namespaces during IPython
6 sessions.
7
8 $Id: FakeModule.py 2754 2007-09-09 10:16:59Z fperez $"""
9
10 #*****************************************************************************
11 # Copyright (C) 2002-2004 Fernando Perez. <fperez@colorado.edu>
12 #
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
15 #*****************************************************************************
16
17 import types
18
19 class FakeModule(types.ModuleType):
20 """Simple class with attribute access to fake a module.
21
22 This is not meant to replace a module, but to allow inserting a fake
23 module in sys.modules so that systems which rely on run-time module
24 importing (like shelve and pickle) work correctly in interactive IPython
25 sessions.
26
27 Do NOT use this code for anything other than this IPython private hack."""
28
29 def __init__(self,adict=None):
30
31 # tmp to force __dict__ instance creation, else self.__dict__ fails
32 self.__iptmp = None
33
34 # It seems pydoc (and perhaps others) needs any module instance to
35 # implement a __nonzero__ method, so we add it if missing:
36 self.__dict__.setdefault('__nonzero__',lambda : True)
37 self.__dict__.setdefault('__file__',__file__)
38
39 # cleanup our temp trick
40 del self.__iptmp
41
42 if adict is not None:
43 self.__dict__.update(adict)
1 # -*- coding: utf-8 -*-
2 """
3 Class which mimics a module.
4
5 Needed to allow pickle to correctly resolve namespaces during IPython
6 sessions.
7
8 $Id: FakeModule.py 2754 2007-09-09 10:16:59Z fperez $"""
9
10 #*****************************************************************************
11 # Copyright (C) 2002-2004 Fernando Perez. <fperez@colorado.edu>
12 #
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
15 #*****************************************************************************
16
17 import types
18
19 class FakeModule(types.ModuleType):
20 """Simple class with attribute access to fake a module.
21
22 This is not meant to replace a module, but to allow inserting a fake
23 module in sys.modules so that systems which rely on run-time module
24 importing (like shelve and pickle) work correctly in interactive IPython
25 sessions.
26
27 Do NOT use this code for anything other than this IPython private hack."""
28
29 def __init__(self,adict=None):
30
31 # tmp to force __dict__ instance creation, else self.__dict__ fails
32 self.__iptmp = None
33
34 # It seems pydoc (and perhaps others) needs any module instance to
35 # implement a __nonzero__ method, so we add it if missing:
36 self.__dict__.setdefault('__nonzero__',lambda : True)
37 self.__dict__.setdefault('__file__',__file__)
38
39 # cleanup our temp trick
40 del self.__iptmp
41
42 if adict is not None:
43 self.__dict__.update(adict)
This diff has been collapsed as it changes many lines, (1332 lines changed) Show them Hide them
@@ -1,666 +1,666 b''
1 # -*- coding: utf-8 -*-
2 """Improved replacement for the Gnuplot.Gnuplot class.
3
4 This module imports Gnuplot and replaces some of its functionality with
5 improved versions. They add better handling of arrays for plotting and more
6 convenient PostScript generation, plus some fixes for hardcopy().
7
8 It also adds a convenient plot2 method for plotting dictionaries and
9 lists/tuples of arrays.
10
11 This module is meant to be used as a drop-in replacement to the original
12 Gnuplot, so it should be safe to do:
13
14 import IPython.Gnuplot2 as Gnuplot
15
16 $Id: Gnuplot2.py 1210 2006-03-13 01:19:31Z fperez $"""
17
18 import cStringIO
19 import os
20 import string
21 import sys
22 import tempfile
23 import time
24 import types
25
26 import Gnuplot as Gnuplot_ori
27 import Numeric
28
29 from IPython.genutils import popkey,xsys
30
31 # needed by hardcopy():
32 gp = Gnuplot_ori.gp
33
34 # Patch for Gnuplot.py 1.6 compatibility.
35 # Thanks to Hayden Callow <h.callow@elec.canterbury.ac.nz>
36 try:
37 OptionException = Gnuplot_ori.PlotItems.OptionException
38 except AttributeError:
39 OptionException = Gnuplot_ori.Errors.OptionError
40
41 # exhibit a similar interface to Gnuplot so it can be somewhat drop-in
42 Data = Gnuplot_ori.Data
43 Func = Gnuplot_ori.Func
44 GridData = Gnuplot_ori.GridData
45 PlotItem = Gnuplot_ori.PlotItem
46 PlotItems = Gnuplot_ori.PlotItems
47
48 # Modify some of Gnuplot's functions with improved versions (or bugfixed, in
49 # hardcopy's case). In order to preserve the docstrings at runtime, I've
50 # copied them from the original code.
51
52 # After some significant changes in v 1.7 of Gnuplot.py, we need to do a bit
53 # of version checking.
54
55 if Gnuplot_ori.__version__ <= '1.6':
56 _BaseFileItem = PlotItems.File
57 _BaseTempFileItem = PlotItems.TempFile
58
59 # Fix the File class to add the 'index' option for Gnuplot versions < 1.7
60 class File(_BaseFileItem):
61
62 _option_list = _BaseFileItem._option_list.copy()
63 _option_list.update({
64 'index' : lambda self, index: self.set_option_index(index),
65 })
66
67 # A new initializer is needed b/c we want to add a modified
68 # _option_sequence list which includes 'index' in the right place.
69 def __init__(self,*args,**kw):
70 self._option_sequence = ['binary', 'index', 'using', 'smooth', 'axes',
71 'title', 'with']
72
73 _BaseFileItem.__init__(self,*args,**kw)
74
75 # Let's fix the constructor docstring
76 __newdoc = \
77 """Additional Keyword arguments added by IPython:
78
79 'index=<int>' -- similar to the `index` keyword in Gnuplot.
80 This allows only some of the datasets in a file to be
81 plotted. Datasets within a file are assumed to be separated
82 by _pairs_ of blank lines, and the first one is numbered as
83 0 (similar to C/Python usage)."""
84 __init__.__doc__ = PlotItems.File.__init__.__doc__ + __newdoc
85
86 def set_option_index(self, index):
87 if index is None:
88 self.clear_option('index')
89 elif type(index) in [type(''), type(1)]:
90 self._options['index'] = (index, 'index %s' % index)
91 elif type(index) is type(()):
92 self._options['index'] = (index,'index %s' %
93 string.join(map(repr, index), ':'))
94 else:
95 raise OptionException('index=%s' % (index,))
96
97 # We need a FileClass with a different name from 'File', which is a
98 # factory function in 1.7, so that our String class can subclass FileClass
99 # in any version.
100 _FileClass = File
101
102 elif Gnuplot_ori.__version__ =='1.7':
103 _FileClass = _BaseFileItem = PlotItems._FileItem
104 _BaseTempFileItem = PlotItems._TempFileItem
105 File = PlotItems.File
106
107 else: # changes in the newer version (svn as of March'06)
108 _FileClass = _BaseFileItem = PlotItems._FileItem
109 _BaseTempFileItem = PlotItems._NewFileItem
110 File = PlotItems.File
111
112
113 # Now, we can add our generic code which is version independent
114
115 # First some useful utilities
116 def eps_fix_bbox(fname):
117 """Fix the bounding box of an eps file by running ps2eps on it.
118
119 If its name ends in .eps, the original file is removed.
120
121 This is particularly useful for plots made by Gnuplot with square aspect
122 ratio: there is a bug in Gnuplot which makes it generate a bounding box
123 which is far wider than the actual plot.
124
125 This function assumes that ps2eps is installed in your system."""
126
127 # note: ps2ps and eps2eps do NOT work, ONLY ps2eps works correctly. The
128 # others make output with bitmapped fonts, which looks horrible.
129 print 'Fixing eps file: <%s>' % fname
130 xsys('ps2eps -f -q -l %s' % fname)
131 if fname.endswith('.eps'):
132 os.rename(fname+'.eps',fname)
133
134 def is_list1d(x,containers = [types.ListType,types.TupleType]):
135 """Returns true if x appears to be a 1d list/tuple/array.
136
137 The heuristics are: identify Numeric arrays, or lists/tuples whose first
138 element is not itself a list/tuple. This way zipped lists should work like
139 the original Gnuplot. There's no inexpensive way to know if a list doesn't
140 have a composite object after its first element, so that kind of input
141 will produce an error. But it should work well in most cases.
142 """
143 x_type = type(x)
144
145 return x_type == Numeric.ArrayType and len(x.shape)==1 or \
146 (x_type in containers and
147 type(x[0]) not in containers + [Numeric.ArrayType])
148
149 def zip_items(items,titles=None):
150 """zip together neighboring 1-d arrays, and zip standalone ones
151 with their index. Leave other plot items alone."""
152
153 class StandaloneItem(Exception): pass
154
155 def get_titles(titles):
156 """Return the next title and the input titles array.
157
158 The input array may be changed to None when no titles are left to
159 prevent extra unnecessary calls to this function."""
160
161 try:
162 title = titles[tit_ct[0]] # tit_ct[0] is in zip_items'scope
163 except IndexError:
164 titles = None # so we don't enter again
165 title = None
166 else:
167 tit_ct[0] += 1
168 return title,titles
169
170 new_items = []
171
172 if titles:
173 # Initialize counter. It was put in a list as a hack to allow the
174 # nested get_titles to modify it without raising a NameError.
175 tit_ct = [0]
176
177 n = 0 # this loop needs to be done by hand
178 while n < len(items):
179 item = items[n]
180 try:
181 if is_list1d(item):
182 if n==len(items)-1: # last in list
183 raise StandaloneItem
184 else: # check the next item and zip together if needed
185 next_item = items[n+1]
186 if next_item is None:
187 n += 1
188 raise StandaloneItem
189 elif is_list1d(next_item):
190 # this would be best done with an iterator
191 if titles:
192 title,titles = get_titles(titles)
193 else:
194 title = None
195 new_items.append(Data(zip(item,next_item),
196 title=title))
197 n += 1 # avoid double-inclusion of next item
198 else: # can't zip with next, zip with own index list
199 raise StandaloneItem
200 else: # not 1-d array
201 new_items.append(item)
202 except StandaloneItem:
203 if titles:
204 title,titles = get_titles(titles)
205 else:
206 title = None
207 new_items.append(Data(zip(range(len(item)),item),title=title))
208 except AttributeError:
209 new_items.append(item)
210 n+=1
211
212 return new_items
213
214 # And some classes with enhanced functionality.
215 class String(_FileClass):
216 """Make a PlotItem from data in a string with the same format as a File.
217
218 This allows writing data directly inside python scripts using the exact
219 same format and manipulation options which would be used for external
220 files."""
221
222 def __init__(self, data_str, **keyw):
223 """Construct a String object.
224
225 <data_str> is a string formatted exactly like a valid Gnuplot data
226 file would be. All options from the File constructor are valid here.
227
228 Warning: when used for interactive plotting in scripts which exit
229 immediately, you may get an error because the temporary file used to
230 hold the string data was deleted before Gnuplot had a chance to see
231 it. You can work around this problem by putting a raw_input() call at
232 the end of the script.
233
234 This problem does not appear when generating PostScript output, only
235 with Gnuplot windows."""
236
237 self.tmpfile = _BaseTempFileItem()
238 tmpfile = file(self.tmpfile.filename,'w')
239 tmpfile.write(data_str)
240 _BaseFileItem.__init__(self,self.tmpfile,**keyw)
241
242
243 class Gnuplot(Gnuplot_ori.Gnuplot):
244 """Improved Gnuplot class.
245
246 Enhancements: better plot,replot and hardcopy methods. New methods for
247 quick range setting.
248 """
249
250 def xrange(self,min='*',max='*'):
251 """Set xrange. If min/max is omitted, it is set to '*' (auto).
252
253 Note that this is different from the regular Gnuplot behavior, where
254 an unspecified limit means no change. Here any unspecified limit is
255 set to autoscaling, allowing these functions to be used for full
256 autoscaling when called with no arguments.
257
258 To preserve one limit's current value while changing the other, an
259 explicit '' argument must be given as the limit to be kept.
260
261 Similar functions exist for [y{2}z{2}rtuv]range."""
262
263 self('set xrange [%s:%s]' % (min,max))
264
265 def yrange(self,min='*',max='*'):
266 self('set yrange [%s:%s]' % (min,max))
267
268 def zrange(self,min='*',max='*'):
269 self('set zrange [%s:%s]' % (min,max))
270
271 def x2range(self,min='*',max='*'):
272 self('set xrange [%s:%s]' % (min,max))
273
274 def y2range(self,min='*',max='*'):
275 self('set yrange [%s:%s]' % (min,max))
276
277 def z2range(self,min='*',max='*'):
278 self('set zrange [%s:%s]' % (min,max))
279
280 def rrange(self,min='*',max='*'):
281 self('set rrange [%s:%s]' % (min,max))
282
283 def trange(self,min='*',max='*'):
284 self('set trange [%s:%s]' % (min,max))
285
286 def urange(self,min='*',max='*'):
287 self('set urange [%s:%s]' % (min,max))
288
289 def vrange(self,min='*',max='*'):
290 self('set vrange [%s:%s]' % (min,max))
291
292 def set_ps(self,option):
293 """Set an option for the PostScript terminal and reset default term."""
294
295 self('set terminal postscript %s ' % option)
296 self('set terminal %s' % gp.GnuplotOpts.default_term)
297
298 def __plot_ps(self, plot_method,*items, **keyw):
299 """Wrapper for plot/splot/replot, with processing of hardcopy options.
300
301 For internal use only."""
302
303 # Filter out PostScript options which will crash the normal plot/replot
304 psargs = {'filename':None,
305 'mode':None,
306 'eps':None,
307 'enhanced':None,
308 'color':None,
309 'solid':None,
310 'duplexing':None,
311 'fontname':None,
312 'fontsize':None,
313 'debug':0 }
314
315 for k in psargs.keys():
316 if keyw.has_key(k):
317 psargs[k] = keyw[k]
318 del keyw[k]
319
320 # Filter out other options the original plot doesn't know
321 hardcopy = popkey(keyw,'hardcopy',psargs['filename'] is not None)
322 titles = popkey(keyw,'titles',0)
323
324 # the filename keyword should control hardcopy generation, this is an
325 # override switch only which needs to be explicitly set to zero
326 if hardcopy:
327 if psargs['filename'] is None:
328 raise ValueError, \
329 'If you request hardcopy, you must give a filename.'
330
331 # set null output so nothing goes to screen. hardcopy() restores output
332 self('set term dumb')
333 # I don't know how to prevent screen output in Windows
334 if os.name == 'posix':
335 self('set output "/dev/null"')
336
337 new_items = zip_items(items,titles)
338 # plot_method is either plot or replot from the original Gnuplot class:
339 plot_method(self,*new_items,**keyw)
340
341 # Do hardcopy if requested
342 if hardcopy:
343 if psargs['filename'].endswith('.eps'):
344 psargs['eps'] = 1
345 self.hardcopy(**psargs)
346
347 def plot(self, *items, **keyw):
348 """Draw a new plot.
349
350 Clear the current plot and create a new 2-d plot containing
351 the specified items. Each arguments should be of the
352 following types:
353
354 'PlotItem' (e.g., 'Data', 'File', 'Func') -- This is the most
355 flexible way to call plot because the PlotItems can
356 contain suboptions. Moreover, PlotItems can be saved to
357 variables so that their lifetime is longer than one plot
358 command; thus they can be replotted with minimal overhead.
359
360 'string' (e.g., 'sin(x)') -- The string is interpreted as
361 'Func(string)' (a function that is computed by gnuplot).
362
363 Anything else -- The object, which should be convertible to an
364 array, is passed to the 'Data' constructor, and thus
365 plotted as data. If the conversion fails, an exception is
366 raised.
367
368
369 This is a modified version of plot(). Compared to the original in
370 Gnuplot.py, this version has several enhancements, listed below.
371
372
373 Modifications to the input arguments
374 ------------------------------------
375
376 (1-d array means Numeric array, list or tuple):
377
378 (i) Any 1-d array which is NOT followed by another 1-d array, is
379 automatically zipped with range(len(array_1d)). Typing g.plot(y) will
380 plot y against its indices.
381
382 (ii) If two 1-d arrays are contiguous in the argument list, they are
383 automatically zipped together. So g.plot(x,y) plots y vs. x, and
384 g.plot(x1,y1,x2,y2) plots y1 vs. x1 and y2 vs. x2.
385
386 (iii) Any 1-d array which is followed by None is automatically zipped
387 with range(len(array_1d)). In this form, typing g.plot(y1,None,y2)
388 will plot both y1 and y2 against their respective indices (and NOT
389 versus one another). The None prevents zipping y1 and y2 together, and
390 since y2 is unpaired it is automatically zipped to its indices by (i)
391
392 (iv) Any other arguments which don't match these cases are left alone and
393 passed to the code below.
394
395 For lists or tuples, the heuristics used to determine whether they are
396 in fact 1-d is fairly simplistic: their first element is checked, and
397 if it is not a list or tuple itself, it is assumed that the whole
398 object is one-dimensional.
399
400 An additional optional keyword 'titles' has been added: it must be a
401 list of strings to be used as labels for the individual plots which
402 are NOT PlotItem objects (since those objects carry their own labels
403 within).
404
405
406 PostScript generation
407 ---------------------
408
409 This version of plot() also handles automatically the production of
410 PostScript output. The main options are (given as keyword arguments):
411
412 - filename: a string, typically ending in .eps. If given, the plot is
413 sent to this file in PostScript format.
414
415 - hardcopy: this can be set to 0 to override 'filename'. It does not
416 need to be given to produce PostScript, its purpose is to allow
417 switching PostScript output off globally in scripts without having to
418 manually change 'filename' values in multiple calls.
419
420 All other keywords accepted by Gnuplot.hardcopy() are transparently
421 passed, and safely ignored if output is sent to the screen instead of
422 PostScript.
423
424 For example:
425
426 In [1]: x=frange(0,2*pi,npts=100)
427
428 Generate a plot in file 'sin.eps':
429
430 In [2]: plot(x,sin(x),filename = 'sin.eps')
431
432 Plot to screen instead, without having to change the filename:
433
434 In [3]: plot(x,sin(x),filename = 'sin.eps',hardcopy=0)
435
436 Pass the 'color=0' option to hardcopy for monochrome output:
437
438 In [4]: plot(x,sin(x),filename = 'sin.eps',color=0)
439
440 PostScript generation through plot() is useful mainly for scripting
441 uses where you are not interested in interactive plotting. For
442 interactive use, the hardcopy() function is typically more convenient:
443
444 In [5]: plot(x,sin(x))
445
446 In [6]: hardcopy('sin.eps') """
447
448 self.__plot_ps(Gnuplot_ori.Gnuplot.plot,*items,**keyw)
449
450 def plot2(self,arg,**kw):
451 """Plot the entries of a dictionary or a list/tuple of arrays.
452
453 This simple utility calls plot() with a list of Gnuplot.Data objects
454 constructed either from the values of the input dictionary, or the entries
455 in it if it is a tuple or list. Each item gets labeled with the key/index
456 in the Gnuplot legend.
457
458 Each item is plotted by zipping it with a list of its indices.
459
460 Any keywords are passed directly to plot()."""
461
462 if hasattr(arg,'keys'):
463 keys = arg.keys()
464 keys.sort()
465 else:
466 keys = range(len(arg))
467
468 pitems = [Data(zip(range(len(arg[k])),arg[k]),title=`k`) for k in keys]
469 self.plot(*pitems,**kw)
470
471 def splot(self, *items, **keyw):
472 """Draw a new three-dimensional plot.
473
474 Clear the current plot and create a new 3-d plot containing
475 the specified items. Arguments can be of the following types:
476
477 'PlotItem' (e.g., 'Data', 'File', 'Func', 'GridData' ) -- This
478 is the most flexible way to call plot because the
479 PlotItems can contain suboptions. Moreover, PlotItems can
480 be saved to variables so that their lifetime is longer
481 than one plot command--thus they can be replotted with
482 minimal overhead.
483
484 'string' (e.g., 'sin(x*y)') -- The string is interpreted as a
485 'Func()' (a function that is computed by gnuplot).
486
487 Anything else -- The object is converted to a Data() item, and
488 thus plotted as data. Note that each data point should
489 normally have at least three values associated with it
490 (i.e., x, y, and z). If the conversion fails, an
491 exception is raised.
492
493 This is a modified version of splot(). Compared to the original in
494 Gnuplot.py, this version has several enhancements, listed in the
495 plot() documentation.
496 """
497
498 self.__plot_ps(Gnuplot_ori.Gnuplot.splot,*items,**keyw)
499
500 def replot(self, *items, **keyw):
501 """Replot the data, possibly adding new 'PlotItem's.
502
503 Replot the existing graph, using the items in the current
504 itemlist. If arguments are specified, they are interpreted as
505 additional items to be plotted alongside the existing items on
506 the same graph. See 'plot' for details.
507
508 If you want to replot to a postscript file, you MUST give the
509 'filename' keyword argument in each call to replot. The Gnuplot python
510 interface has no way of knowing that your previous call to
511 Gnuplot.plot() was meant for PostScript output."""
512
513 self.__plot_ps(Gnuplot_ori.Gnuplot.replot,*items,**keyw)
514
515 # The original hardcopy has a bug. See fix at the end. The rest of the code
516 # was lifted verbatim from the original, so that people using IPython get the
517 # benefits without having to manually patch Gnuplot.py
518 def hardcopy(self, filename=None,
519 mode=None,
520 eps=None,
521 enhanced=None,
522 color=None,
523 solid=None,
524 duplexing=None,
525 fontname=None,
526 fontsize=None,
527 debug = 0,
528 ):
529 """Create a hardcopy of the current plot.
530
531 Create a postscript hardcopy of the current plot to the
532 default printer (if configured) or to the specified filename.
533
534 Note that gnuplot remembers the postscript suboptions across
535 terminal changes. Therefore if you set, for example, color=1
536 for one hardcopy then the next hardcopy will also be color
537 unless you explicitly choose color=0. Alternately you can
538 force all of the options to their defaults by setting
539 mode='default'. I consider this to be a bug in gnuplot.
540
541 Keyword arguments:
542
543 'filename=<string>' -- if a filename is specified, save the
544 output in that file; otherwise print it immediately
545 using the 'default_lpr' configuration option. If the
546 filename ends in '.eps', EPS mode is automatically
547 selected (like manually specifying eps=1 or mode='eps').
548
549 'mode=<string>' -- set the postscript submode ('landscape',
550 'portrait', 'eps', or 'default'). The default is
551 to leave this option unspecified.
552
553 'eps=<bool>' -- shorthand for 'mode="eps"'; asks gnuplot to
554 generate encapsulated postscript.
555
556 'enhanced=<bool>' -- if set (the default), then generate
557 enhanced postscript, which allows extra features like
558 font-switching, superscripts, and subscripts in axis
559 labels. (Some old gnuplot versions do not support
560 enhanced postscript; if this is the case set
561 gp.GnuplotOpts.prefer_enhanced_postscript=None.)
562
563 'color=<bool>' -- if set, create a plot with color. Default
564 is to leave this option unchanged.
565
566 'solid=<bool>' -- if set, force lines to be solid (i.e., not
567 dashed).
568
569 'duplexing=<string>' -- set duplexing option ('defaultplex',
570 'simplex', or 'duplex'). Only request double-sided
571 printing if your printer can handle it. Actually this
572 option is probably meaningless since hardcopy() can only
573 print a single plot at a time.
574
575 'fontname=<string>' -- set the default font to <string>,
576 which must be a valid postscript font. The default is
577 to leave this option unspecified.
578
579 'fontsize=<double>' -- set the default font size, in
580 postscript points.
581
582 'debug=<bool>' -- print extra debugging information (useful if
583 your PostScript files are misteriously not being created).
584 """
585
586 if filename is None:
587 assert gp.GnuplotOpts.default_lpr is not None, \
588 OptionException('default_lpr is not set, so you can only '
589 'print to a file.')
590 filename = gp.GnuplotOpts.default_lpr
591 lpr_output = 1
592 else:
593 if filename.endswith('.eps'):
594 eps = 1
595 lpr_output = 0
596
597 # Be careful processing the options. If the user didn't
598 # request an option explicitly, do not specify it on the 'set
599 # terminal' line (don't even specify the default value for the
600 # option). This is to avoid confusing older versions of
601 # gnuplot that do not support all of these options. The
602 # exception is 'enhanced', which is just too useful to have to
603 # specify each time!
604
605 setterm = ['set', 'terminal', 'postscript']
606 if eps:
607 assert mode is None or mode=='eps', \
608 OptionException('eps option and mode are incompatible')
609 setterm.append('eps')
610 else:
611 if mode is not None:
612 assert mode in ['landscape', 'portrait', 'eps', 'default'], \
613 OptionException('illegal mode "%s"' % mode)
614 setterm.append(mode)
615 if enhanced is None:
616 enhanced = gp.GnuplotOpts.prefer_enhanced_postscript
617 if enhanced is not None:
618 if enhanced: setterm.append('enhanced')
619 else: setterm.append('noenhanced')
620 if color is not None:
621 if color: setterm.append('color')
622 else: setterm.append('monochrome')
623 if solid is not None:
624 if solid: setterm.append('solid')
625 else: setterm.append('dashed')
626 if duplexing is not None:
627 assert duplexing in ['defaultplex', 'simplex', 'duplex'], \
628 OptionException('illegal duplexing mode "%s"' % duplexing)
629 setterm.append(duplexing)
630 if fontname is not None:
631 setterm.append('"%s"' % fontname)
632 if fontsize is not None:
633 setterm.append('%s' % fontsize)
634
635 self(string.join(setterm))
636 self.set_string('output', filename)
637 # replot the current figure (to the printer):
638 self.refresh()
639
640 # fperez. Ugly kludge: often for some reason the file is NOT created
641 # and we must reissue the creation commands. I have no idea why!
642 if not lpr_output:
643 #print 'Hardcopy <%s>' % filename # dbg
644 maxtries = 20
645 delay = 0.1 # delay (in seconds) between print attempts
646 for i in range(maxtries):
647 time.sleep(0.05) # safety, very small delay
648 if os.path.isfile(filename):
649 if debug:
650 print 'Hardcopy to file <%s> success at attempt #%s.' \
651 % (filename,i+1)
652 break
653 time.sleep(delay)
654 # try again, issue all commands just in case
655 self(string.join(setterm))
656 self.set_string('output', filename)
657 self.refresh()
658 if not os.path.isfile(filename):
659 print >> sys.stderr,'ERROR: Tried %s times and failed to '\
660 'create hardcopy file `%s`' % (maxtries,filename)
661
662 # reset the terminal to its `default' setting:
663 self('set terminal %s' % gp.GnuplotOpts.default_term)
664 self.set_string('output')
665
666 #********************** End of file <Gnuplot2.py> ************************
1 # -*- coding: utf-8 -*-
2 """Improved replacement for the Gnuplot.Gnuplot class.
3
4 This module imports Gnuplot and replaces some of its functionality with
5 improved versions. They add better handling of arrays for plotting and more
6 convenient PostScript generation, plus some fixes for hardcopy().
7
8 It also adds a convenient plot2 method for plotting dictionaries and
9 lists/tuples of arrays.
10
11 This module is meant to be used as a drop-in replacement to the original
12 Gnuplot, so it should be safe to do:
13
14 import IPython.Gnuplot2 as Gnuplot
15
16 $Id: Gnuplot2.py 1210 2006-03-13 01:19:31Z fperez $"""
17
18 import cStringIO
19 import os
20 import string
21 import sys
22 import tempfile
23 import time
24 import types
25
26 import Gnuplot as Gnuplot_ori
27 import Numeric
28
29 from IPython.genutils import popkey,xsys
30
31 # needed by hardcopy():
32 gp = Gnuplot_ori.gp
33
34 # Patch for Gnuplot.py 1.6 compatibility.
35 # Thanks to Hayden Callow <h.callow@elec.canterbury.ac.nz>
36 try:
37 OptionException = Gnuplot_ori.PlotItems.OptionException
38 except AttributeError:
39 OptionException = Gnuplot_ori.Errors.OptionError
40
41 # exhibit a similar interface to Gnuplot so it can be somewhat drop-in
42 Data = Gnuplot_ori.Data
43 Func = Gnuplot_ori.Func
44 GridData = Gnuplot_ori.GridData
45 PlotItem = Gnuplot_ori.PlotItem
46 PlotItems = Gnuplot_ori.PlotItems
47
48 # Modify some of Gnuplot's functions with improved versions (or bugfixed, in
49 # hardcopy's case). In order to preserve the docstrings at runtime, I've
50 # copied them from the original code.
51
52 # After some significant changes in v 1.7 of Gnuplot.py, we need to do a bit
53 # of version checking.
54
55 if Gnuplot_ori.__version__ <= '1.6':
56 _BaseFileItem = PlotItems.File
57 _BaseTempFileItem = PlotItems.TempFile
58
59 # Fix the File class to add the 'index' option for Gnuplot versions < 1.7
60 class File(_BaseFileItem):
61
62 _option_list = _BaseFileItem._option_list.copy()
63 _option_list.update({
64 'index' : lambda self, index: self.set_option_index(index),
65 })
66
67 # A new initializer is needed b/c we want to add a modified
68 # _option_sequence list which includes 'index' in the right place.
69 def __init__(self,*args,**kw):
70 self._option_sequence = ['binary', 'index', 'using', 'smooth', 'axes',
71 'title', 'with']
72
73 _BaseFileItem.__init__(self,*args,**kw)
74
75 # Let's fix the constructor docstring
76 __newdoc = \
77 """Additional Keyword arguments added by IPython:
78
79 'index=<int>' -- similar to the `index` keyword in Gnuplot.
80 This allows only some of the datasets in a file to be
81 plotted. Datasets within a file are assumed to be separated
82 by _pairs_ of blank lines, and the first one is numbered as
83 0 (similar to C/Python usage)."""
84 __init__.__doc__ = PlotItems.File.__init__.__doc__ + __newdoc
85
86 def set_option_index(self, index):
87 if index is None:
88 self.clear_option('index')
89 elif type(index) in [type(''), type(1)]:
90 self._options['index'] = (index, 'index %s' % index)
91 elif type(index) is type(()):
92 self._options['index'] = (index,'index %s' %
93 string.join(map(repr, index), ':'))
94 else:
95 raise OptionException('index=%s' % (index,))
96
97 # We need a FileClass with a different name from 'File', which is a
98 # factory function in 1.7, so that our String class can subclass FileClass
99 # in any version.
100 _FileClass = File
101
102 elif Gnuplot_ori.__version__ =='1.7':
103 _FileClass = _BaseFileItem = PlotItems._FileItem
104 _BaseTempFileItem = PlotItems._TempFileItem
105 File = PlotItems.File
106
107 else: # changes in the newer version (svn as of March'06)
108 _FileClass = _BaseFileItem = PlotItems._FileItem
109 _BaseTempFileItem = PlotItems._NewFileItem
110 File = PlotItems.File
111
112
113 # Now, we can add our generic code which is version independent
114
115 # First some useful utilities
116 def eps_fix_bbox(fname):
117 """Fix the bounding box of an eps file by running ps2eps on it.
118
119 If its name ends in .eps, the original file is removed.
120
121 This is particularly useful for plots made by Gnuplot with square aspect
122 ratio: there is a bug in Gnuplot which makes it generate a bounding box
123 which is far wider than the actual plot.
124
125 This function assumes that ps2eps is installed in your system."""
126
127 # note: ps2ps and eps2eps do NOT work, ONLY ps2eps works correctly. The
128 # others make output with bitmapped fonts, which looks horrible.
129 print 'Fixing eps file: <%s>' % fname
130 xsys('ps2eps -f -q -l %s' % fname)
131 if fname.endswith('.eps'):
132 os.rename(fname+'.eps',fname)
133
134 def is_list1d(x,containers = [types.ListType,types.TupleType]):
135 """Returns true if x appears to be a 1d list/tuple/array.
136
137 The heuristics are: identify Numeric arrays, or lists/tuples whose first
138 element is not itself a list/tuple. This way zipped lists should work like
139 the original Gnuplot. There's no inexpensive way to know if a list doesn't
140 have a composite object after its first element, so that kind of input
141 will produce an error. But it should work well in most cases.
142 """
143 x_type = type(x)
144
145 return x_type == Numeric.ArrayType and len(x.shape)==1 or \
146 (x_type in containers and
147 type(x[0]) not in containers + [Numeric.ArrayType])
148
149 def zip_items(items,titles=None):
150 """zip together neighboring 1-d arrays, and zip standalone ones
151 with their index. Leave other plot items alone."""
152
153 class StandaloneItem(Exception): pass
154
155 def get_titles(titles):
156 """Return the next title and the input titles array.
157
158 The input array may be changed to None when no titles are left to
159 prevent extra unnecessary calls to this function."""
160
161 try:
162 title = titles[tit_ct[0]] # tit_ct[0] is in zip_items'scope
163 except IndexError:
164 titles = None # so we don't enter again
165 title = None
166 else:
167 tit_ct[0] += 1
168 return title,titles
169
170 new_items = []
171
172 if titles:
173 # Initialize counter. It was put in a list as a hack to allow the
174 # nested get_titles to modify it without raising a NameError.
175 tit_ct = [0]
176
177 n = 0 # this loop needs to be done by hand
178 while n < len(items):
179 item = items[n]
180 try:
181 if is_list1d(item):
182 if n==len(items)-1: # last in list
183 raise StandaloneItem
184 else: # check the next item and zip together if needed
185 next_item = items[n+1]
186 if next_item is None:
187 n += 1
188 raise StandaloneItem
189 elif is_list1d(next_item):
190 # this would be best done with an iterator
191 if titles:
192 title,titles = get_titles(titles)
193 else:
194 title = None
195 new_items.append(Data(zip(item,next_item),
196 title=title))
197 n += 1 # avoid double-inclusion of next item
198 else: # can't zip with next, zip with own index list
199 raise StandaloneItem
200 else: # not 1-d array
201 new_items.append(item)
202 except StandaloneItem:
203 if titles:
204 title,titles = get_titles(titles)
205 else:
206 title = None
207 new_items.append(Data(zip(range(len(item)),item),title=title))
208 except AttributeError:
209 new_items.append(item)
210 n+=1
211
212 return new_items
213
214 # And some classes with enhanced functionality.
215 class String(_FileClass):
216 """Make a PlotItem from data in a string with the same format as a File.
217
218 This allows writing data directly inside python scripts using the exact
219 same format and manipulation options which would be used for external
220 files."""
221
222 def __init__(self, data_str, **keyw):
223 """Construct a String object.
224
225 <data_str> is a string formatted exactly like a valid Gnuplot data
226 file would be. All options from the File constructor are valid here.
227
228 Warning: when used for interactive plotting in scripts which exit
229 immediately, you may get an error because the temporary file used to
230 hold the string data was deleted before Gnuplot had a chance to see
231 it. You can work around this problem by putting a raw_input() call at
232 the end of the script.
233
234 This problem does not appear when generating PostScript output, only
235 with Gnuplot windows."""
236
237 self.tmpfile = _BaseTempFileItem()
238 tmpfile = file(self.tmpfile.filename,'w')
239 tmpfile.write(data_str)
240 _BaseFileItem.__init__(self,self.tmpfile,**keyw)
241
242
243 class Gnuplot(Gnuplot_ori.Gnuplot):
244 """Improved Gnuplot class.
245
246 Enhancements: better plot,replot and hardcopy methods. New methods for
247 quick range setting.
248 """
249
250 def xrange(self,min='*',max='*'):
251 """Set xrange. If min/max is omitted, it is set to '*' (auto).
252
253 Note that this is different from the regular Gnuplot behavior, where
254 an unspecified limit means no change. Here any unspecified limit is
255 set to autoscaling, allowing these functions to be used for full
256 autoscaling when called with no arguments.
257
258 To preserve one limit's current value while changing the other, an
259 explicit '' argument must be given as the limit to be kept.
260
261 Similar functions exist for [y{2}z{2}rtuv]range."""
262
263 self('set xrange [%s:%s]' % (min,max))
264
265 def yrange(self,min='*',max='*'):
266 self('set yrange [%s:%s]' % (min,max))
267
268 def zrange(self,min='*',max='*'):
269 self('set zrange [%s:%s]' % (min,max))
270
271 def x2range(self,min='*',max='*'):
272 self('set xrange [%s:%s]' % (min,max))
273
274 def y2range(self,min='*',max='*'):
275 self('set yrange [%s:%s]' % (min,max))
276
277 def z2range(self,min='*',max='*'):
278 self('set zrange [%s:%s]' % (min,max))
279
280 def rrange(self,min='*',max='*'):
281 self('set rrange [%s:%s]' % (min,max))
282
283 def trange(self,min='*',max='*'):
284 self('set trange [%s:%s]' % (min,max))
285
286 def urange(self,min='*',max='*'):
287 self('set urange [%s:%s]' % (min,max))
288
289 def vrange(self,min='*',max='*'):
290 self('set vrange [%s:%s]' % (min,max))
291
292 def set_ps(self,option):
293 """Set an option for the PostScript terminal and reset default term."""
294
295 self('set terminal postscript %s ' % option)
296 self('set terminal %s' % gp.GnuplotOpts.default_term)
297
298 def __plot_ps(self, plot_method,*items, **keyw):
299 """Wrapper for plot/splot/replot, with processing of hardcopy options.
300
301 For internal use only."""
302
303 # Filter out PostScript options which will crash the normal plot/replot
304 psargs = {'filename':None,
305 'mode':None,
306 'eps':None,
307 'enhanced':None,
308 'color':None,
309 'solid':None,
310 'duplexing':None,
311 'fontname':None,
312 'fontsize':None,
313 'debug':0 }
314
315 for k in psargs.keys():
316 if keyw.has_key(k):
317 psargs[k] = keyw[k]
318 del keyw[k]
319
320 # Filter out other options the original plot doesn't know
321 hardcopy = popkey(keyw,'hardcopy',psargs['filename'] is not None)
322 titles = popkey(keyw,'titles',0)
323
324 # the filename keyword should control hardcopy generation, this is an
325 # override switch only which needs to be explicitly set to zero
326 if hardcopy:
327 if psargs['filename'] is None:
328 raise ValueError, \
329 'If you request hardcopy, you must give a filename.'
330
331 # set null output so nothing goes to screen. hardcopy() restores output
332 self('set term dumb')
333 # I don't know how to prevent screen output in Windows
334 if os.name == 'posix':
335 self('set output "/dev/null"')
336
337 new_items = zip_items(items,titles)
338 # plot_method is either plot or replot from the original Gnuplot class:
339 plot_method(self,*new_items,**keyw)
340
341 # Do hardcopy if requested
342 if hardcopy:
343 if psargs['filename'].endswith('.eps'):
344 psargs['eps'] = 1
345 self.hardcopy(**psargs)
346
347 def plot(self, *items, **keyw):
348 """Draw a new plot.
349
350 Clear the current plot and create a new 2-d plot containing
351 the specified items. Each arguments should be of the
352 following types:
353
354 'PlotItem' (e.g., 'Data', 'File', 'Func') -- This is the most
355 flexible way to call plot because the PlotItems can
356 contain suboptions. Moreover, PlotItems can be saved to
357 variables so that their lifetime is longer than one plot
358 command; thus they can be replotted with minimal overhead.
359
360 'string' (e.g., 'sin(x)') -- The string is interpreted as
361 'Func(string)' (a function that is computed by gnuplot).
362
363 Anything else -- The object, which should be convertible to an
364 array, is passed to the 'Data' constructor, and thus
365 plotted as data. If the conversion fails, an exception is
366 raised.
367
368
369 This is a modified version of plot(). Compared to the original in
370 Gnuplot.py, this version has several enhancements, listed below.
371
372
373 Modifications to the input arguments
374 ------------------------------------
375
376 (1-d array means Numeric array, list or tuple):
377
378 (i) Any 1-d array which is NOT followed by another 1-d array, is
379 automatically zipped with range(len(array_1d)). Typing g.plot(y) will
380 plot y against its indices.
381
382 (ii) If two 1-d arrays are contiguous in the argument list, they are
383 automatically zipped together. So g.plot(x,y) plots y vs. x, and
384 g.plot(x1,y1,x2,y2) plots y1 vs. x1 and y2 vs. x2.
385
386 (iii) Any 1-d array which is followed by None is automatically zipped
387 with range(len(array_1d)). In this form, typing g.plot(y1,None,y2)
388 will plot both y1 and y2 against their respective indices (and NOT
389 versus one another). The None prevents zipping y1 and y2 together, and
390 since y2 is unpaired it is automatically zipped to its indices by (i)
391
392 (iv) Any other arguments which don't match these cases are left alone and
393 passed to the code below.
394
395 For lists or tuples, the heuristics used to determine whether they are
396 in fact 1-d is fairly simplistic: their first element is checked, and
397 if it is not a list or tuple itself, it is assumed that the whole
398 object is one-dimensional.
399
400 An additional optional keyword 'titles' has been added: it must be a
401 list of strings to be used as labels for the individual plots which
402 are NOT PlotItem objects (since those objects carry their own labels
403 within).
404
405
406 PostScript generation
407 ---------------------
408
409 This version of plot() also handles automatically the production of
410 PostScript output. The main options are (given as keyword arguments):
411
412 - filename: a string, typically ending in .eps. If given, the plot is
413 sent to this file in PostScript format.
414
415 - hardcopy: this can be set to 0 to override 'filename'. It does not
416 need to be given to produce PostScript, its purpose is to allow
417 switching PostScript output off globally in scripts without having to
418 manually change 'filename' values in multiple calls.
419
420 All other keywords accepted by Gnuplot.hardcopy() are transparently
421 passed, and safely ignored if output is sent to the screen instead of
422 PostScript.
423
424 For example:
425
426 In [1]: x=frange(0,2*pi,npts=100)
427
428 Generate a plot in file 'sin.eps':
429
430 In [2]: plot(x,sin(x),filename = 'sin.eps')
431
432 Plot to screen instead, without having to change the filename:
433
434 In [3]: plot(x,sin(x),filename = 'sin.eps',hardcopy=0)
435
436 Pass the 'color=0' option to hardcopy for monochrome output:
437
438 In [4]: plot(x,sin(x),filename = 'sin.eps',color=0)
439
440 PostScript generation through plot() is useful mainly for scripting
441 uses where you are not interested in interactive plotting. For
442 interactive use, the hardcopy() function is typically more convenient:
443
444 In [5]: plot(x,sin(x))
445
446 In [6]: hardcopy('sin.eps') """
447
448 self.__plot_ps(Gnuplot_ori.Gnuplot.plot,*items,**keyw)
449
450 def plot2(self,arg,**kw):
451 """Plot the entries of a dictionary or a list/tuple of arrays.
452
453 This simple utility calls plot() with a list of Gnuplot.Data objects
454 constructed either from the values of the input dictionary, or the entries
455 in it if it is a tuple or list. Each item gets labeled with the key/index
456 in the Gnuplot legend.
457
458 Each item is plotted by zipping it with a list of its indices.
459
460 Any keywords are passed directly to plot()."""
461
462 if hasattr(arg,'keys'):
463 keys = arg.keys()
464 keys.sort()
465 else:
466 keys = range(len(arg))
467
468 pitems = [Data(zip(range(len(arg[k])),arg[k]),title=`k`) for k in keys]
469 self.plot(*pitems,**kw)
470
471 def splot(self, *items, **keyw):
472 """Draw a new three-dimensional plot.
473
474 Clear the current plot and create a new 3-d plot containing
475 the specified items. Arguments can be of the following types:
476
477 'PlotItem' (e.g., 'Data', 'File', 'Func', 'GridData' ) -- This
478 is the most flexible way to call plot because the
479 PlotItems can contain suboptions. Moreover, PlotItems can
480 be saved to variables so that their lifetime is longer
481 than one plot command--thus they can be replotted with
482 minimal overhead.
483
484 'string' (e.g., 'sin(x*y)') -- The string is interpreted as a
485 'Func()' (a function that is computed by gnuplot).
486
487 Anything else -- The object is converted to a Data() item, and
488 thus plotted as data. Note that each data point should
489 normally have at least three values associated with it
490 (i.e., x, y, and z). If the conversion fails, an
491 exception is raised.
492
493 This is a modified version of splot(). Compared to the original in
494 Gnuplot.py, this version has several enhancements, listed in the
495 plot() documentation.
496 """
497
498 self.__plot_ps(Gnuplot_ori.Gnuplot.splot,*items,**keyw)
499
500 def replot(self, *items, **keyw):
501 """Replot the data, possibly adding new 'PlotItem's.
502
503 Replot the existing graph, using the items in the current
504 itemlist. If arguments are specified, they are interpreted as
505 additional items to be plotted alongside the existing items on
506 the same graph. See 'plot' for details.
507
508 If you want to replot to a postscript file, you MUST give the
509 'filename' keyword argument in each call to replot. The Gnuplot python
510 interface has no way of knowing that your previous call to
511 Gnuplot.plot() was meant for PostScript output."""
512
513 self.__plot_ps(Gnuplot_ori.Gnuplot.replot,*items,**keyw)
514
515 # The original hardcopy has a bug. See fix at the end. The rest of the code
516 # was lifted verbatim from the original, so that people using IPython get the
517 # benefits without having to manually patch Gnuplot.py
518 def hardcopy(self, filename=None,
519 mode=None,
520 eps=None,
521 enhanced=None,
522 color=None,
523 solid=None,
524 duplexing=None,
525 fontname=None,
526 fontsize=None,
527 debug = 0,
528 ):
529 """Create a hardcopy of the current plot.
530
531 Create a postscript hardcopy of the current plot to the
532 default printer (if configured) or to the specified filename.
533
534 Note that gnuplot remembers the postscript suboptions across
535 terminal changes. Therefore if you set, for example, color=1
536 for one hardcopy then the next hardcopy will also be color
537 unless you explicitly choose color=0. Alternately you can
538 force all of the options to their defaults by setting
539 mode='default'. I consider this to be a bug in gnuplot.
540
541 Keyword arguments:
542
543 'filename=<string>' -- if a filename is specified, save the
544 output in that file; otherwise print it immediately
545 using the 'default_lpr' configuration option. If the
546 filename ends in '.eps', EPS mode is automatically
547 selected (like manually specifying eps=1 or mode='eps').
548
549 'mode=<string>' -- set the postscript submode ('landscape',
550 'portrait', 'eps', or 'default'). The default is
551 to leave this option unspecified.
552
553 'eps=<bool>' -- shorthand for 'mode="eps"'; asks gnuplot to
554 generate encapsulated postscript.
555
556 'enhanced=<bool>' -- if set (the default), then generate
557 enhanced postscript, which allows extra features like
558 font-switching, superscripts, and subscripts in axis
559 labels. (Some old gnuplot versions do not support
560 enhanced postscript; if this is the case set
561 gp.GnuplotOpts.prefer_enhanced_postscript=None.)
562
563 'color=<bool>' -- if set, create a plot with color. Default
564 is to leave this option unchanged.
565
566 'solid=<bool>' -- if set, force lines to be solid (i.e., not
567 dashed).
568
569 'duplexing=<string>' -- set duplexing option ('defaultplex',
570 'simplex', or 'duplex'). Only request double-sided
571 printing if your printer can handle it. Actually this
572 option is probably meaningless since hardcopy() can only
573 print a single plot at a time.
574
575 'fontname=<string>' -- set the default font to <string>,
576 which must be a valid postscript font. The default is
577 to leave this option unspecified.
578
579 'fontsize=<double>' -- set the default font size, in
580 postscript points.
581
582 'debug=<bool>' -- print extra debugging information (useful if
583 your PostScript files are misteriously not being created).
584 """
585
586 if filename is None:
587 assert gp.GnuplotOpts.default_lpr is not None, \
588 OptionException('default_lpr is not set, so you can only '
589 'print to a file.')
590 filename = gp.GnuplotOpts.default_lpr
591 lpr_output = 1
592 else:
593 if filename.endswith('.eps'):
594 eps = 1
595 lpr_output = 0
596
597 # Be careful processing the options. If the user didn't
598 # request an option explicitly, do not specify it on the 'set
599 # terminal' line (don't even specify the default value for the
600 # option). This is to avoid confusing older versions of
601 # gnuplot that do not support all of these options. The
602 # exception is 'enhanced', which is just too useful to have to
603 # specify each time!
604
605 setterm = ['set', 'terminal', 'postscript']
606 if eps:
607 assert mode is None or mode=='eps', \
608 OptionException('eps option and mode are incompatible')
609 setterm.append('eps')
610 else:
611 if mode is not None:
612 assert mode in ['landscape', 'portrait', 'eps', 'default'], \
613 OptionException('illegal mode "%s"' % mode)
614 setterm.append(mode)
615 if enhanced is None:
616 enhanced = gp.GnuplotOpts.prefer_enhanced_postscript
617 if enhanced is not None:
618 if enhanced: setterm.append('enhanced')
619 else: setterm.append('noenhanced')
620 if color is not None:
621 if color: setterm.append('color')
622 else: setterm.append('monochrome')
623 if solid is not None:
624 if solid: setterm.append('solid')
625 else: setterm.append('dashed')
626 if duplexing is not None:
627 assert duplexing in ['defaultplex', 'simplex', 'duplex'], \
628 OptionException('illegal duplexing mode "%s"' % duplexing)
629 setterm.append(duplexing)
630 if fontname is not None:
631 setterm.append('"%s"' % fontname)
632 if fontsize is not None:
633 setterm.append('%s' % fontsize)
634
635 self(string.join(setterm))
636 self.set_string('output', filename)
637 # replot the current figure (to the printer):
638 self.refresh()
639
640 # fperez. Ugly kludge: often for some reason the file is NOT created
641 # and we must reissue the creation commands. I have no idea why!
642 if not lpr_output:
643 #print 'Hardcopy <%s>' % filename # dbg
644 maxtries = 20
645 delay = 0.1 # delay (in seconds) between print attempts
646 for i in range(maxtries):
647 time.sleep(0.05) # safety, very small delay
648 if os.path.isfile(filename):
649 if debug:
650 print 'Hardcopy to file <%s> success at attempt #%s.' \
651 % (filename,i+1)
652 break
653 time.sleep(delay)
654 # try again, issue all commands just in case
655 self(string.join(setterm))
656 self.set_string('output', filename)
657 self.refresh()
658 if not os.path.isfile(filename):
659 print >> sys.stderr,'ERROR: Tried %s times and failed to '\
660 'create hardcopy file `%s`' % (maxtries,filename)
661
662 # reset the terminal to its `default' setting:
663 self('set terminal %s' % gp.GnuplotOpts.default_term)
664 self.set_string('output')
665
666 #********************** End of file <Gnuplot2.py> ************************
@@ -1,148 +1,148 b''
1 # -*- coding: utf-8 -*-
2 """Interactive functions and magic functions for Gnuplot usage.
3
4 This requires the Gnuplot.py module for interfacing python with Gnuplot, which
5 can be downloaded from:
6
7 http://gnuplot-py.sourceforge.net/
8
9 See gphelp() below for details on the services offered by this module.
10
11 Inspired by a suggestion/request from Arnd Baecker.
12
13 $Id: GnuplotInteractive.py 389 2004-10-09 07:59:30Z fperez $"""
14
15 __all__ = ['Gnuplot','gp','gp_new','plot','plot2','splot','replot',
16 'hardcopy','gpdata','gpfile','gpstring','gpfunc','gpgrid',
17 'gphelp']
18
19 import IPython.GnuplotRuntime as GRun
20 from IPython.genutils import page,warn
21
22 # Set global names for interactive use
23 Gnuplot = GRun.Gnuplot
24 gp_new = GRun.gp_new
25 gp = GRun.gp
26 plot = gp.plot
27 plot2 = gp.plot2
28 splot = gp.splot
29 replot = gp.replot
30 hardcopy = gp.hardcopy
31
32 # Accessors for the main plot object constructors:
33 gpdata = Gnuplot.Data
34 gpfile = Gnuplot.File
35 gpstring = Gnuplot.String
36 gpfunc = Gnuplot.Func
37 gpgrid = Gnuplot.GridData
38
39 def gphelp():
40 """Print information about the Gnuplot facilities in IPython."""
41
42 page("""
43 IPython provides an interface to access the Gnuplot scientific plotting
44 system, in an environment similar to that of Mathematica or Matlab.
45
46 New top-level global objects
47 ----------------------------
48
49 Please see their respective docstrings for further details.
50
51 - gp: a running Gnuplot instance. You can access its methods as
52 gp.<method>. gp(`a string`) will execute the given string as if it had been
53 typed in an interactive gnuplot window.
54
55 - plot, splot, replot and hardcopy: aliases to the methods of the same name in
56 the global running Gnuplot instance gp. These allow you to simply type:
57
58 In [1]: plot(x,sin(x),title='Sin(x)') # assuming x is a Numeric array
59
60 and obtain a plot of sin(x) vs x with the title 'Sin(x)'.
61
62 - gp_new: a function which returns a new Gnuplot instance. This can be used to
63 have multiple Gnuplot instances running in your session to compare different
64 plots, each in a separate window.
65
66 - Gnuplot: alias to the Gnuplot2 module, an improved drop-in replacement for
67 the original Gnuplot.py. Gnuplot2 needs Gnuplot but redefines several of its
68 functions with improved versions (Gnuplot2 comes with IPython).
69
70 - gpdata, gpfile, gpstring, gpfunc, gpgrid: aliases to Gnuplot.Data,
71 Gnuplot.File, Gnuplot.String, Gnuplot.Func and Gnuplot.GridData
72 respectively. These functions create objects which can then be passed to the
73 plotting commands. See the Gnuplot.py documentation for details.
74
75 Keep in mind that all commands passed to a Gnuplot instance are executed in
76 the Gnuplot namespace, where no Python variables exist. For example, for
77 plotting sin(x) vs x as above, typing
78
79 In [2]: gp('plot x,sin(x)')
80
81 would not work. Instead, you would get the plot of BOTH the functions 'x' and
82 'sin(x)', since Gnuplot doesn't know about the 'x' Python array. The plot()
83 method lives in python and does know about these variables.
84
85
86 New magic functions
87 -------------------
88
89 %gpc: pass one command to Gnuplot and execute it or open a Gnuplot shell where
90 each line of input is executed.
91
92 %gp_set_default: reset the value of IPython's global Gnuplot instance.""")
93
94 # Code below is all for IPython use
95 # Define the magic functions for communicating with the above gnuplot instance.
96 def magic_gpc(self,parameter_s=''):
97 """Execute a gnuplot command or open a gnuplot shell.
98
99 Usage (omit the % if automagic is on). There are two ways to use it:
100
101 1) %gpc 'command' -> passes 'command' directly to the gnuplot instance.
102
103 2) %gpc -> will open up a prompt (gnuplot>>>) which takes input like the
104 standard gnuplot interactive prompt. If you need to type a multi-line
105 command, use \\ at the end of each intermediate line.
106
107 Upon exiting of the gnuplot sub-shell, you return to your IPython
108 session (the gnuplot sub-shell can be invoked as many times as needed).
109 """
110
111 if parameter_s.strip():
112 self.shell.gnuplot(parameter_s)
113 else:
114 self.shell.gnuplot.interact()
115
116 def magic_gp_set_default(self,parameter_s=''):
117 """Set the default gnuplot instance accessed by the %gp magic function.
118
119 %gp_set_default name
120
121 Call with the name of the new instance at the command line. If you want to
122 set this instance in your own code (using an embedded IPython, for
123 example), simply set the variable __IPYTHON__.gnuplot to your own gnuplot
124 instance object."""
125
126 gname = parameter_s.strip()
127 G = eval(gname,self.shell.user_ns)
128 self.shell.gnuplot = G
129 self.shell.user_ns.update({'plot':G.plot,'splot':G.splot,'plot2':G.plot2,
130 'replot':G.replot,'hardcopy':G.hardcopy})
131
132 try:
133 __IPYTHON__
134 except NameError:
135 pass
136 else:
137 # make the global Gnuplot instance known to IPython
138 __IPYTHON__.gnuplot = GRun.gp
139 __IPYTHON__.gnuplot.shell_first_time = 1
140
141 print """*** Type `gphelp` for help on the Gnuplot integration features."""
142
143 # Add the new magic functions to the class dict
144 from IPython.iplib import InteractiveShell
145 InteractiveShell.magic_gpc = magic_gpc
146 InteractiveShell.magic_gp_set_default = magic_gp_set_default
147
148 #********************** End of file <GnuplotInteractive.py> *******************
1 # -*- coding: utf-8 -*-
2 """Interactive functions and magic functions for Gnuplot usage.
3
4 This requires the Gnuplot.py module for interfacing python with Gnuplot, which
5 can be downloaded from:
6
7 http://gnuplot-py.sourceforge.net/
8
9 See gphelp() below for details on the services offered by this module.
10
11 Inspired by a suggestion/request from Arnd Baecker.
12
13 $Id: GnuplotInteractive.py 389 2004-10-09 07:59:30Z fperez $"""
14
15 __all__ = ['Gnuplot','gp','gp_new','plot','plot2','splot','replot',
16 'hardcopy','gpdata','gpfile','gpstring','gpfunc','gpgrid',
17 'gphelp']
18
19 import IPython.GnuplotRuntime as GRun
20 from IPython.genutils import page,warn
21
22 # Set global names for interactive use
23 Gnuplot = GRun.Gnuplot
24 gp_new = GRun.gp_new
25 gp = GRun.gp
26 plot = gp.plot
27 plot2 = gp.plot2
28 splot = gp.splot
29 replot = gp.replot
30 hardcopy = gp.hardcopy
31
32 # Accessors for the main plot object constructors:
33 gpdata = Gnuplot.Data
34 gpfile = Gnuplot.File
35 gpstring = Gnuplot.String
36 gpfunc = Gnuplot.Func
37 gpgrid = Gnuplot.GridData
38
39 def gphelp():
40 """Print information about the Gnuplot facilities in IPython."""
41
42 page("""
43 IPython provides an interface to access the Gnuplot scientific plotting
44 system, in an environment similar to that of Mathematica or Matlab.
45
46 New top-level global objects
47 ----------------------------
48
49 Please see their respective docstrings for further details.
50
51 - gp: a running Gnuplot instance. You can access its methods as
52 gp.<method>. gp(`a string`) will execute the given string as if it had been
53 typed in an interactive gnuplot window.
54
55 - plot, splot, replot and hardcopy: aliases to the methods of the same name in
56 the global running Gnuplot instance gp. These allow you to simply type:
57
58 In [1]: plot(x,sin(x),title='Sin(x)') # assuming x is a Numeric array
59
60 and obtain a plot of sin(x) vs x with the title 'Sin(x)'.
61
62 - gp_new: a function which returns a new Gnuplot instance. This can be used to
63 have multiple Gnuplot instances running in your session to compare different
64 plots, each in a separate window.
65
66 - Gnuplot: alias to the Gnuplot2 module, an improved drop-in replacement for
67 the original Gnuplot.py. Gnuplot2 needs Gnuplot but redefines several of its
68 functions with improved versions (Gnuplot2 comes with IPython).
69
70 - gpdata, gpfile, gpstring, gpfunc, gpgrid: aliases to Gnuplot.Data,
71 Gnuplot.File, Gnuplot.String, Gnuplot.Func and Gnuplot.GridData
72 respectively. These functions create objects which can then be passed to the
73 plotting commands. See the Gnuplot.py documentation for details.
74
75 Keep in mind that all commands passed to a Gnuplot instance are executed in
76 the Gnuplot namespace, where no Python variables exist. For example, for
77 plotting sin(x) vs x as above, typing
78
79 In [2]: gp('plot x,sin(x)')
80
81 would not work. Instead, you would get the plot of BOTH the functions 'x' and
82 'sin(x)', since Gnuplot doesn't know about the 'x' Python array. The plot()
83 method lives in python and does know about these variables.
84
85
86 New magic functions
87 -------------------
88
89 %gpc: pass one command to Gnuplot and execute it or open a Gnuplot shell where
90 each line of input is executed.
91
92 %gp_set_default: reset the value of IPython's global Gnuplot instance.""")
93
94 # Code below is all for IPython use
95 # Define the magic functions for communicating with the above gnuplot instance.
96 def magic_gpc(self,parameter_s=''):
97 """Execute a gnuplot command or open a gnuplot shell.
98
99 Usage (omit the % if automagic is on). There are two ways to use it:
100
101 1) %gpc 'command' -> passes 'command' directly to the gnuplot instance.
102
103 2) %gpc -> will open up a prompt (gnuplot>>>) which takes input like the
104 standard gnuplot interactive prompt. If you need to type a multi-line
105 command, use \\ at the end of each intermediate line.
106
107 Upon exiting of the gnuplot sub-shell, you return to your IPython
108 session (the gnuplot sub-shell can be invoked as many times as needed).
109 """
110
111 if parameter_s.strip():
112 self.shell.gnuplot(parameter_s)
113 else:
114 self.shell.gnuplot.interact()
115
116 def magic_gp_set_default(self,parameter_s=''):
117 """Set the default gnuplot instance accessed by the %gp magic function.
118
119 %gp_set_default name
120
121 Call with the name of the new instance at the command line. If you want to
122 set this instance in your own code (using an embedded IPython, for
123 example), simply set the variable __IPYTHON__.gnuplot to your own gnuplot
124 instance object."""
125
126 gname = parameter_s.strip()
127 G = eval(gname,self.shell.user_ns)
128 self.shell.gnuplot = G
129 self.shell.user_ns.update({'plot':G.plot,'splot':G.splot,'plot2':G.plot2,
130 'replot':G.replot,'hardcopy':G.hardcopy})
131
132 try:
133 __IPYTHON__
134 except NameError:
135 pass
136 else:
137 # make the global Gnuplot instance known to IPython
138 __IPYTHON__.gnuplot = GRun.gp
139 __IPYTHON__.gnuplot.shell_first_time = 1
140
141 print """*** Type `gphelp` for help on the Gnuplot integration features."""
142
143 # Add the new magic functions to the class dict
144 from IPython.iplib import InteractiveShell
145 InteractiveShell.magic_gpc = magic_gpc
146 InteractiveShell.magic_gp_set_default = magic_gp_set_default
147
148 #********************** End of file <GnuplotInteractive.py> *******************
@@ -1,147 +1,147 b''
1 # -*- coding: utf-8 -*-
2 """Basic Gnuplot functionality for inclusion in other code.
3
4 This module creates a running Gnuplot instance called 'gp' and builds other
5 convenient globals for quick use in running scripts. It is intended to allow
6 you to script plotting tasks in Python with a minimum of effort. A typical
7 usage would be:
8
9 import IPython.GnuplotRuntime as GP # or some other short name
10 GP.gp.plot(GP.File('your_data.dat'))
11
12
13 This module exposes the following objects:
14
15 - gp: a running Gnuplot instance. You can access its methods as
16 gp.<method>. gp(`a string`) will execute the given string as if it had been
17 typed in an interactive gnuplot window.
18
19 - gp_new: a function which returns a new Gnuplot instance. This can be used to
20 have multiple Gnuplot instances running in your session to compare different
21 plots.
22
23 - Gnuplot: alias to the Gnuplot2 module, an improved drop-in replacement for
24 the original Gnuplot.py. Gnuplot2 needs Gnuplot but redefines several of its
25 functions with improved versions (Gnuplot2 comes with IPython).
26
27 - Data: alias to Gnuplot.Data, makes a PlotItem from array data.
28
29 - File: alias to Gnuplot.File, makes a PlotItem from a file.
30
31 - String: alias to Gnuplot.String, makes a PlotItem from a string formatted
32 exactly like a file for Gnuplot.File would be.
33
34 - Func: alias to Gnuplot.Func, makes a PlotItem from a function string.
35
36 - GridData: alias to Gnuplot.GridData, makes a PlotItem from grid data.
37
38 - pm3d_config: a string with Gnuplot commands to set up the pm3d mode for
39 surface plotting. You can activate it simply by calling gp(pm3d_config).
40
41 - eps_fix_bbox: A Unix-only function to fix eps files with bad bounding boxes
42 (which Gnuplot generates when the plot size is set to square).
43
44 This requires the Gnuplot.py module for interfacing Python with Gnuplot, which
45 can be downloaded from:
46
47 http://gnuplot-py.sourceforge.net/
48
49 Inspired by a suggestion/request from Arnd Baecker.
50
51 $Id: GnuplotRuntime.py 389 2004-10-09 07:59:30Z fperez $"""
52
53 __all__ = ['Gnuplot','gp','gp_new','Data','File','Func','GridData',
54 'pm3d_config','eps_fix_bbox']
55
56 import os,tempfile,sys
57 from IPython.genutils import getoutput
58
59 #---------------------------------------------------------------------------
60 # Notes on mouse support for Gnuplot.py
61
62 # If you do not have a mouse-enabled gnuplot, set gnuplot_mouse to 0. If you
63 # use gnuplot, you should really grab a recent, mouse enabled copy. It is an
64 # extremely useful feature. Mouse support is official as of gnuplot 4.0,
65 # released in April 2004.
66
67 # For the mouse features to work correctly, you MUST set your Gnuplot.py
68 # module to use temporary files instead of 'inline data' for data
69 # communication. Note that this is the default, so unless you've manually
70 # fiddled with it you should be ok. If you need to make changes, in the
71 # Gnuplot module directory, loook for the gp_unix.py file and make sure the
72 # prefer_inline_data variable is set to 0. If you set it to 1 Gnuplot.py will
73 # try to pass the data to gnuplot via standard input, which completely
74 # confuses the mouse control system (even though it may be a bit faster than
75 # using temp files).
76
77 # As of Gnuplot.py v1.7, a new option was added to use FIFOs (pipes). This
78 # mechanism, while fast, also breaks the mouse system. You must therefore set
79 # the variable prefer_fifo_data to 0 in gp_unix.py.
80
81 tmpname = tempfile.mktemp()
82 open(tmpname,'w').write('set mouse')
83 gnu_out = getoutput('gnuplot '+ tmpname)
84 os.unlink(tmpname)
85 if gnu_out: # Gnuplot won't print anything if it has mouse support
86 print "*** Your version of Gnuplot appears not to have mouse support."
87 gnuplot_mouse = 0
88 else:
89 gnuplot_mouse = 1
90 del tmpname,gnu_out
91
92 # Default state for persistence of new gnuplot instances
93 if os.name in ['nt','dos'] or sys.platform == 'cygwin':
94 gnuplot_persist = 0
95 else:
96 gnuplot_persist = 1
97
98 import IPython.Gnuplot2 as Gnuplot
99
100 class NotGiven: pass
101
102 def gp_new(mouse=NotGiven,persist=NotGiven):
103 """Return a new Gnuplot instance.
104
105 The instance returned uses the improved methods defined in Gnuplot2.
106
107 Options (boolean):
108
109 - mouse: if unspecified, the module global gnuplot_mouse is used.
110
111 - persist: if unspecified, the module global gnuplot_persist is used."""
112
113 if mouse is NotGiven:
114 mouse = gnuplot_mouse
115 if persist is NotGiven:
116 persist = gnuplot_persist
117 g = Gnuplot.Gnuplot(persist=persist)
118 if mouse:
119 g('set mouse')
120 return g
121
122 # Global-level names.
123
124 # A global Gnuplot instance for interactive use:
125 gp = gp_new()
126
127 # Accessors for the main plot object constructors:
128 Data = Gnuplot.Data
129 File = Gnuplot.File
130 Func = Gnuplot.Func
131 String = Gnuplot.String
132 GridData = Gnuplot.GridData
133
134 # A Unix-only function to fix eps files with bad bounding boxes (which Gnuplot
135 # generates when the plot size is set to square):
136 eps_fix_bbox = Gnuplot.eps_fix_bbox
137
138 # String for configuring pm3d. Simply call g(pm3d_config) to execute it. pm3d
139 # is a very nice mode for plotting colormaps on surfaces. Modify the defaults
140 # below to suit your taste.
141 pm3d_config = """
142 set pm3d solid
143 set hidden3d
144 unset surface
145 set isosamples 50
146 """
147 #******************** End of file <GnuplotRuntime.py> ******************
1 # -*- coding: utf-8 -*-
2 """Basic Gnuplot functionality for inclusion in other code.
3
4 This module creates a running Gnuplot instance called 'gp' and builds other
5 convenient globals for quick use in running scripts. It is intended to allow
6 you to script plotting tasks in Python with a minimum of effort. A typical
7 usage would be:
8
9 import IPython.GnuplotRuntime as GP # or some other short name
10 GP.gp.plot(GP.File('your_data.dat'))
11
12
13 This module exposes the following objects:
14
15 - gp: a running Gnuplot instance. You can access its methods as
16 gp.<method>. gp(`a string`) will execute the given string as if it had been
17 typed in an interactive gnuplot window.
18
19 - gp_new: a function which returns a new Gnuplot instance. This can be used to
20 have multiple Gnuplot instances running in your session to compare different
21 plots.
22
23 - Gnuplot: alias to the Gnuplot2 module, an improved drop-in replacement for
24 the original Gnuplot.py. Gnuplot2 needs Gnuplot but redefines several of its
25 functions with improved versions (Gnuplot2 comes with IPython).
26
27 - Data: alias to Gnuplot.Data, makes a PlotItem from array data.
28
29 - File: alias to Gnuplot.File, makes a PlotItem from a file.
30
31 - String: alias to Gnuplot.String, makes a PlotItem from a string formatted
32 exactly like a file for Gnuplot.File would be.
33
34 - Func: alias to Gnuplot.Func, makes a PlotItem from a function string.
35
36 - GridData: alias to Gnuplot.GridData, makes a PlotItem from grid data.
37
38 - pm3d_config: a string with Gnuplot commands to set up the pm3d mode for
39 surface plotting. You can activate it simply by calling gp(pm3d_config).
40
41 - eps_fix_bbox: A Unix-only function to fix eps files with bad bounding boxes
42 (which Gnuplot generates when the plot size is set to square).
43
44 This requires the Gnuplot.py module for interfacing Python with Gnuplot, which
45 can be downloaded from:
46
47 http://gnuplot-py.sourceforge.net/
48
49 Inspired by a suggestion/request from Arnd Baecker.
50
51 $Id: GnuplotRuntime.py 389 2004-10-09 07:59:30Z fperez $"""
52
53 __all__ = ['Gnuplot','gp','gp_new','Data','File','Func','GridData',
54 'pm3d_config','eps_fix_bbox']
55
56 import os,tempfile,sys
57 from IPython.genutils import getoutput
58
59 #---------------------------------------------------------------------------
60 # Notes on mouse support for Gnuplot.py
61
62 # If you do not have a mouse-enabled gnuplot, set gnuplot_mouse to 0. If you
63 # use gnuplot, you should really grab a recent, mouse enabled copy. It is an
64 # extremely useful feature. Mouse support is official as of gnuplot 4.0,
65 # released in April 2004.
66
67 # For the mouse features to work correctly, you MUST set your Gnuplot.py
68 # module to use temporary files instead of 'inline data' for data
69 # communication. Note that this is the default, so unless you've manually
70 # fiddled with it you should be ok. If you need to make changes, in the
71 # Gnuplot module directory, loook for the gp_unix.py file and make sure the
72 # prefer_inline_data variable is set to 0. If you set it to 1 Gnuplot.py will
73 # try to pass the data to gnuplot via standard input, which completely
74 # confuses the mouse control system (even though it may be a bit faster than
75 # using temp files).
76
77 # As of Gnuplot.py v1.7, a new option was added to use FIFOs (pipes). This
78 # mechanism, while fast, also breaks the mouse system. You must therefore set
79 # the variable prefer_fifo_data to 0 in gp_unix.py.
80
81 tmpname = tempfile.mktemp()
82 open(tmpname,'w').write('set mouse')
83 gnu_out = getoutput('gnuplot '+ tmpname)
84 os.unlink(tmpname)
85 if gnu_out: # Gnuplot won't print anything if it has mouse support
86 print "*** Your version of Gnuplot appears not to have mouse support."
87 gnuplot_mouse = 0
88 else:
89 gnuplot_mouse = 1
90 del tmpname,gnu_out
91
92 # Default state for persistence of new gnuplot instances
93 if os.name in ['nt','dos'] or sys.platform == 'cygwin':
94 gnuplot_persist = 0
95 else:
96 gnuplot_persist = 1
97
98 import IPython.Gnuplot2 as Gnuplot
99
100 class NotGiven: pass
101
102 def gp_new(mouse=NotGiven,persist=NotGiven):
103 """Return a new Gnuplot instance.
104
105 The instance returned uses the improved methods defined in Gnuplot2.
106
107 Options (boolean):
108
109 - mouse: if unspecified, the module global gnuplot_mouse is used.
110
111 - persist: if unspecified, the module global gnuplot_persist is used."""
112
113 if mouse is NotGiven:
114 mouse = gnuplot_mouse
115 if persist is NotGiven:
116 persist = gnuplot_persist
117 g = Gnuplot.Gnuplot(persist=persist)
118 if mouse:
119 g('set mouse')
120 return g
121
122 # Global-level names.
123
124 # A global Gnuplot instance for interactive use:
125 gp = gp_new()
126
127 # Accessors for the main plot object constructors:
128 Data = Gnuplot.Data
129 File = Gnuplot.File
130 Func = Gnuplot.Func
131 String = Gnuplot.String
132 GridData = Gnuplot.GridData
133
134 # A Unix-only function to fix eps files with bad bounding boxes (which Gnuplot
135 # generates when the plot size is set to square):
136 eps_fix_bbox = Gnuplot.eps_fix_bbox
137
138 # String for configuring pm3d. Simply call g(pm3d_config) to execute it. pm3d
139 # is a very nice mode for plotting colormaps on surfaces. Modify the defaults
140 # below to suit your taste.
141 pm3d_config = """
142 set pm3d solid
143 set hidden3d
144 unset surface
145 set isosamples 50
146 """
147 #******************** End of file <GnuplotRuntime.py> ******************
This diff has been collapsed as it changes many lines, (568 lines changed) Show them Hide them
@@ -1,284 +1,284 b''
1 # -*- coding: utf-8 -*-
2 """String interpolation for Python (by Ka-Ping Yee, 14 Feb 2000).
3
4 This module lets you quickly and conveniently interpolate values into
5 strings (in the flavour of Perl or Tcl, but with less extraneous
6 punctuation). You get a bit more power than in the other languages,
7 because this module allows subscripting, slicing, function calls,
8 attribute lookup, or arbitrary expressions. Variables and expressions
9 are evaluated in the namespace of the caller.
10
11 The itpl() function returns the result of interpolating a string, and
12 printpl() prints out an interpolated string. Here are some examples:
13
14 from Itpl import printpl
15 printpl("Here is a $string.")
16 printpl("Here is a $module.member.")
17 printpl("Here is an $object.member.")
18 printpl("Here is a $functioncall(with, arguments).")
19 printpl("Here is an ${arbitrary + expression}.")
20 printpl("Here is an $array[3] member.")
21 printpl("Here is a $dictionary['member'].")
22
23 The filter() function filters a file object so that output through it
24 is interpolated. This lets you produce the illusion that Python knows
25 how to do interpolation:
26
27 import Itpl
28 sys.stdout = Itpl.filter()
29 f = "fancy"
30 print "Isn't this $f?"
31 print "Standard output has been replaced with a $sys.stdout object."
32 sys.stdout = Itpl.unfilter()
33 print "Okay, back $to $normal."
34
35 Under the hood, the Itpl class represents a string that knows how to
36 interpolate values. An instance of the class parses the string once
37 upon initialization; the evaluation and substitution can then be done
38 each time the instance is evaluated with str(instance). For example:
39
40 from Itpl import Itpl
41 s = Itpl("Here is $foo.")
42 foo = 5
43 print str(s)
44 foo = "bar"
45 print str(s)
46
47 $Id: Itpl.py 2918 2007-12-31 14:34:47Z vivainio $
48 """ # ' -> close an open quote for stupid emacs
49
50 #*****************************************************************************
51 #
52 # Copyright (c) 2001 Ka-Ping Yee <ping@lfw.org>
53 #
54 #
55 # Published under the terms of the MIT license, hereby reproduced:
56 #
57 # Permission is hereby granted, free of charge, to any person obtaining a copy
58 # of this software and associated documentation files (the "Software"), to
59 # deal in the Software without restriction, including without limitation the
60 # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
61 # sell copies of the Software, and to permit persons to whom the Software is
62 # furnished to do so, subject to the following conditions:
63 #
64 # The above copyright notice and this permission notice shall be included in
65 # all copies or substantial portions of the Software.
66 #
67 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
68 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
69 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
70 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
71 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
72 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
73 # IN THE SOFTWARE.
74 #
75 #*****************************************************************************
76
77 __author__ = 'Ka-Ping Yee <ping@lfw.org>'
78 __license__ = 'MIT'
79
80 import string
81 import sys
82 from tokenize import tokenprog
83 from types import StringType
84
85 class ItplError(ValueError):
86 def __init__(self, text, pos):
87 self.text = text
88 self.pos = pos
89 def __str__(self):
90 return "unfinished expression in %s at char %d" % (
91 repr(self.text), self.pos)
92
93 def matchorfail(text, pos):
94 match = tokenprog.match(text, pos)
95 if match is None:
96 raise ItplError(text, pos)
97 return match, match.end()
98
99 class Itpl:
100 """Class representing a string with interpolation abilities.
101
102 Upon creation, an instance works out what parts of the format
103 string are literal and what parts need to be evaluated. The
104 evaluation and substitution happens in the namespace of the
105 caller when str(instance) is called."""
106
107 def __init__(self, format,codec=sys.stdin.encoding,encoding_errors='backslashreplace'):
108 """The single mandatory argument to this constructor is a format
109 string.
110
111 The format string is parsed according to the following rules:
112
113 1. A dollar sign and a name, possibly followed by any of:
114 - an open-paren, and anything up to the matching paren
115 - an open-bracket, and anything up to the matching bracket
116 - a period and a name
117 any number of times, is evaluated as a Python expression.
118
119 2. A dollar sign immediately followed by an open-brace, and
120 anything up to the matching close-brace, is evaluated as
121 a Python expression.
122
123 3. Outside of the expressions described in the above two rules,
124 two dollar signs in a row give you one literal dollar sign.
125
126 Optional arguments:
127
128 - codec('utf_8'): a string containing the name of a valid Python
129 codec.
130
131 - encoding_errors('backslashreplace'): a string with a valid error handling
132 policy. See the codecs module documentation for details.
133
134 These are used to encode the format string if a call to str() fails on
135 the expanded result."""
136
137 if not isinstance(format,basestring):
138 raise TypeError, "needs string initializer"
139 self.format = format
140 self.codec = codec
141 self.encoding_errors = encoding_errors
142
143 namechars = "abcdefghijklmnopqrstuvwxyz" \
144 "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
145 chunks = []
146 pos = 0
147
148 while 1:
149 dollar = string.find(format, "$", pos)
150 if dollar < 0: break
151 nextchar = format[dollar+1]
152
153 if nextchar == "{":
154 chunks.append((0, format[pos:dollar]))
155 pos, level = dollar+2, 1
156 while level:
157 match, pos = matchorfail(format, pos)
158 tstart, tend = match.regs[3]
159 token = format[tstart:tend]
160 if token == "{": level = level+1
161 elif token == "}": level = level-1
162 chunks.append((1, format[dollar+2:pos-1]))
163
164 elif nextchar in namechars:
165 chunks.append((0, format[pos:dollar]))
166 match, pos = matchorfail(format, dollar+1)
167 while pos < len(format):
168 if format[pos] == "." and \
169 pos+1 < len(format) and format[pos+1] in namechars:
170 match, pos = matchorfail(format, pos+1)
171 elif format[pos] in "([":
172 pos, level = pos+1, 1
173 while level:
174 match, pos = matchorfail(format, pos)
175 tstart, tend = match.regs[3]
176 token = format[tstart:tend]
177 if token[0] in "([": level = level+1
178 elif token[0] in ")]": level = level-1
179 else: break
180 chunks.append((1, format[dollar+1:pos]))
181
182 else:
183 chunks.append((0, format[pos:dollar+1]))
184 pos = dollar + 1 + (nextchar == "$")
185
186 if pos < len(format): chunks.append((0, format[pos:]))
187 self.chunks = chunks
188
189 def __repr__(self):
190 return "<Itpl %s >" % repr(self.format)
191
192 def _str(self,glob,loc):
193 """Evaluate to a string in the given globals/locals.
194
195 The final output is built by calling str(), but if this fails, the
196 result is encoded with the instance's codec and error handling policy,
197 via a call to out.encode(self.codec,self.encoding_errors)"""
198 result = []
199 app = result.append
200 for live, chunk in self.chunks:
201 if live:
202 val = eval(chunk,glob,loc)
203 try:
204 app(str(val))
205 except UnicodeEncodeError:
206 app(unicode(val))
207
208 else: app(chunk)
209 out = ''.join(result)
210 try:
211 return str(out)
212 except UnicodeError:
213 return out.encode(self.codec,self.encoding_errors)
214
215 def __str__(self):
216 """Evaluate and substitute the appropriate parts of the string."""
217
218 # We need to skip enough frames to get to the actual caller outside of
219 # Itpl.
220 frame = sys._getframe(1)
221 while frame.f_globals["__name__"] == __name__: frame = frame.f_back
222 loc, glob = frame.f_locals, frame.f_globals
223
224 return self._str(glob,loc)
225
226 class ItplNS(Itpl):
227 """Class representing a string with interpolation abilities.
228
229 This inherits from Itpl, but at creation time a namespace is provided
230 where the evaluation will occur. The interpolation becomes a bit more
231 efficient, as no traceback needs to be extracte. It also allows the
232 caller to supply a different namespace for the interpolation to occur than
233 its own."""
234
235 def __init__(self, format,globals,locals=None,
236 codec='utf_8',encoding_errors='backslashreplace'):
237 """ItplNS(format,globals[,locals]) -> interpolating string instance.
238
239 This constructor, besides a format string, takes a globals dictionary
240 and optionally a locals (which defaults to globals if not provided).
241
242 For further details, see the Itpl constructor."""
243
244 if locals is None:
245 locals = globals
246 self.globals = globals
247 self.locals = locals
248 Itpl.__init__(self,format,codec,encoding_errors)
249
250 def __str__(self):
251 """Evaluate and substitute the appropriate parts of the string."""
252 return self._str(self.globals,self.locals)
253
254 def __repr__(self):
255 return "<ItplNS %s >" % repr(self.format)
256
257 # utilities for fast printing
258 def itpl(text): return str(Itpl(text))
259 def printpl(text): print itpl(text)
260 # versions with namespace
261 def itplns(text,globals,locals=None): return str(ItplNS(text,globals,locals))
262 def printplns(text,globals,locals=None): print itplns(text,globals,locals)
263
264 class ItplFile:
265 """A file object that filters each write() through an interpolator."""
266 def __init__(self, file): self.file = file
267 def __repr__(self): return "<interpolated " + repr(self.file) + ">"
268 def __getattr__(self, attr): return getattr(self.file, attr)
269 def write(self, text): self.file.write(str(Itpl(text)))
270
271 def filter(file=sys.stdout):
272 """Return an ItplFile that filters writes to the given file object.
273
274 'file = filter(file)' replaces 'file' with a filtered object that
275 has a write() method. When called with no argument, this creates
276 a filter to sys.stdout."""
277 return ItplFile(file)
278
279 def unfilter(ifile=None):
280 """Return the original file that corresponds to the given ItplFile.
281
282 'file = unfilter(file)' undoes the effect of 'file = filter(file)'.
283 'sys.stdout = unfilter()' undoes the effect of 'sys.stdout = filter()'."""
284 return ifile and ifile.file or sys.stdout.file
1 # -*- coding: utf-8 -*-
2 """String interpolation for Python (by Ka-Ping Yee, 14 Feb 2000).
3
4 This module lets you quickly and conveniently interpolate values into
5 strings (in the flavour of Perl or Tcl, but with less extraneous
6 punctuation). You get a bit more power than in the other languages,
7 because this module allows subscripting, slicing, function calls,
8 attribute lookup, or arbitrary expressions. Variables and expressions
9 are evaluated in the namespace of the caller.
10
11 The itpl() function returns the result of interpolating a string, and
12 printpl() prints out an interpolated string. Here are some examples:
13
14 from Itpl import printpl
15 printpl("Here is a $string.")
16 printpl("Here is a $module.member.")
17 printpl("Here is an $object.member.")
18 printpl("Here is a $functioncall(with, arguments).")
19 printpl("Here is an ${arbitrary + expression}.")
20 printpl("Here is an $array[3] member.")
21 printpl("Here is a $dictionary['member'].")
22
23 The filter() function filters a file object so that output through it
24 is interpolated. This lets you produce the illusion that Python knows
25 how to do interpolation:
26
27 import Itpl
28 sys.stdout = Itpl.filter()
29 f = "fancy"
30 print "Isn't this $f?"
31 print "Standard output has been replaced with a $sys.stdout object."
32 sys.stdout = Itpl.unfilter()
33 print "Okay, back $to $normal."
34
35 Under the hood, the Itpl class represents a string that knows how to
36 interpolate values. An instance of the class parses the string once
37 upon initialization; the evaluation and substitution can then be done
38 each time the instance is evaluated with str(instance). For example:
39
40 from Itpl import Itpl
41 s = Itpl("Here is $foo.")
42 foo = 5
43 print str(s)
44 foo = "bar"
45 print str(s)
46
47 $Id: Itpl.py 2918 2007-12-31 14:34:47Z vivainio $
48 """ # ' -> close an open quote for stupid emacs
49
50 #*****************************************************************************
51 #
52 # Copyright (c) 2001 Ka-Ping Yee <ping@lfw.org>
53 #
54 #
55 # Published under the terms of the MIT license, hereby reproduced:
56 #
57 # Permission is hereby granted, free of charge, to any person obtaining a copy
58 # of this software and associated documentation files (the "Software"), to
59 # deal in the Software without restriction, including without limitation the
60 # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
61 # sell copies of the Software, and to permit persons to whom the Software is
62 # furnished to do so, subject to the following conditions:
63 #
64 # The above copyright notice and this permission notice shall be included in
65 # all copies or substantial portions of the Software.
66 #
67 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
68 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
69 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
70 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
71 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
72 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
73 # IN THE SOFTWARE.
74 #
75 #*****************************************************************************
76
77 __author__ = 'Ka-Ping Yee <ping@lfw.org>'
78 __license__ = 'MIT'
79
80 import string
81 import sys
82 from tokenize import tokenprog
83 from types import StringType
84
85 class ItplError(ValueError):
86 def __init__(self, text, pos):
87 self.text = text
88 self.pos = pos
89 def __str__(self):
90 return "unfinished expression in %s at char %d" % (
91 repr(self.text), self.pos)
92
93 def matchorfail(text, pos):
94 match = tokenprog.match(text, pos)
95 if match is None:
96 raise ItplError(text, pos)
97 return match, match.end()
98
99 class Itpl:
100 """Class representing a string with interpolation abilities.
101
102 Upon creation, an instance works out what parts of the format
103 string are literal and what parts need to be evaluated. The
104 evaluation and substitution happens in the namespace of the
105 caller when str(instance) is called."""
106
107 def __init__(self, format,codec=sys.stdin.encoding,encoding_errors='backslashreplace'):
108 """The single mandatory argument to this constructor is a format
109 string.
110
111 The format string is parsed according to the following rules:
112
113 1. A dollar sign and a name, possibly followed by any of:
114 - an open-paren, and anything up to the matching paren
115 - an open-bracket, and anything up to the matching bracket
116 - a period and a name
117 any number of times, is evaluated as a Python expression.
118
119 2. A dollar sign immediately followed by an open-brace, and
120 anything up to the matching close-brace, is evaluated as
121 a Python expression.
122
123 3. Outside of the expressions described in the above two rules,
124 two dollar signs in a row give you one literal dollar sign.
125
126 Optional arguments:
127
128 - codec('utf_8'): a string containing the name of a valid Python
129 codec.
130
131 - encoding_errors('backslashreplace'): a string with a valid error handling
132 policy. See the codecs module documentation for details.
133
134 These are used to encode the format string if a call to str() fails on
135 the expanded result."""
136
137 if not isinstance(format,basestring):
138 raise TypeError, "needs string initializer"
139 self.format = format
140 self.codec = codec
141 self.encoding_errors = encoding_errors
142
143 namechars = "abcdefghijklmnopqrstuvwxyz" \
144 "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
145 chunks = []
146 pos = 0
147
148 while 1:
149 dollar = string.find(format, "$", pos)
150 if dollar < 0: break
151 nextchar = format[dollar+1]
152
153 if nextchar == "{":
154 chunks.append((0, format[pos:dollar]))
155 pos, level = dollar+2, 1
156 while level:
157 match, pos = matchorfail(format, pos)
158 tstart, tend = match.regs[3]
159 token = format[tstart:tend]
160 if token == "{": level = level+1
161 elif token == "}": level = level-1
162 chunks.append((1, format[dollar+2:pos-1]))
163
164 elif nextchar in namechars:
165 chunks.append((0, format[pos:dollar]))
166 match, pos = matchorfail(format, dollar+1)
167 while pos < len(format):
168 if format[pos] == "." and \
169 pos+1 < len(format) and format[pos+1] in namechars:
170 match, pos = matchorfail(format, pos+1)
171 elif format[pos] in "([":
172 pos, level = pos+1, 1
173 while level:
174 match, pos = matchorfail(format, pos)
175 tstart, tend = match.regs[3]
176 token = format[tstart:tend]
177 if token[0] in "([": level = level+1
178 elif token[0] in ")]": level = level-1
179 else: break
180 chunks.append((1, format[dollar+1:pos]))
181
182 else:
183 chunks.append((0, format[pos:dollar+1]))
184 pos = dollar + 1 + (nextchar == "$")
185
186 if pos < len(format): chunks.append((0, format[pos:]))
187 self.chunks = chunks
188
189 def __repr__(self):
190 return "<Itpl %s >" % repr(self.format)
191
192 def _str(self,glob,loc):
193 """Evaluate to a string in the given globals/locals.
194
195 The final output is built by calling str(), but if this fails, the
196 result is encoded with the instance's codec and error handling policy,
197 via a call to out.encode(self.codec,self.encoding_errors)"""
198 result = []
199 app = result.append
200 for live, chunk in self.chunks:
201 if live:
202 val = eval(chunk,glob,loc)
203 try:
204 app(str(val))
205 except UnicodeEncodeError:
206 app(unicode(val))
207
208 else: app(chunk)
209 out = ''.join(result)
210 try:
211 return str(out)
212 except UnicodeError:
213 return out.encode(self.codec,self.encoding_errors)
214
215 def __str__(self):
216 """Evaluate and substitute the appropriate parts of the string."""
217
218 # We need to skip enough frames to get to the actual caller outside of
219 # Itpl.
220 frame = sys._getframe(1)
221 while frame.f_globals["__name__"] == __name__: frame = frame.f_back
222 loc, glob = frame.f_locals, frame.f_globals
223
224 return self._str(glob,loc)
225
226 class ItplNS(Itpl):
227 """Class representing a string with interpolation abilities.
228
229 This inherits from Itpl, but at creation time a namespace is provided
230 where the evaluation will occur. The interpolation becomes a bit more
231 efficient, as no traceback needs to be extracte. It also allows the
232 caller to supply a different namespace for the interpolation to occur than
233 its own."""
234
235 def __init__(self, format,globals,locals=None,
236 codec='utf_8',encoding_errors='backslashreplace'):
237 """ItplNS(format,globals[,locals]) -> interpolating string instance.
238
239 This constructor, besides a format string, takes a globals dictionary
240 and optionally a locals (which defaults to globals if not provided).
241
242 For further details, see the Itpl constructor."""
243
244 if locals is None:
245 locals = globals
246 self.globals = globals
247 self.locals = locals
248 Itpl.__init__(self,format,codec,encoding_errors)
249
250 def __str__(self):
251 """Evaluate and substitute the appropriate parts of the string."""
252 return self._str(self.globals,self.locals)
253
254 def __repr__(self):
255 return "<ItplNS %s >" % repr(self.format)
256
257 # utilities for fast printing
258 def itpl(text): return str(Itpl(text))
259 def printpl(text): print itpl(text)
260 # versions with namespace
261 def itplns(text,globals,locals=None): return str(ItplNS(text,globals,locals))
262 def printplns(text,globals,locals=None): print itplns(text,globals,locals)
263
264 class ItplFile:
265 """A file object that filters each write() through an interpolator."""
266 def __init__(self, file): self.file = file
267 def __repr__(self): return "<interpolated " + repr(self.file) + ">"
268 def __getattr__(self, attr): return getattr(self.file, attr)
269 def write(self, text): self.file.write(str(Itpl(text)))
270
271 def filter(file=sys.stdout):
272 """Return an ItplFile that filters writes to the given file object.
273
274 'file = filter(file)' replaces 'file' with a filtered object that
275 has a write() method. When called with no argument, this creates
276 a filter to sys.stdout."""
277 return ItplFile(file)
278
279 def unfilter(ifile=None):
280 """Return the original file that corresponds to the given ItplFile.
281
282 'file = unfilter(file)' undoes the effect of 'file = filter(file)'.
283 'sys.stdout = unfilter()' undoes the effect of 'sys.stdout = filter()'."""
284 return ifile and ifile.file or sys.stdout.file
This diff has been collapsed as it changes many lines, (540 lines changed) Show them Hide them
@@ -1,270 +1,270 b''
1 # -*- coding: utf-8 -*-
2 """
3 Logger class for IPython's logging facilities.
4
5 $Id: Logger.py 2875 2007-11-26 08:37:39Z fperez $
6 """
7
8 #*****************************************************************************
9 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and
10 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #*****************************************************************************
15
16 #****************************************************************************
17 # Modules and globals
18
19 from IPython import Release
20 __author__ = '%s <%s>\n%s <%s>' % \
21 ( Release.authors['Janko'] + Release.authors['Fernando'] )
22 __license__ = Release.license
23
24 # Python standard modules
25 import glob
26 import os
27 import time
28
29 #****************************************************************************
30 # FIXME: This class isn't a mixin anymore, but it still needs attributes from
31 # ipython and does input cache management. Finish cleanup later...
32
33 class Logger(object):
34 """A Logfile class with different policies for file creation"""
35
36 def __init__(self,shell,logfname='Logger.log',loghead='',logmode='over'):
37
38 self._i00,self._i,self._ii,self._iii = '','','',''
39
40 # this is the full ipython instance, we need some attributes from it
41 # which won't exist until later. What a mess, clean up later...
42 self.shell = shell
43
44 self.logfname = logfname
45 self.loghead = loghead
46 self.logmode = logmode
47 self.logfile = None
48
49 # Whether to log raw or processed input
50 self.log_raw_input = False
51
52 # whether to also log output
53 self.log_output = False
54
55 # whether to put timestamps before each log entry
56 self.timestamp = False
57
58 # activity control flags
59 self.log_active = False
60
61 # logmode is a validated property
62 def _set_mode(self,mode):
63 if mode not in ['append','backup','global','over','rotate']:
64 raise ValueError,'invalid log mode %s given' % mode
65 self._logmode = mode
66
67 def _get_mode(self):
68 return self._logmode
69
70 logmode = property(_get_mode,_set_mode)
71
72 def logstart(self,logfname=None,loghead=None,logmode=None,
73 log_output=False,timestamp=False,log_raw_input=False):
74 """Generate a new log-file with a default header.
75
76 Raises RuntimeError if the log has already been started"""
77
78 if self.logfile is not None:
79 raise RuntimeError('Log file is already active: %s' %
80 self.logfname)
81
82 self.log_active = True
83
84 # The parameters can override constructor defaults
85 if logfname is not None: self.logfname = logfname
86 if loghead is not None: self.loghead = loghead
87 if logmode is not None: self.logmode = logmode
88
89 # Parameters not part of the constructor
90 self.timestamp = timestamp
91 self.log_output = log_output
92 self.log_raw_input = log_raw_input
93
94 # init depending on the log mode requested
95 isfile = os.path.isfile
96 logmode = self.logmode
97
98 if logmode == 'append':
99 self.logfile = open(self.logfname,'a')
100
101 elif logmode == 'backup':
102 if isfile(self.logfname):
103 backup_logname = self.logfname+'~'
104 # Manually remove any old backup, since os.rename may fail
105 # under Windows.
106 if isfile(backup_logname):
107 os.remove(backup_logname)
108 os.rename(self.logfname,backup_logname)
109 self.logfile = open(self.logfname,'w')
110
111 elif logmode == 'global':
112 self.logfname = os.path.join(self.shell.home_dir,self.logfname)
113 self.logfile = open(self.logfname, 'a')
114
115 elif logmode == 'over':
116 if isfile(self.logfname):
117 os.remove(self.logfname)
118 self.logfile = open(self.logfname,'w')
119
120 elif logmode == 'rotate':
121 if isfile(self.logfname):
122 if isfile(self.logfname+'.001~'):
123 old = glob.glob(self.logfname+'.*~')
124 old.sort()
125 old.reverse()
126 for f in old:
127 root, ext = os.path.splitext(f)
128 num = int(ext[1:-1])+1
129 os.rename(f, root+'.'+`num`.zfill(3)+'~')
130 os.rename(self.logfname, self.logfname+'.001~')
131 self.logfile = open(self.logfname,'w')
132
133 if logmode != 'append':
134 self.logfile.write(self.loghead)
135
136 self.logfile.flush()
137
138 def switch_log(self,val):
139 """Switch logging on/off. val should be ONLY a boolean."""
140
141 if val not in [False,True,0,1]:
142 raise ValueError, \
143 'Call switch_log ONLY with a boolean argument, not with:',val
144
145 label = {0:'OFF',1:'ON',False:'OFF',True:'ON'}
146
147 if self.logfile is None:
148 print """
149 Logging hasn't been started yet (use logstart for that).
150
151 %logon/%logoff are for temporarily starting and stopping logging for a logfile
152 which already exists. But you must first start the logging process with
153 %logstart (optionally giving a logfile name)."""
154
155 else:
156 if self.log_active == val:
157 print 'Logging is already',label[val]
158 else:
159 print 'Switching logging',label[val]
160 self.log_active = not self.log_active
161 self.log_active_out = self.log_active
162
163 def logstate(self):
164 """Print a status message about the logger."""
165 if self.logfile is None:
166 print 'Logging has not been activated.'
167 else:
168 state = self.log_active and 'active' or 'temporarily suspended'
169 print 'Filename :',self.logfname
170 print 'Mode :',self.logmode
171 print 'Output logging :',self.log_output
172 print 'Raw input log :',self.log_raw_input
173 print 'Timestamping :',self.timestamp
174 print 'State :',state
175
176 def log(self,line_ori,line_mod,continuation=None):
177 """Write the line to a log and create input cache variables _i*.
178
179 Inputs:
180
181 - line_ori: unmodified input line from the user. This is not
182 necessarily valid Python.
183
184 - line_mod: possibly modified input, such as the transformations made
185 by input prefilters or input handlers of various kinds. This should
186 always be valid Python.
187
188 - continuation: if True, indicates this is part of multi-line input."""
189
190 # update the auto _i tables
191 #print '***logging line',line_mod # dbg
192 #print '***cache_count', self.shell.outputcache.prompt_count # dbg
193 try:
194 input_hist = self.shell.user_ns['_ih']
195 except:
196 print 'userns:',self.shell.user_ns.keys()
197 return
198
199 out_cache = self.shell.outputcache
200
201 # add blank lines if the input cache fell out of sync.
202 if out_cache.do_full_cache and \
203 out_cache.prompt_count +1 > len(input_hist):
204 input_hist.extend(['\n'] * (out_cache.prompt_count - len(input_hist)))
205
206 if not continuation and line_mod:
207 self._iii = self._ii
208 self._ii = self._i
209 self._i = self._i00
210 # put back the final \n of every input line
211 self._i00 = line_mod+'\n'
212 #print 'Logging input:<%s>' % line_mod # dbg
213 input_hist.append(self._i00)
214 #print '---[%s]' % (len(input_hist)-1,) # dbg
215
216 # hackish access to top-level namespace to create _i1,_i2... dynamically
217 to_main = {'_i':self._i,'_ii':self._ii,'_iii':self._iii}
218 if self.shell.outputcache.do_full_cache:
219 in_num = self.shell.outputcache.prompt_count
220
221 # but if the opposite is true (a macro can produce multiple inputs
222 # with no output display called), then bring the output counter in
223 # sync:
224 last_num = len(input_hist)-1
225 if in_num != last_num:
226 in_num = self.shell.outputcache.prompt_count = last_num
227 new_i = '_i%s' % in_num
228 if continuation:
229 self._i00 = '%s%s\n' % (self.shell.user_ns[new_i],line_mod)
230 input_hist[in_num] = self._i00
231 to_main[new_i] = self._i00
232 self.shell.user_ns.update(to_main)
233
234 # Write the log line, but decide which one according to the
235 # log_raw_input flag, set when the log is started.
236 if self.log_raw_input:
237 self.log_write(line_ori)
238 else:
239 self.log_write(line_mod)
240
241 def log_write(self,data,kind='input'):
242 """Write data to the log file, if active"""
243
244 #print 'data: %r' % data # dbg
245 if self.log_active and data:
246 write = self.logfile.write
247 if kind=='input':
248 if self.timestamp:
249 write(time.strftime('# %a, %d %b %Y %H:%M:%S\n',
250 time.localtime()))
251 write('%s\n' % data)
252 elif kind=='output' and self.log_output:
253 odata = '\n'.join(['#[Out]# %s' % s
254 for s in data.split('\n')])
255 write('%s\n' % odata)
256 self.logfile.flush()
257
258 def logstop(self):
259 """Fully stop logging and close log file.
260
261 In order to start logging again, a new logstart() call needs to be
262 made, possibly (though not necessarily) with a new filename, mode and
263 other options."""
264
265 self.logfile.close()
266 self.logfile = None
267 self.log_active = False
268
269 # For backwards compatibility, in case anyone was using this.
270 close_log = logstop
1 # -*- coding: utf-8 -*-
2 """
3 Logger class for IPython's logging facilities.
4
5 $Id: Logger.py 2875 2007-11-26 08:37:39Z fperez $
6 """
7
8 #*****************************************************************************
9 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and
10 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #*****************************************************************************
15
16 #****************************************************************************
17 # Modules and globals
18
19 from IPython import Release
20 __author__ = '%s <%s>\n%s <%s>' % \
21 ( Release.authors['Janko'] + Release.authors['Fernando'] )
22 __license__ = Release.license
23
24 # Python standard modules
25 import glob
26 import os
27 import time
28
29 #****************************************************************************
30 # FIXME: This class isn't a mixin anymore, but it still needs attributes from
31 # ipython and does input cache management. Finish cleanup later...
32
33 class Logger(object):
34 """A Logfile class with different policies for file creation"""
35
36 def __init__(self,shell,logfname='Logger.log',loghead='',logmode='over'):
37
38 self._i00,self._i,self._ii,self._iii = '','','',''
39
40 # this is the full ipython instance, we need some attributes from it
41 # which won't exist until later. What a mess, clean up later...
42 self.shell = shell
43
44 self.logfname = logfname
45 self.loghead = loghead
46 self.logmode = logmode
47 self.logfile = None
48
49 # Whether to log raw or processed input
50 self.log_raw_input = False
51
52 # whether to also log output
53 self.log_output = False
54
55 # whether to put timestamps before each log entry
56 self.timestamp = False
57
58 # activity control flags
59 self.log_active = False
60
61 # logmode is a validated property
62 def _set_mode(self,mode):
63 if mode not in ['append','backup','global','over','rotate']:
64 raise ValueError,'invalid log mode %s given' % mode
65 self._logmode = mode
66
67 def _get_mode(self):
68 return self._logmode
69
70 logmode = property(_get_mode,_set_mode)
71
72 def logstart(self,logfname=None,loghead=None,logmode=None,
73 log_output=False,timestamp=False,log_raw_input=False):
74 """Generate a new log-file with a default header.
75
76 Raises RuntimeError if the log has already been started"""
77
78 if self.logfile is not None:
79 raise RuntimeError('Log file is already active: %s' %
80 self.logfname)
81
82 self.log_active = True
83
84 # The parameters can override constructor defaults
85 if logfname is not None: self.logfname = logfname
86 if loghead is not None: self.loghead = loghead
87 if logmode is not None: self.logmode = logmode
88
89 # Parameters not part of the constructor
90 self.timestamp = timestamp
91 self.log_output = log_output
92 self.log_raw_input = log_raw_input
93
94 # init depending on the log mode requested
95 isfile = os.path.isfile
96 logmode = self.logmode
97
98 if logmode == 'append':
99 self.logfile = open(self.logfname,'a')
100
101 elif logmode == 'backup':
102 if isfile(self.logfname):
103 backup_logname = self.logfname+'~'
104 # Manually remove any old backup, since os.rename may fail
105 # under Windows.
106 if isfile(backup_logname):
107 os.remove(backup_logname)
108 os.rename(self.logfname,backup_logname)
109 self.logfile = open(self.logfname,'w')
110
111 elif logmode == 'global':
112 self.logfname = os.path.join(self.shell.home_dir,self.logfname)
113 self.logfile = open(self.logfname, 'a')
114
115 elif logmode == 'over':
116 if isfile(self.logfname):
117 os.remove(self.logfname)
118 self.logfile = open(self.logfname,'w')
119
120 elif logmode == 'rotate':
121 if isfile(self.logfname):
122 if isfile(self.logfname+'.001~'):
123 old = glob.glob(self.logfname+'.*~')
124 old.sort()
125 old.reverse()
126 for f in old:
127 root, ext = os.path.splitext(f)
128 num = int(ext[1:-1])+1
129 os.rename(f, root+'.'+`num`.zfill(3)+'~')
130 os.rename(self.logfname, self.logfname+'.001~')
131 self.logfile = open(self.logfname,'w')
132
133 if logmode != 'append':
134 self.logfile.write(self.loghead)
135
136 self.logfile.flush()
137
138 def switch_log(self,val):
139 """Switch logging on/off. val should be ONLY a boolean."""
140
141 if val not in [False,True,0,1]:
142 raise ValueError, \
143 'Call switch_log ONLY with a boolean argument, not with:',val
144
145 label = {0:'OFF',1:'ON',False:'OFF',True:'ON'}
146
147 if self.logfile is None:
148 print """
149 Logging hasn't been started yet (use logstart for that).
150
151 %logon/%logoff are for temporarily starting and stopping logging for a logfile
152 which already exists. But you must first start the logging process with
153 %logstart (optionally giving a logfile name)."""
154
155 else:
156 if self.log_active == val:
157 print 'Logging is already',label[val]
158 else:
159 print 'Switching logging',label[val]
160 self.log_active = not self.log_active
161 self.log_active_out = self.log_active
162
163 def logstate(self):
164 """Print a status message about the logger."""
165 if self.logfile is None:
166 print 'Logging has not been activated.'
167 else:
168 state = self.log_active and 'active' or 'temporarily suspended'
169 print 'Filename :',self.logfname
170 print 'Mode :',self.logmode
171 print 'Output logging :',self.log_output
172 print 'Raw input log :',self.log_raw_input
173 print 'Timestamping :',self.timestamp
174 print 'State :',state
175
176 def log(self,line_ori,line_mod,continuation=None):
177 """Write the line to a log and create input cache variables _i*.
178
179 Inputs:
180
181 - line_ori: unmodified input line from the user. This is not
182 necessarily valid Python.
183
184 - line_mod: possibly modified input, such as the transformations made
185 by input prefilters or input handlers of various kinds. This should
186 always be valid Python.
187
188 - continuation: if True, indicates this is part of multi-line input."""
189
190 # update the auto _i tables
191 #print '***logging line',line_mod # dbg
192 #print '***cache_count', self.shell.outputcache.prompt_count # dbg
193 try:
194 input_hist = self.shell.user_ns['_ih']
195 except:
196 print 'userns:',self.shell.user_ns.keys()
197 return
198
199 out_cache = self.shell.outputcache
200
201 # add blank lines if the input cache fell out of sync.
202 if out_cache.do_full_cache and \
203 out_cache.prompt_count +1 > len(input_hist):
204 input_hist.extend(['\n'] * (out_cache.prompt_count - len(input_hist)))
205
206 if not continuation and line_mod:
207 self._iii = self._ii
208 self._ii = self._i
209 self._i = self._i00
210 # put back the final \n of every input line
211 self._i00 = line_mod+'\n'
212 #print 'Logging input:<%s>' % line_mod # dbg
213 input_hist.append(self._i00)
214 #print '---[%s]' % (len(input_hist)-1,) # dbg
215
216 # hackish access to top-level namespace to create _i1,_i2... dynamically
217 to_main = {'_i':self._i,'_ii':self._ii,'_iii':self._iii}
218 if self.shell.outputcache.do_full_cache:
219 in_num = self.shell.outputcache.prompt_count
220
221 # but if the opposite is true (a macro can produce multiple inputs
222 # with no output display called), then bring the output counter in
223 # sync:
224 last_num = len(input_hist)-1
225 if in_num != last_num:
226 in_num = self.shell.outputcache.prompt_count = last_num
227 new_i = '_i%s' % in_num
228 if continuation:
229 self._i00 = '%s%s\n' % (self.shell.user_ns[new_i],line_mod)
230 input_hist[in_num] = self._i00
231 to_main[new_i] = self._i00
232 self.shell.user_ns.update(to_main)
233
234 # Write the log line, but decide which one according to the
235 # log_raw_input flag, set when the log is started.
236 if self.log_raw_input:
237 self.log_write(line_ori)
238 else:
239 self.log_write(line_mod)
240
241 def log_write(self,data,kind='input'):
242 """Write data to the log file, if active"""
243
244 #print 'data: %r' % data # dbg
245 if self.log_active and data:
246 write = self.logfile.write
247 if kind=='input':
248 if self.timestamp:
249 write(time.strftime('# %a, %d %b %Y %H:%M:%S\n',
250 time.localtime()))
251 write('%s\n' % data)
252 elif kind=='output' and self.log_output:
253 odata = '\n'.join(['#[Out]# %s' % s
254 for s in data.split('\n')])
255 write('%s\n' % odata)
256 self.logfile.flush()
257
258 def logstop(self):
259 """Fully stop logging and close log file.
260
261 In order to start logging again, a new logstart() call needs to be
262 made, possibly (though not necessarily) with a new filename, mode and
263 other options."""
264
265 self.logfile.close()
266 self.logfile = None
267 self.log_active = False
268
269 # For backwards compatibility, in case anyone was using this.
270 close_log = logstop
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now