## @file | |
# | |
# Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR> | |
# | |
# SPDX-License-Identifier: BSD-2-Clause-Patent | |
# | |
from __future__ import absolute_import | |
from .message import * | |
import re | |
import os | |
section_re = re.compile(r'^\[([\w., "]+)\]') | |
class BaseINIFile(object): | |
_objs = {} | |
def __new__(cls, *args, **kwargs): | |
"""Maintain only a single instance of this object | |
@return: instance of this class | |
""" | |
if len(args) == 0: return object.__new__(cls) | |
filename = args[0] | |
parent = None | |
if len(args) > 1: | |
parent = args[1] | |
key = os.path.normpath(filename) | |
if key not in cls._objs.keys(): | |
cls._objs[key] = object.__new__(cls) | |
if parent is not None: | |
cls._objs[key].AddParent(parent) | |
return cls._objs[key] | |
def __init__(self, filename=None, parent=None): | |
self._lines = [] | |
self._sections = {} | |
self._filename = filename | |
self._globals = [] | |
self._isModify = True | |
def AddParent(self, parent): | |
if parent is None: return | |
if not hasattr(self, "_parents"): | |
self._parents = [] | |
if parent in self._parents: | |
ErrorMsg("Duplicate parent is found for INI file %s" % self._filename) | |
return | |
self._parents.append(parent) | |
def GetFilename(self): | |
return os.path.normpath(self._filename) | |
def IsModified(self): | |
return self._isModify | |
def Modify(self, modify=True, obj=None): | |
if modify == self._isModify: return | |
self._isModify = modify | |
if modify: | |
for parent in self._parents: | |
parent.Modify(True, self) | |
def _ReadLines(self, filename): | |
# | |
# try to open file | |
# | |
if not os.path.exists(filename): | |
return False | |
try: | |
handle = open(filename, 'r') | |
self._lines = handle.readlines() | |
handle.close() | |
except: | |
raise EdkException("Fail to open file %s" % filename) | |
return True | |
def GetSectionInstance(self, parent, name, isCombined=False): | |
return BaseINISection(parent, name, isCombined) | |
def GetSectionByName(self, name): | |
arr = [] | |
for key in self._sections.keys(): | |
if '.private' in key: | |
continue | |
for item in self._sections[key]: | |
if item.GetBaseName().lower().find(name.lower()) != -1: | |
arr.append(item) | |
return arr | |
def GetSectionObjectsByName(self, name): | |
arr = [] | |
sects = self.GetSectionByName(name) | |
for sect in sects: | |
for obj in sect.GetObjects(): | |
arr.append(obj) | |
return arr | |
def Parse(self): | |
if not self._isModify: return True | |
if not self._ReadLines(self._filename): return False | |
sObjs = [] | |
inGlobal = True | |
# process line | |
for index in range(len(self._lines)): | |
templine = self._lines[index].strip() | |
# skip comments | |
if len(templine) == 0: continue | |
if re.match("^\[=*\]", templine) or re.match("^#", templine) or \ | |
re.match("\*+/", templine): | |
continue | |
m = section_re.match(templine) | |
if m is not None: # found a section | |
inGlobal = False | |
# Finish the latest section first | |
if len(sObjs) != 0: | |
for sObj in sObjs: | |
sObj._end = index - 1 | |
if not sObj.Parse(): | |
ErrorMsg("Fail to parse section %s" % sObj.GetBaseName(), | |
self._filename, | |
sObj._start) | |
# start new section | |
sname_arr = m.groups()[0].split(',') | |
sObjs = [] | |
for name in sname_arr: | |
sObj = self.GetSectionInstance(self, name, (len(sname_arr) > 1)) | |
sObj._start = index | |
sObjs.append(sObj) | |
if name.lower() not in self._sections: | |
self._sections[name.lower()] = [sObj] | |
else: | |
self._sections[name.lower()].append(sObj) | |
elif inGlobal: # not start any section and find global object | |
gObj = BaseINIGlobalObject(self) | |
gObj._start = index | |
gObj.Parse() | |
self._globals.append(gObj) | |
# Finish the last section | |
if len(sObjs) != 0: | |
for sObj in sObjs: | |
sObj._end = index | |
if not sObj.Parse(): | |
ErrorMsg("Fail to parse section %s" % sObj.GetBaseName(), | |
self._filename, | |
sObj._start) | |
self._isModify = False | |
return True | |
def Destroy(self, parent): | |
# check referenced parent | |
if parent is not None: | |
assert parent in self._parents, "when destory ini object, can not found parent reference!" | |
self._parents.remove(parent) | |
if len(self._parents) != 0: return | |
for sects in self._sections.values(): | |
for sect in sects: | |
sect.Destroy() | |
# dereference from _objs array | |
assert self.GetFilename() in self._objs.keys(), "When destroy ini object, can not find obj reference!" | |
assert self in self._objs.values(), "When destroy ini object, can not find obj reference!" | |
del self._objs[self.GetFilename()] | |
# dereference self | |
self.Clear() | |
def GetDefine(self, name): | |
sects = self.GetSectionByName('Defines') | |
for sect in sects: | |
for obj in sect.GetObjects(): | |
line = obj.GetLineByOffset(obj._start).split('#')[0].strip() | |
arr = line.split('=') | |
if arr[0].strip().lower() == name.strip().lower(): | |
return arr[1].strip() | |
return None | |
def Clear(self): | |
for sects in self._sections.values(): | |
for sect in sects: | |
del sect | |
self._sections.clear() | |
for gObj in self._globals: | |
del gObj | |
del self._globals[:] | |
del self._lines[:] | |
def Reload(self): | |
self.Clear() | |
ret = self.Parse() | |
if ret: | |
self._isModify = False | |
return ret | |
def AddNewSection(self, sectName): | |
if sectName.lower() in self._sections.keys(): | |
ErrorMsg('Section %s can not be created for conflict with existing section') | |
return None | |
sectionObj = self.GetSectionInstance(self, sectName) | |
sectionObj._start = len(self._lines) | |
sectionObj._end = len(self._lines) + 1 | |
self._lines.append('[%s]\n' % sectName) | |
self._lines.append('\n\n') | |
self._sections[sectName.lower()] = sectionObj | |
return sectionObj | |
def CopySectionsByName(self, oldDscObj, nameStr): | |
sects = oldDscObj.GetSectionByName(nameStr) | |
for sect in sects: | |
sectObj = self.AddNewSection(sect.GetName()) | |
sectObj.Copy(sect) | |
def __str__(self): | |
return ''.join(self._lines) | |
## Get file header's comment from basic INI file. | |
# The file comments has two style: | |
# 1) #/** @file | |
# 2) ## @file | |
# | |
def GetFileHeader(self): | |
desc = [] | |
lineArr = self._lines | |
inHeader = False | |
for num in range(len(self._lines)): | |
line = lineArr[num].strip() | |
if not inHeader and (line.startswith("#/**") or line.startswith("##")) and \ | |
line.find("@file") != -1: | |
inHeader = True | |
continue | |
if inHeader and (line.startswith("#**/") or line.startswith('##')): | |
inHeader = False | |
break | |
if inHeader: | |
prefixIndex = line.find('#') | |
if prefixIndex == -1: | |
desc.append(line) | |
else: | |
desc.append(line[prefixIndex + 1:]) | |
return '<br>\n'.join(desc) | |
class BaseINISection(object): | |
def __init__(self, parent, name, isCombined=False): | |
self._parent = parent | |
self._name = name | |
self._isCombined = isCombined | |
self._start = 0 | |
self._end = 0 | |
self._objs = [] | |
def __del__(self): | |
for obj in self._objs: | |
del obj | |
del self._objs[:] | |
def GetName(self): | |
return self._name | |
def GetObjects(self): | |
return self._objs | |
def GetParent(self): | |
return self._parent | |
def GetStartLinenumber(self): | |
return self._start | |
def GetEndLinenumber(self): | |
return self._end | |
def GetLine(self, linenumber): | |
return self._parent._lines[linenumber] | |
def GetFilename(self): | |
return self._parent.GetFilename() | |
def GetSectionINIObject(self, parent): | |
return BaseINISectionObject(parent) | |
def Parse(self): | |
# skip first line in section, it is used by section name | |
visit = self._start + 1 | |
iniObj = None | |
while (visit <= self._end): | |
line = self.GetLine(visit).strip() | |
if re.match("^\[=*\]", line) or re.match("^#", line) or len(line) == 0: | |
visit += 1 | |
continue | |
line = line.split('#')[0].strip() | |
if iniObj is not None: | |
if line.endswith('}'): | |
iniObj._end = visit - self._start | |
if not iniObj.Parse(): | |
ErrorMsg("Fail to parse ini object", | |
self.GetFilename(), | |
iniObj.GetStartLinenumber()) | |
else: | |
self._objs.append(iniObj) | |
iniObj = None | |
else: | |
iniObj = self.GetSectionINIObject(self) | |
iniObj._start = visit - self._start | |
if not line.endswith('{'): | |
iniObj._end = visit - self._start | |
if not iniObj.Parse(): | |
ErrorMsg("Fail to parse ini object", | |
self.GetFilename(), | |
iniObj.GetStartLinenumber()) | |
else: | |
self._objs.append(iniObj) | |
iniObj = None | |
visit += 1 | |
return True | |
def Destroy(self): | |
for obj in self._objs: | |
obj.Destroy() | |
def GetBaseName(self): | |
return self._name | |
def AddLine(self, line): | |
end = self.GetEndLinenumber() | |
self._parent._lines.insert(end, line) | |
self._end += 1 | |
def Copy(self, sectObj): | |
index = sectObj.GetStartLinenumber() + 1 | |
while index < sectObj.GetEndLinenumber(): | |
line = sectObj.GetLine(index) | |
if not line.strip().startswith('#'): | |
self.AddLine(line) | |
index += 1 | |
def AddObject(self, obj): | |
lines = obj.GenerateLines() | |
for line in lines: | |
self.AddLine(line) | |
def GetComment(self): | |
comments = [] | |
start = self._start - 1 | |
bFound = False | |
while (start > 0): | |
line = self.GetLine(start).strip() | |
if len(line) == 0: | |
start -= 1 | |
continue | |
if line.startswith('##'): | |
bFound = True | |
index = line.rfind('#') | |
if (index + 1) < len(line): | |
comments.append(line[index + 1:]) | |
break | |
if line.startswith('#'): | |
start -= 1 | |
continue | |
break | |
if bFound: | |
end = start + 1 | |
while (end < self._start): | |
line = self.GetLine(end).strip() | |
if len(line) == 0: break | |
if not line.startswith('#'): break | |
index = line.rfind('#') | |
if (index + 1) < len(line): | |
comments.append(line[index + 1:]) | |
end += 1 | |
return comments | |
class BaseINIGlobalObject(object): | |
def __init__(self, parent): | |
self._start = 0 | |
self._end = 0 | |
def Parse(self): | |
return True | |
def __str__(self): | |
return parent._lines[self._start] | |
def __del__(self): | |
pass | |
class BaseINISectionObject(object): | |
def __init__(self, parent): | |
self._start = 0 | |
self._end = 0 | |
self._parent = parent | |
def __del__(self): | |
self._parent = None | |
def GetParent(self): | |
return self._parent | |
def GetFilename(self): | |
return self.GetParent().GetFilename() | |
def GetPackageName(self): | |
return self.GetFilename() | |
def GetFileObj(self): | |
return self.GetParent().GetParent() | |
def GetStartLinenumber(self): | |
return self.GetParent()._start + self._start | |
def GetLineByOffset(self, offset): | |
sect_start = self._parent.GetStartLinenumber() | |
linenumber = sect_start + offset | |
return self._parent.GetLine(linenumber) | |
def GetLinenumberByOffset(self, offset): | |
return offset + self._parent.GetStartLinenumber() | |
def Parse(self): | |
return True | |
def Destroy(self): | |
pass | |
def __str__(self): | |
return self.GetLineByOffset(self._start).strip() | |
def GenerateLines(self): | |
return ['default setion object string\n'] | |
def GetComment(self): | |
comments = [] | |
start = self.GetStartLinenumber() - 1 | |
bFound = False | |
while (start > 0): | |
line = self.GetParent().GetLine(start).strip() | |
if len(line) == 0: | |
start -= 1 | |
continue | |
if line.startswith('##'): | |
bFound = True | |
index = line.rfind('#') | |
if (index + 1) < len(line): | |
comments.append(line[index + 1:]) | |
break | |
if line.startswith('#'): | |
start -= 1 | |
continue | |
break | |
if bFound: | |
end = start + 1 | |
while (end <= self.GetStartLinenumber() - 1): | |
line = self.GetParent().GetLine(end).strip() | |
if len(line) == 0: break | |
if not line.startswith('#'): break | |
index = line.rfind('#') | |
if (index + 1) < len(line): | |
comments.append(line[index + 1:]) | |
end += 1 | |
return comments |