diff options
author | Heinrich Wendel <h_wendel@cojobo.net> | 2008-03-02 13:58:09 +0000 |
---|---|---|
committer | Heinrich Wendel <h_wendel@cojobo.net> | 2008-03-02 13:58:09 +0000 |
commit | 0b063fa389fe9753ceec0c22b3ece88539ffccb8 (patch) | |
tree | e9b09cbfdd52f840e06ebe43276348a591e0bb07 | |
parent | 322eaceb197ee545e577aadbecc24c390526b776 (diff) |
replace tabs with spaces
-rw-r--r-- | ChangeLog | 166 | ||||
-rw-r--r-- | setup.py | 14 | ||||
-rwxr-xr-x | test/test-desktop.py | 38 | ||||
-rwxr-xr-x | test/test-menu.py | 26 | ||||
-rw-r--r-- | xdg/BaseDirectory.py | 102 | ||||
-rw-r--r-- | xdg/Config.py | 34 | ||||
-rw-r--r-- | xdg/DesktopEntry.py | 714 | ||||
-rw-r--r-- | xdg/Exceptions.py | 60 | ||||
-rw-r--r-- | xdg/IconTheme.py | 682 | ||||
-rw-r--r-- | xdg/IniFile.py | 742 | ||||
-rw-r--r-- | xdg/Locale.py | 114 | ||||
-rw-r--r-- | xdg/Menu.py | 1894 | ||||
-rw-r--r-- | xdg/MenuEditor.py | 902 | ||||
-rw-r--r-- | xdg/Mime.py | 776 | ||||
-rw-r--r-- | xdg/RecentFiles.py | 292 |
15 files changed, 3278 insertions, 3278 deletions
@@ -2,183 +2,183 @@ Version 0.16 2008-03-02 Heinrich Wendel <h_wendel@cojobo.net> * IniFile.py: Fix saving of relative filenames - * IniFile.py: Fix __cmp__ method + * IniFile.py: Fix __cmp__ method Version 0.15 2005-08-10 Heinrich Wendel <h_wendel@cojoob.net> - * Menu.py: Add support for TryExec + * Menu.py: Add support for TryExec 2005-08-09 Heinrich Wendel <h_wendel@cojobo.net> - * Menu.py: Unicode bug fixed! - * IconTheme.py: small speedup + * Menu.py: Unicode bug fixed! + * IconTheme.py: small speedup 2005-08-04 Heinrich Wendel <h_wendel@cojobo.net> - * Menu.py, IconTheme.py: Bugfixes... - * MenuEditor.py: Make xml nice; add hide/unhide functions + * Menu.py, IconTheme.py: Bugfixes... + * MenuEditor.py: Make xml nice; add hide/unhide functions Versinon 0.14 2005-06-02 Heinrich Wendel <h_wendel@cojobo.net> - * Menu.py, MenuEditor.py: Bugfixes... + * Menu.py, MenuEditor.py: Bugfixes... version 0.13 2005-06-01 Heinrich Wendel <h_wendel@cojobo.net> - * Menu.py, MenuEditor.py: Bugfixes... - * Config.py: Add root_mode + * Menu.py, MenuEditor.py: Bugfixes... + * Config.py: Add root_mode Version 0.12 2005-05-30 Heinrich Wendel <h_wendel@cojobo.net> - * MenuEditor.py: New in this release, use to edit Menus thx to Travis - Watkins <alleykat@gmail.com> and Matt Kynaston <mattkyn@gmail.com> for - their help - * Menu.py, IniFile.py, DesktopEntry.py: Lot of bugfixing... - * BaseDirectory.py: Add xdg_cache_home - * IconTheme.py, Config.py: More caching stuff, make cachetime - configurable + * MenuEditor.py: New in this release, use to edit Menus thx to Travis + Watkins <alleykat@gmail.com> and Matt Kynaston <mattkyn@gmail.com> for + their help + * Menu.py, IniFile.py, DesktopEntry.py: Lot of bugfixing... + * BaseDirectory.py: Add xdg_cache_home + * IconTheme.py, Config.py: More caching stuff, make cachetime + configurable Version 0.11 2005-05-23 Heinrich Wendel <h_wendel@cojobo.net> - * DesktopEntry.p, Menu.py: A lot of bugfixes, thx to Travis Watkins - <alleykat@gmail.com> + * DesktopEntry.p, Menu.py: A lot of bugfixes, thx to Travis Watkins + <alleykat@gmail.com> 2005-05-02 Heinrich Wendel <h_wendel@cojobo.net> - * Config.py: - Module to configure Basic Settings, currently available: - - Locale, IconTheme, IconSize, WindowManager - * Locale.py: - Internal Module to support Locales - * Mime.py: - Implementation of the Mime Specification - * Menu.py: - Now supports LegacyDirs - * RecentFiles.py: - Implementation of the Recent Files Specification + * Config.py: + Module to configure Basic Settings, currently available: + - Locale, IconTheme, IconSize, WindowManager + * Locale.py: + Internal Module to support Locales + * Mime.py: + Implementation of the Mime Specification + * Menu.py: + Now supports LegacyDirs + * RecentFiles.py: + Implementation of the Recent Files Specification Version 0.10 2005-04-26 Heinrich Wendel <h_wendel@cojobo.net> - * Menu.py: - various bug fixing to support version 1.0.draft-1 + * Menu.py: + various bug fixing to support version 1.0.draft-1 2005-04-13 Heinrich Wendel <h_wendel@cojobo.net> - * IniFily.py: - Detect if a .desktop file was edited - * Menu.py - Fix bug caused by excluding NoDisplay/Hidden Items to early + * IniFily.py: + Detect if a .desktop file was edited + * Menu.py + Fix bug caused by excluding NoDisplay/Hidden Items to early Version 0.9 2005-03-23 Heinrich Wendel <h_wendel@cojobo.net> - * IniFile.py: - various speedups - * Menu.py: - add support for <MergeFile type="parent">, menu-spec-0.91 + * IniFile.py: + various speedups + * Menu.py: + add support for <MergeFile type="parent">, menu-spec-0.91 2005-03-21 Heinrich Wendel <h_wendel@cojobo.net> - * IniFily.py: - Small fixes - * Menu.py: - remove __preparse and don't edit the parsed document, so menu editing is - possible - store parsed document in Menu.Doc - store document name in Menu.Filename + * IniFily.py: + Small fixes + * Menu.py: + remove __preparse and don't edit the parsed document, so menu editing is + possible + store parsed document in Menu.Doc + store document name in Menu.Filename 2005-03-18 Heinrich Wendel <h_wendel@cojobo.net> - * Menu.py: - fix basename argument, thx to Matt Kynaston <mattkyn@gmail.com>; - make it comply to menu-spec-0.9 + * Menu.py: + fix basename argument, thx to Matt Kynaston <mattkyn@gmail.com>; + make it comply to menu-spec-0.9 2004-30-11 Heinrich Wendel <h_wendel@cojobo.net> - * Update BaseDirectory.py to the current ROX version + * Update BaseDirectory.py to the current ROX version Version 0.8 2004-10-18 Ross Burton <ross@burtonini.com> - * xdg/DesktopEntry.py, xdg/IconTheme.py: - Add . to the literal FileExtensions so that the checks work. - * xdg/Menu.py: - Don't read .desktop-* files, only .desktop + * xdg/DesktopEntry.py, xdg/IconTheme.py: + Add . to the literal FileExtensions so that the checks work. + * xdg/Menu.py: + Don't read .desktop-* files, only .desktop 2004-10-18 Martin Grimme <martin@pycage.de> - * xdg/IconTheme.py (getIconPath): The "hicolor" theme has to be used as - the fallback. - * xdg/IniFile.py (IniFile.getList): Fixed bug in splitting up strings. + * xdg/IconTheme.py (getIconPath): The "hicolor" theme has to be used as + the fallback. + * xdg/IniFile.py (IniFile.getList): Fixed bug in splitting up strings. Version 0.7 2004-09-04 Heinrich Wendel <h_wendel@cojobo.net> - * Add 'import codecs' to IniFile, needed by write support - * Fix parsing of lists with only one entry + * Add 'import codecs' to IniFile, needed by write support + * Fix parsing of lists with only one entry Version 0.6 2004-08-04 Heinrich Wendel <h_wendel@cojobo.net> - * Performance Improvements + * Performance Improvements Version 0.5 2004-03-29 Heinrich Wendel <h_wendel@cojobo.net> - * Finished Support for menu-spec 0.7 + * Finished Support for menu-spec 0.7 2004-03-27 Heinrich Wendel <h_wendel@cojobo.net> * 5* speed improvement in Menu.py parsing code 2004-03-20 Heinrich Wendel <h_wendel@cojobo.net> - * check values of Categories/OnlyShowIn keys - * various misc changes + * check values of Categories/OnlyShowIn keys + * various misc changes 2004-03-17 Martin Grimme <martin@pycage.de> - * xdg/Menu.py (__preparse): - * xdg/IconTheme.py (IconTheme.parse): Made compatible with Python 2.3 - (None is a keyword). - (__parseTheme): Prepend new icon themes to make sure that they have - priority when looking up icons. - (icondirs): Add "~/.icons" to the paths where to look for icons. Users - may have icon themes installed in their home directory. + * xdg/Menu.py (__preparse): + * xdg/IconTheme.py (IconTheme.parse): Made compatible with Python 2.3 + (None is a keyword). + (__parseTheme): Prepend new icon themes to make sure that they have + priority when looking up icons. + (icondirs): Add "~/.icons" to the paths where to look for icons. Users + may have icon themes installed in their home directory. 2003-10-08 Heinrich Wendel <h_wendel@cojobo.net> - * Completed write-support in IniFile + * Completed write-support in IniFile 2003-10-05 Heinrich Wendel <h_wendel@cojobo.net> - * Added support for Hidden and NoDisplay in menu-spec - * inital write-support in IniFile + * Added support for Hidden and NoDisplay in menu-spec + * inital write-support in IniFile 2003-10-04 Heinrich Wendel <h_wendel@cojobo.net> - * License change to LGPL-2 - * initial support for menu-spec 0.7 + * License change to LGPL-2 + * initial support for menu-spec 0.7 Version 0.4 2003-09-30 Heinrich Wendel <h_wendel@cojobo.net> - * Bugfix release + * Bugfix release Version 0.3 2003-09-12 Heinrich Wendel <h_wendel@cojobo.net> - * Complete IconSpec implementation, including cache and validation + * Complete IconSpec implementation, including cache and validation 2003-09-07 Heinrich Wendel <h_wendel@cojobo.net> - * Basedir spec converted to version 0.6 - * First part of separating DesktopEntry backend in IniFile - * added getPath(...) function to Menu.py + * Basedir spec converted to version 0.6 + * First part of separating DesktopEntry backend in IniFile + * added getPath(...) function to Menu.py Version 0.2 2003-09-05 Heinrich Wendel <h_wendel@cojobo.net> - * Rewrite of menu-spec code - * Taken basedir-spec code from ROX + * Rewrite of menu-spec code + * Taken basedir-spec code from ROX Version 0.1 2003-08-08 Heinrich Wendel <h_wendel@cojobo.net> - * initial public release + * initial public release @@ -3,10 +3,10 @@ from distutils.core import setup setup( name = "pyxdg", - version = "0.15", - description = "PyXDG contains implementations of freedesktop.org standards in python.", - maintainer = "Heinrich Wendel", - maintainer_email = "h_wendel@cojobo.net", - url = "http://cvs.freedesktop.org/cgi-bin/viewcvs.cgi/pyxdg/", - packages = ['xdg'], - license = "LGPL-2") + version = "0.16", + description = "PyXDG contains implementations of freedesktop.org standards in python.", + maintainer = "Heinrich Wendel", + maintainer_email = "h_wendel@cojobo.net", + url = "http://cvs.freedesktop.org/cgi-bin/viewcvs.cgi/pyxdg/", + packages = ['xdg'], + license = "LGPL-2") diff --git a/test/test-desktop.py b/test/test-desktop.py index 9409cf9..8304b62 100755 --- a/test/test-desktop.py +++ b/test/test-desktop.py @@ -4,28 +4,28 @@ from xdg.DesktopEntry import * import os, sys def checkfiles(path): - if os.path.isdir(path): - ls = os.listdir(path) - for file in ls: - checkfiles(os.path.join(path, file)) - else: - entry = DesktopEntry() - try: - entry.parse(path) - except ParsingError, e: - print e - return + if os.path.isdir(path): + ls = os.listdir(path) + for file in ls: + checkfiles(os.path.join(path, file)) + else: + entry = DesktopEntry() + try: + entry.parse(path) + except ParsingError, e: + print e + return - entry.setLocale("C") - entry.getName() + entry.setLocale("C") + entry.getName() - try: - entry.validate() - except ValidationError, e: - print e + try: + entry.validate() + except ValidationError, e: + print e try: - checkfiles(sys.argv[1]) + checkfiles(sys.argv[1]) except IndexError: - print("No file or directory given!") + print("No file or directory given!") diff --git a/test/test-menu.py b/test/test-menu.py index 892dc90..2b1e7f3 100755 --- a/test/test-menu.py +++ b/test/test-menu.py @@ -6,19 +6,19 @@ import xdg.Menu import xdg.DesktopEntry def show_menu(menu, depth = 0): -# print depth*"-" + "\x1b[01m" + menu.getName().encode("ascii", 'ignore') + "\x1b[0m" -# depth += 1 - for entry in menu.getEntries(): - if isinstance(entry, xdg.Menu.Menu): - show_menu(entry, depth) - elif isinstance(entry, xdg.Menu.MenuEntry): -# print depth*"-" + entry.DesktopEntry.getName().encode("ascii", 'ignore') - print menu.getPath() + "/\t" + entry.DesktopFileID + "\t" + entry.DesktopEntry.getFileName() -# elif isinstance(entry, xdg.Menu.Separator): -# print depth*"-" + "|||" -# elif isinstance(entry, xdg.Menu.Header): -# print depth*"-" + "\x1b[01m" + entry.Name + "\x1b[0m" -# depth -= 1 +# print depth*"-" + "\x1b[01m" + menu.getName().encode("ascii", 'ignore') + "\x1b[0m" +# depth += 1 + for entry in menu.getEntries(): + if isinstance(entry, xdg.Menu.Menu): + show_menu(entry, depth) + elif isinstance(entry, xdg.Menu.MenuEntry): +# print depth*"-" + entry.DesktopEntry.getName().encode("ascii", 'ignore') + print menu.getPath() + "/\t" + entry.DesktopFileID + "\t" + entry.DesktopEntry.getFileName() +# elif isinstance(entry, xdg.Menu.Separator): +# print depth*"-" + "|||" +# elif isinstance(entry, xdg.Menu.Header): +# print depth*"-" + "\x1b[01m" + entry.Name + "\x1b[0m" +# depth -= 1 menu = xdg.Menu.parse() show_menu(menu) diff --git a/xdg/BaseDirectory.py b/xdg/BaseDirectory.py index 9c1d53c..6f532c9 100644 --- a/xdg/BaseDirectory.py +++ b/xdg/BaseDirectory.py @@ -6,7 +6,7 @@ http://cvs.sourceforge.net/viewcvs.py/rox/ROX-Lib2/python/rox/basedir.py?rev=1.9 The freedesktop.org Base Directory specification provides a way for applications to locate shared data and configuration: - http://standards.freedesktop.org/basedir-spec/ + http://standards.freedesktop.org/basedir-spec/ (based on version 0.6) @@ -14,13 +14,13 @@ This module can be used to load and save from and to these directories. Typical usage: - from rox import basedir - - for dir in basedir.load_config_paths('mydomain.org', 'MyProg', 'Options'): - print "Load settings from", dir + from rox import basedir + + for dir in basedir.load_config_paths('mydomain.org', 'MyProg', 'Options'): + print "Load settings from", dir - dir = basedir.save_config_path('mydomain.org', 'MyProg') - print >>file(os.path.join(dir, 'Options'), 'w'), "foo=2" + dir = basedir.save_config_path('mydomain.org', 'MyProg') + print >>file(os.path.join(dir, 'Options'), 'w'), "foo=2" Note: see the rox.Options module for a higher-level API for managing options. """ @@ -30,68 +30,68 @@ import os _home = os.environ.get('HOME', '/') xdg_data_home = os.environ.get('XDG_DATA_HOME', - os.path.join(_home, '.local', 'share')) + os.path.join(_home, '.local', 'share')) xdg_data_dirs = [xdg_data_home] + \ - os.environ.get('XDG_DATA_DIRS', '/usr/local/share:/usr/share').split(':') + os.environ.get('XDG_DATA_DIRS', '/usr/local/share:/usr/share').split(':') xdg_config_home = os.environ.get('XDG_CONFIG_HOME', - os.path.join(_home, '.config')) + os.path.join(_home, '.config')) xdg_config_dirs = [xdg_config_home] + \ - os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg').split(':') + os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg').split(':') xdg_cache_home = os.environ.get('XDG_CACHE_HOME', - os.path.join(_home, '.cache')) + os.path.join(_home, '.cache')) xdg_data_dirs = filter(lambda x: x, xdg_data_dirs) xdg_config_dirs = filter(lambda x: x, xdg_config_dirs) def save_config_path(*resource): - """Ensure $XDG_CONFIG_HOME/<resource>/ exists, and return its path. - 'resource' should normally be the name of your application. Use this - when SAVING configuration settings. Use the xdg_config_dirs variable - for loading.""" - resource = os.path.join(*resource) - assert not resource.startswith('/') - path = os.path.join(xdg_config_home, resource) - if not os.path.isdir(path): - os.makedirs(path, 0700) - return path + """Ensure $XDG_CONFIG_HOME/<resource>/ exists, and return its path. + 'resource' should normally be the name of your application. Use this + when SAVING configuration settings. Use the xdg_config_dirs variable + for loading.""" + resource = os.path.join(*resource) + assert not resource.startswith('/') + path = os.path.join(xdg_config_home, resource) + if not os.path.isdir(path): + os.makedirs(path, 0700) + return path def save_data_path(*resource): - """Ensure $XDG_DATA_HOME/<resource>/ exists, and return its path. - 'resource' is the name of some shared resource. Use this when updating - a shared (between programs) database. Use the xdg_data_dirs variable - for loading.""" - resource = os.path.join(*resource) - assert not resource.startswith('/') - path = os.path.join(xdg_data_home, resource) - if not os.path.isdir(path): - os.makedirs(path) - return path + """Ensure $XDG_DATA_HOME/<resource>/ exists, and return its path. + 'resource' is the name of some shared resource. Use this when updating + a shared (between programs) database. Use the xdg_data_dirs variable + for loading.""" + resource = os.path.join(*resource) + assert not resource.startswith('/') + path = os.path.join(xdg_data_home, resource) + if not os.path.isdir(path): + os.makedirs(path) + return path def load_config_paths(*resource): - """Returns an iterator which gives each directory named 'resource' in the - configuration search path. Information provided by earlier directories should - take precedence over later ones (ie, the user's config dir comes first).""" - resource = os.path.join(*resource) - for config_dir in xdg_config_dirs: - path = os.path.join(config_dir, resource) - if os.path.exists(path): yield path + """Returns an iterator which gives each directory named 'resource' in the + configuration search path. Information provided by earlier directories should + take precedence over later ones (ie, the user's config dir comes first).""" + resource = os.path.join(*resource) + for config_dir in xdg_config_dirs: + path = os.path.join(config_dir, resource) + if os.path.exists(path): yield path def load_first_config(*resource): - """Returns the first result from load_config_paths, or None if there is nothing - to load.""" - for x in load_config_paths(*resource): - return x - return None + """Returns the first result from load_config_paths, or None if there is nothing + to load.""" + for x in load_config_paths(*resource): + return x + return None def load_data_paths(*resource): - """Returns an iterator which gives each directory named 'resource' in the - shared data search path. Information provided by earlier directories should - take precedence over later ones.""" - resource = os.path.join(*resource) - for data_dir in xdg_data_dirs: - path = os.path.join(data_dir, resource) - if os.path.exists(path): yield path + """Returns an iterator which gives each directory named 'resource' in the + shared data search path. Information provided by earlier directories should + take precedence over later ones.""" + resource = os.path.join(*resource) + for data_dir in xdg_data_dirs: + path = os.path.join(data_dir, resource) + if os.path.exists(path): yield path diff --git a/xdg/Config.py b/xdg/Config.py index c47389f..e2fbe64 100644 --- a/xdg/Config.py +++ b/xdg/Config.py @@ -10,30 +10,30 @@ cache_time = 5 root_mode = False def setWindowManager(wm): - global windowmanager - windowmanager = wm + global windowmanager + windowmanager = wm def setIconTheme(theme): - global icon_theme - icon_theme = theme - import xdg.IconTheme - xdg.IconTheme.themes = [] + global icon_theme + icon_theme = theme + import xdg.IconTheme + xdg.IconTheme.themes = [] def setIconSize(size): - global icon_size - icon_size = size + global icon_size + icon_size = size def setCacheTime(time): - global cache_time - cache_time = time + global cache_time + cache_time = time def setLocale(lang): - import locale - lang = locale.normalize(lang) - locale.setlocale(locale.LC_ALL, lang) - import xdg.Locale - xdg.Locale.update(lang) + import locale + lang = locale.normalize(lang) + locale.setlocale(locale.LC_ALL, lang) + import xdg.Locale + xdg.Locale.update(lang) def setRootMode(boolean): - global root_mode - root_mode = boolean + global root_mode + root_mode = boolean diff --git a/xdg/DesktopEntry.py b/xdg/DesktopEntry.py index 44f0426..979971c 100644 --- a/xdg/DesktopEntry.py +++ b/xdg/DesktopEntry.py @@ -15,366 +15,366 @@ from xdg.BaseDirectory import * import os.path class DesktopEntry(IniFile): - "Class to parse and validate DesktopEntries" - - defaultGroup = 'Desktop Entry' - - def __init__(self, filename=None): - self.content = dict() - if filename and os.path.exists(filename): - self.parse(filename) - elif filename: - self.new(filename) - - def __str__(self): - return self.getName() - - def parse(self, file): - IniFile.parse(self, file, ["Desktop Entry", "KDE Desktop Entry"]) - - # start standard keys - def getType(self): - return self.get('Type') - def getVersion(self): - return self.get('Version', type="numeric") - def getEncoding(self): - return self.get('Encoding') - def getName(self): - return self.get('Name', locale=True) - def getGenericName(self): - return self.get('GenericName', locale=True) - def getComment(self): - return self.get('Comment', locale=True) - def getNoDisplay(self): - return self.get('NoDisplay', type="boolean") - def getIcon(self): - return self.get('Icon', locale=True) - def getHidden(self): - return self.get('Hidden', type="boolean") - def getFilePattern(self): - return self.get('FilePattern', type="regex") - def getTryExec(self): - return self.get('TryExec') - def getExec(self): - return self.get('Exec') - def getPath(self): - return self.get('Path') - def getTerminal(self): - return self.get('Terminal', type="boolean") - def getSwallowTitle(self): - return self.get('SwallowTitle', locale=True) - def getSwallowExec(self): - return self.get('SwallowExec') - def getActions(self): - return self.get('Actions', list=True) - def getMimeType(self): - return self.get('MimeType', list=True, type="regex") - def getSortOrder(self): - return self.get('SortOrder', list=True) - def getDev(self): - return self.get('Dev') - def getFSType(self): - return self.get('FSType') - def getMountPoint(self): - return self.get('MountPoint') - def getReadonly(self): - return self.get('ReadOnly', type="boolean") - def getUnmountIcon(self): - return self.get('UnmountIcon', locale=True) - def getURL(self): - return self.get('URL') - def getCategories(self): - return self.get('Categories', list=True) - def getOnlyShowIn(self): - return self.get('OnlyShowIn', list=True) - def getNotShowIn(self): - return self.get('NotShowIn', list=True) - def getStartupNotify(self): - return self.get('StartupNotify', type="boolean") - def getStartupWMClass(self): - return self.get('StartupWMClass') - # end standard keys - - # start kde keys - def getServiceTypes(self): - return self.get('ServiceTypes', list=True) - def getDocPath(self): - return self.get('DocPath') - def getKeywords(self): - return self.get('Keywords', list=True, locale=True) - def getInitialPreference(self): - return self.get('InitialPreference') - # end kde keys - - # start deprecated keys - def getMiniIcon(self): - return self.get('MiniIcon', locale=True) - def getTerminalOptions(self): - return self.get('TerminalOptions') - def getDefaultApp(self): - return self.get('DefaultApp') - def getProtocols(self): - return self.get('Protocols', list=True) - def getExtensions(self): - return self.get('Extensions', list=True) - def getBinaryPattern(self): - return self.get('BinaryPattern') - def getMapNotify(self): - return self.get('MapNotify') - # end deprecated keys - - # desktop entry edit stuff - def new(self, filename): - if os.path.splitext(filename)[1] == ".desktop": - type = "Application" - elif os.path.splitext(filename)[1] == ".directory": - type = "Directory" - self.content = dict() - self.addGroup(self.defaultGroup) - self.set("Encoding", "UTF-8") - self.set("Type", type) - self.filename = filename - # end desktop entry edit stuff - - # validation stuff - def checkExtras(self): - # header - if self.defaultGroup == "KDE Desktop Entry": - self.warnings.append('[KDE Desktop Entry]-Header is deprecated') - - # file extension - if self.fileExtension == ".kdelnk": - self.warnings.append("File extension .kdelnk is deprecated") - elif self.fileExtension != ".desktop" and self.fileExtension != ".directory": - self.warnings.append('Unknown File extension') - - # Type - try: - self.type = self.content[self.defaultGroup]["Type"] - except KeyError: - self.errors.append("Key 'Type' is missing") - - # Encoding - try: - self.encoding = self.content[self.defaultGroup]["Encoding"] - except KeyError: - self.errors.append("Key 'Encoding' is missing") - - # Version - try: - self.version = self.content[self.defaultGroup]["Version"] - except KeyError: - self.warnings.append("Key 'Version' is missing") - - # Name - try: - self.name = self.content[self.defaultGroup]["Name"] - except KeyError: - self.errors.append("Key 'Name' is missing") - - def checkGroup(self, group): - # check if group header is valid - if not (group == self.defaultGroup \ - or re.match("^\Desktop Action [a-zA-Z]+\$", group) \ - or (re.match("^\X-", group) and group.decode("utf-8", "ignore").encode("ascii", 'ignore') == group)): - self.errors.append("Invalid Group name: %s" % group) - else: - #OnlyShowIn and NotShowIn - if self.content[group].has_key("OnlyShowIn") and self.content[group].has_key("NotShowIn"): - self.errors.append("Group may either have OnlyShowIn or NotShowIn, but not both") - - def checkKey(self, key, value, group): - # standard keys - if key == "Type": - if value == "ServiceType" or value == "Service": - self.warnings.append("Type=%s is a KDE extension" % key) - elif value == "MimeType": - self.warnings.append("Type=MimeType is deprecated") - elif not (value == "Application" or value == "Link" or value == "FSDevice" or value == "Directory"): - self.errors.append("Value of key 'Type' must be Application, Link, FSDevice or Directory, but is '%s'" % value) - - if self.fileExtension == ".directory" and not value == "Directory": - self.warnings.append("File extension is .directory, but Type is '%s'" % value) - elif self.fileExtension == ".desktop" and value == "Directory": - self.warnings.append("Files with Type=Directory should have the extension .directory") - - elif key == "Version": - self.checkValue(key, value, type="number") - - elif key == "Encoding": - if value == "Legacy-Mixed": - self.errors.append("Encoding=Legacy-Mixed is deprecated and not supported by this parser") - elif not value == "UTF-8": - self.errors.append("Value of key 'Encoding' must be UTF-8") - - elif re.match("^Name"+xdg.Locale.regex+"$", key): - pass # locale string - - elif re.match("^GenericName"+xdg.Locale.regex+"$", key): - pass # locale string - - elif re.match("^Comment"+xdg.Locale.regex+"$", key): - pass # locale string - - elif key == "NoDisplay": - self.checkValue(key, value, type="boolean") - - elif key == "Hidden": - self.checkValue(key, value, type="boolean") - - elif key == "Terminal": - self.checkValue(key, value, type="boolean") - self.checkType(key, "Application") - - elif key == "TryExec": - self.checkValue(key, value) - self.checkType(key, "Application") - - elif key == "Exec": - self.checkValue(key, value) - self.checkType(key, "Application") - - elif key == "Path": - self.checkValue(key, value) - self.checkType(key, "Application") - - elif re.match("^Icon"+xdg.Locale.regex+"$", key): - self.checkValue(key, value) - - elif re.match("^SwallowTitle"+xdg.Locale.regex+"$", key): - self.checkType(key, "Application") - - elif key == "SwallowExec": - self.checkValue(key, value) - self.checkType(key, "Application") - - elif key == "FilePatterns": - self.checkValue(key, value, type="regex", list=True) - self.checkType(key, "Application") - - elif key == "Actions": - self.checkValue(key, value, list=True) - self.checkType(key, "Application") - - elif key == "MimeType": - self.checkValue(key, value, type="regex", list=True) - self.checkType(key, "Application") - - elif key == "Categories": - self.checkValue(key, value) - self.checkType(key, "Application") - self.checkCategorie(value) - - elif key == "OnlyShowIn": - self.checkValue(key, value, list=True) - self.checkOnlyShowIn(value) - - elif key == "NotShowIn": - self.checkValue(key, value, list=True) - self.checkOnlyShowIn(value) - - elif key == "StartupNotify": - self.checkValue(key, value, type="boolean") - self.checkType(key, "Application") - - elif key == "StartupWMClass": - self.checkType(key, "Application") - - elif key == "SortOrder": - self.checkValue(key, value, list=True) - self.checkType(key, "Directory") - - elif key == "URL": - self.checkValue(key, value) - self.checkType(key, "URL") - - elif key == "Dev": - self.checkValue(key, value) - self.checkType(key, "FSDevice") + "Class to parse and validate DesktopEntries" + + defaultGroup = 'Desktop Entry' + + def __init__(self, filename=None): + self.content = dict() + if filename and os.path.exists(filename): + self.parse(filename) + elif filename: + self.new(filename) + + def __str__(self): + return self.getName() + + def parse(self, file): + IniFile.parse(self, file, ["Desktop Entry", "KDE Desktop Entry"]) + + # start standard keys + def getType(self): + return self.get('Type') + def getVersion(self): + return self.get('Version', type="numeric") + def getEncoding(self): + return self.get('Encoding') + def getName(self): + return self.get('Name', locale=True) + def getGenericName(self): + return self.get('GenericName', locale=True) + def getComment(self): + return self.get('Comment', locale=True) + def getNoDisplay(self): + return self.get('NoDisplay', type="boolean") + def getIcon(self): + return self.get('Icon', locale=True) + def getHidden(self): + return self.get('Hidden', type="boolean") + def getFilePattern(self): + return self.get('FilePattern', type="regex") + def getTryExec(self): + return self.get('TryExec') + def getExec(self): + return self.get('Exec') + def getPath(self): + return self.get('Path') + def getTerminal(self): + return self.get('Terminal', type="boolean") + def getSwallowTitle(self): + return self.get('SwallowTitle', locale=True) + def getSwallowExec(self): + return self.get('SwallowExec') + def getActions(self): + return self.get('Actions', list=True) + def getMimeType(self): + return self.get('MimeType', list=True, type="regex") + def getSortOrder(self): + return self.get('SortOrder', list=True) + def getDev(self): + return self.get('Dev') + def getFSType(self): + return self.get('FSType') + def getMountPoint(self): + return self.get('MountPoint') + def getReadonly(self): + return self.get('ReadOnly', type="boolean") + def getUnmountIcon(self): + return self.get('UnmountIcon', locale=True) + def getURL(self): + return self.get('URL') + def getCategories(self): + return self.get('Categories', list=True) + def getOnlyShowIn(self): + return self.get('OnlyShowIn', list=True) + def getNotShowIn(self): + return self.get('NotShowIn', list=True) + def getStartupNotify(self): + return self.get('StartupNotify', type="boolean") + def getStartupWMClass(self): + return self.get('StartupWMClass') + # end standard keys + + # start kde keys + def getServiceTypes(self): + return self.get('ServiceTypes', list=True) + def getDocPath(self): + return self.get('DocPath') + def getKeywords(self): + return self.get('Keywords', list=True, locale=True) + def getInitialPreference(self): + return self.get('InitialPreference') + # end kde keys + + # start deprecated keys + def getMiniIcon(self): + return self.get('MiniIcon', locale=True) + def getTerminalOptions(self): + return self.get('TerminalOptions') + def getDefaultApp(self): + return self.get('DefaultApp') + def getProtocols(self): + return self.get('Protocols', list=True) + def getExtensions(self): + return self.get('Extensions', list=True) + def getBinaryPattern(self): + return self.get('BinaryPattern') + def getMapNotify(self): + return self.get('MapNotify') + # end deprecated keys + + # desktop entry edit stuff + def new(self, filename): + if os.path.splitext(filename)[1] == ".desktop": + type = "Application" + elif os.path.splitext(filename)[1] == ".directory": + type = "Directory" + self.content = dict() + self.addGroup(self.defaultGroup) + self.set("Encoding", "UTF-8") + self.set("Type", type) + self.filename = filename + # end desktop entry edit stuff + + # validation stuff + def checkExtras(self): + # header + if self.defaultGroup == "KDE Desktop Entry": + self.warnings.append('[KDE Desktop Entry]-Header is deprecated') + + # file extension + if self.fileExtension == ".kdelnk": + self.warnings.append("File extension .kdelnk is deprecated") + elif self.fileExtension != ".desktop" and self.fileExtension != ".directory": + self.warnings.append('Unknown File extension') + + # Type + try: + self.type = self.content[self.defaultGroup]["Type"] + except KeyError: + self.errors.append("Key 'Type' is missing") + + # Encoding + try: + self.encoding = self.content[self.defaultGroup]["Encoding"] + except KeyError: + self.errors.append("Key 'Encoding' is missing") + + # Version + try: + self.version = self.content[self.defaultGroup]["Version"] + except KeyError: + self.warnings.append("Key 'Version' is missing") + + # Name + try: + self.name = self.content[self.defaultGroup]["Name"] + except KeyError: + self.errors.append("Key 'Name' is missing") + + def checkGroup(self, group): + # check if group header is valid + if not (group == self.defaultGroup \ + or re.match("^\Desktop Action [a-zA-Z]+\$", group) \ + or (re.match("^\X-", group) and group.decode("utf-8", "ignore").encode("ascii", 'ignore') == group)): + self.errors.append("Invalid Group name: %s" % group) + else: + #OnlyShowIn and NotShowIn + if self.content[group].has_key("OnlyShowIn") and self.content[group].has_key("NotShowIn"): + self.errors.append("Group may either have OnlyShowIn or NotShowIn, but not both") + + def checkKey(self, key, value, group): + # standard keys + if key == "Type": + if value == "ServiceType" or value == "Service": + self.warnings.append("Type=%s is a KDE extension" % key) + elif value == "MimeType": + self.warnings.append("Type=MimeType is deprecated") + elif not (value == "Application" or value == "Link" or value == "FSDevice" or value == "Directory"): + self.errors.append("Value of key 'Type' must be Application, Link, FSDevice or Directory, but is '%s'" % value) + + if self.fileExtension == ".directory" and not value == "Directory": + self.warnings.append("File extension is .directory, but Type is '%s'" % value) + elif self.fileExtension == ".desktop" and value == "Directory": + self.warnings.append("Files with Type=Directory should have the extension .directory") + + elif key == "Version": + self.checkValue(key, value, type="number") + + elif key == "Encoding": + if value == "Legacy-Mixed": + self.errors.append("Encoding=Legacy-Mixed is deprecated and not supported by this parser") + elif not value == "UTF-8": + self.errors.append("Value of key 'Encoding' must be UTF-8") + + elif re.match("^Name"+xdg.Locale.regex+"$", key): + pass # locale string + + elif re.match("^GenericName"+xdg.Locale.regex+"$", key): + pass # locale string + + elif re.match("^Comment"+xdg.Locale.regex+"$", key): + pass # locale string + + elif key == "NoDisplay": + self.checkValue(key, value, type="boolean") + + elif key == "Hidden": + self.checkValue(key, value, type="boolean") + + elif key == "Terminal": + self.checkValue(key, value, type="boolean") + self.checkType(key, "Application") + + elif key == "TryExec": + self.checkValue(key, value) + self.checkType(key, "Application") + + elif key == "Exec": + self.checkValue(key, value) + self.checkType(key, "Application") + + elif key == "Path": + self.checkValue(key, value) + self.checkType(key, "Application") + + elif re.match("^Icon"+xdg.Locale.regex+"$", key): + self.checkValue(key, value) + + elif re.match("^SwallowTitle"+xdg.Locale.regex+"$", key): + self.checkType(key, "Application") + + elif key == "SwallowExec": + self.checkValue(key, value) + self.checkType(key, "Application") + + elif key == "FilePatterns": + self.checkValue(key, value, type="regex", list=True) + self.checkType(key, "Application") + + elif key == "Actions": + self.checkValue(key, value, list=True) + self.checkType(key, "Application") + + elif key == "MimeType": + self.checkValue(key, value, type="regex", list=True) + self.checkType(key, "Application") + + elif key == "Categories": + self.checkValue(key, value) + self.checkType(key, "Application") + self.checkCategorie(value) + + elif key == "OnlyShowIn": + self.checkValue(key, value, list=True) + self.checkOnlyShowIn(value) + + elif key == "NotShowIn": + self.checkValue(key, value, list=True) + self.checkOnlyShowIn(value) + + elif key == "StartupNotify": + self.checkValue(key, value, type="boolean") + self.checkType(key, "Application") + + elif key == "StartupWMClass": + self.checkType(key, "Application") + + elif key == "SortOrder": + self.checkValue(key, value, list=True) + self.checkType(key, "Directory") + + elif key == "URL": + self.checkValue(key, value) + self.checkType(key, "URL") + + elif key == "Dev": + self.checkValue(key, value) + self.checkType(key, "FSDevice") - elif key == "FSType": - self.checkValue(key, value) - self.checkType(key, "FSDevice") + elif key == "FSType": + self.checkValue(key, value) + self.checkType(key, "FSDevice") - elif key == "MountPoint": - self.checkValue(key, value) - self.checkType(key, "FSDevice") + elif key == "MountPoint": + self.checkValue(key, value) + self.checkType(key, "FSDevice") - elif re.match("^UnmountIcon"+xdg.Locale.regex+"$", key): - self.checkValue(key, value) - self.checkType(key, "FSDevice") + elif re.match("^UnmountIcon"+xdg.Locale.regex+"$", key): + self.checkValue(key, value) + self.checkType(key, "FSDevice") - elif key == "ReadOnly": - self.checkValue(key, value, type="boolean") - self.checkType(key, "FSDevice") + elif key == "ReadOnly": + self.checkValue(key, value, type="boolean") + self.checkType(key, "FSDevice") - # kde extensions - elif key == "ServiceTypes": - self.checkValue(key, value, list=True) - self.warnings.append("Key '%s' is a KDE extension" % key) + # kde extensions + elif key == "ServiceTypes": + self.checkValue(key, value, list=True) + self.warnings.append("Key '%s' is a KDE extension" % key) - elif key == "DocPath": - self.checkValue(key, value) - self.warnings.append("Key '%s' is a KDE extension" % key) - - elif re.match("^Keywords"+xdg.Locale.regex+"$", key): - self.checkValue(key, value, list=True) - self.warnings.append("Key '%s' is a KDE extension" % key) - - elif key == "InitialPreference": - self.checkValue(key, value, type="number") - self.warnings.append("Key '%s' is a KDE extension" % key) - - # deprecated keys - elif re.match("^MiniIcon"+xdg.Locale.regex+"$", key): - self.checkValue(key, value) - self.warnings.append("Key '%s' is deprecated" % key) - - elif key == "TerminalOptions": - self.checkValue(key, value) - self.warnings.append("Key '%s' is deprecated" % key) - - elif key == "DefaultApp": - self.checkValue(key, value) - self.warnings.append("Key '%s' is deprecated" % key) - - elif key == "Protocols": - self.checkValue(key, value, list=True) - self.warnings.append("Key '%s' is deprecated" % key) - - elif key == "Extensions": - self.checkValue(key, value, list=True) - self.warnings.append("Key '%s' is deprecated" % key) - - elif key == "BinaryPattern": - self.checkValue(key, value) - self.warnings.append("Key '%s' is deprecated" % key) - - elif key == "MapNotify": - self.checkValue(key, value) - self.warnings.append("Key '%s' is deprecated" % key) - - # "X-" extensions - elif re.match("^X-[a-zA-Z0-9-]+", key): - pass - - else: - self.errors.append("Invalid key: %s" % key) - - def checkType(self, key, type): - if not self.type == type: - self.errors.append("Key '%s' only allowed in Type=%s" % (key, type)) - - def checkOnlyShowIn(self, value): - values = self.getList(value) - valid = ["GNOME", "KDE", "ROX", "XFCE", "Old"] - for item in values: - if item not in valid: - self.errors.append("'%s' is not a registered OnlyShowIn value" % item); - - def checkCategorie(self, value): - values = self.getList(value) - valid = ["Legacy","Core","Development","Building","Debugger","IDE","GUIDesigner","Profiling","RevisionControl","Translation","Office","Calendar","ContactManagement","Database","Dictionary","Chart","Email","Finance","FlowChart","PDA","ProjectManagement","Presentation","Spreadsheet","WordProcessor","Graphics","2DGraphics","VectorGraphics","RasterGraphics","3DGraphics","Scanning","OCR","Photograph","Viewer","Settings","DesktopSettings","HardwareSettings","PackageManager","Network","Dialup","InstantMessaging","IRCClient","FileTransfer","HamRadio","News","P2P","RemoteAccess","Telephony","WebBrowser","WebDevelopment","AudioVideo","Audio","Midi","Mixer","Sequencer","Tuner","Video","TV","AudioVideoEditing","Player","Recorder","DiscBurning","Game","ActionGame","AdventureGame","ArcadeGame","BoardGame","BlocksGame","CardGame","KidsGame","LogicGame","RolePlaying","Simulation","SportsGame","StrategyGame","Education","Art","Art","Contruction","Music","Languages","Science","Astronomy","Biology","Chemistry","Geology","Math","MedicalSoftware","Physics","Teaching","Amusement","Applet","Archiving","Electronics","Emulator","Engineering","FileManager","Shell","Screensaver","TerminalEmulator","TrayIcon","System","Filesystem","Monitor","Security","Utility","Accessibility","Calculator","Clock","TextEditor","KDE","GNOME","GTK","Qt","Motif","Java","ConsoleOnly"] - for item in values: - if item not in valid: - self.errors.append("'%s' is not a registered Category" % item); + elif key == "DocPath": + self.checkValue(key, value) + self.warnings.append("Key '%s' is a KDE extension" % key) + + elif re.match("^Keywords"+xdg.Locale.regex+"$", key): + self.checkValue(key, value, list=True) + self.warnings.append("Key '%s' is a KDE extension" % key) + + elif key == "InitialPreference": + self.checkValue(key, value, type="number") + self.warnings.append("Key '%s' is a KDE extension" % key) + + # deprecated keys + elif re.match("^MiniIcon"+xdg.Locale.regex+"$", key): + self.checkValue(key, value) + self.warnings.append("Key '%s' is deprecated" % key) + + elif key == "TerminalOptions": + self.checkValue(key, value) + self.warnings.append("Key '%s' is deprecated" % key) + + elif key == "DefaultApp": + self.checkValue(key, value) + self.warnings.append("Key '%s' is deprecated" % key) + + elif key == "Protocols": + self.checkValue(key, value, list=True) + self.warnings.append("Key '%s' is deprecated" % key) + + elif key == "Extensions": + self.checkValue(key, value, list=True) + self.warnings.append("Key '%s' is deprecated" % key) + + elif key == "BinaryPattern": + self.checkValue(key, value) + self.warnings.append("Key '%s' is deprecated" % key) + + elif key == "MapNotify": + self.checkValue(key, value) + self.warnings.append("Key '%s' is deprecated" % key) + + # "X-" extensions + elif re.match("^X-[a-zA-Z0-9-]+", key): + pass + + else: + self.errors.append("Invalid key: %s" % key) + + def checkType(self, key, type): + if not self.type == type: + self.errors.append("Key '%s' only allowed in Type=%s" % (key, type)) + + def checkOnlyShowIn(self, value): + values = self.getList(value) + valid = ["GNOME", "KDE", "ROX", "XFCE", "Old"] + for item in values: + if item not in valid: + self.errors.append("'%s' is not a registered OnlyShowIn value" % item); + + def checkCategorie(self, value): + values = self.getList(value) + valid = ["Legacy","Core","Development","Building","Debugger","IDE","GUIDesigner","Profiling","RevisionControl","Translation","Office","Calendar","ContactManagement","Database","Dictionary","Chart","Email","Finance","FlowChart","PDA","ProjectManagement","Presentation","Spreadsheet","WordProcessor","Graphics","2DGraphics","VectorGraphics","RasterGraphics","3DGraphics","Scanning","OCR","Photograph","Viewer","Settings","DesktopSettings","HardwareSettings","PackageManager","Network","Dialup","InstantMessaging","IRCClient","FileTransfer","HamRadio","News","P2P","RemoteAccess","Telephony","WebBrowser","WebDevelopment","AudioVideo","Audio","Midi","Mixer","Sequencer","Tuner","Video","TV","AudioVideoEditing","Player","Recorder","DiscBurning","Game","ActionGame","AdventureGame","ArcadeGame","BoardGame","BlocksGame","CardGame","KidsGame","LogicGame","RolePlaying","Simulation","SportsGame","StrategyGame","Education","Art","Art","Contruction","Music","Languages","Science","Astronomy","Biology","Chemistry","Geology","Math","MedicalSoftware","Physics","Teaching","Amusement","Applet","Archiving","Electronics","Emulator","Engineering","FileManager","Shell","Screensaver","TerminalEmulator","TrayIcon","System","Filesystem","Monitor","Security","Utility","Accessibility","Calculator","Clock","TextEditor","KDE","GNOME","GTK","Qt","Motif","Java","ConsoleOnly"] + for item in values: + if item not in valid: + self.errors.append("'%s' is not a registered Category" % item); diff --git a/xdg/Exceptions.py b/xdg/Exceptions.py index ab6c35a..f7d08be 100644 --- a/xdg/Exceptions.py +++ b/xdg/Exceptions.py @@ -5,47 +5,47 @@ Exception Classes for the xdg package debug = False class Error(Exception): - def __init__(self, msg): - self.msg = msg - Exception.__init__(self, msg) - def __str__(self): - return self.msg + def __init__(self, msg): + self.msg = msg + Exception.__init__(self, msg) + def __str__(self): + return self.msg class ValidationError(Error): - def __init__(self, msg, file): - self.msg = msg - self.file = file - Error.__init__(self, "ValidationError in file '%s': %s " % (file, msg)) + def __init__(self, msg, file): + self.msg = msg + self.file = file + Error.__init__(self, "ValidationError in file '%s': %s " % (file, msg)) class ParsingError(Error): - def __init__(self, msg, file): - self.msg = msg - self.file = file - Error.__init__(self, "ParsingError in file '%s', %s" % (file, msg)) + def __init__(self, msg, file): + self.msg = msg + self.file = file + Error.__init__(self, "ParsingError in file '%s', %s" % (file, msg)) class NoKeyError(Error): - def __init__(self, key, group, file): - Error.__init__(self, "No key '%s' in group %s of file %s" % (key, group, file)) - self.key = key - self.group = group + def __init__(self, key, group, file): + Error.__init__(self, "No key '%s' in group %s of file %s" % (key, group, file)) + self.key = key + self.group = group class DuplicateKeyError(Error): - def __init__(self, key, group, file): - Error.__init__(self, "Duplicate key '%s' in group %s of file %s" % (key, group, file)) - self.key = key - self.group = group + def __init__(self, key, group, file): + Error.__init__(self, "Duplicate key '%s' in group %s of file %s" % (key, group, file)) + self.key = key + self.group = group class NoGroupError(Error): - def __init__(self, group, file): - Error.__init__(self, "No group: %s in file %s" % (group, file)) - self.group = group + def __init__(self, group, file): + Error.__init__(self, "No group: %s in file %s" % (group, file)) + self.group = group class DuplicateGroupError(Error): - def __init__(self, group, file): - Error.__init__(self, "Duplicate group: %s in file %s" % (group, file)) - self.group = group + def __init__(self, group, file): + Error.__init__(self, "Duplicate group: %s in file %s" % (group, file)) + self.group = group class NoThemeError(Error): - def __init__(self, theme): - Error.__init__(self, "No such icon-theme: %s" % theme) - self.theme = theme + def __init__(self, theme): + Error.__init__(self, "No such icon-theme: %s" % theme) + self.theme = theme diff --git a/xdg/IconTheme.py b/xdg/IconTheme.py index 45d5c22..5c30ebf 100644 --- a/xdg/IconTheme.py +++ b/xdg/IconTheme.py @@ -12,209 +12,209 @@ from xdg.Exceptions import * import xdg.Config class IconTheme(IniFile): - "Class to parse and validate IconThemes" - def __init__(self): - IniFile.__init__(self) - - def __repr__(self): - return self.name - - def parse(self, file): - IniFile.parse(self, file, ["Icon Theme", "KDE Icon Theme"]) - self.dir = os.path.dirname(file) - (nil, self.name) = os.path.split(self.dir) - - def getDir(self): - return self.dir - - # Standard Keys - def getName(self): - return self.get('Name', locale=True) - def getComment(self): - return self.get('Comment', locale=True) - def getInherits(self): - return self.get('Inherits', list=True) - def getDirectories(self): - return self.get('Directories', list=True) - def getHidden(self): - return self.get('Hidden', type="boolean") - def getExample(self): - return self.get('Example') - - # Per Directory Keys - def getSize(self, directory): - return self.get('Size', type="integer", group=directory) - def getContext(self, directory): - return self.get('Context', group=directory) - def getType(self, directory): - value = self.get('Type', group=directory) - if value: - return value - else: - return "Threshold" - def getMaxSize(self, directory): - value = self.get('MaxSize', type="integer", group=directory) - if value or value == 0: - return value - else: - return self.getSize(directory) - def getMinSize(self, directory): - value = self.get('MinSize', type="integer", group=directory) - if value or value == 0: - return value - else: - return self.getSize(directory) - def getThreshold(self, directory): - value = self.get('Threshold', type="integer", group=directory) - if value or value == 0: - return value - else: - return 2 - - # validation stuff - def checkExtras(self): - # header - if self.defaultGroup == "KDE Icon Theme": - self.warnings.append('[KDE Icon Theme]-Header is deprecated') - - # file extension - if self.fileExtension == ".theme": - pass - elif self.fileExtension == ".desktop": - self.warnings.append('.desktop fileExtension is deprecated') - else: - self.warnings.append('Unknown File extension') - - # Check required keys - # Name - try: - self.name = self.content[self.defaultGroup]["Name"] - except KeyError: - self.errors.append("Key 'Name' is missing") - - # Comment - try: - self.comment = self.content[self.defaultGroup]["Comment"] - except KeyError: - self.errors.append("Key 'Comment' is missing") - - # Directories - try: - self.directories = self.content[self.defaultGroup]["Directories"] - except KeyError: - self.errors.append("Key 'Directories' is missing") - - def checkGroup(self, group): - # check if group header is valid - if group == self.defaultGroup: - pass - elif group in self.getDirectories(): - try: - self.type = self.content[group]["Type"] - except KeyError: - self.type = "Threshold" - try: - self.name = self.content[group]["Name"] - except KeyError: - self.errors.append("Key 'Name' in Group '%s' is missing" % group) - elif not (re.match("^\[X-", group) and group.decode("utf-8", "ignore").encode("ascii", 'ignore') == group): - self.errors.append("Invalid Group name: %s" % group) - - def checkKey(self, key, value, group): - # standard keys - if group == self.defaultGroup: - if re.match("^Name"+xdg.Locale.regex+"$", key): - pass - elif re.match("^Comment"+xdg.Locale.regex+"$", key): - pass - elif key == "Inherits": - self.checkValue(key, value, list=True) - elif key == "Directories": - self.checkValue(key, value, list=True) - elif key == "Hidden": - self.checkValue(key, value, type="boolean") - elif key == "Example": - self.checkValue(key, value) - elif re.match("^X-[a-zA-Z0-9-]+", key): - pass - else: - self.errors.append("Invalid key: %s" % key) - elif group in self.getDirectories(): - if key == "Size": - self.checkValue(key, value, type="integer") - elif key == "Context": - self.checkValue(key, value) - elif key == "Type": - self.checkValue(key, value) - if value not in ["Fixed", "Scalable", "Threshold"]: - self.errors.append("Key 'Type' must be one out of 'Fixed','Scalable','Threshold', but is %s" % value) - elif key == "MaxSize": - self.checkValue(key, value, type="integer") - if self.type != "Scalable": - self.errors.append("Key 'MaxSize' give, but Type is %s" % self.type) - elif key == "MinSize": - self.checkValue(key, value, type="integer") - if self.type != "Scalable": - self.errors.append("Key 'MinSize' give, but Type is %s" % self.type) - elif key == "Threshold": - self.checkValue(key, value, type="integer") - if self.type != "Threshold": - self.errors.append("Key 'Threshold' give, but Type is %s" % self.type) - elif re.match("^X-[a-zA-Z0-9-]+", key): - pass - else: - self.errors.append("Invalid key: %s" % key) + "Class to parse and validate IconThemes" + def __init__(self): + IniFile.__init__(self) + + def __repr__(self): + return self.name + + def parse(self, file): + IniFile.parse(self, file, ["Icon Theme", "KDE Icon Theme"]) + self.dir = os.path.dirname(file) + (nil, self.name) = os.path.split(self.dir) + + def getDir(self): + return self.dir + + # Standard Keys + def getName(self): + return self.get('Name', locale=True) + def getComment(self): + return self.get('Comment', locale=True) + def getInherits(self): + return self.get('Inherits', list=True) + def getDirectories(self): + return self.get('Directories', list=True) + def getHidden(self): + return self.get('Hidden', type="boolean") + def getExample(self): + return self.get('Example') + + # Per Directory Keys + def getSize(self, directory): + return self.get('Size', type="integer", group=directory) + def getContext(self, directory): + return self.get('Context', group=directory) + def getType(self, directory): + value = self.get('Type', group=directory) + if value: + return value + else: + return "Threshold" + def getMaxSize(self, directory): + value = self.get('MaxSize', type="integer", group=directory) + if value or value == 0: + return value + else: + return self.getSize(directory) + def getMinSize(self, directory): + value = self.get('MinSize', type="integer", group=directory) + if value or value == 0: + return value + else: + return self.getSize(directory) + def getThreshold(self, directory): + value = self.get('Threshold', type="integer", group=directory) + if value or value == 0: + return value + else: + return 2 + + # validation stuff + def checkExtras(self): + # header + if self.defaultGroup == "KDE Icon Theme": + self.warnings.append('[KDE Icon Theme]-Header is deprecated') + + # file extension + if self.fileExtension == ".theme": + pass + elif self.fileExtension == ".desktop": + self.warnings.append('.desktop fileExtension is deprecated') + else: + self.warnings.append('Unknown File extension') + + # Check required keys + # Name + try: + self.name = self.content[self.defaultGroup]["Name"] + except KeyError: + self.errors.append("Key 'Name' is missing") + + # Comment + try: + self.comment = self.content[self.defaultGroup]["Comment"] + except KeyError: + self.errors.append("Key 'Comment' is missing") + + # Directories + try: + self.directories = self.content[self.defaultGroup]["Directories"] + except KeyError: + self.errors.append("Key 'Directories' is missing") + + def checkGroup(self, group): + # check if group header is valid + if group == self.defaultGroup: + pass + elif group in self.getDirectories(): + try: + self.type = self.content[group]["Type"] + except KeyError: + self.type = "Threshold" + try: + self.name = self.content[group]["Name"] + except KeyError: + self.errors.append("Key 'Name' in Group '%s' is missing" % group) + elif not (re.match("^\[X-", group) and group.decode("utf-8", "ignore").encode("ascii", 'ignore') == group): + self.errors.append("Invalid Group name: %s" % group) + + def checkKey(self, key, value, group): + # standard keys + if group == self.defaultGroup: + if re.match("^Name"+xdg.Locale.regex+"$", key): + pass + elif re.match("^Comment"+xdg.Locale.regex+"$", key): + pass + elif key == "Inherits": + self.checkValue(key, value, list=True) + elif key == "Directories": + self.checkValue(key, value, list=True) + elif key == "Hidden": + self.checkValue(key, value, type="boolean") + elif key == "Example": + self.checkValue(key, value) + elif re.match("^X-[a-zA-Z0-9-]+", key): + pass + else: + self.errors.append("Invalid key: %s" % key) + elif group in self.getDirectories(): + if key == "Size": + self.checkValue(key, value, type="integer") + elif key == "Context": + self.checkValue(key, value) + elif key == "Type": + self.checkValue(key, value) + if value not in ["Fixed", "Scalable", "Threshold"]: + self.errors.append("Key 'Type' must be one out of 'Fixed','Scalable','Threshold', but is %s" % value) + elif key == "MaxSize": + self.checkValue(key, value, type="integer") + if self.type != "Scalable": + self.errors.append("Key 'MaxSize' give, but Type is %s" % self.type) + elif key == "MinSize": + self.checkValue(key, value, type="integer") + if self.type != "Scalable": + self.errors.append("Key 'MinSize' give, but Type is %s" % self.type) + elif key == "Threshold": + self.checkValue(key, value, type="integer") + if self.type != "Threshold": + self.errors.append("Key 'Threshold' give, but Type is %s" % self.type) + elif re.match("^X-[a-zA-Z0-9-]+", key): + pass + else: + self.errors.append("Invalid key: %s" % key) class IconData(IniFile): - "Class to parse and validate IconData Files" - def __init__(self): - IniFile.__init__(self) - - def __repr__(self): - return self.getDisplayName() - - def parse(self, file): - IniFile.parse(self, file, ["Icon Data"]) - - # Standard Keys - def getDisplayName(self): - return self.get('DisplayName', locale=True) - def getEmbeddedTextRectangle(self): - return self.get('EmbeddedTextRectangle', list=True) - def getAttachPoints(self): - return self.get('AttachPoints', type="point", list=True) - - # validation stuff - def checkExtras(self): - # file extension - if self.fileExtension != ".icon": - self.warnings.append('Unknown File extension') - - def checkGroup(self, group): - # check if group header is valid - if not (group == self.defaultGroup \ - or (re.match("^\[X-", group) and group.encode("ascii", 'ignore') == group)): - self.errors.append("Invalid Group name: %s" % group.encode("ascii", "replace")) - - def checkKey(self, key, value, group): - # standard keys - if re.match("^DisplayName"+xdg.Locale.regex+"$", key): - pass - elif key == "EmbeddedTextRectangle": - self.checkValue(key, value, type="integer", list=True) - elif key == "AttachPoints": - self.checkValue(key, value, type="point", list=True) - elif re.match("^X-[a-zA-Z0-9-]+", key): - pass - else: - self.errors.append("Invalid key: %s" % key) + "Class to parse and validate IconData Files" + def __init__(self): + IniFile.__init__(self) + + def __repr__(self): + return self.getDisplayName() + + def parse(self, file): + IniFile.parse(self, file, ["Icon Data"]) + + # Standard Keys + def getDisplayName(self): + return self.get('DisplayName', locale=True) + def getEmbeddedTextRectangle(self): + return self.get('EmbeddedTextRectangle', list=True) + def getAttachPoints(self): + return self.get('AttachPoints', type="point", list=True) + + # validation stuff + def checkExtras(self): + # file extension + if self.fileExtension != ".icon": + self.warnings.append('Unknown File extension') + + def checkGroup(self, group): + # check if group header is valid + if not (group == self.defaultGroup \ + or (re.match("^\[X-", group) and group.encode("ascii", 'ignore') == group)): + self.errors.append("Invalid Group name: %s" % group.encode("ascii", "replace")) + + def checkKey(self, key, value, group): + # standard keys + if re.match("^DisplayName"+xdg.Locale.regex+"$", key): + pass + elif key == "EmbeddedTextRectangle": + self.checkValue(key, value, type="integer", list=True) + elif key == "AttachPoints": + self.checkValue(key, value, type="point", list=True) + elif re.match("^X-[a-zA-Z0-9-]+", key): + pass + else: + self.errors.append("Invalid key: %s" % key) icondirs = [] for basedir in xdg_data_dirs: - icondirs.append(os.path.join(basedir, "icons")) + icondirs.append(os.path.join(basedir, "icons")) icondirs.append("/usr/share/pixmaps") icondirs.append(os.path.expanduser("~/.icons")) @@ -225,161 +225,161 @@ dache = dict() eache = dict() def getIconPath(iconname, size = None, theme = None, extensions = ["png", "svg", "xpm"]): - global themes - - if size == None: - size = xdg.Config.icon_size - if theme == None: - theme = xdg.Config.icon_theme - - # if we have an absolute path, just return it - if os.path.isabs(iconname): - return iconname - - # check if it has an extension and strip it - if os.path.splitext(iconname)[1][1:] in extensions: - iconname = os.path.splitext(iconname)[0] - - # parse theme files - try: - if themes[0].name != theme: - themes = [] - __addTheme(theme) - except IndexError: - __addTheme(theme) - - # more caching (icon looked up in the last 5 seconds?) - tmp = "".join([iconname, str(size), theme, "".join(extensions)]) - if eache.has_key(tmp): - if int(time.time() - eache[tmp][0]) >= xdg.Config.cache_time: - del eache[tmp] - else: - return eache[tmp][1] - - for thme in themes: - icon = LookupIcon(iconname, size, thme, extensions) - if icon: - eache[tmp] = [time.time(), icon] - return icon - - # cache stuff again (directories lookuped up in the last 5 seconds?) - for directory in icondirs: - if (not dache.has_key(directory) \ - or (int(time.time() - dache[directory][1]) >= xdg.Config.cache_time \ - and dache[directory][2] < os.path.getmtime(directory))) \ - and os.path.isdir(directory): - dache[directory] = [os.listdir(directory), time.time(), os.path.getmtime(directory)] - - for dir, values in dache.items(): - for extension in extensions: - if iconname + "." + extension in values[0]: - icon = os.path.join(dir, iconname + "." + extension) - eache[tmp] = [time.time(), icon] - return icon - - # we haven't found anything? "hicolor" is our fallback - if theme != "hicolor": - icon = getIconPath(iconname, size, "hicolor") - eache[tmp] = [time.time(), icon] - return icon + global themes + + if size == None: + size = xdg.Config.icon_size + if theme == None: + theme = xdg.Config.icon_theme + + # if we have an absolute path, just return it + if os.path.isabs(iconname): + return iconname + + # check if it has an extension and strip it + if os.path.splitext(iconname)[1][1:] in extensions: + iconname = os.path.splitext(iconname)[0] + + # parse theme files + try: + if themes[0].name != theme: + themes = [] + __addTheme(theme) + except IndexError: + __addTheme(theme) + + # more caching (icon looked up in the last 5 seconds?) + tmp = "".join([iconname, str(size), theme, "".join(extensions)]) + if eache.has_key(tmp): + if int(time.time() - eache[tmp][0]) >= xdg.Config.cache_time: + del eache[tmp] + else: + return eache[tmp][1] + + for thme in themes: + icon = LookupIcon(iconname, size, thme, extensions) + if icon: + eache[tmp] = [time.time(), icon] + return icon + + # cache stuff again (directories lookuped up in the last 5 seconds?) + for directory in icondirs: + if (not dache.has_key(directory) \ + or (int(time.time() - dache[directory][1]) >= xdg.Config.cache_time \ + and dache[directory][2] < os.path.getmtime(directory))) \ + and os.path.isdir(directory): + dache[directory] = [os.listdir(directory), time.time(), os.path.getmtime(directory)] + + for dir, values in dache.items(): + for extension in extensions: + if iconname + "." + extension in values[0]: + icon = os.path.join(dir, iconname + "." + extension) + eache[tmp] = [time.time(), icon] + return icon + + # we haven't found anything? "hicolor" is our fallback + if theme != "hicolor": + icon = getIconPath(iconname, size, "hicolor") + eache[tmp] = [time.time(), icon] + return icon def getIconData(path): - if os.path.isfile(path): - dirname = os.path.dirname(path) - basename = os.path.basename(path) - if os.path.isfile(os.path.join(dirname, basename + ".icon")): - data = IconData() - data.parse(os.path.join(dirname, basename + ".icon")) - return data + if os.path.isfile(path): + dirname = os.path.dirname(path) + basename = os.path.basename(path) + if os.path.isfile(os.path.join(dirname, basename + ".icon")): + data = IconData() + data.parse(os.path.join(dirname, basename + ".icon")) + return data def __addTheme(theme): - for dir in icondirs: - if os.path.isfile(os.path.join(dir, theme, "index.theme")): - __parseTheme(os.path.join(dir,theme, "index.theme")) - break - elif os.path.isfile(os.path.join(dir, theme, "index.desktop")): - __parseTheme(os.path.join(dir,theme, "index.desktop")) - break - else: - if debug: - raise NoThemeError(theme) + for dir in icondirs: + if os.path.isfile(os.path.join(dir, theme, "index.theme")): + __parseTheme(os.path.join(dir,theme, "index.theme")) + break + elif os.path.isfile(os.path.join(dir, theme, "index.desktop")): + __parseTheme(os.path.join(dir,theme, "index.desktop")) + break + else: + if debug: + raise NoThemeError(theme) def __parseTheme(file): - theme = IconTheme() - theme.parse(file) - themes.append(theme) - for subtheme in theme.getInherits(): - __addTheme(subtheme) + theme = IconTheme() + theme.parse(file) + themes.append(theme) + for subtheme in theme.getInherits(): + __addTheme(subtheme) def LookupIcon(iconname, size, theme, extensions): - # look for the cache - if not cache.has_key(theme.name): - cache[theme.name] = [] - cache[theme.name].append(time.time() - (xdg.Config.cache_time + 1)) # [0] last time of lookup - cache[theme.name].append(0) # [1] mtime - cache[theme.name].append(dict()) # [2] dir: [subdir, [items]] - - # cache stuff (directory lookuped up the in the last 5 seconds?) - if int(time.time() - cache[theme.name][0]) >= xdg.Config.cache_time: - cache[theme.name][0] = time.time() - for subdir in theme.getDirectories(): - for directory in icondirs: - dir = os.path.join(directory,theme.name,subdir) - if (not cache[theme.name][2].has_key(dir) \ - or cache[theme.name][1] < os.path.getmtime(os.path.join(directory,theme.name))) \ - and subdir != "" \ - and os.path.isdir(dir): - cache[theme.name][2][dir] = [subdir, os.listdir(dir)] - cache[theme.name][1] = os.path.getmtime(os.path.join(directory,theme.name)) - - for dir, values in cache[theme.name][2].items(): - if DirectoryMatchesSize(values[0], size, theme): - for extension in extensions: - if iconname + "." + extension in values[1]: - return os.path.join(dir, iconname + "." + extension) - - minimal_size = sys.maxint - closest_filename = "" - for dir, values in cache[theme.name][2].items(): - distance = DirectorySizeDistance(values[0], size, theme) - if distance < minimal_size: - for extension in extensions: - if iconname + "." + extension in values[1]: - closest_filename = os.path.join(dir, iconname + "." + extension) - minimal_size = distance - - return closest_filename + # look for the cache + if not cache.has_key(theme.name): + cache[theme.name] = [] + cache[theme.name].append(time.time() - (xdg.Config.cache_time + 1)) # [0] last time of lookup + cache[theme.name].append(0) # [1] mtime + cache[theme.name].append(dict()) # [2] dir: [subdir, [items]] + + # cache stuff (directory lookuped up the in the last 5 seconds?) + if int(time.time() - cache[theme.name][0]) >= xdg.Config.cache_time: + cache[theme.name][0] = time.time() + for subdir in theme.getDirectories(): + for directory in icondirs: + dir = os.path.join(directory,theme.name,subdir) + if (not cache[theme.name][2].has_key(dir) \ + or cache[theme.name][1] < os.path.getmtime(os.path.join(directory,theme.name))) \ + and subdir != "" \ + and os.path.isdir(dir): + cache[theme.name][2][dir] = [subdir, os.listdir(dir)] + cache[theme.name][1] = os.path.getmtime(os.path.join(directory,theme.name)) + + for dir, values in cache[theme.name][2].items(): + if DirectoryMatchesSize(values[0], size, theme): + for extension in extensions: + if iconname + "." + extension in values[1]: + return os.path.join(dir, iconname + "." + extension) + + minimal_size = sys.maxint + closest_filename = "" + for dir, values in cache[theme.name][2].items(): + distance = DirectorySizeDistance(values[0], size, theme) + if distance < minimal_size: + for extension in extensions: + if iconname + "." + extension in values[1]: + closest_filename = os.path.join(dir, iconname + "." + extension) + minimal_size = distance + + return closest_filename def DirectoryMatchesSize(subdir, iconsize, theme): - Type = theme.getType(subdir) - Size = theme.getSize(subdir) - Threshold = theme.getThreshold(subdir) - MinSize = theme.getMinSize(subdir) - MaxSize = theme.getMaxSize(subdir) - if Type == "Fixed": - return Size == iconsize - elif Type == "Scaleable": - return MinSize <= iconsize <= MaxSize - elif Type == "Threshold": - return Size - Threshold <= iconsize <= Size + Threshold + Type = theme.getType(subdir) + Size = theme.getSize(subdir) + Threshold = theme.getThreshold(subdir) + MinSize = theme.getMinSize(subdir) + MaxSize = theme.getMaxSize(subdir) + if Type == "Fixed": + return Size == iconsize + elif Type == "Scaleable": + return MinSize <= iconsize <= MaxSize + elif Type == "Threshold": + return Size - Threshold <= iconsize <= Size + Threshold def DirectorySizeDistance(subdir, iconsize, theme): - Type = theme.getType(subdir) - Size = theme.getSize(subdir) - Threshold = theme.getThreshold(subdir) - MinSize = theme.getMinSize(subdir) - MaxSize = theme.getMaxSize(subdir) - if Type == "Fixed": - return abs(Size - iconsize) - elif Type == "Scalable": - if iconsize < MinSize: - return MinSize - iconsize - elif iconsize > MaxSize: - return MaxSize - iconsize - return 0 - elif Type == "Threshold": - if iconsize < Size - Threshold: - return MinSize - iconsize - elif iconsize > Size + Threshold: - return iconsize - MaxSize - return 0 + Type = theme.getType(subdir) + Size = theme.getSize(subdir) + Threshold = theme.getThreshold(subdir) + MinSize = theme.getMinSize(subdir) + MaxSize = theme.getMaxSize(subdir) + if Type == "Fixed": + return abs(Size - iconsize) + elif Type == "Scalable": + if iconsize < MinSize: + return MinSize - iconsize + elif iconsize > MaxSize: + return MaxSize - iconsize + return 0 + elif Type == "Threshold": + if iconsize < Size - Threshold: + return MinSize - iconsize + elif iconsize > Size + Threshold: + return iconsize - MaxSize + return 0 diff --git a/xdg/IniFile.py b/xdg/IniFile.py index 90093a6..d711772 100644 --- a/xdg/IniFile.py +++ b/xdg/IniFile.py @@ -9,374 +9,374 @@ from Exceptions import * import xdg.Locale class IniFile: - defaultGroup = '' - fileExtension = '' - - filename = '' - - tainted = False - - def __init__(self, filename=None): - self.content = dict() - if filename: - self.parse(filename) - - def __cmp__(self, other): - return cmp(self.content, other.content) - - def parse(self, filename, headers): - # for performance reasons - content = self.content - - if not os.path.isfile(filename): - raise ParsingError("File not found", filename) - - # parse file - for line in file(filename,'r'): - line = line.strip() - # empty line - if not line: - continue - # comment - elif line[0] == '#': - continue - # new group - elif line[0] == '[': - currentGroup = line.lstrip("[").rstrip("]") - if debug and self.hasGroup(currentGroup): - raise DuplicateGroupError(currentGroup, filename) - else: - content[currentGroup] = {} - # key - else: - index = line.find("=") - key = line[0:index].strip() - value = line[index+1:].strip() - try: - if debug and self.hasKey(key, currentGroup): - raise DuplicateKeyError(key, currentGroup, filename) - else: - content[currentGroup][key] = value - except (IndexError, UnboundLocalError): - raise ParsingError("[%s]-Header missing" % headers[0], filename) - - self.filename = filename - self.tainted = False - - # check header - for header in headers: - if content.has_key(header): - self.defaultGroup = header - break - else: - raise ParsingError("[%s]-Header missing" % headers[0], filename) - - # start stuff to access the keys - def get(self, key, group=None, locale=False, type="string", list=False): - # set default group - if not group: - group = self.defaultGroup - - # return key (with locale) - if self.content.has_key(group) and self.content[group].has_key(key): - if locale: - value = self.content[group][self.__addLocale(key, group)] - else: - value = self.content[group][key] - else: - if debug: - if not self.content.has_key(group): - raise NoGroupError(group, self.filename) - elif not self.content[group].has_key(key): - raise NoKeyError(key, group, self.filename) - else: - value = "" - - if list == True: - values = self.getList(value) - result = [] - else: - values = [value] - - for value in values: - if type == "string" and locale == True: - value = value.decode("utf-8", "ignore") - elif type == "boolean": - value = self.__getBoolean(value) - elif type == "integer": - try: - value = int(value) - except ValueError: - value = 0 - elif type == "numeric": - try: - value = float(value) - except ValueError: - value = 0.0 - elif type == "regex": - value = re.compile(value) - elif type == "point": - value = value.split(",") - - if list == True: - result.append(value) - else: - result = value - - return result - # end stuff to access the keys - - # start subget - def getList(self, string): - if re.search(r"(?<!\\)\;", string): - list = re.split(r"(?<!\\);", string) - elif re.search(r"(?<!\\)\|", string): - list = re.split(r"(?<!\\)\|", string) - elif re.search(r"(?<!\\),", string): - list = re.split(r"(?<!\\),", string) - else: - list = [string] - if list[-1] == "": - list.pop() - return list - - def __getBoolean(self, boolean): - if boolean == 1 or boolean == "true" or boolean == "True": - return True - elif boolean == 0 or boolean == "false" or boolean == "False": - return False - return False - # end subget - - def __addLocale(self, key, group=None): - "add locale to key according the current lc_messages" - # set default group - if not group: - group = self.defaultGroup - - for lang in xdg.Locale.langs: - if self.content[group].has_key(key+'['+lang+']'): - return key+'['+lang+']' - - return key - - # start validation stuff - def validate(self, report="All"): - "validate ... report = All / Warnings / Errors" - - self.warnings = [] - self.errors = [] - - # get file extension - self.fileExtension = os.path.splitext(self.filename)[1] - - # overwrite this for own checkings - self.checkExtras() - - # check all keys - for group in self.content: - self.checkGroup(group) - for key in self.content[group]: - self.checkKey(key, self.content[group][key], group) - # check if value is empty - if self.content[group][key] == "": - self.warnings.append("Value of Key '%s' is empty" % key) - - # raise Warnings / Errors - msg = "" - - if report == "All" or report == "Warnings": - for line in self.warnings: - msg += "\n- " + line - - if report == "All" or report == "Errors": - for line in self.errors: - msg += "\n- " + line - - if msg: - raise ValidationError(msg, self.filename) - - # check if group header is valid - def checkGroup(self, group): - pass - - # check if key is valid - def checkKey(self, key, value, group): - pass - - # check random stuff - def checkValue(self, key, value, type="string", list=False): - if list == True: - values = self.getList(value) - else: - values = [value] - - for value in values: - if type == "string": - code = self.checkString(value) - elif type == "boolean": - code = self.checkBoolean(value) - elif type == "number": - code = self.checkNumber(value) - elif type == "integer": - code = self.checkInteger(value) - elif type == "regex": - code = self.checkRegex(value) - elif type == "point": - code = self.checkPoint(value) - if code == 1: - self.errors.append("'%s' is not a valid %s" % (value, type)) - elif code == 2: - self.warnings.append("Value of key '%s' is deprecated" % key) - - def checkExtras(self): - pass - - def checkBoolean(self, value): - # 1 or 0 : deprecated - if (value == "1" or value == "0"): - return 2 - # true or false: ok - elif not (value == "true" or value == "false"): - return 1 - - def checkNumber(self, value): - # float() ValueError - try: - float(value) - except: - return 1 - - def checkInteger(self, value): - # int() ValueError - try: - int(value) - except: - return 1 - - def checkPoint(self, value): - if not re.match("^[0-9]+,[0-9]+$", value): - return 1 - - def checkString(self, value): - # convert to ascii - if not value.decode("utf-8", "ignore").encode("ascii", 'ignore') == value: - return 1 - - def checkRegex(self, value): - try: - re.compile(value) - except: - return 1 - - # write support - def write(self, filename=None): - if not filename and not self.filename: - raise ParsingError("File not found", "") - - if filename: - self.filename = filename - else: - filename = self.filename - - if os.path.dirname(filename) and not os.path.isdir(os.path.dirname(filename)): - os.makedirs(os.path.dirname(filename)) - - fp = codecs.open(filename, 'w') - if self.defaultGroup: - fp.write("[%s]\n" % self.defaultGroup) - for (key, value) in self.content[self.defaultGroup].items(): - fp.write("%s=%s\n" % (key, value)) - fp.write("\n") - for (name, group) in self.content.items(): - if name != self.defaultGroup: - fp.write("[%s]\n" % name) - for (key, value) in group.items(): - fp.write("%s=%s\n" % (key, value)) - fp.write("\n") - self.tainted = False - - def set(self, key, value, group=None, locale=False): - # set default group - if not group: - group = self.defaultGroup - - if locale == True and len(xdg.Locale.langs) > 0: - key = key + "[" + xdg.Locale.langs[0] + "]" - - try: - if isinstance(value, unicode): - self.content[group][key] = value.encode("utf-8", "ignore") - else: - self.content[group][key] = value - except KeyError: - raise NoGroupError(group, self.filename) - - self.tainted = (value == self.get(key, group)) - - def addGroup(self, group): - if self.hasGroup(group): - if debug: - raise DuplicateGroupError(group, self.filename) - else: - pass - else: - self.content[group] = {} - self.tainted = True - - def removeGroup(self, group): - existed = group in self.content - if existed: - del self.content[group] - self.tainted = True - else: - if debug: - raise NoGroupError(group, self.filename) - return existed - - def removeKey(self, key, group=None, locales=True): - # set default group - if not group: - group = self.defaultGroup - - try: - if locales: - for (name, value) in self.content[group].items(): - if re.match("^" + key + xdg.Locale.regex + "$", name) and name != key: - value = self.content[group][name] - del self.content[group][name] - value = self.content[group][key] - del self.content[group][key] - self.tainted = True - return value - except KeyError, e: - if debug: - if e == group: - raise NoGroupError(group, self.filename) - else: - raise NoKeyError(key, group, self.filename) - else: - return "" - - # misc - def groups(self): - return self.content.keys() - - def hasGroup(self, group): - if self.content.has_key(group): - return True - else: - return False - - def hasKey(self, key, group=None): - # set default group - if not group: - group = self.defaultGroup - - if self.content[group].has_key(key): - return True - else: - return False - - def getFileName(self): - return self.filename + defaultGroup = '' + fileExtension = '' + + filename = '' + + tainted = False + + def __init__(self, filename=None): + self.content = dict() + if filename: + self.parse(filename) + + def __cmp__(self, other): + return cmp(self.content, other.content) + + def parse(self, filename, headers): + # for performance reasons + content = self.content + + if not os.path.isfile(filename): + raise ParsingError("File not found", filename) + + # parse file + for line in file(filename,'r'): + line = line.strip() + # empty line + if not line: + continue + # comment + elif line[0] == '#': + continue + # new group + elif line[0] == '[': + currentGroup = line.lstrip("[").rstrip("]") + if debug and self.hasGroup(currentGroup): + raise DuplicateGroupError(currentGroup, filename) + else: + content[currentGroup] = {} + # key + else: + index = line.find("=") + key = line[0:index].strip() + value = line[index+1:].strip() + try: + if debug and self.hasKey(key, currentGroup): + raise DuplicateKeyError(key, currentGroup, filename) + else: + content[currentGroup][key] = value + except (IndexError, UnboundLocalError): + raise ParsingError("[%s]-Header missing" % headers[0], filename) + + self.filename = filename + self.tainted = False + + # check header + for header in headers: + if content.has_key(header): + self.defaultGroup = header + break + else: + raise ParsingError("[%s]-Header missing" % headers[0], filename) + + # start stuff to access the keys + def get(self, key, group=None, locale=False, type="string", list=False): + # set default group + if not group: + group = self.defaultGroup + + # return key (with locale) + if self.content.has_key(group) and self.content[group].has_key(key): + if locale: + value = self.content[group][self.__addLocale(key, group)] + else: + value = self.content[group][key] + else: + if debug: + if not self.content.has_key(group): + raise NoGroupError(group, self.filename) + elif not self.content[group].has_key(key): + raise NoKeyError(key, group, self.filename) + else: + value = "" + + if list == True: + values = self.getList(value) + result = [] + else: + values = [value] + + for value in values: + if type == "string" and locale == True: + value = value.decode("utf-8", "ignore") + elif type == "boolean": + value = self.__getBoolean(value) + elif type == "integer": + try: + value = int(value) + except ValueError: + value = 0 + elif type == "numeric": + try: + value = float(value) + except ValueError: + value = 0.0 + elif type == "regex": + value = re.compile(value) + elif type == "point": + value = value.split(",") + + if list == True: + result.append(value) + else: + result = value + + return result + # end stuff to access the keys + + # start subget + def getList(self, string): + if re.search(r"(?<!\\)\;", string): + list = re.split(r"(?<!\\);", string) + elif re.search(r"(?<!\\)\|", string): + list = re.split(r"(?<!\\)\|", string) + elif re.search(r"(?<!\\),", string): + list = re.split(r"(?<!\\),", string) + else: + list = [string] + if list[-1] == "": + list.pop() + return list + + def __getBoolean(self, boolean): + if boolean == 1 or boolean == "true" or boolean == "True": + return True + elif boolean == 0 or boolean == "false" or boolean == "False": + return False + return False + # end subget + + def __addLocale(self, key, group=None): + "add locale to key according the current lc_messages" + # set default group + if not group: + group = self.defaultGroup + + for lang in xdg.Locale.langs: + if self.content[group].has_key(key+'['+lang+']'): + return key+'['+lang+']' + + return key + + # start validation stuff + def validate(self, report="All"): + "validate ... report = All / Warnings / Errors" + + self.warnings = [] + self.errors = [] + + # get file extension + self.fileExtension = os.path.splitext(self.filename)[1] + + # overwrite this for own checkings + self.checkExtras() + + # check all keys + for group in self.content: + self.checkGroup(group) + for key in self.content[group]: + self.checkKey(key, self.content[group][key], group) + # check if value is empty + if self.content[group][key] == "": + self.warnings.append("Value of Key '%s' is empty" % key) + + # raise Warnings / Errors + msg = "" + + if report == "All" or report == "Warnings": + for line in self.warnings: + msg += "\n- " + line + + if report == "All" or report == "Errors": + for line in self.errors: + msg += "\n- " + line + + if msg: + raise ValidationError(msg, self.filename) + + # check if group header is valid + def checkGroup(self, group): + pass + + # check if key is valid + def checkKey(self, key, value, group): + pass + + # check random stuff + def checkValue(self, key, value, type="string", list=False): + if list == True: + values = self.getList(value) + else: + values = [value] + + for value in values: + if type == "string": + code = self.checkString(value) + elif type == "boolean": + code = self.checkBoolean(value) + elif type == "number": + code = self.checkNumber(value) + elif type == "integer": + code = self.checkInteger(value) + elif type == "regex": + code = self.checkRegex(value) + elif type == "point": + code = self.checkPoint(value) + if code == 1: + self.errors.append("'%s' is not a valid %s" % (value, type)) + elif code == 2: + self.warnings.append("Value of key '%s' is deprecated" % key) + + def checkExtras(self): + pass + + def checkBoolean(self, value): + # 1 or 0 : deprecated + if (value == "1" or value == "0"): + return 2 + # true or false: ok + elif not (value == "true" or value == "false"): + return 1 + + def checkNumber(self, value): + # float() ValueError + try: + float(value) + except: + return 1 + + def checkInteger(self, value): + # int() ValueError + try: + int(value) + except: + return 1 + + def checkPoint(self, value): + if not re.match("^[0-9]+,[0-9]+$", value): + return 1 + + def checkString(self, value): + # convert to ascii + if not value.decode("utf-8", "ignore").encode("ascii", 'ignore') == value: + return 1 + + def checkRegex(self, value): + try: + re.compile(value) + except: + return 1 + + # write support + def write(self, filename=None): + if not filename and not self.filename: + raise ParsingError("File not found", "") + + if filename: + self.filename = filename + else: + filename = self.filename + + if os.path.dirname(filename) and not os.path.isdir(os.path.dirname(filename)): + os.makedirs(os.path.dirname(filename)) + + fp = codecs.open(filename, 'w') + if self.defaultGroup: + fp.write("[%s]\n" % self.defaultGroup) + for (key, value) in self.content[self.defaultGroup].items(): + fp.write("%s=%s\n" % (key, value)) + fp.write("\n") + for (name, group) in self.content.items(): + if name != self.defaultGroup: + fp.write("[%s]\n" % name) + for (key, value) in group.items(): + fp.write("%s=%s\n" % (key, value)) + fp.write("\n") + self.tainted = False + + def set(self, key, value, group=None, locale=False): + # set default group + if not group: + group = self.defaultGroup + + if locale == True and len(xdg.Locale.langs) > 0: + key = key + "[" + xdg.Locale.langs[0] + "]" + + try: + if isinstance(value, unicode): + self.content[group][key] = value.encode("utf-8", "ignore") + else: + self.content[group][key] = value + except KeyError: + raise NoGroupError(group, self.filename) + + self.tainted = (value == self.get(key, group)) + + def addGroup(self, group): + if self.hasGroup(group): + if debug: + raise DuplicateGroupError(group, self.filename) + else: + pass + else: + self.content[group] = {} + self.tainted = True + + def removeGroup(self, group): + existed = group in self.content + if existed: + del self.content[group] + self.tainted = True + else: + if debug: + raise NoGroupError(group, self.filename) + return existed + + def removeKey(self, key, group=None, locales=True): + # set default group + if not group: + group = self.defaultGroup + + try: + if locales: + for (name, value) in self.content[group].items(): + if re.match("^" + key + xdg.Locale.regex + "$", name) and name != key: + value = self.content[group][name] + del self.content[group][name] + value = self.content[group][key] + del self.content[group][key] + self.tainted = True + return value + except KeyError, e: + if debug: + if e == group: + raise NoGroupError(group, self.filename) + else: + raise NoKeyError(key, group, self.filename) + else: + return "" + + # misc + def groups(self): + return self.content.keys() + + def hasGroup(self, group): + if self.content.has_key(group): + return True + else: + return False + + def hasKey(self, key, group=None): + # set default group + if not group: + group = self.defaultGroup + + if self.content[group].has_key(key): + return True + else: + return False + + def getFileName(self): + return self.filename diff --git a/xdg/Locale.py b/xdg/Locale.py index 88a4fa8..d30d91a 100644 --- a/xdg/Locale.py +++ b/xdg/Locale.py @@ -12,68 +12,68 @@ from locale import normalize regex = "(\[([a-zA-Z]+)(_[a-zA-Z]+)?(\.[a-zA-Z\-0-9]+)?(@[a-zA-Z]+)?\])?" def _expand_lang(locale): - locale = normalize(locale) - COMPONENT_CODESET = 1 << 0 - COMPONENT_MODIFIER = 1 << 1 - COMPONENT_TERRITORY = 1 << 2 - # split up the locale into its base components - mask = 0 - pos = locale.find('@') - if pos >= 0: - modifier = locale[pos:] - locale = locale[:pos] - mask |= COMPONENT_MODIFIER - else: - modifier = '' - pos = locale.find('.') - codeset = '' - if pos >= 0: - locale = locale[:pos] - pos = locale.find('_') - if pos >= 0: - territory = locale[pos:] - locale = locale[:pos] - mask |= COMPONENT_TERRITORY - else: - territory = '' - language = locale - ret = [] - for i in range(mask+1): - if not (i & ~mask): # if all components for this combo exist ... - val = language - if i & COMPONENT_TERRITORY: val += territory - if i & COMPONENT_CODESET: val += codeset - if i & COMPONENT_MODIFIER: val += modifier - ret.append(val) - ret.reverse() - return ret + locale = normalize(locale) + COMPONENT_CODESET = 1 << 0 + COMPONENT_MODIFIER = 1 << 1 + COMPONENT_TERRITORY = 1 << 2 + # split up the locale into its base components + mask = 0 + pos = locale.find('@') + if pos >= 0: + modifier = locale[pos:] + locale = locale[:pos] + mask |= COMPONENT_MODIFIER + else: + modifier = '' + pos = locale.find('.') + codeset = '' + if pos >= 0: + locale = locale[:pos] + pos = locale.find('_') + if pos >= 0: + territory = locale[pos:] + locale = locale[:pos] + mask |= COMPONENT_TERRITORY + else: + territory = '' + language = locale + ret = [] + for i in range(mask+1): + if not (i & ~mask): # if all components for this combo exist ... + val = language + if i & COMPONENT_TERRITORY: val += territory + if i & COMPONENT_CODESET: val += codeset + if i & COMPONENT_MODIFIER: val += modifier + ret.append(val) + ret.reverse() + return ret def expand_languages(languages=None): - # Get some reasonable defaults for arguments that were not supplied - if languages is None: - languages = [] - for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'): - val = os.environ.get(envar) - if val: - languages = val.split(':') - break - #if 'C' not in languages: - # languages.append('C') + # Get some reasonable defaults for arguments that were not supplied + if languages is None: + languages = [] + for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'): + val = os.environ.get(envar) + if val: + languages = val.split(':') + break + #if 'C' not in languages: + # languages.append('C') - # now normalize and expand the languages - nelangs = [] - for lang in languages: - for nelang in _expand_lang(lang): - if nelang not in nelangs: - nelangs.append(nelang) - return nelangs + # now normalize and expand the languages + nelangs = [] + for lang in languages: + for nelang in _expand_lang(lang): + if nelang not in nelangs: + nelangs.append(nelang) + return nelangs def update(language=None): - global langs - if language: - langs = expand_languages([language]) - else: - langs = expand_languages() + global langs + if language: + langs = expand_languages([language]) + else: + langs = expand_languages() langs = [] update() diff --git a/xdg/Menu.py b/xdg/Menu.py index 03a933a..fd62f12 100644 --- a/xdg/Menu.py +++ b/xdg/Menu.py @@ -16,1051 +16,1051 @@ import xdg.Config ELEMENT_NODE = xml.dom.Node.ELEMENT_NODE class Menu: - def __init__(self): - # Public stuff - self.Name = "" - self.Directory = None - self.Entries = [] - self.Doc = "" - self.Filename = "" - self.Depth = 0 - self.Parent = None - self.NotInXml = False - - # Can be one of Deleted/NoDisplay/Hidden/Empty/NotShowIn or True - self.Show = True - self.Visible = 0 - - # Private stuff, only needed for parsing - self.AppDirs = [] - self.DefaultLayout = None - self.Deleted = "notset" - self.Directories = [] - self.DirectoryDirs = [] - self.Layout = None - self.MenuEntries = [] - self.Moves = [] - self.OnlyUnallocated = "notset" - self.Rules = [] - self.Submenus = [] - - def __str__(self): - return self.Name - - def __add__(self, other): - for dir in other.AppDirs: - self.AppDirs.append(dir) - - for dir in other.DirectoryDirs: - self.DirectoryDirs.append(dir) - - for directory in other.Directories: - self.Directories.append(directory) - - if other.Deleted != "notset": - self.Deleted = other.Deleted - - if other.OnlyUnallocated != "notset": - self.OnlyUnallocated = other.OnlyUnallocated - - if other.Layout: - self.Layout = other.Layout - - if other.DefaultLayout: - self.DefaultLayout = other.DefaultLayout - - for rule in other.Rules: - self.Rules.append(rule) - - for move in other.Moves: - self.Moves.append(move) - - for submenu in other.Submenus: - self.addSubmenu(submenu) - - return self - - # FIXME: Performance: cache getName() - def __cmp__(self, other): - return locale.strcoll(self.getName(), other.getName()) - - def __eq__(self, other): - if self.Name == str(other): - return True - else: - return False - - """ PUBLIC STUFF """ - def getEntries(self, hidden=False): - for entry in self.Entries: - if hidden == True: - yield entry - elif entry.Show == True: - yield entry - - # FIXME: Add searchEntry/seaqrchMenu function - # search for name/comment/genericname/desktopfileide - # return multiple items - - def getMenuEntry(self, desktopfileid, deep = False): - for menuentry in self.MenuEntries: - if menuentry.DesktopFileID == desktopfileid: - return menuentry - if deep == True: - for submenu in self.Submenus: - submenu.getMenuEntry(desktopfileid, deep) - - def getMenu(self, path): - array = path.split("/", 1) - for submenu in self.Submenus: - if submenu.Name == array[0]: - if len(array) > 1: - return submenu.getMenu(array[1]) - else: - return submenu - - def getPath(self, org=False, toplevel=False): - parent = self - names=[] - while 1: - if org: - names.append(parent.Name) - else: - names.append(parent.getName()) - if parent.Depth > 0: - parent = parent.Parent - else: - break - names.reverse() - path = "" - if toplevel == False: - names.pop(0) - for name in names: - path = os.path.join(path, name) - return path - - def getName(self): - try: - return self.Directory.DesktopEntry.getName() - except AttributeError: - return self.Name - - def getGenericName(self): - try: - return self.Directory.DesktopEntry.getGenericName() - except AttributeError: - return "" - - def getComment(self): - try: - return self.Directory.DesktopEntry.getComment() - except AttributeError: - return "" - - def getIcon(self): - try: - return self.Directory.DesktopEntry.getIcon() - except AttributeError: - return "" - - """ PRIVATE STUFF """ - def addSubmenu(self, newmenu): - for submenu in self.Submenus: - if submenu == newmenu: - submenu += newmenu - break - else: - self.Submenus.append(newmenu) - newmenu.Parent = self - newmenu.Depth = self.Depth + 1 + def __init__(self): + # Public stuff + self.Name = "" + self.Directory = None + self.Entries = [] + self.Doc = "" + self.Filename = "" + self.Depth = 0 + self.Parent = None + self.NotInXml = False + + # Can be one of Deleted/NoDisplay/Hidden/Empty/NotShowIn or True + self.Show = True + self.Visible = 0 + + # Private stuff, only needed for parsing + self.AppDirs = [] + self.DefaultLayout = None + self.Deleted = "notset" + self.Directories = [] + self.DirectoryDirs = [] + self.Layout = None + self.MenuEntries = [] + self.Moves = [] + self.OnlyUnallocated = "notset" + self.Rules = [] + self.Submenus = [] + + def __str__(self): + return self.Name + + def __add__(self, other): + for dir in other.AppDirs: + self.AppDirs.append(dir) + + for dir in other.DirectoryDirs: + self.DirectoryDirs.append(dir) + + for directory in other.Directories: + self.Directories.append(directory) + + if other.Deleted != "notset": + self.Deleted = other.Deleted + + if other.OnlyUnallocated != "notset": + self.OnlyUnallocated = other.OnlyUnallocated + + if other.Layout: + self.Layout = other.Layout + + if other.DefaultLayout: + self.DefaultLayout = other.DefaultLayout + + for rule in other.Rules: + self.Rules.append(rule) + + for move in other.Moves: + self.Moves.append(move) + + for submenu in other.Submenus: + self.addSubmenu(submenu) + + return self + + # FIXME: Performance: cache getName() + def __cmp__(self, other): + return locale.strcoll(self.getName(), other.getName()) + + def __eq__(self, other): + if self.Name == str(other): + return True + else: + return False + + """ PUBLIC STUFF """ + def getEntries(self, hidden=False): + for entry in self.Entries: + if hidden == True: + yield entry + elif entry.Show == True: + yield entry + + # FIXME: Add searchEntry/seaqrchMenu function + # search for name/comment/genericname/desktopfileide + # return multiple items + + def getMenuEntry(self, desktopfileid, deep = False): + for menuentry in self.MenuEntries: + if menuentry.DesktopFileID == desktopfileid: + return menuentry + if deep == True: + for submenu in self.Submenus: + submenu.getMenuEntry(desktopfileid, deep) + + def getMenu(self, path): + array = path.split("/", 1) + for submenu in self.Submenus: + if submenu.Name == array[0]: + if len(array) > 1: + return submenu.getMenu(array[1]) + else: + return submenu + + def getPath(self, org=False, toplevel=False): + parent = self + names=[] + while 1: + if org: + names.append(parent.Name) + else: + names.append(parent.getName()) + if parent.Depth > 0: + parent = parent.Parent + else: + break + names.reverse() + path = "" + if toplevel == False: + names.pop(0) + for name in names: + path = os.path.join(path, name) + return path + + def getName(self): + try: + return self.Directory.DesktopEntry.getName() + except AttributeError: + return self.Name + + def getGenericName(self): + try: + return self.Directory.DesktopEntry.getGenericName() + except AttributeError: + return "" + + def getComment(self): + try: + return self.Directory.DesktopEntry.getComment() + except AttributeError: + return "" + + def getIcon(self): + try: + return self.Directory.DesktopEntry.getIcon() + except AttributeError: + return "" + + """ PRIVATE STUFF """ + def addSubmenu(self, newmenu): + for submenu in self.Submenus: + if submenu == newmenu: + submenu += newmenu + break + else: + self.Submenus.append(newmenu) + newmenu.Parent = self + newmenu.Depth = self.Depth + 1 class Move: - "A move operation" - def __init__(self, node=None): - if node: - self.parseNode(node) - else: - self.Old = "" - self.New = "" - - def __cmp__(self, other): - return cmp(self.Old, other.Old) - - def parseNode(self, node): - for child in node.childNodes: - if child.nodeType == ELEMENT_NODE: - if child.tagName == "Old": - try: - self.parseOld(child.childNodes[0].nodeValue) - except IndexError: - raise ValidationError('Old cannot be empty', '??') - elif child.tagName == "New": - try: - self.parseNew(child.childNodes[0].nodeValue) - except IndexError: - raise ValidationError('New cannot be empty', '??') - - def parseOld(self, value): - self.Old = value - def parseNew(self, value): - self.New = value + "A move operation" + def __init__(self, node=None): + if node: + self.parseNode(node) + else: + self.Old = "" + self.New = "" + + def __cmp__(self, other): + return cmp(self.Old, other.Old) + + def parseNode(self, node): + for child in node.childNodes: + if child.nodeType == ELEMENT_NODE: + if child.tagName == "Old": + try: + self.parseOld(child.childNodes[0].nodeValue) + except IndexError: + raise ValidationError('Old cannot be empty', '??') + elif child.tagName == "New": + try: + self.parseNew(child.childNodes[0].nodeValue) + except IndexError: + raise ValidationError('New cannot be empty', '??') + + def parseOld(self, value): + self.Old = value + def parseNew(self, value): + self.New = value class Layout: - "Menu Layout class" - def __init__(self, node=None): - self.order = [] - if node: - self.show_empty = node.getAttribute("show_empty") or "false" - self.inline = node.getAttribute("inline") or "false" - self.inline_limit = node.getAttribute("inline_limit") or 4 - self.inline_header = node.getAttribute("inline_header") or "true" - self.inline_alias = node.getAttribute("inline_alias") or "false" - self.inline_limit = int(self.inline_limit) - self.parseNode(node) - else: - self.show_empty = "false" - self.inline = "false" - self.inline_limit = 4 - self.inline_header = "true" - self.inline_alias = "false" - self.order.append(["Merge", "menus"]) - self.order.append(["Merge", "files"]) - - def parseNode(self, node): - for child in node.childNodes: - if child.nodeType == ELEMENT_NODE: - if child.tagName == "Menuname": - try: - self.parseMenuname( - child.childNodes[0].nodeValue, - child.getAttribute("show_empty") or "false", - child.getAttribute("inline") or "false", - child.getAttribute("inline_limit") or 4, - child.getAttribute("inline_header") or "true", - child.getAttribute("inline_alias") or "false" ) - except IndexError: - raise ValidationError('Menuname cannot be empty', "") - elif child.tagName == "Separator": - self.parseSeparator() - elif child.tagName == "Filename": - try: - self.parseFilename(child.childNodes[0].nodeValue) - except IndexError: - raise ValidationError('Filename cannot be empty', "") - elif child.tagName == "Merge": - self.parseMerge(child.getAttribute("type") or "all") - - def parseMenuname(self, value, empty="false", inline="false", inline_limit=4, inline_header="true", inline_alias="false"): - self.order.append(["Menuname", value, empty, inline, inline_limit, inline_header, inline_alias]) - self.order[-1][4] = int(self.order[-1][4]) - - def parseSeparator(self): - self.order.append(["Separator"]) - - def parseFilename(self, value): - self.order.append(["Filename", value]) - - def parseMerge(self, type="all"): - self.order.append(["Merge", type]) + "Menu Layout class" + def __init__(self, node=None): + self.order = [] + if node: + self.show_empty = node.getAttribute("show_empty") or "false" + self.inline = node.getAttribute("inline") or "false" + self.inline_limit = node.getAttribute("inline_limit") or 4 + self.inline_header = node.getAttribute("inline_header") or "true" + self.inline_alias = node.getAttribute("inline_alias") or "false" + self.inline_limit = int(self.inline_limit) + self.parseNode(node) + else: + self.show_empty = "false" + self.inline = "false" + self.inline_limit = 4 + self.inline_header = "true" + self.inline_alias = "false" + self.order.append(["Merge", "menus"]) + self.order.append(["Merge", "files"]) + + def parseNode(self, node): + for child in node.childNodes: + if child.nodeType == ELEMENT_NODE: + if child.tagName == "Menuname": + try: + self.parseMenuname( + child.childNodes[0].nodeValue, + child.getAttribute("show_empty") or "false", + child.getAttribute("inline") or "false", + child.getAttribute("inline_limit") or 4, + child.getAttribute("inline_header") or "true", + child.getAttribute("inline_alias") or "false" ) + except IndexError: + raise ValidationError('Menuname cannot be empty', "") + elif child.tagName == "Separator": + self.parseSeparator() + elif child.tagName == "Filename": + try: + self.parseFilename(child.childNodes[0].nodeValue) + except IndexError: + raise ValidationError('Filename cannot be empty', "") + elif child.tagName == "Merge": + self.parseMerge(child.getAttribute("type") or "all") + + def parseMenuname(self, value, empty="false", inline="false", inline_limit=4, inline_header="true", inline_alias="false"): + self.order.append(["Menuname", value, empty, inline, inline_limit, inline_header, inline_alias]) + self.order[-1][4] = int(self.order[-1][4]) + + def parseSeparator(self): + self.order.append(["Separator"]) + + def parseFilename(self, value): + self.order.append(["Filename", value]) + + def parseMerge(self, type="all"): + self.order.append(["Merge", type]) class Rule: - "Inlcude / Exclude Rules Class" - def __init__(self, type, node=None): - # Type is Include or Exclude - self.Type = type - # Rule is a python expression - self.Rule = "" - - # Private attributes, only needed for parsing - self.Depth = 0 - self.Expr = [ "or" ] - self.New = True - - # Begin parsing - if node: - self.parseNode(node) - self.compile() - - def __str__(self): - return self.Rule - - def compile(self): - exec(""" + "Inlcude / Exclude Rules Class" + def __init__(self, type, node=None): + # Type is Include or Exclude + self.Type = type + # Rule is a python expression + self.Rule = "" + + # Private attributes, only needed for parsing + self.Depth = 0 + self.Expr = [ "or" ] + self.New = True + + # Begin parsing + if node: + self.parseNode(node) + self.compile() + + def __str__(self): + return self.Rule + + def compile(self): + exec(""" def do(menuentries, type, run): for menuentry in menuentries: - if run == 2 and ( menuentry.MatchedInclude == True \ - or menuentry.Allocated == True ): - continue - elif %s: - if type == "Include": - menuentry.Add = True - menuentry.MatchedInclude = True - else: - menuentry.Add = False + if run == 2 and ( menuentry.MatchedInclude == True \ + or menuentry.Allocated == True ): + continue + elif %s: + if type == "Include": + menuentry.Add = True + menuentry.MatchedInclude = True + else: + menuentry.Add = False return menuentries """ % self.Rule) in self.__dict__ - def parseNode(self, node): - for child in node.childNodes: - if child.nodeType == ELEMENT_NODE: - if child.tagName == 'Filename': - try: - self.parseFilename(child.childNodes[0].nodeValue) - except IndexError: - raise ValidationError('Filename cannot be empty', "???") - elif child.tagName == 'Category': - try: - self.parseCategory(child.childNodes[0].nodeValue) - except IndexError: - raise ValidationError('Category cannot be empty', "???") - elif child.tagName == 'All': - self.parseAll() - elif child.tagName == 'And': - self.parseAnd(child) - elif child.tagName == 'Or': - self.parseOr(child) - elif child.tagName == 'Not': - self.parseNot(child) - - def parseNew(self, set=True): - if not self.New: - self.Rule += " " + self.Expr[self.Depth] + " " - if not set: - self.New = True - elif set: - self.New = False - - def parseFilename(self, value): - self.parseNew() - self.Rule += "menuentry.DesktopFileID == '%s'" % value.strip().replace("\\", r"\\").replace("'", r"\'") - - def parseCategory(self, value): - self.parseNew() - self.Rule += "'%s' in menuentry.Categories" % value.strip() - - def parseAll(self): - self.parseNew() - self.Rule += "True" - - def parseAnd(self, node): - self.parseNew(False) - self.Rule += "(" - self.Depth += 1 - self.Expr.append("and") - self.parseNode(node) - self.Depth -= 1 - self.Expr.pop() - self.Rule += ")" - - def parseOr(self, node): - self.parseNew(False) - self.Rule += "(" - self.Depth += 1 - self.Expr.append("or") - self.parseNode(node) - self.Depth -= 1 - self.Expr.pop() - self.Rule += ")" - - def parseNot(self, node): - self.parseNew(False) - self.Rule += "not (" - self.Depth += 1 - self.Expr.append("or") - self.parseNode(node) - self.Depth -= 1 - self.Expr.pop() - self.Rule += ")" + def parseNode(self, node): + for child in node.childNodes: + if child.nodeType == ELEMENT_NODE: + if child.tagName == 'Filename': + try: + self.parseFilename(child.childNodes[0].nodeValue) + except IndexError: + raise ValidationError('Filename cannot be empty', "???") + elif child.tagName == 'Category': + try: + self.parseCategory(child.childNodes[0].nodeValue) + except IndexError: + raise ValidationError('Category cannot be empty', "???") + elif child.tagName == 'All': + self.parseAll() + elif child.tagName == 'And': + self.parseAnd(child) + elif child.tagName == 'Or': + self.parseOr(child) + elif child.tagName == 'Not': + self.parseNot(child) + + def parseNew(self, set=True): + if not self.New: + self.Rule += " " + self.Expr[self.Depth] + " " + if not set: + self.New = True + elif set: + self.New = False + + def parseFilename(self, value): + self.parseNew() + self.Rule += "menuentry.DesktopFileID == '%s'" % value.strip().replace("\\", r"\\").replace("'", r"\'") + + def parseCategory(self, value): + self.parseNew() + self.Rule += "'%s' in menuentry.Categories" % value.strip() + + def parseAll(self): + self.parseNew() + self.Rule += "True" + + def parseAnd(self, node): + self.parseNew(False) + self.Rule += "(" + self.Depth += 1 + self.Expr.append("and") + self.parseNode(node) + self.Depth -= 1 + self.Expr.pop() + self.Rule += ")" + + def parseOr(self, node): + self.parseNew(False) + self.Rule += "(" + self.Depth += 1 + self.Expr.append("or") + self.parseNode(node) + self.Depth -= 1 + self.Expr.pop() + self.Rule += ")" + + def parseNot(self, node): + self.parseNew(False) + self.Rule += "not (" + self.Depth += 1 + self.Expr.append("or") + self.parseNode(node) + self.Depth -= 1 + self.Expr.pop() + self.Rule += ")" class MenuEntry: - "Wrapper for 'Menu Style' Desktop Entries" - def __init__(self, filename, dir="", prefix=""): - # Create entry - self.DesktopEntry = DesktopEntry(os.path.join(dir,filename)) - self.setAttributes(filename, dir, prefix) - - # Can be one of Deleted/Hidden/Empty/NotShowIn/NoExec or True - self.Show = True - - # Semi-Private - self.Original = None - self.Parents = [] - - # Private Stuff - self.Allocated = False - self.Add = False - self.MatchedInclude = False - - # Caching - self.Categories = self.DesktopEntry.getCategories() - - def save(self): - if self.DesktopEntry.tainted == True: - self.DesktopEntry.write() - - def getDir(self): - return self.DesktopEntry.filename.replace(self.Filename, '') - - def getType(self): - # Can be one of System/User/Both - if xdg.Config.root_mode == False: - if self.Original: - return "Both" - elif xdg_data_dirs[0] in self.DesktopEntry.filename: - return "User" - else: - return "System" - else: - return "User" - - def setAttributes(self, filename, dir="", prefix=""): - self.Filename = filename - self.Prefix = prefix - self.DesktopFileID = os.path.join(prefix,filename).replace("/", "-") - - if not os.path.isabs(self.DesktopEntry.filename): - self.__setFilename() - - def updateAttributes(self): - if self.getType() == "System": - self.Original = MenuEntry(self.Filename, self.getDir(), self.Prefix) - self.__setFilename() - - def __setFilename(self): - if xdg.Config.root_mode == False: - path = xdg_data_dirs[0] - else: - path= xdg_data_dirs[1] - - if self.DesktopEntry.getType() == "Application": - dir = os.path.join(path, "applications") - else: - dir = os.path.join(path, "desktop-directories") - - self.DesktopEntry.filename = os.path.join(dir, self.Filename) - - def __cmp__(self, other): - return locale.strcoll(self.DesktopEntry.getName(), other.DesktopEntry.getName()) - - def __eq__(self,other): - if self.DesktopFileID == str(other): - return True - else: - return False - - def __repr__(self): - return self.DesktopFileID + "Wrapper for 'Menu Style' Desktop Entries" + def __init__(self, filename, dir="", prefix=""): + # Create entry + self.DesktopEntry = DesktopEntry(os.path.join(dir,filename)) + self.setAttributes(filename, dir, prefix) + + # Can be one of Deleted/Hidden/Empty/NotShowIn/NoExec or True + self.Show = True + + # Semi-Private + self.Original = None + self.Parents = [] + + # Private Stuff + self.Allocated = False + self.Add = False + self.MatchedInclude = False + + # Caching + self.Categories = self.DesktopEntry.getCategories() + + def save(self): + if self.DesktopEntry.tainted == True: + self.DesktopEntry.write() + + def getDir(self): + return self.DesktopEntry.filename.replace(self.Filename, '') + + def getType(self): + # Can be one of System/User/Both + if xdg.Config.root_mode == False: + if self.Original: + return "Both" + elif xdg_data_dirs[0] in self.DesktopEntry.filename: + return "User" + else: + return "System" + else: + return "User" + + def setAttributes(self, filename, dir="", prefix=""): + self.Filename = filename + self.Prefix = prefix + self.DesktopFileID = os.path.join(prefix,filename).replace("/", "-") + + if not os.path.isabs(self.DesktopEntry.filename): + self.__setFilename() + + def updateAttributes(self): + if self.getType() == "System": + self.Original = MenuEntry(self.Filename, self.getDir(), self.Prefix) + self.__setFilename() + + def __setFilename(self): + if xdg.Config.root_mode == False: + path = xdg_data_dirs[0] + else: + path= xdg_data_dirs[1] + + if self.DesktopEntry.getType() == "Application": + dir = os.path.join(path, "applications") + else: + dir = os.path.join(path, "desktop-directories") + + self.DesktopEntry.filename = os.path.join(dir, self.Filename) + + def __cmp__(self, other): + return locale.strcoll(self.DesktopEntry.getName(), other.DesktopEntry.getName()) + + def __eq__(self,other): + if self.DesktopFileID == str(other): + return True + else: + return False + + def __repr__(self): + return self.DesktopFileID class Separator: - "Just a dummy class for Separators" - def __init__(self, parent): - self.Parent = parent - self.Show = True + "Just a dummy class for Separators" + def __init__(self, parent): + self.Parent = parent + self.Show = True class Header: - "Class for Inline Headers" - def __init__(self, name, generic_name, comment): - self.Name = name - self.GenericName = generic_name - self.Comment = comment + "Class for Inline Headers" + def __init__(self, name, generic_name, comment): + self.Name = name + self.GenericName = generic_name + self.Comment = comment - def __str__(self): - return self.Name + def __str__(self): + return self.Name tmp = {} def __getFileName(filename): - dirs = xdg_config_dirs[:] - if xdg.Config.root_mode == True: - dirs.pop(0) + dirs = xdg_config_dirs[:] + if xdg.Config.root_mode == True: + dirs.pop(0) - for dir in dirs: - menuname = os.path.join (dir, "menus" , filename) - if os.path.isdir(dir) and os.path.isfile(menuname): - return menuname + for dir in dirs: + menuname = os.path.join (dir, "menus" , filename) + if os.path.isdir(dir) and os.path.isfile(menuname): + return menuname def parse(filename=None): - # conver to absolute path - if filename and not os.path.isabs(filename): - filename = __getFileName(filename) + # conver to absolute path + if filename and not os.path.isabs(filename): + filename = __getFileName(filename) - # use default if no filename given - if not filename: - filename = __getFileName("applications.menu") + # use default if no filename given + if not filename: + filename = __getFileName("applications.menu") - if not filename: - raise ParsingError('File not found', "/etc/xdg/menus/applications.menu") + if not filename: + raise ParsingError('File not found', "/etc/xdg/menus/applications.menu") - # check if it is a .menu file - if not os.path.splitext(filename)[1] == ".menu": - raise ParsingError('Not a .menu file', filename) + # check if it is a .menu file + if not os.path.splitext(filename)[1] == ".menu": + raise ParsingError('Not a .menu file', filename) - # create xml parser - try: - doc = xml.dom.minidom.parse(filename) - except xml.parsers.expat.ExpatError: - raise ParsingError('Not a valid .menu file', filename) + # create xml parser + try: + doc = xml.dom.minidom.parse(filename) + except xml.parsers.expat.ExpatError: + raise ParsingError('Not a valid .menu file', filename) - # parse menufile - tmp["Root"] = "" - tmp["mergeFiles"] = [] - tmp["DirectoryDirs"] = [] - tmp["cache"] = MenuEntryCache() + # parse menufile + tmp["Root"] = "" + tmp["mergeFiles"] = [] + tmp["DirectoryDirs"] = [] + tmp["cache"] = MenuEntryCache() - __parse(doc, filename, tmp["Root"]) - __parsemove(tmp["Root"]) - __postparse(tmp["Root"]) + __parse(doc, filename, tmp["Root"]) + __parsemove(tmp["Root"]) + __postparse(tmp["Root"]) - tmp["Root"].Doc = doc - tmp["Root"].Filename = filename + tmp["Root"].Doc = doc + tmp["Root"].Filename = filename - # generate the menu - __genmenuNotOnlyAllocated(tmp["Root"]) - __genmenuOnlyAllocated(tmp["Root"]) + # generate the menu + __genmenuNotOnlyAllocated(tmp["Root"]) + __genmenuOnlyAllocated(tmp["Root"]) - # and finally sort - sort(tmp["Root"]) + # and finally sort + sort(tmp["Root"]) - return tmp["Root"] + return tmp["Root"] def __parse(node, filename, parent=None): - for child in node.childNodes: - if child.nodeType == ELEMENT_NODE: - if child.tagName == 'Menu': - __parseMenu(child, filename, parent) - elif child.tagName == 'AppDir': - try: - __parseAppDir(child.childNodes[0].nodeValue, filename, parent) - except IndexError: - raise ValidationError('AppDir cannot be empty', filename) - elif child.tagName == 'DefaultAppDirs': - __parseDefaultAppDir(filename, parent) - elif child.tagName == 'DirectoryDir': - try: - __parseDirectoryDir(child.childNodes[0].nodeValue, filename, parent) - except IndexError: - raise ValidationError('DirectoryDir cannot be empty', filename) - elif child.tagName == 'DefaultDirectoryDirs': - __parseDefaultDirectoryDir(filename, parent) - elif child.tagName == 'Name' : - try: - parent.Name = child.childNodes[0].nodeValue - except IndexError: - raise ValidationError('Name cannot be empty', filename) - elif child.tagName == 'Directory' : - try: - parent.Directories.append(child.childNodes[0].nodeValue) - except IndexError: - raise ValidationError('Directory cannot be empty', filename) - elif child.tagName == 'OnlyUnallocated': - parent.OnlyUnallocated = True - elif child.tagName == 'NotOnlyUnallocated': - parent.OnlyUnallocated = False - elif child.tagName == 'Deleted': - parent.Deleted = True - elif child.tagName == 'NotDeleted': - parent.Deleted = False - elif child.tagName == 'Include' or child.tagName == 'Exclude': - parent.Rules.append(Rule(child.tagName, child)) - elif child.tagName == 'MergeFile': - try: - if child.getAttribute("type") == "parent": - __parseMergeFile("applications.menu", child, filename, parent) - else: - __parseMergeFile(child.childNodes[0].nodeValue, child, filename, parent) - except IndexError: - raise ValidationError('MergeFile cannot be empty', filename) - elif child.tagName == 'MergeDir': - try: - __parseMergeDir(child.childNodes[0].nodeValue, child, filename, parent) - except IndexError: - raise ValidationError('MergeDir cannot be empty', filename) - elif child.tagName == 'DefaultMergeDirs': - __parseDefaultMergeDirs(child, filename, parent) - elif child.tagName == 'Move': - parent.Moves.append(Move(child)) - elif child.tagName == 'Layout': - if len(child.childNodes) > 1: - parent.Layout = Layout(child) - elif child.tagName == 'DefaultLayout': - if len(child.childNodes) > 1: - parent.DefaultLayout = Layout(child) - elif child.tagName == 'LegacyDir': - try: - __parseLegacyDir(child.childNodes[0].nodeValue, child.getAttribute("prefix"), filename, parent) - except IndexError: - raise ValidationError('LegacyDir cannot be empty', filename) - elif child.tagName == 'KDELegacyDirs': - __parseKDELegacyDirs(filename, parent) + for child in node.childNodes: + if child.nodeType == ELEMENT_NODE: + if child.tagName == 'Menu': + __parseMenu(child, filename, parent) + elif child.tagName == 'AppDir': + try: + __parseAppDir(child.childNodes[0].nodeValue, filename, parent) + except IndexError: + raise ValidationError('AppDir cannot be empty', filename) + elif child.tagName == 'DefaultAppDirs': + __parseDefaultAppDir(filename, parent) + elif child.tagName == 'DirectoryDir': + try: + __parseDirectoryDir(child.childNodes[0].nodeValue, filename, parent) + except IndexError: + raise ValidationError('DirectoryDir cannot be empty', filename) + elif child.tagName == 'DefaultDirectoryDirs': + __parseDefaultDirectoryDir(filename, parent) + elif child.tagName == 'Name' : + try: + parent.Name = child.childNodes[0].nodeValue + except IndexError: + raise ValidationError('Name cannot be empty', filename) + elif child.tagName == 'Directory' : + try: + parent.Directories.append(child.childNodes[0].nodeValue) + except IndexError: + raise ValidationError('Directory cannot be empty', filename) + elif child.tagName == 'OnlyUnallocated': + parent.OnlyUnallocated = True + elif child.tagName == 'NotOnlyUnallocated': + parent.OnlyUnallocated = False + elif child.tagName == 'Deleted': + parent.Deleted = True + elif child.tagName == 'NotDeleted': + parent.Deleted = False + elif child.tagName == 'Include' or child.tagName == 'Exclude': + parent.Rules.append(Rule(child.tagName, child)) + elif child.tagName == 'MergeFile': + try: + if child.getAttribute("type") == "parent": + __parseMergeFile("applications.menu", child, filename, parent) + else: + __parseMergeFile(child.childNodes[0].nodeValue, child, filename, parent) + except IndexError: + raise ValidationError('MergeFile cannot be empty', filename) + elif child.tagName == 'MergeDir': + try: + __parseMergeDir(child.childNodes[0].nodeValue, child, filename, parent) + except IndexError: + raise ValidationError('MergeDir cannot be empty', filename) + elif child.tagName == 'DefaultMergeDirs': + __parseDefaultMergeDirs(child, filename, parent) + elif child.tagName == 'Move': + parent.Moves.append(Move(child)) + elif child.tagName == 'Layout': + if len(child.childNodes) > 1: + parent.Layout = Layout(child) + elif child.tagName == 'DefaultLayout': + if len(child.childNodes) > 1: + parent.DefaultLayout = Layout(child) + elif child.tagName == 'LegacyDir': + try: + __parseLegacyDir(child.childNodes[0].nodeValue, child.getAttribute("prefix"), filename, parent) + except IndexError: + raise ValidationError('LegacyDir cannot be empty', filename) + elif child.tagName == 'KDELegacyDirs': + __parseKDELegacyDirs(filename, parent) def __parsemove(menu): - for submenu in menu.Submenus: - __parsemove(submenu) - - # parse move operations - for move in menu.Moves: - move_from_menu = menu.getMenu(move.Old) - if move_from_menu: - move_to_menu = menu.getMenu(move.New) - - menus = move.New.split("/") - oldparent = None - while len(menus) > 0: - if not oldparent: - oldparent = menu - newmenu = oldparent.getMenu(menus[0]) - if not newmenu: - newmenu = Menu() - newmenu.Name = menus[0] - if len(menus) > 1: - newmenu.NotInXml = True - oldparent.addSubmenu(newmenu) - oldparent = newmenu - menus.pop(0) - - newmenu += move_from_menu - move_from_menu.Parent.Submenus.remove(move_from_menu) + for submenu in menu.Submenus: + __parsemove(submenu) + + # parse move operations + for move in menu.Moves: + move_from_menu = menu.getMenu(move.Old) + if move_from_menu: + move_to_menu = menu.getMenu(move.New) + + menus = move.New.split("/") + oldparent = None + while len(menus) > 0: + if not oldparent: + oldparent = menu + newmenu = oldparent.getMenu(menus[0]) + if not newmenu: + newmenu = Menu() + newmenu.Name = menus[0] + if len(menus) > 1: + newmenu.NotInXml = True + oldparent.addSubmenu(newmenu) + oldparent = newmenu + menus.pop(0) + + newmenu += move_from_menu + move_from_menu.Parent.Submenus.remove(move_from_menu) def __postparse(menu): - # unallocated / deleted - if menu.Deleted == "notset": - menu.Deleted = False - if menu.OnlyUnallocated == "notset": - menu.OnlyUnallocated = False - - # Layout Tags - if not menu.Layout or not menu.DefaultLayout: - if menu.DefaultLayout: - menu.Layout = menu.DefaultLayout - elif menu.Layout: - if menu.Depth > 0: - menu.DefaultLayout = menu.Parent.DefaultLayout - else: - menu.DefaultLayout = Layout() - else: - if menu.Depth > 0: - menu.Layout = menu.Parent.DefaultLayout - menu.DefaultLayout = menu.Parent.DefaultLayout - else: - menu.Layout = Layout() - menu.DefaultLayout = Layout() - - # add parent's app/directory dirs - if menu.Depth > 0: - menu.AppDirs = menu.Parent.AppDirs + menu.AppDirs - menu.DirectoryDirs = menu.Parent.DirectoryDirs + menu.DirectoryDirs - - # remove duplicates - menu.Directories = __removeDuplicates(menu.Directories) - menu.DirectoryDirs = __removeDuplicates(menu.DirectoryDirs) - menu.AppDirs = __removeDuplicates(menu.AppDirs) - - # go recursive through all menus - for submenu in menu.Submenus: - __postparse(submenu) - - # reverse so handling is easier - menu.Directories.reverse() - menu.DirectoryDirs.reverse() - menu.AppDirs.reverse() - - # get the valid .directory file out of the list - for directory in menu.Directories: - for dir in menu.DirectoryDirs: - if os.path.isfile(os.path.join(dir, directory)): - menuentry = MenuEntry(directory, dir) - if not menu.Directory: - menu.Directory = menuentry - elif menuentry.getType() == "System": - if menu.Directory.getType() == "User": - menu.Directory.Original = menuentry - if menu.Directory: - break + # unallocated / deleted + if menu.Deleted == "notset": + menu.Deleted = False + if menu.OnlyUnallocated == "notset": + menu.OnlyUnallocated = False + + # Layout Tags + if not menu.Layout or not menu.DefaultLayout: + if menu.DefaultLayout: + menu.Layout = menu.DefaultLayout + elif menu.Layout: + if menu.Depth > 0: + menu.DefaultLayout = menu.Parent.DefaultLayout + else: + menu.DefaultLayout = Layout() + else: + if menu.Depth > 0: + menu.Layout = menu.Parent.DefaultLayout + menu.DefaultLayout = menu.Parent.DefaultLayout + else: + menu.Layout = Layout() + menu.DefaultLayout = Layout() + + # add parent's app/directory dirs + if menu.Depth > 0: + menu.AppDirs = menu.Parent.AppDirs + menu.AppDirs + menu.DirectoryDirs = menu.Parent.DirectoryDirs + menu.DirectoryDirs + + # remove duplicates + menu.Directories = __removeDuplicates(menu.Directories) + menu.DirectoryDirs = __removeDuplicates(menu.DirectoryDirs) + menu.AppDirs = __removeDuplicates(menu.AppDirs) + + # go recursive through all menus + for submenu in menu.Submenus: + __postparse(submenu) + + # reverse so handling is easier + menu.Directories.reverse() + menu.DirectoryDirs.reverse() + menu.AppDirs.reverse() + + # get the valid .directory file out of the list + for directory in menu.Directories: + for dir in menu.DirectoryDirs: + if os.path.isfile(os.path.join(dir, directory)): + menuentry = MenuEntry(directory, dir) + if not menu.Directory: + menu.Directory = menuentry + elif menuentry.getType() == "System": + if menu.Directory.getType() == "User": + menu.Directory.Original = menuentry + if menu.Directory: + break # Menu parsing stuff def __parseMenu(child, filename, parent): - m = Menu() - __parse(child, filename, m) - if parent: - parent.addSubmenu(m) - else: - tmp["Root"] = m + m = Menu() + __parse(child, filename, m) + if parent: + parent.addSubmenu(m) + else: + tmp["Root"] = m # helper function def __check(value, filename, type): - path = os.path.dirname(filename) + path = os.path.dirname(filename) - if not os.path.isabs(value): - value = os.path.join(path, value) + if not os.path.isabs(value): + value = os.path.join(path, value) - value = os.path.abspath(value) + value = os.path.abspath(value) - if type == "dir" and os.path.exists(value) and os.path.isdir(value): - return value - elif type == "file" and os.path.exists(value) and os.path.isfile(value): - return value - else: - return False + if type == "dir" and os.path.exists(value) and os.path.isdir(value): + return value + elif type == "file" and os.path.exists(value) and os.path.isfile(value): + return value + else: + return False # App/Directory Dir Stuff def __parseAppDir(value, filename, parent): - value = __check(value, filename, "dir") - if value: - parent.AppDirs.append(value) + value = __check(value, filename, "dir") + if value: + parent.AppDirs.append(value) def __parseDefaultAppDir(filename, parent): - for dir in reversed(xdg_data_dirs): - __parseAppDir(os.path.join(dir, "applications"), filename, parent) + for dir in reversed(xdg_data_dirs): + __parseAppDir(os.path.join(dir, "applications"), filename, parent) def __parseDirectoryDir(value, filename, parent): - value = __check(value, filename, "dir") - if value: - parent.DirectoryDirs.append(value) + value = __check(value, filename, "dir") + if value: + parent.DirectoryDirs.append(value) def __parseDefaultDirectoryDir(filename, parent): - for dir in reversed(xdg_data_dirs): - __parseDirectoryDir(os.path.join(dir, "desktop-directories"), filename, parent) + for dir in reversed(xdg_data_dirs): + __parseDirectoryDir(os.path.join(dir, "desktop-directories"), filename, parent) # Merge Stuff def __parseMergeFile(value, child, filename, parent): - if child.getAttribute("type") == "parent": - for dir in xdg_config_dirs: - rel_file = filename.replace(dir, "").strip("/") - if rel_file != filename: - for p in xdg_config_dirs: - if dir == p: - continue - if os.path.isfile(os.path.join(p,rel_file)): - __mergeFile(os.path.join(p,rel_file),child,parent) - break - else: - value = __check(value, filename, "file") - if value: - __mergeFile(value, child, parent) + if child.getAttribute("type") == "parent": + for dir in xdg_config_dirs: + rel_file = filename.replace(dir, "").strip("/") + if rel_file != filename: + for p in xdg_config_dirs: + if dir == p: + continue + if os.path.isfile(os.path.join(p,rel_file)): + __mergeFile(os.path.join(p,rel_file),child,parent) + break + else: + value = __check(value, filename, "file") + if value: + __mergeFile(value, child, parent) def __parseMergeDir(value, child, filename, parent): - value = __check(value, filename, "dir") - if value: - for item in os.listdir(value): - try: - if os.path.splitext(item)[1] == ".menu": - __mergeFile(os.path.join(value, item), child, parent) - except UnicodeDecodeError: - continue + value = __check(value, filename, "dir") + if value: + for item in os.listdir(value): + try: + if os.path.splitext(item)[1] == ".menu": + __mergeFile(os.path.join(value, item), child, parent) + except UnicodeDecodeError: + continue def __parseDefaultMergeDirs(child, filename, parent): - basename = os.path.splitext(os.path.basename(filename))[0] - for dir in reversed(xdg_config_dirs): - __parseMergeDir(os.path.join(dir, "menus", basename + "-merged"), child, filename, parent) + basename = os.path.splitext(os.path.basename(filename))[0] + for dir in reversed(xdg_config_dirs): + __parseMergeDir(os.path.join(dir, "menus", basename + "-merged"), child, filename, parent) def __mergeFile(filename, child, parent): - # check for infinite loops - if filename in tmp["mergeFiles"]: - if debug: - raise ParsingError('Infinite MergeFile loop detected', filename) - else: - return - - tmp["mergeFiles"].append(filename) - - # load file - try: - doc = xml.dom.minidom.parse(filename) - except IOError: - if debug: - raise ParsingError('File not found', filename) - else: - return - except xml.parsers.expat.ExpatError: - if debug: - raise ParsingError('Not a valid .menu file', filename) - else: - return - - # append file - for child in doc.childNodes: - if child.nodeType == ELEMENT_NODE: - __parse(child,filename,parent) - break + # check for infinite loops + if filename in tmp["mergeFiles"]: + if debug: + raise ParsingError('Infinite MergeFile loop detected', filename) + else: + return + + tmp["mergeFiles"].append(filename) + + # load file + try: + doc = xml.dom.minidom.parse(filename) + except IOError: + if debug: + raise ParsingError('File not found', filename) + else: + return + except xml.parsers.expat.ExpatError: + if debug: + raise ParsingError('Not a valid .menu file', filename) + else: + return + + # append file + for child in doc.childNodes: + if child.nodeType == ELEMENT_NODE: + __parse(child,filename,parent) + break # Legacy Dir Stuff def __parseLegacyDir(dir, prefix, filename, parent): - m = __mergeLegacyDir(dir,prefix,filename,parent) - if m: - parent += m + m = __mergeLegacyDir(dir,prefix,filename,parent) + if m: + parent += m def __mergeLegacyDir(dir, prefix, filename, parent): - dir = __check(dir,filename,"dir") - if dir and dir not in tmp["DirectoryDirs"]: - tmp["DirectoryDirs"].append(dir) - - m = Menu() - m.AppDirs.append(dir) - m.DirectoryDirs.append(dir) - m.Name = os.path.basename(dir) - m.NotInXml = True - - for item in os.listdir(dir): - try: - if item == ".directory": - m.Directories.append(item) - elif os.path.isdir(os.path.join(dir,item)): - m.addSubmenu(__mergeLegacyDir(os.path.join(dir,item), prefix, filename, parent)) - except UnicodeDecodeError: - continue - - tmp["cache"].addMenuEntries([dir],prefix, True) - menuentries = tmp["cache"].getMenuEntries([dir], False) - - for menuentry in menuentries: - categories = menuentry.Categories - if len(categories) == 0: - r = Rule("Include") - r.parseFilename(menuentry.DesktopFileID) - r.compile() - m.Rules.append(r) - if not dir in parent.AppDirs: - categories.append("Legacy") - menuentry.Categories = categories - - return m + dir = __check(dir,filename,"dir") + if dir and dir not in tmp["DirectoryDirs"]: + tmp["DirectoryDirs"].append(dir) + + m = Menu() + m.AppDirs.append(dir) + m.DirectoryDirs.append(dir) + m.Name = os.path.basename(dir) + m.NotInXml = True + + for item in os.listdir(dir): + try: + if item == ".directory": + m.Directories.append(item) + elif os.path.isdir(os.path.join(dir,item)): + m.addSubmenu(__mergeLegacyDir(os.path.join(dir,item), prefix, filename, parent)) + except UnicodeDecodeError: + continue + + tmp["cache"].addMenuEntries([dir],prefix, True) + menuentries = tmp["cache"].getMenuEntries([dir], False) + + for menuentry in menuentries: + categories = menuentry.Categories + if len(categories) == 0: + r = Rule("Include") + r.parseFilename(menuentry.DesktopFileID) + r.compile() + m.Rules.append(r) + if not dir in parent.AppDirs: + categories.append("Legacy") + menuentry.Categories = categories + + return m def __parseKDELegacyDirs(filename, parent): - f=os.popen3("kde-config --path apps") - output = f[1].readlines() - try: - for dir in output[0].split(":"): - __parseLegacyDir(dir,"kde", filename, parent) - except IndexError: - pass + f=os.popen3("kde-config --path apps") + output = f[1].readlines() + try: + for dir in output[0].split(":"): + __parseLegacyDir(dir,"kde", filename, parent) + except IndexError: + pass # remove duplicate entries from a list def __removeDuplicates(list): - set = {} - list.reverse() - list = [set.setdefault(e,e) for e in list if e not in set] - list.reverse() - return list + set = {} + list.reverse() + list = [set.setdefault(e,e) for e in list if e not in set] + list.reverse() + return list # Finally generate the menu def __genmenuNotOnlyAllocated(menu): - for submenu in menu.Submenus: - __genmenuNotOnlyAllocated(submenu) - - if menu.OnlyUnallocated == False: - tmp["cache"].addMenuEntries(menu.AppDirs) - menuentries = [] - for rule in menu.Rules: - menuentries = rule.do(tmp["cache"].getMenuEntries(menu.AppDirs), rule.Type, 1) - for menuentry in menuentries: - if menuentry.Add == True: - menuentry.Parents.append(menu) - menuentry.Add = False - menuentry.Allocated = True - menu.MenuEntries.append(menuentry) + for submenu in menu.Submenus: + __genmenuNotOnlyAllocated(submenu) + + if menu.OnlyUnallocated == False: + tmp["cache"].addMenuEntries(menu.AppDirs) + menuentries = [] + for rule in menu.Rules: + menuentries = rule.do(tmp["cache"].getMenuEntries(menu.AppDirs), rule.Type, 1) + for menuentry in menuentries: + if menuentry.Add == True: + menuentry.Parents.append(menu) + menuentry.Add = False + menuentry.Allocated = True + menu.MenuEntries.append(menuentry) def __genmenuOnlyAllocated(menu): - for submenu in menu.Submenus: - __genmenuOnlyAllocated(submenu) - - if menu.OnlyUnallocated == True: - tmp["cache"].addMenuEntries(menu.AppDirs) - menuentries = [] - for rule in menu.Rules: - menuentries = rule.do(tmp["cache"].getMenuEntries(menu.AppDirs), rule.Type, 2) - for menuentry in menuentries: - if menuentry.Add == True: - menuentry.Parents.append(menu) - # menuentry.Add = False - # menuentry.Allocated = True - menu.MenuEntries.append(menuentry) + for submenu in menu.Submenus: + __genmenuOnlyAllocated(submenu) + + if menu.OnlyUnallocated == True: + tmp["cache"].addMenuEntries(menu.AppDirs) + menuentries = [] + for rule in menu.Rules: + menuentries = rule.do(tmp["cache"].getMenuEntries(menu.AppDirs), rule.Type, 2) + for menuentry in menuentries: + if menuentry.Add == True: + menuentry.Parents.append(menu) + # menuentry.Add = False + # menuentry.Allocated = True + menu.MenuEntries.append(menuentry) # And sorting ... def sort(menu): - menu.Entries = [] - menu.Visible = 0 - - for submenu in menu.Submenus: - sort(submenu) - - tmp_s = [] - tmp_e = [] - - for order in menu.Layout.order: - if order[0] == "Filename": - tmp_e.append(order[1]) - elif order[0] == "Menuname": - tmp_s.append(order[1]) - - for order in menu.Layout.order: - if order[0] == "Separator": - separator = Separator(menu) - if len(menu.Entries) > 0 and isinstance(menu.Entries[-1], Separator): - separator.Show = False - menu.Entries.append(separator) - elif order[0] == "Filename": - menuentry = menu.getMenuEntry(order[1]) - if menuentry: - menu.Entries.append(menuentry) - elif order[0] == "Menuname": - submenu = menu.getMenu(order[1]) - if submenu: - __parse_inline(submenu, menu) - elif order[0] == "Merge": - if order[1] == "files" or order[1] == "all": - menu.MenuEntries.sort() - for menuentry in menu.MenuEntries: - if menuentry not in tmp_e: - menu.Entries.append(menuentry) - elif order[1] == "menus" or order[1] == "all": - menu.Submenus.sort() - for submenu in menu.Submenus: - if submenu.Name not in tmp_s: - __parse_inline(submenu, menu) - - # getHidden / NoDisplay / OnlyShowIn / NotOnlyShowIn / Deleted / NoExec - for entry in menu.Entries: - entry.Show = True - menu.Visible += 1 - if isinstance(entry, Menu): - if entry.Deleted == True: - entry.Show = "Deleted" - menu.Visible -= 1 - elif isinstance(entry.Directory, MenuEntry): - if entry.Directory.DesktopEntry.getNoDisplay() == True: - entry.Show = "NoDisplay" - menu.Visible -= 1 - elif entry.Directory.DesktopEntry.getHidden() == True: - entry.Show = "Hidden" - menu.Visible -= 1 - elif isinstance(entry, MenuEntry): - if entry.DesktopEntry.getNoDisplay() == True: - entry.Show = "NoDisplay" - menu.Visible -= 1 - elif entry.DesktopEntry.getHidden() == True: - entry.Show = "Hidden" - menu.Visible -= 1 - elif entry.DesktopEntry.getTryExec() and not __try_exec(entry.DesktopEntry.getTryExec()): - entry.Show = "NoExec" - menu.Visible -= 1 - elif xdg.Config.windowmanager: - if ( entry.DesktopEntry.getOnlyShowIn() != [] and xdg.Config.windowmanager not in entry.DesktopEntry.getOnlyShowIn() ) \ - or xdg.Config.windowmanager in entry.DesktopEntry.getNotShowIn(): - entry.Show = "NotShowIn" - menu.Visible -= 1 - elif isinstance(entry,Separator): - menu.Visible -= 1 - - # remove separators at the beginning and at the end - if len(menu.Entries) > 0: - if isinstance(menu.Entries[0], Separator): - menu.Entries[0].Show = False - if len(menu.Entries) > 1: - if isinstance(menu.Entries[-1], Separator): - menu.Entries[-1].Show = False - - # show_empty tag - for entry in menu.Entries: - if isinstance(entry,Menu) and entry.Layout.show_empty == "false" and entry.Visible == 0: - entry.Show = "Empty" - menu.Visible -= 1 - if entry.NotInXml == True: - menu.Entries.remove(entry) + menu.Entries = [] + menu.Visible = 0 + + for submenu in menu.Submenus: + sort(submenu) + + tmp_s = [] + tmp_e = [] + + for order in menu.Layout.order: + if order[0] == "Filename": + tmp_e.append(order[1]) + elif order[0] == "Menuname": + tmp_s.append(order[1]) + + for order in menu.Layout.order: + if order[0] == "Separator": + separator = Separator(menu) + if len(menu.Entries) > 0 and isinstance(menu.Entries[-1], Separator): + separator.Show = False + menu.Entries.append(separator) + elif order[0] == "Filename": + menuentry = menu.getMenuEntry(order[1]) + if menuentry: + menu.Entries.append(menuentry) + elif order[0] == "Menuname": + submenu = menu.getMenu(order[1]) + if submenu: + __parse_inline(submenu, menu) + elif order[0] == "Merge": + if order[1] == "files" or order[1] == "all": + menu.MenuEntries.sort() + for menuentry in menu.MenuEntries: + if menuentry not in tmp_e: + menu.Entries.append(menuentry) + elif order[1] == "menus" or order[1] == "all": + menu.Submenus.sort() + for submenu in menu.Submenus: + if submenu.Name not in tmp_s: + __parse_inline(submenu, menu) + + # getHidden / NoDisplay / OnlyShowIn / NotOnlyShowIn / Deleted / NoExec + for entry in menu.Entries: + entry.Show = True + menu.Visible += 1 + if isinstance(entry, Menu): + if entry.Deleted == True: + entry.Show = "Deleted" + menu.Visible -= 1 + elif isinstance(entry.Directory, MenuEntry): + if entry.Directory.DesktopEntry.getNoDisplay() == True: + entry.Show = "NoDisplay" + menu.Visible -= 1 + elif entry.Directory.DesktopEntry.getHidden() == True: + entry.Show = "Hidden" + menu.Visible -= 1 + elif isinstance(entry, MenuEntry): + if entry.DesktopEntry.getNoDisplay() == True: + entry.Show = "NoDisplay" + menu.Visible -= 1 + elif entry.DesktopEntry.getHidden() == True: + entry.Show = "Hidden" + menu.Visible -= 1 + elif entry.DesktopEntry.getTryExec() and not __try_exec(entry.DesktopEntry.getTryExec()): + entry.Show = "NoExec" + menu.Visible -= 1 + elif xdg.Config.windowmanager: + if ( entry.DesktopEntry.getOnlyShowIn() != [] and xdg.Config.windowmanager not in entry.DesktopEntry.getOnlyShowIn() ) \ + or xdg.Config.windowmanager in entry.DesktopEntry.getNotShowIn(): + entry.Show = "NotShowIn" + menu.Visible -= 1 + elif isinstance(entry,Separator): + menu.Visible -= 1 + + # remove separators at the beginning and at the end + if len(menu.Entries) > 0: + if isinstance(menu.Entries[0], Separator): + menu.Entries[0].Show = False + if len(menu.Entries) > 1: + if isinstance(menu.Entries[-1], Separator): + menu.Entries[-1].Show = False + + # show_empty tag + for entry in menu.Entries: + if isinstance(entry,Menu) and entry.Layout.show_empty == "false" and entry.Visible == 0: + entry.Show = "Empty" + menu.Visible -= 1 + if entry.NotInXml == True: + menu.Entries.remove(entry) def __try_exec(executable): - paths = os.environ['PATH'].split(os.pathsep) - if not os.path.isfile(executable): - for p in paths: - f = os.path.join(p, executable) - if os.path.isfile(f): - if os.access(f, os.X_OK): - return True - else: - if os.access(executable, os.X_OK): - return True - return False + paths = os.environ['PATH'].split(os.pathsep) + if not os.path.isfile(executable): + for p in paths: + f = os.path.join(p, executable) + if os.path.isfile(f): + if os.access(f, os.X_OK): + return True + else: + if os.access(executable, os.X_OK): + return True + return False # inline tags def __parse_inline(submenu, menu): - if submenu.Layout.inline == "true": - if len(submenu.Entries) == 1 and submenu.Layout.inline_alias == "true": - menuentry = submenu.Entries[0] - menuentry.DesktopEntry.set("Name", submenu.getName(), locale = True) - menuentry.DesktopEntry.set("GenericName", submenu.getGenericName(), locale = True) - menuentry.DesktopEntry.set("Comment", submenu.getComment(), locale = True) - menu.Entries.append(menuentry) - elif len(submenu.Entries) <= submenu.Layout.inline_limit or submenu.Layout.inline_limit == 0: - if submenu.Layout.inline_header == "true": - header = Header(submenu.getName(), submenu.getGenericName(), submenu.getComment()) - menu.Entries.append(header) - for entry in submenu.Entries: - menu.Entries.append(entry) - else: - menu.Entries.append(submenu) - else: - menu.Entries.append(submenu) + if submenu.Layout.inline == "true": + if len(submenu.Entries) == 1 and submenu.Layout.inline_alias == "true": + menuentry = submenu.Entries[0] + menuentry.DesktopEntry.set("Name", submenu.getName(), locale = True) + menuentry.DesktopEntry.set("GenericName", submenu.getGenericName(), locale = True) + menuentry.DesktopEntry.set("Comment", submenu.getComment(), locale = True) + menu.Entries.append(menuentry) + elif len(submenu.Entries) <= submenu.Layout.inline_limit or submenu.Layout.inline_limit == 0: + if submenu.Layout.inline_header == "true": + header = Header(submenu.getName(), submenu.getGenericName(), submenu.getComment()) + menu.Entries.append(header) + for entry in submenu.Entries: + menu.Entries.append(entry) + else: + menu.Entries.append(submenu) + else: + menu.Entries.append(submenu) class MenuEntryCache: - "Class to cache Desktop Entries" - def __init__(self): - self.cacheEntries = {} - self.cacheEntries['legacy'] = [] - self.cache = {} - - def addMenuEntries(self, dirs, prefix="", legacy=False): - for dir in dirs: - if not self.cacheEntries.has_key(dir): - self.cacheEntries[dir] = [] - self.__addFiles(dir, "", prefix, legacy) - - def __addFiles(self, dir, subdir, prefix, legacy): - for item in os.listdir(os.path.join(dir,subdir)): - if os.path.splitext(item)[1] == ".desktop": - try: - menuentry = MenuEntry(os.path.join(subdir,item), dir, prefix) - except ParsingError: - continue - - self.cacheEntries[dir].append(menuentry) - if legacy == True: - self.cacheEntries['legacy'].append(menuentry) - elif os.path.isdir(os.path.join(dir,subdir,item)) and legacy == False: - self.__addFiles(dir, os.path.join(subdir,item), prefix, legacy) - - def getMenuEntries(self, dirs, legacy=True): - list = [] - ids = [] - # handle legacy items - appdirs = dirs[:] - if legacy == True: - appdirs.append("legacy") - # cache the results again - key = "".join(appdirs) - try: - return self.cache[key] - except KeyError: - pass - for dir in appdirs: - for menuentry in self.cacheEntries[dir]: - try: - if menuentry.DesktopFileID not in ids: - ids.append(menuentry.DesktopFileID) - list.append(menuentry) - elif menuentry.getType() == "System": - # FIXME: This is only 99% correct, but still... - i = list.index(menuentry) - e = list[i] - if e.getType() == "User": - e.Original = menuentry - except UnicodeDecodeError: - continue - self.cache[key] = list - return list + "Class to cache Desktop Entries" + def __init__(self): + self.cacheEntries = {} + self.cacheEntries['legacy'] = [] + self.cache = {} + + def addMenuEntries(self, dirs, prefix="", legacy=False): + for dir in dirs: + if not self.cacheEntries.has_key(dir): + self.cacheEntries[dir] = [] + self.__addFiles(dir, "", prefix, legacy) + + def __addFiles(self, dir, subdir, prefix, legacy): + for item in os.listdir(os.path.join(dir,subdir)): + if os.path.splitext(item)[1] == ".desktop": + try: + menuentry = MenuEntry(os.path.join(subdir,item), dir, prefix) + except ParsingError: + continue + + self.cacheEntries[dir].append(menuentry) + if legacy == True: + self.cacheEntries['legacy'].append(menuentry) + elif os.path.isdir(os.path.join(dir,subdir,item)) and legacy == False: + self.__addFiles(dir, os.path.join(subdir,item), prefix, legacy) + + def getMenuEntries(self, dirs, legacy=True): + list = [] + ids = [] + # handle legacy items + appdirs = dirs[:] + if legacy == True: + appdirs.append("legacy") + # cache the results again + key = "".join(appdirs) + try: + return self.cache[key] + except KeyError: + pass + for dir in appdirs: + for menuentry in self.cacheEntries[dir]: + try: + if menuentry.DesktopFileID not in ids: + ids.append(menuentry.DesktopFileID) + list.append(menuentry) + elif menuentry.getType() == "System": + # FIXME: This is only 99% correct, but still... + i = list.index(menuentry) + e = list[i] + if e.getType() == "User": + e.Original = menuentry + except UnicodeDecodeError: + continue + self.cache[key] = list + return list diff --git a/xdg/MenuEditor.py b/xdg/MenuEditor.py index ce1d271..cc5ce54 100644 --- a/xdg/MenuEditor.py +++ b/xdg/MenuEditor.py @@ -21,491 +21,491 @@ import re # Complex Rules/Deleted/OnlyAllocated/AppDirs/DirectoryDirs class MenuEditor: - def __init__(self, menu=None, filename=None, root=False): - self.menu = None - self.filename = None - self.doc = None - self.parse(menu, filename, root) + def __init__(self, menu=None, filename=None, root=False): + self.menu = None + self.filename = None + self.doc = None + self.parse(menu, filename, root) - # fix for creating two menus with the same name on the fly - self.filenames = [] + # fix for creating two menus with the same name on the fly + self.filenames = [] - def parse(self, menu=None, filename=None, root=False): - if root == True: - setRootMode(True) + def parse(self, menu=None, filename=None, root=False): + if root == True: + setRootMode(True) - if isinstance(menu, Menu): - self.menu = menu - elif menu: - self.menu = parse(menu) - else: - self.menu = parse() + if isinstance(menu, Menu): + self.menu = menu + elif menu: + self.menu = parse(menu) + else: + self.menu = parse() - if root == True: - self.filename = self.menu.Filename - elif filename: - self.filename = filename - else: - self.filename = os.path.join(xdg_config_dirs[0], "menus", os.path.split(self.menu.Filename)[1]) + if root == True: + self.filename = self.menu.Filename + elif filename: + self.filename = filename + else: + self.filename = os.path.join(xdg_config_dirs[0], "menus", os.path.split(self.menu.Filename)[1]) - try: - self.doc = xml.dom.minidom.parse(self.filename) - except IOError: - self.doc = xml.dom.minidom.parseString('<!DOCTYPE Menu PUBLIC "-//freedesktop//DTD Menu 1.0//EN" "http://standards.freedesktop.org/menu-spec/menu-1.0.dtd"><Menu><Name>Applications</Name><MergeFile type="parent">'+self.menu.Filename+'</MergeFile></Menu>') - except xml.parsers.expat.ExpatError: - raise ParsingError('Not a valid .menu file', self.filename) + try: + self.doc = xml.dom.minidom.parse(self.filename) + except IOError: + self.doc = xml.dom.minidom.parseString('<!DOCTYPE Menu PUBLIC "-//freedesktop//DTD Menu 1.0//EN" "http://standards.freedesktop.org/menu-spec/menu-1.0.dtd"><Menu><Name>Applications</Name><MergeFile type="parent">'+self.menu.Filename+'</MergeFile></Menu>') + except xml.parsers.expat.ExpatError: + raise ParsingError('Not a valid .menu file', self.filename) - self.__remove_whilespace_nodes(self.doc) + self.__remove_whilespace_nodes(self.doc) - def save(self): - self.__saveEntries(self.menu) - self.__saveMenu() + def save(self): + self.__saveEntries(self.menu) + self.__saveMenu() - def createMenuEntry(self, parent, name, command=None, genericname=None, comment=None, icon=None, terminal=None, after=None, before=None): - menuentry = MenuEntry(self.__getFileName(name, ".desktop")) - menuentry = self.editMenuEntry(menuentry, name, genericname, comment, command, icon, terminal) + def createMenuEntry(self, parent, name, command=None, genericname=None, comment=None, icon=None, terminal=None, after=None, before=None): + menuentry = MenuEntry(self.__getFileName(name, ".desktop")) + menuentry = self.editMenuEntry(menuentry, name, genericname, comment, command, icon, terminal) - self.__addEntry(parent, menuentry, after, before) + self.__addEntry(parent, menuentry, after, before) - sort(self.menu) + sort(self.menu) - return menuentry + return menuentry - def createMenu(self, parent, name, genericname=None, comment=None, icon=None, after=None, before=None): - menu = Menu() + def createMenu(self, parent, name, genericname=None, comment=None, icon=None, after=None, before=None): + menu = Menu() - menu.Parent = parent - menu.Depth = parent.Depth + 1 - menu.Layout = parent.DefaultLayout - menu.DefaultLayout = parent.DefaultLayout + menu.Parent = parent + menu.Depth = parent.Depth + 1 + menu.Layout = parent.DefaultLayout + menu.DefaultLayout = parent.DefaultLayout - menu = self.editMenu(menu, name, genericname, comment, icon) + menu = self.editMenu(menu, name, genericname, comment, icon) - self.__addEntry(parent, menu, after, before) + self.__addEntry(parent, menu, after, before) - sort(self.menu) + sort(self.menu) - return menu + return menu - def createSeparator(self, parent, after=None, before=None): - separator = Separator(parent) + def createSeparator(self, parent, after=None, before=None): + separator = Separator(parent) - self.__addEntry(parent, separator, after, before) + self.__addEntry(parent, separator, after, before) - sort(self.menu) + sort(self.menu) - return separator + return separator - def moveMenuEntry(self, menuentry, oldparent, newparent, after=None, before=None): - self.__deleteEntry(oldparent, menuentry, after, before) - self.__addEntry(newparent, menuentry, after, before) + def moveMenuEntry(self, menuentry, oldparent, newparent, after=None, before=None): + self.__deleteEntry(oldparent, menuentry, after, before) + self.__addEntry(newparent, menuentry, after, before) - sort(self.menu) + sort(self.menu) - return menuentry + return menuentry - def moveMenu(self, menu, oldparent, newparent, after=None, before=None): - self.__deleteEntry(oldparent, menu, after, before) - self.__addEntry(newparent, menu, after, before) + def moveMenu(self, menu, oldparent, newparent, after=None, before=None): + self.__deleteEntry(oldparent, menu, after, before) + self.__addEntry(newparent, menu, after, before) - root_menu = self.__getXmlMenu(self.menu.Name) - if oldparent.getPath(True) != newparent.getPath(True): - self.__addXmlMove(root_menu, os.path.join(oldparent.getPath(True), menu.Name), os.path.join(newparent.getPath(True), menu.Name)) + root_menu = self.__getXmlMenu(self.menu.Name) + if oldparent.getPath(True) != newparent.getPath(True): + self.__addXmlMove(root_menu, os.path.join(oldparent.getPath(True), menu.Name), os.path.join(newparent.getPath(True), menu.Name)) - sort(self.menu) - - return menu - - def moveSeparator(self, separator, parent, after=None, before=None): - self.__deleteEntry(parent, separator, after, before) - self.__addEntry(parent, separator, after, before) + sort(self.menu) + + return menu + + def moveSeparator(self, separator, parent, after=None, before=None): + self.__deleteEntry(parent, separator, after, before) + self.__addEntry(parent, separator, after, before) - sort(self.menu) + sort(self.menu) - return separator + return separator - def copyMenuEntry(self, menuentry, oldparent, newparent, after=None, before=None): - self.__addEntry(newparent, menuentry, after, before) + def copyMenuEntry(self, menuentry, oldparent, newparent, after=None, before=None): + self.__addEntry(newparent, menuentry, after, before) - sort(self.menu) + sort(self.menu) - return menuentry + return menuentry - def editMenuEntry(self, menuentry, name=None, genericname=None, comment=None, command=None, icon=None, terminal=None, nodisplay=None, hidden=None): - deskentry = menuentry.DesktopEntry + def editMenuEntry(self, menuentry, name=None, genericname=None, comment=None, command=None, icon=None, terminal=None, nodisplay=None, hidden=None): + deskentry = menuentry.DesktopEntry - if name: - if not deskentry.hasKey("Name"): - deskentry.set("Name", name) - deskentry.set("Name", name, locale = True) - if comment: - if not deskentry.hasKey("Comment"): - deskentry.set("Comment", comment) - deskentry.set("Comment", comment, locale = True) - if genericname: - if not deskentry.hasKey("GnericNe"): - deskentry.set("GenericName", genericname) - deskentry.set("GenericName", genericname, locale = True) - if command: - deskentry.set("Exec", command) - if icon: - deskentry.set("Icon", icon) + if name: + if not deskentry.hasKey("Name"): + deskentry.set("Name", name) + deskentry.set("Name", name, locale = True) + if comment: + if not deskentry.hasKey("Comment"): + deskentry.set("Comment", comment) + deskentry.set("Comment", comment, locale = True) + if genericname: + if not deskentry.hasKey("GnericNe"): + deskentry.set("GenericName", genericname) + deskentry.set("GenericName", genericname, locale = True) + if command: + deskentry.set("Exec", command) + if icon: + deskentry.set("Icon", icon) - if terminal == True: - deskentry.set("Terminal", "true") - elif terminal == False: - deskentry.set("Terminal", "false") + if terminal == True: + deskentry.set("Terminal", "true") + elif terminal == False: + deskentry.set("Terminal", "false") - if nodisplay == True: - deskentry.set("NoDisplay", "true") - elif nodisplay == False: - deskentry.set("NoDisplay", "false") + if nodisplay == True: + deskentry.set("NoDisplay", "true") + elif nodisplay == False: + deskentry.set("NoDisplay", "false") - if hidden == True: - deskentry.set("Hidden", "true") - elif hidden == False: - deskentry.set("Hidden", "false") - - menuentry.updateAttributes() - - if len(menuentry.Parents) > 0: - sort(self.menu) - - return menuentry - - def editMenu(self, menu, name=None, genericname=None, comment=None, icon=None, nodisplay=None, hidden=None): - # Hack for legacy dirs - if isinstance(menu.Directory, MenuEntry) and menu.Directory.Filename == ".directory": - xml_menu = self.__getXmlMenu(menu.getPath(True, True)) - self.__addXmlTextElement(xml_menu, 'Directory', menu.Name + ".directory") - menu.Directory.setAttributes(menu.Name + ".directory") - # Hack for New Entries - elif not isinstance(menu.Directory, MenuEntry): - if not name: - name = menu.Name - filename = self.__getFileName(name, ".directory").replace("/", "") - if not menu.Name: - menu.Name = filename.replace(".directory", "") - xml_menu = self.__getXmlMenu(menu.getPath(True, True)) - self.__addXmlTextElement(xml_menu, 'Directory', filename) - menu.Directory = MenuEntry(filename) - - deskentry = menu.Directory.DesktopEntry - - if name: - if not deskentry.hasKey("Name"): - deskentry.set("Name", name) - deskentry.set("Name", name, locale = True) - if genericname: - if not deskentry.hasKey("GenericName"): - deskentry.set("GenericName", genericname) - deskentry.set("GenericName", genericname, locale = True) - if comment: - if not deskentry.hasKey("Comment"): - deskentry.set("Comment", comment) - deskentry.set("Comment", comment, locale = True) - if icon: - deskentry.set("Icon", icon) - - if nodisplay == True: - deskentry.set("NoDisplay", "true") - elif nodisplay == False: - deskentry.set("NoDisplay", "false") - - if hidden == True: - deskentry.set("Hidden", "true") - elif hidden == False: - deskentry.set("Hidden", "false") - - menu.Directory.updateAttributes() - - if isinstance(menu.Parent, Menu): - sort(self.menu) - - return menu - - def hideMenuEntry(self, menuentry): - self.editMenuEntry(menuentry, nodisplay = True) - - def unhideMenuEntry(self, menuentry): - self.editMenuEntry(menuentry, nodisplay = False, hidden = False) - - def hideMenu(self, menu): - self.editMenu(menu, nodisplay = True) - - def unhideMenu(self, menu): - self.editMenu(menu, nodisplay = False, hidden = False) - xml_menu = self.__getXmlMenu(menu.getPath(True,True), False) - for node in self.__getXmlNodesByName(["Deleted", "NotDeleted"], xml_menu): - node.parentNode.removeChild(node) - - def deleteMenuEntry(self, menuentry): - if self.getAction(menuentry) == "delete": - self.__deleteFile(menuentry.DesktopEntry.filename) - for parent in menuentry.Parents: - self.__deleteEntry(parent, menuentry) - sort(self.menu) - return menuentry - - def revertMenuEntry(self, menuentry): - if self.getAction(menuentry) == "revert": - self.__deleteFile(menuentry.DesktopEntry.filename) - menuentry.Original.Parents = [] - for parent in menuentry.Parents: - index = parent.Entries.index(menuentry) - parent.Entries[index] = menuentry.Original - index = parent.MenuEntries.index(menuentry) - parent.MenuEntries[index] = menuentry.Original - menuentry.Original.Parents.append(parent) - sort(self.menu) - return menuentry - - def deleteMenu(self, menu): - if self.getAction(menu) == "delete": - self.__deleteFile(menu.Directory.DesktopEntry.filename) - self.__deleteEntry(menu.Parent, menu) - xml_menu = self.__getXmlMenu(menu.getPath(True, True)) - xml_menu.parentNode.removeChild(xml_menu) - sort(self.menu) - return menu - - def revertMenu(self, menu): - if self.getAction(menu) == "revert": - self.__deleteFile(menu.Directory.DesktopEntry.filename) - menu.Directory = menu.Directory.Original - sort(self.menu) - return menu - - def deleteSeparator(self, separator): - self.__deleteEntry(separator.Parent, separator, after=True) - - sort(self.menu) - - return separator - - """ Private Stuff """ - def getAction(self, entry): - if isinstance(entry, Menu): - if not isinstance(entry.Directory, MenuEntry): - return "none" - elif entry.Directory.getType() == "Both": - return "revert" - elif entry.Directory.getType() == "User" \ - and (len(entry.Submenus) + len(entry.MenuEntries)) == 0: - return "delete" - - elif isinstance(entry, MenuEntry): - if entry.getType() == "Both": - return "revert" - elif entry.getType() == "User": - return "delete" - else: - return "none" - - return "none" - - def __saveEntries(self, menu): - if not menu: - menu = self.menu - if isinstance(menu.Directory, MenuEntry): - menu.Directory.save() - for entry in menu.getEntries(hidden=True): - if isinstance(entry, MenuEntry): - entry.save() - elif isinstance(entry, Menu): - self.__saveEntries(entry) - - def __saveMenu(self): - if not os.path.isdir(os.path.dirname(self.filename)): - os.makedirs(os.path.dirname(self.filename)) - fd = open(self.filename, 'w') - fd.write(re.sub("\n[\s]*([^\n<]*)\n[\s]*</", "\\1</", self.doc.toprettyxml().replace('<?xml version="1.0" ?>\n', ''))) - fd.close() - - def __getFileName(self, name, extension): - postfix = 0 - while 1: - if postfix == 0: - filename = name + extension - else: - filename = name + "-" + str(postfix) + extension - if extension == ".desktop": - dir = "applications" - elif extension == ".directory": - dir = "desktop-directories" - if not filename in self.filenames and not \ - os.path.isfile(os.path.join(xdg_data_dirs[0], dir, filename)): - self.filenames.append(filename) - break - else: - postfix += 1 - - return filename - - def __getXmlMenu(self, path, create=True, element=None): - if not element: - element = self.doc - - if "/" in path: - (name, path) = path.split("/", 1) - else: - name = path - path = "" - - found = None - for node in self.__getXmlNodesByName("Menu", element): - for child in self.__getXmlNodesByName("Name", node): - if child.childNodes[0].nodeValue == name: - if path: - found = self.__getXmlMenu(path, create, node) - else: - found = node - break - if found: - break - if not found and create == True: - node = self.__addXmlMenuElement(element, name) - if path: - found = self.__getXmlMenu(path, create, node) - else: - found = node - - return found - - def __addXmlMenuElement(self, element, name): - node = self.doc.createElement('Menu') - self.__addXmlTextElement(node, 'Name', name) - return element.appendChild(node) - - def __addXmlTextElement(self, element, name, text): - node = self.doc.createElement(name) - text = self.doc.createTextNode(text) - node.appendChild(text) - return element.appendChild(node) - - def __addXmlFilename(self, element, filename, type = "Include"): - # remove old filenames - for node in self.__getXmlNodesByName(["Include", "Exclude"], element): - if node.childNodes[0].nodeName == "Filename" and node.childNodes[0].childNodes[0].nodeValue == filename: - element.removeChild(node) - - # add new filename - node = self.doc.createElement(type) - node.appendChild(self.__addXmlTextElement(node, 'Filename', filename)) - return element.appendChild(node) - - def __addXmlMove(self, element, old, new): - node = self.doc.createElement("Move") - node.appendChild(self.__addXmlTextElement(node, 'Old', old)) - node.appendChild(self.__addXmlTextElement(node, 'New', new)) - return element.appendChild(node) - - def __addXmlLayout(self, element, layout): - # remove old layout - for node in self.__getXmlNodesByName("Layout", element): - element.removeChild(node) - - # add new layout - node = self.doc.createElement("Layout") - for order in layout.order: - if order[0] == "Separator": - child = self.doc.createElement("Separator") - node.appendChild(child) - elif order[0] == "Filename": - child = self.__addXmlTextElement(node, "Filename", order[1]) - elif order[0] == "Menuname": - child = self.__addXmlTextElement(node, "Menuname", order[1]) - elif order[0] == "Merge": - child = self.doc.createElement("Merge") - child.setAttribute("type", order[1]) - node.appendChild(child) - return element.appendChild(node) - - def __getXmlNodesByName(self, name, element): - for child in element.childNodes: - if child.nodeType == xml.dom.Node.ELEMENT_NODE and child.nodeName in name: - yield child - - def __addLayout(self, parent): - layout = Layout() - layout.order = [] - layout.show_empty = parent.Layout.show_empty - layout.inline = parent.Layout.inline - layout.inline_header = parent.Layout.inline_header - layout.inline_alias = parent.Layout.inline_alias - layout.inline_limit = parent.Layout.inline_limit - - layout.order.append(["Merge", "menus"]) - for entry in parent.Entries: - if isinstance(entry, Menu): - layout.parseMenuname(entry.Name) - elif isinstance(entry, MenuEntry): - layout.parseFilename(entry.DesktopFileID) - elif isinstance(entry, Separator): - layout.parseSeparator() - layout.order.append(["Merge", "files"]) - - parent.Layout = layout - - return layout - - def __addEntry(self, parent, entry, after=None, before=None): - if after or before: - if after: - index = parent.Entries.index(after) + 1 - elif before: - index = parent.Entries.index(before) - parent.Entries.insert(index, entry) - else: - parent.Entries.append(entry) - - xml_parent = self.__getXmlMenu(parent.getPath(True, True)) - - if isinstance(entry, MenuEntry): - parent.MenuEntries.append(entry) - entry.Parents.append(parent) - self.__addXmlFilename(xml_parent, entry.DesktopFileID, "Include") - elif isinstance(entry, Menu): - parent.addSubmenu(entry) - - if after or before: - self.__addLayout(parent) - self.__addXmlLayout(xml_parent, parent.Layout) - - def __deleteEntry(self, parent, entry, after=None, before=None): - parent.Entries.remove(entry) - - xml_parent = self.__getXmlMenu(parent.getPath(True, True)) - - if isinstance(entry, MenuEntry): - entry.Parents.remove(parent) - parent.MenuEntries.remove(entry) - self.__addXmlFilename(xml_parent, entry.DesktopFileID, "Exclude") - elif isinstance(entry, Menu): - parent.Submenus.remove(entry) - - if after or before: - self.__addLayout(parent) - self.__addXmlLayout(xml_parent, parent.Layout) - - def __deleteFile(self, filename): - try: - os.remove(filename) - except OSError: - pass - try: - self.filenames.remove(filename) - except ValueError: - pass - - def __remove_whilespace_nodes(self, node): - remove_list = [] - for child in node.childNodes: - if child.nodeType == xml.dom.minidom.Node.TEXT_NODE: - child.data = child.data.strip() - if not child.data.strip(): - remove_list.append(child) - elif child.hasChildNodes(): - self.__remove_whilespace_nodes(child) - for node in remove_list: - node.parentNode.removeChild(node) + if hidden == True: + deskentry.set("Hidden", "true") + elif hidden == False: + deskentry.set("Hidden", "false") + + menuentry.updateAttributes() + + if len(menuentry.Parents) > 0: + sort(self.menu) + + return menuentry + + def editMenu(self, menu, name=None, genericname=None, comment=None, icon=None, nodisplay=None, hidden=None): + # Hack for legacy dirs + if isinstance(menu.Directory, MenuEntry) and menu.Directory.Filename == ".directory": + xml_menu = self.__getXmlMenu(menu.getPath(True, True)) + self.__addXmlTextElement(xml_menu, 'Directory', menu.Name + ".directory") + menu.Directory.setAttributes(menu.Name + ".directory") + # Hack for New Entries + elif not isinstance(menu.Directory, MenuEntry): + if not name: + name = menu.Name + filename = self.__getFileName(name, ".directory").replace("/", "") + if not menu.Name: + menu.Name = filename.replace(".directory", "") + xml_menu = self.__getXmlMenu(menu.getPath(True, True)) + self.__addXmlTextElement(xml_menu, 'Directory', filename) + menu.Directory = MenuEntry(filename) + + deskentry = menu.Directory.DesktopEntry + + if name: + if not deskentry.hasKey("Name"): + deskentry.set("Name", name) + deskentry.set("Name", name, locale = True) + if genericname: + if not deskentry.hasKey("GenericName"): + deskentry.set("GenericName", genericname) + deskentry.set("GenericName", genericname, locale = True) + if comment: + if not deskentry.hasKey("Comment"): + deskentry.set("Comment", comment) + deskentry.set("Comment", comment, locale = True) + if icon: + deskentry.set("Icon", icon) + + if nodisplay == True: + deskentry.set("NoDisplay", "true") + elif nodisplay == False: + deskentry.set("NoDisplay", "false") + + if hidden == True: + deskentry.set("Hidden", "true") + elif hidden == False: + deskentry.set("Hidden", "false") + + menu.Directory.updateAttributes() + + if isinstance(menu.Parent, Menu): + sort(self.menu) + + return menu + + def hideMenuEntry(self, menuentry): + self.editMenuEntry(menuentry, nodisplay = True) + + def unhideMenuEntry(self, menuentry): + self.editMenuEntry(menuentry, nodisplay = False, hidden = False) + + def hideMenu(self, menu): + self.editMenu(menu, nodisplay = True) + + def unhideMenu(self, menu): + self.editMenu(menu, nodisplay = False, hidden = False) + xml_menu = self.__getXmlMenu(menu.getPath(True,True), False) + for node in self.__getXmlNodesByName(["Deleted", "NotDeleted"], xml_menu): + node.parentNode.removeChild(node) + + def deleteMenuEntry(self, menuentry): + if self.getAction(menuentry) == "delete": + self.__deleteFile(menuentry.DesktopEntry.filename) + for parent in menuentry.Parents: + self.__deleteEntry(parent, menuentry) + sort(self.menu) + return menuentry + + def revertMenuEntry(self, menuentry): + if self.getAction(menuentry) == "revert": + self.__deleteFile(menuentry.DesktopEntry.filename) + menuentry.Original.Parents = [] + for parent in menuentry.Parents: + index = parent.Entries.index(menuentry) + parent.Entries[index] = menuentry.Original + index = parent.MenuEntries.index(menuentry) + parent.MenuEntries[index] = menuentry.Original + menuentry.Original.Parents.append(parent) + sort(self.menu) + return menuentry + + def deleteMenu(self, menu): + if self.getAction(menu) == "delete": + self.__deleteFile(menu.Directory.DesktopEntry.filename) + self.__deleteEntry(menu.Parent, menu) + xml_menu = self.__getXmlMenu(menu.getPath(True, True)) + xml_menu.parentNode.removeChild(xml_menu) + sort(self.menu) + return menu + + def revertMenu(self, menu): + if self.getAction(menu) == "revert": + self.__deleteFile(menu.Directory.DesktopEntry.filename) + menu.Directory = menu.Directory.Original + sort(self.menu) + return menu + + def deleteSeparator(self, separator): + self.__deleteEntry(separator.Parent, separator, after=True) + + sort(self.menu) + + return separator + + """ Private Stuff """ + def getAction(self, entry): + if isinstance(entry, Menu): + if not isinstance(entry.Directory, MenuEntry): + return "none" + elif entry.Directory.getType() == "Both": + return "revert" + elif entry.Directory.getType() == "User" \ + and (len(entry.Submenus) + len(entry.MenuEntries)) == 0: + return "delete" + + elif isinstance(entry, MenuEntry): + if entry.getType() == "Both": + return "revert" + elif entry.getType() == "User": + return "delete" + else: + return "none" + + return "none" + + def __saveEntries(self, menu): + if not menu: + menu = self.menu + if isinstance(menu.Directory, MenuEntry): + menu.Directory.save() + for entry in menu.getEntries(hidden=True): + if isinstance(entry, MenuEntry): + entry.save() + elif isinstance(entry, Menu): + self.__saveEntries(entry) + + def __saveMenu(self): + if not os.path.isdir(os.path.dirname(self.filename)): + os.makedirs(os.path.dirname(self.filename)) + fd = open(self.filename, 'w') + fd.write(re.sub("\n[\s]*([^\n<]*)\n[\s]*</", "\\1</", self.doc.toprettyxml().replace('<?xml version="1.0" ?>\n', ''))) + fd.close() + + def __getFileName(self, name, extension): + postfix = 0 + while 1: + if postfix == 0: + filename = name + extension + else: + filename = name + "-" + str(postfix) + extension + if extension == ".desktop": + dir = "applications" + elif extension == ".directory": + dir = "desktop-directories" + if not filename in self.filenames and not \ + os.path.isfile(os.path.join(xdg_data_dirs[0], dir, filename)): + self.filenames.append(filename) + break + else: + postfix += 1 + + return filename + + def __getXmlMenu(self, path, create=True, element=None): + if not element: + element = self.doc + + if "/" in path: + (name, path) = path.split("/", 1) + else: + name = path + path = "" + + found = None + for node in self.__getXmlNodesByName("Menu", element): + for child in self.__getXmlNodesByName("Name", node): + if child.childNodes[0].nodeValue == name: + if path: + found = self.__getXmlMenu(path, create, node) + else: + found = node + break + if found: + break + if not found and create == True: + node = self.__addXmlMenuElement(element, name) + if path: + found = self.__getXmlMenu(path, create, node) + else: + found = node + + return found + + def __addXmlMenuElement(self, element, name): + node = self.doc.createElement('Menu') + self.__addXmlTextElement(node, 'Name', name) + return element.appendChild(node) + + def __addXmlTextElement(self, element, name, text): + node = self.doc.createElement(name) + text = self.doc.createTextNode(text) + node.appendChild(text) + return element.appendChild(node) + + def __addXmlFilename(self, element, filename, type = "Include"): + # remove old filenames + for node in self.__getXmlNodesByName(["Include", "Exclude"], element): + if node.childNodes[0].nodeName == "Filename" and node.childNodes[0].childNodes[0].nodeValue == filename: + element.removeChild(node) + + # add new filename + node = self.doc.createElement(type) + node.appendChild(self.__addXmlTextElement(node, 'Filename', filename)) + return element.appendChild(node) + + def __addXmlMove(self, element, old, new): + node = self.doc.createElement("Move") + node.appendChild(self.__addXmlTextElement(node, 'Old', old)) + node.appendChild(self.__addXmlTextElement(node, 'New', new)) + return element.appendChild(node) + + def __addXmlLayout(self, element, layout): + # remove old layout + for node in self.__getXmlNodesByName("Layout", element): + element.removeChild(node) + + # add new layout + node = self.doc.createElement("Layout") + for order in layout.order: + if order[0] == "Separator": + child = self.doc.createElement("Separator") + node.appendChild(child) + elif order[0] == "Filename": + child = self.__addXmlTextElement(node, "Filename", order[1]) + elif order[0] == "Menuname": + child = self.__addXmlTextElement(node, "Menuname", order[1]) + elif order[0] == "Merge": + child = self.doc.createElement("Merge") + child.setAttribute("type", order[1]) + node.appendChild(child) + return element.appendChild(node) + + def __getXmlNodesByName(self, name, element): + for child in element.childNodes: + if child.nodeType == xml.dom.Node.ELEMENT_NODE and child.nodeName in name: + yield child + + def __addLayout(self, parent): + layout = Layout() + layout.order = [] + layout.show_empty = parent.Layout.show_empty + layout.inline = parent.Layout.inline + layout.inline_header = parent.Layout.inline_header + layout.inline_alias = parent.Layout.inline_alias + layout.inline_limit = parent.Layout.inline_limit + + layout.order.append(["Merge", "menus"]) + for entry in parent.Entries: + if isinstance(entry, Menu): + layout.parseMenuname(entry.Name) + elif isinstance(entry, MenuEntry): + layout.parseFilename(entry.DesktopFileID) + elif isinstance(entry, Separator): + layout.parseSeparator() + layout.order.append(["Merge", "files"]) + + parent.Layout = layout + + return layout + + def __addEntry(self, parent, entry, after=None, before=None): + if after or before: + if after: + index = parent.Entries.index(after) + 1 + elif before: + index = parent.Entries.index(before) + parent.Entries.insert(index, entry) + else: + parent.Entries.append(entry) + + xml_parent = self.__getXmlMenu(parent.getPath(True, True)) + + if isinstance(entry, MenuEntry): + parent.MenuEntries.append(entry) + entry.Parents.append(parent) + self.__addXmlFilename(xml_parent, entry.DesktopFileID, "Include") + elif isinstance(entry, Menu): + parent.addSubmenu(entry) + + if after or before: + self.__addLayout(parent) + self.__addXmlLayout(xml_parent, parent.Layout) + + def __deleteEntry(self, parent, entry, after=None, before=None): + parent.Entries.remove(entry) + + xml_parent = self.__getXmlMenu(parent.getPath(True, True)) + + if isinstance(entry, MenuEntry): + entry.Parents.remove(parent) + parent.MenuEntries.remove(entry) + self.__addXmlFilename(xml_parent, entry.DesktopFileID, "Exclude") + elif isinstance(entry, Menu): + parent.Submenus.remove(entry) + + if after or before: + self.__addLayout(parent) + self.__addXmlLayout(xml_parent, parent.Layout) + + def __deleteFile(self, filename): + try: + os.remove(filename) + except OSError: + pass + try: + self.filenames.remove(filename) + except ValueError: + pass + + def __remove_whilespace_nodes(self, node): + remove_list = [] + for child in node.childNodes: + if child.nodeType == xml.dom.minidom.Node.TEXT_NODE: + child.data = child.data.strip() + if not child.data.strip(): + remove_list.append(child) + elif child.hasChildNodes(): + self.__remove_whilespace_nodes(child) + for node in remove_list: + node.parentNode.removeChild(node) diff --git a/xdg/Mime.py b/xdg/Mime.py index d92f57d..73ff914 100644 --- a/xdg/Mime.py +++ b/xdg/Mime.py @@ -30,273 +30,273 @@ from xml.dom import Node, minidom, XML_NAMESPACE FREE_NS = 'http://www.freedesktop.org/standards/shared-mime-info' -types = {} # Maps MIME names to type objects +types = {} # Maps MIME names to type objects -exts = None # Maps extensions to types -globs = None # List of (glob, type) pairs -literals = None # Maps liternal names to types +exts = None # Maps extensions to types +globs = None # List of (glob, type) pairs +literals = None # Maps liternal names to types magic = None def _get_node_data(node): - """Get text of XML node""" - return ''.join([n.nodeValue for n in node.childNodes]).strip() + """Get text of XML node""" + return ''.join([n.nodeValue for n in node.childNodes]).strip() def lookup(media, subtype = None): - "Get the MIMEtype object for this type, creating a new one if needed." - if subtype is None and '/' in media: - media, subtype = media.split('/', 1) - if (media, subtype) not in types: - types[(media, subtype)] = MIMEtype(media, subtype) - return types[(media, subtype)] + "Get the MIMEtype object for this type, creating a new one if needed." + if subtype is None and '/' in media: + media, subtype = media.split('/', 1) + if (media, subtype) not in types: + types[(media, subtype)] = MIMEtype(media, subtype) + return types[(media, subtype)] class MIMEtype: - """Type holding data about a MIME type""" - def __init__(self, media, subtype): - "Don't use this constructor directly; use mime.lookup() instead." - assert media and '/' not in media - assert subtype and '/' not in subtype - assert (media, subtype) not in types - - self.media = media - self.subtype = subtype - self._comment = None - - def _load(self): - "Loads comment for current language. Use get_comment() instead." - resource = os.path.join('mime', self.media, self.subtype + '.xml') - for path in xdg.BaseDirectory.load_data_paths(resource): - doc = minidom.parse(path) - if doc is None: - continue - for comment in doc.documentElement.getElementsByTagNameNS(FREE_NS, 'comment'): - lang = comment.getAttributeNS(XML_NAMESPACE, 'lang') or 'en' - goodness = 1 + (lang in xdg.Locale.langs) - if goodness > self._comment[0]: - self._comment = (goodness, _get_node_data(comment)) - if goodness == 2: return - - # FIXME: add get_icon method - def get_comment(self): - """Returns comment for current language, loading it if needed.""" - # Should we ever reload? - if self._comment is None: - self._comment = (0, str(self)) - self._load() - return self._comment[1] - - def __str__(self): - return self.media + '/' + self.subtype - - def __repr__(self): - return '[%s: %s]' % (self, self._comment or '(comment not loaded)') + """Type holding data about a MIME type""" + def __init__(self, media, subtype): + "Don't use this constructor directly; use mime.lookup() instead." + assert media and '/' not in media + assert subtype and '/' not in subtype + assert (media, subtype) not in types + + self.media = media + self.subtype = subtype + self._comment = None + + def _load(self): + "Loads comment for current language. Use get_comment() instead." + resource = os.path.join('mime', self.media, self.subtype + '.xml') + for path in xdg.BaseDirectory.load_data_paths(resource): + doc = minidom.parse(path) + if doc is None: + continue + for comment in doc.documentElement.getElementsByTagNameNS(FREE_NS, 'comment'): + lang = comment.getAttributeNS(XML_NAMESPACE, 'lang') or 'en' + goodness = 1 + (lang in xdg.Locale.langs) + if goodness > self._comment[0]: + self._comment = (goodness, _get_node_data(comment)) + if goodness == 2: return + + # FIXME: add get_icon method + def get_comment(self): + """Returns comment for current language, loading it if needed.""" + # Should we ever reload? + if self._comment is None: + self._comment = (0, str(self)) + self._load() + return self._comment[1] + + def __str__(self): + return self.media + '/' + self.subtype + + def __repr__(self): + return '[%s: %s]' % (self, self._comment or '(comment not loaded)') class MagicRule: - def __init__(self, f): - self.next=None - self.prev=None - - #print line - ind='' - while True: - c=f.read(1) - if c=='>': - break - ind+=c - if not ind: - self.nest=0 - else: - self.nest=int(ind) - - start='' - while True: - c=f.read(1) - if c=='=': - break - start+=c - self.start=int(start) - - hb=f.read(1) - lb=f.read(1) - self.lenvalue=ord(lb)+(ord(hb)<<8) - - self.value=f.read(self.lenvalue) - - c=f.read(1) - if c=='&': - self.mask=f.read(self.lenvalue) - c=f.read(1) - else: - self.mask=None - - if c=='~': - w='' - while c!='+' and c!='\n': - c=f.read(1) - if c=='+' or c=='\n': - break - w+=c - - self.word=int(w) - else: - self.word=1 - - if c=='+': - r='' - while c!='\n': - c=f.read(1) - if c=='\n': - break - r+=c - #print r - self.range=int(r) - else: - self.range=1 - - if c!='\n': - raise 'Malformed MIME magic line' - - def getLength(self): - return self.start+self.lenvalue+self.range - - def appendRule(self, rule): - if self.nest<rule.nest: - self.next=rule - rule.prev=self - - elif self.prev: - self.prev.appendRule(rule) - - def match(self, buffer): - if self.match0(buffer): - if self.next: - return self.next.match(buffer) - return True - - def match0(self, buffer): - l=len(buffer) - for o in range(self.range): - s=self.start+o - e=s+self.lenvalue - if l<e: - return False - if self.mask: - test='' - for i in range(self.lenvalue): - c=ord(buffer[s+i]) & ord(self.mask[i]) - test+=chr(c) - else: - test=buffer[s:e] - - if test==self.value: - return True - - def __repr__(self): - return '<MagicRule %d>%d=[%d]%s&%s~%d+%d>' % (self.nest, - self.start, - self.lenvalue, - `self.value`, - `self.mask`, - self.word, - self.range) + def __init__(self, f): + self.next=None + self.prev=None + + #print line + ind='' + while True: + c=f.read(1) + if c=='>': + break + ind+=c + if not ind: + self.nest=0 + else: + self.nest=int(ind) + + start='' + while True: + c=f.read(1) + if c=='=': + break + start+=c + self.start=int(start) + + hb=f.read(1) + lb=f.read(1) + self.lenvalue=ord(lb)+(ord(hb)<<8) + + self.value=f.read(self.lenvalue) + + c=f.read(1) + if c=='&': + self.mask=f.read(self.lenvalue) + c=f.read(1) + else: + self.mask=None + + if c=='~': + w='' + while c!='+' and c!='\n': + c=f.read(1) + if c=='+' or c=='\n': + break + w+=c + + self.word=int(w) + else: + self.word=1 + + if c=='+': + r='' + while c!='\n': + c=f.read(1) + if c=='\n': + break + r+=c + #print r + self.range=int(r) + else: + self.range=1 + + if c!='\n': + raise 'Malformed MIME magic line' + + def getLength(self): + return self.start+self.lenvalue+self.range + + def appendRule(self, rule): + if self.nest<rule.nest: + self.next=rule + rule.prev=self + + elif self.prev: + self.prev.appendRule(rule) + + def match(self, buffer): + if self.match0(buffer): + if self.next: + return self.next.match(buffer) + return True + + def match0(self, buffer): + l=len(buffer) + for o in range(self.range): + s=self.start+o + e=s+self.lenvalue + if l<e: + return False + if self.mask: + test='' + for i in range(self.lenvalue): + c=ord(buffer[s+i]) & ord(self.mask[i]) + test+=chr(c) + else: + test=buffer[s:e] + + if test==self.value: + return True + + def __repr__(self): + return '<MagicRule %d>%d=[%d]%s&%s~%d+%d>' % (self.nest, + self.start, + self.lenvalue, + `self.value`, + `self.mask`, + self.word, + self.range) class MagicType: - def __init__(self, mtype): - self.mtype=mtype - self.top_rules=[] - self.last_rule=None + def __init__(self, mtype): + self.mtype=mtype + self.top_rules=[] + self.last_rule=None - def getLine(self, f): - nrule=MagicRule(f) + def getLine(self, f): + nrule=MagicRule(f) - if nrule.nest and self.last_rule: - self.last_rule.appendRule(nrule) - else: - self.top_rules.append(nrule) + if nrule.nest and self.last_rule: + self.last_rule.appendRule(nrule) + else: + self.top_rules.append(nrule) - self.last_rule=nrule + self.last_rule=nrule - return nrule + return nrule - def match(self, buffer): - for rule in self.top_rules: - if rule.match(buffer): - return self.mtype + def match(self, buffer): + for rule in self.top_rules: + if rule.match(buffer): + return self.mtype - def __repr__(self): - return '<MagicType %s>' % self.mtype - + def __repr__(self): + return '<MagicType %s>' % self.mtype + class MagicDB: - def __init__(self): - self.types={} # Indexed by priority, each entry is a list of type rules - self.maxlen=0 - - def mergeFile(self, fname): - f=file(fname, 'r') - line=f.readline() - if line!='MIME-Magic\0\n': - raise 'Not a MIME magic file' - - while True: - shead=f.readline() - #print shead - if not shead: - break - if shead[0]!='[' or shead[-2:]!=']\n': - raise 'Malformed section heading' - pri, tname=shead[1:-2].split(':') - #print shead[1:-2] - pri=int(pri) - mtype=lookup(tname) - - try: - ents=self.types[pri] - except: - ents=[] - self.types[pri]=ents - - magictype=MagicType(mtype) - #print tname - - #rline=f.readline() - c=f.read(1) - f.seek(-1, 1) - while c and c!='[': - rule=magictype.getLine(f) - #print rule - if rule and rule.getLength()>self.maxlen: - self.maxlen=rule.getLength() - - c=f.read(1) - f.seek(-1, 1) - - ents.append(magictype) - #self.types[pri]=ents - if not c: - break - - def match(self, path, max_pri=100, min_pri=0): - try: - buf=file(path, 'r').read(self.maxlen) - pris=self.types.keys() - pris.sort(lambda a, b: -cmp(a, b)) - for pri in pris: - #print pri, max_pri, min_pri - if pri>max_pri: - continue - if pri<min_pri: - break - for type in self.types[pri]: - m=type.match(buf) - if m: - return m - except: - pass - - return None - - def __repr__(self): - return '<MagicDB %s>' % self.types - + def __init__(self): + self.types={} # Indexed by priority, each entry is a list of type rules + self.maxlen=0 + + def mergeFile(self, fname): + f=file(fname, 'r') + line=f.readline() + if line!='MIME-Magic\0\n': + raise 'Not a MIME magic file' + + while True: + shead=f.readline() + #print shead + if not shead: + break + if shead[0]!='[' or shead[-2:]!=']\n': + raise 'Malformed section heading' + pri, tname=shead[1:-2].split(':') + #print shead[1:-2] + pri=int(pri) + mtype=lookup(tname) + + try: + ents=self.types[pri] + except: + ents=[] + self.types[pri]=ents + + magictype=MagicType(mtype) + #print tname + + #rline=f.readline() + c=f.read(1) + f.seek(-1, 1) + while c and c!='[': + rule=magictype.getLine(f) + #print rule + if rule and rule.getLength()>self.maxlen: + self.maxlen=rule.getLength() + + c=f.read(1) + f.seek(-1, 1) + + ents.append(magictype) + #self.types[pri]=ents + if not c: + break + + def match(self, path, max_pri=100, min_pri=0): + try: + buf=file(path, 'r').read(self.maxlen) + pris=self.types.keys() + pris.sort(lambda a, b: -cmp(a, b)) + for pri in pris: + #print pri, max_pri, min_pri + if pri>max_pri: + continue + if pri<min_pri: + break + for type in self.types[pri]: + m=type.match(buf) + if m: + return m + except: + pass + + return None + + def __repr__(self): + return '<MagicDB %s>' % self.types + # Some well-known types text = lookup('text', 'plain') @@ -312,152 +312,152 @@ app_exe = lookup('application', 'executable') _cache_uptodate = False def _cache_database(): - global exts, globs, literals, magic, _cache_uptodate - - _cache_uptodate = True - - exts = {} # Maps extensions to types - globs = [] # List of (glob, type) pairs - literals = {} # Maps liternal names to types - magic = MagicDB() - - def _import_glob_file(path): - """Loads name matching information from a MIME directory.""" - for line in file(path): - if line.startswith('#'): continue - line = line[:-1] - - type_name, pattern = line.split(':', 1) - mtype = lookup(type_name) - - if pattern.startswith('*.'): - rest = pattern[2:] - if not ('*' in rest or '[' in rest or '?' in rest): - exts[rest] = mtype - continue - if '*' in pattern or '[' in pattern or '?' in pattern: - globs.append((pattern, mtype)) - else: - literals[pattern] = mtype - - for path in xdg.BaseDirectory.load_data_paths(os.path.join('mime', 'globs')): - _import_glob_file(path) - for path in xdg.BaseDirectory.load_data_paths(os.path.join('mime', 'magic')): - magic.mergeFile(path) - - # Sort globs by length - globs.sort(lambda a, b: cmp(len(b[0]), len(a[0]))) + global exts, globs, literals, magic, _cache_uptodate + + _cache_uptodate = True + + exts = {} # Maps extensions to types + globs = [] # List of (glob, type) pairs + literals = {} # Maps liternal names to types + magic = MagicDB() + + def _import_glob_file(path): + """Loads name matching information from a MIME directory.""" + for line in file(path): + if line.startswith('#'): continue + line = line[:-1] + + type_name, pattern = line.split(':', 1) + mtype = lookup(type_name) + + if pattern.startswith('*.'): + rest = pattern[2:] + if not ('*' in rest or '[' in rest or '?' in rest): + exts[rest] = mtype + continue + if '*' in pattern or '[' in pattern or '?' in pattern: + globs.append((pattern, mtype)) + else: + literals[pattern] = mtype + + for path in xdg.BaseDirectory.load_data_paths(os.path.join('mime', 'globs')): + _import_glob_file(path) + for path in xdg.BaseDirectory.load_data_paths(os.path.join('mime', 'magic')): + magic.mergeFile(path) + + # Sort globs by length + globs.sort(lambda a, b: cmp(len(b[0]), len(a[0]))) def get_type_by_name(path): - """Returns type of file by its name, or None if not known""" - if not _cache_uptodate: - _cache_database() - - leaf = os.path.basename(path) - if leaf in literals: - return literals[leaf] - - lleaf = leaf.lower() - if lleaf in literals: - return literals[lleaf] - - ext = leaf - while 1: - p = ext.find('.') - if p < 0: break - ext = ext[p + 1:] - if ext in exts: - return exts[ext] - ext = lleaf - while 1: - p = ext.find('.') - if p < 0: break - ext = ext[p+1:] - if ext in exts: - return exts[ext] - for (glob, mime_type) in globs: - if fnmatch.fnmatch(leaf, glob): - return mime_type - if fnmatch.fnmatch(lleaf, glob): - return mime_type - return None + """Returns type of file by its name, or None if not known""" + if not _cache_uptodate: + _cache_database() + + leaf = os.path.basename(path) + if leaf in literals: + return literals[leaf] + + lleaf = leaf.lower() + if lleaf in literals: + return literals[lleaf] + + ext = leaf + while 1: + p = ext.find('.') + if p < 0: break + ext = ext[p + 1:] + if ext in exts: + return exts[ext] + ext = lleaf + while 1: + p = ext.find('.') + if p < 0: break + ext = ext[p+1:] + if ext in exts: + return exts[ext] + for (glob, mime_type) in globs: + if fnmatch.fnmatch(leaf, glob): + return mime_type + if fnmatch.fnmatch(lleaf, glob): + return mime_type + return None def get_type_by_contents(path, max_pri=100, min_pri=0): - """Returns type of file by its contents, or None if not known""" - if not _cache_uptodate: - _cache_database() + """Returns type of file by its contents, or None if not known""" + if not _cache_uptodate: + _cache_database() - return magic.match(path, max_pri, min_pri) + return magic.match(path, max_pri, min_pri) def get_type(path, follow=1, name_pri=100): - """Returns type of file indicated by path. - path - pathname to check (need not exist) - follow - when reading file, follow symbolic links - name_pri - Priority to do name matches. 100=override magic""" - if not _cache_uptodate: - _cache_database() - - try: - if follow: - st = os.stat(path) - else: - st = os.lstat(path) - except: - t = get_type_by_name(path) - return t or text - - if stat.S_ISREG(st.st_mode): - t = get_type_by_contents(path, min_pri=name_pri) - if not t: t = get_type_by_name(path) - if not t: t = get_type_by_contents(path, max_pri=name_pri) - if t is None: - if stat.S_IMODE(st.st_mode) & 0111: - return app_exe - else: - return text - return t - elif stat.S_ISDIR(st.st_mode): return inode_dir - elif stat.S_ISCHR(st.st_mode): return inode_char - elif stat.S_ISBLK(st.st_mode): return inode_block - elif stat.S_ISFIFO(st.st_mode): return inode_fifo - elif stat.S_ISLNK(st.st_mode): return inode_symlink - elif stat.S_ISSOCK(st.st_mode): return inode_socket - return inode_door + """Returns type of file indicated by path. + path - pathname to check (need not exist) + follow - when reading file, follow symbolic links + name_pri - Priority to do name matches. 100=override magic""" + if not _cache_uptodate: + _cache_database() + + try: + if follow: + st = os.stat(path) + else: + st = os.lstat(path) + except: + t = get_type_by_name(path) + return t or text + + if stat.S_ISREG(st.st_mode): + t = get_type_by_contents(path, min_pri=name_pri) + if not t: t = get_type_by_name(path) + if not t: t = get_type_by_contents(path, max_pri=name_pri) + if t is None: + if stat.S_IMODE(st.st_mode) & 0111: + return app_exe + else: + return text + return t + elif stat.S_ISDIR(st.st_mode): return inode_dir + elif stat.S_ISCHR(st.st_mode): return inode_char + elif stat.S_ISBLK(st.st_mode): return inode_block + elif stat.S_ISFIFO(st.st_mode): return inode_fifo + elif stat.S_ISLNK(st.st_mode): return inode_symlink + elif stat.S_ISSOCK(st.st_mode): return inode_socket + return inode_door def install_mime_info(application, package_file): - """Copy 'package_file' as ~/.local/share/mime/packages/<application>.xml. - If package_file is None, install <app_dir>/<application>.xml. - If already installed, does nothing. May overwrite an existing - file with the same name (if the contents are different)""" - application += '.xml' - - new_data = file(package_file).read() - - # See if the file is already installed - package_dir = os.path.join('mime', 'packages') - resource = os.path.join(package_dir, application) - for x in xdg.BaseDirectory.load_data_paths(resource): - try: - old_data = file(x).read() - except: - continue - if old_data == new_data: - return # Already installed - - global _cache_uptodate - _cache_uptodate = False - - # Not already installed; add a new copy - # Create the directory structure... - new_file = os.path.join(xdg.BaseDirectory.save_data_path(package_dir), application) - - # Write the file... - file(new_file, 'w').write(new_data) - - # Update the database... - command = 'update-mime-database' - if os.spawnlp(os.P_WAIT, command, command, xdg.BaseDirectory.save_data_path('mime')): - os.unlink(new_file) - raise Exception("The '%s' command returned an error code!\n" \ - "Make sure you have the freedesktop.org shared MIME package:\n" \ - "http://standards.freedesktop.org/shared-mime-info/") % command + """Copy 'package_file' as ~/.local/share/mime/packages/<application>.xml. + If package_file is None, install <app_dir>/<application>.xml. + If already installed, does nothing. May overwrite an existing + file with the same name (if the contents are different)""" + application += '.xml' + + new_data = file(package_file).read() + + # See if the file is already installed + package_dir = os.path.join('mime', 'packages') + resource = os.path.join(package_dir, application) + for x in xdg.BaseDirectory.load_data_paths(resource): + try: + old_data = file(x).read() + except: + continue + if old_data == new_data: + return # Already installed + + global _cache_uptodate + _cache_uptodate = False + + # Not already installed; add a new copy + # Create the directory structure... + new_file = os.path.join(xdg.BaseDirectory.save_data_path(package_dir), application) + + # Write the file... + file(new_file, 'w').write(new_data) + + # Update the database... + command = 'update-mime-database' + if os.spawnlp(os.P_WAIT, command, command, xdg.BaseDirectory.save_data_path('mime')): + os.unlink(new_file) + raise Exception("The '%s' command returned an error code!\n" \ + "Make sure you have the freedesktop.org shared MIME package:\n" \ + "http://standards.freedesktop.org/shared-mime-info/") % command diff --git a/xdg/RecentFiles.py b/xdg/RecentFiles.py index 32034b9..6c2cd85 100644 --- a/xdg/RecentFiles.py +++ b/xdg/RecentFiles.py @@ -8,152 +8,152 @@ import os, time, fcntl from xdg.Exceptions import * class RecentFiles: - def __init__(self): - self.RecentFiles = [] - self.filename = "" - - def parse(self, filename=None): - if not filename: - filename = os.path.join(os.getenv("HOME"), ".recently-used") - - try: - doc = xml.dom.minidom.parse(filename) - except IOError: - raise ParsingError('File not found', filename) - except xml.parsers.expat.ExpatError: - raise ParsingError('Not a valid .menu file', filename) - - self.filename = filename - - for child in doc.childNodes: - if child.nodeType == xml.dom.Node.ELEMENT_NODE: - if child.tagName == "RecentFiles": - for recent in child.childNodes: - if recent.nodeType == xml.dom.Node.ELEMENT_NODE: - if recent.tagName == "RecentItem": - self.__parseRecentItem(recent) - - self.sort() - - def __parseRecentItem(self, item): - recent = RecentFile() - self.RecentFiles.append(recent) - - for attribute in item.childNodes: - if attribute.nodeType == xml.dom.Node.ELEMENT_NODE: - if attribute.tagName == "URI": - recent.URI = attribute.childNodes[0].nodeValue - elif attribute.tagName == "Mime-Type": - recent.MimeType = attribute.childNodes[0].nodeValue - elif attribute.tagName == "Timestamp": - recent.Timestamp = attribute.childNodes[0].nodeValue - elif attribute.tagName == "Private": - recent.Prviate = True - elif attribute.tagName == "Groups": - - for group in attribute.childNodes: - if group.nodeType == xml.dom.Node.ELEMENT_NODE: - if group.tagName == "Group": - recent.Groups.append(group.childNodes[0].nodeValue) - - def write(self, filename=None): - if not filename and not self.filename: - raise ParsingError('File not found', filename) - elif not filename: - filename = self.filename - - f = open(filename, "w") - fcntl.lockf(f, fcntl.LOCK_EX) - f.write('<?xml version="1.0"?>\n') - f.write("<RecentFiles>\n") - - for r in self.RecentFiles: - f.write(" <RecentItem>\n") - f.write(" <URI>%s</URI>\n" % xml.sax.saxutils.escape(r.URI)) - f.write(" <Mime-Type>%s</Mime-Type>\n" % r.MimeType) - f.write(" <Timestamp>%s</Timestamp>\n" % r.Timestamp) - if r.Private == True: - f.write(" <Private/>\n") - if len(r.Groups) > 0: - f.write(" <Groups>\n") - for group in r.Groups: - f.write(" <Group>%s</Group>\n" % group) - f.write(" </Groups>\n") - f.write(" </RecentItem>\n") - - f.write("</RecentFiles>\n") - fcntl.lockf(f, fcntl.LOCK_UN) - f.close() - - def getFiles(self, mimetypes=None, groups=None, limit=0): - tmp = [] - i = 0 - for item in self.RecentFiles: - if groups: - for group in groups: - if group in item.Groups: - tmp.append(item) - i += 1 - elif mimetypes: - for mimetype in mimetypes: - if mimetype == item.MimeType: - tmp.append(item) - i += 1 - else: - if item.Private == False: - tmp.append(item) - i += 1 - if limit != 0 and i == limit: - break - - return tmp - - def addFile(self, item, mimetype, groups=None, private=False): - # check if entry already there - if item in self.RecentFiles: - index = self.RecentFiles.index(item) - recent = self.RecentFiles[index] - else: - # delete if more then 500 files - if len(self.RecentFiles) == 500: - self.RecentFiles.pop() - # add entry - recent = RecentFile() - self.RecentFiles.append(recent) - - recent.URI = item - recent.MimeType = mimetype - recent.Timestamp = int(time.time()) - recent.Private = private - recent.Groups = groups - - self.sort() - - def deleteFile(self, item): - if item in self.RecentFiles: - self.RecentFiles.remove(item) - - def sort(self): - self.RecentFiles.sort() - self.RecentFiles.reverse() + def __init__(self): + self.RecentFiles = [] + self.filename = "" + + def parse(self, filename=None): + if not filename: + filename = os.path.join(os.getenv("HOME"), ".recently-used") + + try: + doc = xml.dom.minidom.parse(filename) + except IOError: + raise ParsingError('File not found', filename) + except xml.parsers.expat.ExpatError: + raise ParsingError('Not a valid .menu file', filename) + + self.filename = filename + + for child in doc.childNodes: + if child.nodeType == xml.dom.Node.ELEMENT_NODE: + if child.tagName == "RecentFiles": + for recent in child.childNodes: + if recent.nodeType == xml.dom.Node.ELEMENT_NODE: + if recent.tagName == "RecentItem": + self.__parseRecentItem(recent) + + self.sort() + + def __parseRecentItem(self, item): + recent = RecentFile() + self.RecentFiles.append(recent) + + for attribute in item.childNodes: + if attribute.nodeType == xml.dom.Node.ELEMENT_NODE: + if attribute.tagName == "URI": + recent.URI = attribute.childNodes[0].nodeValue + elif attribute.tagName == "Mime-Type": + recent.MimeType = attribute.childNodes[0].nodeValue + elif attribute.tagName == "Timestamp": + recent.Timestamp = attribute.childNodes[0].nodeValue + elif attribute.tagName == "Private": + recent.Prviate = True + elif attribute.tagName == "Groups": + + for group in attribute.childNodes: + if group.nodeType == xml.dom.Node.ELEMENT_NODE: + if group.tagName == "Group": + recent.Groups.append(group.childNodes[0].nodeValue) + + def write(self, filename=None): + if not filename and not self.filename: + raise ParsingError('File not found', filename) + elif not filename: + filename = self.filename + + f = open(filename, "w") + fcntl.lockf(f, fcntl.LOCK_EX) + f.write('<?xml version="1.0"?>\n') + f.write("<RecentFiles>\n") + + for r in self.RecentFiles: + f.write(" <RecentItem>\n") + f.write(" <URI>%s</URI>\n" % xml.sax.saxutils.escape(r.URI)) + f.write(" <Mime-Type>%s</Mime-Type>\n" % r.MimeType) + f.write(" <Timestamp>%s</Timestamp>\n" % r.Timestamp) + if r.Private == True: + f.write(" <Private/>\n") + if len(r.Groups) > 0: + f.write(" <Groups>\n") + for group in r.Groups: + f.write(" <Group>%s</Group>\n" % group) + f.write(" </Groups>\n") + f.write(" </RecentItem>\n") + + f.write("</RecentFiles>\n") + fcntl.lockf(f, fcntl.LOCK_UN) + f.close() + + def getFiles(self, mimetypes=None, groups=None, limit=0): + tmp = [] + i = 0 + for item in self.RecentFiles: + if groups: + for group in groups: + if group in item.Groups: + tmp.append(item) + i += 1 + elif mimetypes: + for mimetype in mimetypes: + if mimetype == item.MimeType: + tmp.append(item) + i += 1 + else: + if item.Private == False: + tmp.append(item) + i += 1 + if limit != 0 and i == limit: + break + + return tmp + + def addFile(self, item, mimetype, groups=None, private=False): + # check if entry already there + if item in self.RecentFiles: + index = self.RecentFiles.index(item) + recent = self.RecentFiles[index] + else: + # delete if more then 500 files + if len(self.RecentFiles) == 500: + self.RecentFiles.pop() + # add entry + recent = RecentFile() + self.RecentFiles.append(recent) + + recent.URI = item + recent.MimeType = mimetype + recent.Timestamp = int(time.time()) + recent.Private = private + recent.Groups = groups + + self.sort() + + def deleteFile(self, item): + if item in self.RecentFiles: + self.RecentFiles.remove(item) + + def sort(self): + self.RecentFiles.sort() + self.RecentFiles.reverse() class RecentFile: - def __init__(self): - self.URI = "" - self.MimeType = "" - self.Timestamp = "" - self.Private = False - self.Groups = [] - - def __cmp__(self, other): - return cmp(self.Timestamp, other.Timestamp) - - def __eq__(self, other): - if self.URI == str(other): - return True - else: - return False - - def __str__(self): - return self.URI + def __init__(self): + self.URI = "" + self.MimeType = "" + self.Timestamp = "" + self.Private = False + self.Groups = [] + + def __cmp__(self, other): + return cmp(self.Timestamp, other.Timestamp) + + def __eq__(self, other): + if self.URI == str(other): + return True + else: + return False + + def __str__(self): + return self.URI |