Use distutils instead of my own installation script.
[confparser-old] / tuxeenet / confparser.py
1 # -*- coding: utf-8 -*-
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 parseList() :
60
61                 items = []
62                 while 1 :
63                         t = p.next( 'string' , 'integer' , ']' )
64                         if t[ 0 ] == ']' :
65                                 break
66                         elif t[ 0 ] == 'integer' :
67                                 items.append( int( t[ 1 ] ) )
68                         elif t[ 0 ] == 'string' :
69                                 items.append( parseString( t[ 1 ] ) )
70                         else :
71                                 raise NotReached
72                 return items
73
74         def parseNode() :
75
76                 x , y = p.x , p.y
77                 #
78                 # Node name
79                 #
80                 t = p.next( 'keyword' )
81                 kw = t[ 1 ]
82                 #
83                 # Values
84                 #
85                 values = []
86                 while 1 :
87                         t = p.next( 'string' , 'integer' , '[' , '{' , ';' )
88                         if t[ 0 ] in [ '{' , ';' ] :
89                                 break
90                         elif t[ 0 ] == '[' :
91                                 values.append( parseList() )
92                         elif t[ 0 ] == 'integer' :
93                                 values.append( int( t[ 1 ] ) )
94                         elif t[ 0 ] == 'string' :
95                                 values.append( parseString( t[ 1 ] ) )
96                         else :
97                                 raise NotReached
98                 #
99                 # Contents
100                 #
101                 subNodes = []
102                 if t[ 0 ] == '{' :
103                         subNodes = []
104                         while not p.snext( '}' ) :
105                                 r = parseNode()
106                                 subNodes.append( r )
107                 return ( kw , values , subNodes , ( y , x , meta ) )
108
109         nodes = []
110         #
111         # Parse the entire file
112         #
113         while not p.snext( 'eot' ) :
114                 r = parseNode()
115                 if not r :
116                         break
117                 nodes.append( r )
118         return ('__root__',None,nodes,None)
119
120 def parse( doc , filename = None ) :
121
122         kw = r'[_a-zA-Z](?:[-_a-zA-Z0-9]*[_a-zA-Z0-9])?'
123         tokenMatches = {
124                   'eot'     : '$'
125                 , 'blank'   : r'\s+'
126                 , 'integer' : r'[0-9]+'
127                 , 'keyword' : kw
128                 , 'string'  : kw + r"|'(?:[^\\']|\\.)*'"
129                 , 'comment' : r'#[^\n]*(?:\n|$)'
130                 , '{'       : '{'
131                 , '}'       : '}'
132                 , '['       : r'\['
133                 , ']'       : r'\]'
134                 , ';'       : ';'
135         }
136         p = basicparser.Parser( tokenMatches , doc )
137         p.ignore( 'blank' , 'comment' )
138         try :
139                 result = parseConf( p , filename )
140         except basicparser.ParserError , e :
141                 msg = p.point()
142                 msg += str( e )
143                 raise Error( msg )
144         return result
145
146 #--[ Read&Write configuration ]-----------------------------------------------
147
148 def lastModificationTime( filename ) :
149
150         try :
151                 result = os.stat( filename )[ stat.ST_MTIME ]
152         except :
153                 result = None
154         return result
155
156 #
157 # Return None | ( tree , isValid )
158 #
159 def readCachedConfiguration( filename ) :
160
161         result = None
162         cachedFilename = filename + '.cache'
163         #
164         # Check if cached file is older than the source.
165         #
166         dateCached = lastModificationTime( cachedFilename )
167         if dateCached is not None :
168                 dateSource = lastModificationTime( filename )
169                 if dateSource is not None and dateCached > dateSource :
170                         try :
171                                 result = pickle.load( open( cachedFilename ) )
172                         except :
173                                 pass
174         return result
175
176 def writeCachedConfiguration( filename , tree , isValid ) :
177
178         try :
179                 pickle.dump( ( tree , isValid ) , open( filename + '.cache' , 'w' ) )
180         except :
181                 pass
182
183 def readConfiguration( filename , validator = None ) :
184
185         try :
186                 #
187                 # 1. Read from cache file
188                 #
189                 r = readCachedConfiguration( filename )
190                 cached = False
191                 if r is not None :
192                         conf , isValid = r
193                         cached = True
194                 else :
195                         isValid = False
196                         #
197                         # 2. Parse the file
198                         #
199                         conf = open( filename ).read()
200                         conf = parse( conf , filename )
201                 if not isValid :
202                         #
203                         # 3. Validate it
204                         #
205                         if validator is not None :
206                                 basicvalidator.validate( conf , validator )
207                                 isValid = True
208                                 cached = False
209                 #
210                 # 4. Keep cached result
211                 #
212                 if not cached :
213                         writeCachedConfiguration( filename , conf , isValid )
214         except IOError , e :
215                 if e[ 0 ] == errno.ENOENT :
216                         conf = None
217                 else :
218                         raise Exception( 'While reading file %s:\n%s' % ( filename , str( e ) ) )
219         except Exception , e :
220                 raise Exception( 'While reading file %s:\n%s' % ( filename , str( e ) ) )
221         return conf
222
223 #--[ Extended stuff ]---------------------------------------------------------
224
225 def readConfigurationExt( filename , validator = None ) :
226
227         conf = readConfiguration( filename , validator )
228         if conf is not None :
229                 import confparser_ext
230                 conf = confparser_ext.confToNodeset( conf )
231         return conf
232
233 def parseExt( doc , filename = None ) :
234
235         import confparser_ext
236         conf = parse( doc , filename )
237         return confparser_ext.confToNodeset( conf )
238
239 #--[ Dump configuration tree ]------------------------------------------------
240
241 def printTreeInner( t , prt = sys.stdout.write , prefix = '' , verbose = False ) :
242
243         prt( prefix )
244         prt( t[ 0 ] )
245         for kw in t[ 1 ] or [] :
246                 prt( ' ' + str( kw ) )
247         if t[ 2 ] :
248                 prt( ' {' )
249         else :
250                 prt( ' ;' )
251         if t[ 3 ] and verbose :
252                 prt( ' # ' )
253                 if t[ 3 ][ 2 ] :
254                         prt( '%s:' % t[ 3 ][ 2 ] )
255                 prt( '%s:%s' % ( t[ 3 ][ 0 ] , t[ 3 ][ 1 ] ) )
256         prt( '\n' )
257
258         if t[ 2 ] :
259                 for sub in t[ 2 ] :
260                         printTreeInner( sub , prt , prefix + '  ' )
261                 prt( prefix )
262                 prt( '}\n' )
263
264 def printTree( t , prt = sys.stdout.write , prefix = '' , verbose = False ) :
265
266         for sub in t[ 2 ] or [] :
267                 printTreeInner( sub , prt , prefix , verbose )
268
269 # Local Variables:
270 # tab-width: 4
271 # python-indent: 4
272 # End: