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