~ubuntu-branches/ubuntu/maverick/rapache/maverick

« back to all changes in this revision

Viewing changes to RapacheCore/LineElement.py

  • Committer: Bazaar Package Importer
  • Author(s): Emanuele Gentili
  • Date: 2008-10-15 15:44:27 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20081015154427-sc86e7urpacfwppr
Tags: 0.7-0ubuntu1
* rapache version bump (LP: #283770)
* debian/patches:
 + removed 01_surf_this_button.patch, fixed in bzr branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#TODO: remove this
 
2
import ApacheConf
 
3
from lxml import etree
 
4
import re
 
5
 
 
6
#FIXME: p.ServerAlias should not overwrite the Selection, but trigger an exception
 
7
 
 
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
 
10
 
 
11
# if p.notexisting.documentroot:pass should not trigger exception
 
12
 
 
13
class ReadOnly( Exception ):
 
14
    pass
 
15
 
 
16
class ListWrapper (object):
 
17
    
 
18
    def __init__(self):
 
19
        pass
 
20
        
 
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)
 
27
    
 
28
    #def __repr__(self):
 
29
    #    return "<ListWrapper %x>" %  id(self)
 
30
    ##
 
31
    # Returns the number of subelements.
 
32
    #
 
33
    # @return The number of subelements.
 
34
 
 
35
    def __len__(self):
 
36
        
 
37
        return len( self._get_list() )
 
38
 
 
39
    ##
 
40
    # Returns the given subelement.
 
41
    #
 
42
    # @param index What subelement to return.
 
43
    # @return The given subelement.
 
44
    # @exception IndexError If the given element does not exist.
 
45
 
 
46
    def __getitem__(self, index):
 
47
        return self._get_list()[index]
 
48
 
 
49
    ##
 
50
    # Replaces the given subelement.
 
51
    #
 
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.
 
56
 
 
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)
 
62
 
 
63
    ##
 
64
    # Deletes the given subelement.
 
65
    #
 
66
    # @param index What subelement to delete.
 
67
    # @exception IndexError If the given element does not exist.
 
68
 
 
69
    def __delitem__(self, index):
 
70
        options = self._get_list()
 
71
        del options[index]
 
72
        self._set_list(options)
 
73
        
 
74
    def append(self, element):
 
75
        options = self._get_list()
 
76
        options.append(element)
 
77
        self._set_list(options)
 
78
 
 
79
    ##
 
80
    # Inserts a subelement at the given position in this element.
 
81
    #
 
82
    # @param index Where to insert the new subelement.
 
83
    # @exception AssertionError If the element is not a valid object.
 
84
 
 
85
    def insert(self, index, element):
 
86
        options = self._get_list()
 
87
        options.insert(index, element)
 
88
        self._set_list(options)
 
89
 
 
90
    ##
 
91
    # Removes a matching subelement.  Unlike the <b>find</b> methods,
 
92
    # this method compares elements based on identity, not on tag
 
93
    # value or contents.
 
94
    #
 
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.
 
98
 
 
99
    def remove(self, element):
 
100
        options = self._get_list()
 
101
        options.remove(element)
 
102
        self._set_list(options)
 
103
 
 
104
class Options (ListWrapper):        
 
105
    parser = None
 
106
    def __init__(self, parent):
 
107
        super (ListWrapper, self).__init__()
 
108
        self.parent = parent
 
109
        #TODO: remove this
 
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 []
 
114
        return options
 
115
    def _set_list(self, options):
 
116
        if options is None: options = []
 
117
        sanitized = []
 
118
        for o in 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 ))
 
122
class Line (object):
 
123
    parser = None
 
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
 
129
        else:
 
130
            self.reset()
 
131
        self.__dict__['parser'] = ApacheConf.LineParser()
 
132
        
 
133
    def reset(self):
 
134
        """resets the instance to an empty state"""        
 
135
        self.element = etree.Element( 'line' )        
 
136
    def is_empty (self):    
 
137
        """has the instance loaded a line?"""
 
138
        return self is not None
 
139
    def delete(self):        
 
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 )
 
