1## @file 2# 3# Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR> 4# 5# SPDX-License-Identifier: BSD-2-Clause-Patent 6# 7 8from __future__ import absolute_import 9from .message import * 10import re 11import os 12 13section_re = re.compile(r'^\[([\w., "]+)\]') 14 15class BaseINIFile(object): 16 _objs = {} 17 def __new__(cls, *args, **kwargs): 18 """Maintain only a single instance of this object 19 @return: instance of this class 20 21 """ 22 if len(args) == 0: return object.__new__(cls) 23 filename = args[0] 24 parent = None 25 if len(args) > 1: 26 parent = args[1] 27 28 key = os.path.normpath(filename) 29 if key not in cls._objs.keys(): 30 cls._objs[key] = object.__new__(cls) 31 32 if parent is not None: 33 cls._objs[key].AddParent(parent) 34 35 return cls._objs[key] 36 37 def __init__(self, filename=None, parent=None): 38 self._lines = [] 39 self._sections = {} 40 self._filename = filename 41 self._globals = [] 42 self._isModify = True 43 44 def AddParent(self, parent): 45 if parent is None: return 46 if not hasattr(self, "_parents"): 47 self._parents = [] 48 49 if parent in self._parents: 50 ErrorMsg("Duplicate parent is found for INI file %s" % self._filename) 51 return 52 self._parents.append(parent) 53 54 def GetFilename(self): 55 return os.path.normpath(self._filename) 56 57 def IsModified(self): 58 return self._isModify 59 60 def Modify(self, modify=True, obj=None): 61 if modify == self._isModify: return 62 self._isModify = modify 63 if modify: 64 for parent in self._parents: 65 parent.Modify(True, self) 66 67 def _ReadLines(self, filename): 68 # 69 # try to open file 70 # 71 if not os.path.exists(filename): 72 return False 73 74 try: 75 handle = open(filename, 'r') 76 self._lines = handle.readlines() 77 handle.close() 78 except: 79 raise EdkException("Fail to open file %s" % filename) 80 81 return True 82 83 def GetSectionInstance(self, parent, name, isCombined=False): 84 return BaseINISection(parent, name, isCombined) 85 86 def GetSectionByName(self, name): 87 arr = [] 88 for key in self._sections.keys(): 89 if '.private' in key: 90 continue 91 for item in self._sections[key]: 92 if item.GetBaseName().lower().find(name.lower()) != -1: 93 arr.append(item) 94 return arr 95 96 def GetSectionObjectsByName(self, name): 97 arr = [] 98 sects = self.GetSectionByName(name) 99 for sect in sects: 100 for obj in sect.GetObjects(): 101 arr.append(obj) 102 return arr 103 104 def Parse(self): 105 if not self._isModify: return True 106 if not self._ReadLines(self._filename): return False 107 108 sObjs = [] 109 inGlobal = True 110 # process line 111 for index in range(len(self._lines)): 112 templine = self._lines[index].strip() 113 # skip comments 114 if len(templine) == 0: continue 115 if re.match("^\[=*\]", templine) or re.match("^#", templine) or \ 116 re.match("\*+/", templine): 117 continue 118 119 m = section_re.match(templine) 120 if m is not None: # found a section 121 inGlobal = False 122 # Finish the latest section first 123 if len(sObjs) != 0: 124 for sObj in sObjs: 125 sObj._end = index - 1 126 if not sObj.Parse(): 127 ErrorMsg("Fail to parse section %s" % sObj.GetBaseName(), 128 self._filename, 129 sObj._start) 130 131 # start new section 132 sname_arr = m.groups()[0].split(',') 133 sObjs = [] 134 for name in sname_arr: 135 sObj = self.GetSectionInstance(self, name, (len(sname_arr) > 1)) 136 sObj._start = index 137 sObjs.append(sObj) 138 if name.lower() not in self._sections: 139 self._sections[name.lower()] = [sObj] 140 else: 141 self._sections[name.lower()].append(sObj) 142 elif inGlobal: # not start any section and find global object 143 gObj = BaseINIGlobalObject(self) 144 gObj._start = index 145 gObj.Parse() 146 self._globals.append(gObj) 147 148 # Finish the last section 149 if len(sObjs) != 0: 150 for sObj in sObjs: 151 sObj._end = index 152 if not sObj.Parse(): 153 ErrorMsg("Fail to parse section %s" % sObj.GetBaseName(), 154 self._filename, 155 sObj._start) 156 157 self._isModify = False 158 return True 159 160 def Destroy(self, parent): 161 162 # check referenced parent 163 if parent is not None: 164 assert parent in self._parents, "when destory ini object, can not found parent reference!" 165 self._parents.remove(parent) 166 167 if len(self._parents) != 0: return 168 169 for sects in self._sections.values(): 170 for sect in sects: 171 sect.Destroy() 172 173 # dereference from _objs array 174 assert self.GetFilename() in self._objs.keys(), "When destroy ini object, can not find obj reference!" 175 assert self in self._objs.values(), "When destroy ini object, can not find obj reference!" 176 del self._objs[self.GetFilename()] 177 178 # dereference self 179 self.Clear() 180 181 def GetDefine(self, name): 182 sects = self.GetSectionByName('Defines') 183 for sect in sects: 184 for obj in sect.GetObjects(): 185 line = obj.GetLineByOffset(obj._start).split('#')[0].strip() 186 arr = line.split('=') 187 if arr[0].strip().lower() == name.strip().lower(): 188 return arr[1].strip() 189 return None 190 191 def Clear(self): 192 for sects in self._sections.values(): 193 for sect in sects: 194 del sect 195 self._sections.clear() 196 for gObj in self._globals: 197 del gObj 198 199 del self._globals[:] 200 del self._lines[:] 201 202 def Reload(self): 203 self.Clear() 204 ret = self.Parse() 205 if ret: 206 self._isModify = False 207 return ret 208 209 def AddNewSection(self, sectName): 210 if sectName.lower() in self._sections.keys(): 211 ErrorMsg('Section %s can not be created for conflict with existing section') 212 return None 213 214 sectionObj = self.GetSectionInstance(self, sectName) 215 sectionObj._start = len(self._lines) 216 sectionObj._end = len(self._lines) + 1 217 self._lines.append('[%s]\n' % sectName) 218 self._lines.append('\n\n') 219 self._sections[sectName.lower()] = sectionObj 220 return sectionObj 221 222 def CopySectionsByName(self, oldDscObj, nameStr): 223 sects = oldDscObj.GetSectionByName(nameStr) 224 for sect in sects: 225 sectObj = self.AddNewSection(sect.GetName()) 226 sectObj.Copy(sect) 227 228 def __str__(self): 229 return ''.join(self._lines) 230 231 ## Get file header's comment from basic INI file. 232 # The file comments has two style: 233 # 1) #/** @file 234 # 2) ## @file 235 # 236 def GetFileHeader(self): 237 desc = [] 238 lineArr = self._lines 239 inHeader = False 240 for num in range(len(self._lines)): 241 line = lineArr[num].strip() 242 if not inHeader and (line.startswith("#/**") or line.startswith("##")) and \ 243 line.find("@file") != -1: 244 inHeader = True 245 continue 246 if inHeader and (line.startswith("#**/") or line.startswith('##')): 247 inHeader = False 248 break 249 if inHeader: 250 prefixIndex = line.find('#') 251 if prefixIndex == -1: 252 desc.append(line) 253 else: 254 desc.append(line[prefixIndex + 1:]) 255 return '<br>\n'.join(desc) 256 257class BaseINISection(object): 258 def __init__(self, parent, name, isCombined=False): 259 self._parent = parent 260 self._name = name 261 self._isCombined = isCombined 262 self._start = 0 263 self._end = 0 264 self._objs = [] 265 266 def __del__(self): 267 for obj in self._objs: 268 del obj 269 del self._objs[:] 270 271 def GetName(self): 272 return self._name 273 274 def GetObjects(self): 275 return self._objs 276 277 def GetParent(self): 278 return self._parent 279 280 def GetStartLinenumber(self): 281 return self._start 282 283 def GetEndLinenumber(self): 284 return self._end 285 286 def GetLine(self, linenumber): 287 return self._parent._lines[linenumber] 288 289 def GetFilename(self): 290 return self._parent.GetFilename() 291 292 def GetSectionINIObject(self, parent): 293 return BaseINISectionObject(parent) 294 295 def Parse(self): 296 # skip first line in section, it is used by section name 297 visit = self._start + 1 298 iniObj = None 299 while (visit <= self._end): 300 line = self.GetLine(visit).strip() 301 if re.match("^\[=*\]", line) or re.match("^#", line) or len(line) == 0: 302 visit += 1 303 continue 304 line = line.split('#')[0].strip() 305 if iniObj is not None: 306 if line.endswith('}'): 307 iniObj._end = visit - self._start 308 if not iniObj.Parse(): 309 ErrorMsg("Fail to parse ini object", 310 self.GetFilename(), 311 iniObj.GetStartLinenumber()) 312 else: 313 self._objs.append(iniObj) 314 iniObj = None 315 else: 316 iniObj = self.GetSectionINIObject(self) 317 iniObj._start = visit - self._start 318 if not line.endswith('{'): 319 iniObj._end = visit - self._start 320 if not iniObj.Parse(): 321 ErrorMsg("Fail to parse ini object", 322 self.GetFilename(), 323 iniObj.GetStartLinenumber()) 324 else: 325 self._objs.append(iniObj) 326 iniObj = None 327 visit += 1 328 return True 329 330 def Destroy(self): 331 for obj in self._objs: 332 obj.Destroy() 333 334 def GetBaseName(self): 335 return self._name 336 337 def AddLine(self, line): 338 end = self.GetEndLinenumber() 339 self._parent._lines.insert(end, line) 340 self._end += 1 341 342 def Copy(self, sectObj): 343 index = sectObj.GetStartLinenumber() + 1 344 while index < sectObj.GetEndLinenumber(): 345 line = sectObj.GetLine(index) 346 if not line.strip().startswith('#'): 347 self.AddLine(line) 348 index += 1 349 350 def AddObject(self, obj): 351 lines = obj.GenerateLines() 352 for line in lines: 353 self.AddLine(line) 354 355 def GetComment(self): 356 comments = [] 357 start = self._start - 1 358 bFound = False 359 360 while (start > 0): 361 line = self.GetLine(start).strip() 362 if len(line) == 0: 363 start -= 1 364 continue 365 if line.startswith('##'): 366 bFound = True 367 index = line.rfind('#') 368 if (index + 1) < len(line): 369 comments.append(line[index + 1:]) 370 break 371 if line.startswith('#'): 372 start -= 1 373 continue 374 break 375 if bFound: 376 end = start + 1 377 while (end < self._start): 378 line = self.GetLine(end).strip() 379 if len(line) == 0: break 380 if not line.startswith('#'): break 381 index = line.rfind('#') 382 if (index + 1) < len(line): 383 comments.append(line[index + 1:]) 384 end += 1 385 return comments 386 387class BaseINIGlobalObject(object): 388 def __init__(self, parent): 389 self._start = 0 390 self._end = 0 391 392 def Parse(self): 393 return True 394 395 def __str__(self): 396 return parent._lines[self._start] 397 398 def __del__(self): 399 pass 400 401class BaseINISectionObject(object): 402 def __init__(self, parent): 403 self._start = 0 404 self._end = 0 405 self._parent = parent 406 407 def __del__(self): 408 self._parent = None 409 410 def GetParent(self): 411 return self._parent 412 413 def GetFilename(self): 414 return self.GetParent().GetFilename() 415 416 def GetPackageName(self): 417 return self.GetFilename() 418 419 def GetFileObj(self): 420 return self.GetParent().GetParent() 421 422 def GetStartLinenumber(self): 423 return self.GetParent()._start + self._start 424 425 def GetLineByOffset(self, offset): 426 sect_start = self._parent.GetStartLinenumber() 427 linenumber = sect_start + offset 428 return self._parent.GetLine(linenumber) 429 430 def GetLinenumberByOffset(self, offset): 431 return offset + self._parent.GetStartLinenumber() 432 433 def Parse(self): 434 return True 435 436 def Destroy(self): 437 pass 438 439 def __str__(self): 440 return self.GetLineByOffset(self._start).strip() 441 442 def GenerateLines(self): 443 return ['default setion object string\n'] 444 445 def GetComment(self): 446 comments = [] 447 start = self.GetStartLinenumber() - 1 448 bFound = False 449 450 while (start > 0): 451 line = self.GetParent().GetLine(start).strip() 452 if len(line) == 0: 453 start -= 1 454 continue 455 if line.startswith('##'): 456 bFound = True 457 index = line.rfind('#') 458 if (index + 1) < len(line): 459 comments.append(line[index + 1:]) 460 break 461 if line.startswith('#'): 462 start -= 1 463 continue 464 break 465 if bFound: 466 end = start + 1 467 while (end <= self.GetStartLinenumber() - 1): 468 line = self.GetParent().GetLine(end).strip() 469 if len(line) == 0: break 470 if not line.startswith('#'): break 471 index = line.rfind('#') 472 if (index + 1) < len(line): 473 comments.append(line[index + 1:]) 474 end += 1 475 return comments 476