More cleaning. Added string comments. Removed remaining mailfilter stuff.
authorFrederic Jolliton <frederic@jolliton.com>
Mon, 7 Mar 2005 09:42:29 +0000 (09:42 +0000)
committerFrederic Jolliton <frederic@jolliton.com>
Mon, 7 Mar 2005 09:42:29 +0000 (09:42 +0000)
git-archimport-id: frederic@jolliton.com--2005-private/confparser--main--0.1--patch-3

basicparser.py
basicvalidator.py
confparser.py

index fefaee9..b1d9273 100644 (file)
@@ -43,25 +43,36 @@ class Parser :
 
        def ignore( self , *tokens ) :
 
+               '''Declare tokens 'tokens' as tokens to ignore when
+               parsing.'''
+
                self.tokensToIgnore = tokens
 
        def peek( self , *set ) :
 
+               '''Try to match any of the defined tokens (or only
+               those from set 'set' if specified.) All matchings tokens
+               are returned, ordered (by decreasing order) by length.'''
+
                tokens = []
                if not set :
                        set = self.tokenMatches.keys()
                for tokenName in set :
                        tokenMatch = self.tokenMatches.get( tokenName )
-                       if not tokenMatch : continue
-                       m = tokenMatch.match( self.text , self.pos )
-                       if m :
-                               tk = m.group( 0 )
-                               tokens.append( ( tokenName , tk ) )
+                       if tokenMatch :
+                               m = tokenMatch.match( self.text , self.pos )
+                               if m :
+                                       tk = m.group( 0 )
+                                       tokens.append( ( tokenName , tk ) )
                tokens.sort( lambda a , b : cmp( len( a[ 1 ] ) , len( b[ 1 ] ) ) )
                return tokens
 
        def advance( self , n ) :
 
+               '''Update current position (line,column) for the 'n'
+               next characters (if 'n' is an integer, or for the length of
+               the 'n' string otherwise.)'''
+
                if type( n ) in types.StringTypes :
                        n = len( n )
                p = self.text.rfind( '\n' , self.pos , self.pos + n )
@@ -74,6 +85,10 @@ class Parser :
 
        def snext( self , *set ) :
 
+               '''Try to match the longest string matching defined tokens
+               (or only from one of set 'set' if specified.) If no tokens
+               match, None is returned.'''
+
                set += self.tokensToIgnore
                r = None
                while 1 :
@@ -88,6 +103,10 @@ class Parser :
 
        def next( self , *set ) :
 
+               '''Try to match the longest string matching defined tokens
+               (or only from one of set 'set' if specified.) If no tokens
+               match, an exception is raised.'''
+
                r = self.snext( *set )
                if not r :
                        r = self.peek()
@@ -101,10 +120,15 @@ class Parser :
 
        def getPos( self ) :
 
+               '''Return current position in a texture representation.'''
+
                return 'at line %d, column %d' % ( self.y , self.x )
 
        def point( self ) :
 
+               '''Return a multiple-line string to show where an error
+               happened.'''
+
                lineStart = self.text.rfind( '\n' , 0 , self.pos + 1 ) + 1
                lineEnd = self.text.find( '\n' , self.pos + 1 )
                if lineEnd == -1 :
index 9e422f8..eeb0965 100644 (file)
@@ -37,51 +37,46 @@ class Validator :
 
        def descend( self , item ) :
 
-               # Return a validator for the contents
-               # of the node 'item', or throw an exception.
+               '''Return a validator for the contents
+               of the node 'item', or throw an exception.'''
+
                error( 'Invalid keyword `%r\' in %r.' % ( item , self.name ) )
 
        def check( self , values ) :
 
-               # Check node values.
+               '''Check node values.'''
+
                if values :
                        error( 'Unexpected values %r for %r.' % ( values , self.name ) )
 
        def valid( self ) :
 
-               pass
-
-class MixinNonEmpty :
+               '''Called once node contents is valided.'''
 
-       children = 0
-
-       def valid( self ) :
-
-               if not self.children :
-                       error( 'Empty block for %r.' % self.name )
+               pass
 
-#-----------------------------------------------------------------------------
+def validate( node , validator ) :
 
-def checkConf( confNode , rootClass ) :
+       '''Validate tree from node 'node' with validator 'validator'.'''
 
-       checkConf.lastNode = confNode
+       validate.lastNode = node
 
-       def _checkConf( confNode , syntaxNode ) :
+       def _checkConf( node , syntaxNode ) :
 
-               checkConf.lastNode = confNode
-               name , values , contents , meta = confNode
+               validate.lastNode = node
+               name , values , contents , meta = node
                r = syntaxNode.descend( name )
                r.check( values )
                for item in contents :
                        _checkConf( item , r )
                r.valid()
 
-       name , values , contents , info = confNode
-       root = rootClass( '__root__' )
+       name , values , contents , meta = node
+       root = validator( '__root__' )
        try :
                for item in contents :
                        _checkConf( item , root )
        except Error , e :
-               meta = checkConf.lastNode[ 3 ]
+               meta = validate.lastNode[ 3 ]
                raise Error( 'at line %d, column %d, %s' 
                        % ( meta[ 0 ] , meta[ 1 ] , str( e ) ) )
index 934ef87..835a2f3 100644 (file)
@@ -45,6 +45,8 @@ class Error( Exception ) : pass
 
 def parseString( s ) :
 
+       '''Strip string delimiters.'''
+
        if s.startswith( "'" ) :
                return s[ 1 : -1 ]
        elif s.startswith( '"' ) :
@@ -68,7 +70,8 @@ def parseConf( p , meta = None ) :
                values = []
                while 1 :
                        t = p.next( 'string' , '{' , ';' )
