6
#FIXME: p.ServerAlias should not overwrite the Selection, but trigger an exception
8
#(12:58:59 AM) tacone: now I have the move thing, recursive search, and exception-on-readonly-setting on my todo list
9
#(12:59:35 AM) KillerKiwi2005: can u fix the indenting... low priority
11
# if p.notexisting.documentroot:pass should not trigger exception
13
class ReadOnly( Exception ):
16
class ListWrapper (object):
21
def _get_list( self ):
22
raise NotImplementedError
23
def _set_list(self, options):
24
raise NotImplementedError
25
def set(self, options):
26
return self._set_list(options)
29
# return "<ListWrapper %x>" % id(self)
31
# Returns the number of subelements.
33
# @return The number of subelements.
37
return len( self._get_list() )
40
# Returns the given subelement.
42
# @param index What subelement to return.
43
# @return The given subelement.
44
# @exception IndexError If the given element does not exist.
46
def __getitem__(self, index):
47
return self._get_list()[index]
50
# Replaces the given subelement.
52
# @param index What subelement to replace.
53
# @param element The new element value.
54
# @exception IndexError If the given element does not exist.
55
# @exception AssertionError If element is not a valid object.
57
def __setitem__(self, index, element):
58
if element is None: element = ''
59
options = self._get_list()
60
options[index] = element
61
self._set_list(options)
64
# Deletes the given subelement.
66
# @param index What subelement to delete.
67
# @exception IndexError If the given element does not exist.
69
def __delitem__(self, index):
70
options = self._get_list()
72
self._set_list(options)
74
def append(self, element):
75
options = self._get_list()
76
options.append(element)
77
self._set_list(options)
80
# Inserts a subelement at the given position in this element.
82
# @param index Where to insert the new subelement.
83
# @exception AssertionError If the element is not a valid object.
85
def insert(self, index, element):
86
options = self._get_list()
87
options.insert(index, element)
88
self._set_list(options)
91
# Removes a matching subelement. Unlike the <b>find</b> methods,
92
# this method compares elements based on identity, not on tag
95
# @param element What element to remove.
96
# @exception ValueError If a matching element could not be found.
97
# @exception AssertionError If the element is not a valid object.
99
def remove(self, element):
100
options = self._get_list()
101
options.remove(element)
102
self._set_list(options)
104
class Options (ListWrapper):
106
def __init__(self, parent):
107
super (ListWrapper, self).__init__()
110
self.parser = ApacheConf.LineParser()
111
def _get_list( self ):
112
options = self.parser.parse_options( self.parent.get_raw_value() )
113
if not isinstance( options, list ): return []
115
def _set_list(self, options):
116
if options is None: options = []
119
if isinstance( o, int ): o = str(o)
120
sanitized.append( self.parser.value_escape(o) )
121
self.parent.set_raw_value(" ".join( sanitized ))
124
def __init__(self, element = None):
125
self.__dict__['element'] = None
126
self.__dict__['_opts'] = Options( self )
127
if element is not None:
128
self.element = element
131
self.__dict__['parser'] = ApacheConf.LineParser()
134
"""resets the instance to an empty state"""
135
self.element = etree.Element( 'line' )
137
"""has the instance loaded a line?"""
138
return self is not None
140
parent = self.element.getparent()
141
raw_index = parent.index( self.element )
142
del parent[ raw_index ]
143
def _get_value(self):
144
if self.element == None : return None
145
#oh, by the way, should be the only element of the list
146
return self.parser.value_unescape( self.get_raw_value() )
147
def _set_value(self, value):
148
return self.set_raw_value( self.parser.value_escape( value ))
149
value = property ( _get_value, _set_value )
151
if self.element == None : return None
152
#oh, by the way, should be the only element of the list
153
return self.element.get('directive')
154
def _set_key(self, value):
155
if self.element == None : return False
156
if self.element.attrib.get('source'):
157
del self.element.attrib['source']
158
self.element.set('directive', value)
159
key = property ( _get_key, _set_key )
162
def _set_opts(self, value):
163
return self._opts.set( value )
164
opts = property ( _get_opts, _set_opts )
165
def get_raw_value(self):
166
return self.element.get('value')
167
def set_raw_value(self, value):
169
self.element.attrib['value'] = value
170
if self.element.attrib.get('source'):
171
del self.element.attrib['source']
173
return not bool( self.element.attrib.get('source' ) )
174
def parse(self, line, set_as_source = True):
175
"""parses a configuration line into a <line> xml element"""
179
directive = parser.get_directive( line )
181
c.attrib['directive'] = directive
183
value = parser.get_value( line )
184
c.attrib[ 'value' ] = value
186
c.attrib['unparsable' ] = 'unparsable'
188
comment = parser.get_comment( line )
189
if comment: c.attrib['comment'] = comment
191
indentation = parser.get_indentation( line )
192
if indentation: c.attrib['indentation'] = indentation
193
if set_as_source: c.attrib[ 'source' ] = line
194
def _compile(self, obj_line):
195
source = obj_line.attrib.get('source')
196
if source: return source.rstrip()+"\n"
199
indentation = obj_line.attrib.get('indentation')
200
if indentation: line += indentation
203
if obj_line.tag != 'line': line += '<'
205
directive = obj_line.attrib.get('directive')
207
#if line != '': line += ' ' #terribly wrong.
210
value = obj_line.attrib.get('value')
212
if line != '': line += ' '
216
if obj_line.tag != 'line': line += '>'
218
comment = obj_line.attrib.get('comment')
220
if line != '': line += ' '
221
line += "#" + comment
222
#remove traling spaces and assure a newline at the end of the file.
223
#line = line.rstrip() + "\n"
224
return line.rstrip()+"\n"
225
def get_as_str(self):
226
return self._compile( self.element )
227
def get_as_list(self):
228
return [self._compile( self.element )]
230
print etree.tostring( self.element, pretty_print = True )
233
class NotALine(object):
234
def _unified_get ( self, name ):
235
raise AttributeError, "'NotALine' Object has no attribute "+name
236
def _unified_set(self, name, value ):
237
raise AttributeError, "'NotALine' Object has no attribute "+name
238
def _get_value(self):
239
return self._unified_get( 'value' )
240
def _set_value(self, value):
241
return self._unified_set( 'value', value )
242
value = property ( _get_value, _set_value )
244
return self._unified_get( 'key' )
245
def _set_key(self, value):
246
return self._unified_set( 'key', value )
247
key = property ( _get_key, _set_key )
249
return self._unified_get( 'opts' )
250
def _set_opts(self, value):
251
return self._unified_set( 'opts', value )
252
opts = property ( _get_opts, _set_opts )
254
return self._unified_get( 'changed' )
256
class LineGroup( NotALine ):
257
def _unified_get(self, name):
258
return getattr(self[-1], name)
259
def _unified_set(self, name, value):
260
return setattr(self[-1], name, value)
262
class AbstractSelection( ListWrapper):
263
"""def _unified_get(self, name):
264
return getattr(self[-1], name)
265
def _unified_set(self, name, value):
266
return setattr(self[-1], name, value)"""
267
def __getattr__(self, name):
268
return getattr(self[-1], name)
269
def __setattr__(self, name, value):
270
return setattr(self[-1], name, value)
272
class PlainSelection(AbstractSelection):
273
def __init__(self, caller, query ):
274
self.__dict__['_query'] = self._sanitize_query(query)
275
self.__dict__['_caller'] = caller
276
def _sanitize_query(self, query):
278
def _build_xpath(self):
279
#print "query for", self._name, "in", self._caller
280
name = self._query.lower()
281
directive_attr = 'translate(@directive, "ABCDEFGHIJKLMNOPQRSTUVWXYZ",'\
282
+'"abcdefghijklmnopqrstuvwxyz")'
283
return '*[%s="%s"]' % (directive_attr, name)
286
return self._caller.xpath( self._build_xpath() )
288
#shuold never be called (for now at least)
290
def search(self, query):
291
return SimpleSearch( self, query )
292
def __setattr__(self, name, value):
294
obj = self._create_new()
295
return setattr(obj, name, value)
296
return setattr(self[-1], name, value)
297
def __delitem__( self, index ):
299
def __delattr__(self, name):
302
del getattr( self, name)[0]
311
def _create_new(self ):
313
line.key = self._query
314
self._caller.element.append(line.element)
316
class TypeSelection(PlainSelection):
319
name = self._query.lower()
321
return self._caller.xpath( query )
322
def create(self, key, value = None):
323
if self._query == 'line':
328
if value is not None: obj.value = value
329
self._caller.element.append(obj.element)
331
class SimpleSearch(PlainSelection):
332
def _sanitize_query(self, query):
333
if isinstance( query, int ): query = str(query)
334
if isinstance( query, tuple ): query = list(query)
337
if not self._query: return []
338
if isinstance( self._query, str ):
339
result = [line for line in self._caller if line.value == self._query]
340
elif isinstance( self._query, list ):
341
terms = [ (idx, str(term) ) for idx, term in enumerate(self._query) if term ]
343
for line in self._caller:
344
opts = list( line.opts )
346
found = [ term for term in terms if term[1] == opts[ term[0] ] ]
347
if len( found ) == len( terms ): result.append( line )
351
def _create_new(self ):
352
return self._caller._create_new()
353
class RecursiveSearch(PlainSelection):
354
def _build_xpath(self):
355
return "descendant::"+super( RecursiveSearch, self )._build_xpath()
356
def _create_new(self ):
357
raise NotImplementedError
359
def __init__(self, element=None):
360
super (Parser, self).__init__( element )
361
self.__dict__['open_child'] = None
363
self.__dict__['parser'] = ApacheConf.LineParser()
364
self.__dict__['element'] = None
365
if element is not None:
366
self.__dict__['element'] = element
370
def __getattr__(self, name):
371
return PlainSelection(self, name)
372
"""def __setattr__(self, name, value):
374
if self.__dict__.has_key(name):
375
self.__dict__[name] = value
376
elif dir(self).has_key(name) and b.a
378
raise ReadOnly, "Selections are read-only. Were you trying to set "+name+".value maybe ?"
380
def _get_lines(self):
381
return TypeSelection(self, 'line')
382
"""Lines returns all the line elements"""
383
lines= property ( _get_lines )
384
def _get_sections(self):
385
return TypeSelection(self, 'section')
386
"""section returns all section elements"""
387
sections= property ( _get_sections )
389
def rsearch(self, name):
390
return RecursiveSearch( self, name )
392
def __delattr__(self, name):
395
del getattr( self, name)[0]
400
def load(self, filename ):
401
"""Loads a configuration file in the parser"""
402
file = open ( filename, 'r' )
403
content = file.readlines()
405
self.set_from_list(content)
407
#tag_name = self.key.lower()
408
self.__dict__['element'] = etree.Element( 'root' )
409
def _append_string(self, line):
410
"""Parses a line of code and appends it's xml equivalent to
412
if self.open_child != None:
413
if self.open_child.open_child == None \
414
and self.is_tag_close(line, self.open_child.key ):
415
self.open_child.close( line )
416
self.element.append( self.open_child.element )
417
self.open_child = None
419
self.open_child._append_string( line )
421
tag_open = self.is_tag_open(line)
422
if tag_open is not False:
423
self.open_child = Section()
424
self.open_child.key = tag_open[0]
425
self.open_child.value = tag_open[1]
427
#unexpected closure ? we will trigger an exception
428
self.is_tag_close(line, False )
432
self.element.append(lineobj.element)
433
def is_tag_open( self, line ):
434
basic_regexp = r'^(\s*)<s*([A-Z0-9\-.]+)(\s+[^>]*)*>.*'
435
result = re.match( basic_regexp, line, re.IGNORECASE )
436
if ( result == None or result == False ): return False
437
result_list = list( result.groups() )
438
indentation = result_list[0]
440
line = line.strip().lstrip( '<' ).rstrip( '>' ).strip()
441
key = self.parser.get_directive( line )
443
value = self.parser.get_value( line )
444
except AttributeError:
446
pass#value may not be a string
447
# return indentation, key," ",value
449
def is_tag_close (self, line, name):
450
basic_regexp = r'^\s*<s*(/[A-Z0-9\-._]+)\s*>.*'
451
result = re.match( basic_regexp, line, re.IGNORECASE )
452
if result == None or result == False:
455
raise VhostNotFound \
456
, 'TagEndUnexpected, Unexpected closure found: %s, there\'s no open tag in the current context node.' \
459
basic_regexp = r'^\s*<s*(/'+name+r')\s*>.*'
460
result = re.match( basic_regexp, line, re.IGNORECASE )
461
if ( result != None and result != False ):
464
raise VhostNotFound \
465
, 'TagEndUnexpected, Unexpected closure found: %s, expecting %s' \
466
% ( line.strip(), '</%s>' % name )
468
def _raw_xpath(self, query):
469
xpath = etree.XPath( query )
470
selection = xpath( self.element )
471
#oh, by the way, should be the only element of the list
472
if not selection : return []
474
def _element_factory (self, element):
475
if element.tag == 'line':
478
return Section(element)
479
def xpath(self, query):
481
for element in self._raw_xpath(query):
482
selection.append( self._element_factory (element) )
485
def close (self, line):
486
"""Sets source for closing the tag"""
487
self.element.attrib[ 'close_source' ] = line
488
def set_from_str(self, string):
489
return self.set_from_list( string.split("\n") )
490
def set_from_list(self, list):
491
"""uses a (line) list as the configuration file to be parsed"""
494
#if not line.endswith( "\n" ): line = line.rstrip()+"\n"
495
self._append_string(line)
496
if self.open_child != None:
497
#something wrong, one tag has been left open in the .conf
498
raise VhostNotFound, 'TagEndExpected: expected end tag for:'+self.open_child.key
499
def get_as_list(self):
500
"""returns the content as a list (i.e. for saving)"""
502
for element in self.element:
503
line = self._element_factory( element )
504
content += line.get_as_list()
505
#prevent adding a new newline to the end of file.
506
if len(content) > 0 : content[-1] = content[-1].rstrip()
507
#if self.element.getparent() == None:
508
#if line.getnext() != None or self.element.getparent() != None:
509
#if len(content) > 0 : content[-1] = content[-1].rstrip()
511
def get_as_str(self):
512
return "".join( self.get_as_list() )
513
def set_element (self, element):
514
self.element = element
515
def dump_xml (self, include_comments = False):
516
"""prints out the internal xml structure"""
518
print etree.tostring( self.element, pretty_print = True )
521
selection = etree.XPath( './line[attribute::directive]' )
522
for el in selection(self.element):
523
print etree.tostring( el, pretty_print = True )
524
def create(self, *args, **kwargs):
525
return self.lines.create( *args, **kwargs )
526
class Section(Parser):
527
def get_as_list (self):
528
# replace compile_line !
529
content = [ self._compile( self.element).rstrip()+"\n" ]
530
content += super (Section, self).get_as_list()
531
if len(content) > 0 : content[-1] = content[-1].rstrip()+"\n"
532
#content += [ "</%s>\n" % self.key ]
533
if self.element.get('close_source'):
534
content += [ self.element.attrib[ 'close_source' ].rstrip()+"\n" ]
536
content += [ "</%s>\n" % self.element.attrib['directive'] ]
539
#tag_name = self.key.lower()
540
self.__dict__['element'] = etree.Element( 'section' )