Hi,

Recently, I encoutered this bug. So I decided to look at the code.  I
seems that obamenu code is poor. The translation to python3 has been
very basic. 
It don't follow the python3 coding style. First, there is a mix of tab
and spaces.

One can use f-string instead of the % operator for formating string.
There are many too long lines.

The fix for the bug regarding broken links is easy. In the function
`process_dtfile`, it must frame the whole code of the function in a
`try: exception`.

I decided to rewrite the code. I made many changes to get a cleaner
code. I believe this will be easier to maintain.

I don't know what is the best meethod to suggest my improvements.
See the attachement for the entire code.
If I must proceed by other way, let me know how.

Best regards.
--
Gilles Crèvecoeur.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import glob
import sys

# Version 1.1.7
# ---- config ---
applications_dirs = ("/usr/share/applications", )
image_dir_base = "/usr/share"  # without "pixmaps" -/usr/local/share in 
FreeBSD, /usr/share on linux
icon_Theme = "Humanity"
image_cat_prefix = "applications-"  # if empty will create no icon text only 
menu
application_groups = ("Office",
                      "Development",
                      "Graphics",
                      "Internet",
                      "Games",
                      "System",
                      "Multimedia",
                      "Utilities",
                      "Settings")
group_aliases = {"Audio": "Multimedia",
                 "AudioVideo": "Multimedia",
                 "Network": "Internet",
                 "Game": "Games",
                 "Utility": "Utilities",
                 "GTK": "",
                 "GNOME": ""}
ignoreList = ("evince-previewer",
              "Ted",
              "wingide3.2",
              "python3.4",
              "feh",
              "xfce4-power-manager-settings")
terminal_string = "evte -e"  # your favourites terminal exec string
simpleOBheader = False       # print full xml style OB header
# --- End of user config ---


class dtItem(object):
    def __init__(self, fName):
        self.fileName = fName
        self.Name = ""
        self.Comment = ""
        self.Exec = ""
        self.Terminal = None
        self.Type = ""
        self.Icon = ""
        self.Categories = ()

    def addName(self, data):
        self.Name = xescape(data)

    def addComment(self, data):
        self.Comment = data

    def addExec(self, data):
        if len(data) > 3 and data[-2] == '%':  # get rid of filemanager 
arguments in dt files
            data = data[:-2].strip()
        self.Exec = data

    def addIcon(self, data):
        self.Icon = ""
        if image_cat_prefix == "":
            return
        image_dir = image_dir_base + "/pixmaps/"
        di = data.strip()
        if len(di) < 3:
            # "Error in %s: Invalid or no icon '%s'" % (self.fileName,  di)
            return
        dix = di.find("/")      # is it a full path?
        if dix >= 0 and dix <= 2:    # yes, its a path (./path or ../path or 
/path ...)
            self.Icon = di
            return
        # else a short name like "myapp"
        tmp = image_dir + di + ".*"
        tmp = glob.glob(tmp)
        if len(tmp) > 0:
            self.Icon = tmp[0]
        return

    def addTerminal(self, data):
        if data == "True" or data == "true":
            self.Terminal = True
        else:
            self.Terminal = False

    def addType(self, data):
        self.Type = data

    def addCategories(self, data):
        self.Categories = data


def getCatIcon(cat):
    iconDir = image_dir_base + "/icons/" + icon_Theme + "/categories/24/"
    cat = image_cat_prefix + cat.lower()
    tmp = glob.glob(iconDir + cat + ".*")
    if len(tmp) > 0:
        return tmp[0]
    return ""


def xescape(s):
    Rep = {"&": "&amp;",
           "<": "&lt;",
           ">": "&gt;",
           "'": "&apos;",
           "\"": "&quot;"}
    for p in ("&", "<", ">", "'", "\""):
        sl = len(s)
        last = -1
        while last < sl:
            i = s.find(p,  last+1)
            if i < 0:
                #  done = True # never used
                break
            last = i
            x = s[:i]
            r = s[i+1:]
            s = x + Rep[p] + r
    return s


