From: Frank Brehm Date: Thu, 27 Jan 2011 17:29:40 +0000 (+0000) Subject: Weitergekommen X-Git-Url: https://git.uhu-banane.org/?a=commitdiff_plain;h=a74dc85c3ca2a81ac98bb5f96b179cd66df29c5a;p=my-stuff%2Fisc-config-parser.git Weitergekommen git-svn-id: http://svn.brehm-online.com/svn/my-stuff/python/IscConfigParser/trunk@190 ec8d2aa5-1599-4edb-8739-2b3a1bc399aa --- diff --git a/IscConfigParser.py b/IscConfigParser.py index 71d53bf..e432f74 100755 --- a/IscConfigParser.py +++ b/IscConfigParser.py @@ -15,6 +15,7 @@ import re import pprint +import os import os.path import sys @@ -42,10 +43,16 @@ class IscConfigParser(LoggingObject): """ #------------------------------------------------------ - 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 """ @@ -53,73 +60,242 @@ class IscConfigParser(LoggingObject): 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 = '' - 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 ) #======================================================================== @@ -138,12 +314,15 @@ if __name__ == "__main__": 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 ) )