Blame plugins/commander/commander/commands/__init__.py

Packit 6978fb
# -*- coding: utf-8 -*-
Packit 6978fb
#
Packit 6978fb
#  __init__.py - commander
Packit 6978fb
#
Packit 6978fb
#  Copyright (C) 2010 - Jesse van den Kieboom
Packit 6978fb
#
Packit 6978fb
#  This program is free software; you can redistribute it and/or modify
Packit 6978fb
#  it under the terms of the GNU General Public License as published by
Packit 6978fb
#  the Free Software Foundation; either version 2 of the License, or
Packit 6978fb
#  (at your option) any later version.
Packit 6978fb
#
Packit 6978fb
#  This program is distributed in the hope that it will be useful,
Packit 6978fb
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6978fb
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 6978fb
#  GNU General Public License for more details.
Packit 6978fb
#
Packit 6978fb
#  You should have received a copy of the GNU General Public License
Packit 6978fb
#  along with this program; if not, write to the Free Software
Packit 6978fb
#  Foundation, Inc., 51 Franklin Street, Fifth Floor,
Packit 6978fb
#  Boston, MA 02110-1301, USA.
Packit 6978fb
Packit 6978fb
import os
Packit 6978fb
from gi.repository import GLib, GObject, Gio
Packit 6978fb
Packit 6978fb
import sys, bisect, types, shlex, re, os, traceback
Packit 6978fb
Packit 6978fb
from . import module, method, result, exceptions, metamodule
Packit 6978fb
Packit 6978fb
from commands.accel_group import AccelGroup
Packit 6978fb
from commands.accel_group import Accelerator
Packit 6978fb
Packit 6978fb
__all__ = ['is_commander_module', 'Commands', 'Accelerator']
Packit 6978fb
Packit 6978fb
import commander.modules
Packit 6978fb
Packit 6978fb
def attrs(**kwargs):
Packit 6978fb
    def generator(f):
Packit 6978fb
        for k in kwargs:
Packit 6978fb
            setattr(f, k, kwargs[k])
Packit 6978fb
Packit 6978fb
        return f
Packit 6978fb
Packit 6978fb
    return generator
Packit 6978fb
Packit 6978fb
def autocomplete(d={}, **kwargs):
Packit 6978fb
    ret = {}
Packit 6978fb
Packit 6978fb
    for dic in (d, kwargs):
Packit 6978fb
        for k in dic:
Packit 6978fb
            if type(dic[k]) == types.FunctionType:
Packit 6978fb
                ret[k] = dic[k]
Packit 6978fb
Packit 6978fb
    return attrs(autocomplete=ret)
Packit 6978fb
Packit 6978fb
def accelerator(*args, **kwargs):
Packit 6978fb
    return attrs(accelerator=Accelerator(args, kwargs))
Packit 6978fb
Packit 6978fb
def is_commander_module(mod):
Packit 6978fb
    if type(mod) == types.ModuleType:
Packit 6978fb
        return mod and ('__commander_module__' in mod.__dict__)
Packit 6978fb
    else:
Packit 6978fb
        mod = str(mod)
Packit 6978fb
        return mod.endswith('.py') or (os.path.isdir(mod) and os.path.isfile(os.path.join(mod, '__init__.py')))
Packit 6978fb
Packit 6978fb
class Singleton(object):
Packit 6978fb
    _instance = None
Packit 6978fb
Packit 6978fb
    def __new__(cls, *args, **kwargs):
Packit 6978fb
        if not cls._instance:
Packit 6978fb
            cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
Packit 6978fb
            cls._instance.__init_once__()
Packit 6978fb
Packit 6978fb
        return cls._instance
Packit 6978fb
Packit 6978fb
class Commands(Singleton):
Packit 6978fb
    class Continuated:
Packit 6978fb
        def __init__(self, generator):
Packit 6978fb
            self.generator = generator
Packit 6978fb
            self.retval = None
Packit 6978fb
Packit 6978fb
        def autocomplete_func(self):
Packit 6978fb
            mod = sys.modules['commander.commands.result']
Packit 6978fb
Packit 6978fb
            if self.retval == mod.Result.PROMPT:
Packit 6978fb
                return self.retval.autocomplete
Packit 6978fb
            else:
Packit 6978fb
                return {}
Packit 6978fb
Packit 6978fb
        def args(self):
