<commit_before># -*- coding: utf-8 -*-
from os import path
import imp
import inspect
import pkg_resources
import traceback
import sys

from twisted.internet import reactor

from ..events import Hook, ServerInput, ServerStarted, ServerStopping


class PluginLoadError(Exception):
    def __init__(self, message, exc=None):
        self.message = message
        self.exc = exc

    def format(self, name):
        l = ["{0}: {1}".format(name, self.message)]
        if self.exc:
            l += ''.join(traceback.format_exception(*self.exc)).split('\n')
        return l


class PluginLoader(object):
    def __init__(self, search_path):
        self.search_path = search_path


class ResourcePluginLoader(PluginLoader):
    def load_plugin(self, name):
        p = path.join(self.search_path, name + '.py')
        if not pkg_resources.resource_exists('mk2', p):
            return False

        try:
            module = imp.load_source(name, pkg_resources.resource_filename('mk2', p))
            classes = inspect.getmembers(module, inspect.isclass)
            for n, cls in classes:
                if issubclass(cls, Plugin) and not cls is Plugin:
                    return cls
            #if we've reached this point, there's no subclass of Plugin in the file!
            raise PluginLoadError("a file for '{0}' exists, but there is no plugin in it".format(name))
        except Exception:
            raise PluginLoadError("'{0}'' failed to load".format(name), sys.exc_info())

    def find_plugins(self):
        for f in pkg_resources.resource_listdir('mk2', self.search_path):
            if pkg_resources.resource_isdir('mk2', path.join(self.search_path, f)):
                continue
            if f.endswith('.py') and f != '__init__.py':
                yield f[:-3]


class EntryPointPluginLoader(PluginLoader):
    def load_plugin(self, name):
        pl = list(pkg_resources.iter_entry_points('mark2.{0}'.format(self.search_path), name))
        if len(pl) == 0:
            return False
        elif len(pl) > 1:
            def_list = ', '.join(ep.dist.project_name for ep in pl)
            raise PluginLoadError("{0} is multiply provided (by {1})".format(name, def_list))
        # we've established that pl contains exactly one EntryPoint
        ep = pl[0]
        try:
            ep.require()
        except ImportError:
            raise PluginLoadError("couldn't load requirements for {0}".format(name), sys.exc_info())
        try:
            # force reloading
            if ep.module_name in sys.modules:
                del sys.modules[ep.module_name]
            cls = ep.load(require=False)
        except ImportError:
            raise PluginLoadError("couldn't load '{0}'".format(name), sys.exc_info())
        # cls must be a Plugin subclass
        if not issubclass(cls, Plugin):
            raise PluginLoadError("'{0}' was advertised, but is not a Plugin".format(name))
        return cls

    def find_plugins(self):
        for ep in pkg_resources.iter_entry_points('mark2.{0}'.format(self.search_path)):
            yield ep.name


class Plugin:
    restore = tuple()

    def __init__(self, parent, name, **kwargs):
        self.parent = parent
        self.name = name
        
        self.config             = self.parent.config
        self.console            = self.parent.console
        self.fatal_error        = self.parent.fatal_error
        self.dispatch           = self.parent.events.dispatch
        self.dispatch_delayed   = self.parent.events.dispatch_delayed
        self.dispatch_repeating = self.parent.events.dispatch_repeating
        
        self._tasks = []
        self._events = []
        self._services = []
    
        for n, v in kwargs.iteritems():
            setattr(self, n, v)
        
        self.register(self.server_started, ServerStarted)
        self.register(self.server_stopping, ServerStopping)
        
        self.setup()

    #called on plugin load
    def setup(self):
        pass

    #called on plugin unload
    def teardown(self):
        pass
    
    def server_started(self, event):
        pass
    
    def server_stopping(self, event):
        self.stop_tasks()
    
    def register(self, *a, **k):
        track = True
        if 'track' in k:
            track = k['track']
            del k['track']

        ident = self.parent.events.register(*a, **k)

        if track:
            self._events.append(ident)
            
        return ident

    def unregister(self, ident):
        self.parent.events.unregister(ident)
        if ident in self._events:
            self._events.remove(ident)

    def unregister_all(self):
        for ident in list(self._events):
            self.unregister(ident)
    
    def save_state(self):
        return [getattr(self, k) for k in self.restore]

    def load_state(self, state):
        [setattr(self, k, state.pop(0)) for k in self.restore]
    <commit_msg>plugins: use a dict instead of list for the default load/save_state<commit_after># -*- coding: utf-8 -*-
from os import path
import imp
import inspect
import pkg_resources
import traceback
import sys

