home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / python-support / python-xdg / xdg / Menu.py < prev    next >
Encoding:
Python Source  |  2009-03-04  |  28.6 KB  |  1,067 lines

  1. """
  2. Implementation of the XDG Menu Specification Version 1.0.draft-1
  3. http://standards.freedesktop.org/menu-spec/
  4. """
  5.  
  6. from __future__ import generators
  7. import locale, os, xml.dom.minidom
  8.  
  9. from xdg.BaseDirectory import *
  10. from xdg.DesktopEntry import *
  11. from xdg.Exceptions import *
  12.  
  13. import xdg.Locale
  14. import xdg.Config
  15.  
  16. ELEMENT_NODE = xml.dom.Node.ELEMENT_NODE
  17.  
  18. class Menu:
  19.     def __init__(self):
  20.         # Public stuff
  21.         self.Name = ""
  22.         self.Directory = None
  23.         self.Entries = []
  24.         self.Doc = ""
  25.         self.Filename = ""
  26.         self.Depth = 0
  27.         self.Parent = None
  28.         self.NotInXml = False
  29.  
  30.         # Can be one of Deleted/NoDisplay/Hidden/Empty/NotShowIn or True
  31.         self.Show = True
  32.         self.Visible = 0
  33.  
  34.         # Private stuff, only needed for parsing
  35.         self.AppDirs = []
  36.         self.DefaultLayout = None
  37.         self.Deleted = "notset"
  38.         self.Directories = []
  39.         self.DirectoryDirs = []
  40.         self.Layout = None
  41.         self.MenuEntries = []
  42.         self.Moves = []
  43.         self.OnlyUnallocated = "notset"
  44.         self.Rules = []
  45.         self.Submenus = []
  46.  
  47.     def __str__(self):
  48.         return self.Name
  49.  
  50.     def __add__(self, other):
  51.         for dir in other.AppDirs:
  52.             self.AppDirs.append(dir)
  53.  
  54.         for dir in other.DirectoryDirs:
  55.             self.DirectoryDirs.append(dir)
  56.  
  57.         for directory in other.Directories:
  58.             self.Directories.append(directory)
  59.  
  60.         if other.Deleted != "notset":
  61.             self.Deleted = other.Deleted
  62.  
  63.         if other.OnlyUnallocated != "notset":
  64.             self.OnlyUnallocated = other.OnlyUnallocated
  65.  
  66.         if other.Layout:
  67.             self.Layout = other.Layout
  68.  
  69.         if other.DefaultLayout:
  70.             self.DefaultLayout = other.DefaultLayout
  71.  
  72.         for rule in other.Rules:
  73.             self.Rules.append(rule)
  74.  
  75.         for move in other.Moves:
  76.             self.Moves.append(move)
  77.  
  78.         for submenu in other.Submenus:
  79.             self.addSubmenu(submenu)
  80.  
  81.         return self
  82.  
  83.     # FIXME: Performance: cache getName()
  84.     def __cmp__(self, other):
  85.         return locale.strcoll(self.getName(), other.getName())
  86.  
  87.     def __eq__(self, other):
  88.         if self.Name == str(other):
  89.             return True
  90.         else:
  91.             return False
  92.  
  93.     """ PUBLIC STUFF """
  94.     def getEntries(self, hidden=False):
  95.         for entry in self.Entries:
  96.             if hidden == True:
  97.                 yield entry
  98.             elif entry.Show == True:
  99.                 yield entry
  100.  
  101.     # FIXME: Add searchEntry/seaqrchMenu function
  102.     # search for name/comment/genericname/desktopfileide
  103.     # return multiple items
  104.  
  105.     def getMenuEntry(self, desktopfileid, deep = False):
  106.         for menuentry in self.MenuEntries:
  107.             if menuentry.DesktopFileID == desktopfileid:
  108.                 return menuentry
  109.         if deep == True:
  110.             for submenu in self.Submenus:
  111.                 submenu.getMenuEntry(desktopfileid, deep)
  112.  
  113.     def getMenu(self, path):
  114.         array = path.split("/", 1)
  115.         for submenu in self.Submenus:
  116.             if submenu.Name == array[0]:
  117.                 if len(array) > 1:
  118.                     return submenu.getMenu(array[1])
  119.                 else:
  120.                     return submenu
  121.  
  122.     def getPath(self, org=False, toplevel=False):
  123.         parent = self
  124.         names=[]
  125.         while 1:
  126.             if org:
  127.                 names.append(parent.Name)
  128.             else:
  129.                 names.append(parent.getName())
  130.             if parent.Depth > 0:
  131.                 parent = parent.Parent
  132.             else:
  133.                 break
  134.         names.reverse()
  135.         path = ""
  136.         if toplevel == False:
  137.             names.pop(0)
  138.         for name in names:
  139.             path = os.path.join(path, name)
  140.         return path
  141.  
  142.     def getName(self):
  143.         try:
  144.             return self.Directory.DesktopEntry.getName()
  145.         except AttributeError:
  146.             return self.Name
  147.  
  148.     def getGenericName(self):
  149.         try:
  150.             return self.Directory.DesktopEntry.getGenericName()
  151.         except AttributeError:
  152.             return ""
  153.  
  154.     def getComment(self):
  155.         try:
  156.             return self.Directory.DesktopEntry.getComment()
  157.         except AttributeError:
  158.             return ""
  159.  
  160.     def getIcon(self):
  161.         try:
  162.             return self.Directory.DesktopEntry.getIcon()
  163.         except AttributeError:
  164.             return ""
  165.  
  166.     """ PRIVATE STUFF """
  167.     def addSubmenu(self, newmenu):
  168.         for submenu in self.Submenus:
  169.             if submenu == newmenu:
  170.                 submenu += newmenu
  171.                 break
  172.         else:
  173.             self.Submenus.append(newmenu)
  174.             newmenu.Parent = self
  175.             newmenu.Depth = self.Depth + 1
  176.  
  177. class Move:
  178.     "A move operation"
  179.     def __init__(self, node=None):
  180.         if node:
  181.             self.parseNode(node)
  182.         else:
  183.             self.Old = ""
  184.             self.New = ""
  185.  
  186.     def __cmp__(self, other):
  187.         return cmp(self.Old, other.Old)
  188.  
  189.     def parseNode(self, node):
  190.         for child in node.childNodes:
  191.             if child.nodeType == ELEMENT_NODE:
  192.                 if child.tagName == "Old":
  193.                     try:
  194.                         self.parseOld(child.childNodes[0].nodeValue)
  195.                     except IndexError:
  196.                         raise ValidationError('Old cannot be empty', '??')                                            
  197.                 elif child.tagName == "New":
  198.                     try:
  199.                         self.parseNew(child.childNodes[0].nodeValue)
  200.                     except IndexError:
  201.                         raise ValidationError('New cannot be empty', '??')                                            
  202.  
  203.     def parseOld(self, value):
  204.         self.Old = value
  205.     def parseNew(self, value):
  206.         self.New = value
  207.  
  208.  
  209. class Layout:
  210.     "Menu Layout class"
  211.     def __init__(self, node=None):
  212.         self.order = []
  213.         if node:
  214.             self.show_empty = node.getAttribute("show_empty") or "false"
  215.             self.inline = node.getAttribute("inline") or "false"
  216.             self.inline_limit = node.getAttribute("inline_limit") or 4
  217.             self.inline_header = node.getAttribute("inline_header") or "true"
  218.             self.inline_alias = node.getAttribute("inline_alias") or "false"
  219.             self.inline_limit = int(self.inline_limit)
  220.             self.parseNode(node)
  221.         else:
  222.             self.show_empty = "false"
  223.             self.inline = "false"
  224.             self.inline_limit = 4
  225.             self.inline_header = "true"
  226.             self.inline_alias = "false"
  227.             self.order.append(["Merge", "menus"])
  228.             self.order.append(["Merge", "files"])
  229.  
  230.     def parseNode(self, node):
  231.         for child in node.childNodes:
  232.             if child.nodeType == ELEMENT_NODE:
  233.                 if child.tagName == "Menuname":
  234.                     try:
  235.                         self.parseMenuname(
  236.                             child.childNodes[0].nodeValue,
  237.                             child.getAttribute("show_empty") or "false",
  238.                             child.getAttribute("inline") or "false",
  239.                             child.getAttribute("inline_limit") or 4,
  240.                             child.getAttribute("inline_header") or "true",
  241.                             child.getAttribute("inline_alias") or "false" )
  242.                     except IndexError:
  243.                         raise ValidationError('Menuname cannot be empty', "")
  244.                 elif child.tagName == "Separator":
  245.                     self.parseSeparator()
  246.                 elif child.tagName == "Filename":
  247.                     try:
  248.                         self.parseFilename(child.childNodes[0].nodeValue)
  249.                     except IndexError:
  250.                         raise ValidationError('Filename cannot be empty', "")
  251.                 elif child.tagName == "Merge":
  252.                     self.parseMerge(child.getAttribute("type") or "all")
  253.  
  254.     def parseMenuname(self, value, empty="false", inline="false", inline_limit=4, inline_header="true", inline_alias="false"):
  255.         self.order.append(["Menuname", value, empty, inline, inline_limit, inline_header, inline_alias])
  256.         self.order[-1][4] = int(self.order[-1][4])
  257.  
  258.     def parseSeparator(self):
  259.         self.order.append(["Separator"])
  260.  
  261.     def parseFilename(self, value):
  262.         self.order.append(["Filename", value])
  263.  
  264.     def parseMerge(self, type="all"):
  265.         self.order.append(["Merge", type])
  266.  
  267.  
  268. class Rule:
  269.     "Inlcude / Exclude Rules Class"
  270.     def __init__(self, type, node=None):
  271.         # Type is Include or Exclude
  272.         self.Type = type
  273.         # Rule is a python expression
  274.         self.Rule = ""
  275.  
  276.         # Private attributes, only needed for parsing
  277.         self.Depth = 0
  278.         self.Expr = [ "or" ]
  279.         self.New = True
  280.  
  281.         # Begin parsing
  282.         if node:
  283.             self.parseNode(node)
  284.             self.compile()
  285.  
  286.     def __str__(self):
  287.         return self.Rule
  288.  
  289.     def compile(self):
  290.         exec("""
  291. def do(menuentries, type, run):
  292.     for menuentry in menuentries:
  293.         if run == 2 and ( menuentry.MatchedInclude == True \
  294.         or menuentry.Allocated == True ):
  295.             continue
  296.         elif %s:
  297.             if type == "Include":
  298.                 menuentry.Add = True
  299.                 menuentry.MatchedInclude = True
  300.             else:
  301.                 menuentry.Add = False
  302.     return menuentries
  303. """ % self.Rule) in self.__dict__
  304.  
  305.     def parseNode(self, node):
  306.         for child in node.childNodes:
  307.             if child.nodeType == ELEMENT_NODE:
  308.                 if child.tagName == 'Filename':
  309.                     try:
  310.                         self.parseFilename(child.childNodes[0].nodeValue)
  311.                     except IndexError:
  312.                         raise ValidationError('Filename cannot be empty', "???")
  313.                 elif child.tagName == 'Category':
  314.                     try:
  315.                         self.parseCategory(child.childNodes[0].nodeValue)
  316.                     except IndexError:
  317.                         raise ValidationError('Category cannot be empty', "???")
  318.                 elif child.tagName == 'All':
  319.                     self.parseAll()
  320.                 elif child.tagName == 'And':
  321.                     self.parseAnd(child)
  322.                 elif child.tagName == 'Or':
  323.                     self.parseOr(child)
  324.                 elif child.tagName == 'Not':
  325.                     self.parseNot(child)
  326.  
  327.     def parseNew(self, set=True):
  328.         if not self.New:
  329.             self.Rule += " " + self.Expr[self.Depth] + " "
  330.         if not set:
  331.             self.New = True
  332.         elif set:
  333.             self.New = False
  334.  
  335.     def parseFilename(self, value):
  336.         self.parseNew()
  337.         self.Rule += "menuentry.DesktopFileID == '%s'" % value.strip().replace("\\", r"\\").replace("'", r"\'")
  338.  
  339.     def parseCategory(self, value):
  340.         self.parseNew()
  341.         self.Rule += "'%s' in menuentry.Categories" % value.strip()
  342.  
  343.     def parseAll(self):
  344.         self.parseNew()
  345.         self.Rule += "True"
  346.  
  347.     def parseAnd(self, node):
  348.         self.parseNew(False)
  349.         self.Rule += "("
  350.         self.Depth += 1
  351.         self.Expr.append("and")
  352.         self.parseNode(node)
  353.         self.Depth -= 1
  354.         self.Expr.pop()
  355.         self.Rule += ")"
  356.  
  357.     def parseOr(self, node):
  358.         self.parseNew(False)
  359.         self.Rule += "("
  360.         self.Depth += 1
  361.         self.Expr.append("or")
  362.         self.parseNode(node)
  363.         self.Depth -= 1
  364.         self.Expr.pop()
  365.         self.Rule += ")"
  366.  
  367.     def parseNot(self, node):
  368.         self.parseNew(False)
  369.         self.Rule += "not ("
  370.         self.Depth += 1
  371.         self.Expr.append("or")
  372.         self.parseNode(node)
  373.         self.Depth -= 1
  374.         self.Expr.pop()
  375.         self.Rule += ")"
  376.  
  377.  
  378. class MenuEntry:
  379.     "Wrapper for 'Menu Style' Desktop Entries"
  380.     def __init__(self, filename, dir="", prefix=""):
  381.         # Create entry
  382.         self.DesktopEntry = DesktopEntry(os.path.join(dir,filename))
  383.         self.setAttributes(filename, dir, prefix)
  384.  
  385.         # Can be one of Deleted/Hidden/Empty/NotShowIn/NoExec or True
  386.         self.Show = True
  387.  
  388.         # Semi-Private
  389.         self.Original = None
  390.         self.Parents = []
  391.  
  392.         # Private Stuff
  393.         self.Allocated = False
  394.         self.Add = False
  395.         self.MatchedInclude = False
  396.  
  397.         # Caching
  398.         self.Categories = self.DesktopEntry.getCategories()
  399.  
  400.     def save(self):
  401.         if self.DesktopEntry.tainted == True:
  402.             self.DesktopEntry.write()
  403.  
  404.     def getDir(self):
  405.         return self.DesktopEntry.filename.replace(self.Filename, '')
  406.  
  407.     def getType(self):
  408.         # Can be one of System/User/Both
  409.         if xdg.Config.root_mode == False:
  410.             if self.Original:
  411.                 return "Both"
  412.             elif xdg_data_dirs[0] in self.DesktopEntry.filename:
  413.                 return "User"
  414.             else:
  415.                 return "System"
  416.         else:
  417.             return "User"
  418.  
  419.     def setAttributes(self, filename, dir="", prefix=""):
  420.         self.Filename = filename
  421.         self.Prefix = prefix
  422.         self.DesktopFileID = os.path.join(prefix,filename).replace("/", "-")
  423.  
  424.         if not os.path.isabs(self.DesktopEntry.filename):
  425.             self.__setFilename()
  426.  
  427.     def updateAttributes(self):
  428.         if self.getType() == "System":
  429.             self.Original = MenuEntry(self.Filename, self.getDir(), self.Prefix)
  430.             self.__setFilename()
  431.  
  432.     def __setFilename(self):
  433.         if xdg.Config.root_mode == False:
  434.             path = xdg_data_dirs[0]
  435.         else:
  436.             path= xdg_data_dirs[1]
  437.  
  438.         if self.DesktopEntry.getType() == "Application":
  439.             dir = os.path.join(path, "applications")
  440.         else:
  441.             dir = os.path.join(path, "desktop-directories")
  442.  
  443.         self.DesktopEntry.filename = os.path.join(dir, self.Filename)
  444.  
  445.     def __cmp__(self, other):
  446.         return locale.strcoll(self.DesktopEntry.getName(), other.DesktopEntry.getName())
  447.  
  448.     def __eq__(self,other):
  449.         if self.DesktopFileID == str(other):
  450.             return True
  451.         else:
  452.             return False
  453.  
  454.     def __repr__(self):
  455.         return self.DesktopFileID
  456.  
  457.  
  458. class Separator:
  459.     "Just a dummy class for Separators"
  460.     def __init__(self, parent):
  461.         self.Parent = parent
  462.         self.Show = True
  463.  
  464.  
  465. class Header:
  466.     "Class for Inline Headers"
  467.     def __init__(self, name, generic_name, comment):
  468.         self.Name = name
  469.         self.GenericName = generic_name
  470.         self.Comment = comment
  471.  
  472.     def __str__(self):
  473.         return self.Name
  474.  
  475.  
  476. tmp = {}
  477.  
  478. def __getFileName(filename):
  479.     dirs = xdg_config_dirs[:]
  480.     if xdg.Config.root_mode == True:
  481.         dirs.pop(0)
  482.  
  483.     for dir in dirs:
  484.         menuname = os.path.join (dir, "menus" , filename)
  485.         if os.path.isdir(dir) and os.path.isfile(menuname):
  486.             return menuname
  487.  
  488. def parse(filename=None):
  489.     # conver to absolute path
  490.     if filename and not os.path.isabs(filename):
  491.         filename = __getFileName(filename)
  492.  
  493.     # use default if no filename given
  494.     if not filename:
  495.         filename = __getFileName("applications.menu")
  496.  
  497.     if not filename:
  498.         raise ParsingError('File not found', "/etc/xdg/menus/applications.menu")
  499.  
  500.     # check if it is a .menu file
  501.     if not os.path.splitext(filename)[1] == ".menu":
  502.         raise ParsingError('Not a .menu file', filename)
  503.  
  504.     # create xml parser
  505.     try:
  506.         doc = xml.dom.minidom.parse(filename)
  507.     except xml.parsers.expat.ExpatError:
  508.         raise ParsingError('Not a valid .menu file', filename)
  509.  
  510.     # parse menufile
  511.     tmp["Root"] = ""
  512.     tmp["mergeFiles"] = []
  513.     tmp["DirectoryDirs"] = []
  514.     tmp["cache"] = MenuEntryCache()
  515.  
  516.     __parse(doc, filename, tmp["Root"])
  517.     __parsemove(tmp["Root"])
  518.     __postparse(tmp["Root"])
  519.  
  520.     tmp["Root"].Doc = doc
  521.     tmp["Root"].Filename = filename
  522.  
  523.     # generate the menu
  524.     __genmenuNotOnlyAllocated(tmp["Root"])
  525.     __genmenuOnlyAllocated(tmp["Root"])
  526.  
  527.     # and finally sort
  528.     sort(tmp["Root"])
  529.  
  530.     return tmp["Root"]
  531.  
  532.  
  533. def __parse(node, filename, parent=None):
  534.     for child in node.childNodes:
  535.         if child.nodeType == ELEMENT_NODE:
  536.             if child.tagName == 'Menu':
  537.                 __parseMenu(child, filename, parent)
  538.             elif child.tagName == 'AppDir':
  539.                 try:
  540.                     __parseAppDir(child.childNodes[0].nodeValue, filename, parent)
  541.                 except IndexError:
  542.                     raise ValidationError('AppDir cannot be empty', filename)
  543.             elif child.tagName == 'DefaultAppDirs':
  544.                 __parseDefaultAppDir(filename, parent)
  545.             elif child.tagName == 'DirectoryDir':
  546.                 try:
  547.                     __parseDirectoryDir(child.childNodes[0].nodeValue, filename, parent)
  548.                 except IndexError:
  549.                     raise ValidationError('DirectoryDir cannot be empty', filename)
  550.             elif child.tagName == 'DefaultDirectoryDirs':
  551.                 __parseDefaultDirectoryDir(filename, parent)
  552.             elif child.tagName == 'Name' :
  553.                 try:
  554.                     parent.Name = child.childNodes[0].nodeValue
  555.                 except IndexError:
  556.                     raise ValidationError('Name cannot be empty', filename)
  557.             elif child.tagName == 'Directory' :
  558.                 try:
  559.                     parent.Directories.append(child.childNodes[0].nodeValue)
  560.                 except IndexError:
  561.                     raise ValidationError('Directory cannot be empty', filename)
  562.             elif child.tagName == 'OnlyUnallocated':
  563.                 parent.OnlyUnallocated = True
  564.             elif child.tagName == 'NotOnlyUnallocated':
  565.                 parent.OnlyUnallocated = False
  566.             elif child.tagName == 'Deleted':
  567.                 parent.Deleted = True
  568.             elif child.tagName == 'NotDeleted':
  569.                 parent.Deleted = False
  570.             elif child.tagName == 'Include' or child.tagName == 'Exclude':
  571.                 parent.Rules.append(Rule(child.tagName, child))
  572.             elif child.tagName == 'MergeFile':
  573.                 try:
  574.                     if child.getAttribute("type") == "parent":
  575.                         __parseMergeFile("applications.menu", child, filename, parent)
  576.                     else:
  577.                         __parseMergeFile(child.childNodes[0].nodeValue, child, filename, parent)
  578.                 except IndexError:
  579.                     raise ValidationError('MergeFile cannot be empty', filename)
  580.             elif child.tagName == 'MergeDir':
  581.                 try:
  582.                     __parseMergeDir(child.childNodes[0].nodeValue, child, filename, parent)
  583.                 except IndexError:
  584.                     raise ValidationError('MergeDir cannot be empty', filename)
  585.             elif child.tagName == 'DefaultMergeDirs':
  586.                 __parseDefaultMergeDirs(child, filename, parent)
  587.             elif child.tagName == 'Move':
  588.                 parent.Moves.append(Move(child))
  589.             elif child.tagName == 'Layout':
  590.                 if len(child.childNodes) > 1:
  591.                     parent.Layout = Layout(child)
  592.             elif child.tagName == 'DefaultLayout':
  593.                 if len(child.childNodes) > 1:
  594.                     parent.DefaultLayout = Layout(child)
  595.             elif child.tagName == 'LegacyDir':
  596.                 try:
  597.                     __parseLegacyDir(child.childNodes[0].nodeValue, child.getAttribute("prefix"), filename, parent)
  598.                 except IndexError:
  599.                     raise ValidationError('LegacyDir cannot be empty', filename)
  600.             elif child.tagName == 'KDELegacyDirs':
  601.                 __parseKDELegacyDirs(filename, parent)
  602.  
  603. def __parsemove(menu):
  604.     for submenu in menu.Submenus:
  605.         __parsemove(submenu)
  606.  
  607.     # parse move operations
  608.     for move in menu.Moves:
  609.         move_from_menu = menu.getMenu(move.Old)
  610.         if move_from_menu:
  611.             move_to_menu = menu.getMenu(move.New)
  612.  
  613.             menus = move.New.split("/")
  614.             oldparent = None
  615.             while len(menus) > 0:
  616.                 if not oldparent:
  617.                     oldparent = menu
  618.                 newmenu = oldparent.getMenu(menus[0])
  619.                 if not newmenu:
  620.                     newmenu = Menu()
  621.                     newmenu.Name = menus[0]
  622.                     if len(menus) > 1:
  623.                         newmenu.NotInXml = True
  624.                     oldparent.addSubmenu(newmenu)
  625.                 oldparent = newmenu
  626.                 menus.pop(0)
  627.  
  628.             newmenu += move_from_menu
  629.             move_from_menu.Parent.Submenus.remove(move_from_menu)
  630.  
  631. def __postparse(menu):
  632.     # unallocated / deleted
  633.     if menu.Deleted == "notset":
  634.         menu.Deleted = False
  635.     if menu.OnlyUnallocated == "notset":
  636.         menu.OnlyUnallocated = False
  637.  
  638.     # Layout Tags
  639.     if not menu.Layout or not menu.DefaultLayout:
  640.         if menu.DefaultLayout:
  641.             menu.Layout = menu.DefaultLayout
  642.         elif menu.Layout:
  643.             if menu.Depth > 0:
  644.                 menu.DefaultLayout = menu.Parent.DefaultLayout
  645.             else:
  646.                 menu.DefaultLayout = Layout()
  647.         else:
  648.             if menu.Depth > 0:
  649.                 menu.Layout = menu.Parent.DefaultLayout
  650.                 menu.DefaultLayout = menu.Parent.DefaultLayout
  651.             else:
  652.                 menu.Layout = Layout()
  653.                 menu.DefaultLayout = Layout()
  654.  
  655.     # add parent's app/directory dirs
  656.     if menu.Depth > 0:
  657.         menu.AppDirs = menu.Parent.AppDirs + menu.AppDirs
  658.         menu.DirectoryDirs = menu.Parent.DirectoryDirs + menu.DirectoryDirs
  659.  
  660.     # remove duplicates
  661.     menu.Directories = __removeDuplicates(menu.Directories)
  662.     menu.DirectoryDirs = __removeDuplicates(menu.DirectoryDirs)
  663.     menu.AppDirs = __removeDuplicates(menu.AppDirs)
  664.  
  665.     # go recursive through all menus
  666.     for submenu in menu.Submenus:
  667.         __postparse(submenu)
  668.  
  669.     # reverse so handling is easier
  670.     menu.Directories.reverse()
  671.     menu.DirectoryDirs.reverse()
  672.     menu.AppDirs.reverse()
  673.  
  674.     # get the valid .directory file out of the list
  675.     for directory in menu.Directories:
  676.         for dir in menu.DirectoryDirs:
  677.             if os.path.isfile(os.path.join(dir, directory)):
  678.                 menuentry = MenuEntry(directory, dir)
  679.                 if not menu.Directory:
  680.                     menu.Directory = menuentry
  681.                 elif menuentry.getType() == "System":
  682.                     if menu.Directory.getType() == "User":
  683.                         menu.Directory.Original = menuentry
  684.         if menu.Directory:
  685.             break
  686.  
  687.  
  688. # Menu parsing stuff
  689. def __parseMenu(child, filename, parent):
  690.     m = Menu()
  691.     __parse(child, filename, m)
  692.     if parent:
  693.         parent.addSubmenu(m)
  694.     else:
  695.         tmp["Root"] = m
  696.  
  697. # helper function
  698. def __check(value, filename, type):
  699.     path = os.path.dirname(filename)
  700.  
  701.     if not os.path.isabs(value):
  702.         value = os.path.join(path, value)
  703.  
  704.     value = os.path.abspath(value)
  705.  
  706.     if type == "dir" and os.path.exists(value) and os.path.isdir(value):
  707.         return value
  708.     elif type == "file" and os.path.exists(value) and os.path.isfile(value):
  709.         return value
  710.     else:
  711.         return False
  712.  
  713. # App/Directory Dir Stuff
  714. def __parseAppDir(value, filename, parent):
  715.     value = __check(value, filename, "dir")
  716.     if value:
  717.         parent.AppDirs.append(value)
  718.  
  719. def __parseDefaultAppDir(filename, parent):
  720.     for dir in reversed(xdg_data_dirs):
  721.         __parseAppDir(os.path.join(dir, "applications"), filename, parent)
  722.  
  723. def __parseDirectoryDir(value, filename, parent):
  724.     value = __check(value, filename, "dir")
  725.     if value:
  726.         parent.DirectoryDirs.append(value)
  727.  
  728. def __parseDefaultDirectoryDir(filename, parent):
  729.     for dir in reversed(xdg_data_dirs):
  730.         __parseDirectoryDir(os.path.join(dir, "desktop-directories"), filename, parent)
  731.  
  732. # Merge Stuff
  733. def __parseMergeFile(value, child, filename, parent):
  734.     if child.getAttribute("type") == "parent":
  735.         for dir in xdg_config_dirs:
  736.             rel_file = filename.replace(dir, "").strip("/")
  737.             if rel_file != filename:
  738.                 for p in xdg_config_dirs:
  739.                     if dir == p:
  740.                         continue
  741.                     if os.path.isfile(os.path.join(p,rel_file)):
  742.                         __mergeFile(os.path.join(p,rel_file),child,parent)
  743.                         break
  744.     else:
  745.         value = __check(value, filename, "file")
  746.         if value:
  747.             __mergeFile(value, child, parent)
  748.  
  749. def __parseMergeDir(value, child, filename, parent):
  750.     value = __check(value, filename, "dir")
  751.     if value:
  752.         for item in os.listdir(value):
  753.             try:
  754.                 if os.path.splitext(item)[1] == ".menu":
  755.                     __mergeFile(os.path.join(value, item), child, parent)
  756.             except UnicodeDecodeError:
  757.                 continue
  758.  
  759. def __parseDefaultMergeDirs(child, filename, parent):
  760.     basename = os.path.splitext(os.path.basename(filename))[0]
  761.     for dir in reversed(xdg_config_dirs):
  762.         __parseMergeDir(os.path.join(dir, "menus", basename + "-merged"), child, filename, parent)
  763.  
  764. def __mergeFile(filename, child, parent):
  765.     # check for infinite loops
  766.     if filename in tmp["mergeFiles"]:
  767.         if debug:
  768.             raise ParsingError('Infinite MergeFile loop detected', filename)
  769.         else:
  770.             return
  771.  
  772.     tmp["mergeFiles"].append(filename)
  773.  
  774.     # load file
  775.     try:
  776.         doc = xml.dom.minidom.parse(filename)
  777.     except IOError:
  778.         if debug:
  779.             raise ParsingError('File not found', filename)
  780.         else:
  781.             return
  782.     except xml.parsers.expat.ExpatError:
  783.         if debug:
  784.             raise ParsingError('Not a valid .menu file', filename)
  785.         else:
  786.             return
  787.  
  788.     # append file
  789.     for child in doc.childNodes:
  790.         if child.nodeType == ELEMENT_NODE:
  791.             __parse(child,filename,parent)
  792.             break
  793.  
  794. # Legacy Dir Stuff
  795. def __parseLegacyDir(dir, prefix, filename, parent):
  796.     m = __mergeLegacyDir(dir,prefix,filename,parent)
  797.     if m:
  798.         parent += m
  799.  
  800. def __mergeLegacyDir(dir, prefix, filename, parent):
  801.     dir = __check(dir,filename,"dir")
  802.     if dir and dir not in tmp["DirectoryDirs"]:
  803.         tmp["DirectoryDirs"].append(dir)
  804.  
  805.         m = Menu()
  806.         m.AppDirs.append(dir)
  807.         m.DirectoryDirs.append(dir)
  808.         m.Name = os.path.basename(dir)
  809.         m.NotInXml = True
  810.  
  811.         for item in os.listdir(dir):
  812.             try:
  813.                 if item == ".directory":
  814.                     m.Directories.append(item)
  815.                 elif os.path.isdir(os.path.join(dir,item)):
  816.                     m.addSubmenu(__mergeLegacyDir(os.path.join(dir,item), prefix, filename, parent))
  817.             except UnicodeDecodeError:
  818.                 continue
  819.  
  820.         tmp["cache"].addMenuEntries([dir],prefix, True)
  821.         menuentries = tmp["cache"].getMenuEntries([dir], False)
  822.  
  823.         for menuentry in menuentries:
  824.             categories = menuentry.Categories
  825.             if len(categories) == 0:
  826.                 r = Rule("Include")
  827.                 r.parseFilename(menuentry.DesktopFileID)
  828.                 r.compile()
  829.                 m.Rules.append(r)
  830.             if not dir in parent.AppDirs:
  831.                 categories.append("Legacy")
  832.                 menuentry.Categories = categories
  833.  
  834.         return m
  835.  
  836. def __parseKDELegacyDirs(filename, parent):
  837.     f=os.popen3("kde-config --path apps")
  838.     output = f[1].readlines()
  839.     try:
  840.         for dir in output[0].split(":"):
  841.             __parseLegacyDir(dir,"kde", filename, parent)
  842.     except IndexError:
  843.         pass
  844.  
  845. # remove duplicate entries from a list
  846. def __removeDuplicates(list):
  847.     set = {}
  848.     list.reverse()
  849.     list = [set.setdefault(e,e) for e in list if e not in set]
  850.     list.reverse()
  851.     return list
  852.  
  853. # Finally generate the menu
  854. def __genmenuNotOnlyAllocated(menu):
  855.     for submenu in menu.Submenus:
  856.         __genmenuNotOnlyAllocated(submenu)
  857.  
  858.     if menu.OnlyUnallocated == False:
  859.         tmp["cache"].addMenuEntries(menu.AppDirs)
  860.         menuentries = []
  861.         for rule in menu.Rules:
  862.             menuentries = rule.do(tmp["cache"].getMenuEntries(menu.AppDirs), rule.Type, 1)
  863.         for menuentry in menuentries:
  864.             if menuentry.Add == True:
  865.                 menuentry.Parents.append(menu)
  866.                 menuentry.Add = False
  867.                 menuentry.Allocated = True
  868.                 menu.MenuEntries.append(menuentry)
  869.  
  870. def __genmenuOnlyAllocated(menu):
  871.     for submenu in menu.Submenus:
  872.         __genmenuOnlyAllocated(submenu)
  873.  
  874.     if menu.OnlyUnallocated == True:
  875.         tmp["cache"].addMenuEntries(menu.AppDirs)
  876.         menuentries = []
  877.         for rule in menu.Rules:
  878.             menuentries = rule.do(tmp["cache"].getMenuEntries(menu.AppDirs), rule.Type, 2)
  879.         for menuentry in menuentries:
  880.             if menuentry.Add == True:
  881.                 menuentry.Parents.append(menu)
  882.             #    menuentry.Add = False
  883.             #    menuentry.Allocated = True
  884.                 menu.MenuEntries.append(menuentry)
  885.  
  886. # And sorting ...
  887. def sort(menu):
  888.     menu.Entries = []
  889.     menu.Visible = 0
  890.  
  891.     for submenu in menu.Submenus:
  892.         sort(submenu)
  893.  
  894.     tmp_s = []
  895.     tmp_e = []
  896.  
  897.     for order in menu.Layout.order:
  898.         if order[0] == "Filename":
  899.             tmp_e.append(order[1])
  900.         elif order[0] == "Menuname":
  901.             tmp_s.append(order[1])
  902.     
  903.     for order in menu.Layout.order:
  904.         if order[0] == "Separator":
  905.             separator = Separator(menu)
  906.             if len(menu.Entries) > 0 and isinstance(menu.Entries[-1], Separator):
  907.                 separator.Show = False
  908.             menu.Entries.append(separator)
  909.         elif order[0] == "Filename":
  910.             menuentry = menu.getMenuEntry(order[1])
  911.             if menuentry:
  912.                 menu.Entries.append(menuentry)
  913.         elif order[0] == "Menuname":
  914.             submenu = menu.getMenu(order[1])
  915.             if submenu:
  916.                 __parse_inline(submenu, menu)
  917.         elif order[0] == "Merge":
  918.             if order[1] == "files" or order[1] == "all":
  919.                 menu.MenuEntries.sort()
  920.                 for menuentry in menu.MenuEntries:
  921.                     if menuentry not in tmp_e:
  922.                         menu.Entries.append(menuentry)
  923.             elif order[1] == "menus" or order[1] == "all":
  924.                 menu.Submenus.sort()
  925.                 for submenu in menu.Submenus:
  926.                     if submenu.Name not in tmp_s:
  927.                         __parse_inline(submenu, menu)
  928.  
  929.     # getHidden / NoDisplay / OnlyShowIn / NotOnlyShowIn / Deleted / NoExec
  930.     for entry in menu.Entries:
  931.         entry.Show = True
  932.         menu.Visible += 1
  933.         if isinstance(entry, Menu):
  934.             if entry.Deleted == True:
  935.                 entry.Show = "Deleted"
  936.                 menu.Visible -= 1
  937.             elif isinstance(entry.Directory, MenuEntry):
  938.                 if entry.Directory.DesktopEntry.getNoDisplay() == True:
  939.                     entry.Show = "NoDisplay"
  940.                     menu.Visible -= 1
  941.                 elif entry.Directory.DesktopEntry.getHidden() == True:
  942.                     entry.Show = "Hidden"
  943.                     menu.Visible -= 1
  944.         elif isinstance(entry, MenuEntry):
  945.             if entry.DesktopEntry.getNoDisplay() == True:
  946.                 entry.Show = "NoDisplay"
  947.                 menu.Visible -= 1
  948.             elif entry.DesktopEntry.getHidden() == True:
  949.                 entry.Show = "Hidden"
  950.                 menu.Visible -= 1
  951.             elif entry.DesktopEntry.getTryExec() and not __try_exec(entry.DesktopEntry.getTryExec()):
  952.                 entry.Show = "NoExec"
  953.                 menu.Visible -= 1
  954.             elif xdg.Config.windowmanager:
  955.                 if ( entry.DesktopEntry.getOnlyShowIn() != [] and xdg.Config.windowmanager not in entry.DesktopEntry.getOnlyShowIn() ) \
  956.                 or xdg.Config.windowmanager in entry.DesktopEntry.getNotShowIn():
  957.                     entry.Show = "NotShowIn"
  958.                     menu.Visible -= 1
  959.         elif isinstance(entry,Separator):
  960.             menu.Visible -= 1
  961.  
  962.     # remove separators at the beginning and at the end
  963.     if len(menu.Entries) > 0:
  964.         if isinstance(menu.Entries[0], Separator):
  965.             menu.Entries[0].Show = False
  966.     if len(menu.Entries) > 1:
  967.         if isinstance(menu.Entries[-1], Separator):
  968.             menu.Entries[-1].Show = False
  969.  
  970.     # show_empty tag
  971.     for entry in menu.Entries:
  972.         if isinstance(entry,Menu) and entry.Layout.show_empty == "false" and entry.Visible == 0:
  973.             entry.Show = "Empty"
  974.             menu.Visible -= 1
  975.             if entry.NotInXml == True:
  976.                 menu.Entries.remove(entry)
  977.  
  978. def __try_exec(executable):
  979.     paths = os.environ['PATH'].split(os.pathsep)
  980.     if not os.path.isfile(executable):
  981.         for p in paths:
  982.             f = os.path.join(p, executable)
  983.             if os.path.isfile(f):
  984.                 if os.access(f, os.X_OK):
  985.                     return True
  986.     else:
  987.         if os.access(executable, os.X_OK):
  988.             return True
  989.     return False
  990.  
  991. # inline tags
  992. def __parse_inline(submenu, menu):
  993.     if submenu.Layout.inline == "true":
  994.         if len(submenu.Entries) == 1 and submenu.Layout.inline_alias == "true":
  995.             menuentry = submenu.Entries[0]
  996.             menuentry.DesktopEntry.set("Name", submenu.getName(), locale = True)
  997.             menuentry.DesktopEntry.set("GenericName", submenu.getGenericName(), locale = True)
  998.             menuentry.DesktopEntry.set("Comment", submenu.getComment(), locale = True)
  999.             menu.Entries.append(menuentry)
  1000.         elif len(submenu.Entries) <= submenu.Layout.inline_limit or submenu.Layout.inline_limit == 0:
  1001.             if submenu.Layout.inline_header == "true":
  1002.                 header = Header(submenu.getName(), submenu.getGenericName(), submenu.getComment())
  1003.                 menu.Entries.append(header)
  1004.             for entry in submenu.Entries:
  1005.                 menu.Entries.append(entry)
  1006.         else:
  1007.             menu.Entries.append(submenu)
  1008.     else:
  1009.         menu.Entries.append(submenu)
  1010.  
  1011. class MenuEntryCache:
  1012.     "Class to cache Desktop Entries"
  1013.     def __init__(self):
  1014.         self.cacheEntries = {}
  1015.         self.cacheEntries['legacy'] = []
  1016.         self.cache = {}
  1017.  
  1018.     def addMenuEntries(self, dirs, prefix="", legacy=False):
  1019.         for dir in dirs:
  1020.             if not self.cacheEntries.has_key(dir):
  1021.                 self.cacheEntries[dir] = []
  1022.                 self.__addFiles(dir, "", prefix, legacy)
  1023.  
  1024.     def __addFiles(self, dir, subdir, prefix, legacy):
  1025.         for item in os.listdir(os.path.join(dir,subdir)):
  1026.             if os.path.splitext(item)[1] == ".desktop":
  1027.                 try:
  1028.                     menuentry = MenuEntry(os.path.join(subdir,item), dir, prefix)
  1029.                 except ParsingError:
  1030.                     continue
  1031.  
  1032.                 self.cacheEntries[dir].append(menuentry)
  1033.                 if legacy == True:
  1034.                     self.cacheEntries['legacy'].append(menuentry)
  1035.             elif os.path.isdir(os.path.join(dir,subdir,item)) and legacy == False:
  1036.                 self.__addFiles(dir, os.path.join(subdir,item), prefix, legacy)
  1037.  
  1038.     def getMenuEntries(self, dirs, legacy=True):
  1039.         list = []
  1040.         ids = []
  1041.         # handle legacy items
  1042.         appdirs = dirs[:]
  1043.         if legacy == True:
  1044.             appdirs.append("legacy")
  1045.         # cache the results again
  1046.         key = "".join(appdirs)
  1047.         try:
  1048.             return self.cache[key]
  1049.         except KeyError:
  1050.             pass
  1051.         for dir in appdirs:
  1052.             for menuentry in self.cacheEntries[dir]:
  1053.                 try:
  1054.                     if menuentry.DesktopFileID not in ids:
  1055.                         ids.append(menuentry.DesktopFileID)
  1056.                         list.append(menuentry)
  1057.                     elif menuentry.getType() == "System":
  1058.                     # FIXME: This is only 99% correct, but still...
  1059.                         i = list.index(menuentry)
  1060.                         e = list[i]
  1061.                         if e.getType() == "User":
  1062.                             e.Original = menuentry
  1063.                 except UnicodeDecodeError:
  1064.                     continue
  1065.         self.cache[key] = list
  1066.         return list
  1067.