# PYINI -- Python INI file parser with support for <<(ID)..(ID) block comments # # Michael Thomas # Center for Computation & Technology # Louisiana State University # # import sys import re import libutil class IniParser: def __init__(self, filename=None): global parser parser = self self.filename = filename if self.filename: self.OpenFile() self.PerformParse() else: self.InitBlankParser() def InitBlankParser(self): self.parser = InternalParser() def PerformParse(self): self.parser = InternalParser(self.fptr) self.parser.Parse() def OpenFile(self): try: self.fptr = open(self.filename, 'r') except IOError: print "Could not open %s for reading" % self.filename sys.exit(1) def ParseFile(self, file): self.filename = file self.OpenFile() self.PerformParse() def UpdateFromIni(self, iniFile, importSections=False): uIniParser = self.__class__(iniFile) sections = uIniParser.GetSections() if "default" in sections and importSections == True: keys = uIniParser.GetSectionKeys("default") ess = self.GetSections() for ss in ess: for k in keys: io = uIniParser.GetOption("default", k) self.parser.WriteKey(ss, k, io) try: sections.remove("default") except: pass # if they are global options, we merrrrgeeeeee very carefully. gkeys = uIniParser.GetGlobalKeys() for gk in gkeys: gio = uIniParser.GetGlobalOption(gk) #print "getting option %s" % gk io = self.GetGlobalOption(gk) if io == None: continue if type(io.Value) == list: #convert to list gio.ConvertToList() ll = io.Value ll.extend(gio.Value) io.UpdateValue(ll) #print "%s: after extending, io is now: %s" % (self, io.Value) self.parser.WriteKey(None, gk, io) else: print "Error when attempting to merge udb config key %s: configuration key %s is internally not a list" % (gk, gk) sys.exit(1) for s in sections: keys = uIniParser.GetSectionKeys(s) if importSections == True: # if import is set to true, then create the section # this function will not clobber an already existing section, so its safe # to use blindly self.parser.InitSection(s) if self.HasSection(s): for k in keys: io = uIniParser.GetOption(s, k) #print "updating %s.%s" % (s, k) self.parser.WriteKey(s, k, io) continue # more "readable" alias for self.CheckExistence def HasOption(self, section, key): return self.CheckExistence(section, key) def HasSection(self, section): return self.parser.Section.has_key(section) def GetOptionByPath(self, path): # path is in section.key format if path.count(".") == 0: return self.GetGlobalOption(path) else: parts = path.split(".") return self.GetSectionOption(parts[0], parts[1]) def GetSectionAsDict(self, section): if self.HasSection(section): return self.parser.Section[section] return None def GetOption(self, section, key): if section == None: if self.CheckExistence(None, key): return self.parser.Globals[key] else: if self.CheckExistence(section, key): return self.parser.Section[section][key] def GetGlobalOption(self, key): return self.GetOption(None, key) def GetSectionOption(self, section, key): return self.GetOption(section, key) def CheckExistence(self, section, key): if section == None: if self.parser.Globals.has_key(key): return True else: if self.parser.Section.has_key(section): if key == None: return True if self.parser.Section[section].has_key(key): return True return False def GetSections(self): return self.parser.Section.keys() def GetGlobalKeys(self): return self.GetKeys(None) def GetSectionKeys(self, section): return self.GetKeys(section) def GetKeys(self, section): if section == None: return self.parser.Globals.keys() if self.CheckExistence(section, None): return self.parser.Section[section].keys() return None def GetIniAsString(self): return self.parser.PrintParsedIni() def PrintIni(self): print self.parser.PrintParsedIni() class IniOption: def __init__(self, section, key, value, IsBlock): #print "making ini option %s.%s with value %s" % (section, key, value) global parser if value == None: value = "" if value is not str: value = str(value) self.IsQuoted = False self.Key = key self.IsBlock = IsBlock self.BlockIdentifier = None self.Parser = parser # strip any trailing non-quoted space off the end of the string. self.OriginalValue = value.rstrip() self.InterpretQuotes() self.key = self.Key self.value = self.Value def UpdateValue(self, value): self.Value = self.value = value def ConvertToList(self): if type(self.Value) == list: return if self.Value == None: self.UpdateValue([]) return if self.IsBlock: value = self.Value lines = value.split("\n") else: if len(self.Value) == 0: lines = [] else: lines = [self.Value] self.UpdateValue(lines) def InterpretQuotes(self): if self.OriginalValue == None or len(self.OriginalValue) == 0: self.Value = self.OriginalValue return if self.OriginalValue[0] == "'" or self.OriginalValue[0] == '"': #print "value %s has quotes" % value try: vv = eval(self.OriginalValue) self.Value = vv self.IsQuoted = True except Exception: error = "Could not interpret quoted string: %s" % self.OriginalValue print "Syntax error on line %s of %s: %s" % (self.Parser.parser.CurrentLineNumber, self.Parser.filename, error) sys.exit(1) else: self.Value = self.OriginalValue class IniSyntaxChecker: def __init__(self, syntaxfile, inifile, importDefaults=False): self.IniFile = inifile self.SyntaxFile = syntaxfile self.ImportDefaults = importDefaults def SyntaxCheck(self): # init our parser self.IniParser = IniParser(self.IniFile) self.SyntaxParser = IniParser(self.SyntaxFile) # lets get the sections from the Syntax Parser # each section represents a key that each section inside the # ini being checked should possess. syntax_keys = self.SyntaxParser.GetSections() ini_sections = self.IniParser.GetSections() if len(ini_sections) == 0: print "Warning: checked ini %s contains no sections" % self.IniFile return True for ini_section in ini_sections: for skey in syntax_keys: io = self.IniParser.GetOption(ini_section, skey) #if io == None: #print "Warning: None for GetOption on section %s, key %s" % (ini_section, skey) # lets gather some information about it. entry = self.GetSyntaxEntry(skey) # Self-test #die if $necessity eq 'required' and defined $keydesc->{'default'}; if entry['necessity'] == 'required' and entry.has_key('default'): self.SyntaxError("necessity for key %s is required, but a default entry was provided" % skey) #die if $type ne 'string' and defined $keydesc->{'pattern'}; if entry['type'] != 'string' and entry.has_key('pattern'): self.SyntaxError("cannot define a pattern on a non-string key %s" % skey) #die if defined $keydesc->{'default'} and # $type eq 'string' and defined $keydesc->{'pattern'} and # $default !~ $pattern; if entry.has_key('default') and entry['type'] == 'string' \ and entry.has_key('pattern') and not(self.PatternMatches(entry['pattern'], entry['default'])): self.SyntaxError("specified default value %s for key %s does not match converted pattern %s" \ % (entry['default'], skey, libutil.ReConvert(entry['pattern']))) #die if $type eq 'string' and defined $keydesc->{'pattern'} and # $example !~ $pattern; if not(entry.has_key('example')): self.SyntaxError("specified key %s does not have an example defined") if entry['type'] == 'string' and entry.has_key('pattern') and not(self.PatternMatches(entry['pattern'], entry['example'])): self.SyntaxError("specified example value %s for key %s does not match converted pattern %s" \ % (entry['example'], skey, libutil.ReConvert(entry['pattern']))) # moving along -- type check our actual entry # check if required if entry['necessity'] == 'required' and io == None: self.SyntaxError("required key %s in section %s is missing" % (skey, ini_section)) # type check # if the type is a string/any, then we just accept anything. if entry.has_key('type') and (entry['type'] != 'string' and entry['type'] != 'any') and io != None: type = entry['type'] value = io.Value if type == "int": try: int(value) except ValueError: self.SyntaxError("value for key %s in section %s is not of required type %s" % (skey, ini_section, type)) if type == "double" or type == "float": try: float(value) except TypeError: self.SyntaxError("value for key %s in section %s is not of required type %s" % (skey, ini_section, type)) # check if matches pattern if there is a pattern if entry.has_key('pattern') and io != None: value = io.Value if not(self.PatternMatches(entry['pattern'], value)): self.SyntaxError("specified value %s for key %s does not match converted pattern %s" % (value, skey, libutil.ReConvert(entry['pattern']))) # import default if self.ImportDefaults: #print "importing defaults for section %s, key %s" % (ini_section, skey) if io == None: #print "for section %s, key %s, importing a default value.." % (ini_section, skey) if entry.has_key('default'): io = IniOption(ini_section, skey, entry['default'], False) # if it's default #print "Importing default value %s into section %s, key %s" % (entry['default'], ini_section, skey) self.IniParser.parser.WriteKey(ini_section, skey, io) def PatternMatches(self, pattern, value): cpattern = libutil.ReConvert(pattern) #print "matching %s against pattern %s" % (value, cpattern) try: p = re.compile(cpattern) except: print "pyini.py[%s]: could not interpret pattern %s, converted from orig pattern %s" % (libutil.LineNumber(), cpattern, pattern) sys.exit(1) mm = p.search(value) if mm == None: return False return True def SyntaxError(self, error): print "Syntax error in %s: %s" % (self.IniFile, error) sys.exit(0) def GetSyntaxEntry(self, skey): sdict = self.SyntaxParser.GetSectionAsDict(skey) if sdict == None: print "Non-recoverable error encountered when attempting to retrieve syntax dict for %s" % skey sys.exit(1) sentry = dict() keys = sdict.keys() #translate from our IniOption class down to a flat dict. #we won't need any of the extraneous information for key in keys: sentry[key] = sdict[key].Value; return sentry class InternalParser: def __init__(self, fptr=None): self.SectionOrder = list() self.Section = dict() self.Globals = dict() self.Keys = dict() if fptr != None: self.Lines = fptr.readlines() else: self.Lines = [] self.CurrentSection = None self.BlockMode = False self.BlockKey = None self.BlockText = None self.BlockModeBeginLine = 0 self.BlockIdentifier = None self.CurrentLineNumber = 0 # -- PRINTING THE INI -- def PrintParsedIni(self): str = "# -*-conf-*-\n" gkeys = self.Globals.keys() for key in gkeys: str = "%s%s\n" % (str, self.PrintKey(key, self.Globals[key])) for s in self.SectionOrder: str = "%s%s\n" % (str, self.PrintSection(s)) return str def PrintSection(self, section): str = "\n[%s]\n" % section keys = self.Section[section].keys() for key in keys: value = self.Section[section][key] if value.IsBlock: str = "%s%s\n" % (str, self.PrintBlock(key, value)) else: str = "%s%s\n" % (str, self.PrintKey(key, value)) return str def PrintKey(self, key, value): num_spaces = 16 - len(key) ss = self.BuildSpaces(num_spaces) if value.IsQuoted: str = "%s%s= %s" % (key, ss, value.OriginalValue) else: str = "%s%s= %s" % (key, ss, value.Value) return str def BuildSpaces(self, num_spaces): ss = str() for i in range(num_spaces): if i == 0: ss = " " else: ss = "%s " %ss return ss def PrintBlock(self, key, value): str = "%s = <<%s\n%s\n%s\n" % (key, value.BlockIdentifier, value.Value, value.BlockIdentifier) return str # -- PARSER -- def WriteKey(self, section, key, value): # if there is no current section, write it to our globals dictionary if section == None: #print "writing key %s to globals" % key self.Globals[key] = value #print "globals.%s = %s" % (key, value) else: #print "writing key %s to section %s" % (key, self.CurrentSection) self.Section[section][key] = value #print "%s.%s = %s" % (self.CurrentSection, key, self.Section[self.CurrentSection][key]) def InitSection(self, section): if not(self.Section.has_key(section)): self.Section[section] = dict() def EnterSection(self, section): #print "entering section %s" % section self.CurrentSection = section self.InitSection(section) if section not in self.SectionOrder: self.SectionOrder.append(section) def HandleError(self, line): # extend this to interpret each error to try to give better feedback as to the # nature of the error encountered error = "Unknown statement: %s" % line if line.count("["): if line.count("]"): error = "no whitespace allowed in section identifier: %s" % line else: error = "Missing ] on section identifier: %s" % line if line.count("="): error = "no whitespace allowed in key identifier: %s" % line print self.MakeError(self.CurrentLineNumber, error) sys.exit(1) def MakeError(self, line_number, error): global parser return "Syntax error on line %s of %s: %s" % (line_number, parser.filename, error) def StripInlineComment(self, line): cchars = ['#', ';'] found = None for cc in cchars: if line.count(cc) > 0: found = cc if found == None: return line parts = line.split(found) return parts[0].rstrip() def Parse(self): # match [] section_re = '\[(\S+)\]' # match key = value up to a comment [#;] key_re = '(\S+)\s*=\s*([^#;]*)[#;]?.*$' # match block statement key = <