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