Ticket #12: build-toolchest-menus.py

File build-toolchest-menus.py, 11.6 KB (added by joeb1002, 18 months ago)

Python Script to Build system.chestrc for MaXX Desktop Toolchest

Line 
1#!/usr/bin/env python
2"""
3build-toolchest-menus
4
5Utility to create a system wide chestrc file
6for the Maxx desktop toolchest application.
7
8Copyright 2010, Joe Bacom
9
10This program is free software; you can redistribute it and/or
11modify it under the terms of the GNU General Public License
12as published by the Free Software Foundation; either version 2
13of the License, or (at your option) any later version.
14
15This program is distributed in the hope that it will be useful,
16but WITHOUT ANY WARRANTY; without even the implied warranty of
17MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18GNU General Public License for more details.
19
20You should have received a copy of the GNU General Public License
21along with this program; if not, write to the Free Software
22Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
23USA.
24
25"""
26
27import sys, os, xml, re, xml.sax.handler
28
29from subprocess import *
30
31menuNames = []
32chestNames = []
33
34defaultShell = '/opt/MaXX/bin/winterm'
35
36def get_XML_doc():
37    # execute xdg_menu with the following parameters
38    # --format xfce4
39    # --fullmenu
40    # and capture the XML output into string variable
41
42    p = Popen('/usr/bin/xdg_menu --format xfce4 --fullmenu',
43              bufsize=0, stdin=PIPE, stdout=PIPE, close_fds=True, shell=True)
44
45    rawXML = p.stdout.read()
46   
47    if len(rawXML) < 1:
48        print "No XML content from XDG Menu."
49        print "Exiting..."
50        exit()
51   
52    xmldoc = XML2obj(rawXML)
53   
54    build_chest_menu(xmldoc)
55
56def get_menus_list(node):
57   
58    menus = []
59   
60    for menu in node.menu:
61        menus.append(menu.name)
62   
63    return menus
64   
65def get_apps_list(node):
66   
67    apps = {}
68   
69    for app in node.app:
70        apps[app.name] = {}
71        apps[app.name]['name'] = app.name
72        apps[app.name]['cmd']  = app.cmd
73        apps[app.name]['term'] = app.term 
74   
75    return apps
76   
77def process_menu(node):
78   
79    chest = ""
80   
81    # This is sub menu
82    for menuItem in node.menu:
83        hasSubMenu = False
84        # Open this chest
85        chest += open_chest(menuItem, node.name, False)
86        # Check to see if there is another sub menu
87        if menuItem.menu:
88            hasSubMenu = True
89            # There is a sub-menu, get the menu list
90            chest += add_menu_list_to_chest(menuItem, chest, False)
91       
92        # Check to see if there are any apps for this menu
93        if menuItem.app:
94            # There are apps, get the appl list
95            chest += add_app_list_to_chest(menuItem)
96           
97        if menuItem.name == 'System':
98            chest += append_to_system()
99           
100        chest += close_chest()
101       
102        if hasSubMenu:
103            # Recursively call processMenu until the end
104            chest += process_menu(menuItem)
105           
106    return chest
107
108def open_chest(node, parent, toplevel=False):
109    # Open a new chest menu
110   
111    if toplevel:
112        # Open the toplevel chest (ToolChest)
113        chest = 'Menu ToolChest\n{\n'
114    else:
115        menuTag = node.name.replace(' ', '')
116       
117        # If menuTag is already in chestNames, the menuTag has already been
118        # used
119        if menuTag in chestNames:
120            menuTag = '%s%s' % (node.name.replace(' ', ''),
121                                parent.replace(' ', ''))
122           
123        chest = 'Menu %s\n{\n' % menuTag
124           
125        chestNames.append(menuTag)
126       
127    return chest
128
129def close_chest():
130    # Close the menu list   
131    return '}\n\n'
132
133def add_menu_list_to_chest(node, hasApps = False):
134    # Adds a set of menu options to the chest
135   
136    chest = ""
137    menus = get_menus_list(node)
138   
139    x = 1
140    if len(menus) > 0:
141        menus.sort()
142       
143    for menu in menus:
144        if menu in menuNames:
145            menuTag = '%s%s' % (menu, node.name)
146        else:
147            menuTag = menu
148           
149        chest += '\t"%s"\t\t\tf.menu %s\n' % (menu,
150                                              menuTag.replace(' ', ''))
151       
152        # Don't add a separator to the last menu option unless
153        # this menu also contains applications
154        if x < len(menus) or hasApps:
155            chest += '\tseparator\t\t\tf.separator\n'
156           
157        x += 1
158       
159        # Add this menu to the menu list for later checking
160        menuNames.append(menu)
161       
162    if node.name == 'System':
163        chest += append_to_system()
164       
165    return chest
166
167def add_app_list_to_chest(node):
168    # Adds a set of application(s) options to the chest
169   
170    chest = ""
171    apps = get_apps_list(node)
172   
173    keys = apps.keys()
174    if len(keys) > 0:
175        keys.sort()
176       
177    for k in keys:
178        app = apps[k]
179        cmd = app['cmd'].replace('"', "'")
180       
181        if app['term'] == 'yes':
182            # The app requires a shell window to launch
183            chest += '\t"%s..."\t\t\tf.checkexec.sh.le ' % app['name']
184            chest += '"%s %s"\n' % (defaultShell,  cmd)
185        else:
186            chest += '\t"%s..."\t\t\tf.checkexec.sh.le "%s"\n' % (
187                app['name'], cmd)
188           
189    return chest
190   
191def append_to_system():
192   
193    menus = '\tseparator\t\t\tf.separator\n'
194    menus += '\tRestart Window Manager\t\t\tf.exec "/opt/MaXX/bin/tellwm restart"\n'
195    menus += '\tseparator\t\t\tf.separator\n'
196    menus += '\tseparator\t\t\tf.separator\n'
197    menus += '\t"Log Out"\t\t\tf.exec "/opt/MaXX/bin/tellwm quit"\n'
198   
199    return menus
200   
201def build_chest_menu(xmldoc):
202   
203    fullchest = '!!\n!! System Menu Description\n!!\n'
204   
205    # Open the chest for the top level menu
206    fullchest += open_chest(xmldoc, None, True)
207   
208    # Add user level Desktop menu
209    fullchest += '\t"Desktop"\t\t\tf.menu Desktop\n'
210    fullchest += '\tseparator\t\t\tf.separator\n'
211    menuNames.append(u"Desktop")
212    chestNames.append(u"Desktop")
213   
214    # Get the toplevel menus
215    fullchest += add_menu_list_to_chest(xmldoc)
216   
217    # Close the top level menus
218    fullchest += close_chest()
219   
220    # Process each menu in toplevel menus
221    for menuItem in xmldoc.menu:
222        hasSubMenu = False
223        hasApps = False
224        # Process menus first so they are at the
225        # top of the drop down / fold out
226       
227        # Open the chest for this menu
228        fullchest += open_chest(menuItem, menuItem.name, False)
229       
230        if menuItem.app:
231            hasApps = True
232           
233        # Check to see if there is a sub menu
234        if menuItem.menu:
235            hasSubMenu = True
236            # There is a sub-menu, get the menu list
237            fullchest += add_menu_list_to_chest(menuItem, hasApps)
238           
239        # Check to see if there are any apps for this menu
240        if menuItem.app:
241            # There are apps, get the appl list
242            fullchest += add_app_list_to_chest(menuItem)
243           
244        # Close the chest
245        fullchest += close_chest()
246       
247        if hasSubMenu:
248            fullchest += process_menu(menuItem)
249
250    # Write the file out to disk
251    fd = open('/tmp/system.chestrc', 'w')
252    fd.write(fullchest.encode('UTF-8'))
253    fd.close()
254   
255    return
256
257def XML2obj(src):
258    """
259       xmlstruct.py
260       Module to convert a XML source file to a python data
261       structure.
262       
263       Adapted from original source by author:  Wai Yip Tung.
264       Original source can be found here:
265       http://code.activestate.com/recipes/534109/
266       
267       Example:
268       
269       >>> SAMPLE_XML = <?xml version="1.0" encoding="UTF-8"?>
270       ... <address_book>
271       ...   <person gender='m'>
272       ...     <name>fred</name>
273       ...     <phone type='home'>54321</phone>
274       ...     <phone type='cell'>12345</phone>
275       ...     <note>&quot;A<!-- comment --><![CDATA[ <note>]]>&quot;</note>
276       ...   </person>
277       ... </address_book>
278       >>> address_book = xml2obj(SAMPLE_XML)
279       >>> person = address_book.person
280       
281       person.gender        -> 'm'     # an attribute
282       person['gender']     -> 'm'     # alternative dictionary syntax
283       person.name          -> 'fred'  # shortcut to a text node
284       person.phone[0].type -> 'home'  # multiple elements becomes an list
285       person.phone[0].data -> '54321' # use .data to get the text value
286       str(person.phone[0]) -> '54321' # alternative syntax for the text value
287       person[0]            -> person  # if there are only one <person>, it can still
288                                       # be used as if it is a list of 1 element.
289       'address' in person  -> False   # test for existence of an attr or child
290       person.address       -> None    # non-exist element returns None
291       bool(person.address) -> False   # has any 'address' data (attr, child or text)
292       person.note          -> '"A <note>"'
293    """
294   
295    non_id_char = re.compile('[^_0-9a-zA-Z]')
296    def _name_mangle(name):
297        return non_id_char.sub('_', name)
298
299    class DataNode(object):
300        def __init__(self):
301            # XML attributes and child elements
302            self._attrs = {}
303             # child text data
304            self.data = None
305        def __len__(self):
306            # treat single element as a list of 1
307            return 1
308        def __getitem__(self, key):
309            if isinstance(key, basestring):
310                return self._attrs.get(key,None)
311            else:
312                return [self][key]
313        def __contains__(self, name):
314            return self._attrs.has_key(name)
315        def __nonzero__(self):
316            return bool(self._attrs or self.data)
317        def __getattr__(self, name):
318            if name.startswith('__'):
319                # need to do this for Python special methods???
320                raise AttributeError(name)
321            return self._attrs.get(name,None)
322        def _add_xml_attr(self, name, value):
323            if name in self._attrs:
324                # multiple attribute of the same name are represented by a list
325                children = self._attrs[name]
326                if not isinstance(children, list):
327                    children = [children]
328                    self._attrs[name] = children
329                children.append(value)
330            else:
331                self._attrs[name] = value
332        def __str__(self):
333            return self.data or ''
334        def __repr__(self):
335            items = sorted(self._attrs.items())
336            if self.data:
337                items.append(('data', self.data))
338            return '{%s}' % ', '.join(['%s:%s' % (k,repr(v)) for k,v in items])
339
340    class TreeBuilder(xml.sax.handler.ContentHandler):
341        def __init__(self):
342            self.stack = []
343            self.root = DataNode()
344            self.current = self.root
345            self.text_parts = []
346        def startElement(self, name, attrs):
347            self.stack.append((self.current, self.text_parts))
348            self.current = DataNode()
349            self.text_parts = []
350            # xml attributes --> python attributes
351            for k, v in attrs.items():
352                self.current._add_xml_attr(_name_mangle(k), v)
353        def endElement(self, name):
354            text = ''.join(self.text_parts).strip()
355            if text:
356                self.current.data = text
357            if self.current._attrs:
358                obj = self.current
359            else:
360                # a text only node is simply represented by the string
361                obj = text or ''
362            self.current, self.text_parts = self.stack.pop()
363            self.current._add_xml_attr(_name_mangle(name), obj)
364        def characters(self, content):
365            self.text_parts.append(content)
366
367    builder = TreeBuilder()
368    if isinstance(src,basestring):
369        xml.sax.parseString(src, builder)
370    else:
371        xml.sax.parse(src, builder)
372    return builder.root._attrs.values()[0]
373
374if __name__ == '__main__':
375    get_XML_doc()
376