from twisted.internet import reactor

from ..events import Hook, ServerInput, ServerStarted, ServerStopping


class PluginLoadError(Exception):
    def __init__(self, message, exc=None):
        self.message = message
        self.exc = exc

    def format(self, name):
        l = ["{0}: {1}".format(name, self.message)]
        if self.exc:
            l += ''.join(traceback.format_exception(*self.exc)).split('\n')
        return l


class PluginLoader(object):
    def __init__(self, search_path):
        self.search_path = search_path


class ResourcePluginLoader(PluginLoader):
    def load_plugin(self, name):
        p = path.join(self.search_path, name + '.py')
        if not pkg_resources.resource_exists('mk2', p):
            return False

        try:
            module = imp.load_source(name, pkg_resources.resource_filename('mk2', p))
            classes = inspect.getmembers(module, inspect.isclass)
            for n, cls in classes:
                if issubclass(cls, Plugin) and not cls is Plugin:
                    return cls
            #if we've reached this point, there's no subclass of Plugin in the file!
            raise PluginLoadError("a file for '{0}' exists, but there is no plugin in it".format(name))
        except Exception:
            raise PluginLoadError("'{0}'' failed to load".format(name), sys.exc_info())

    def find_plugins(self):
        for f in pkg_resources.resource_listdir('mk2', self.search_path):
            if pkg_resources.resource_isdir('mk2', path.join(self.search_path, f)):
                continue
            if f.endswith('.py') and f != '__init__.py':
                yield f[:-3]


class EntryPointPluginLoader(PluginLoader):
    def load_plugin(self, name):
        pl = list(pkg_resources.iter_entry_points('mark2.{0}'.format(self.search_path), name))
        if len(pl) == 0:
            return False
        elif len(pl) > 1:
            def_list = ', '.join(ep.dist.project_name for ep in pl)
            raise PluginLoadError("{0} is multiply provided (by {1})".format(name, def_list))
        # we've established that pl contains exactly one EntryPoint
        ep = pl[0]
        try:
            ep.require()
        except ImportError:
            raise PluginLoadError("couldn't load requirements for {0}".format(name), sys.exc_info())
        try:
            # force reloading
            if ep.module_name in sys.modules:
                del sys.modules[ep.module_name]
            cls = ep.load(require=False)
        except ImportError:
            raise PluginLoadError("couldn't load '{0}'".format(name), sys.exc_info())
        # cls must be a Plugin subclass
        if not issubclass(cls, Plugin):
            raise PluginLoadError("'{0}' was advertised, but is not a Plugin".format(name))
        return cls

    def find_plugins(self):
        for ep in pkg_resources.iter_entry_points('mark2.{0}'.format(self.search_path)):
            yield ep.name


class Plugin:
    restore = tuple()

    def __init__(self, parent, name, **kwargs):
        self.parent = parent
        self.name = name
        
        self.config             = self.parent.config
        self.console            = self.parent.console
        self.fatal_error        = self.parent.fatal_error
        self.dispatch           = self.parent.events.dispatch
        self.dispatch_delayed   = self.parent.events.dispatch_delayed
        self.dispatch_repeating = self.parent.events.dispatch_repeating
        
        self._tasks = []
        self._events = []
        self._services = []
    
        for n, v in kwargs.iteritems():
            setattr(self, n, v)
        
        self.register(self.server_started, ServerStarted)
        self.register(self.server_stopping, ServerStopping)
        
        self.setup()

    #called on plugin load
    def setup(self):
        pass

    #called on plugin unload
    def teardown(self):
        pass
    
    def server_started(self, event):
        pass
    
    def server_stopping(self, event):
        self.stop_tasks()
    
    def register(self, *a, **k):
        track = True
        if 'track' in k:
            track = k['track']
            del k['track']

        ident = self.parent.events.register(*a, **k)

        if track:
            self._events.append(ident)
            
        return ident

    def unregister(self, ident):
        self.parent.events.unregister(ident)
        if ident in self._events:
            self._events.remove(ident)

    def unregister_all(self):
        for ident in list(self._events):
            self.unregister(ident)
    
    def save_state(self):
        return dict((k, getattr(self, k)) for k in self.restore)

    def load_state(self, state):
        [setattr(self, k, v) for k, v in state.iteritems()]
    
{'fraction_non_alphanumeric': 0.10632067851373182, 'fraction_numerical': 0.0045436187399030695, 'mean_word_length': 2.131520708188429, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4517995', 'n_tokens_mistral': 2908, 'n_tokens_neox': 2806, 'n_words': 746}