Packit 6978fb
            return [], True
Packit 6978fb
Packit 6978fb
    class State:
Packit 6978fb
        def __init__(self):
Packit 6978fb
            self.clear()
Packit 6978fb
Packit 6978fb
        def clear(self):
Packit 6978fb
            self.stack = []
Packit 6978fb
Packit 6978fb
        def top(self):
Packit 6978fb
            return self.stack[0]
Packit 6978fb
Packit 6978fb
        def run(self, ret):
Packit 6978fb
            ct = self.top()
Packit 6978fb
Packit 6978fb
            if ret:
Packit 6978fb
                ct.retval = ct.generator.send(ret)
Packit 6978fb
            else:
Packit 6978fb
                ct.retval = next(ct.generator)
Packit 6978fb
Packit 6978fb
            return ct.retval
Packit 6978fb
Packit 6978fb
        def push(self, gen):
Packit 6978fb
            self.stack.insert(0, Commands.Continuated(gen))
Packit 6978fb
Packit 6978fb
        def pop(self):
Packit 6978fb
            if not self.stack:
Packit 6978fb
                return
Packit 6978fb
Packit 6978fb
            try:
Packit 6978fb
                self.stack[0].generator.close()
Packit 6978fb
            except GeneratorExit:
Packit 6978fb
                pass
Packit 6978fb
Packit 6978fb
            del self.stack[0]
Packit 6978fb
Packit 6978fb
        def __len__(self):
Packit 6978fb
            return len(self.stack)
Packit 6978fb
Packit 6978fb
        def __nonzero__(self):
Packit 6978fb
            return len(self) != 0
Packit 6978fb
Packit 6978fb
    def __init_once__(self):
Packit 6978fb
        self._modules = None
Packit 6978fb
        self._dirs = []
Packit 6978fb
        self._monitors = []
Packit 6978fb
        self._accel_group = None
Packit 6978fb
Packit 6978fb
        self._timeouts = {}
Packit 6978fb
Packit 6978fb
        self._stack = []
Packit 6978fb
Packit 6978fb
    def set_dirs(self, dirs):
Packit 6978fb
        self._dirs = dirs
Packit 6978fb
Packit 6978fb
    def stop(self):
Packit 6978fb
        for mon in self._monitors:
Packit 6978fb
            mon.cancel()
Packit 6978fb
Packit 6978fb
        self._monitors = []
Packit 6978fb
        self._modules = None
Packit 6978fb
Packit 6978fb
        for k in self._timeouts:
Packit 6978fb
            GLib.source_remove(self._timeouts[k])
Packit 6978fb
Packit 6978fb
        self._timeouts = {}
Packit 6978fb
Packit 6978fb
    def accelerator_activated(self, accel, mod, state, entry):
Packit 6978fb
        self.run(state, mod.execute('', [], entry, 0, accel.arguments))
Packit 6978fb
Packit 6978fb
    def scan_accelerators(self, modules=None):
Packit 6978fb
        if modules == None:
Packit 6978fb
            self._accel_group = AccelGroup()
Packit 6978fb
            modules = self.modules()
Packit 6978fb
Packit 6978fb
        recurse_mods = []
Packit 6978fb
Packit 6978fb
        for mod in modules:
Packit 6978fb
            if type(mod) == types.ModuleType:
Packit 6978fb
                recurse_mods.append(mod)
Packit 6978fb
            else:
Packit 6978fb
                accel = mod.accelerator()
Packit 6978fb
Packit 6978fb
                if accel != None:
Packit 6978fb
                    self._accel_group.add(accel, self.accelerator_activated, mod)
Packit 6978fb
Packit 6978fb
        for mod in recurse_mods:
Packit 6978fb
            self.scan_accelerators(mod.commands())
Packit 6978fb
Packit 6978fb
    def accelerator_group(self):
Packit 6978fb
        if not self._accel_group:
Packit 6978fb
            self.scan_accelerators()
Packit 6978fb
Packit 6978fb
        return self._accel_group
Packit 6978fb
Packit 6978fb
    def modules(self):
Packit 6978fb
        self.ensure()
Packit 6978fb
        return list(self._modules)
Packit 6978fb
Packit 6978fb
    def add_monitor(self, d):
Packit 6978fb
        gfile = Gio.file_new_for_path(d)
