1import re 2import os.path 3import glob 4import fnmatch 5import codecs 6import Prophet 7 8from Paths import ValidPathSet as PS 9from Keywords import Keyword as Kw, Set as KwS 10from Prophet import NotSet 11from Prophet.Categories import * 12from Prophet import msg, verbose 13 14 15class DebianSet(PS): 16 17 def adopt(self, path): 18 path = super(DebianSet, self).adopt(path) 19 if len(glob.glob(os.path.join(path, "*"))): 20 return path # Directory should not be empty 21 raise ValueError 22 23 24dirs = DebianSet(("/etc/menu", "/usr/lib/menu", 25 "/usr/share/menu", "/usr/share/menu/default", "~/.menu")) 26 27# If dirs isn't empty then assume we're running Debian and therefore it's reasonable 28# to use local menu entries instead of cached ones 29if not len(dirs): 30 dirs = DebianSet((os.path.join(__path__[0], "menu"),)) 31 runDebian = False 32 33else: 34 runDebian = True 35 36 37def scan(): 38 """Scan through all containers and return a list of found valid entries""" 39 result = [] 40 msg(" debian...", newline=False) 41 for d in dirs: 42 for f in glob.glob(os.path.join(d, "*")): 43 if verbose > 1: 44 msg("\nparsing " + f) 45 try: 46 entries = _parse(f) 47 except NotSet as e: 48 if verbose > 1: 49 msg("REJECTED : " + str(e)) 50 else: 51 for e in entries: 52 try: 53 result.append(App(e)) 54 except Prophet.NotSet as e: 55 if verbose > 1: 56 msg("REJECTED : " + str(e)) 57 58 msg(" %d apps found" % len(result)) 59 return result 60 61 62def _parse(debmenu): 63 try: 64 # Latest Py3 versions in 64-bit mode sometimes have problems decoding Debian menus 65 # complaining about malformed utf-8 codes, therefore one has to enforce the 8-bit encoding. 66 # encoding= is not supported by Py2, so use codecs.open() as the least 67 # common denominator. 68 file = codecs.open(debmenu, "r", "latin1") # Py 2.4+ 69 except IOError: 70 raise NotSet("couldn't read the file " + debmenu) 71 entry = "" 72 entries = [] 73 next = False 74 try: 75 for x in file: 76 x = x.strip() 77 if next: 78 if not len(x): 79 next = False 80 continue 81 if not x.startswith("#"): 82 if x.endswith("\\"): 83 entry += " " + x.rstrip("\\") 84 next = True 85 else: 86 entry += " " + x 87 next = False 88 89 else: 90 if not len(x): 91 continue 92 if not x.startswith("#") and x.startswith("?"): 93 if len(entry): 94 entries += _parseEntry(entry) 95 if x.endswith("\\"): 96 entry = x.rstrip("\\") 97 next = True 98 else: 99 entry = x 100 next = False 101 102 except IOError: 103 raise NotSet("i/o error while reading the file " + debmenu) 104 except StopIteration: 105 pass 106 if len(entry): 107 entries += _parseEntry(entry) 108 return entries 109 110_package = re.compile('^\?\s*?package\s*?\((.*?)\)\s*?\:') 111_key = re.compile('(\w+)\s*=\s*(".*?"|\S+)') 112 113 114def _parseEntry(s): 115 entry = {} 116 x = _package.match(s) 117 if x: 118 entry["package"] = x.group(1) 119 for x in re.findall(_key, s): 120 entry[x[0]] = x[1].strip('"') 121 122 return [entry] 123 124 125class App(Prophet.App): 126 127 pref = 10 128 129 def __new__(cls, entry): 130 self = object.__new__(cls) 131 self.__setup__(entry) 132 return self 133 134 def __setup__(self, entry): 135 self.entry = entry 136 super(App, self).__setup__() 137 del self.entry 138 # TODO : filter out GNOME/KDE apps since they should be picked up by the .desktop scanner anyways 139 # To accomplish this scan title/longtitle/description/whatever for the 140 # clues (gnome-*, "Calculator for GNOME", etc.) 141 142 def setName(self): 143 try: 144 self.name = self.entry["title"] 145 except KeyError: 146 super(App, self).setName() 147 148 def setComment(self): 149 try: 150 self.comment = self.entry["longtitle"] 151 except KeyError: 152 try: 153 self.comment = self.entry["description"] 154 except KeyError: 155 super(App, self).setComment() 156 157 def setKeywords(self): 158 try: 159 sect = self.entry["section"] 160 except KeyError: 161 sect = "" 162 try: 163 hint = self.entry["hints"] 164 except KeyError: 165 hint = "" 166 kws = _deb2kws(sect, hint) 167 if not len(kws): 168 raise Prophet.NotSet("no keywords guessed") 169 self.keywords = kws 170 171 def setTerminal(self): 172 try: 173 needs = Kw(self.entry["needs"]) 174 if needs == Kw("X11"): 175 self.terminal = False 176 elif needs == Kw("text"): 177 self.terminal = True 178 else: 179 raise Prophet.NotSet("unsupported needs entry (%s)" % needs) 180 181 except KeyError: 182 super(App, self).setTerminal() 183 184 def setExename(self): 185 try: 186 cmd = self.entry["command"] 187 except KeyError: 188 raise Prophet.NotSet("no command entry found") 189 self.testGoodness(cmd) 190 scmd = cmd.split(" ", 1) 191 if len(scmd) >= 2: 192 cmd = scmd[0].strip() 193 args = scmd[1].strip() 194 for x in args.split(): 195 if fnmatch.fnmatch(x, "*/*/*") and not os.path.exists(x): 196 # An argument specified in the command line looks like a path. If it 197 # doesn't exist, the entire command is likely to be 198 # worthless, so throw it off 199 raise Prophet.NotSet( 200 "nonexistent path %s as command argument") 201 202 self.exeargs = args 203 if not runDebian: 204 # If we're not running Debian specified command may be located 205 # elsewhere therefore we have to scan PATH for it 206 cmd = os.path.basename(cmd) 207 # List of globs of unwanted commands 208 if cmd in ( 209 "sh", 210 "bash", 211 "csh", 212 "tcsh", 213 "zsh", 214 "ls", 215 "killall", 216 "echo", 217 "nice", 218 "xmessage", 219 "xlock"): 220 # Debian seems to have lots of shell commands. While it would be nice 221 # to try to analyze them, this isn't a priority since they're often 222 # too Debian-specific. 223 raise Prophet.NotSet("%s blacklisted" % cmd) 224 225 self.exename = cmd 226 227 228_secs = { 229 Kw("Appearance"): (Settings, DesktopSettings), 230 Kw("AI"): (Science,), 231 Kw("Databases"): (Database, Office), 232 Kw("Editors"): (TextEditor, Utility), 233 Kw("Educational"): (Education,), 234 Kw("Emulators"): (Emulator,), 235 Kw("Games"): (Game,), 236 Kw("Adventure"): (AdventureGame, Game), 237 Kw("Arcade"): (ArcadeGame, Game), 238 Kw("Board"): (BoardGame, Game), 239 Kw("Card"): (CardGame, Game), 240 Kw("Puzzles"): (LogicGame, Game), 241 Kw("Simulation"): (Simulation, Game), 242 Kw("Sports"): (SportsGame, Game), 243 Kw("Strategy"): (StrategyGame, Game), 244 Kw("Tetris-like"): (BlocksGame, Game), 245 Kw("Graphics"): (Graphics,), 246 Kw("Hamradio"): (HamRadio, Network), 247 Kw("Math"): (Math, Science), 248 Kw("Misc"): (Utility,), # ??? 249 Kw("Net"): (Network,), 250 Kw("Analog"): (Engineering, Network), 251 Kw("Headlines"): (News, Network), 252 Kw("Programming"): (Development,), 253 Kw("Shells"): (Shell, ConsoleOnly), # ??? 254 Kw("Sound"): (Audio, AudioVideo), 255 Kw("System"): (System,), 256 Kw("Admin"): (Settings, System), 257 258 # Kw("GNOME") : (GNOME, GTK), 259 Kw("Technical"): (Engineering,), 260 Kw("Text"): (TextEditor, Utility), 261 Kw("Tools"): (Utility,), 262 Kw("Viewers"): (Viewer, Graphics), 263 Kw("XawTV"): (TV, Viewer, Video, AudioVideo), 264 Kw("Toys"): (Amusement,), 265 Kw("Help"): (Utility, Kw("X-Help")), 266 Kw("Screen"): (Utility,), # ??? 267 268 # Kw("Lock") : (Utility, Kw("X-Lock")), 269 Kw("Saver"): (Screensaver, Utility), 270 Kw("System"): (System,), 271 Kw("XShells"): (Shell,) 272} 273 274_hints = { 275 Kw("3D"): (ActionGame, Game), # ??? 276 Kw("Bitmap"): (RasterGraphics, x2DGraphics, Graphics), 277 Kw("Boot"): (System, Utility), 278 Kw("Bug reporting"): (Development,), 279 Kw("Calculators"): (Calculator, Utility), 280 Kw("Clocks"): (Clock, Utility), 281 Kw("Config"): (Settings,), 282 Kw("Debuggers"): (Debugger, Development), 283 Kw("Documents"): (Office,), 284 Kw("Doom"): (ActionGame, Game), 285 Kw("Drawing"): (RasterGraphics, x2DGraphics, Graphics), 286 Kw("Vector"): (VectorGraphics, Graphics), 287 Kw("Equation"): (Math, Science), 288 Kw("Editor"): (WordProcessor, Office), 289 Kw("Formula"): (Presentation, Office), 290 Kw("Fonts"): (DesktopSettings, Settings), 291 Kw("Mail"): (Email, Office, Network), 292 Kw("Calendar"): (Calendar, Office), 293 Kw("Monitoring"): (Monitor, System), 294 Kw("IRC"): (IRCClient, Network), 295 Kw("ISDN"): (Telephony, Dialup, Network), # ??? 296 Kw("Images"): (x2DGraphics, Graphics), 297 Kw("Internet"): (Network,), 298 Kw("HTML"): (Network,), 299 Kw("Mahjongg"): (LogicGame, Game), 300 Kw("Mines"): (LogicGame, Game), 301 Kw("Mixers"): (Mixer, Audio), 302 Kw("Real-time"): (StrategyGame, Game), 303 Kw("Parallel"): (Math, Science, Network), 304 Kw("Distributed"): (Math, Science, Network), 305 Kw("PostScript"): (Presentation, Office), 306 Kw("Presentation"): (Presentation, Office), 307 Kw("SameGame"): (LogicGame, Game), 308 Kw("Screenshot"): (Graphics, Utility), 309 Kw("Setup"): (System,), 310 Kw("Install"): (System,), 311 Kw("Config"): (Settings,), 312 Kw("Spreadsheets"): (Spreadsheet, Office), 313 Kw("Terminal"): (TerminalEmulator), 314 Kw("Translation"): (Languages, Education), 315 Kw("Dictionary"): (Languages, Education), 316 Kw("Time"): (Clock, Utility), 317 Kw("Users"): (System, Settings), 318 Kw("VNC"): (Network,), 319 Kw("Video"): (Video, AudioVideo), 320 Kw("Web Browsers"): (WebBrowser, Network), 321 Kw("Word Processors"): (WordProcessor, Office) 322} 323 324_rejs = ( 325 Kw("Modules"), Kw("WindowManagers"), Kw( 326 "WorkSpace"), Kw("Lock"), Kw("GNOME") 327) 328 329 330def _deb2kws(sec, hint): 331 """Convert Debian style section/hints entries to an equivalent keyword set""" 332 kws = [] 333 for x in sec.split("/"): 334 x = Kw(x) 335 if x in _rejs: 336 raise Prophet.NotSet("section %s rejected" % x) 337 try: 338 kws += _secs[x] 339 except KeyError: 340 pass 341 342 for x in hint.split(","): 343 x = Kw(x) 344 try: 345 kws += _hints[x] 346 except KeyError: 347 pass 348 349 return KwS(*kws) 350