04e633ee3d459f70fe2d5caaa86fe0c31a7a7f8d
[confparser-old] / confparser.py
1 # -*- coding: iso-8859-1 -*-
2
3 #
4 # Configuration parser
5 # Copyright (C) 2005  Frédéric Jolliton <frederic@jolliton.com>
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20 #
21
22 import re
23 import types
24 import sys
25 import os
26 import stat
27 import errno
28
29 try :
30         import cPickle as pickle
31 except :
32         import pickle
33
34 import basicparser
35 import basicvalidator
36
37 class Error( Exception ) : pass
38
39 #
40 # Configuration parser check *syntax*.
41 # Validator check *structure*, and eventually some values.
42 #
43
44 #--[ Parser ]-----------------------------------------------------------------
45
46 def parseString( s ) :
47
48         '''Strip string delimiters.'''
49
50         if s.startswith( "'" ) :
51                 return s[ 1 : -1 ]
52         elif s.startswith( '"' ) :
53                 return s[ 1 : -1 ]
54         else :
55                 return s
56
57 def parseConf( p , meta = None ) :
58
59         def parseNode() :
60
61                 x , y = p.x , p.y
62                 #
63                 # Node name
64                 #
65                 t = p.next( 'keyword' )
66                 kw = t[ 1 ]
67                 #
68                 # Values
69                 #
70                 values = []
71                 while 1 :
72                         t = p.next( 'string' , '{' , ';' )
73                         if t[ 0 ] in [ '{' , ';' ] :
74                                 break
75                         values.append( parseString( t[ 1 ] ) )
76                 #
77                 # Contents
78                 #
79                 subNodes = []
80                 if t[ 0 ] == '{' :
81                         subNodes = []
82                         while not p.snext( '}' ) :
83                                 r = parseNode()
84                                 subNodes.append( r )
85                 return ( kw , values , subNodes , ( y , x , meta ) )
86
87         nodes = []
88         #
89         # Parse the entire file
90         #
91         while not p.snext( 'eot' ) :
92                 r = parseNode()
93                 if not r :
94                         break
95                 nodes.append( r )
96         return ('__root__',None,nodes,None)
97
98 def parse( doc , filename = None ) :
99
100         tokenMatches = {
101                 'eot'     : '$' ,
102                 'blank'   : r'\s+' ,
103                 'keyword' : r'[_a-zA-Z][_a-zA-Z0-9]*' ,
104                 'string'  : r'[_a-zA-Z][_a-zA-Z0-9]*|\'(?:[^\\\']|\\.)*\'' ,
105                 'comment' : r'#[^\n]*(?:\n|$)' ,
106                 '{'       : '{' ,
107                 '}'       : '}' ,
108                 ';'       : ';'
109         }
110         p = basicparser.Parser( tokenMatches , doc )
111         p.ignore( 'blank' , 'comment' )
112         try :
113                 result = parseConf( p , filename )
114         except basicparser.ParserError , e :
115                 msg = p.point()
116                 msg += str( e )
117                 raise Error( msg )
118         return result
119
120 #--[ Read&Write configuration ]-----------------------------------------------
121
122 def lastModificationTime( filename ) :
123
124         try :
125                 result = os.stat( filename )[ stat.ST_MTIME ]
126         except :
127                 result = None
128         return result
129
130 #
131 # Return None | ( tree , isValid )
132 #
133 def readCachedConfiguration( filename ) :
134
135         result = None
136         cachedFilename = filename + '.cache'
137         #
138         # Check if cached file is older than the source.
139         #
140         dateCached = lastModificationTime( cachedFilename )
141         if dateCached is not None :
142                 dateSource = lastModificationTime( filename )
143                 if dateSource is not None and dateCached > dateSource :
144                         try :
145                                 result = pickle.load( open( cachedFilename ) )
146                         except :
147                                 pass
148         return result
149
150 def writeCachedConfiguration( filename , tree , isValid ) :
151
152         try :
153                 pickle.dump( ( tree , isValid ) , open( filename + '.cache' , 'w' ) )
154         except :
155                 pass
156
157 def readConfiguration( filename , validator = None ) :
158
159         try :
160                 #
161                 # 1. Read from cache file
162                 #
163                 r = readCachedConfiguration( filename )
164                 cached = False
165                 if r :
166                         conf , isValid = r
167                         cached = True
168                 else :
169                         isValid = False
170                         #
171                         # 2. Parse the file
172                         #
173                         conf = open( filename ).read()
174                         conf = parse( conf , filename )
175                 if not isValid :
176                         #
177                         # 3. Validate it
178                         #
179                         if validator is not None :
180                                 basicvalidator.validate( conf , validator )
181                                 isValid = True
182                 #
183                 # 4. Keep cached result
184                 #
185                 writeCachedConfiguration( filename , conf , isValid )
186         except IOError , e :
187                 if e[ 0 ] == errno.ENOENT :
188                         conf = None
189                 else :
190                         raise Exception( 'While reading file %s:\n%s' % ( filename , str( e ) ) )
191         except Exception , e :
192                 raise Exception( 'While reading file %s:\n%s' % ( filename , str( e ) ) )
193         return conf
194
195 #--[ Dump configuration tree ]------------------------------------------------
196
197 def printTreeInner( t , prt = sys.stdout.write , prefix = '' ) :
198
199         prt( prefix )
200         prt( t[ 0 ] )
201         for kw in t[ 1 ] :
202                 prt( ' ' + kw )
203         if t[ 2 ] :
204                 prt( ' {' )
205         else :
206                 prt( ' ;' )
207         if t[ 3 ] :
208                 prt( ' # ' )
209                 if t[ 3 ][ 2 ] :
210                         prt( '%s:' % t[ 3 ][ 2 ] )
211                 prt( '%s:%s' % ( t[ 3 ][ 0 ] , t[ 3 ][ 1 ] ) )
212         prt( '\n' )
213
214         if t[ 2 ] :
215                 for sub in t[ 2 ] :
216                         printTreeInner( sub , prt , prefix + '  ' )
217                 prt( prefix )
218                 prt( '}\n' )
219
220 def printTree( t ) :
221
222         for sub in t[ 2 ] or [] :
223                 printTreeInner( sub )