-                       if t[ 0 ] in [ '{' , ';' ] : break
+                       if t[ 0 ] in [ '{' , ';' ] :
+                               break
                        values.append( parseString( t[ 1 ] ) )
                #
                # Contents
@@ -87,7 +90,8 @@ def parseConf( p , meta = None ) :
        #
        while not p.snext( 'eot' ) :
                r = parseNode()
-               if not r : break
+               if not r :
+                       break
                nodes.append( r )
        return ('__root__',None,nodes,None)
 
@@ -106,136 +110,42 @@ def parse( doc , filename = None ) :
        p = basicparser.Parser( tokenMatches , doc )
        p.ignore( 'blank' , 'comment' )
        try :
-               return parseConf( p , filename )
+               result = parseConf( p , filename )
        except basicparser.ParserError , e :
                msg = p.point()
                msg += str( e )
                raise Error( msg )
-
-#--[ Validator ]--------------------------------------------------------------
-
-#
-# Check mail.filter configuration file structure.
-#
-
-from basicvalidator import *
-
-#-----------------------------------------------------------------------------
-
-class Keyword( Validator ) : pass
-
-class Header( Validator ) :
-
-       allowedMatches = [ 'is' , 'contains' , 'match' ]
-
-       def check( self , values ) :
-
-               if len( values ) == 2 :
-                       if values[ 1 ] != 'present' :
-                               error( 'Invalid keyword %r. Expected \'present\'.' % values[ 1 ] )
-               elif len( values ) == 3 :
-                       if values[ 1 ] not in self.allowedMatches :
-                               error( '%r is not an allowed match type. Allowed matches type are: %r'
-                                       % ( values[ 1 ] , self.allowedMatches ) )
-               else :
-                       error( '''header expect 2 or 3 arguments:
-HEADER-NAME MATCH-TYPE MATCH-ARGUMENT
-HEADER-NAME MATCH-FLAG''' )
-
-#-----------------------------------------------------------------------------
-
-class Logical( Validator , MixinNonEmpty ) :
-
-       def descend( self , item ) :
-
-               self.children += 1
-               return ruleValidator( item )
-
-class Reject( Validator , MixinNonEmpty ) :
-
-       def descend( self , item ) :
-
-               self.children += 1
-               return ruleValidator( item )
-
-       def check( self , values ) :
-
-               if len( values ) != 1 :
-                       error( 'reject CODE { .. }' )
-
-class Folder( Validator , MixinNonEmpty ) :
-
-       def descend( self , item ) :
-
-               self.children += 1
-               return ruleValidator( item )
-
-       def check( self , values ) :
-
-               if len( values ) != 1 :
-                       error( 'folder FOLDER-NAME { .. }' )
-
-#-----------------------------------------------------------------------------
-
-def ruleValidator( item ) :
-
-       if item in [ 'broken' , 'infected' , 'spam' , 'all' ] :
-               return Keyword( item )
-       elif item in [ 'or' , 'and' , 'not' ] :
-               return Logical( item )
-       elif item == 'header' :
-               return Header( item )
-       else :
-               error( 'unexpected keyword %r.' % item )
-
-#-----------------------------------------------------------------------------
-
-class Root( Validator ) :
-
-       def descend( self , item ) :
-
-               if item == 'reject' :
-                       return Reject( item )
-               elif item == 'folder' :
-                       return Folder( item )
-               else :
-                       error( 'unexpected keyword %r.' % item )
-
-       def values( self , values ) :
-
-               raise Exception( 'Internal error' )
+       return result
 
 #--[ Read&Write configuration ]-----------------------------------------------
 
-def changedDate( filename ) :
+def lastModificationTime( filename ) :
 
        try :
-               return os.stat( filename )[ stat.ST_CTIME ]
+               result = os.stat( filename )[ stat.ST_MTIME ]
        except :
-               return
+               result = None
+       return result
 
 #
 # Return None | ( tree , isValid )
 #
 def readCachedConfiguration( filename ) :
 
+       result = None
        cachedFilename = filename + '.cache'
        #
        # Check if cached file is older than the source.
        #
-       dateCached = changedDate( cachedFilename )
-       if not dateCached : return
-       dateSource = changedDate( filename )
-       if not dateSource : return
-       if dateCached <= dateSource : return
-       #
-       #
-       #
-       try :
-               r = pickle.load( open( cachedFilename ) )
-       except :
-               return
-       return r
+       dateCached = lastModificationTime( cachedFilename )
+       if dateCached is not None :
+               dateSource = lastModificationTime( filename )
+               if dateSource is not None and dateCached > dateSource :
+                       try :
+                               result = pickle.load( open( cachedFilename ) )
+                       except :
+                               pass
+       return result
 
 def writeCachedConfiguration( filename , tree , isValid ) :
 
@@ -244,7 +154,7 @@ def writeCachedConfiguration( filename , tree , isValid ) :
        except :
                pass
 
-def readConfiguration( filename ) :
+def readConfiguration( filename , validator = None ) :
 
        try :
                #
@@ -266,15 +176,18 @@ def readConfiguration( filename ) :
                        #
                        # 3. Validate it
                        #
-                       basicvalidator.checkConf( conf , Root )
+                       if validator is not None :
+                               basicvalidator.checkConf( conf , validator )
+                               isValid = True
                #
                # 4. Keep cached result
                #
                writeCachedConfiguration( filename , conf , isValid )
        except IOError , e :
                if e[ 0 ] == errno.ENOENT :
-                       return None
-               raise Exception( 'While reading file %s:\n%s' % ( filename , str( e ) ) )
+                       conf = None
+               else :
+                       raise Exception( 'While reading file %s:\n%s' % ( filename , str( e ) ) )
        except Exception , e :
                raise Exception( 'While reading file %s:\n%s' % ( filename , str( e ) ) )
        return conf