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 (<)</h3> | |
|
78 | ||
|
79 | <h3>Move the cursor one column to the right (>)</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 (<)</h3> | |
|
78 | ||
|
79 | <h3>Move the cursor one column to the right (>)</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