150
    def _get_key(self): 
 
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 )
 
160
    def _get_opts(self): 
 
161
        return self._opts
 
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):  
 
168
        if value != None:
 
169
            self.element.attrib['value'] = value
 
170
            if self.element.attrib.get('source'): 
 
171
                del self.element.attrib['source']
 
172
    def changed(self):
 
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"""
 
176
        parser = self.parser
 
177
        self.reset()
 
178
        c = self.element
 
179
        directive = parser.get_directive( line )
 
180
        if directive: 
 
181
            c.attrib['directive'] = directive
 
182
            try:
 
183
                value = parser.get_value( line )
 
184
                c.attrib[ 'value' ] = value
 
185
            except:
 
186
                c.attrib['unparsable' ] = 'unparsable'
 
187
        else:
 
188
            comment = parser.get_comment( line )
 
189
            if comment: c.attrib['comment'] = comment
 
190
            
 
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"
 
197
        line = ''
 
198
        
 
199
        indentation = obj_line.attrib.get('indentation')
 
200
        if indentation: line += indentation
 
201
        
 
202
        #lets open subtag
 
203
        if obj_line.tag != 'line': line += '<'
 
204
            
 
205
        directive = obj_line.attrib.get('directive')
 
206
        if directive: 
 
207
            #if line != '': line += ' ' #terribly wrong.
 
208
            line += directive
 
209
            
 
210
        value = obj_line.attrib.get('value')
 
211
        if value: 
 
212
            if line != '': line += ' '
 
213
            line += value
 
214
         
 
215
        #lets close subtag
 
216
        if obj_line.tag != 'line': line += '>'    
 
217
        
 
218
        comment = obj_line.attrib.get('comment')
 
219
        if 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 )]
 
229
    def dump_xml(self):
 
230
        print etree.tostring( self.element, pretty_print = True )
 
231
 
 
232
"""
 
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 )
 
243
    def _get_key(self): 
 
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 )
 
248
    def _get_opts(self): 
 
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 )
 
253
    def changed(self):
 
254
        return self._unified_get( 'changed' )
 
255
        
 
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)
 
261
"""        
 
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)
 
271
 
 
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):
 
277
        return 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)    
 
284
        
 
285
    def _get_list(self):         
 
286
        return self._caller.xpath( self._build_xpath() )        
 
287
    def _set_list(self): 
 
288
        #shuold never be called (for now at least)
 
289
        pass
 
290
    def search(self,  query):
 
291
        return SimpleSearch( self,  query )
 
292
    def __setattr__(self, name, value):
 
293
        if len(self) == 0:
 
294
            obj = self._create_new()
 
295
            return setattr(obj, name, value)
 
296
        return setattr(self[-1], name, value)
 
297
    def __delitem__( self,  index ):
 
298
        self[index].delete()
 
299
    def __delattr__(self,  name):                
 
300
        try: 
 
301
                while (1):
 
302
                    del getattr( self,  name)[0] 
 
303
        except IndexError:
 
304
            pass
 
305
    def delete (self):                            
 
306
        try: 
 
307
                while (1):
 
308
                    del self[0] 
 
309
        except IndexError:
 
310
            pass
 
311
    def _create_new(self ):
 
312
        line = Line()
 
313
        line.key = self._query                
 
314
        self._caller.element.append(line.element)                
 
315
        return line
 
316
class TypeSelection(PlainSelection):    
 
317
    
 
318
    def _get_list(self):
 
319
        name = self._query.lower()        
 
320
        query = './' + name
 
321
        return self._caller.xpath( query )
 
322
    def create(self,  key,  value = None):
 
323
        if self._query == 'line':
 
324
            obj = Line()
 
325
        else:
 
326
            obj = Section()
 
327
        obj.key = key
 
328
        if value is not None: obj.value = value
 
329
        self._caller.element.append(obj.element)        
 
330
        return obj
 
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)
 
335
        return query
 
336
    def _get_list(self):
 
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 ]
 
342
            result = []
 
343
            for line in self._caller:
 
344
                opts = list( line.opts )
 
345
                try:
 
346
                    found = [ term for term in terms if term[1] == opts[ term[0] ] ]                    
 
347
                    if len( found ) == len( terms ): result.append( line )
 
348
                except IndexError:
 
349
                    pass
 
350
        return result
 
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
 
358
class Parser(Line):
 
359
    def __init__(self, element=None):
 
360
        super (Parser, self).__init__( element )
 
361
        self.__dict__['open_child'] = None
 
362
        """
 
