ini.py 15 KB

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