toolboxes.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. """!
  2. @package core.toolboxes
  3. @brief Functions for modifying menu from default/user toolboxes specified in XML files
  4. (C) 2013 by the GRASS Development Team
  5. This program is free software under the GNU General Public License
  6. (>=v2). Read the file COPYING that comes with GRASS for details.
  7. @author Vaclav Petras <wenzeslaus gmail.com>
  8. @author Anna Petrasova <kratochanna gmail.com>
  9. """
  10. import os
  11. import sys
  12. import copy
  13. import xml.etree.ElementTree as etree
  14. from xml.parsers import expat
  15. # Get the XML parsing exceptions to catch. The behavior chnaged with Python 2.7
  16. # and ElementTree 1.3.
  17. if hasattr(etree, 'ParseError'):
  18. ETREE_EXCEPTIONS = (etree.ParseError, expat.ExpatError)
  19. else:
  20. ETREE_EXCEPTIONS = (expat.ExpatError)
  21. if sys.version_info[0:2] > (2, 6):
  22. has_xpath = True
  23. else:
  24. has_xpath = False
  25. if __name__ == '__main__':
  26. gui_wx_path = os.path.join(os.getenv('GISBASE'), 'etc', 'gui', 'wxpython')
  27. if gui_wx_path not in sys.path:
  28. sys.path.append(gui_wx_path)
  29. from core.globalvar import ETCWXDIR
  30. from core.utils import GetSettingsPath
  31. from core.gcmd import GError, RunCommand
  32. import grass.script.task as gtask
  33. import grass.script.core as gcore
  34. from grass.script.core import ScriptError
  35. # this could be placed to functions
  36. mainMenuFile = os.path.join(ETCWXDIR, 'xml', 'main_menu.xml')
  37. toolboxesFile = os.path.join(ETCWXDIR, 'xml', 'toolboxes.xml')
  38. wxguiItemsFile = os.path.join(ETCWXDIR, 'xml', 'wxgui_items.xml')
  39. moduleItemsFile = os.path.join(ETCWXDIR, 'xml', 'module_items.xml')
  40. userToolboxesFile = os.path.join(GetSettingsPath(), 'toolboxes', 'toolboxes.xml')
  41. userMainMenuFile = os.path.join(GetSettingsPath(), 'toolboxes', 'main_menu.xml')
  42. if not os.path.exists(userToolboxesFile):
  43. userToolboxesFile = None
  44. if not os.path.exists(userMainMenuFile):
  45. userMainMenuFile = None
  46. def toolboxesOutdated():
  47. """!Removes auto-generated menudata.xml
  48. to let gui regenerate it next time it starts."""
  49. path = os.path.join(GetSettingsPath(), 'toolboxes', 'menudata.xml')
  50. if os.path.exists(path):
  51. gcore.try_remove(path)
  52. def getMenuFile():
  53. """!Returns path to XML file for building menu.
  54. Creates toolbox directory where user defined toolboxes should be located.
  55. Checks whether it is needed to create new XML file (user changed toolboxes)
  56. or the already generated file could be used.
  57. If something goes wrong during building or user doesn't modify menu,
  58. default file (from distribution) is returned.
  59. """
  60. fallback = os.path.join(ETCWXDIR, 'xml', 'menudata.xml')
  61. # always create toolboxes directory if does not exist yet
  62. tbDir = _setupToolboxes()
  63. if tbDir:
  64. menudataFile = os.path.join(tbDir, 'menudata.xml')
  65. generateNew = False
  66. # when any of main_menu.xml or toolboxes.xml are changed,
  67. # generate new menudata.xml
  68. if os.path.exists(menudataFile):
  69. # remove menu file when there is no main_menu and toolboxes
  70. if not userToolboxesFile and not userMainMenuFile:
  71. os.remove(menudataFile)
  72. return fallback
  73. if bool(userToolboxesFile) != bool(userMainMenuFile):
  74. # always generate new because we don't know if there has been any change
  75. generateNew = True
  76. else:
  77. # if newer files -> generate new
  78. menudataTime = os.path.getmtime(menudataFile)
  79. if userToolboxesFile:
  80. if os.path.getmtime(userToolboxesFile) > menudataTime:
  81. generateNew = True
  82. if userMainMenuFile:
  83. if os.path.getmtime(userMainMenuFile) > menudataTime:
  84. generateNew = True
  85. elif userToolboxesFile or userMainMenuFile:
  86. generateNew = True
  87. else:
  88. return fallback
  89. if generateNew:
  90. try:
  91. tree = toolboxes2menudata()
  92. except ETREE_EXCEPTIONS:
  93. GError(_("Unable to parse user toolboxes XML files. "
  94. "Default toolboxes will be loaded."))
  95. return fallback
  96. try:
  97. xml = _getXMLString(tree.getroot())
  98. fh = open(os.path.join(tbDir, 'menudata.xml'), 'w')
  99. fh.write(xml)
  100. fh.close()
  101. return menudataFile
  102. except:
  103. return fallback
  104. else:
  105. return menudataFile
  106. else:
  107. return fallback
  108. def _setupToolboxes():
  109. """!Create 'toolboxes' directory if doesn't exist."""
  110. basePath = GetSettingsPath()
  111. path = os.path.join(basePath, 'toolboxes')
  112. if not os.path.exists(basePath):
  113. return None
  114. if _createPath(path):
  115. return path
  116. return None
  117. def _createPath(path):
  118. """!Creates path (for toolboxes) if it doesn't exist'"""
  119. if not os.path.exists(path):
  120. try:
  121. os.mkdir(path)
  122. except OSError, e:
  123. # we cannot use GError or similar because the gui doesn''t start at all
  124. gcore.warning('%(reason)s\n%(detail)s' %
  125. ({'reason':_('Unable to create toolboxes directory.'),
  126. 'detail': str(e)}))
  127. return False
  128. return True
  129. def toolboxes2menudata(userDefined=True):
  130. """!Creates XML file with data for menu.
  131. Parses toolboxes files from distribution and from users,
  132. puts them together, adds metadata to modules and convert
  133. tree to previous format used for loading menu.
  134. @param userDefined use toolboxes defined by user or not (during compilation)
  135. @return ElementTree instance
  136. """
  137. if userDefined and userMainMenuFile:
  138. mainMenu = etree.parse(userMainMenuFile)
  139. else:
  140. mainMenu = etree.parse(mainMenuFile)
  141. toolboxes = etree.parse(toolboxesFile)
  142. if userDefined and userToolboxesFile:
  143. userToolboxes = etree.parse(userToolboxesFile)
  144. else:
  145. userToolboxes = None
  146. wxguiItems = etree.parse(wxguiItemsFile)
  147. moduleItems = etree.parse(moduleItemsFile)
  148. return toolboxes2menudataInternal(mainMenu=mainMenu,
  149. toolboxes=toolboxes,
  150. userToolboxes=userToolboxes,
  151. wxguiItems=wxguiItems,
  152. moduleItems=moduleItems)
  153. def toolboxes2menudataInternal(mainMenu, toolboxes, userToolboxes,
  154. wxguiItems, moduleItems):
  155. """!Creates XML file with data for menu.
  156. Parses toolboxes files from distribution and from users,
  157. puts them together, adds metadata to modules and convert
  158. tree to previous format used for loading menu.
  159. @param userDefined use toolboxes defined by user or not (during compilation)
  160. @return ElementTree instance
  161. """
  162. root = mainMenu.getroot()
  163. userHasToolboxes = False
  164. # in case user has empty toolboxes file (to avoid genereation)
  165. if userToolboxes and userToolboxes.findall('.//toolbox'):
  166. _expandUserToolboxesItem(root, userToolboxes)
  167. _expandToolboxes(root, userToolboxes)
  168. userHasToolboxes = True
  169. if not userHasToolboxes:
  170. _removeUserToolboxesItem(root)
  171. _expandAddonsItem(root)
  172. _expandToolboxes(root, toolboxes)
  173. _expandItems(root, moduleItems, 'module-item')
  174. _expandItems(root, wxguiItems, 'wxgui-item')
  175. # in case of compilation there are no additional runtime modules
  176. # but we need to create empty elements
  177. _expandRuntimeModules(root)
  178. _addHandlers(root)
  179. _convertTree(root)
  180. _indent(root)
  181. return mainMenu
  182. def _indent(elem, level=0):
  183. """!Helper function to fix indentation of XML files."""
  184. i = "\n" + level * " "
  185. if len(elem):
  186. if not elem.text or not elem.text.strip():
  187. elem.text = i + " "
  188. if not elem.tail or not elem.tail.strip():
  189. elem.tail = i
  190. for elem in elem:
  191. _indent(elem, level + 1)
  192. if not elem.tail or not elem.tail.strip():
  193. elem.tail = i
  194. else:
  195. if level and (not elem.tail or not elem.tail.strip()):
  196. elem.tail = i
  197. def _expandToolboxes(node, toolboxes):
  198. """!Expands tree with toolboxes.
  199. Function is called recursively.
  200. @param node tree node where to look for subtoolboxes to be expanded
  201. @param toolboxes tree of toolboxes to be used for expansion
  202. >>> menu = etree.fromstring('''
  203. ... <toolbox name="Raster">
  204. ... <label>&amp;Raster</label>
  205. ... <items>
  206. ... <module-item name="r.mask"/>
  207. ... <wxgui-item name="RasterMapCalculator"/>
  208. ... <subtoolbox name="NeighborhoodAnalysis"/>
  209. ... <subtoolbox name="OverlayRasters"/>
  210. ... </items>
  211. ... </toolbox>''')
  212. >>> toolboxes = etree.fromstring('''
  213. ... <toolboxes>
  214. ... <toolbox name="NeighborhoodAnalysis">
  215. ... <label>Neighborhood analysis</label>
  216. ... <items>
  217. ... <module-item name="r.neighbors"/>
  218. ... <module-item name="v.neighbors"/>
  219. ... </items>
  220. ... </toolbox>
  221. ... <toolbox name="OverlayRasters">
  222. ... <label>Overlay rasters</label>
  223. ... <items>
  224. ... <module-item name="r.cross"/>
  225. ... </items>
  226. ... </toolbox>
  227. ... </toolboxes>''')
  228. >>> _expandToolboxes(menu, toolboxes)
  229. >>> print etree.tostring(menu)
  230. <toolbox name="Raster">
  231. <label>&amp;Raster</label>
  232. <items>
  233. <module-item name="r.mask" />
  234. <wxgui-item name="RasterMapCalculator" />
  235. <toolbox name="NeighborhoodAnalysis">
  236. <label>Neighborhood analysis</label>
  237. <items>
  238. <module-item name="r.neighbors" />
  239. <module-item name="v.neighbors" />
  240. </items>
  241. </toolbox>
  242. <toolbox name="OverlayRasters">
  243. <label>Overlay rasters</label>
  244. <items>
  245. <module-item name="r.cross" />
  246. </items>
  247. </toolbox>
  248. </items>
  249. </toolbox>
  250. """
  251. nodes = node.findall('.//toolbox')
  252. if node.tag == 'toolbox': # root
  253. nodes.append(node)
  254. for n in nodes:
  255. if n.find('items') is None:
  256. continue
  257. for subtoolbox in n.findall('./items/subtoolbox'):
  258. items = n.find('./items')
  259. idx = items.getchildren().index(subtoolbox)
  260. if has_xpath:
  261. toolbox = toolboxes.find('.//toolbox[@name="%s"]' % subtoolbox.get('name'))
  262. else:
  263. toolbox = None
  264. potentialToolboxes = toolboxes.findall('.//toolbox')
  265. sName = subtoolbox.get('name')
  266. for pToolbox in potentialToolboxes:
  267. if pToolbox.get('name') == sName:
  268. toolbox = pToolbox
  269. break
  270. if toolbox is None: # not in file
  271. continue
  272. _expandToolboxes(toolbox, toolboxes)
  273. items.insert(idx, toolbox)
  274. items.remove(subtoolbox)
  275. def _expandUserToolboxesItem(node, toolboxes):
  276. """!Expand tag 'user-toolboxes-list'.
  277. Include all user toolboxes.
  278. >>> tree = etree.fromstring('<toolbox><items><user-toolboxes-list/></items></toolbox>')
  279. >>> toolboxes = etree.fromstring('<toolboxes><toolbox name="UserToolbox"><items><module-item name="g.region"/></items></toolbox></toolboxes>')
  280. >>> _expandUserToolboxesItem(tree, toolboxes)
  281. >>> etree.tostring(tree)
  282. '<toolbox><items><toolbox name="GeneratedUserToolboxesList"><label>Toolboxes</label><items><toolbox name="UserToolbox"><items><module-item name="g.region" /></items></toolbox></items></toolbox></items></toolbox>'
  283. """
  284. tboxes = toolboxes.findall('.//toolbox')
  285. for n in node.findall('./items/user-toolboxes-list'):
  286. items = node.find('./items')
  287. idx = items.getchildren().index(n)
  288. el = etree.Element('toolbox', attrib={'name': 'GeneratedUserToolboxesList'})
  289. items.insert(idx, el)
  290. label = etree.SubElement(el, tag='label')
  291. label.text = _("Toolboxes")
  292. it = etree.SubElement(el, tag='items')
  293. for toolbox in tboxes:
  294. it.append(copy.deepcopy(toolbox))
  295. items.remove(n)
  296. def _removeUserToolboxesItem(root):
  297. """!Removes tag 'user-toolboxes-list' if there are no user toolboxes.
  298. >>> tree = etree.fromstring('<toolbox><items><user-toolboxes-list/></items></toolbox>')
  299. >>> _removeUserToolboxesItem(tree)
  300. >>> etree.tostring(tree)
  301. '<toolbox><items /></toolbox>'
  302. """
  303. for n in root.findall('./items/user-toolboxes-list'):
  304. items = root.find('./items')
  305. items.remove(n)
  306. def _expandAddonsItem(node):
  307. """!Expands tag addons (in main_menu.xml) with currently installed addons.append
  308. Note: there is no mechanism yet to tell the gui to rebuild the menudata.xml
  309. file when new addons are added/removed.
  310. """
  311. # no addonsTag -> do nothing
  312. addonsTags = node.findall('./items/addons')
  313. if not addonsTags:
  314. return
  315. # fetch addons
  316. addons = sorted(RunCommand('g.extension', quiet=True, read=True,
  317. flags = 'a').splitlines())
  318. # no addons -> remove addons tag
  319. if not addons:
  320. for n in addonsTags:
  321. items = node.find('./items')
  322. idx = items.getchildren().index(n)
  323. items.remove(n)
  324. return
  325. # create addons toolbox
  326. # keywords and desc are handled later automatically
  327. for n in addonsTags:
  328. items = node.find('./items')
  329. idx = items.getchildren().index(n)
  330. el = etree.Element('toolbox', attrib={'name': 'AddonsToolboxesList'})
  331. items.insert(idx, el)
  332. label = etree.SubElement(el, tag='label')
  333. label.text = _("Addons")
  334. it = etree.SubElement(el, tag='items')
  335. for addon in addons:
  336. addonItem = etree.SubElement(it, tag='module-item')
  337. addonItem.attrib = {'name': addon}
  338. addonLabel = etree.SubElement(addonItem, tag='label')
  339. addonLabel.text = addon
  340. items.remove(n)
  341. def _expandItems(node, items, itemTag):
  342. """!Expand items from file
  343. >>> tree = etree.fromstring('<items><module-item name="g.region"></module-item></items>')
  344. >>> items = etree.fromstring('<module-items><module-item name="g.region"><module>g.region</module><description>GRASS region management</description></module-item></module-items>')
  345. >>> _expandItems(tree, items, 'module-item')
  346. >>> etree.tostring(tree)
  347. '<items><module-item name="g.region"><module>g.region</module><description>GRASS region management</description></module-item></items>'
  348. """
  349. for moduleItem in node.findall('.//' + itemTag):
  350. itemName = moduleItem.get('name')
  351. if has_xpath:
  352. moduleNode = items.find('.//%s[@name="%s"]' % (itemTag, itemName))
  353. else:
  354. moduleNode = None
  355. potentialModuleNodes = items.findall('.//%s' % itemTag)
  356. for mNode in potentialModuleNodes:
  357. if mNode.get('name') == itemName:
  358. moduleNode = mNode
  359. break
  360. if moduleNode is None: # module not available in dist
  361. continue
  362. mItemChildren = moduleItem.getchildren()
  363. tagList = [n.tag for n in mItemChildren]
  364. for node in moduleNode.getchildren():
  365. if node.tag not in tagList:
  366. moduleItem.append(node)
  367. def _expandRuntimeModules(node):
  368. """!Add information to modules (desc, keywords)
  369. by running them with --interface-description.
  370. >>> tree = etree.fromstring('<items>'
  371. ... '<module-item name="g.region"></module-item>'
  372. ... '</items>')
  373. >>> _expandRuntimeModules(tree)
  374. >>> etree.tostring(tree)
  375. '<items><module-item name="g.region"><module>g.region</module><description>Manages the boundary definitions for the geographic region.</description><keywords>general,settings</keywords></module-item></items>'
  376. """
  377. modules = node.findall('.//module-item')
  378. for module in modules:
  379. name = module.get('name')
  380. if module.find('module') is None:
  381. n = etree.SubElement(parent=module, tag='module')
  382. n.text = name
  383. if module.find('description') is None:
  384. desc, keywords = _loadMetadata(name)
  385. n = etree.SubElement(parent=module, tag='description')
  386. n.text = _escapeXML(desc)
  387. n = etree.SubElement(parent=module, tag='keywords')
  388. n.text = _escapeXML(','.join(keywords))
  389. def _escapeXML(text):
  390. """!Helper function for correct escaping characters for XML.
  391. Duplicate function in core/toolboxes and probably also in man compilation
  392. and some existing Python package.
  393. >>> _escapeXML('<>&')
  394. '&amp;lt;&gt;&amp;'
  395. """
  396. return text.replace('<', '&lt;').replace("&", '&amp;').replace(">", '&gt;')
  397. def _loadMetadata(module):
  398. """!Load metadata to modules.
  399. @param module module name
  400. @return (description, keywords as a list)
  401. """
  402. try:
  403. task = gtask.parse_interface(module)
  404. except ScriptError:
  405. return '', ''
  406. return task.get_description(full=True), \
  407. task.get_keywords()
  408. def _addHandlers(node):
  409. """!Add missing handlers to modules"""
  410. for n in node.findall('.//module-item'):
  411. if n.find('handler') is None:
  412. handlerNode = etree.SubElement(parent=n, tag='handler')
  413. handlerNode.text = 'OnMenuCmd'
  414. # e.g. g.region -p
  415. for n in node.findall('.//wxgui-item'):
  416. if n.find('command') is not None:
  417. handlerNode = etree.SubElement(parent=n, tag='handler')
  418. handlerNode.text = 'RunMenuCmd'
  419. def _convertTag(node, old, new):
  420. """!Converts tag name.
  421. >>> tree = etree.fromstring('<toolboxes><toolbox><items><module-item/></items></toolbox></toolboxes>')
  422. >>> _convertTag(tree, 'toolbox', 'menu')
  423. >>> _convertTag(tree, 'module-item', 'menuitem')
  424. >>> etree.tostring(tree)
  425. '<toolboxes><menu><items><menuitem /></items></menu></toolboxes>'
  426. """
  427. for n in node.findall('.//%s' % old):
  428. n.tag = new
  429. def _convertTagAndRemoveAttrib(node, old, new):
  430. """Converts tag name and removes attributes.
  431. >>> tree = etree.fromstring('<toolboxes><toolbox name="Raster"><items><module-item name="g.region"/></items></toolbox></toolboxes>')
  432. >>> _convertTagAndRemoveAttrib(tree, 'toolbox', 'menu')
  433. >>> _convertTagAndRemoveAttrib(tree, 'module-item', 'menuitem')
  434. >>> etree.tostring(tree)
  435. '<toolboxes><menu><items><menuitem /></items></menu></toolboxes>'
  436. """
  437. for n in node.findall('.//%s' % old):
  438. n.tag = new
  439. n.attrib = {}
  440. def _convertTree(root):
  441. """!Converts tree to be the form readable by core/menutree.py.
  442. >>> tree = etree.fromstring('<toolbox name="MainMenu"><label>Main menu</label><items><toolbox><label>Raster</label><items><module-item name="g.region"><module>g.region</module></module-item></items></toolbox></items></toolbox>')
  443. >>> _convertTree(tree)
  444. >>> etree.tostring(tree)
  445. '<menudata><menubar><menu><label>Raster</label><items><menuitem><command>g.region</command></menuitem></items></menu></menubar></menudata>'
  446. """
  447. root.attrib = {}
  448. label = root.find('label')
  449. root.remove(label)
  450. _convertTag(root, 'description', 'help')
  451. _convertTag(root, 'wx-id', 'id')
  452. _convertTag(root, 'module', 'command')
  453. _convertTag(root, 'related-module', 'command')
  454. _convertTagAndRemoveAttrib(root, 'wxgui-item', 'menuitem')
  455. _convertTagAndRemoveAttrib(root, 'module-item', 'menuitem')
  456. root.tag = 'menudata'
  457. i1 = root.find('./items')
  458. i1.tag = 'menubar'
  459. _convertTagAndRemoveAttrib(root, 'toolbox', 'menu')
  460. def _getXMLString(root):
  461. """!Converts XML tree to string
  462. Since it is usually requier, this function adds a comment (about
  463. autogenerated file) to XML file.
  464. @return XML as string
  465. """
  466. xml = etree.tostring(root, encoding='UTF-8')
  467. return xml.replace("<?xml version='1.0' encoding='UTF-8'?>\n",
  468. "<?xml version='1.0' encoding='UTF-8'?>\n"
  469. "<!--This is an auto-generated file-->\n")
  470. def do_doctest_gettext_workaround():
  471. """Setups environment for doing a doctest with gettext usage.
  472. When using gettext with dynamically defined underscore function
  473. (`_("For translation")`), doctest does not work properly. One option is to
  474. use `import as` instead of dynamically defined underscore function but this
  475. would require change all modules which are used by tested module. This
  476. should be considered for the future. The second option is to define dummy
  477. underscore function and one other function which creates the right
  478. environment to satisfy all. This is done by this function.
  479. """
  480. def new_displayhook(string):
  481. """A replacement for default `sys.displayhook`"""
  482. if string is not None:
  483. sys.stdout.write("%r\n" % (string,))
  484. def new_translator(string):
  485. """A fake gettext underscore function."""
  486. return string
  487. sys.displayhook = new_displayhook
  488. import __builtin__
  489. __builtin__._ = new_translator
  490. def doc_test():
  491. """Tests the module using doctest
  492. @return a number of failed tests
  493. """
  494. import doctest
  495. do_doctest_gettext_workaround()
  496. return doctest.testmod().failed
  497. def module_test():
  498. """Tests the module using test files included in the current directory and
  499. in files from distribution.
  500. """
  501. toolboxesFile = os.path.join(ETCWXDIR, 'xml', 'toolboxes.xml')
  502. userToolboxesFile = 'test.toolboxes_user_toolboxes.xml'
  503. menuFile = 'test.toolboxes_menu.xml'
  504. wxguiItemsFile = os.path.join(ETCWXDIR, 'xml', 'wxgui_items.xml')
  505. moduleItemsFile = os.path.join(ETCWXDIR, 'xml', 'module_items.xml')
  506. toolboxes = etree.parse(toolboxesFile)
  507. userToolboxes = etree.parse(userToolboxesFile)
  508. menu = etree.parse(menuFile)
  509. wxguiItems = etree.parse(wxguiItemsFile)
  510. moduleItems = etree.parse(moduleItemsFile)
  511. tree = toolboxes2menudataInternal(mainMenu=menu,
  512. toolboxes=toolboxes,
  513. userToolboxes=userToolboxes,
  514. wxguiItems=wxguiItems,
  515. moduleItems=moduleItems)
  516. root = tree.getroot()
  517. tested = _getXMLString(root)
  518. # useful to generate the correct file (uncomment)
  519. #sys.stdout.write(_getXMLString(root))
  520. #return 0
  521. menudataFile = 'test.toolboxes_menudata.xml'
  522. with open(menudataFile) as correctMenudata:
  523. correct = str(correctMenudata.read())
  524. import difflib
  525. differ = difflib.Differ()
  526. result = list(differ.compare(correct.splitlines(True),
  527. tested.splitlines(True)))
  528. someDiff = False
  529. for line in result:
  530. if line.startswith('+') or line.startswith('-'):
  531. sys.stdout.write(line)
  532. someDiff = True
  533. if someDiff:
  534. print "difference"
  535. else:
  536. print "OK"
  537. return 0
  538. def main():
  539. """Converts the toolboxes files on standard paths to the menudata file
  540. File is written to the standard output.
  541. """
  542. tree = toolboxes2menudata(userDefined=False)
  543. root = tree.getroot()
  544. sys.stdout.write(_getXMLString(root))
  545. return 0
  546. if __name__ == '__main__':
  547. if len(sys.argv) > 1:
  548. if sys.argv[1] == 'doctest':
  549. sys.exit(doc_test())
  550. elif sys.argv[1] == 'test':
  551. sys.exit(module_test())
  552. sys.exit(main())