Package Biskit :: Module SettingsParser
[hide private]
[frames] | no frames]

Source Code for Module Biskit.SettingsParser

  1  ## 
  2  ## Biskit, a toolkit for the manipulation of macromolecular structures 
  3  ## Copyright (C) 2004-2005 Raik Gruenberg & Johan Leckner 
  4  ## 
  5  ## This program is free software; you can redistribute it and/or 
  6  ## modify it under the terms of the GNU General Public License as 
  7  ## published by the Free Software Foundation; either version 2 of the 
  8  ## License, or any later version. 
  9  ## 
 10  ## This program is distributed in the hope that it will be useful, 
 11  ## but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 13  ## General Public License for more details. 
 14  ## 
 15  ## You find a copy of the GNU General Public License in the file 
 16  ## license.txt along with this program; if not, write to the Free 
 17  ## Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 18  ## 
 19  ## 
 20  ## last $Author: graik $ 
 21  ## last $Date: 2006/09/11 17:46:13 $ 
 22  ## $Revision: 1.6 $ 
 23  """ 
 24  Parse a Biskit settings file. 
 25  """ 
 26   
 27  import os 
 28  import user 
 29  import sys 
 30  import ConfigParser 
 31   
 32  import Biskit as B 
 33  import Biskit.tools as T 
 34   
