1import stat
2import sys
3import os.path
4import MenuMaker
5import Prophet
6
7from Keywords import Keyword as Kw, Set as KwS
8
9import optparse
10from optparse import OptionParser as _OP
11
12from MenuMaker import msg, warn, fatal, fronts, terms
13
14# Output the generated menu to stdout instead of the menu file
15writeToStdout = False
16forceWrite = False  # Overwrite existing files
17noDesktop = False  # Do not scan for .desktop files
18noLegacy = False  # Do not scan for the legacy apps
19noDebian = False  # Exclude Debian apps database
20
21vStr = "be verbose"
22
23
24def vOpt(option, opt, value, parser):
25    v = MenuMaker.verbose
26    if v < 3:
27        v += 1
28        MenuMaker.verbose = Prophet.verbose = v
29
30
31fStr = "overwrite existing files"
32
33
34def fOpt(option, opt, value, parser):
35    global forceWrite
36    forceWrite = True
37
38cStr = "dump the menu to stdout instead of file"
39
40
41def cOpt(option, opt, value, parser):
42    global writeToStdout
43    writeToStdout = True
44
45iStr = "produce the output suitable for inclusion into the custom menu; MUST be used in conjunction with -c"
46
47
48def iOpt(option, opt, value, parser):
49    MenuMaker.writeFullMenu = False
50
51pStr = "always retain the path to executable (by default, the path is omitted whenever possible)"
52
53
54def pOpt(option, opt, value, parser):
55    Prophet.omitExecPath = False
56
57xdStr = "skip .desktop database scanning"
58
59
60def xdOpt(option, opt, value, parser):
61    global noDesktop
62    noDesktop = True
63
64xlStr = "skip legacy knowledge base scanning"
65
66
67def xlOpt(option, opt, value, parser):
68    global noLegacy
69    noLegacy = True
70
71xeStr = "skip Debian database scanning"
72
73
74def xeOpt(option, opt, value, parser):
75    global noDebian
76    noDebian = True
77
78sKwS = KwS(*Prophet.skip.keys())
79sStr = "skip specific categories (comma-separated list)"
80
81
82def sOpt(option, opt, value, parser):
83    for x in value.split(","):
84        if not len(x):
85            continue
86        x = Kw(x)
87        try:
88            Prophet.skip[x]
89        except KeyError:
90            raise optparse.OptionValueError(
91                "wrong argument %s for option %s" % (x, opt))
92        Prophet.skip[x] = True
93
94
95tStr = "terminal emulator to use for console applications"
96
97
98def tOpt(option, opt, value, parser):
99    global term
100    for t, n in terms:
101        if value in n:
102            term = t
103            return
104
105    raise optparse.OptionValueError(
106        "wrong argument %s for option %s" % (value, opt))
107
108
109class OP(_OP):
110
111    def print_help(self):
112        sys.stdout.write(
113            "This is %s %s, a 100%% Python heuristics-driven menu generator\n" %
114            (MenuMaker.pkgName, MenuMaker.pkgVer))
115        sys.stdout.write(
116            "%s %s %s <%s>\n\n" %
117            (MenuMaker.pkgHome,
118             MenuMaker.copyright,
119             MenuMaker.author,
120             MenuMaker.email))
121        _OP.print_help(self)
122        sys.stdout.write("\nfrontends (case insensitive):\n")
123        for v in fronts.values():
124            vars = list(v)
125            s = str(vars[0])
126            for x in vars[1:]:
127                s += " | " + str(x)
128            sys.stdout.write("  %s\n" % s)
129        sys.stdout.write(
130            "\nterminal emulators for -t (case insensitive), in order of decreasing preference:\n")
131        for t, n in terms:
132            vars = list(n)
133            s = str(vars[0])
134            for x in vars[1:]:
135                s += " | " + str(x)
136            sys.stdout.write("  %s\n" % s)
137        sys.stdout.write("\nskip categories for -s (case insensitive):\n")
138        for k in sKwS:
139            sys.stdout.write("  %s\n" % k)
140
141
142op = OP(usage="%prog [options] frontend", version="%s %s" %
143        (MenuMaker.pkgName, MenuMaker.pkgVer))
144op.add_option("-v", "--verbose", action="callback", callback=vOpt, help=vStr)
145op.add_option("-f", "--force", action="callback", callback=fOpt, help=fStr)
146op.add_option("-c", "--stdout", action="callback", callback=cOpt, help=cStr)
147op.add_option("-i", action="callback", callback=iOpt, help=iStr)
148op.add_option("-t", metavar="terminal", nargs=1, type="string",
149              action="callback", callback=tOpt, help=tStr)
150op.add_option(
151    "-p", "--retain-path", action="callback", callback=pOpt, help=pStr)
152op.add_option("--no-desktop", action="callback", callback=xdOpt, help=xdStr)
153op.add_option("--no-legacy", action="callback", callback=xlOpt, help=xlStr)
154op.add_option("--no-debian", action="callback", callback=xeOpt, help=xeStr)
155op.add_option("-s", "--skip", metavar="list", nargs=1,
156              type="string", action="callback", callback=sOpt, help=sStr)
157
158opts, args = op.parse_args()
159
160if not len(args):
161    fatal("no frontend specified")
162
163found = False
164for k, v in fronts.items():
165    if args[0] in v:
166        found = True
167        name = "MenuMaker.%s" % k
168        frontName = k
169        __import__(name)
170        front = sys.modules[name]
171        MenuMaker.setFrontend(front)
172        break
173
174
175if not found:
176    fatal("unknown frontend: %s" % args[0])
177
178if len(args) > 1:
179    warn("multiple frontends specified; the first one (%s) will be used" %
180         frontName)
181
182try:
183    menuFile = front.menuFile
184
185except AttributeError:
186    if not writeToStdout:
187        fatal(
188            "%s doesn't support writing to a file; use -c option instead" %
189            frontName)
190
191
192if not MenuMaker.writeFullMenu and not writeToStdout:
193    fatal(
194        "-i MUST be used in conjunction with -c; this is done to prevent accidental overwriting of the custom menu")
195
196# Postpone the setup to the time when it's actually needed
197msg("* scanning")
198
199if noDesktop:
200    msg("  skipping desktop")
201    desktop = []
202else:
203    import Prophet.Desktop
204    desktop = Prophet.Desktop.scan()
205
206if noLegacy:
207    msg("  skipping legacy")
208    legacy = []
209else:
210    import Prophet.Legacy
211    Prophet.Legacy.setup()
212    legacy = Prophet.Legacy.scan()
213
214if noDebian:
215    msg("  skipping Debian")
216    debian = []
217else:
218    import Prophet.Debian
219    debian = Prophet.Debian.scan()
220
221merged = Prophet.merge(legacy + desktop + debian)
222
223msg("* generating")
224
225if not Prophet.skip[Prophet.App.Console]:
226    try:
227        MenuMaker.terminal = term()
228    except Prophet.NotSet:
229        fatal("specified terminal emulator couldn't be found; try another one")
230    except NameError:
231        warn("  no terminal emulator specified; will use the default")
232        found = False
233        for tcls, tname in terms:
234            try:
235                MenuMaker.terminal = tcls()
236                found = True
237                break
238            except Prophet.NotSet:
239                pass
240
241        if not found:
242            fatal("no suitable terminal emulator found")
243
244    msg("  using %s as terminal emulator" % MenuMaker.terminal.name)
245
246menu = MenuMaker.Root()
247
248if writeToStdout:
249    msg("* writing")
250    file = sys.stdout
251
252else:
253    msg("* writing to %s" % menuFile)
254    file = os.path.expanduser(menuFile)
255    dir = os.path.dirname(file)
256    if not os.path.isdir(dir):
257        msg("  creating missing directory %s" % dir)
258        os.makedirs(dir)
259    if not forceWrite and os.path.isfile(file):
260        fatal(
261            "refuse to overwrite existing file %s; either delete it or use -f option" %
262            file)
263    file = open(file, "wt")
264
265menu.distribute(merged)
266
267menu.arrange()
268
269for x in menu.emit(0):
270    file.write(x)
271    file.write("\n")
272
273msg("* done")
274