##// END OF EJS Templates
First draft of refactored ipstruct.py.
Brian Granger -
Show More
@@ -1,177 +1,177 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tools for coloring text in ANSI terminals.
2 """Tools for coloring text in ANSI terminals.
3 """
3 """
4
4
5 #*****************************************************************************
5 #*****************************************************************************
6 # Copyright (C) 2002-2006 Fernando Perez. <fperez@colorado.edu>
6 # Copyright (C) 2002-2006 Fernando Perez. <fperez@colorado.edu>
7 #
7 #
8 # Distributed under the terms of the BSD License. The full license is in
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
9 # the file COPYING, distributed as part of this software.
10 #*****************************************************************************
10 #*****************************************************************************
11
11
12 __all__ = ['TermColors','InputTermColors','ColorScheme','ColorSchemeTable']
12 __all__ = ['TermColors','InputTermColors','ColorScheme','ColorSchemeTable']
13
13
14 import os
14 import os
15
15
16 from IPython.utils.ipstruct import Struct
16 from IPython.utils.ipstruct import Struct
17
17
18 def make_color_table(in_class):
18 def make_color_table(in_class):
19 """Build a set of color attributes in a class.
19 """Build a set of color attributes in a class.
20
20
21 Helper function for building the *TermColors classes."""
21 Helper function for building the *TermColors classes."""
22
22
23 color_templates = (
23 color_templates = (
24 # Dark colors
24 # Dark colors
25 ("Black" , "0;30"),
25 ("Black" , "0;30"),
26 ("Red" , "0;31"),
26 ("Red" , "0;31"),
27 ("Green" , "0;32"),
27 ("Green" , "0;32"),
28 ("Brown" , "0;33"),
28 ("Brown" , "0;33"),
29 ("Blue" , "0;34"),
29 ("Blue" , "0;34"),
30 ("Purple" , "0;35"),
30 ("Purple" , "0;35"),
31 ("Cyan" , "0;36"),
31 ("Cyan" , "0;36"),
32 ("LightGray" , "0;37"),
32 ("LightGray" , "0;37"),
33 # Light colors
33 # Light colors
34 ("DarkGray" , "1;30"),
34 ("DarkGray" , "1;30"),
35 ("LightRed" , "1;31"),
35 ("LightRed" , "1;31"),
36 ("LightGreen" , "1;32"),
36 ("LightGreen" , "1;32"),
37 ("Yellow" , "1;33"),
37 ("Yellow" , "1;33"),
38 ("LightBlue" , "1;34"),
38 ("LightBlue" , "1;34"),
39 ("LightPurple" , "1;35"),
39 ("LightPurple" , "1;35"),
40 ("LightCyan" , "1;36"),
40 ("LightCyan" , "1;36"),
41 ("White" , "1;37"),
41 ("White" , "1;37"),
42 # Blinking colors. Probably should not be used in anything serious.
42 # Blinking colors. Probably should not be used in anything serious.
43 ("BlinkBlack" , "5;30"),
43 ("BlinkBlack" , "5;30"),
44 ("BlinkRed" , "5;31"),
44 ("BlinkRed" , "5;31"),
45 ("BlinkGreen" , "5;32"),
45 ("BlinkGreen" , "5;32"),
46 ("BlinkYellow" , "5;33"),
46 ("BlinkYellow" , "5;33"),
47 ("BlinkBlue" , "5;34"),
47 ("BlinkBlue" , "5;34"),
48 ("BlinkPurple" , "5;35"),
48 ("BlinkPurple" , "5;35"),
49 ("BlinkCyan" , "5;36"),
49 ("BlinkCyan" , "5;36"),
50 ("BlinkLightGray", "5;37"),
50 ("BlinkLightGray", "5;37"),
51 )
51 )
52
52
53 for name,value in color_templates:
53 for name,value in color_templates:
54 setattr(in_class,name,in_class._base % value)
54 setattr(in_class,name,in_class._base % value)
55
55
56 class TermColors:
56 class TermColors:
57 """Color escape sequences.
57 """Color escape sequences.
58
58
59 This class defines the escape sequences for all the standard (ANSI?)
59 This class defines the escape sequences for all the standard (ANSI?)
60 colors in terminals. Also defines a NoColor escape which is just the null
60 colors in terminals. Also defines a NoColor escape which is just the null
61 string, suitable for defining 'dummy' color schemes in terminals which get
61 string, suitable for defining 'dummy' color schemes in terminals which get
62 confused by color escapes.
62 confused by color escapes.
63
63
64 This class should be used as a mixin for building color schemes."""
64 This class should be used as a mixin for building color schemes."""
65
65
66 NoColor = '' # for color schemes in color-less terminals.
66 NoColor = '' # for color schemes in color-less terminals.
67 Normal = '\033[0m' # Reset normal coloring
67 Normal = '\033[0m' # Reset normal coloring
68 _base = '\033[%sm' # Template for all other colors
68 _base = '\033[%sm' # Template for all other colors
69
69
70 # Build the actual color table as a set of class attributes:
70 # Build the actual color table as a set of class attributes:
71 make_color_table(TermColors)
71 make_color_table(TermColors)
72
72
73 class InputTermColors:
73 class InputTermColors:
74 """Color escape sequences for input prompts.
74 """Color escape sequences for input prompts.
75
75
76 This class is similar to TermColors, but the escapes are wrapped in \001
76 This class is similar to TermColors, but the escapes are wrapped in \001
77 and \002 so that readline can properly know the length of each line and
77 and \002 so that readline can properly know the length of each line and
78 can wrap lines accordingly. Use this class for any colored text which
78 can wrap lines accordingly. Use this class for any colored text which
79 needs to be used in input prompts, such as in calls to raw_input().
79 needs to be used in input prompts, such as in calls to raw_input().
80
80
81 This class defines the escape sequences for all the standard (ANSI?)
81 This class defines the escape sequences for all the standard (ANSI?)
82 colors in terminals. Also defines a NoColor escape which is just the null
82 colors in terminals. Also defines a NoColor escape which is just the null
83 string, suitable for defining 'dummy' color schemes in terminals which get
83 string, suitable for defining 'dummy' color schemes in terminals which get
84 confused by color escapes.
84 confused by color escapes.
85
85
86 This class should be used as a mixin for building color schemes."""
86 This class should be used as a mixin for building color schemes."""
87
87
88 NoColor = '' # for color schemes in color-less terminals.
88 NoColor = '' # for color schemes in color-less terminals.
89
89
90 if os.name == 'nt' and os.environ.get('TERM','dumb') == 'emacs':
90 if os.name == 'nt' and os.environ.get('TERM','dumb') == 'emacs':
91 # (X)emacs on W32 gets confused with \001 and \002 so we remove them
91 # (X)emacs on W32 gets confused with \001 and \002 so we remove them
92 Normal = '\033[0m' # Reset normal coloring
92 Normal = '\033[0m' # Reset normal coloring
93 _base = '\033[%sm' # Template for all other colors
93 _base = '\033[%sm' # Template for all other colors
94 else:
94 else:
95 Normal = '\001\033[0m\002' # Reset normal coloring
95 Normal = '\001\033[0m\002' # Reset normal coloring
96 _base = '\001\033[%sm\002' # Template for all other colors
96 _base = '\001\033[%sm\002' # Template for all other colors
97
97
98 # Build the actual color table as a set of class attributes:
98 # Build the actual color table as a set of class attributes:
99 make_color_table(InputTermColors)
99 make_color_table(InputTermColors)
100
100
101 class ColorScheme:
101 class ColorScheme:
102 """Generic color scheme class. Just a name and a Struct."""
102 """Generic color scheme class. Just a name and a Struct."""
103 def __init__(self,__scheme_name_,colordict=None,**colormap):
103 def __init__(self,__scheme_name_,colordict=None,**colormap):
104 self.name = __scheme_name_
104 self.name = __scheme_name_
105 if colordict is None:
105 if colordict is None:
106 self.colors = Struct(**colormap)
106 self.colors = Struct(**colormap)
107 else:
107 else:
108 self.colors = Struct(colordict)
108 self.colors = Struct(colordict)
109
109
110 def copy(self,name=None):
110 def copy(self,name=None):
111 """Return a full copy of the object, optionally renaming it."""
111 """Return a full copy of the object, optionally renaming it."""
112 if name is None:
112 if name is None:
113 name = self.name
113 name = self.name
114 return ColorScheme(name,self.colors.__dict__)
114 return ColorScheme(name, self.colors.dict())
115
115
116 class ColorSchemeTable(dict):
116 class ColorSchemeTable(dict):
117 """General class to handle tables of color schemes.
117 """General class to handle tables of color schemes.
118
118
119 It's basically a dict of color schemes with a couple of shorthand
119 It's basically a dict of color schemes with a couple of shorthand
120 attributes and some convenient methods.
120 attributes and some convenient methods.
121
121
122 active_scheme_name -> obvious
122 active_scheme_name -> obvious
123 active_colors -> actual color table of the active scheme"""
123 active_colors -> actual color table of the active scheme"""
124
124
125 def __init__(self,scheme_list=None,default_scheme=''):
125 def __init__(self,scheme_list=None,default_scheme=''):
126 """Create a table of color schemes.
126 """Create a table of color schemes.
127
127
128 The table can be created empty and manually filled or it can be
128 The table can be created empty and manually filled or it can be
129 created with a list of valid color schemes AND the specification for
129 created with a list of valid color schemes AND the specification for
130 the default active scheme.
130 the default active scheme.
131 """
131 """
132
132
133 # create object attributes to be set later
133 # create object attributes to be set later
134 self.active_scheme_name = ''
134 self.active_scheme_name = ''
135 self.active_colors = None
135 self.active_colors = None
136
136
137 if scheme_list:
137 if scheme_list:
138 if default_scheme == '':
138 if default_scheme == '':
139 raise ValueError,'you must specify the default color scheme'
139 raise ValueError,'you must specify the default color scheme'
140 for scheme in scheme_list:
140 for scheme in scheme_list:
141 self.add_scheme(scheme)
141 self.add_scheme(scheme)
142 self.set_active_scheme(default_scheme)
142 self.set_active_scheme(default_scheme)
143
143
144 def copy(self):
144 def copy(self):
145 """Return full copy of object"""
145 """Return full copy of object"""
146 return ColorSchemeTable(self.values(),self.active_scheme_name)
146 return ColorSchemeTable(self.values(),self.active_scheme_name)
147
147
148 def add_scheme(self,new_scheme):
148 def add_scheme(self,new_scheme):
149 """Add a new color scheme to the table."""
149 """Add a new color scheme to the table."""
150 if not isinstance(new_scheme,ColorScheme):
150 if not isinstance(new_scheme,ColorScheme):
151 raise ValueError,'ColorSchemeTable only accepts ColorScheme instances'
151 raise ValueError,'ColorSchemeTable only accepts ColorScheme instances'
152 self[new_scheme.name] = new_scheme
152 self[new_scheme.name] = new_scheme
153
153
154 def set_active_scheme(self,scheme,case_sensitive=0):
154 def set_active_scheme(self,scheme,case_sensitive=0):
155 """Set the currently active scheme.
155 """Set the currently active scheme.
156
156
157 Names are by default compared in a case-insensitive way, but this can
157 Names are by default compared in a case-insensitive way, but this can
158 be changed by setting the parameter case_sensitive to true."""
158 be changed by setting the parameter case_sensitive to true."""
159
159
160 scheme_names = self.keys()
160 scheme_names = self.keys()
161 if case_sensitive:
161 if case_sensitive:
162 valid_schemes = scheme_names
162 valid_schemes = scheme_names
163 scheme_test = scheme
163 scheme_test = scheme
164 else:
164 else:
165 valid_schemes = [s.lower() for s in scheme_names]
165 valid_schemes = [s.lower() for s in scheme_names]
166 scheme_test = scheme.lower()
166 scheme_test = scheme.lower()
167 try:
167 try:
168 scheme_idx = valid_schemes.index(scheme_test)
168 scheme_idx = valid_schemes.index(scheme_test)
169 except ValueError:
169 except ValueError:
170 raise ValueError,'Unrecognized color scheme: ' + scheme + \
170 raise ValueError,'Unrecognized color scheme: ' + scheme + \
171 '\nValid schemes: '+str(scheme_names).replace("'', ",'')
171 '\nValid schemes: '+str(scheme_names).replace("'', ",'')
172 else:
172 else:
173 active = scheme_names[scheme_idx]
173 active = scheme_names[scheme_idx]
174 self.active_scheme_name = active
174 self.active_scheme_name = active
175 self.active_colors = self[active].colors
175 self.active_colors = self[active].colors
176 # Now allow using '' as an index for the current active scheme
176 # Now allow using '' as an index for the current active scheme
177 self[''] = self[active]
177 self[''] = self[active]
@@ -1,416 +1,417 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Mimic C structs with lots of extra functionality.
2 """Mimic C structs with lots of extra functionality.
3 """
3 """
4
4
5 #*****************************************************************************
5 #*****************************************************************************
6 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
6 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
7 #
7 #
8 # Distributed under the terms of the BSD License. The full license is in
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
9 # the file COPYING, distributed as part of this software.
10 #*****************************************************************************
10 #*****************************************************************************
11
11
12 __all__ = ['Struct']
12 __all__ = ['Struct']
13
13
14 import inspect
14 import types
15 import types
15 import pprint
16 import pprint
16
17
17 from IPython.utils.genutils import list2dict2
18 from IPython.utils.genutils import list2dict2
18
19
19 class Struct:
20 class Struct(object):
20 """Class to mimic C structs but also provide convenient dictionary-like
21 """Class to mimic C structs but also provide convenient dictionary-like
21 functionality.
22 functionality.
22
23
23 Instances can be initialized with a dictionary, a list of key=value pairs
24 Instances can be initialized with a dictionary, a list of key=value pairs
24 or both. If both are present, the dictionary must come first.
25 or both. If both are present, the dictionary must come first.
25
26
26 Because Python classes provide direct assignment to their members, it's
27 Because Python classes provide direct assignment to their members, it's
27 easy to overwrite normal methods (S.copy = 1 would destroy access to
28 easy to overwrite normal methods (S.copy = 1 would destroy access to
28 S.copy()). For this reason, all builtin method names are protected and
29 S.copy()). For this reason, all builtin method names are protected and
29 can't be assigned to. An attempt to do s.copy=1 or s['copy']=1 will raise
30 can't be assigned to. An attempt to do s.copy=1 or s['copy']=1 will raise
30 a KeyError exception. If you really want to, you can bypass this
31 a KeyError exception. If you really want to, you can bypass this
31 protection by directly assigning to __dict__: s.__dict__['copy']=1 will
32 protection by directly assigning to __dict__: s.__dict__['copy']=1 will
32 still work. Doing this will break functionality, though. As in most of
33 still work. Doing this will break functionality, though. As in most of
33 Python, namespace protection is weakly enforced, so feel free to shoot
34 Python, namespace protection is weakly enforced, so feel free to shoot
34 yourself if you really want to.
35 yourself if you really want to.
35
36
36 Note that this class uses more memory and is *much* slower than a regular
37 Note that this class uses more memory and is *much* slower than a regular
37 dictionary, so be careful in situations where memory or performance are
38 dictionary, so be careful in situations where memory or performance are
38 critical. But for day to day use it should behave fine. It is particularly
39 critical. But for day to day use it should behave fine. It is particularly
39 convenient for storing configuration data in programs.
40 convenient for storing configuration data in programs.
40
41
41 +,+=,- and -= are implemented. +/+= do merges (non-destructive updates),
42 +,+=,- and -= are implemented. +/+= do merges (non-destructive updates),
42 -/-= remove keys from the original. See the method descripitions.
43 -/-= remove keys from the original. See the method descripitions.
43
44
44 This class allows a quick access syntax: both s.key and s['key'] are
45 This class allows a quick access syntax: both s.key and s['key'] are
45 valid. This syntax has a limitation: each 'key' has to be explicitly
46 valid. This syntax has a limitation: each 'key' has to be explicitly
46 accessed by its original name. The normal s.key syntax doesn't provide
47 accessed by its original name. The normal s.key syntax doesn't provide
47 access to the keys via variables whose values evaluate to the desired
48 access to the keys via variables whose values evaluate to the desired
48 keys. An example should clarify this:
49 keys. An example should clarify this:
49
50
50 Define a dictionary and initialize both with dict and k=v pairs:
51 Define a dictionary and initialize both with dict and k=v pairs:
51 >>> d={'a':1,'b':2}
52 >>> d={'a':1,'b':2}
52 >>> s=Struct(d,hi=10,ho=20)
53 >>> s=Struct(d,hi=10,ho=20)
53
54
54 The return of __repr__ can be used to create a new instance:
55 The return of __repr__ can be used to create a new instance:
55 >>> s
56 >>> s
56 Struct({'__allownew': True, 'a': 1, 'b': 2, 'hi': 10, 'ho': 20})
57 Struct({'__allownew': True, 'a': 1, 'b': 2, 'hi': 10, 'ho': 20})
57
58
58 Note: the special '__allownew' key is used for internal purposes.
59 Note: the special '__allownew' key is used for internal purposes.
59
60
60 __str__ (called by print) shows it's not quite a regular dictionary:
61 __str__ (called by print) shows it's not quite a regular dictionary:
61 >>> print s
62 >>> print s
62 Struct({'__allownew': True, 'a': 1, 'b': 2, 'hi': 10, 'ho': 20})
63 Struct({'__allownew': True, 'a': 1, 'b': 2, 'hi': 10, 'ho': 20})
63
64
64 Access by explicitly named key with dot notation:
65 Access by explicitly named key with dot notation:
65 >>> s.a
66 >>> s.a
66 1
67 1
67
68
68 Or like a dictionary:
69 Or like a dictionary:
69 >>> s['a']
70 >>> s['a']
70 1
71 1
71
72
72 If you want a variable to hold the key value, only dictionary access works:
73 If you want a variable to hold the key value, only dictionary access works:
73 >>> key='hi'
74 >>> key='hi'
74 >>> s.key
75 >>> s.key
75 Traceback (most recent call last):
76 Traceback (most recent call last):
76 File "<stdin>", line 1, in ?
77 File "<stdin>", line 1, in ?
77 AttributeError: Struct instance has no attribute 'key'
78 AttributeError: Struct instance has no attribute 'key'
78
79
79 >>> s[key]
80 >>> s[key]
80 10
81 10
81
82
82 Another limitation of the s.key syntax (and Struct(key=val)
83 Another limitation of the s.key syntax (and Struct(key=val)
83 initialization): keys can't be numbers. But numeric keys can be used and
84 initialization): keys can't be numbers. But numeric keys can be used and
84 accessed using the dictionary syntax. Again, an example:
85 accessed using the dictionary syntax. Again, an example:
85
86
86 This doesn't work (prompt changed to avoid confusing the test system):
87 This doesn't work (prompt changed to avoid confusing the test system):
87 ->> s=Struct(4='hi')
88 ->> s=Struct(4='hi')
88 Traceback (most recent call last):
89 Traceback (most recent call last):
89 ...
90 ...
90 SyntaxError: keyword can't be an expression
91 SyntaxError: keyword can't be an expression
91
92
92 But this does:
93 But this does:
93 >>> s=Struct()
94 >>> s=Struct()
94 >>> s[4]='hi'
95 >>> s[4]='hi'
95 >>> s
96 >>> s
96 Struct({4: 'hi', '__allownew': True})
97 Struct({4: 'hi', '__allownew': True})
97 >>> s[4]
98 >>> s[4]
98 'hi'
99 'hi'
99 """
100 """
100
101
101 # Attributes to which __setitem__ and __setattr__ will block access.
102 # Attributes to which __setitem__ and __setattr__ will block access.
102 # Note: much of this will be moot in Python 2.2 and will be done in a much
103 # Note: much of this will be moot in Python 2.2 and will be done in a much
103 # cleaner way.
104 # cleaner way.
104 __protected = ('copy dict dictcopy get has_attr has_key items keys '
105 __protected = ('copy dict dictcopy get has_attr has_key items keys '
105 'merge popitem setdefault update values '
106 'merge popitem setdefault update values '
106 '__make_dict __dict_invert ').split()
107 '__make_dict __dict_invert ').split()
107
108
108 def __init__(self,dict=None,**kw):
109 def __init__(self,data=None,**kw):
109 """Initialize with a dictionary, another Struct, or by giving
110 """Initialize with a dictionary, another Struct, or data.
110 explicitly the list of attributes.
111
111
112 Both can be used, but the dictionary must come first:
112 Parameters
113 Struct(dict), Struct(k1=v1,k2=v2) or Struct(dict,k1=v1,k2=v2).
113 ----------
114 data : dict, Struct
115 Initialize with this data.
116 kw : dict
117 Initialize with key, value pairs.
118
119 Examples
120 --------
121
114 """
122 """
115 self.__dict__['__allownew'] = True
123 object.__setattr__(self, '_allownew', True)
116 if dict is None:
124 object.__setattr__(self, '_data',{})
117 dict = {}
125 if data is None:
118 if isinstance(dict,Struct):
126 data = {}
119 dict = dict.dict()
127 if isinstance(data, Struct):
120 elif dict and type(dict) is not types.DictType:
128 data = data.dict()
121 raise TypeError,\
129 elif data and not isinstance(data, dict):
122 'Initialize with a dictionary or key=val pairs.'
130 raise TypeError('initialize with a dict, Struct or key=val pairs')
123 dict.update(kw)
131 data.update(kw)
124 # do the updating by hand to guarantee that we go through the
132 # do the updating by hand to guarantee that we go through the
125 # safety-checked __setitem__
133 # safety-checked __setitem__
126 for k,v in dict.items():
134 for k, v in data.items():
127 self[k] = v
135 self[k] = v
128
129
136
130 def __setitem__(self,key,value):
137 def __setitem__(self, key, value):
131 """Used when struct[key] = val calls are made."""
138 """Used when struct[key] = val calls are made."""
132 if key in Struct.__protected:
139 if isinstance(key, str):
133 raise KeyError,'Key '+`key`+' is a protected key of class Struct.'
140 # I can't simply call hasattr here because it calls getattr, which
134 if not self['__allownew'] and key not in self.__dict__:
141 # calls self.__getattr__, which returns True for keys in
142 # self._data. But I only want keys in the class and in
143 # self.__dict__
144 if key in self.__dict__ or hasattr(Struct, key):
145 raise KeyError(
146 'key %s is a protected key of class Struct.' % key
147 )
148 if not self._allownew and key not in self._data:
135 raise KeyError(
149 raise KeyError(
136 "Can't create unknown attribute %s - Check for typos, or use allow_new_attr to create new attributes!" %
150 "can't create unknown attribute %s. Check for typos, or use allow_new_attr" % key)
137 key)
151 self._data[key] = value
138
139 self.__dict__[key] = value
140
152
141 def __setattr__(self, key, value):
153 def __setattr__(self, key, value):
142 """Used when struct.key = val calls are made."""
154 self.__setitem__(key, value)
143 self.__setitem__(key,value)
155
156 def __getattr__(self, key):
157 try:
158 result = self._data[key]
159 except KeyError:
160 raise AttributeError(key)
161 else:
162 return result
163
164 def __getitem__(self, key):
165 return self._data[key]
144
166
145 def __str__(self):
167 def __str__(self):
146 """Gets called by print."""
168 return 'Struct('+ pprint.pformat(self._data)+')'
147
148 return 'Struct('+ pprint.pformat(self.__dict__)+')'
149
169
150 def __repr__(self):
170 def __repr__(self):
151 """Gets called by repr.
152
153 A Struct can be recreated with S_new=eval(repr(S_old))."""
154 return self.__str__()
171 return self.__str__()
155
172
156 def __getitem__(self,key):
173 def __contains__(self, key):
157 """Allows struct[key] access."""
174 return key in self._data
158 return self.__dict__[key]
159
160 def __contains__(self,key):
161 """Allows use of the 'in' operator.
162
163 Examples:
164 >>> s = Struct(x=1)
165 >>> 'x' in s
166 True
167 >>> 'y' in s
168 False
169 >>> s[4] = None
170 >>> 4 in s
171 True
172 >>> s.z = None
173 >>> 'z' in s
174 True
175 """
176 return key in self.__dict__
177
175
178 def __iadd__(self,other):
176 def __iadd__(self, other):
179 """S += S2 is a shorthand for S.merge(S2)."""
177 """S += S2 is a shorthand for S.merge(S2)."""
180 self.merge(other)
178 self.merge(other)
181 return self
179 return self
182
180
183 def __add__(self,other):
181 def __add__(self,other):
184 """S + S2 -> New Struct made form S and S.merge(S2)"""
182 """S + S2 -> New Struct made from S.merge(S2)"""
185 Sout = self.copy()
183 Sout = self.copy()
186 Sout.merge(other)
184 Sout.merge(other)
187 return Sout
185 return Sout
188
186
189 def __sub__(self,other):
187 def __sub__(self,other):
190 """Return S1-S2, where all keys in S2 have been deleted (if present)
188 """Out of place remove keys from self that are in other."""
191 from S1."""
192 Sout = self.copy()
189 Sout = self.copy()
193 Sout -= other
190 Sout -= other
194 return Sout
191 return Sout
195
192
196 def __isub__(self,other):
193 def __isub__(self,other):
197 """Do in place S = S - S2, meaning all keys in S2 have been deleted
194 """Inplace remove keys from self that are in other."""
198 (if present) from S1."""
199
200 for k in other.keys():
195 for k in other.keys():
201 if self.has_key(k):
196 if self.has_key(k):
202 del self.__dict__[k]
197 del self._data[k]
203
198
204 def __make_dict(self,__loc_data__,**kw):
199 def __make_dict(self,__loc_data__,**kw):
205 "Helper function for update and merge. Return a dict from data."
200 """Helper function for update and merge. Return a dict from data.
206
201 """
207 if __loc_data__ == None:
202 if __loc_data__ == None:
208 dict = {}
203 data = {}
209 elif type(__loc_data__) is types.DictType:
204 elif isinstance(__loc_data__, dict):
210 dict = __loc_data__
205 data = __loc_data__
211 elif isinstance(__loc_data__,Struct):
206 elif isinstance(__loc_data__, Struct):
212 dict = __loc_data__.__dict__
207 data = __loc_data__._data
213 else:
208 else:
214 raise TypeError, 'Update with a dict, a Struct or key=val pairs.'
209 raise TypeError('update with a dict, Struct or key=val pairs')
215 if kw:
210 if kw:
216 dict.update(kw)
211 data.update(kw)
217 return dict
212 return data
218
213
219 def __dict_invert(self,dict):
214 def __dict_invert(self, data):
220 """Helper function for merge. Takes a dictionary whose values are
215 """Helper function for merge.
221 lists and returns a dict. with the elements of each list as keys and
216
222 the original keys as values."""
217 Takes a dictionary whose values are lists and returns a dict with
223
218 the elements of each list as keys and the original keys as values.
219 """
224 outdict = {}
220 outdict = {}
225 for k,lst in dict.items():
221 for k,lst in data.items():
226 if type(lst) is types.StringType:
222 if isinstance(lst, str):
227 lst = lst.split()
223 lst = lst.split()
228 for entry in lst:
224 for entry in lst:
229 outdict[entry] = k
225 outdict[entry] = k
230 return outdict
226 return outdict
231
227
232 def clear(self):
228 def clear(self):
233 """Clear all attributes."""
229 """Clear all attributes."""
234 self.__dict__.clear()
230 self._data.clear()
235
231
236 def copy(self):
232 def copy(self):
237 """Return a (shallow) copy of a Struct."""
233 """Return a (shallow) copy of a Struct."""
238 return Struct(self.__dict__.copy())
234 return Struct(self._data.copy())
239
235
240 def dict(self):
236 def dict(self):
241 """Return the Struct's dictionary."""
237 """Return the Struct's dictionary."""
242 return self.__dict__
238 return self._data
243
239
244 def dictcopy(self):
240 def dictcopy(self):
245 """Return a (shallow) copy of the Struct's dictionary."""
241 """Return a (shallow) copy of the Struct's dictionary."""
246 return self.__dict__.copy()
242 return self._data.copy()
247
243
248 def popitem(self):
244 def popitem(self):
249 """S.popitem() -> (k, v), remove and return some (key, value) pair as
245 """Return (key, value) tuple and remove from Struct.
250 a 2-tuple; but raise KeyError if S is empty."""
246
251 return self.__dict__.popitem()
247 If key is not present raise KeyError.
252
248 """
249 return self._data.popitem()
250
253 def update(self,__loc_data__=None,**kw):
251 def update(self,__loc_data__=None,**kw):
254 """Update (merge) with data from another Struct or from a dictionary.
252 """Update (merge) with data from another Struct or dict.
255 Optionally, one or more key=value pairs can be given at the end for
253
256 direct update."""
254 Parameters
257
255 ----------
256 __loc_data : dict, Struct
257 The new data to add to self.
258 kw : dict
259 Key, value pairs to add to self.
260 """
258 # The funny name __loc_data__ is to prevent a common variable name
261 # The funny name __loc_data__ is to prevent a common variable name
259 # which could be a fieled of a Struct to collide with this
262 # which could be a fieled of a Struct to collide with this
260 # parameter. The problem would arise if the function is called with a
263 # parameter. The problem would arise if the function is called with a
261 # keyword with this same name that a user means to add as a Struct
264 # keyword with this same name that a user means to add as a Struct
262 # field.
265 # field.
263 newdict = Struct.__make_dict(self,__loc_data__,**kw)
266 newdict = self.__make_dict(__loc_data__, **kw)
264 for k,v in newdict.iteritems():
267 for k, v in newdict.iteritems():
265 self[k] = v
268 self[k] = v
266
269
267 def merge(self,__loc_data__=None,__conflict_solve=None,**kw):
270 def merge(self, __loc_data__=None, __conflict_solve=None, **kw):
268 """S.merge(data,conflict,k=v1,k=v2,...) -> merge data and k=v into S.
271 """S.merge(data,conflict,k=v1,k=v2,...) -> merge data and k=v into S.
269
272
270 This is similar to update(), but much more flexible. First, a dict is
273 This is similar to update(), but much more flexible. First, a dict is
271 made from data+key=value pairs. When merging this dict with the Struct
274 made from data+key=value pairs. When merging this dict with the Struct
272 S, the optional dictionary 'conflict' is used to decide what to do.
275 S, the optional dictionary 'conflict' is used to decide what to do.
273
276
274 If conflict is not given, the default behavior is to preserve any keys
277 If conflict is not given, the default behavior is to preserve any keys
275 with their current value (the opposite of the update method's
278 with their current value (the opposite of the update method's
276 behavior).
279 behavior).
277
280
278 conflict is a dictionary of binary functions which will be used to
281 conflict is a dictionary of binary functions which will be used to
279 solve key conflicts. It must have the following structure:
282 solve key conflicts. It must have the following structure:
280
283
281 conflict == { fn1 : [Skey1,Skey2,...], fn2 : [Skey3], etc }
284 conflict == { fn1 : [Skey1,Skey2,...], fn2 : [Skey3], etc }
282
285
283 Values must be lists or whitespace separated strings which are
286 Values must be lists or whitespace separated strings which are
284 automatically converted to lists of strings by calling string.split().
287 automatically converted to lists of strings by calling string.split().
285
288
286 Each key of conflict is a function which defines a policy for
289 Each key of conflict is a function which defines a policy for
287 resolving conflicts when merging with the input data. Each fn must be
290 resolving conflicts when merging with the input data. Each fn must be
288 a binary function which returns the desired outcome for a key
291 a binary function which returns the desired outcome for a key
289 conflict. These functions will be called as fn(old,new).
292 conflict. These functions will be called as fn(old,new).
290
293
291 An example is probably in order. Suppose you are merging the struct S
294 An example is probably in order. Suppose you are merging the struct S
292 with a dict D and the following conflict policy dict:
295 with a dict D and the following conflict policy dict:
293
296
294 S.merge(D,{fn1:['a','b',4], fn2:'key_c key_d'})
297 S.merge(D,{fn1:['a','b',4], fn2:'key_c key_d'})
295
298
296 If the key 'a' is found in both S and D, the merge method will call:
299 If the key 'a' is found in both S and D, the merge method will call:
297
300
298 S['a'] = fn1(S['a'],D['a'])
301 S['a'] = fn1(S['a'],D['a'])
299
302
300 As a convenience, merge() provides five (the most commonly needed)
303 As a convenience, merge() provides five (the most commonly needed)
301 pre-defined policies: preserve, update, add, add_flip and add_s. The
304 pre-defined policies: preserve, update, add, add_flip and add_s. The
302 easiest explanation is their implementation:
305 easiest explanation is their implementation:
303
306
304 preserve = lambda old,new: old
307 preserve = lambda old,new: old
305 update = lambda old,new: new
308 update = lambda old,new: new
306 add = lambda old,new: old + new
309 add = lambda old,new: old + new
307 add_flip = lambda old,new: new + old # note change of order!
310 add_flip = lambda old,new: new + old # note change of order!
308 add_s = lambda old,new: old + ' ' + new # only works for strings!
311 add_s = lambda old,new: old + ' ' + new # only works for strings!
309
312
310 You can use those four words (as strings) as keys in conflict instead
313 You can use those four words (as strings) as keys in conflict instead
311 of defining them as functions, and the merge method will substitute
314 of defining them as functions, and the merge method will substitute
312 the appropriate functions for you. That is, the call
315 the appropriate functions for you. That is, the call
313
316
314 S.merge(D,{'preserve':'a b c','add':[4,5,'d'],my_function:[6]})
317 S.merge(D,{'preserve':'a b c','add':[4,5,'d'],my_function:[6]})
315
318
316 will automatically substitute the functions preserve and add for the
319 will automatically substitute the functions preserve and add for the
317 names 'preserve' and 'add' before making any function calls.
320 names 'preserve' and 'add' before making any function calls.
318
321
319 For more complicated conflict resolution policies, you still need to
322 For more complicated conflict resolution policies, you still need to
320 construct your own functions. """
323 construct your own functions. """
321
324
322 data_dict = Struct.__make_dict(self,__loc_data__,**kw)
325 data_dict = self.__make_dict(__loc_data__,**kw)
323
326
324 # policies for conflict resolution: two argument functions which return
327 # policies for conflict resolution: two argument functions which return
325 # the value that will go in the new struct
328 # the value that will go in the new struct
326 preserve = lambda old,new: old
329 preserve = lambda old,new: old
327 update = lambda old,new: new
330 update = lambda old,new: new
328 add = lambda old,new: old + new
331 add = lambda old,new: old + new
329 add_flip = lambda old,new: new + old # note change of order!
332 add_flip = lambda old,new: new + old # note change of order!
330 add_s = lambda old,new: old + ' ' + new
333 add_s = lambda old,new: old + ' ' + new
331
334
332 # default policy is to keep current keys when there's a conflict
335 # default policy is to keep current keys when there's a conflict
333 conflict_solve = list2dict2(self.keys(),default = preserve)
336 conflict_solve = list2dict2(self.keys(), default = preserve)
334
337
335 # the conflict_solve dictionary is given by the user 'inverted': we
338 # the conflict_solve dictionary is given by the user 'inverted': we
336 # need a name-function mapping, it comes as a function -> names
339 # need a name-function mapping, it comes as a function -> names
337 # dict. Make a local copy (b/c we'll make changes), replace user
340 # dict. Make a local copy (b/c we'll make changes), replace user
338 # strings for the three builtin policies and invert it.
341 # strings for the three builtin policies and invert it.
339 if __conflict_solve:
342 if __conflict_solve:
340 inv_conflict_solve_user = __conflict_solve.copy()
343 inv_conflict_solve_user = __conflict_solve.copy()
341 for name, func in [('preserve',preserve), ('update',update),
344 for name, func in [('preserve',preserve), ('update',update),
342 ('add',add), ('add_flip',add_flip),
345 ('add',add), ('add_flip',add_flip),
343 ('add_s',add_s)]:
346 ('add_s',add_s)]:
344 if name in inv_conflict_solve_user.keys():
347 if name in inv_conflict_solve_user.keys():
345 inv_conflict_solve_user[func] = inv_conflict_solve_user[name]
348 inv_conflict_solve_user[func] = inv_conflict_solve_user[name]
346 del inv_conflict_solve_user[name]
349 del inv_conflict_solve_user[name]
347 conflict_solve.update(Struct.__dict_invert(self,inv_conflict_solve_user))
350 conflict_solve.update(self.__dict_invert(inv_conflict_solve_user))
348 #print 'merge. conflict_solve: '; pprint(conflict_solve) # dbg
351 #print 'merge. conflict_solve: '; pprint(conflict_solve) # dbg
349 #print '*'*50,'in merger. conflict_solver:'; pprint(conflict_solve)
352 #print '*'*50,'in merger. conflict_solver:'; pprint(conflict_solve)
350 for key in data_dict:
353 for key in data_dict:
351 if key not in self:
354 if key not in self:
352 self[key] = data_dict[key]
355 self[key] = data_dict[key]
353 else:
356 else:
354 self[key] = conflict_solve[key](self[key],data_dict[key])
357 self[key] = conflict_solve[key](self[key],data_dict[key])
355
358
356 def has_key(self,key):
359 def has_key(self,key):
357 """Like has_key() dictionary method."""
360 """Like has_key() dictionary method."""
358 return self.__dict__.has_key(key)
361 return self._data.has_key(key)
359
362
360 def hasattr(self,key):
363 def hasattr(self,key):
361 """hasattr function available as a method.
364 """hasattr function available as a method.
362
365
363 Implemented like has_key, to make sure that all available keys in the
366 Implemented like has_key, to make sure that all available keys in the
364 internal dictionary of the Struct appear also as attributes (even
367 internal dictionary of the Struct appear also as attributes (even
365 numeric keys)."""
368 numeric keys)."""
366 return self.__dict__.has_key(key)
369 return self._data.has_key(key)
367
370
368 def items(self):
371 def items(self):
369 """Return the items in the Struct's dictionary, in the same format
372 """Return the items in the Struct's dictionary as (key, value)'s."""
370 as a call to {}.items()."""
373 return self._data.items()
371 return self.__dict__.items()
374
372
373 def keys(self):
375 def keys(self):
374 """Return the keys in the Struct's dictionary, in the same format
376 """Return the keys in the Struct's dictionary.."""
375 as a call to {}.keys()."""
377 return self._data.keys()
376 return self.__dict__.keys()
378
377
379 def values(self, keys=None):
378 def values(self,keys=None):
380 """Return the values in the Struct's dictionary.
379 """Return the values in the Struct's dictionary, in the same format
381
380 as a call to {}.values().
381
382 Can be called with an optional argument keys, which must be a list or
382 Can be called with an optional argument keys, which must be a list or
383 tuple of keys. In this case it returns only the values corresponding
383 tuple of keys. In this case it returns only the values corresponding
384 to those keys (allowing a form of 'slicing' for Structs)."""
384 to those keys (allowing a form of 'slicing' for Structs).
385 """
385 if not keys:
386 if not keys:
386 return self.__dict__.values()
387 return self._data.values()
387 else:
388 else:
388 ret=[]
389 result=[]
389 for k in keys:
390 for k in keys:
390 ret.append(self[k])
391 result.append(self[k])
391 return ret
392 return result
392
393
393 def get(self,attr,val=None):
394 def get(self, attr, val=None):
394 """S.get(k[,d]) -> S[k] if k in S, else d. d defaults to None."""
395 """S.get(k[,d]) -> S[k] if k in S, else d. d defaults to None."""
395 try:
396 try:
396 return self[attr]
397 return self[attr]
397 except KeyError:
398 except KeyError:
398 return val
399 return val
399
400
400 def setdefault(self,attr,val=None):
401 def setdefault(self, attr, val=None):
401 """S.setdefault(k[,d]) -> S.get(k,d), also set S[k]=d if k not in S"""
402 """S.setdefault(k[,d]) -> S.get(k,d), also set S[k]=d if k not in S"""
402 if not self.has_key(attr):
403 if not self._data.has_key(attr):
403 self[attr] = val
404 self[attr] = val
404 return self.get(attr,val)
405 return self.get(attr, val)
405
406
406 def allow_new_attr(self, allow = True):
407 def allow_new_attr(self, allow = True):
407 """ Set whether new attributes can be created inside struct
408 """Set whether new attributes can be created in this Struct.
408
409
409 This can be used to catch typos by verifying that the attribute user
410 This can be used to catch typos by verifying that the attribute user
410 tries to change already exists in this Struct.
411 tries to change already exists in this Struct.
411 """
412 """
412 self['__allownew'] = allow
413 object.__setattr__(self, '_allownew', allow)
413
414
414
415
415 # end class Struct
416 # end class Struct
416
417
General Comments 0
You need to be logged in to leave comments. Login now