Integers are now handled correctly by printTree.
[confparser-old] / confparser.py
index d9b213b..1ddcd9d 100644 (file)
@@ -1,19 +1,39 @@
 # -*- coding: iso-8859-1 -*-
 
-import basicparser
-import basicvalidator
+#
+# Configuration parser
+# Copyright (C) 2005  Frédéric Jolliton <frederic@jolliton.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
 
 import re
 import types
 import sys
 import os
 import stat
+import errno
 
 try :
        import cPickle as pickle
 except :
        import pickle
 
+import basicparser
+import basicvalidator
+
 class Error( Exception ) : pass
 
 #
@@ -25,6 +45,8 @@ class Error( Exception ) : pass
 
 def parseString( s ) :
 
+       '''Strip string delimiters.'''
+
        if s.startswith( "'" ) :
                return s[ 1 : -1 ]
        elif s.startswith( '"' ) :
@@ -47,9 +69,13 @@ def parseConf( p , meta = None ) :
                #
                values = []
                while 1 :
-                       t = p.next( 'string' , '{' , ';' )
-                       if t[ 0 ] in [ '{' , ';' ] : break
-                       values.append( parseString( t[ 1 ] ) )
+                       t = p.next( 'string' , 'integer' , '{' , ';' )
+                       if t[ 0 ] in [ '{' , ';' ] :
+                               break
+                       if t[ 0 ] == 'integer' :
+                               values.append( int( t[ 1 ] ) )
+                       else :
+                               values.append( parseString( t[ 1 ] ) )
                #
                # Contents
                #
@@ -67,7 +93,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)
 
@@ -76,6 +103,7 @@ def parse( doc , filename = None ) :
        tokenMatches = {
                'eot'     : '$' ,
                'blank'   : r'\s+' ,
+               'integer' : r'[0-9]+' ,
                'keyword' : r'[_a-zA-Z][_a-zA-Z0-9]*' ,
                'string'  : r'[_a-zA-Z][_a-zA-Z0-9]*|\'(?:[^\\\']|\\.)*\'' ,
                'comment' : r'#[^\n]*(?:\n|$)' ,
@@ -86,136 +114,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 ) :
 
@@ -224,7 +158,7 @@ def writeCachedConfiguration( filename , tree , isValid ) :
        except :
                pass
 
-def readConfiguration( filename ) :
+def readConfiguration( filename , validator = None ) :
 
        try :
                #
@@ -232,7 +166,7 @@ def readConfiguration( filename ) :
                #
                r = readCachedConfiguration( filename )
                cached = False
-               if r :
+               if r is not None :
                        conf , isValid = r
                        cached = True
                else :
@@ -246,11 +180,20 @@ def readConfiguration( filename ) :
                        #
                        # 3. Validate it
                        #
-                       basicvalidator.checkConf( conf , Root )
+                       if validator is not None :
+                               basicvalidator.validate( conf , validator )
+                               isValid = True
+                               cached = False
                #
                # 4. Keep cached result
                #
-               writeCachedConfiguration( filename , conf , isValid )
+               if not cached :
+                       writeCachedConfiguration( filename , conf , isValid )
+       except IOError , e :
+               if e[ 0 ] == errno.ENOENT :
+                       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
@@ -262,7 +205,7 @@ def printTreeInner( t , prt = sys.stdout.write , prefix = '' ) :
        prt( prefix )
        prt( t[ 0 ] )
        for kw in t[ 1 ] :
-               prt( ' ' + kw )
+               prt( ' ' + str( kw ) )
        if t[ 2 ] :
                prt( ' {' )
        else :
@@ -284,11 +227,3 @@ def printTree( t ) :
 
        for sub in t[ 2 ] or [] :
                printTreeInner( sub )
-
-def main() :
-
-       doc = open( 'fred.mf' ).read()
-       printTree( parse( doc , 'fred.mf' ) )
-
-if __name__ == '__main__' :
-       main()