import re
import pprint
+import os
import os.path
import sys
"""
#------------------------------------------------------
- def __init__( self, verbose = 0, ):
+ def __init__( self, verbose = 0, base_dir = os.getcwd(), do_includes = False ):
"""Constructor.
+
@param verbose: verbosity level (default: 0)
@type verbose: int
+ @param base_dir: base directory for all non absolute includes (must exists)
+ @type base_dir: str
+ @param do_includes: should include directives be evaluated
+ @type do_includes: bool
+
@return: None
@rtype: None
"""
super( IscConfigParser, self ).__init__( verbose = verbose )
self.current_filename = None
+ self.base_dir = base_dir
+ self.do_includes = do_includes
# Other properties
#------------------------------------------------------
- def parse( self, input ):
- """Read a file in a ISC-like config style.
+ # Getter method for the base directory property
+ @property
+ def base_dir( self ):
+ """The property base_dir is the base directory for all non absolute includes."""
+ return self._base_dir
+
+ #------------------------------------------------------
+ # Setter method for the base directory property
+ @base_dir.setter
+ def base_dir( self, new_base_dir ):
- The input can be either a file-like object or a string. If a string
- is used you can optionally also provide a filename, which will be
- used for raised exceptions.
+ if not os.path.exists( new_base_dir ):
+ raise IscConfigParserError( "Given base directory {0!r} doesn't exists.".format( new_base_dir ) )
+
+ if not os.path.isdir( new_base_dir ):
+ raise IscConfigParserError( "Given base directory {0!r} is not a directory.".format( new_base_dir ) )
+
+ self._base_dir = new_base_dir
+
+ #------------------------------------------------------
+ def parse( self, filename ):
+ """Read a file in a ISC-like config style - it's the main function.
The contents from the file is returned as a standard python dictionary.
- @param input: file-like object or a string
- @type input: str
+ @param filename: filename of the configuration file or '-' or None for standard input
+ @type filename: str or None
+
@return: the contents from the file as a standard python dictionary
- @rtype: dict
+ @rtype: dict
"""
result = {}
- if input is not None and not os.path.isfile( input ):
- raise IscConfigParserError( "File {0!r} not found.".format( input ) )
-
fd = None
- if input is not None:
- fd = open( input, "r" )
- self.current_filename = input
- else:
+
+ if ( filename is None ) or ( filename == '-' ):
fd = sys.stdin
- self.current_filename = '<stdin>'
- self.current_fd = fd
-
+ current_filename = '-'
+ else:
+ fd = self._open_configfile( filename )
+ current_filename = filename
+
+ self._current_filename = current_filename
- self.logger.debug( "Reading configuration file {0!r} ...".format( self.current_filename ) )
+ self._files = {
+ current_filename: {
+ 'fd': fd,
+ 'current_rownum': 0,
+ 'next_rownum': 1,
+ },
+ }
+ self._in_quoting = False
+ self.logger.debug( "Reading configuration file {0!r} ...".format( self._current_filename ) )
+ self._read_current_file()
- if input is not None:
+ if filename is not None:
fd.close()
- del self.current_fd
+
+ del self._files
+ del self._current_filename
return result
#------------------------------------------------------
- def read_next_line( self ):
+ def _read_current_file( self ):
+
+ filename = self._current_filename
+ result = {}
+ self.logger.debug( "Reading file {0!r} ...".format( filename ) )
+
+ pp = pprint.PrettyPrinter( indent = 4, depth = 6, width = 120 )
+ line_tokens = self._read_next_line()
+ while line_tokens is not None:
+ rownum = self._files[filename]['current_rownum']
+ if ( self.verbose > 1 ) and ( len(line_tokens) > 0 ):
+ self.logger.debug( "%4d: %s" % ( rownum, pp.pformat( line_tokens ) ) )
+ line_tokens = self._read_next_line()
+
+ return result
+
+ #------------------------------------------------------
+ def _open_configfile( self, filename ):
+ """Checks the availablity of the given filename, and opens it readonly.
+ Raises an IscConfigParserError exception, if the file isn't available, or another
+ exception if the open fails.
+
+ @param filename: the filename to open
+ @type filename: str
+
+ @return: The file descriptor of the opened file
+ @rtype: file object
+ """
+
+ if not os.path.isfile( filename ):
+ raise IscConfigParserError( "File {0!r} not found.".format( filename ) )
+
+ fd = None
+
+ if not os.access( filename, os.R_OK ):
+ raise IscConfigParserError( "File {0!r} is not readable.".format( filename ) )
+
+ fd = open( filename, "r" )
+
+ return fd
+
+ #------------------------------------------------------
+ def _read_next_line( self ):
"""Read the next line from current file handle.
- @return: the content of this line
- @rtype: str
+ Comments are removed.
+ The number of the beginning of line is after in
+ self._files[ self._current_filename ]['current_rownum'] (except in case of EOF).
+
+ @return: the content of this line or None if EOF
+ @rtype: str or None
"""
- fd = self.current_fd
+ filename = self._current_filename
+
+ if not filename in self._files:
+ raise Exception( "File {0!r} is not opened.".format( filename ) )
+
+ fd = self._files[filename]['fd']
+ current_rownum = self._files[filename]['next_rownum']
+ next_rownum = current_rownum + 1
+
+ line = fd.readline()
+
+ # EOF reached
+ if line == '' or line is None:
+ return None
+
+ if ( self.verbose > 2 ):
+ self.logger.debug( "read line {0}: {1!r}".format( current_rownum, line ) )
+
+ res_array = []
+
+ line = line.lstrip()
+ in_comment = False
+ while line != '':
+
+ if ( self.verbose > 3 ):
+ self.logger.debug( "Rest of line: {0!r}".format( line ) )
+
+ # Remoce C++-like comments
+ match = re.search( r'^[^"]*?//', line )
+ if match is not None:
+ line = re.sub( r'^([^"]*?)//.*\s*', r'\1', line )
+ continue
+
+ # Remove Perl-like comments
+ match = re.search( r'^[^"]*?#', line )
+ if match is not None:
+ line = re.sub( r'^([^"]*?)#.*\s*', r'\1', line )
+ continue
+
+ # Remove C-like comments
+ match = re.search( r'^[^"]*?/\*', line )
+ if match is not None:
+
+ line = re.sub( r'^([^"]*?)/\*[^\*]*', r'\1', line )
+
+ do_comment_loop = True
+ while do_comment_loop:
+ c_comment_end_match = re.search( r'^[^\*]*?\*/', line )
+ if c_comment_end_match is not None:
+ line = re.sub( r'^[^\*]*?\*/', '', line )
+ do_comment_loop = False
+ else:
+ new_line = fd.readline()
+ if new_line == '' or new_line is None:
+ raise IscConfigParserError( "Unbalanced C-like comment in {0!r}, starting at line {1}.".format( filename, current_rownum ) )
+ if ( self.verbose > 2 ):
+ self.logger.debug( "read line {0}: {1!r}".format( next_rownum, new_line ) )
+ next_rownum += 1
+ line += new_line
+ if ( self.verbose > 2 ):
+ self.logger.debug( "new line: {0!r}".format( line ) )
+
+ line = line.lstrip()
+
+ match = re.search( r'^([^"\s]+)', line )
+ if match is not None:
+ res_array.append( match.group(1) )
+ line = re.sub( r'^[^"\s]+\s*', '', line )
+ continue
+
+ match = re.search( r'^\s*"((?:[^"]|(?<=\\"))*)"', line )
+ if match is not None:
+ res_array.append( match.group(1) )
+ line = re.sub( r'^\s*"(?:[^"]|(?<=\\"))*\s*"', '', line )
+
+# # remove whitespaces from the beginning and end of the line
+# if not self._in_quoting:
+# result = result.strip( " \t\n\r" )
+
+# # Remove Perl-like comments
+# if re.search( r'^(:?[^"]*(:?"[^"]*")?)*?#', result ):
+# result = re.sub( r'^((:?[^"]*(:?"[^"]*")?)*?)#.*', r'\1', result )
+
+# # Remoce C++-like comments
+# if re.search( r'^(?:[^"]*(:?"[^"]*")?)*?//', result ):
+# result = re.sub( r'^((:?[^"]*(:?"[^"]*")?)*?)//.*', r'\1', result )
+
+ self._files[filename]['current_rownum'] = current_rownum
+ self._files[filename]['next_rownum'] = next_rownum
+
+ return res_array
#========================================================================
-def parse_isc_config( input, verbose = 0 ):
+def parse_isc_config( input, verbose = 0, base_dir = os.getcwd(), do_includes = False ):
'''Wrapper for IscConfigParser.parse()
@param input: file-like object or a string
@type input: str
@param verbose: verbosity level (default: 0)
@type verbose: int
+ @param base_dir: base directory for all non absolute includes (must exists)
+ @type base_dir: str
+ @param do_includes: should include directives be evaluated
+ @type do_includes: bool
+
@return: the contents from the file as a standard python dictionary
@rtype: dict
'''
- parser = IscConfigParser( verbose = verbose )
+ parser = IscConfigParser( verbose = verbose, base_dir = base_dir, do_includes = do_includes )
return parser.parse( input )
#========================================================================
verbose = opt_parser.options.verbose
files = []
+ pp = pprint.PrettyPrinter( indent = 4, depth = 6, width = 120 )
if opt_parser.args is not None and len( opt_parser.args ) > 0:
- files = opt_parser.args[1:]
+ files = opt_parser.args[:]
else:
files = [ os.sep + os.path.join( 'etc', 'bind', 'named.conf' ) ]
- pp = pprint.PrettyPrinter( indent = 4, depth = 6, width = 120 )
+ if verbose > 1:
+ print "Configfiles to read: %s" % ( pp.pformat( files ) )
+
for conffile in files:
conf = parse_isc_config( conffile, verbose = verbose )
print "Read configuration: \n%s" % ( pp.pformat( conf ) )