Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/tutorius/addon.py
blob: 8aa7b58524a5e6abc10ac97898c957ecceb2b230 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# Copyright (C) 2009, Tutorius.org
# Copyright (C) 2009, Simon Poirier <simpoir@gmail.com>
#
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
"""
This module manages the loading and listing of tutorius addons.
Addons are modular actions and events that are package in such a way that they
can be autodetected and can integrate with Tutorius components (the editor)
without any configuration or explicit dependencies (python import).

An action addon is expected to have a metadata dict such as this one:
__action__ = {
    "name" : "HelloWorld",
    "display_name" : "Hello World!",
    "icon" : "hello",
    "class" : HelloAction,
    "mandatory_props" : ["text"],
}
"""

import os
import sys
import re
import logging

PREFIX = __name__+"s"
PATH = re.sub("addon\\.py[c]$", "", __file__)+"addons"

TYPE_ACTION = 'action'
TYPE_EVENT = 'event'

STAT_MODIFY = 8

# whether to auto reload modules on modifications
AUTO_RELOAD = True

__cache__ = {}

def _reload_addons():
    """
    Scan for all add-ons under the tutorius 'addons' folder and (re)cache
    all modules found.
    """
    __cache__.clear()
    for addon in (x for x in os.listdir(PATH) if x.endswith("py")):
        mod_name = PREFIX+'.'+re.sub("\\.py$", "", addon)
        _reload_module(mod_name)

def _reload_module(mod_name):
    """
    Reload a specific module and update the cache. If the module changed
    the module will be reloaded and future calls to create() will use
    the new module. Existing instances won't be changed.

    @param mod_name: the python name of the module.
                     Ex: 'sugar.tutorius.addons.foo'
    """
    # ensure module reloaded, by removing sys reference
    sys.modules.pop(mod_name, False)

    mod = __import__(name=mod_name, fromlist=[""])
    if hasattr(mod, "__action__"):
        __cache__[mod.__action__['name']] = mod.__action__
        mod.__action__['type'] = TYPE_ACTION
        mod.__action__['ts'] = os.stat(mod.__file__)[STAT_MODIFY]
    elif hasattr(mod, "__event__"):
        __cache__[mod.__event__['name']] = mod.__event__
        mod.__event__['type'] = TYPE_EVENT
        mod.__event__['ts'] = os.stat(mod.__file__)[STAT_MODIFY]

def create(name, *args, **kwargs):
    """
    Create an instance of an addon from its name.
    If it cannot be instanciated for whatever reason, a message will be logged
    to the console.

    Note: properties can usually be initialized kwargs having the same name.
    However, args and kwargs are always optionals.

    @param name: the short name of the addon, as found in the 'name' key
                 of the __action__ or __event__ dict.
    @returns: an instance of the add-on or None.
    """
    if (not __cache__) or (name not in __cache__):
        _reload_addons()
    try:
        metadata = __cache__[name]
        module_name = metadata['class'].__module__
        module_file = sys.modules[module_name].__file__
        if AUTO_RELOAD and metadata['ts'] != os.stat(module_file)[STAT_MODIFY]:
            _reload_module(module_name)
            metadata = __cache__[name]

        try:
            return metadata['class'](*args, **kwargs)
        except TypeError:
            logging.error("Could not instantiate %s with parameters %s, %s" \
                          %(metadata['name'],str(args), str(kwargs)))
            return None
    except KeyError:
        logging.error("Addon not found for class '%s'", name)
        return None

def list_addons():
    """
    Returns a list of all loaded add-ons, filling the cache if necesary.

    @returns: a list of addon names.
    """
    if not __cache__:
        _reload_addons()
    return __cache__.keys()

def get_addon_meta(name):
    """
    Returns an add-on's meta data from its name.
    @param name: the short name of the add-on, as found in the 'name' metadata.
    @returns: a reference to the dictionary of meta data.
    """
    if not __cache__ or (name not in __cache__):
        _reload_addons()
    return __cache__[name]

def get_name_from_type(typ):
    """
    Lookup an addon's name from the type of an object returned by create().
    Note: After refreshing the cache, this function should still return the
    correct name of old instances but calls using that name will refer to
    the new code.

    @param typ: the type (class) of an add-on.
    @returns: the short name of the add-on, as used in this module's functions.
    """
    mod = sys.modules[typ.__module__]
    meta = getattr(mod, '__action__', None) or getattr(mod, '__event__')
    return meta['name']

# vim:set ts=4 sts=4 sw=4 et: