1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 """
28 This is a generator retrieving all plugin (by their name) and retrieve some basic statisitcs
29 """
30
31 __authors__ = ["Jérôme Kieffer", "Régis Perdreau"]
32 __contact__ = "Jerome.Kieffer@esrf.eu"
33 __license__ = "GPLv3"
34 __date__ = "2011-05-20"
35 __copyright__ = "ESRF"
36
37 import os, sys, shutil, stat, time
38
39
40 if "EDNA_HOME" not in os.environ:
41 strEdnaHome = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
42 os.environ["EDNA_HOME"] = strEdnaHome
43 else:
44 strEdnaHome = os.environ["EDNA_HOME"]
47 """
48 @return: a dict with key=plugin and value=path
49 """
50 dictPlugins = {}
51 dictProject = {}
52 iControlPl = 0
53 iExecPl = 0
54 iOtherPl = 0
55 ilen = len(strPath) + 1
56 for root, dirs, files in os.walk(strPath, topdown=False):
57 for name in files:
58 if name.startswith("EDPlugin") and name.endswith(".py"):
59 strPlugin = name[:-3]
60 plugin = EdnaPlugin(os.path.join(root, name)[ilen:])
61 dictPlugins[strPlugin] = plugin
62 plugin.analyse()
63 project = plugin.project
64 strType = plugin.type
65 if htmlpath is not None:
66 html = plugin.getHtml()
67 open(os.path.join(htmlpath, strPlugin + ".html"), "w").write(html)
68
69 if strType == "Control Plugins":
70 iControlPl += 1
71 elif strType == "Execution Plugins":
72 iExecPl += 1
73 else:
74 iOtherPl += 1
75 if project in dictProject:
76 if strType in dictProject[project]:
77 dictProject[project][strType][strPlugin] = plugin
78 else:
79 dictProject[project][strType] = {strPlugin:plugin}
80 else:
81 dictProject[project] = {strType:{strPlugin:plugin}}
82 return dictProject, {"Control":iControlPl, "Execution":iExecPl, "Others": iOtherPl}
83
86 """
87 @type dictProjects: dict
88 @type dictNumbers: dict
89 @return: a string
90 """
91 lstStr = []
92 for i in dictNumbers:
93 lstStr += ["Number of %s Plugins:\t%d" % (i, dictNumbers[i])]
94 lstProjects = dictProjects.keys()
95 lstProjects.sort()
96 for project in lstProjects:
97 lstStr.append(project)
98 lstType = dictProjects[project].keys()
99 lstType.sort()
100 for strType in lstType:
101 lstStr.append("\t%s" % strType)
102 lstPlugins = dictProjects[project][strType].keys()
103 lstPlugins.sort()
104 for strPlugin in lstPlugins:
105 lstStr.append("\t\t%s:\t%s" % (strPlugin, os.linesep.join(dictProjects[project][strType][strPlugin].classDoc)))
106 lstStr.append("")
107 return os.linesep.join(lstStr)
108
111 """
112 @return: a string
113 """
114 lstStr = ["<html>", "<head>", "<title>List of EDNA Plugins</title>", "</head>", \
115 "<body>", "<center><h1>List of EDNA Plugins</h1></center>", "<table>", \
116 "<tr><td>Generated on:</td><td>%s</td></tr>" % (time.asctime())]
117 for i in dictNumbers:
118 lstStr += ["<tr><td>Number of %s Plugins:</td><td>%d</td></tr>" % (i, dictNumbers[i])]
119 lstStr += ["</table><hr>"]
120 lstProjects = dictProjects.keys()
121 lstProjects.sort()
122 for project in lstProjects:
123 lstStr.append("<h2>%s<h2>" % project)
124 lstType = dictProjects[project].keys()
125 lstType.sort()
126 for strType in lstType:
127 lstStr.append("<h3>%s<h3>" % strType)
128 lstPlugins = dictProjects[project][strType].keys()
129 lstPlugins.sort()
130 lstStr.append("<table>")
131 for strPlugin in lstPlugins:
132 plugin = dictProjects[project][strType][strPlugin]
133 for datamodel in plugin.datamodel:
134 for datamodelImg in plugin.dictXSD:
135 if (plugin.datamodel[datamodel] is not None) and \
136 datamodelImg.startswith(plugin.datamodel[datamodel]) and \
137 plugin.dictXSD[datamodelImg].endswith(".png"):
138 destDatamodel = os.path.join(strDest, datamodelImg + ".png")
139 if os.path.isfile(destDatamodel) and os.path.isfile(plugin.dictXSD[datamodelImg]):
140 os.remove(destDatamodel)
141 try:
142 shutil.copy(plugin.dictXSD[datamodelImg], destDatamodel)
143 except shutil.Error:
144 print("Unable to copy %s to %s" % (plugin.dictXSD[plugin.datamodel], destDatamodel))
145 except IOError:
146 print("Unable to find Datamodel Image %s" % (plugin.dictXSD[datamodelImg]))
147 lstStr.append("<tr><td><a href='%s'>%s</a></td><td>%s</td></tr>" %
148 (os.path.join(os.path.basename(strDest), strPlugin + ".html"), strPlugin,
149 "<br>".join([unicode2html(i) for i in plugin.classDoc])))
150
151 lstStr.append("</table>")
152 lstStr.append("<hr>")
153 lstStr.append("</body></html>")
154 return os.linesep.join(lstStr)
155
157 """
158 Converts an unicode input into a "html" like string
159
160 @param _unicodeIn: input unicode
161 @return: html string
162 """
163 dico = {u'\u0022': '"', u'\u0026': '&', u'\u0027': ''', u'\u003C': '<', u'\u003E': '>', u'\u00A0': ' ', u'\u00A1': '¡', u'\u00A2': '¢',
164 u'\u00A3': '£', u'\u00A4': '¤', u'\u00A5': '¥', u'\u00A6': '¦', u'\u00A7': '§', u'\u00A8': '¨', u'\u00A9': '©', u'\u00AA': 'ª',
165 u'\u00AB': '«', u'\u00AC': '¬', u'\u00AD': '­', u'\u00AE': '®', u'\u00AF': '¯', u'\u00B0': '°', u'\u00B1': '±', u'\u00B2': '²',
166 u'\u00B3': '³', u'\u00B4': '´', u'\u00B5': 'µ', u'\u00B6': '¶', u'\u00B7': '·', u'\u00B8': '¸', u'\u00B9': '¹', u'\u00BA': 'º',
167 u'\u00BB': '»', u'\u00BC': '¼', u'\u00BD': '½', u'\u00BE': '¾', u'\u00BF': '¿', u'\u00C0': 'À', u'\u00C1': 'Á', u'\u00C2': 'Â',
168 u'\u00C3': 'Ã', u'\u00C4': 'Ä', u'\u00C5': 'Å', u'\u00C6': 'Æ', u'\u00C7': 'Ç', u'\u00C8': 'È', u'\u00C9': 'É', u'\u00CA': 'Ê',
169 u'\u00CB': 'Ë', u'\u00CC': 'Ì', u'\u00CD': 'Í', u'\u00CE': 'Î', u'\u00CF': 'Ï', u'\u00D0': 'Ð', u'\u00D1': 'Ñ', u'\u00D2': 'Ò',
170 u'\u00D3': 'Ó', u'\u00D4': 'Ô', u'\u00D5': 'Õ', u'\u00D6': 'Ö', u'\u00D7': '×', u'\u00D8': 'Ø', u'\u00D9': 'Ù', u'\u00DA': 'Ú',
171 u'\u00DB': 'Û', u'\u00DC': 'Ü', u'\u00DD': 'Ý', u'\u00DE': 'Þ', u'\u00DF': 'ß', u'\u00E0': 'à', u'\u00E1': 'á', u'\u00E2': 'â',
172 u'\u00E3': 'ã', u'\u00E4': 'ä', u'\u00E5': 'å', u'\u00E6': 'æ', u'\u00E7': 'ç', u'\u00E8': 'è', u'\u00E9': 'é', u'\u00EA': 'ê',
173 u'\u00EB': 'ë', u'\u00EC': 'ì', u'\u00ED': 'í', u'\u00EE': 'î', u'\u00EF': 'ï', u'\u00F0': 'ð', u'\u00F1': 'ñ', u'\u00F2': 'ò',
174 u'\u00F3': 'ó', u'\u00F4': 'ô', u'\u00F5': 'õ', u'\u00F6': 'ö', u'\u00F7': '÷', u'\u00F8': 'ø', u'\u00F9': 'ù', u'\u00FA': 'ú',
175 u'\u00FB': 'û', u'\u00FC': 'ü', u'\u00FD': 'ý', u'\u00FE': 'þ', u'\u00FF': 'ÿ', u'\u0152': 'Œ', u'\u0153': 'œ',
176 u'\u0160': 'Š', u'\u0161': 'š', u'\u0178': 'Ÿ', u'\u0192': 'ƒ', u'\u02C6': 'ˆ', u'\u02DC': '˜',
177 u'\u0391': 'Α', u'\u0392': 'Β', u'\u0393': 'Γ', u'\u0394': 'Δ', u'\u0395': 'Ε',
178 u'\u0396': 'Ζ', u'\u0397': 'Η', u'\u0398': 'Θ', u'\u0399': 'Ι', u'\u039A': 'Κ',
179 u'\u039B': 'Λ', u'\u039C': 'Μ', u'\u039D': 'Ν', u'\u039E': 'Ξ', u'\u039F': 'Ο', u'\u03A0': 'Π', u'\u03A1': 'Ρ', u'\u03A3': 'Σ',
180 u'\u03A4': 'Τ', u'\u03A5': 'Υ', u'\u03A6': 'Φ', u'\u03A7': 'Χ', u'\u03A8': 'Ψ', u'\u03A9': 'Ω', u'\u03B1': 'α', u'\u03B2': 'β',
181 u'\u03B3': 'γ', u'\u03B4': 'δ', u'\u03B5': 'ε', u'\u03B6': 'ζ', u'\u03B7': 'η', u'\u03B8': 'θ',
182 u'\u03B9': 'ι', u'\u03BA': 'κ', u'\u03BB': 'λ', u'\u03BC': 'μ', u'\u03BD': 'ν', u'\u03BE': 'ξ',
183 u'\u03BF': 'ο', u'\u03C0': 'π', u'\u03C1': 'ρ', u'\u03C2': 'ς', u'\u03C3': 'σ', u'\u03C4': 'τ', u'\u03C5': 'υ',
184 u'\u03C6': 'φ', u'\u03C7': 'χ', u'\u03C8': 'ψ', u'\u03C9': 'ω', u'\u03D1': 'ϑ', u'\u03D2': 'ϒ', u'\u03D6': 'ϖ', u'\u2002': ' ',
185 u'\u2003': ' ', u'\u2009': ' ', u'\u200C': '‌', u'\u200D': '‍', u'\u200E': '‎', u'\u200F': '‏', u'\u2013': '–', u'\u2014': '—', u'\u2018': '‘',
186 u'\u2019': '’', u'\u201A': '‚', u'\u201C': '“', u'\u201D': '”', u'\u201E': '„', u'\u2020': '†', u'\u2021': '‡', u'\u2022': '•',
187 u'\u2026': '…', u'\u2030': '‰', u'\u2032': '′', u'\u2033': '″', u'\u2039': '‹', u'\u203A': '›', u'\u203E': '‾', u'\u2044': '⁄',
188 u'\u20AC': '€', u'\u2111': 'ℑ', u'\u2118': '℘', u'\u211C': 'ℜ', u'\u2122': '™', u'\u2135': 'ℵ', u'\u2190': '←', u'\u2191': '↑',
189 u'\u2192': '→', u'\u2193': '↓', u'\u2194': '↔', u'\u21B5': '↵', u'\u21D0': '⇐', u'\u21D1': '⇑', u'\u21D2': '⇒', u'\u21D3': '⇓', u'\u21D4': '⇔',
190 u'\u2200': '∀', u'\u2202': '∂', u'\u2203': '∃', u'\u2205': '∅', u'\u2207': '∇', u'\u2208': '∈', u'\u2209': '∉', u'\u220B': '∋',
191 u'\u220F': '∏', u'\u2211': '∑', u'\u2212': '−', u'\u2217': '∗', u'\u221A': '√', u'\u221D': '∝', u'\u221E': '∞', u'\u2220': '∠', u'\u2227': '∧',
192 u'\u2228': '∨', u'\u2229': '∩', u'\u222A': '∪', u'\u222B': '∫', u'\u2234': '∴', u'\u223C': '∼', u'\u2245': '≅', u'\u2248': '≈', u'\u2260': '≠',
193 u'\u2261': '≡', u'\u2264': '≤', u'\u2265': '≥', u'\u2282': '⊂', u'\u2283': '⊃', u'\u2284': '⊄', u'\u2286': '⊆', u'\u2287': '⊇', u'\u2295': '⊕',
194 u'\u2297': '⊗', u'\u22A5': '⊥', u'\u22C5': '⋅', u'\u2308': '⌈', u'\u2309': '⌉', u'\u230A': '⌊', u'\u230B': '⌋', u'\u27E8': '⟨',
195 u'\u27E9': '⟩', u'\u25CA': '◊', u'\u2660': '♠', u'\u2663': '♣', u'\u2665': '♥', u'\u2666': '♦'}
196
197 strOut = ""
198 if _unicodeIn is not None:
199 for i in _unicodeIn:
200 if i in dico:
201 strOut += dico[i]
202 else:
203 strOut += str(i)
204 return strOut
205
207 """
208 represents an EDNA Plugin: authore, date, doc, datamodel, ...
209 """
210 strEdnaHome = strEdnaHome
211 dictXSD = {}
213 self.dirname, self.basename = os.path.split(_strPath)
214 self.path = _strPath
215 if not(self.basename.startswith("EDPlugin") and self.basename.endswith(".py")):
216 self.isaPlugin = False
217 raise RuntimeError("Not an EDNA Plugin: %s" % _strPath)
218 self.name = self.basename[:-3]
219 self.project = _strPath.split(os.sep)[0]
220 if len(self.project) == 0:
221 self.project = _strPath.split(os.sep)[1]
222 self.abspath = os.path.abspath(os.path.join(self.strEdnaHome, _strPath))
223 self.date = None
224 self.author = None
225 self.license = None
226 self.copyright = None
227 self.type = None
228 self.moduleDoc = []
229 self.classDoc = []
230 self.type = "Plugins (other)"
231 self.isaPlugin = True
232 self.__coding = None
233 self.datamodel = {}
234 if self.dictXSD == {}:
235 self.loadXSD()
236
237
239 """
240 Parse the source code to analyse the plugin
241 """
242 print("Analysing: %s" % self.name)
243 lines = open(os.path.join(self.strEdnaHome, self.path)).readlines()
244 classline = None
245
246 starter = None
247 coding = None
248 lstmoduledoc = []
249 for line in lines:
250 if ("coding:" in line) and (coding is None):
251 coding = line[line.index("coding:") + 7:].split()[0].strip()
252 if "#" in line:
253 doc = line[:line.index("#")].rstrip()
254 if line.startswith("__author__") and "=" in line:
255 if coding is not None:
256 self.author = eval(line.split("=")[1]).decode(coding)
257 else:
258 self.author = eval(line.split("=")[1])
259 if line.startswith("__authors__") and "=" in line:
260 if coding is not None:
261 self.author = ", ".join(eval(line.split("=")[1])).decode(coding)
262 else:
263 self.author = ", ".join(eval(line.split("=")[1]))
264 if line.startswith("__copyright__") and "=" in line:
265 if coding is not None:
266 self.copyright = eval(line.split("=")[1]).decode(coding)
267 else:
268 self.copyright = eval(line.split("=")[1])
269 if line.startswith("__date__") and "=" in line:
270 if coding is not None:
271 self.date = eval(line.split("=")[1]).decode(coding)
272 else:
273 self.date = eval(line.split("=")[1])
274
275 if line.startswith("__license__") and "=" in line:
276 if coding is not None:
277 self.license = eval(line.split("=")[1]).decode(coding)
278 else:
279 self.license = eval(line.split("=")[1])
280 if (line.startswith("class") and line.split()[1].startswith(self.name)):
281 classline = line
282 if line.strip().startswith("self.setXSDataInputClass("):
283 self.datamodel[(line.split("(", 1)[1].split(")", 1)[0].split(",")[0].split(".")[0]).strip()] = None
284
285 if (self.moduleDoc == []) and (starter is not None) and (lstmoduledoc != []):
286 if starter in line:
287 lstmoduledoc.append(line[:line.index(starter)])
288 self.moduleDoc = lstmoduledoc
289 else:
290 lstmoduledoc.append(line)
291
292 if (self.moduleDoc == []) and (starter is None):
293 if line.startswith("'''"):
294 starter = "'''"
295 elif line.startswith('"""'):
296 starter = '"""'
297 elif line.startswith("'"):
298 starter = "'"
299 elif line.startswith('"'):
300 starter = '"'
301 else:
302 starter = None
303 if starter is not None:
304 if line.strip().endswith(starter) and (len(line) > (2 * len(starter))):
305 self.moduleDoc = [line[len(starter):-len(starter)]]
306 else:
307 lstmoduledoc.append(line[len(starter):])
308
309 for xsdIn in self.datamodel:
310 for line in lines:
311 if line.startswith("from") and ("import" in line) and (xsdIn in line):
312 self.datamodel[xsdIn] = line.split()[1]
313 break
314
315 starter = None
316 if classline is not None:
317 lstclasstypes = classline.split("(", 1)
318 if len(lstclasstypes) == 1:
319 self.isaPlugin = False
320 print("ERROR: Does not inherit from EDPlugin: %s" % self.name)
321 return
322 classtypes = lstclasstypes[1].strip()
323 if classtypes.startswith("EDPluginControl"):
324 self.type = "Control Plugins"
325 elif classtypes.startswith("EDPluginExec"):
326 self.type = "Execution Plugins"
327 else:
328 self.isaPlugin = False
329 raise RuntimeError("Does not look like an EDNA Plugin: %s" % self.path)
330 return
331 idx = lines.index(classline) + 1
332 lstclassdoc = []
333 doc = lines[idx ].strip()
334 if "#" in doc:
335 doc = doc[:doc.index["#"]].strip()
336 if doc.startswith("'''"):
337 starter = "'''"
338 elif doc.startswith('"""'):
339 starter = '"""'
340 elif doc.startswith("'"):
341 starter = "'"
342 elif doc.startswith('"'):
343 starter = '"'
344 else:
345 starter = None
346 if starter is not None:
347 if doc.endswith(starter) and (len(doc) > (2 * len(starter))):
348 lstclassdoc.append(doc[len(starter):-len(starter)])
349 else:
350 lstclassdoc.append(doc[len(starter):])
351 bFinished = False
352 while not bFinished:
353 idx += 1
354 doc = lines[idx ].strip()
355 if "#" in doc:
356 doc = doc[:doc.index("#")].strip()
357 if starter in doc:
358 lstclassdoc.append(doc[:doc.index(starter)])
359 bFinished = True
360 else:
361 lstclassdoc.append(doc)
362
363 self.classDoc = lstclassdoc
364 self.moduleDoc = [i for i in self.moduleDoc if i.strip() != "" ]
365 self.classDoc = [i for i in self.classDoc if i.strip() != "" ]
366 if coding is not None:
367 newModuleDoc = []
368 newClassDoc = []
369 for line in self.moduleDoc:
370 try:
371 dec = line.decode(coding)
372 except Exception:
373 dec = line
374 print("Unable to decode %s with %s" % (line, coding))
375 else:
376 newModuleDoc.append(dec)
377 self.moduleDoc = newModuleDoc
378 for line in self.classDoc:
379 try:
380 dec = line.decode(coding)
381 except Exception:
382 dec = line
383 print("Unable to decode %s with %s" % (line, coding))
384 else:
385 newClassDoc.append(dec)
386 self.classDoc = newClassDoc
387 self.isaPlugin = True
388 self.__coding = coding
389
390
392 """
393 Generate a web page with the documentation for the plugin
394 """
395 if coding is None:
396 coding = self.__coding
397 if coding is None:
398 try:
399 import locale
400 except ImportError:
401 coding = "ascii"
402 else:
403 coding = locale.getdefaultlocale()[1]
404 if coding is None:
405 coding = "ascii"
406
407 lstStr = ["<html>", "<head>", "<title>EDNA Plugin: %s</title>" % self.name, "</head>", "<body>", "<center><h1>EDNA Plugin: %s</h1></center>" % self.name, "<table>"]
408 lstStr.append("<tr><td>Name:</td><td>%s</td</tr>" % self.name)
409 lstStr.append("<tr><td>Project:</td><td>%s</td</tr>" % self.project)
410 lstStr.append("<tr><td>Path:</td><td>%s</td</tr>" % self.path)
411 lstStr.append("<tr><td>Author:</td><td>%s</td</tr>" % unicode2html(self.author))
412 lstStr.append("<tr><td>Date:</td><td>%s</td</tr>" % unicode2html(self.date))
413 lstStr.append("<tr><td>Copyright:</td><td>%s</td</tr>" % unicode2html(self.copyright))
414 lstStr.append("<tr><td>License:</td><td>%s</td</tr>" % unicode2html(self.license))
415 lstStr.append("<tr><td>Module doc:</td><td>%s</td</tr>" % "<br>".join([unicode2html(i) for i in self.moduleDoc]))
416 lstStr.append("<tr><td>Class doc:</td><td>%s</td</tr>" % "<br>".join([unicode2html(i) for i in self.classDoc]))
417 lstStr += ["</table><hr>"]
418
419 onpage = {}
420 for datamodel in self.datamodel:
421 if self.datamodel[datamodel] is None:
422 continue
423 for datamodelImg in self.dictXSD:
424 if datamodelImg.startswith(self.datamodel[datamodel]):
425 if datamodelImg in onpage:
426 onpage[datamodelImg].append(datamodel)
427 else:
428 onpage[datamodelImg] = [datamodel]
429
430 for img in onpage:
431 lstStr.append("Datamodels: %s <br \ >" % ", ".join(onpage[img]))
432 if self.dictXSD[img].endswith(".png"):
433 lstStr.append("<img src='%s.png'><hr>" % img)
434 elif self.dictXSD[img].endswith(".edml"):
435 lstStr += [edml2html(open(self.dictXSD[img]).read()), "<hr>"]
436 lstStr.append("</body></html>")
437
438 return os.linesep.join(lstStr)
439
440 @classmethod
442 """
443 look for all datamodel images.
444 """
445 fullPath = ""
446 if cls.dictXSD == {}:
447 for root, dirs, files in os.walk(strEdnaHome, topdown=False):
448 for name in files:
449 base, ext = os.path.splitext(name)
450 if name.startswith("XSData") and (ext in [".edml", ".png"]):
451 fullPath = os.path.join(root, name)
452 if fullPath.startswith(strDest):
453 try:
454 os.remove(fullPath)
455 except IOError:
456 print("Unable to remove %s" % fullPath)
457 elif base in cls.dictXSD :
458 if os.stat(fullPath)[stat.ST_MTIME] > os.stat(cls.dictXSD[base])[stat.ST_MTIME]:
459 cls.dictXSD[base] = fullPath
460 else:
461 cls.dictXSD[base] = fullPath
462
465 """
466 Convert an EDML file to a piece of HTML string
467 @param chaine: input string (EDML format)
468 @type chaine: string
469 """
470 COLORS = {'red':'FF0000', 'orange':'FF4500', 'yellow':'FFFF00', 'green':'00EE00', 'magenta':'FF00FF', 'cyan':'00FFFF', 'blue':'0000FF',
471 'violet':'D02090', 'black':'000000', 'gray':'BEBEBE'}
472 dictInst = {'targetNamespace': 'red',
473 'complex': 'blue',
474 'type': 'blue',
475 'COMMENT': 'green',
476 'package': 'orange',
477 "optional": "orange",
478 "[": "orange",
479 "]": "orange",
480 ":": "violet",
481 "{": "blue",
482 "}": "blue",
483 }
484
485 listStruct = []
486 for idx, subchain in enumerate(chaine.split('"')):
487 if idx % 2 == 0:
488 listLines = []
489 for line in subchain.replace("\t", " ").replace(" ", " ").split("\n"):
490 for key in dictInst:
491 pos = line.find(key)
492 if pos >= 0:
493 line = '%s<FONT COLOR="%s">%s</FONT>%s' % (line[:pos], COLORS[dictInst[key]], line[pos:pos + len(key)], line[pos + len(key):])
494 listLines.append(line)
495 listStruct.append("<BR>\n".join(listLines))
496 else:
497 listStruct.append('<FONT COLOR="%s">"%s"</FONT>' % (COLORS[dictInst['COMMENT']], subchain.replace("\n", "<BR>\n")))
498 return "".join(listStruct)
499
500
501 if __name__ == "__main__":
502 strDest = os.path.join(os.getcwd() , "edna")
503 if len(sys.argv) > 1:
504 strDest = os.path.abspath(sys.argv[1])
505 if not os.path.isdir(strDest):
506 os.makedirs(strDest)
507 datas = getPlugins(strEdnaHome, strDest)
508 txt = prettyHtml(*datas)
509 open(strDest + ".html", "w").write(txt)
510