Packit 6978fb
        monitor = None
Packit 6978fb
Packit 6978fb
        try:
Packit 6978fb
            monitor = gfile.monitor_directory(Gio.FileMonitorFlags.NONE, None)
Packit 6978fb
        except Exception as e:
Packit 6978fb
            # Could not create monitor, this happens on systems where file monitoring is
Packit 6978fb
            # not supported, but we don't really care
Packit 6978fb
            pass
Packit 6978fb
Packit 6978fb
        if monitor:
Packit 6978fb
            monitor.connect('changed', self.on_monitor_changed)
Packit 6978fb
            self._monitors.append(monitor)
Packit 6978fb
Packit 6978fb
    def scan(self, d):
Packit 6978fb
        files = []
Packit 6978fb
Packit 6978fb
        try:
Packit 6978fb
            files = os.listdir(d)
Packit 6978fb
        except OSError:
Packit 6978fb
            pass
Packit 6978fb
Packit 6978fb
        for f in files:
Packit 6978fb
            full = os.path.join(d, f)
Packit 6978fb
Packit 6978fb
            # Test for python files or modules
Packit 6978fb
            if is_commander_module(full):
Packit 6978fb
                if self.add_module(full) and os.path.isdir(full):
Packit 6978fb
                    # Add monitor on the module directory if module was
Packit 6978fb
                    # successfully added. TODO: recursively add monitors
Packit 6978fb
                    self.add_monitor(full)
Packit 6978fb
Packit 6978fb
        # Add a monitor on the scanned directory itself
Packit 6978fb
        self.add_monitor(d)
Packit 6978fb
Packit 6978fb
    def module_name(self, filename):
Packit 6978fb
        # Module name is the basename without the .py
Packit 6978fb
        return os.path.basename(os.path.splitext(filename)[0])
Packit 6978fb
Packit 6978fb
    def add_module(self, filename):
Packit 6978fb
        base = self.module_name(filename)
Packit 6978fb
Packit 6978fb
        # Check if module already exists
Packit 6978fb
        if base in self._modules:
Packit 6978fb
            return
Packit 6978fb
Packit 6978fb
        # Create new 'empty' module
Packit 6978fb
        mod = module.Module(base, os.path.dirname(filename))
Packit 6978fb
        bisect.insort_right(self._modules, mod)
Packit 6978fb
Packit 6978fb
        # Reload the module
Packit 6978fb
        self.reload_module(mod)
Packit 6978fb
        return True
Packit 6978fb
Packit 6978fb
    def ensure(self):
Packit 6978fb
        # Ensure that modules have been scanned
Packit 6978fb
        if self._modules != None:
Packit 6978fb
            return
Packit 6978fb
Packit 6978fb
        self._modules = []
Packit 6978fb
Packit 6978fb
        for d in self._dirs:
Packit 6978fb
            self.scan(d)
Packit 6978fb
Packit 6978fb
    def _run_generator(self, state, ret=None):
Packit 6978fb
        mod = sys.modules['commander.commands.result']
Packit 6978fb
Packit 6978fb
        try:
Packit 6978fb
            # Determine first use
Packit 6978fb
            retval = state.run(ret)
Packit 6978fb
Packit 6978fb
            if not retval or (isinstance(retval, mod.Result) and (retval == mod.DONE or retval == mod.HIDE)):
Packit 6978fb
                state.pop()
Packit 6978fb
Packit 6978fb
                if state:
Packit 6978fb
                    return self._run_generator(state)
Packit 6978fb
Packit 6978fb
            return self.run(state, retval)
Packit 6978fb
Packit 6978fb
        except StopIteration:
Packit 6978fb
            state.pop()
Packit 6978fb
Packit 6978fb
            if state:
Packit 6978fb
                return self.run(state)
Packit 6978fb
        except Exception as e:
Packit 6978fb
            # Something error like, we throw on the parent generator
Packit 6978fb
            state.pop()
Packit 6978fb
Packit 6978fb
            if state:
Packit 6978fb
                state.top().generator.throw(type(e), e, e.__traceback__)
Packit 6978fb
            else:
Packit 6978fb
                # Re raise it for the top most to show the error
Packit 6978fb
                raise
Packit 6978fb
Packit 6978fb
        return None