363
        self.__dict__['parser'] = ApacheConf.LineParser()
 
364
        self.__dict__['element'] = None
 
365
        if element is not None:
 
366
            self.__dict__['element'] = element
 
367
        else:
 
368
            self.reset()
 
369
        """
 
370
    def __getattr__(self, name):
 
371
        return PlainSelection(self, name)
 
372
    """def __setattr__(self, name,  value):
 
373
        print dir(self)
 
374
        if self.__dict__.has_key(name):
 
375
            self.__dict__[name] = value
 
376
        elif dir(self).has_key(name) and b.a
 
377
        else:
 
378
            raise ReadOnly,  "Selections are read-only. Were you trying to set "+name+".value maybe ?"
 
379
    """
 
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 )
 
388
    
 
389
    def rsearch(self,  name):
 
390
        return RecursiveSearch( self,  name )
 
391
    
 
392
    def __delattr__(self,  name):                
 
393
        try: 
 
394
                while (1):
 
395
                    del getattr( self,  name)[0] 
 
396
        except IndexError:
 
397
            pass
 
398
            
 
399
    
 
400
    def load(self, filename ):
 
401
        """Loads a configuration file in the parser"""        
 
402
        file = open ( filename, 'r' )
 
403
        content = file.readlines()
 
404
        file.close()
 
405
        self.set_from_list(content)
 
406
    def reset (self):        
 
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
 
411
        self.element"""
 
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
 
418
            else:
 
419
                self.open_child._append_string( line )
 
420
        else:    
 
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]
 
426
            else:
 
427
                #unexpected closure ? we will trigger an exception
 
428
                self.is_tag_close(line, False )
 
429
                
 
430
                lineobj = Line()
 
431
                lineobj.parse(line)
 
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]
 
439
        
 
440
        line = line.strip().lstrip( '<' ).rstrip( '>' ).strip()
 
441
        key = self.parser.get_directive( line )
 
442
        try:
 
443
            value = self.parser.get_value( line )
 
444
        except AttributeError:
 
445
            value = None
 
446
            pass#value may not be a string
 
447
        #        return indentation, key," ",value
 
448
        return 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:
 
453
            return False
 
454
        if name == False:
 
455
            raise VhostNotFound \
 
456
                , 'TagEndUnexpected, Unexpected closure found: %s, there\'s no open tag in the current context node.' \
 
457
                % line.strip()
 
458
        else:
 
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 ): 
 
462
                return True
 
463
            else:
 
464
                raise VhostNotFound \
 
465
                    , 'TagEndUnexpected, Unexpected closure found: %s, expecting %s' \
 
466
                    % ( line.strip(), '</%s>' % name )
 
467
        return False
 
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 []
 
473
        return selection 
 
474
    def _element_factory (self,  element):
 
475
        if element.tag == 'line':
 
476
            return Line(element)
 
477
        else:
 
478
            return Section(element)
 
479
    def xpath(self, query):
 
480
        selection = []
 
481
        for element in self._raw_xpath(query):
 
482
            selection.append( self._element_factory (element) )
 
483
            
 
484
        return selection    
 
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"""
 
492
        self.reset()
 
493
        for line in list:
 
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)"""
 
501
        content = []
 
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()
 
510
        return content
 
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"""
 
517
        if include_comments:
 
518
            print etree.tostring( self.element, pretty_print = True )
 
519
        else:
 
520
            #hackish
 
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" ]
 
535
        else:
 
536
            content += [ "</%s>\n" % self.element.attrib['directive'] ]
 
537
        return content
 
538
    def reset (self):        
 
539
        #tag_name = self.key.lower()
 
540
        self.__dict__['element'] = etree.Element( 'section' )