35 -class SettingsError( Exception ):
36 pass
37
38 -class InvalidType( SettingsError ):
39 pass
40
41 -class InvalidValue( SettingsError ):
42 pass
43
44 -class InvalidFile( SettingsError ):
45 pass
46
47 -class SettingsWarning( SettingsError ):
48 pass
49
50 -class InvalidPath( SettingsWarning ):
51 pass
52
53 -class InvalidBinary( SettingsWarning ):
54 pass
55 56
57 -class CaseSensitiveConfigParser( ConfigParser.SafeConfigParser ):
58 """ 59 Change ConfigParser so that it doesn't convert option names to lower case. 60 """
61 - def optionxform(self, optionstr):
62 return optionstr
63
64 -class Setting:
65 """ 66 Simple container for a single parameter 67 """ 68 ## types of settings // section name in cfg file 69 NORMAL = 'NORMAL' 70 PATH = 'PATHS' 71 BIN = 'BINARIES' 72
73 - def __init__( self, name=None, value=None, vtype=str, comment=None, 74 error=None, section=NORMAL ):
75 self.name = name 76 self.value = value 77 self.vtype = vtype 78 self.comment = comment 79 self.error = error 80 self.section = section
81
82 - def typeCast( self, vtype ):
83 """ 84 Recast value to a new type. Value None remains unchanged. 85 @param vtype: new type for value 86 @type vtype: type 87 @raise InvalidValue: if current value is incompatible with vtype 88 """ 89 try: 90 if not self.value is None: 91 self.value = vtype( self.value ) 92 self.vtype = vtype 93 except ValueError, e: 94 raise InvalidValue, '%s: cannot convert "%s" to %r.' %\ 95 (self.name,self.value,vtype)
96
97 - def __repr__( self, tab='' ):
98 error = '' 99 if self.error: error = '(!)' 100 101 comment = self.comment or '' 102 if comment: comment = ' # '+comment 103 104 return '%s%s = %s%s(%s)%s%s' %\ 105 (error, self.name, tab, self.vtype.__name__, str(self.value),\ 106 tab, comment)
107
108 - def __str__( self ):
109 return self.__repr__( tab='\t' )
110
111 - def __cmp__( self, other ):
112 """ 113 Compare Setting instances by their name. 114 @return: 1,0,-1 115 @rtype: int 116 """ 117 if isinstance( other, self.__class__ ): 118 return cmp( self.name, other.name ) 119 120 return cmp( self, other )
121 122
123 - def formatted( self ):
124 """ 125 @return: parameter formatted for setting file 126 @rtype: str 127 """ 128 comment = '' 129 error = '' 130 name = self.name 131 value = self.value or '' 132 133 if self.vtype != str: 134 name = self.vtype.__name__ + '-' + name 135 136 if self.comment: 137 comment = '\t## ' + self.comment 138 139 if self.error: 140 error = '\t#! ' + self.error + ' !' 141 142 return '%s = %s%s%s' % (name, str(value), comment, error)
143 144
145 -class SettingsParser(object):
146 """ 147 A config file parser on steroids -- performs the following tasks: 148 149 1. read a ini-style settings file 150 2. type-cast options (e.g. of the form int-some_name into int(some_name)) 151 3. validate that all entries of section [PATHS] point to existing paths 152 4. absolutize all valid paths 153 5. validate that all entries of section [BINARIES] point to binaries 154 """ 155
156 - def __init__(self, ini):
157 158 self.f_ini = T.absfile( ini ) 159 self.result = {}
160 161
162 - def __validPath( self, v ):
163 """ 164 @param v: potential path name 165 @type v: str 166 167 @return: validated absolute Path 168 @rtype : str 169 170 @raise InvalidPath: if path is not found 171 """ 172 try: 173 v = T.absfile( v ) 174 if not v or not os.path.exists( v ): 175 raise InvalidPath, 'invalid path %r' % v 176 177 return v 178 179 except InvalidPath, e: 180 raise 181 except Exception, e: 182 raise InvalidPath, 'error during path validation: %r' % str(e)
183 184
185 - def __validBinary( self, v ):
186 """ 187 @param v: potential binary path 188 @type v: str 189 190 @return: validated absolute path to existing binary 191 @rtype : str 192 193 @raise InvalidBinary: if path is not found 194 """ 195 try: 196 if not v: 197 raise IOError, 'empty path' 198 199 return T.absbinary( v ) 200 201 except IOError, msg: 202 raise InvalidBinary( str(msg) )
203 204
205 - def __type( self, option, default=str ):
206 """ 207 Extract type from option name. 208 209 @param option: name of parameter 210 @type option: str 211 @param default: default type [str] 212 @type default: type 213 214 @return: type, stripped option name (e.g. 'int_var1' -> int, 'var1') 215 @rtype: type, str 216 217 @raise TypeError: if type cannot be interpreted 218 """ 219 t = default 220 o = option 221 222 if option.count('-') > 0: 223 224 try: 225 226 splt = option.split('-') 227 228 s = splt[0] 229 o = ''.join( splt[1:] ) 230 231 t = eval( s ) 232 233 if not type(t) is type: 234 raise TypeError, '%s is not a valid type' % s 235 236 except Exception, e: 237 raise TypeError, 'Cannot extract type from %s: %r'\ 238 % option, e 239 240 return t, o
241 242
243 - def __process( self, option, value, section=Setting.NORMAL ):
244 """ 245 @param option: option name 246 @type option: str 247 248 @param value: option value 249 @type value: str 250 251 @param section: which section are we working on 252 @type section: str 253 254 @return: new setting 255 @rtype: Setting 256 257 @raise SettingsError: InvalidType or Value 258 """ 259 r = Setting( section=section ) 260 261 try: 262 263 x = value.split('#') ## split off comments 264 r.value = x[0].strip() or None ## don't return empty strings 265 266 if len(x) > 1: 267 r.comment = ''.join( x[1:] ) 268 269 vtype, r.name = self.__type( option ) 270 r.typeCast( vtype ) 271 272 if section == Setting.PATH: 273 r.value = self.__validPath( r.value ) 274 275 if section == Setting.BIN: 276 r.value = self.__validBinary( r.value ) 277 278 except SettingsWarning, e: ## catch and record warnings 279 r.error = str(e) 280 281 return r
282 283
284 - def __processSection( self, items, section=Setting.NORMAL, verbose=False ):
285 """ 286 @param items: section comming from ConfigParser 287 @type items: [ ( str, str ) ] 288 289 @param section: which config section are we working on? 290 @type section: str 291 292 @return: validated path values 293 @rtype : dict, {str: Setting} 294 """ 295 r = {} 296 297 for name, value in items: 298 299 s = self.__process( name, value, section ) 300 301 r[ s.name ] = s 302 303 if verbose and s.error: 304 B.EHandler.warning( s.error, trace=0, error=0 ) 305 306 return r
307 308
309 - def parse( self, verbose=False ):
310 """ 311 @param verbose: print warning messages via Biskit.EHandler 312 @type verbose: bool 313 314 @return: dict of type-cast params contained in fini 315 @rtype: dict, {str: Setting} 316 317 @raise IOError: if the settings file does not exist 318 @raise SettingsError: (InvalidFile, InvalidValue, InvalidType) 319 """ 320 try: 321 ## read from file 322 c = CaseSensitiveConfigParser() 323 324 if c.read( self.f_ini ) != [ self.f_ini ]: 325 raise IOError, 'Settings file %s not found.' % self.f_ini 326 327 for section in c.sections(): 328 329 self.result.update( 330 self.__processSection( c.items(section), section) ) 331 332 except ConfigParser.Error, e: 333 raise InvalidFile, 'Error parsing settings file %s: ' %\ 334 self.f_ini + str(e) 335 336 return self.result
337
338 - def __repr__( self, tab='\t' ):
339 r = super( SettingsParser, self).__repr__() 340 err = len( [ s for s in self.result.values() if s.error ] ) 341 r += ' -- %i entries, (!) %i errors' % (len( self.result ), err) 342 343 values = self.result.values() 344 values.sort() 345 346 for v in values: 347 r+= T.clipStr( '\n - %s' % str(v), 75) 348 349 return r
350 351 ############# 352 ## TESTING 353 ############# 354
355 -class Test:
356 """ 357 Test class 358 """ 359
360 - def run( self ):
361 """ 362 run function test 363 364 @return: 1 365 @rtype: int 366 """ 367 p = SettingsParser( T.projectRoot()+'/external/defaults/settings.cfg') 368 369 p.parse() 370 371 t = p.result.get('testparam', Setting()) 372 373 globals().update( locals() ) 374 375 return t.name, t.value
376 377
378 - def expected_result( self ):
379 """ 380 Precalculated result to check for consistent performance. 381 382 @return: 1 383 @rtype: int 384 """ 385 return 'testparam', 42
386 387 388 389 if __name__ == '__main__': 390 391 test = Test() 392 393 assert test.run( ) == test.expected_result() 394