Packit 6978fb
Packit 6978fb
    def run(self, state, ret=None):
Packit 6978fb
        mod = sys.modules['commander.commands.result']
Packit 6978fb
Packit 6978fb
        if type(ret) == types.GeneratorType:
Packit 6978fb
            # Ok, this is cool stuff, generators can ask and susped execution
Packit 6978fb
            # of commands, for instance to prompt for some more information
Packit 6978fb
            state.push(ret)
Packit 6978fb
Packit 6978fb
            return self._run_generator(state)
Packit 6978fb
        elif not isinstance(ret, mod.Result) and len(state) > 1:
Packit 6978fb
            # Basicly, send it to the previous?
Packit 6978fb
            state.pop()
Packit 6978fb
Packit 6978fb
            return self._run_generator(state, ret)
Packit 6978fb
        else:
Packit 6978fb
            return ret
Packit 6978fb
Packit 6978fb
    def execute(self, state, argstr, words, wordsstr, entry, modifier):
Packit 6978fb
        self.ensure()
Packit 6978fb
Packit 6978fb
        if state:
Packit 6978fb
            return self._run_generator(state, [argstr, words, modifier])
Packit 6978fb
Packit 6978fb
        cmd = completion.single_command(wordsstr, 0)
Packit 6978fb
Packit 6978fb
        if not cmd:
Packit 6978fb
            raise exceptions.Execute('Could not find command: ' + wordsstr[0])
Packit 6978fb
Packit 6978fb
        if len(words) > 1:
Packit 6978fb
            argstr = argstr[words[1].start(0):]
Packit 6978fb
        else:
Packit 6978fb
            argstr = ''
Packit 6978fb
Packit 6978fb
        # Execute command
Packit 6978fb
        return self.run(state, cmd.execute(argstr, wordsstr[1:], entry, modifier))
Packit 6978fb
Packit 6978fb
    def invoke(self, entry, modifier, command, args, argstr=None):
Packit 6978fb
        self.ensure()
Packit 6978fb
Packit 6978fb
        cmd = completion.single_command([command], 0)
Packit 6978fb
Packit 6978fb
        if not cmd:
Packit 6978fb
            raise exceptions.Execute('Could not find command: ' + command)
Packit 6978fb
Packit 6978fb
        if argstr == None:
Packit 6978fb
            argstr = ' '.join(args)
Packit 6978fb
Packit 6978fb
        ret = cmd.execute(argstr, args, entry, modifier)
Packit 6978fb
Packit 6978fb
        if type(ret) == types.GeneratorType:
Packit 6978fb
            raise exceptions.Execute('Cannot invoke commands that yield (yet)')
Packit 6978fb
        else:
Packit 6978fb
            return ret
Packit 6978fb
Packit 6978fb
    def resolve_module(self, path, load=True):
Packit 6978fb
        if not self._modules or not is_commander_module(path):
Packit 6978fb
            return None
Packit 6978fb
Packit 6978fb
        # Strip off __init__.py for module kind of modules
Packit 6978fb
        if path.endswith('__init__.py'):
Packit 6978fb
            path = os.path.dirname(path)
Packit 6978fb
Packit 6978fb
        base = self.module_name(path)
Packit 6978fb
Packit 6978fb
        # Find module
Packit 6978fb
        idx = bisect.bisect_left(self._modules, base)
Packit 6978fb
        mod = None
Packit 6978fb
Packit 6978fb
        if idx < len(self._modules):
Packit 6978fb
            mod = self._modules[idx]
Packit 6978fb
Packit 6978fb
        if not mod or mod.name != base:
Packit 6978fb
            if load:
Packit 6978fb
                self.add_module(path)
Packit 6978fb
Packit 6978fb
            return None
Packit 6978fb
Packit 6978fb
        return mod
Packit 6978fb
Packit 6978fb
    def remove_module_accelerators(self, modules):
Packit 6978fb
        recurse_mods = []
Packit 6978fb
Packit 6978fb
        for mod in modules:
Packit 6978fb
            if type(mod) == types.ModuleType:
Packit 6978fb
                recurse_mods.append(mod)
Packit 6978fb
            else:
Packit 6978fb
                accel = mod.accelerator()
Packit 6978fb
Packit 6978fb
                if accel != None:
Packit 6978fb
                    self._accel_group.remove(accel)
