1# Copyright 2016 Ryan Dellenbaugh 2# 2017 Nick Boultbee 3# 4# This program is free software; you can redistribute it and/or modify 5# it under the terms of the GNU General Public License as published by 6# the Free Software Foundation; either version 2 of the License, or 7# (at your option) any later version. 8 9import random 10import time 11 12from quodlibet import _, print_d, print_w 13from quodlibet.plugins.query import QueryPlugin, QueryPluginError 14 15 16class PythonQuery(QueryPlugin): 17 PLUGIN_ID = "python_query" 18 PLUGIN_NAME = _("Python Query") 19 PLUGIN_DESC = _("Use Python expressions in queries. " 20 "Syntax is '@(python: expression)'. " 21 "The variable 's' (or 'a') " 22 "is the song / album being matched. " 23 "'_ts' is a (real number) timestamp at start of query. " 24 "Modules 'time' and 'random' are also available, " 25 "and the class 'Random' (==random.Random) too.") 26 key = 'python' 27 28 def __init__(self): 29 print_d("Initialising") 30 self._globals = {'random': random, 'Random': random.Random, 31 'time': time} 32 self._reported = set() 33 self._raw_body = None 34 35 def search(self, data, body): 36 try: 37 self._globals['s'] = data 38 # Albums can be queried too... 39 self._globals['a'] = data 40 # eval modifies the globals in place, it seems 41 ret = eval(body, dict(self._globals)) 42 return ret 43 except Exception as e: 44 key = str(e) 45 if key not in self._reported: 46 self._reported.add(key) 47 print_w("%s(%s) in expression '%s'. " 48 "Example failing data: %s" 49 % (type(e).__name__, key, self._raw_body, 50 self._globals)) 51 return False 52 53 def parse_body(self, body): 54 if body is None: 55 raise QueryPluginError 56 self._raw_body = body.strip() 57 self._reported.clear() 58 try: 59 self._globals.update(_ts=time.time()) 60 return compile(body.strip(), 'query', 'eval') 61 except SyntaxError as e: 62 print_w("Couldn't compile query (%s)" % e) 63 raise QueryPluginError 64