]> git.proxmox.com Git - mirror_edk2.git/blob - BaseTools/Scripts/PackageDocumentTools/packagedocapp.pyw
BaseTools: Replace BSD License with BSD+Patent License
[mirror_edk2.git] / BaseTools / Scripts / PackageDocumentTools / packagedocapp.pyw
1 ## @file
2 # This file is used to define common string related functions used in parsing
3 # process
4 #
5 # Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR>
6 #
7 # SPDX-License-Identifier: BSD-2-Clause-Patent
8 #
9
10 import os, sys, wx, logging
11
12 import wx.stc
13 import wx.lib.newevent
14 import wx.lib.agw.genericmessagedialog as GMD
15 from plugins.EdkPlugins.edk2.model import baseobject
16 from plugins.EdkPlugins.edk2.model import doxygengen
17
18 if hasattr(sys, "frozen"):
19 appPath = os.path.abspath(os.path.dirname(sys.executable))
20 else:
21 appPath = os.path.abspath(os.path.dirname(__file__))
22
23 AppCallBackEvent, EVT_APP_CALLBACK = wx.lib.newevent.NewEvent()
24 LogEvent, EVT_LOG = wx.lib.newevent.NewEvent()
25
26 class PackageDocApp(wx.App):
27
28 def OnInit(self):
29 logfile = os.path.join(appPath, 'log.txt')
30 logging.basicConfig(format='%(name)-8s %(levelname)-8s %(message)s',
31 filename=logfile, level=logging.ERROR)
32
33 self.SetAppName('Package Doxygen Generate Application')
34 frame = PackageDocMainFrame(None, "Package Document Generation Application!")
35 self.SetTopWindow(frame)
36
37 frame.Show(True)
38
39 EVT_APP_CALLBACK( self, self.OnAppCallBack)
40 return True
41
42 def GetLogger(self):
43 return logging.getLogger('')
44
45 def ForegroundProcess(self, function, args):
46 wx.PostEvent(self, AppCallBackEvent(callback=function, args=args))
47
48 def OnAppCallBack(self, event):
49 try:
50 event.callback(*event.args)
51 except:
52 self._logger.exception( 'OnAppCallBack<%s.%s>\n' %
53 (event.callback.__module__, event.callback.__name__ ))
54
55 class PackageDocMainFrame(wx.Frame):
56 def __init__(self, parent, title):
57 wx.Frame.__init__(self, parent, -1, title, size=(550, 290), style=wx.MINIMIZE_BOX|wx.SYSTEM_MENU|wx.CAPTION|wx.CLOSE_BOX )
58
59 panel = wx.Panel(self)
60 sizer = wx.BoxSizer(wx.VERTICAL)
61
62 subsizer = wx.GridBagSizer(5, 10)
63 subsizer.AddGrowableCol(1)
64 subsizer.Add(wx.StaticText(panel, -1, "Workspace Location : "), (0, 0), flag=wx.ALIGN_CENTER_VERTICAL)
65 self._workspacePathCtrl = wx.ComboBox(panel, -1)
66 list = self.GetConfigure("WorkspacePath")
67 if len(list) != 0:
68 for item in list:
69 self._workspacePathCtrl.Append(item)
70 self._workspacePathCtrl.SetValue(list[len(list) - 1])
71
72 subsizer.Add(self._workspacePathCtrl, (0, 1), flag=wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
73 self._workspacePathBt = wx.BitmapButton(panel, -1, bitmap=wx.ArtProvider_GetBitmap(wx.ART_FILE_OPEN))
74 subsizer.Add(self._workspacePathBt, (0, 2), flag=wx.ALIGN_CENTER_VERTICAL)
75 wx.EVT_BUTTON(self._workspacePathBt, self._workspacePathBt.GetId(), self.OnBrowsePath)
76
77 subsizer.Add(wx.StaticText(panel, -1, "Package DEC Location : "), (1, 0), flag=wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
78 self._packagePathCtrl = wx.ComboBox(panel, -1)
79 list = self.GetConfigure("PackagePath")
80 if len(list) != 0:
81 for item in list:
82 self._packagePathCtrl.Append(item)
83 self._packagePathCtrl.SetValue(list[len(list) - 1])
84 subsizer.Add(self._packagePathCtrl, (1, 1), flag=wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
85 self._packagePathBt = wx.BitmapButton(panel, -1, bitmap=wx.ArtProvider_GetBitmap(wx.ART_FILE_OPEN))
86 subsizer.Add(self._packagePathBt, (1, 2), flag=wx.ALIGN_CENTER_VERTICAL)
87 wx.EVT_BUTTON(self._packagePathBt, self._packagePathBt.GetId(), self.OnBrowsePath)
88
89 subsizer.Add(wx.StaticText(panel, -1, "Doxygen Tool Location : "), (2, 0), flag=wx.ALIGN_CENTER_VERTICAL)
90 self._doxygenPathCtrl = wx.TextCtrl(panel, -1)
91 list = self.GetConfigure('DoxygenPath')
92 if len(list) != 0:
93 self._doxygenPathCtrl.SetValue(list[0])
94 else:
95 if wx.Platform == '__WXMSW__':
96 self._doxygenPathCtrl.SetValue('C:\\Program Files\\Doxygen\\bin\\doxygen.exe')
97 else:
98 self._doxygenPathCtrl.SetValue('/usr/bin/doxygen')
99
100 self._doxygenPathBt = wx.BitmapButton(panel, -1, bitmap=wx.ArtProvider_GetBitmap(wx.ART_FILE_OPEN))
101 subsizer.Add(self._doxygenPathCtrl, (2, 1), flag=wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
102 subsizer.Add(self._doxygenPathBt, (2, 2), flag=wx.ALIGN_CENTER_VERTICAL)
103 wx.EVT_BUTTON(self._doxygenPathBt, self._doxygenPathBt.GetId(), self.OnBrowsePath)
104
105 subsizer.Add(wx.StaticText(panel, -1, "CHM Tool Location : "), (3, 0), flag=wx.ALIGN_CENTER_VERTICAL)
106 self._chmPathCtrl = wx.TextCtrl(panel, -1)
107 list = self.GetConfigure('CHMPath')
108 if len(list) != 0:
109 self._chmPathCtrl.SetValue(list[0])
110 else:
111 self._chmPathCtrl.SetValue('C:\\Program Files\\HTML Help Workshop\\hhc.exe')
112
113 self._chmPathBt = wx.BitmapButton(panel, -1, bitmap=wx.ArtProvider_GetBitmap(wx.ART_FILE_OPEN))
114 subsizer.Add(self._chmPathCtrl, (3, 1), flag=wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
115 subsizer.Add(self._chmPathBt, (3, 2), flag=wx.ALIGN_CENTER_VERTICAL)
116 wx.EVT_BUTTON(self._chmPathBt, self._chmPathBt.GetId(), self.OnBrowsePath)
117
118 subsizer.Add(wx.StaticText(panel, -1, "Output Location : "), (4, 0), flag=wx.ALIGN_CENTER_VERTICAL)
119 self._outputPathCtrl = wx.ComboBox(panel, -1)
120 list = self.GetConfigure("OutputPath")
121 if len(list) != 0:
122 for item in list:
123 self._outputPathCtrl.Append(item)
124 self._outputPathCtrl.SetValue(list[len(list) - 1])
125
126 subsizer.Add(self._outputPathCtrl, (4, 1), flag=wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
127 self._outputPathBt = wx.BitmapButton(panel, -1, bitmap=wx.ArtProvider_GetBitmap(wx.ART_FILE_OPEN))
128 subsizer.Add(self._outputPathBt, (4, 2), flag=wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
129 wx.EVT_BUTTON(self._outputPathBt, self._outputPathBt.GetId(), self.OnBrowsePath)
130
131 subsizer.Add(wx.StaticText(panel, -1, "Architecture Specified : "), (5, 0), flag=wx.ALIGN_CENTER_VERTICAL)
132 self._archCtrl = wx.ComboBox(panel, -1, value='ALL', choices=['ALL', 'IA32/MSFT', 'IA32/GNU', 'X64/INTEL', 'X64/GNU', 'IPF/MSFT', 'IPF/GNU', 'EBC/INTEL'],
133 style=wx.CB_READONLY)
134 self._archCtrl.Bind(wx.EVT_COMBOBOX, self.OnArchtectureSelectChanged)
135 subsizer.Add(self._archCtrl, (5, 1), (1, 2), flag=wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
136 sizer.Add(subsizer, 0, wx.EXPAND|wx.TOP|wx.BOTTOM|wx.LEFT|wx.RIGHT, 5)
137
138 sizer6 = wx.BoxSizer(wx.HORIZONTAL)
139 self._modesel = wx.RadioBox(panel, -1, 'Generated Document Mode', majorDimension=2, choices=['CHM', 'HTML'], style=wx.RA_SPECIFY_COLS)
140 self._modesel.SetStringSelection('HTML')
141
142 self._includeonlysel = wx.CheckBox(panel, -1, 'Only document public include')
143
144 sizer6.Add(self._modesel, 0 , wx.EXPAND)
145 sizer6.Add(self._includeonlysel, 0, wx.EXPAND|wx.LEFT, 5)
146
147 sizer.Add(sizer6, 0, wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, 5)
148
149 self._generateBt = wx.Button(panel, -1, "Generate Package Document!")
150 self._generateBt.Bind(wx.EVT_BUTTON, self.OnGenerate)
151 sizer.Add(self._generateBt, 0, wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, 5)
152
153 panel.SetSizer(sizer)
154 panel.Layout()
155 panel.SetAutoLayout(True)
156 self.CenterOnScreen()
157
158 def SaveConfigure(self, name, value):
159 if value ==None or len(value) == 0:
160 return
161 config = wx.ConfigBase_Get()
162 oldvalues = config.Read(name, '').split(';')
163 if len(oldvalues) >= 10:
164 oldvalues.remove(oldvalues[0])
165 if value not in oldvalues:
166 oldvalues.append(value)
167 else:
168 oldvalues.remove(value)
169 oldvalues.append(value)
170
171 config.Write(name, ';'.join(oldvalues))
172
173 def GetConfigure(self, name):
174 config = wx.ConfigBase_Get()
175 values = config.Read(name, '').split(';')
176 list = []
177 for item in values:
178 if len(item) != 0:
179 list.append(item)
180 return list
181
182 def OnBrowsePath(self, event):
183 id = event.GetId()
184 editctrl = None
185 startdir = ''
186 isFile = False
187 if id == self._packagePathBt.GetId():
188 dlgTitle = "Choose package path:"
189 editctrl = self._packagePathCtrl
190 isFile = True
191 if os.path.exists(self.GetWorkspace()):
192 startdir = self.GetWorkspace()
193 elif id == self._workspacePathBt.GetId():
194 dlgTitle = "Choose workspace path:"
195 editctrl = self._workspacePathCtrl
196 startdir = editctrl.GetValue()
197 elif id == self._doxygenPathBt.GetId():
198 isFile = True
199 dlgTitle = "Choose doxygen installation path:"
200 editctrl = self._doxygenPathCtrl
201 startdir = editctrl.GetValue()
202 elif id == self._outputPathBt.GetId():
203 dlgTitle = "Choose document output path:"
204 editctrl = self._outputPathCtrl
205 if os.path.exists(self.GetWorkspace()):
206 startdir = self.GetWorkspace()
207 startdir = editctrl.GetValue()
208 elif id == self._chmPathBt.GetId():
209 isFile = True
210 dlgTitle = "Choose installation path for Microsoft HTML workshop software"
211 editctrl = self._chmPathCtrl
212 startdir = editctrl.GetValue()
213 else:
214 return
215
216 if not isFile:
217 dlg = wx.DirDialog(self, dlgTitle, defaultPath=startdir)
218 else:
219 dlg = wx.FileDialog(self, dlgTitle, defaultDir=startdir)
220
221 if dlg.ShowModal() == wx.ID_OK:
222 editctrl.SetValue(dlg.GetPath())
223 dlg.Destroy()
224
225 def OnArchtectureSelectChanged(self, event):
226 str = ''
227 selarch = self._archCtrl.GetValue()
228 if selarch == 'ALL':
229 str += 'MDE_CPU_IA32 MDE_CPU_X64 MDE_CPU_EBC MDE_CPU_IPF _MSC_EXTENSIONS __GNUC__ __INTEL_COMPILER'
230 elif selarch == 'IA32/MSFT':
231 str += 'MDE_CPU_IA32 _MSC_EXTENSIONS'
232 elif selarch == 'IA32/GNU':
233 str += 'MDE_CPU_IA32 __GNUC__'
234 elif selarch == 'X64/MSFT':
235 str += 'MDE_CPU_X64 _MSC_EXTENSIONS'
236 elif selarch == 'X64/GNU':
237 str += 'MDE_CPU_X64 __GNUC__'
238 elif selarch == 'IPF/MSFT':
239 str += 'MDE_CPU_IPF _MSC_EXTENSIONS'
240 elif selarch == 'IPF/GNU':
241 str += 'MDE_CPU_IPF __GNUC__'
242 elif selarch == 'EBC/INTEL':
243 str += 'MDE_CPU_EBC __INTEL_COMPILER'
244
245 str += ' ASM_PFX= OPTIONAL= '
246
247 def OnMacroText(self, event):
248 str = ''
249 selarch = self._archCtrl.GetValue()
250 if selarch == 'ALL':
251 str += 'MDE_CPU_IA32 MDE_CPU_X64 MDE_CPU_EBC MDE_CPU_IPF _MSC_EXTENSIONS __GNUC__ __INTEL_COMPILER'
252 elif selarch == 'IA32/MSFT':
253 str += 'MDE_CPU_IA32 _MSC_EXTENSIONS'
254 elif selarch == 'IA32/GNU':
255 str += 'MDE_CPU_IA32 __GNUC__'
256 elif selarch == 'X64/MSFT':
257 str += 'MDE_CPU_X64 _MSC_EXTENSIONS'
258 elif selarch == 'X64/GNU':
259 str += 'MDE_CPU_X64 __GNUC__'
260 elif selarch == 'IPF/MSFT':
261 str += 'MDE_CPU_IPF _MSC_EXTENSIONS'
262 elif selarch == 'IPF/GNU':
263 str += 'MDE_CPU_IPF __GNUC__'
264 elif selarch == 'EBC/INTEL':
265 str += 'MDE_CPU_EBC __INTEL_COMPILER'
266
267 str += ' ASM_PFX= OPTIONAL= '
268
269 def OnGenerate(self, event):
270 if not self.CheckInput(): return
271
272 dlg = ProgressDialog(self)
273 dlg.ShowModal()
274 dlg.Destroy()
275
276 def CheckInput(self):
277 pPath = self.GetPackagePath()
278 wPath = self.GetWorkspace()
279 dPath = self.GetDoxygenToolPath()
280 cPath = self.GetChmToolPath()
281 oPath = self.GetOutputPath()
282
283 if len(wPath) == 0 or not os.path.exists(wPath):
284 self._Error("Please input existing workspace path!")
285 return False
286 else:
287 self.SaveConfigure('WorkspacePath', wPath)
288
289 if len(pPath) == 0 or not os.path.exists(pPath) or not pPath.lower().endswith('.dec'):
290 self._Error("Please input existing package file location!")
291 return False
292 elif pPath.lower().find(wPath.lower()) == -1:
293 self._Error("Package patch should starts with workspace path, such as if workspace path is c:\\edk2, package patch could be c:\\edk2\MdePkg")
294 return False
295 else:
296 self.SaveConfigure('PackagePath', pPath)
297
298 if len(dPath) == 0 or not os.path.exists(dPath):
299 self._Error("Can not find doxygen tool from path %s! Please download it from www.stack.nl/~dimitri/doxygen/download.html" % dPath)
300 return False
301 else:
302 self.SaveConfigure('DoxygenPath', dPath)
303
304 if self._modesel.GetStringSelection() == 'CHM':
305 if (len(cPath) == 0 or not os.path.exists(cPath)):
306 self._Error("You select CHM mode to generate document, but can not find software of Microsoft HTML Help Workshop.\nPlease\
307 download it from http://www.microsoft.com/downloads/details.aspx?FamilyID=00535334-c8a6-452f-9aa0-d597d16580cc&displaylang=en\n\
308 and install!")
309 return False
310 else:
311 self.SaveConfigure('CHMPath', cPath)
312
313 if len(oPath) == 0:
314 self._Error("You must specific document output path")
315 return False
316 else:
317 self.SaveConfigure('OutputPath', oPath)
318
319 if os.path.exists(oPath):
320 # add checking whether there is old doxygen config file here
321 files = os.listdir(oPath)
322 for file in files:
323 if os.path.isfile(os.path.join(oPath,file)):
324 basename, ext = os.path.splitext(file)
325 if ext.lower() == '.doxygen_config':
326 dlg = GMD.GenericMessageDialog(self, "Existing doxygen document in output directory will be overwritten\n, Are you sure?",
327 "Info", wx.ICON_WARNING|wx.YES_NO)
328 if dlg.ShowModal() == wx.ID_YES:
329 break
330 else:
331 return False
332 else:
333 try:
334 os.makedirs(oPath)
335 except:
336 self._Error("Fail to create output directory, please select another output directory!")
337 return False
338
339 return True
340
341 def _Error(self, message):
342 dlg = GMD.GenericMessageDialog(self, message,
343 "Error", wx.ICON_ERROR|wx.OK)
344 dlg.ShowModal()
345 dlg.Destroy()
346
347 def GetWorkspace(self):
348 return os.path.normpath(self._workspacePathCtrl.GetValue())
349
350 def GetPackagePath(self):
351 return os.path.normpath(self._packagePathCtrl.GetValue())
352
353 def GetOutputPath(self):
354 return os.path.normpath(self._outputPathCtrl.GetValue())
355
356 def GetDoxygenToolPath(self):
357 return os.path.normpath(self._doxygenPathCtrl.GetValue())
358
359 def GetChmToolPath(self):
360 return os.path.normpath(self._chmPathCtrl.GetValue())
361
362 def GetDocumentMode(self):
363 return self._modesel.GetStringSelection()
364
365 def GetArchitecture(self):
366 value = self._archCtrl.GetValue()
367 return value.split('/')[0]
368
369 def GetToolTag(self):
370 value = self._archCtrl.GetValue()
371 if value == 'ALL':
372 return 'ALL'
373 return value.split('/')[1]
374
375 def GetIsOnlyDocumentInclude(self):
376 return self._includeonlysel.IsChecked()
377
378 class ProgressDialog(wx.Dialog):
379 def __init__(self, parent, id=wx.ID_ANY):
380 title = "Generate Document for " + parent.GetPackagePath()
381 wx.Dialog.__init__(self, parent, id, title=title, style=wx.CAPTION, size=(600, 300))
382 self.Freeze()
383 sizer = wx.BoxSizer(wx.VERTICAL)
384 self._textCtrl = wx.StaticText(self, -1, "Start launching!")
385 self._gaugeCtrl = wx.Gauge(self, -1, 100, size=(-1, 10))
386 self._resultCtrl = wx.stc.StyledTextCtrl(self, -1)
387 self._closeBt = wx.Button(self, -1, "Close")
388 self._gotoOuputBt = wx.Button(self, -1, "Goto Output")
389
390 # clear all margin
391 self._resultCtrl.SetMarginWidth(0, 0)
392 self._resultCtrl.SetMarginWidth(1, 0)
393 self._resultCtrl.SetMarginWidth(2, 0)
394
395 sizer.Add(self._textCtrl, 0, wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, 5)
396 sizer.Add(self._gaugeCtrl, 0, wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, 5)
397 sizer.Add(self._resultCtrl, 1, wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, 5)
398 btsizer = wx.BoxSizer(wx.HORIZONTAL)
399 btsizer.Add(self._gotoOuputBt, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.LEFT|wx.TOP|wx.LEFT|wx.BOTTOM, 5)
400 btsizer.Add(self._closeBt, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.LEFT|wx.TOP|wx.LEFT|wx.BOTTOM, 5)
401 sizer.Add(btsizer, 0, wx.ALIGN_CENTER_HORIZONTAL)
402
403 self.SetSizer(sizer)
404 self.CenterOnScreen()
405 self.Thaw()
406
407 self._logger = logging.getLogger('')
408 self._loghandle = ResultHandler(self)
409 logging.getLogger('edk').addHandler(self._loghandle)
410 logging.getLogger('').addHandler(self._loghandle)
411 logging.getLogger('app').addHandler(self._loghandle)
412
413 wx.EVT_BUTTON(self._closeBt, self._closeBt.GetId(), self.OnButtonClose)
414 wx.EVT_UPDATE_UI(self, self._closeBt.GetId(), self.OnUpdateCloseButton)
415 wx.EVT_BUTTON(self._gotoOuputBt, self._gotoOuputBt.GetId(), self.OnGotoOutput)
416 EVT_LOG(self, self.OnPostLog)
417
418 self._process = None
419 self._pid = None
420 self._input = None
421 self._output = None
422 self._error = None
423 self._inputThread = None
424 self._errorThread = None
425 self._isBusy = True
426 self._pObj = None
427
428 wx.CallAfter(self.GenerateAction)
429
430 def OnUpdateCloseButton(self, event):
431 self._closeBt.Enable(not self._isBusy)
432 return True
433
434 def OnButtonClose(self, event):
435 if self._isBusy:
436 self._InfoDialog("Please don't close in progressing...")
437 return
438
439 if self._process != None:
440 self._process.CloseOutput()
441
442 if self._inputThread:
443 self._inputThread.Terminate()
444 if self._errorThread:
445 self._errorThread.Terminate()
446
447 if self._pid != None:
448 wx.Process.Kill(self._pid, wx.SIGKILL, wx.KILL_CHILDREN)
449
450 logging.getLogger('edk').removeHandler(self._loghandle)
451 logging.getLogger('').removeHandler(self._loghandle)
452 logging.getLogger('app').removeHandler(self._loghandle)
453
454 if self._pObj != None:
455 self._pObj.Destroy()
456
457 self.EndModal(0)
458
459 def OnGotoOutput(self, event):
460 output = self.GetParent().GetOutputPath()
461 if os.path.exists(output):
462 if wx.Platform == '__WXMSW__':
463 os.startfile(self.GetParent().GetOutputPath())
464 else:
465 import webbrowser
466 webbrowser.open(self.GetParent().GetOutputPath())
467 else:
468 self._ErrorDialog("Output directory does not exist!")
469
470 def _ErrorDialog(self, message):
471 dlg = GMD.GenericMessageDialog(self, message,
472 "Error", wx.ICON_ERROR|wx.OK)
473 dlg.ShowModal()
474 dlg.Destroy()
475
476 def _InfoDialog(self, message):
477 dlg = GMD.GenericMessageDialog(self, message,
478 "Info", wx.ICON_INFORMATION|wx.OK)
479 dlg.ShowModal()
480 dlg.Destroy()
481
482 def _LogStep(self, index, message):
483 stepstr = "Step %d: %s" % (index, message)
484 self._textCtrl.SetLabel(stepstr)
485 self.LogMessage(os.linesep + stepstr + os.linesep)
486 self._gaugeCtrl.SetValue(index * 100 / 6 )
487
488 def OnPostLog(self, event):
489 self.LogMessage(event.message)
490
491 def GenerateAction(self):
492 self._LogStep(1, "Create Package Object Model")
493 wsPath = self.GetParent().GetWorkspace()
494 pkPath = self.GetParent().GetPackagePath()[len(wsPath) + 1:]
495
496 try:
497 pObj = baseobject.Package(None, self.GetParent().GetWorkspace())
498 pObj.Load(pkPath)
499 except:
500 self._ErrorDialog("Fail to create package object model! Please check log.txt under this application folder!")
501 self._isBusy = False
502 return
503 self._pObj = pObj
504
505 self.LogMessage(str(pObj.GetPcds()))
506
507 self._LogStep(2, "Preprocess and Generate Doxygen Config File")
508 try:
509 action = doxygengen.PackageDocumentAction(self.GetParent().GetDoxygenToolPath(),
510 self.GetParent().GetChmToolPath(),
511 self.GetParent().GetOutputPath(),
512 pObj,
513 self.GetParent().GetDocumentMode(),
514 self.LogMessage,
515 self.GetParent().GetArchitecture(),
516 self.GetParent().GetToolTag(),
517 self.GetParent().GetIsOnlyDocumentInclude(),
518 True)
519 except:
520 self._ErrorDialog("Fail to preprocess! Please check log.txt under this application folder!")
521 self._isBusy = False
522 return
523
524 action.RegisterCallbackDoxygenProcess(self.CreateDoxygeProcess)
525
526 try:
527 if not action.Generate():
528 self._isBusy = False
529 self.LogMessage("Fail to generate package document! Please check log.txt under this application folder!", 'error')
530 except:
531 import traceback
532 message = traceback.format_exception(*sys.exc_info())
533 logging.getLogger('').error(''.join(message))
534 self._isBusy = False
535 self._ErrorDialog("Fail to generate package document! Please check log.txt under this application folder!")
536
537 def LogMessage(self, message, level='info'):
538 self._resultCtrl.DocumentEnd()
539 self._resultCtrl.SetReadOnly(False)
540 self._resultCtrl.AppendText(message)
541 self._resultCtrl.Home()
542 self._resultCtrl.Home()
543 self._resultCtrl.SetReadOnly(True)
544 if level == 'error':
545 wx.GetApp().GetLogger().error(message)
546
547 def CreateDoxygeProcess(self, doxPath, configFile):
548 self._LogStep(3, "Launch Doxygen Tool and Generate Package Document")
549
550 cmd = '"%s" %s' % (doxPath, configFile)
551 try:
552 self._process = DoxygenProcess()
553 self._process.SetParent(self)
554 self._process.Redirect()
555 self._pid = wx.Execute(cmd, wx.EXEC_ASYNC, self._process)
556 self._input = self._process.GetInputStream()
557 self._output = self._process.GetOutputStream()
558 self._error = self._process.GetErrorStream()
559 except:
560 self._ErrorDialog('Fail to launch doxygen cmd %s! Please check log.txt under this application folder!' % cmd)
561 self._isBusy = False
562 return False
563
564 self._inputThread = MonitorThread(self._input, self.LogMessage)
565 self._errorThread = MonitorThread(self._error, self.LogMessage)
566 self._inputThread.start()
567 self._errorThread.start()
568 return True
569
570 def OnTerminateDoxygenProcess(self):
571 if self._inputThread:
572 self._inputThread.Terminate()
573 self._inputThread = None
574 if self._errorThread:
575 self._errorThread.Terminate()
576 self._errorThread = None
577
578 if self._error:
579 while self._error.CanRead():
580 text = self._error.read()
581 self.LogMessage(text)
582
583 if self._input:
584 while self._input.CanRead():
585 text = self._input.read()
586 self.LogMessage(text)
587 self._process.Detach()
588
589 self._process.CloseOutput()
590 self._process = None
591 self._pid = None
592
593 self.DocumentFixup()
594
595 if self.GetParent().GetDocumentMode().lower() == 'chm':
596 hhcfile = os.path.join(self.GetParent().GetOutputPath(), 'html', 'index.hhc')
597 hhpfile = os.path.join(self.GetParent().GetOutputPath(), 'html', 'index.hhp')
598 self.FixDecDoxygenFileLink(hhcfile, None)
599 if not self.CreateCHMProcess(self.GetParent().GetChmToolPath(), hhpfile):
600 self._ErrorDialog("Fail to Create %s process for %s" % (self.GetParent().GetChmToolPath(), hhpfile))
601 self._isBusy = False
602 else:
603 self._LogStep(6, "Finished Document Generation!")
604 self._isBusy = False
605 indexpath = os.path.realpath(os.path.join(self.GetParent().GetOutputPath(), 'html', 'index.html'))
606 if wx.Platform == '__WXMSW__':
607 os.startfile(indexpath)
608 else:
609 import webbrowser
610 webbrowser.open(indexpath)
611
612 self._InfoDialog('Success create HTML doxgen document %s' % indexpath)
613
614 def CreateCHMProcess(self, chmPath, hhpfile):
615 self.LogMessage(" >>>>>> Start Microsoft HTML workshop process...Zzz...\n")
616 cmd = '"%s" %s' % (chmPath, hhpfile)
617 try:
618 self._process = CHMProcess()
619 self._process.SetParent(self)
620 self._process.Redirect()
621 self._pid = wx.Execute(cmd, wx.EXEC_ASYNC, self._process)
622 self._input = self._process.GetInputStream()
623 self._output = self._process.GetOutputStream()
624 self._error = self._process.GetErrorStream()
625 except:
626 self.LogMessage('\nFail to launch hhp cmd %s!\n' % cmd)
627 self._isBusy = False
628 return False
629 self._inputThread = MonitorThread(self._input, self.LogMessage)
630 self._errorThread = MonitorThread(self._error, self.LogMessage)
631 self._inputThread.start()
632 self._errorThread.start()
633 return True
634
635 def OnTerminateCHMProcess(self):
636 if self._inputThread:
637 self._inputThread.Terminate()
638 self._inputThread = None
639 if self._errorThread:
640 self._errorThread.Terminate()
641 self._errorThread = None
642
643 if self._error:
644 while self._error.CanRead():
645 text = self._error.read()
646 self.LogMessage(text)
647 if self._input:
648 while self._input.CanRead():
649 text = self._input.read()
650 self.LogMessage(text)
651 self._process.Detach()
652
653 self._process.CloseOutput()
654 self._process = None
655 self._pid = None
656 self._isBusy = False
657 indexpath = os.path.realpath(os.path.join(self.GetParent().GetOutputPath(), 'html', 'index.chm'))
658 if os.path.exists(indexpath):
659 if wx.Platform == '__WXMSW__':
660 os.startfile(indexpath)
661 else:
662 import webbrowser
663 webbrowser.open(indexpath)
664
665 self._LogStep(6, "Finished Document Generation!")
666 self.LogMessage('\nSuccess create CHM doxgen document %s\n' % indexpath)
667 self._InfoDialog('Success create CHM doxgen document %s' % indexpath)
668
669 def DocumentFixup(self):
670 # find BASE_LIBRARY_JUMP_BUFFER structure reference page
671 self._LogStep(4, "Fixup Package Document!")
672 self.LogMessage('\n >>> Start fixup document \n')
673
674 for root, dirs, files in os.walk(os.path.join(self.GetParent().GetOutputPath(), 'html')):
675 for dir in dirs:
676 if dir.lower() in ['.svn', '_svn', 'cvs']:
677 dirs.remove(dir)
678 for file in files:
679 wx.YieldIfNeeded()
680 if not file.lower().endswith('.html'): continue
681 fullpath = os.path.join(self.GetParent().GetOutputPath(), root, file)
682 try:
683 f = open(fullpath, 'r')
684 text = f.read()
685 f.close()
686 except:
687 self.LogMessage('\nFail to open file %s\n' % fullpath)
688 continue
689 if text.find('BASE_LIBRARY_JUMP_BUFFER Struct Reference') != -1 and self.GetParent().GetArchitecture() == 'ALL':
690 self.FixPageBASE_LIBRARY_JUMP_BUFFER(fullpath, text)
691 if text.find('MdePkg/Include/Library/BaseLib.h File Reference') != -1 and self.GetParent().GetArchitecture() == 'ALL':
692 self.FixPageBaseLib(fullpath, text)
693 if text.find('IA32_IDT_GATE_DESCRIPTOR Union Reference') != -1 and self.GetParent().GetArchitecture() == 'ALL':
694 self.FixPageIA32_IDT_GATE_DESCRIPTOR(fullpath, text)
695 if text.find('MdePkg/Include/Library/UefiDriverEntryPoint.h File Reference') != -1:
696 self.FixPageUefiDriverEntryPoint(fullpath, text)
697 if text.find('MdePkg/Include/Library/UefiApplicationEntryPoint.h File Reference') != -1:
698 self.FixPageUefiApplicationEntryPoint(fullpath, text)
699 if text.lower().find('.s.dox') != -1 or \
700 text.lower().find('.asm.dox') != -1 or \
701 text.lower().find('.uni.dox') != -1:
702 self.FixDoxFileLink(fullpath, text)
703
704 self.RemoveFileList()
705 self.LogMessage(' >>> Finish all document fixing up! \n')
706
707 def RemoveFileList(self):
708 path_html = os.path.join(self.GetParent().GetOutputPath(), "html", "tree.html")
709 path_chm = os.path.join(self.GetParent().GetOutputPath(), "html", "index.hhc")
710 if os.path.exists(path_html):
711 self.LogMessage(' >>>Remove FileList item from generated HTML document.\n');
712 lines = []
713 f = open (path_html, "r")
714 lines = f.readlines()
715 f.close()
716 bfound = False
717 for index in range(len(lines)):
718 if lines[index].find('<a class="el" href="files.html" target="basefrm">File List</a>') != -1:
719 lines[index] = "<!-- %s" % lines[index]
720 bfound = True
721 continue
722 if bfound:
723 if lines[index].find('</div>') != -1:
724 lines[index] = "%s -->" % lines[index]
725 break
726 if bfound:
727 f = open(path_html, "w")
728 f.write("".join(lines))
729 f.close()
730 else:
731 self.LogMessage (' !!!Can not found FileList item in HTML document!\n')
732
733 if os.path.exists(path_chm):
734 self.LogMessage(" >>>Warning: Can not remove FileList for CHM files!\n");
735 """
736 self.LogMessage(' >>>Remove FileList item from generated CHM document!\n');
737 lines = []
738 f = open (path_chm, "r")
739 lines = f.readlines()
740 f.close()
741 bfound = False
742 for index in xrange(len(lines)):
743 if not bfound:
744 if lines[index].find('<param name="Local" value="files.html">') != -1:
745 lines[index] = '<!-- %s' % lines[index]
746 bfound = True
747 continue
748 if bfound:
749 if lines[index].find('</UL>') != -1:
750 lines[index] = '%s -->\n' % lines[index].rstrip()
751 break
752 if bfound:
753 f = open(path_chm, "w")
754 f.write("".join(lines))
755 f.close()
756 import time
757 time.sleep(2)
758 else:
759 self.LogMessage(' !!!Can not found the FileList item in CHM document!')
760 """
761 def FixPageBaseLib(self, path, text):
762 self.LogMessage(' >>> Fixup BaseLib file page at file %s \n' % path)
763 lines = text.split('\n')
764 lastBaseJumpIndex = -1
765 lastIdtGateDescriptor = -1
766 for index in range(len(lines) - 1, -1, -1):
767 line = lines[index]
768 if line.strip() == '<td class="memname">#define BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT&nbsp;&nbsp;&nbsp;4 </td>':
769 lines[index] = '<td class="memname">#define BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT&nbsp;&nbsp;&nbsp;4&nbsp;[IA32] </td>'
770 if line.strip() == '<td class="memname">#define BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT&nbsp;&nbsp;&nbsp;0x10 </td>':
771 lines[index] = '<td class="memname">#define BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT&nbsp;&nbsp;&nbsp;0x10&nbsp;[IPF] </td>'
772 if line.strip() == '<td class="memname">#define BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT&nbsp;&nbsp;&nbsp;8 </td>':
773 lines[index] = '<td class="memname">#define BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT&nbsp;&nbsp;&nbsp;9&nbsp;[EBC, x64] </td>'
774 if line.find('BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a>&nbsp;&nbsp;&nbsp;4') != -1:
775 lines[index] = lines[index].replace('BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a>&nbsp;&nbsp;&nbsp;4',
776 'BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a>&nbsp;&nbsp;&nbsp;4&nbsp;[IA32]')
777 if line.find('BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a>&nbsp;&nbsp;&nbsp;0x10') != -1:
778 lines[index] = lines[index].replace('BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a>&nbsp;&nbsp;&nbsp;0x10',
779 'BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a>&nbsp;&nbsp;&nbsp;0x10&nbsp;[IPF]')
780 if line.find('BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a>&nbsp;&nbsp;&nbsp;8') != -1:
781 lines[index] = lines[index].replace('BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a>&nbsp;&nbsp;&nbsp;8',
782 'BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT</a>&nbsp;&nbsp;&nbsp;8&nbsp;[x64, EBC]')
783 if line.find('>BASE_LIBRARY_JUMP_BUFFER</a>') != -1:
784 if lastBaseJumpIndex != -1:
785 del lines[lastBaseJumpIndex]
786 lastBaseJumpIndex = index
787 if line.find('>IA32_IDT_GATE_DESCRIPTOR</a></td>') != -1:
788 if lastIdtGateDescriptor != -1:
789 del lines[lastIdtGateDescriptor]
790 lastIdtGateDescriptor = index
791 try:
792 f = open(path, 'w')
793 f.write('\n'.join(lines))
794 f.close()
795 except:
796 self._isBusy = False
797 self.LogMessage(" <<< Fail to fixup file %s\n" % path)
798 self.LogMessage(" <<< Finish to fixup file %s\n" % path)
799
800 def FixPageIA32_IDT_GATE_DESCRIPTOR(self, path, text):
801 self.LogMessage(' >>> Fixup structure reference IA32_IDT_GATE_DESCRIPTOR at file %s \n' % path)
802 lines = text.split('\n')
803 for index in range(len(lines) - 1, -1, -1):
804 line = lines[index].strip()
805 if line.find('struct {</td>') != -1 and lines[index - 2].find('>Uint64</a></td>') != -1:
806 lines.insert(index, '<tr><td colspan="2"><br><h2>Data Fields For X64</h2></td></tr>')
807 if line.find('struct {</td>') != -1 and lines[index - 1].find('Data Fields') != -1:
808 lines.insert(index, '<tr><td colspan="2"><br><h2>Data Fields For IA32</h2></td></tr>')
809 try:
810 f = open(path, 'w')
811 f.write('\n'.join(lines))
812 f.close()
813 except:
814 self._isBusy = False
815 self.LogMessage(" <<< Fail to fixup file %s\n" % path)
816 self.LogMessage(" <<< Finish to fixup file %s\n" % path)
817
818 def FixPageBASE_LIBRARY_JUMP_BUFFER(self, path, text):
819 self.LogMessage(' >>> Fixup structure reference BASE_LIBRARY_JUMP_BUFFER at file %s \n' % path)
820 lines = text.split('\n')
821 bInDetail = True
822 bNeedRemove = False
823 for index in range(len(lines) - 1, -1, -1):
824 line = lines[index]
825 if line.find('Detailed Description') != -1:
826 bInDetail = False
827 if line.startswith('EBC context buffer used by') and lines[index - 1].startswith('x64 context buffer'):
828 lines[index] = "IA32/IPF/X64/" + line
829 bNeedRemove = True
830 if line.startswith("x64 context buffer") or line.startswith('IPF context buffer used by') or \
831 line.startswith('IA32 context buffer used by'):
832 if bNeedRemove:
833 lines.remove(line)
834 if line.find('>R0</a>') != -1 and not bInDetail:
835 if lines[index - 1] != '<tr><td colspan="2"><br><h2>Data Fields For EBC</h2></td></tr>':
836 lines.insert(index, '<tr><td colspan="2"><br><h2>Data Fields For EBC</h2></td></tr>')
837 if line.find('>Rbx</a>') != -1 and not bInDetail:
838 if lines[index - 1] != '<tr><td colspan="2"><br><h2>Data Fields For X64</h2></td></tr>':
839 lines.insert(index, '<tr><td colspan="2"><br><h2>Data Fields For X64</h2></td></tr>')
840 if line.find('>F2</a>') != -1 and not bInDetail:
841 if lines[index - 1] != '<tr><td colspan="2"><br><h2>Data Fields For IPF</h2></td></tr>':
842 lines.insert(index, '<tr><td colspan="2"><br><h2>Data Fields For IPF</h2></td></tr>')
843 if line.find('>Ebx</a>') != -1 and not bInDetail:
844 if lines[index - 1] != '<tr><td colspan="2"><br><h2>Data Fields For IA32</h2></td></tr>':
845 lines.insert(index, '<tr><td colspan="2"><br><h2>Data Fields For IA32</h2></td></tr>')
846 try:
847 f = open(path, 'w')
848 f.write('\n'.join(lines))
849 f.close()
850 except:
851 self._isBusy = False
852 self.LogMessage(" <<< Fail to fixup file %s" % path)
853 self.LogMessage(" <<< Finish to fixup file %s\n" % path)
854
855 def FixPageUefiDriverEntryPoint(self, path, text):
856 self.LogMessage(' >>> Fixup file reference MdePkg/Include/Library/UefiDriverEntryPoint.h at file %s \n' % path)
857 lines = text.split('\n')
858 bInModuleEntry = False
859 bInEfiMain = False
860 ModuleEntryDlCount = 0
861 ModuleEntryDelStart = 0
862 ModuleEntryDelEnd = 0
863 EfiMainDlCount = 0
864 EfiMainDelStart = 0
865 EfiMainDelEnd = 0
866
867 for index in range(len(lines)):
868 line = lines[index].strip()
869 if line.find('EFI_STATUS</a> EFIAPI _ModuleEntryPoint </td>') != -1:
870 bInModuleEntry = True
871 if line.find('EFI_STATUS</a> EFIAPI EfiMain </td>') != -1:
872 bInEfiMain = True
873 if line.startswith('<p>References <a'):
874 if bInModuleEntry:
875 ModuleEntryDelEnd = index - 1
876 bInModuleEntry = False
877 elif bInEfiMain:
878 EfiMainDelEnd = index - 1
879 bInEfiMain = False
880 if bInModuleEntry:
881 if line.startswith('</dl>'):
882 ModuleEntryDlCount = ModuleEntryDlCount + 1
883 if ModuleEntryDlCount == 1:
884 ModuleEntryDelStart = index + 1
885 if bInEfiMain:
886 if line.startswith('</dl>'):
887 EfiMainDlCount = EfiMainDlCount + 1
888 if EfiMainDlCount == 1:
889 EfiMainDelStart = index + 1
890
891 if EfiMainDelEnd > EfiMainDelStart:
892 for index in range(EfiMainDelEnd, EfiMainDelStart, -1):
893 del lines[index]
894 if ModuleEntryDelEnd > ModuleEntryDelStart:
895 for index in range(ModuleEntryDelEnd, ModuleEntryDelStart, -1):
896 del lines[index]
897
898 try:
899 f = open(path, 'w')
900 f.write('\n'.join(lines))
901 f.close()
902 except:
903 self._isBusy = False
904 self.LogMessage(" <<< Fail to fixup file %s" % path)
905 self.LogMessage(" <<< Finish to fixup file %s\n" % path)
906
907 def FixPageUefiApplicationEntryPoint(self, path, text):
908 self.LogMessage(' >>> Fixup file reference MdePkg/Include/Library/UefiApplicationEntryPoint.h at file %s \n' % path)
909 lines = text.split('\n')
910 bInModuleEntry = False
911 bInEfiMain = False
912 ModuleEntryDlCount = 0
913 ModuleEntryDelStart = 0
914 ModuleEntryDelEnd = 0
915 EfiMainDlCount = 0
916 EfiMainDelStart = 0
917 EfiMainDelEnd = 0
918
919 for index in range(len(lines)):
920 line = lines[index].strip()
921 if line.find('EFI_STATUS</a> EFIAPI _ModuleEntryPoint </td>') != -1:
922 bInModuleEntry = True
923 if line.find('EFI_STATUS</a> EFIAPI EfiMain </td>') != -1:
924 bInEfiMain = True
925 if line.startswith('<p>References <a'):
926 if bInModuleEntry:
927 ModuleEntryDelEnd = index - 1
928 bInModuleEntry = False
929 elif bInEfiMain:
930 EfiMainDelEnd = index - 1
931 bInEfiMain = False
932 if bInModuleEntry:
933 if line.startswith('</dl>'):
934 ModuleEntryDlCount = ModuleEntryDlCount + 1
935 if ModuleEntryDlCount == 1:
936 ModuleEntryDelStart = index + 1
937 if bInEfiMain:
938 if line.startswith('</dl>'):
939 EfiMainDlCount = EfiMainDlCount + 1
940 if EfiMainDlCount == 1:
941 EfiMainDelStart = index + 1
942
943 if EfiMainDelEnd > EfiMainDelStart:
944 for index in range(EfiMainDelEnd, EfiMainDelStart, -1):
945 del lines[index]
946 if ModuleEntryDelEnd > ModuleEntryDelStart:
947 for index in range(ModuleEntryDelEnd, ModuleEntryDelStart, -1):
948 del lines[index]
949
950 try:
951 f = open(path, 'w')
952 f.write('\n'.join(lines))
953 f.close()
954 except:
955 self._isBusy = False
956 self.LogMessage(" <<< Fail to fixup file %s" % path)
957 self.LogMessage(" <<< Finish to fixup file %s\n" % path)
958
959
960 def FixDoxFileLink(self, path, text):
961 self.LogMessage(' >>> Fixup .dox postfix for file %s \n' % path)
962 try:
963 fd = open(path, 'r')
964 text = fd.read()
965 fd.close()
966 except Exception as e:
967 self.LogMessage (" <<<Fail to open file %s" % path)
968 return
969 text = text.replace ('.s.dox', '.s')
970 text = text.replace ('.S.dox', '.S')
971 text = text.replace ('.asm.dox', '.asm')
972 text = text.replace ('.Asm.dox', '.Asm')
973 text = text.replace ('.uni.dox', '.uni')
974 text = text.replace ('.Uni.dox', '.Uni')
975 try:
976 fd = open(path, 'w')
977 fd.write(text)
978 fd.close()
979 except Exception as e:
980 self.LogMessage (" <<<Fail to fixup file %s" % path)
981 return
982 self.LogMessage(' >>> Finish to fixup .dox postfix for file %s \n' % path)
983
984 def FixDecDoxygenFileLink(self, path, text):
985 self.LogMessage(' >>> Fixup .decdoxygen postfix for file %s \n' % path)
986 try:
987 fd = open(path, 'r')
988 lines = fd.readlines()
989 fd.close()
990 except Exception as e:
991 self.LogMessage (" <<<Fail to open file %s" % path)
992 return
993 for line in lines:
994 if line.find('.decdoxygen') != -1:
995 lines.remove(line)
996 break
997 try:
998 fd = open(path, 'w')
999 fd.write("".join(lines))
1000 fd.close()
1001 except Exception as e:
1002 self.LogMessage (" <<<Fail to fixup file %s" % path)
1003 return
1004 self.LogMessage(' >>> Finish to fixup .decdoxygen postfix for file %s \n' % path)
1005
1006 import threading
1007 class MonitorThread(threading.Thread):
1008 def __init__(self, pipe, callback):
1009 threading.Thread.__init__(self)
1010 self._pipe = pipe
1011 self._callback = callback
1012 self._isCancel = False
1013
1014 def run(self):
1015 while (not self._isCancel):
1016 self._pipe.Peek()
1017 if self._pipe.LastRead() == 0:
1018 break
1019 text = self._pipe.read()
1020 if len(text.strip()) != 0:
1021 wx.GetApp().ForegroundProcess(self._callback, (text,))
1022
1023 def Terminate(self):
1024 self._pipe.flush()
1025 self._isCancel = True
1026
1027 class DoxygenProcess(wx.Process):
1028 def OnTerminate(self, id, status):
1029 self._parent.OnTerminateDoxygenProcess()
1030
1031 def SetParent(self, parent):
1032 self._parent = parent
1033
1034 class CHMProcess(wx.Process):
1035 def OnTerminate(self, id, status):
1036 self._parent.OnTerminateCHMProcess()
1037
1038 def SetParent(self, parent):
1039 self._parent = parent
1040
1041 class ResultHandler:
1042 def __init__(self, parent):
1043 self._parent = parent
1044 self.level = 0
1045
1046 def emit(self, record):
1047 self._parent.LogMessage(record)
1048
1049 def handle(self, record):
1050 wx.PostEvent(self._parent, LogEvent(message=record.getMessage()))
1051
1052 def acquire(self):
1053 pass
1054
1055 def release(self):
1056 pass
1057
1058 if __name__ == '__main__':
1059 app = PackageDocApp(redirect=False)
1060 app.MainLoop()