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