Packit 6978fb
Packit 6978fb
        for mod in recurse_mods:
Packit 6978fb
            self.remove_module_accelerators(mod.commands())
Packit 6978fb
Packit 6978fb
    def remove_module(self, mod):
Packit 6978fb
        # Remove roots
Packit 6978fb
        for r in mod.roots():
Packit 6978fb
            if r in self._modules:
Packit 6978fb
                self._modules.remove(r)
Packit 6978fb
Packit 6978fb
        # Remove accelerators
Packit 6978fb
        if self._accel_group:
Packit 6978fb
            self.remove_module_accelerators([mod])
Packit 6978fb
Packit 6978fb
        if mod.name in commander.modules.__dict__:
Packit 6978fb
            del commander.modules.__dict__[mod.name]
Packit 6978fb
Packit 6978fb
    def reload_module(self, mod):
Packit 6978fb
        if isinstance(mod, str):
Packit 6978fb
            mod = self.resolve_module(mod)
Packit 6978fb
Packit 6978fb
        if not mod or not self._modules:
Packit 6978fb
            return
Packit 6978fb
Packit 6978fb
        # Remove roots
Packit 6978fb
        self.remove_module(mod)
Packit 6978fb
Packit 6978fb
        # Now, try to reload the module
Packit 6978fb
        try:
Packit 6978fb
            mod.reload()
Packit 6978fb
        except Exception as e:
Packit 6978fb
            # Reload failed, we remove the module
Packit 6978fb
            info = traceback.format_tb(sys.exc_info()[2])
Packit 6978fb
            print('Failed to reload module ({0}):\n  {1}'.format(mod.name, info[-1]))
Packit 6978fb
Packit 6978fb
            self._modules.remove(mod)
Packit 6978fb
            return
Packit 6978fb
Packit 6978fb
        # Insert roots
Packit 6978fb
        for r in mod.roots():
Packit 6978fb
            bisect.insort(self._modules, r)
Packit 6978fb
Packit 6978fb
        commander.modules.__dict__[mod.name] = metamodule.MetaModule(mod.mod)
Packit 6978fb
Packit 6978fb
        if self._accel_group:
Packit 6978fb
            self.scan_accelerators([mod])
Packit 6978fb
Packit 6978fb
    def on_timeout_delete(self, path, mod):
Packit 6978fb
        if not path in self._timeouts:
Packit 6978fb
            return False
Packit 6978fb
Packit 6978fb
        # Remove the module
Packit 6978fb
        mod.unload()
Packit 6978fb
        self.remove_module(mod)
Packit 6978fb
        self._modules.remove(mod)
Packit 6978fb
Packit 6978fb
        return False
Packit 6978fb
Packit 6978fb
    def on_monitor_changed(self, monitor, gfile1, gfile2, evnt):
Packit 6978fb
        if evnt == Gio.FileMonitorEvent.CHANGED:
Packit 6978fb
            # Reload the module
Packit 6978fb
            self.reload_module(gfile1.get_path())
Packit 6978fb
        elif evnt == Gio.FileMonitorEvent.DELETED:
Packit 6978fb
            path = gfile1.get_path()
Packit 6978fb
            mod = self.resolve_module(path, False)
Packit 6978fb
Packit 6978fb
            if not mod:
Packit 6978fb
                return
Packit 6978fb
Packit 6978fb
            if path in self._timeouts:
Packit 6978fb
                GLib.source_remove(self._timeouts[path])
Packit 6978fb
Packit 6978fb
            # We add a timeout because a common save strategy causes a
Packit 6978fb
            # DELETE/CREATE event chain
Packit 6978fb
            self._timeouts[path] = GLib.timeout_add(500, self.on_timeout_delete, path, mod)
Packit 6978fb
        elif evnt == Gio.FileMonitorEvent.CREATED:
Packit 6978fb
            path = gfile1.get_path()
Packit 6978fb
Packit 6978fb
            # Check if this CREATE followed a previous DELETE
Packit 6978fb
            if path in self._timeouts:
Packit 6978fb
                GLib.source_remove(self._timeouts[path])
Packit 6978fb
                del self._timeouts[path]
Packit 6978fb
Packit 6978fb
            # Reload the module
Packit 6978fb
            self.reload_module(path)
Packit 6978fb
Packit 6978fb
# ex:ts=4:et