def process_category(cat, curCats,  appGroups=application_groups,  
aliases=group_aliases ):
    # first process aliases
    if cat in aliases:
        if aliases[cat] == "":
            return ""                               # ignore this one
        cat = aliases[cat]
    if cat in appGroups and cat not in curCats:  # valid categories only and no 
doublettes, please
        curCats.append(cat)
        return cat
    return ""


def process_dtfile(dtf,  catDict):  # process this file & extract relevant info
    try:
        with open(dtf, "r") as lines:
            active = False          # parse only after "[Desktop Entry]" line
            this = dtItem(dtf)
            for line in lines:
                line = line.strip()
                if line == "[Desktop Entry]":
                    active = True
                    continue
                if active is False:  # we don't care about licenses or other 
comments
                    continue
                if line is None or len(line) < 1 or line[0] == '#':
                    continue
                if line[0] == '[' and line != "[Desktop Entry]":
                    active = False
                    continue
                # else
                eqi = line.split('=')
                if len(eqi) < 2:
                    print("Error: Invalid .desktop line'" + line + "'")
                    continue
                # Check what it is ...
                if eqi[0] == "Name":
                    this.addName(eqi[1])
                elif eqi[0] == "Comment":
                    this.addComment(eqi[1])
                elif eqi[0] == "Exec":
                    this.addExec(eqi[1])
                elif eqi[0] == "Icon":
                    this.addIcon(eqi[1])
                elif eqi[0] == "Terminal":
                    this.addTerminal(eqi[1])
                elif eqi[0] == "Type":
                    if eqi[1] != "Application":
                        continue
                    this.addType(eqi[1])
                elif eqi[0] == "Categories":
                    if eqi[1] and eqi[1][-1] == ';':
                        eqi[1] = eqi[1][0:-1]
                    cats = []
                    # DEBUG
                    dtCats = eqi[1].split(';')
                    for cat in dtCats:
                        # result = process_category(cat, cats) # result is 
never used
                        process_category(cat, cats)
                    this.addCategories(cats)
                else:
                    continue
            # add to catDict
            # this.dprint()
            if len(this.Categories) > 0:  # don't care about stuff w/o category
                for cat in this.Categories:
                    catDict[cat].append(this)
    except FileNotFoundError:
        prog_name = sys.argv[0]
        print(F"{prog_name}: file not found: {dtf}", file=sys.stderr)


categoryDict = {}

if __name__ == "__main__":
    # init the application group dict (which will contain list of apps)
    for appGroup in application_groups:
        categoryDict[appGroup] = []

    # now let's look  into the app dirs ...
    for appDir in applications_dirs:
        appDir += "/*.desktop"
        dtFiles = glob.glob(appDir)

        # process each .desktop file in dir
        for dtf in dtFiles:
            skipFlag = False
            for ifn in ignoreList:
                if dtf.find(ifn) >= 0:
                    skipFlag = True
            if skipFlag is False:
                process_dtfile(dtf,  categoryDict)

    # now, generate jwm menu include
    if simpleOBheader is True:
        print('<openbox_pipe_menu>')       # magic header
    else:
        print('<?xml version="1.0" encoding="UTF-8" ?><openbox_pipe_menu 
xmlns="http://openbox.org/";  
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";  
xsi:schemaLocation="http://openbox.org/"; >')       # magic header
    appGroupLen = len(application_groups)
    for ag in range(appGroupLen):
        catList = categoryDict[application_groups[ag]]
        if len(catList) < 1:
            continue                # don't create empty menus
        s = application_groups[ag]
        catStr = F"<menu id=\"openbox-{s}\" label=\"{s}\" "
        tmp = getCatIcon(application_groups[ag])
        if tmp != "":
            catStr += F"icon=\"{tmp}\""
        print(catStr,  ">")
        for app in catList:
            progStr = "<item "
            progStr += F"label=\"{app.Name}\" "
            if app.Icon != "":
                progStr += F"icon=\"{app.Icon}\" "
            progStr += "><action name=\"Execute\"><command><![CDATA["
            if app.Terminal is True:
                progStr += terminal_string + " "
            progStr += F"{app.Exec}]]></command></action></item>"
            print(progStr)
        print("</menu>")
    print("</openbox_pipe_menu>")       # magic footer
    pass  # done/debug break

Reply via email to