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