1
2
3
4
5
6
7
8
9
10
11 __doc__ = """
12 Zenoss config parsers.
13
14 There are mutiple stages to config parsing. Parsing is split into stages so that
15 we can validate a whole config file and possibly rebuild it to correct errors.
16
17 The stages are:
18
19 * Parse - Split the config file in to ConfigLine types while maintaining line order, comments, and new lines
20 * Validate - Check that all lines are valid
21 * Report - Investigate why a line might be invalid (ex: invalid key format)
22 * Load - Get a config object back
23 * Write - An optional stage to write the config back to a file
24 """
25
26 import re
29 """
30 Error for problems parsing config files.
31 """
32 pass
33
35 """
36 Error for problems parsing config files with line context.
37 """
41
43 return '%s on line %d' % (self.message, self.lineno)
44
46 """
47 A group of errors while parsing config.
48 """
52
59
62
65
67 """
68 A bunch of configuration settings. Uses dictionary access,
69 or object attribute access.
70
71 Provides some Convenience functions for different types.
72 """
75
76 - def getbool(self, key, default=None):
77 """
78 Convenience function to convert the value to a bool.
79 Valid values are and case of true, yes, y, 1 anything
80 else is considered False.
81
82 If key doesn't exist, returns `default`.
83 """
84 try:
85 return self[key].lower() in ('true', 'yes', 'y', '1')
86 except KeyError:
87 return default
88
89 getboolean = getbool
90
91 - def getint(self, key, default=None):
92 """
93 Convenience function to convert the value to an int.
94 Valid values are anything `int(x)` will accept.
95
96 If key doesn't exist or can't be converted to int, returns `default`.
97 """
98 try:
99 return int(self[key])
100 except (KeyError, ValueError):
101 return default
102
104 """
105 Convenience function to convert the value to a float.
106 Valid values are anything `float(x)` will accept.
107
108 If key doesn't exist or can't be converted to float, returns `default`.
109 """
110 try:
111 return float(self[key])
112 except (KeyError, ValueError):
113 return default
114
116 """
117 Abstract class that represents a single line in the config.
118 """
121
124
125 @property
127 """
128 Return a key, value tuple if this line represents a setting.
129 Implemented in base classes.
130 """
131 return None
132
133 @classmethod
135 """
136 Returns an instance of cls if this class can parse this line. Otherwise returns None.
137 Implemented in base classes.
138 """
139 return None
140
141 @classmethod
143 """
144 Checks the string for possible matches, considers why it doesn't match exactly if it's close
145 and returns a ConfigLineError.
146 Implemented in base classes.
147 """
148 return None
149
151 """
152 Represents a config line with a `key = value` pair.
153 """
154 _regexp = re.compile(r'^(?P<key>[a-z][a-z\d_-]*)\s*(?P<delim>(=|:|\s)*)\s*(?P<value>.*)$', re.I)
155
156 - def __init__(self, key, value=None, delim='='):
160
161 @property
164
166 return '{key} {delim} {value}'.format(**self.__dict__)
167
168 @classmethod
173
174
175 @classmethod
181
190
194
195 @classmethod
197 if line == '':
198 return cls()
199
202
204 """
205 Default line if no other ConfigLines matched. Assumed to be invalid
206 input.
207 """
208 pass
209
211 """
212 Parses Zenoss's config file format.
213
214 Example:
215
216 key value
217 key intvalue
218 key = value
219 key=value
220 key:value
221 key : value
222 """
223
224
225 _lineTypes = [
226 SettingLine,
227 CommentLine,
228 EmptyLine,
229 ]
230
231
232 _invalidLineType = InvalidLine
233
235 """
236 @param file file-like-object
237 """
238 self.file = file
239 self.filename = self.file.name if hasattr(self.file, 'name') else 'Unknown'
240 self._lines = None
241
250
251
258
260 """
261 Parse a config file which has key-value pairs.Returns a list of config
262 line information. This line information can be used to accuratly recreate
263 the config without losing comments or invalid data.
264 """
265 if self._lines is None:
266 self._lines = []
267 for line in self.file:
268 self._lines.append(self._parseLine(line))
269
270 return self._lines
271
273 """
274 Write the config out to a file. Takes a new file argument
275 because the input file object often doesn't have write access.
276
277 @param file file-like-object
278 """
279 for line in self:
280 file.write(str(line) + '\n')
281
283 """
284 Validate that there are no errors in the config file
285
286 @throws ConfigError
287 """
288 errors = []
289 for lineno, line in enumerate(self):
290 if isinstance(line, self._invalidLineType):
291
292 error = self._checkLine(line.line, lineno)
293 if error:
294 errors.append(error)
295 else:
296 errors.append(ConfigLineError('Unexpected config line "%s"' % line.line, lineno + 1))
297
298 if errors:
299 raise ConfigErrors('There were errors parsing the config "%s".' % self.filename, errors)
300
302 for line in self.parse():
303 yield line
304
309
315
318 """
319 Lazily load the config when requested.
320 """
322 """
323 @param config Config The config instance or class to load data into. Must support update which accepts an iterable of (key, value).
324 @param parser Parser The parser to use to parse the config files. Must be a callable and return an iterable of (key, value).
325 @param config_files list<string> A list of config file names to parse in order.
326 """
327 if not isinstance(config_files, list):
328 config_files = [config_files]
329
330 self.config_files = config_files
331 self.parser = parser
332 self.config = config
333 self._config = None
334
336 """
337 Load the config_files into an instance of config_class
338 """
339 if isinstance(self.config, type):
340 self._config = self.config()
341 else:
342 self._config = self.config
343
344 if not self.config_files:
345 raise ConfigError('Config loader has no config files to load.')
346
347 for file in self.config_files:
348 if not hasattr(file, 'read') and isinstance(file, basestring):
349
350 with open(file, 'r') as fp:
351 options = self.parser(fp)
352 else:
353 options = self.parser(file)
354
355 self._config.update(options)
356
358 """
359 Lazily load the config file.
360 """
361 if self._config is None:
362 self.load()
363
364 return self._config
365