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