<commit_before>"""
Internal hook annotation, representation and calling machinery.
"""
import inspect
import warnings
from .callers import _legacymulticall, _multicall

class HookspecMarker(object):
    """ Decorator helper class for marking functions as hook specifications.

    You can instantiate it with a project_name to get a decorator.
    Calling PluginManager.add_hookspecs later will discover all marked functions
    if the PluginManager uses the same project_name.
    """

    def __init__(self, project_name):
        self.project_name = project_name

    def __call__(self, function=None, firstresult=False, historic=False, warn_on_impl=None):
        """ if passed a function, directly sets attributes on the function
        which will make it discoverable to add_hookspecs().  If passed no
        function, returns a decorator which can be applied to a function
        later using the attributes supplied.

        If firstresult is True the 1:N hook call (N being the number of registered
        hook implementation functions) will stop at I<=N when the I'th function
        returns a non-None result.

        If historic is True calls to a hook will be memorized and replayed
        on later registered plugins.

        """
        def setattr_hookspec_opts(func):
            if historic and firstresult:
                raise ValueError("cannot have a historic firstresult hook")
            setattr(func, self.project_name + "_spec",
                    dict(firstresult=firstresult, historic=historic,
                         warn_on_impl=warn_on_impl,))
            return func

        if function is not None:
            return setattr_hookspec_opts(function)
        else:
            return setattr_hookspec_opts

class HookimplMarker(object):
    """ Decorator helper class for marking functions as hook implementations.

    You can instantiate with a project_name to get a decorator.
    Calling PluginManager.register later will discover all marked functions
    if the PluginManager uses the same project_name.
    """
    def __init__(self, project_name):
        self.project_name = project_name

    def __call__(self, function=None, hookwrapper=False, optionalhook=False,
                 tryfirst=False, trylast=False):

        """ if passed a function, directly sets attributes on the function
        which will make it discoverable to register().  If passed no function,
        returns a decorator which can be applied to a function later using
        the attributes supplied.

        If optionalhook is True a missing matching hook specification will not result
        in an error (by default it is an error if no matching spec is found).

        If tryfirst is True this hook implementation will run as early as possible
        in the chain of N hook implementations for a specfication.

        If trylast is True this hook implementation will run as late as possible
        in the chain of N hook implementations.

        If hookwrapper is True the hook implementations needs to execute exactly
        one "yield".  The code before the yield is run early before any non-hookwrapper
        function is run.  The code after the yield is run after all non-hookwrapper
        function have run.  The yield receives a ``_Result`` object representing
        the exception or result outcome of the inner calls (including other hookwrapper
        calls).

        """
        def setattr_hookimpl_opts(func):
            setattr(func, self.project_name + "_impl",
                    dict(hookwrapper=hookwrapper, optionalhook=optionalhook,
                         tryfirst=tryfirst, trylast=trylast))
            return func

        if function is None:
            return setattr_hookimpl_opts
        else:
            return setattr_hookimpl_opts(function)

def normalize_hookimpl_opts(opts):
    opts.setdefault("tryfirst", False)
    opts.setdefault("trylast", False)
    opts.setdefault("hookwrapper", False)
    opts.setdefault("optionalhook", False)

if hasattr(inspect, 'getfullargspec'):
    def _getargspec(func):
        return inspect.getfullargspec(func)
else:
    def _getargspec(func):
        return inspect.getargspec(func)

def varnames(func):
    """Return tuple of positional and keywrord argument names for a function,
    method, class or callable.

    In case of a class, its ``__init__`` method is considered.
    For methods the ``self`` parameter is not included.
    """
    cache = getattr(func, "__dict__", {})
    try:
        return cache["_varnames"]
    except KeyError:
        pass

    if inspect.isclass(func):
        try:
            func = func.__init__
        except AttributeError:
            return (), ()
    elif not inspect.isroutine(func):  # callable object?
        try:
            func = getattr(func, '__call__', func)
        except Exception:
            return ()

    try:  # func MUST be a function or method here or we won't parse any args
        spec = _getargspec(func)
    except TypeError:
        return (), ()

    args, defaults = tuple(spec.args), spec.defaults
    if defaults:
        index = -len(defaults)
        args, defaults = args[:index], tuple(args[index:])
    else:
        defaults = ()

    # strip any implicit instance arg
    if args:
        if inspect.ismethod(func) or (
            '.' in getattr(func, '__qualname__', ()) and args[0] == 'self'
        ):
            args = args[1:]

    assert "self" not in args  # best naming practises check?
    try:
        cache["_varnames"] = args, defaults
    except TypeError:
        pass
    return args, defaults

class _HookRelay(object):
    """ hook holder object for performing 1:N hook calls where N is the number
    of registered plugins.

    """

    def __init__(self, trace):
        self._trace = trace

class _HookCaller(object):
    def __init__(self, name, hook_execute, specmodule_or_class=None,
                 spec_opts=None):
        self.name = name
        self._wrappers = []
        self._nonwrappers = []
        self._hookexec = hook_execute
        self._specmodule_or_class = None
        self.argnames = None
        self.kwargnames = None
        self.multicall = _multicall
        self.spec_opts = spec_opts or {}
        if specmodule_or_class is not None:
            self.set_specification(specmodule_or_class, spec_opts)

    def has_spec(self):
        return self._specmodule_or_class is not None

    def set_specification(self, specmodule_or_class, spec_opts):
        assert not self.has_spec()
        self._specmodule_or_class = specmodule_or_class
        specfunc = getattr(specmodule_or_class, self.name)
        # get spec arg signature
        argnames, self.kwargnames = varnames(specfunc)
        self.argnames = ["__multicall__"] + list(argnames)
        self.spec_opts.update(spec_opts)
        if spec_opts.get("historic"):
            self._call_history = []
        self.warn_on_impl = spec_opts.get('warn_on_impl')

    def is_historic(self):
        return hasattr(self, "_call_history")

    def _remove_plugin(self, plugin):
        def remove(wrappers):
            for i, method in enumerate(wrappers):
                if method.plugin == plugin:
                    del wrappers[i]
                    return True
        if remove(self._wrappers) is None:
            if remove(self._nonwrappers) is None:
                raise ValueError("plugin %r not found" % (plugin,))

    def _add_hookimpl(self, hookimpl):
        """Add an implementation to the callback chain.
        """
        if hookimpl.hookwrapper:
            methods = self._wrappers
        else:
            methods = self._nonwrappers

        if hookimpl.trylast:
            methods.insert(0, hookimpl)
        elif hookimpl.tryfirst:
            methods.append(hookimpl)
        else:
            # find last non-tryfirst method
            i = len(methods) - 1
            while i >= 0 and methods[i].tryfirst:
                i -= 1
            methods.insert(i + 1, hookimpl)

        if '__multicall__' in hookimpl.argnames:
            warnings.warn(
                "Support for __multicall__ is now deprecated and will be"
                "removed in an upcoming release.",
                DeprecationWarning
            )
            self.multicall = _legacymulticall

    def __repr__(self):
        return "<_HookCaller %r>" % (self.name,)

    def __call__(self, *args, **kwargs):
        if args:
            raise TypeError("hook calling supports only keyword arguments")
        assert not self.is_historic()
        if self.argnames:
            notincall = set(self.argnames) - set(['__multicall__']) - set(
                kwargs.keys())
            if notincall:
                warnings.warn(
                    "Argument(s) {} which are declared in the hookspec "
                    "can not be found in this hook call"
                    .format(tuple(notincall)),
                    stacklevel=2,
                )
        return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)

    def call_historic(self, result_callback=None, kwargs=None, proc=None):
        """Call the hook with given ``kwargs`` for all registered plugins and
        for all plugins which will be registered afterwards.

        If ``result_callback`` is not ``None`` it will be called for for each
        non-None result obtained from a hook implementation.

        .. note::
            The ``proc`` argument is now deprecated.
        """
        if proc is not None:
            warnings.warn(
                "Support for `proc` argument is now deprecated and will be"
                "removed in an upcoming release.",
                DeprecationWarning
            )
            result_callback = proc

        self._call_history.append((kwargs or {}, result_callback))
        # historizing hooks don't return results
        res = self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
        if result_callback is None:
            return
        # XXX: remember firstresult isn't compat with historic
        for x in res or []:
            result_callback(x)

    def call_extra(self, methods, kwargs):
        """ Call the hook with some additional temporarily participating
        methods using the specified kwargs as call parameters. """
        old = list(self._nonwrappers), list(self._wrappers)
        for method in methods:
            opts = dict(hookwrapper=False, trylast=False, tryfirst=False)
            hookimpl = HookImpl(None, "<temp>", method, opts)
            self._add_hookimpl(hookimpl)
        try:
            return self(**kwargs)
        finally:
            self._nonwrappers, self._wrappers = old

    def _maybe_apply_history(self, method):
        """Apply call history to a new hookimpl if it is marked as historic.
        """
        if self.is_historic():
            for kwargs, result_callback in self._call_history:
                res = self._hookexec(self, [method], kwargs)
                if res and result_callback is not None:
                    result_callback(res[0])

class HookImpl(object):
    def __init__(self, plugin, plugin_name, function, hook_impl_opts):
        self.function = function
        self.argnames, self.kwargnames = varnames(self.function)
        self.plugin = plugin
        self.opts = hook_impl_opts
        self.plugin_name = plugin_name
        self.__dict__.update(hook_impl_opts)
<commit_msg>Add HookImpl.__repr__<commit_after>"""
Internal hook annotation, representation and calling machinery.
"""
import inspect
import warnings
from .callers import _legacymulticall, _multicall

class HookspecMarker(object):
    """ Decorator helper class for marking functions as hook specifications.

    You can instantiate it with a project_name to get a decorator.
    Calling PluginManager.add_hookspecs later will discover all marked functions
    if the PluginManager uses the same project_name.
    """

    def __init__(self, project_name):
        self.project_name = project_name

    def __call__(self, function=None, firstresult=False, historic=False, warn_on_impl=None):
        """ if passed a function, directly sets attributes on the function
        which will make it discoverable to add_hookspecs().  If passed no
        function, returns a decorator which can be applied to a function
        later using the attributes supplied.

        If firstresult is True the 1:N hook call (N being the number of registered
        hook implementation functions) will stop at I<=N when the I'th function
        returns a non-None result.

        If historic is True calls to a hook will be memorized and replayed
        on later registered plugins.

        """
        def setattr_hookspec_opts(func):
            if historic and firstresult:
                raise ValueError("cannot have a historic firstresult hook")
            setattr(func, self.project_name + "_spec",
                    dict(firstresult=firstresult, historic=historic,
                         warn_on_impl=warn_on_impl,))
            return func

        if function is not None:
            return setattr_hookspec_opts(function)
        else:
            return setattr_hookspec_opts

class HookimplMarker(object):
    """ Decorator helper class for marking functions as hook implementations.

    You can instantiate with a project_name to get a decorator.
    Calling PluginManager.register later will discover all marked functions
    if the PluginManager uses the same project_name.
    """
    def __init__(self, project_name):
        self.project_name = project_name

    def __call__(self, function=None, hookwrapper=False, optionalhook=False,
                 tryfirst=False, trylast=False):

        """ if passed a function, directly sets attributes on the function
        which will make it discoverable to register().  If passed no function,
        returns a decorator which can be applied to a function later using
        the attributes supplied.

        If optionalhook is True a missing matching hook specification will not result
        in an error (by default it is an error if no matching spec is found).

        If tryfirst is True this hook implementation will run as early as possible
        in the chain of N hook implementations for a specfication.

        If trylast is True this hook implementation will run as late as possible
        in the chain of N hook implementations.

        If hookwrapper is True the hook implementations needs to execute exactly
        one "yield".  The code before the yield is run early before any non-hookwrapper
        function is run.  The code after the yield is run after all non-hookwrapper
        function have run.  The yield receives a ``_Result`` object representing
        the exception or result outcome of the inner calls (including other hookwrapper
        calls).

        """
        def setattr_hookimpl_opts(func):
            setattr(func, self.project_name + "_impl",
                    dict(hookwrapper=hookwrapper, optionalhook=optionalhook,
                         tryfirst=tryfirst, trylast=trylast))
            return func

        if function is None:
            return setattr_hookimpl_opts
        else:
            return setattr_hookimpl_opts(function)

def normalize_hookimpl_opts(opts):
    opts.setdefault("tryfirst", False)
    opts.setdefault("trylast", False)
    opts.setdefault("hookwrapper", False)
    opts.setdefault("optionalhook", False)

if hasattr(inspect, 'getfullargspec'):
    def _getargspec(func):
        return inspect.getfullargspec(func)
else:
    def _getargspec(func):
        return inspect.getargspec(func)

def varnames(func):
    """Return tuple of positional and keywrord argument names for a function,
    method, class or callable.

    In case of a class, its ``__init__`` method is considered.
    For methods the ``self`` parameter is not included.
    """
    cache = getattr(func, "__dict__", {})
    try:
        return cache["_varnames"]
    except KeyError:
        pass

    if inspect.isclass(func):
        try:
            func = func.__init__
        except AttributeError:
            return (), ()
    elif not inspect.isroutine(func):  # callable object?
        try:
            func = getattr(func, '__call__', func)
        except Exception:
            return ()

    try:  # func MUST be a function or method here or we won't parse any args
        spec = _getargspec(func)
    except TypeError:
        return (), ()

    args, defaults = tuple(spec.args), spec.defaults
    if defaults:
        index = -len(defaults)
        args, defaults = args[:index], tuple(args[index:])
    else:
        defaults = ()

    # strip any implicit instance arg
    if args:
        if inspect.ismethod(func) or (
            '.' in getattr(func, '__qualname__', ()) and args[0] == 'self'
        ):
            args = args[1:]

    assert "self" not in args  # best naming practises check?
    try:
        cache["_varnames"] = args, defaults
    except TypeError:
        pass
    return args, defaults

class _HookRelay(object):
    """ hook holder object for performing 1:N hook calls where N is the number
    of registered plugins.

    """

    def __init__(self, trace):
        self._trace = trace

class _HookCaller(object):
    def __init__(self, name, hook_execute, specmodule_or_class=None,
                 spec_opts=None):
        self.name = name
        self._wrappers = []
        self._nonwrappers = []
        self._hookexec = hook_execute
        self._specmodule_or_class = None
        self.argnames = None
        self.kwargnames = None
        self.multicall = _multicall
        self.spec_opts = spec_opts or {}
        if specmodule_or_class is not None:
            self.set_specification(specmodule_or_class, spec_opts)

    def has_spec(self):
        return self._specmodule_or_class is not None

    def set_specification(self, specmodule_or_class, spec_opts):
        assert not self.has_spec()
        self._specmodule_or_class = specmodule_or_class
        specfunc = getattr(specmodule_or_class, self.name)
        # get spec arg signature
        argnames, self.kwargnames = varnames(specfunc)
        self.argnames = ["__multicall__"] + list(argnames)
        self.spec_opts.update(spec_opts)
        if spec_opts.get("historic"):
            self._call_history = []
        self.warn_on_impl = spec_opts.get('warn_on_impl')

    def is_historic(self):
        return hasattr(self, "_call_history")

    def _remove_plugin(self, plugin):
        def remove(wrappers):
            for i, method in enumerate(wrappers):
                if method.plugin == plugin:
                    del wrappers[i]
                    return True
        if remove(self._wrappers) is None:
            if remove(self._nonwrappers) is None:
                raise ValueError("plugin %r not found" % (plugin,))

    def _add_hookimpl(self, hookimpl):
        """Add an implementation to the callback chain.
        """
        if hookimpl.hookwrapper:
            methods = self._wrappers
        else:
            methods = self._nonwrappers

        if hookimpl.trylast:
            methods.insert(0, hookimpl)
        elif hookimpl.tryfirst:
            methods.append(hookimpl)
        else:
            # find last non-tryfirst method
            i = len(methods) - 1
            while i >= 0 and methods[i].tryfirst:
                i -= 1
            methods.insert(i + 1, hookimpl)

        if '__multicall__' in hookimpl.argnames:
            warnings.warn(
                "Support for __multicall__ is now deprecated and will be"
                "removed in an upcoming release.",
                DeprecationWarning
            )
            self.multicall = _legacymulticall

    def __repr__(self):
        return "<_HookCaller %r>" % (self.name,)

    def __call__(self, *args, **kwargs):
        if args:
            raise TypeError("hook calling supports only keyword arguments")
        assert not self.is_historic()
        if self.argnames:
            notincall = set(self.argnames) - set(['__multicall__']) - set(
                kwargs.keys())
            if notincall:
                warnings.warn(
                    "Argument(s) {} which are declared in the hookspec "
                    "can not be found in this hook call"
                    .format(tuple(notincall)),
                    stacklevel=2,
                )
        return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)

    def call_historic(self, result_callback=None, kwargs=None, proc=None):
        """Call the hook with given ``kwargs`` for all registered plugins and
        for all plugins which will be registered afterwards.

        If ``result_callback`` is not ``None`` it will be called for for each
        non-None result obtained from a hook implementation.

        .. note::
            The ``proc`` argument is now deprecated.
        """
        if proc is not None:
            warnings.warn(
                "Support for `proc` argument is now deprecated and will be"
                "removed in an upcoming release.",
                DeprecationWarning
            )
            result_callback = proc

        self._call_history.append((kwargs or {}, result_callback))
        # historizing hooks don't return results
        res = self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
        if result_callback is None:
            return
        # XXX: remember firstresult isn't compat with historic
        for x in res or []:
            result_callback(x)

    def call_extra(self, methods, kwargs):
        """ Call the hook with some additional temporarily participating
        methods using the specified kwargs as call parameters. """
        old = list(self._nonwrappers), list(self._wrappers)
        for method in methods:
            opts = dict(hookwrapper=False, trylast=False, tryfirst=False)
            hookimpl = HookImpl(None, "<temp>", method, opts)
            self._add_hookimpl(hookimpl)
        try:
            return self(**kwargs)
        finally:
            self._nonwrappers, self._wrappers = old

    def _maybe_apply_history(self, method):
        """Apply call history to a new hookimpl if it is marked as historic.
        """
        if self.is_historic():
            for kwargs, result_callback in self._call_history:
                res = self._hookexec(self, [method], kwargs)
                if res and result_callback is not None:
                    result_callback(res[0])

class HookImpl(object):
    def __init__(self, plugin, plugin_name, function, hook_impl_opts):
        self.function = function
        self.argnames, self.kwargnames = varnames(self.function)
        self.plugin = plugin
        self.opts = hook_impl_opts
        self.plugin_name = plugin_name
        self.__dict__.update(hook_impl_opts)

    def __repr__(self):
        return "<HookImpl plugin_name=%r, plugin_file=%r>" % (
            self.plugin_name,
            self.plugin.__file__,
        )
<|endoftext|>


<commit_before>import mdpopups
import sublime
import sublime_plugin
import webbrowser

from .core.configurations import is_supported_syntax
from .core.diagnostics import get_point_diagnostics
from .core.clients import LspTextCommand, client_for_view
from .core.protocol import Request, DiagnosticSeverity
from .core.documents import get_document_position
from .core.popups import popup_css, popup_class

SUBLIME_WORD_MASK = 515
NO_HOVER_SCOPES = 'comment, string'

class HoverHandler(sublime_plugin.ViewEventListener):
    def __init__(self, view):
        self.view = view

    @classmethod
    def is_applicable(cls, settings):
        syntax = settings.get('syntax')
        return syntax and is_supported_syntax(syntax)

    def on_hover(self, point, hover_zone):
        if hover_zone != sublime.HOVER_TEXT or self.view.is_popup_visible():
            return
        self.view.run_command("lsp_hover", {"point": point})

class LspHoverCommand(LspTextCommand):
    def __init__(self, view):
        super().__init__(view)

    def is_likely_at_symbol(self, point):
        word_at_sel = self.view.classify(point)
        return word_at_sel & SUBLIME_WORD_MASK and not self.view.match_selector(point, NO_HOVER_SCOPES)

    def run(self, edit, point=None):
        if point is None:
            point = self.view.sel()[0].begin()
        if self.is_likely_at_symbol(point):
            self.request_symbol_hover(point)
        point_diagnostics = get_point_diagnostics(self.view, point)
        if point_diagnostics:
            self.show_hover(point, self.diagnostics_content(point_diagnostics))

    def request_symbol_hover(self, point):
        client = client_for_view(self.view)
        if client and client.has_capability('hoverProvider'):
            document_position = get_document_position(self.view, point)
            if document_position:
                client.send_request(
                    Request.hover(document_position),
                    lambda response: self.handle_response(response, point))

    def handle_response(self, response, point):
        all_content = ""

        point_diagnostics = get_point_diagnostics(self.view, point)
        if point_diagnostics:
            all_content += self.diagnostics_content(point_diagnostics)

        all_content += self.hover_content(point, response)
        all_content += self.symbol_actions_content()

        self.show_hover(point, all_content)

    def symbol_actions_content(self):
        actions = []
        # TODO: filter by client capabilities
        actions.append("{}".format('definition', 'Definition'))
        actions.append("{}".format('references', 'References'))
        actions.append("{}".format('rename', 'Rename'))
        return "
" + " | ".join(actions) + "
"

    def diagnostics_content(self, diagnostics):
        formatted_errors = list(
            "
{}
".format(diagnostic.message)
            for diagnostic in diagnostics
            if diagnostic.severity == DiagnosticSeverity.Error)
        if len(formatted_errors) > 0:
            formatted = ["
"]
            formatted.extend(formatted_errors)
            formatted.append("{}".format('code-actions',
                                                          'Code Actions'))
            formatted.append("
")

        formatted_warnings = list(
            "
{}
".format(diagnostic.message)
            for diagnostic in diagnostics
            if diagnostic.severity == DiagnosticSeverity.Warning)

        if len(formatted_warnings) > 0:
            formatted = ["
"]
            formatted.extend(formatted_warnings)
            formatted.append("{}".format('code-actions',
                                                          'Code Actions'))
            formatted.append("
")

        return "".join(formatted)

    def hover_content(self, point, response):
        contents = ["No description available."]
        if isinstance(response, dict):
            # Flow returns None sometimes
            # See: https://github.com/flowtype/flow-language-server/issues/51
            response_content = response.get('contents')
            if response_content:
                if isinstance(response_content, list):
                    contents = response_content
                else:
                    contents = [response_content]

        formatted = []
        for item in contents:
            value = ""
            language = None
            if isinstance(item, str):
                value = item
            else:
                value = item.get("value")
                language = item.get("language")
            if language:
                formatted.append("```{}\n{}\n```\n".format(language, value))
            else:
                formatted.append(value)

        return mdpopups.md2html(self.view, "\n".join(formatted))

    def show_hover(self, point, contents):
        mdpopups.show_popup(
            self.view,
            contents,
            css=popup_css,
            md=False,
            flags=sublime.HIDE_ON_MOUSE_MOVE_AWAY,
            location=point,
            wrapper_class=popup_class,
            max_width=800,
            on_navigate=lambda href: self.on_hover_navigate(href, point))

    def on_hover_navigate(self, href, point):
        if href == 'definition':
            self.run_command_from_point(point, "lsp_symbol_definition")
        elif href == 'references':
            self.run_command_from_point(point, "lsp_symbol_references")
        elif href == 'rename':
            self.run_command_from_point(point, "lsp_symbol_rename")
        elif href == 'code-actions':
            self.run_command_from_point(point, "lsp_code_actions")
        else:
            webbrowser.open_new_tab(href)

    def run_command_from_point(self, point, command_name):
        sel = self.view.sel()
        sel.clear()
        sel.add(sublime.Region(point, point))
        self.view.run_command(command_name)
<commit_msg>fix: local variable referenced before assignment<commit_after>import mdpopups
import sublime
import sublime_plugin
import webbrowser

from .core.configurations import is_supported_syntax
from .core.diagnostics import get_point_diagnostics
from .core.clients import LspTextCommand, client_for_view
from .core.protocol import Request, DiagnosticSeverity
from .core.documents import get_document_position
from .core.popups import popup_css, popup_class

SUBLIME_WORD_MASK = 515
NO_HOVER_SCOPES = 'comment, string'

class HoverHandler(sublime_plugin.ViewEventListener):
    def __init__(self, view):
        self.view = view

    @classmethod
    def is_applicable(cls, settings):
        syntax = settings.get('syntax')
        return syntax and is_supported_syntax(syntax)

    def on_hover(self, point, hover_zone):
        if hover_zone != sublime.HOVER_TEXT or self.view.is_popup_visible():
            return
        self.view.run_command("lsp_hover", {"point": point})

class LspHoverCommand(LspTextCommand):
    def __init__(self, view):
        super().__init__(view)

    def is_likely_at_symbol(self, point):
        word_at_sel = self.view.classify(point)
        return word_at_sel & SUBLIME_WORD_MASK and not self.view.match_selector(point, NO_HOVER_SCOPES)

    def run(self, edit, point=None):
        if point is None:
            point = self.view.sel()[0].begin()
        if self.is_likely_at_symbol(point):
            self.request_symbol_hover(point)
        point_diagnostics = get_point_diagnostics(self.view, point)
        if point_diagnostics:
            self.show_hover(point, self.diagnostics_content(point_diagnostics))

    def request_symbol_hover(self, point):
        client = client_for_view(self.view)
        if client and client.has_capability('hoverProvider'):
            document_position = get_document_position(self.view, point)
            if document_position:
                client.send_request(
                    Request.hover(document_position),
                    lambda response: self.handle_response(response, point))

    def handle_response(self, response, point):
        all_content = ""

        point_diagnostics = get_point_diagnostics(self.view, point)
        if point_diagnostics:
            all_content += self.diagnostics_content(point_diagnostics)

        all_content += self.hover_content(point, response)
        all_content += self.symbol_actions_content()

        self.show_hover(point, all_content)

    def symbol_actions_content(self):
        actions = []
        # TODO: filter by client capabilities
        actions.append("{}".format('definition', 'Definition'))
        actions.append("{}".format('references', 'References'))
        actions.append("{}".format('rename', 'Rename'))
        return "
" + " | ".join(actions) + "
"

    def diagnostics_content(self, diagnostics):
        formatted_errors = list(
            "
{}
".format(diagnostic.message)
            for diagnostic in diagnostics
            if diagnostic.severity == DiagnosticSeverity.Error)
        formatted = []
        if len(formatted_errors) > 0:
            formatted.append("
")
            formatted.extend(formatted_errors)
            formatted.append("{}".format('code-actions',
                                                          'Code Actions'))
            formatted.append("
")

        formatted_warnings = list(
            "
{}
".format(diagnostic.message)
            for diagnostic in diagnostics
            if diagnostic.severity == DiagnosticSeverity.Warning)

        if len(formatted_warnings) > 0:
            formatted.append("
")
            formatted.extend(formatted_warnings)
            formatted.append("{}".format('code-actions',
                                                          'Code Actions'))
            formatted.append("
")

        return "".join(formatted)

    def hover_content(self, point, response):
        contents = ["No description available."]
        if isinstance(response, dict):
            # Flow returns None sometimes
            # See: https://github.com/flowtype/flow-language-server/issues/51
            response_content = response.get('contents')
            if response_content:
                if isinstance(response_content, list):
                    contents = response_content
                else:
                    contents = [response_content]

        formatted = []
        for item in contents:
            value = ""
            language = None
            if isinstance(item, str):
                value = item
            else:
                value = item.get("value")
                language = item.get("language")
            if language:
                formatted.append("```{}\n{}\n```\n".format(language, value))
            else:
                formatted.append(value)

        return mdpopups.md2html(self.view, "\n".join(formatted))

    def show_hover(self, point, contents):
        mdpopups.show_popup(
            self.view,
            contents,
            css=popup_css,
            md=False,
            flags=sublime.HIDE_ON_MOUSE_MOVE_AWAY,
            location=point,
            wrapper_class=popup_class,
            max_width=800,
            on_navigate=lambda href: self.on_hover_navigate(href, point))

    def on_hover_navigate(self, href, point):
        if href == 'definition':
            self.run_command_from_point(point, "lsp_symbol_definition")
        elif href == 'references':
            self.run_command_from_point(point, "lsp_symbol_references")
        elif href == 'rename':
            self.run_command_from_point(point, "lsp_symbol_rename")
        elif href == 'code-actions':
            self.run_command_from_point(point, "lsp_code_actions")
        else:
            webbrowser.open_new_tab(href)

    def run_command_from_point(self, point, command_name):
        sel = self.view.sel()
        sel.clear()
        sel.add(sublime.Region(point, point))
        self.view.run_command(command_name)
<|endoftext|>


<commit_before>from assets import Player, Camera

import pygame

def camera_update(camera, target_rect):
    l, t, _, _ = target_rect
    _, _, w, h = camera
    return Rect(-l+400, -t+300, w, h)

WATER_TOP = 500
CENTAUR_PLATFORM = 100

class Water(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface([800, 100])
        self.image.fill((0, 0, 255))
        self.rect = self.image.get_rect()
        self.rect.topleft = 0, WATER_TOP

class Centaur(pygame.sprite.Sprite):
    def __init__(self, y):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load('images/enemy.png')
        self.rect = self.image.get_rect()
        self.rect.topleft = 0, y
        self.y=y

    def update(self, player):
        self.rect.topleft = (player.rect.topleft[0], self.y)

class SinkingPlayer(Player):
    air_speed = 5
    water_speed = 3
    isFalling = 1

    def move(self, event):
        if (event.type == pygame.KEYDOWN):
            if event.key == pygame.K_UP:
                    self.isJumping = 1
            if event.key == pygame.K_RIGHT:
                    self.movingRight = 1
            if event.key == pygame.K_LEFT:
                    self.movingLeft = 1

        if (event.type == pygame.KEYUP):
            if event.key == pygame.K_RIGHT:
                self.movingRight = 0
            if event.key == pygame.K_LEFT:
                self.movingLeft = 0

    def update(self, in_water):
        movement = [0,0]
        if (self.movingLeft == 1):
            movement[0] = -2
        if (self.movingRight == 1):
            movement[0] = 2
        if (self.isJumping == 1):
            movement[1] = -20
            self.isJumping = 0

        elif (self.isFalling ==1):
            movement[1] = self.water_speed if in_water else self.air_speed

        self.rect = self.rect.move(movement)

def main():
    #game objects
    things = pygame.sprite.OrderedUpdates()
    player = SinkingPlayer(0, WATER_TOP)
    centaur = Centaur(CENTAUR_PLATFORM)
    camera = Camera(camera_update, 400, 600)
    water = Water()

    things.add(water)
    things.add(centaur)
    things.add(player)

    #screen
    size = width, height = 800, 600
    screen = pygame.display.set_mode(size)

    pygame.init()

    timer = pygame.time.Clock()

    while 1:
        timer.tick(40)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return
            player.move(event)

        screen.fill((0,0,0))
        things.draw(screen)
        player.update(player.rect.colliderect(water.rect))
        centaur.update(player)

        pygame.display.flip()

if __name__ == '__main__': main()

<commit_msg>basic mechanics finished for violence - win/lose etc<commit_after>from assets import Player, Camera

import pygame

def camera_update(camera, target_rect):
    l, t, _, _ = target_rect
    _, _, w, h = camera
    return Rect(-l+400, -t+300, w, h)

WATER_TOP = 500
CENTAUR_PLATFORM = 100

class Water(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface([800, 100])
        self.image.fill((0, 0, 255))
        self.rect = self.image.get_rect()
        self.rect.topleft = 0, WATER_TOP

class Goal(pygame.sprite.Sprite):
	def __init__(self, image):
		pygame.sprite.Sprite.__init__(self)
		self.image = pygame.image.load(image)
		self.rect = self.image.get_rect()
		self.rect.topleft = 750,550

class Centaur(pygame.sprite.Sprite):
    def __init__(self, y):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load('images/enemy.png')
        self.rect = self.image.get_rect()
        self.rect.topleft = 0, y
        self.y=y

    def update(self, player):
        self.rect.topleft = (player.rect.topleft[0], self.y)

class SinkingPlayer(Player):
    air_speed = 5
    water_speed = 2
    isFalling = 1

    def move(self, event):
        if (event.type == pygame.KEYDOWN):
            if event.key == pygame.K_UP:
                    self.isJumping = 1
            if event.key == pygame.K_RIGHT:
                    self.movingRight = 1
            if event.key == pygame.K_LEFT:
                    self.movingLeft = 1

        if (event.type == pygame.KEYUP):
            if event.key == pygame.K_RIGHT:
                self.movingRight = 0
            if event.key == pygame.K_LEFT:
                self.movingLeft = 0

    def update(self, in_water):
        movement = [0,0]
        if (self.movingLeft == 1):
            movement[0] = -2
        if (self.movingRight == 1):
            movement[0] = 2
        if (self.isJumping == 1):
            movement[1] = -20
            self.isJumping = 0

        elif (self.isFalling ==1):
            movement[1] = self.water_speed if in_water else self.air_speed

        self.rect = self.rect.move(movement)

def main():
    #game objects
    things = pygame.sprite.OrderedUpdates()
    player = SinkingPlayer(0, WATER_TOP)
    centaur = Centaur(CENTAUR_PLATFORM)
    goal = Goal("images/goal.png")
    camera = Camera(camera_update, 400, 600)
    water = Water()

    things.add(water)
    things.add(centaur)
    things.add(player)
    things.add(goal)

    #screen
    size = width, height = 800, 600
    screen = pygame.display.set_mode(size)

    pygame.init()

    timer = pygame.time.Clock()

    while 1:
        timer.tick(40)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return
            player.move(event)

        screen.fill((0,0,0))
        things.draw(screen)
        player.update(player.rect.colliderect(water.rect))
        centaur.update(player)

        pygame.display.flip()

	if (player.rect.top > 600 or player.rect.top < 488):
	    print "You lose"
	    return

	if (player.rect.colliderect(goal.rect)):
	    print "You win"
	    return

if __name__ == '__main__': main()

<|endoftext|>


<commit_before>########
# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
#    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    * See the License for the specific language governing permissions and
#    * limitations under the License.

# ctx is imported and used in operations
from cloudify import ctx

# put the operation decorator on any function that is a task
from cloudify.decorators import operation

@operation
def my_task(some_property, **kwargs):
    # setting node instance runtime property
    ctx.instance.runtime_properties['value_of_some_property'] = some_property
<commit_msg>Fixed unclear instruction<commit_after>########
# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
#    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    * See the License for the specific language governing permissions and
#    * limitations under the License.

# ctx is imported and used in operations
from cloudify import ctx

# put the operation decorator on any function that is a task
from cloudify.decorators import operation

@operation
def my_task(some_property, **kwargs):
    # setting node instance runtime property
    ctx.instance.runtime_properties['some_property'] = 'value_of_some_property'
<|endoftext|>


<commit_before>#!/usr/bin/python

import os, sys
import json
import random

from argparse import ArgumentParser

class Sentinel:
  def __init__(self, id=None, name=None):
    self.id = hex(random.getrandbits(42*8))[2:]
    self.name = '{0}, {1} {2} sentinel'.format(self.__color(),
                                               self.__adjective(),
                                               self.__generation())

  def __eq__(self, other):
    return (self.id == other.id) or (self.name == other.name)

  def __hash__(self):
    return hash(self.name)

  def __color(self):
    colors = ['aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', 'lime',
              'maroon', 'navy', 'navy', 'olive', 'orange', 'purple', 'red',
              'silver', 'teal', 'white', 'yellow']
    return colors[random.randint(0,len(colors)-1)]

  def __adjective(self):
    adjectives = ['abandoned', 'able', 'absolute', 'academic', 'acceptable',
                  'acclaimed', 'accomplished', 'accurate', 'aching', 'acidic',
                  'acrobatic', 'active', 'actual', 'adept', 'admirable',
                  'admired', 'adolescent', 'adorable', 'advanced',
                  'adventurous', 'affectionate', 'afraid']
    return adjectives[random.randint(0,len(adjectives)-1)]

  def __generation(self):
    generations = ['mark ii', 'composite', 'mark iii', 'mark iv', 'mark v',
                   'mark vi', 'mark vii', 'nimrod', 'prime', 'omega prime',
                   'wild', 'mark viii', 'bio']
    return generations[random.randint(0,len(generations)-1)]

  def is_applicable(self, command):
    if command.exists('targetdevice'):
      return command.get('targetdevice') == self.id
    else:
      return command.get('commandname') == 'refresh'

  def process_command(self, command):
    process = {
      'refresh': {'output': '{0}.report'.format(self.id),
                  'result':  {'device id': self.id,
                              'data source': 'trask',
                              'computer name': self.name}},
      0:         {'output': '{0}.json'.format(command.get('commandid')),
                  'result': {'CommandID': command.get('commandid'),
                             'DeviceID': self.id,
                             'Result': 'Completed'}}
    }

    name = command.get('commandname')
    if command.get('commandname') not in process:
      name = 0

    loc = os.path.join(command.get('outputdirectory'),
                       process[name]['output'])
    with open(loc, 'w') as f:
      json.dump(process[name]['result'], f, ensure_ascii=False)

    if name == 0:
      os.remove(command.location)

class Command:
  def __init__(self, location):
    self.location = location
    with open(location, 'r') as f:
      self.__command = json.load(f, object_hook=Command.__json_lower)
    self.__validate()

  @staticmethod
  def __json_lower(d):
     return dict((k.lower(), d[k]) for k in d)

  def __validate(self):
    is_valid = True

    for k in ['outputdirectory', 'commandname']:
      if not self.exists(k):
        is_valid = False

    if is_valid:
      if self.get('commandname') != 'refresh':
        for k in ['targetdevice', 'commandid']:
          if not self.exists(k):
            return False
      self.requires_result = True

  def exists(self, k):
    return k in self.__command

  def get(self, k):
    if self.exists(k):
      return self.__command[k]
    else:
      return ''

class Trask:
  def __init__(self, n):
    self.sentinels = []
    for i in range(n):
      sentinel = Sentinel()
      while sentinel in self.sentinels:
        sentinel = Sentinel()
      self.sentinels.append(sentinel)

  def process_commands(self, location):
    for cf in os.listdir(location):
      command = Command(os.path.join(location, cf))
      if command.requires_result:
        for sentinel in self.sentinels:
          if sentinel.is_applicable(command):
            sentinel.process_command(command)

def parse_args():
  description = """\
An IBM Endpoint Manager Proxy Agent plugin to simulate clients"""

  usage = """Usage: python trask.py [options]

{0}

Options:
  --configOptions OPTIONS  config options
  --commandDir LOCATION    command directory location

  -h, --help               print this help text and exit
  """.format(description)

  argparser = ArgumentParser(description=description,
                             usage=usage,
                             add_help=False)

  argparser.add_argument('--configOptions')
  argparser.add_argument('--commandDir', required=True)

  argparser.add_argument('-h', '--help')

  if '-h' in sys.argv or '--help' in sys.argv:
    print(usage)
    sys.exit()

  args = argparser.parse_args()

  return args

def main():
  args = parse_args()

  trask = Trask(42)
  trask.process_commands(args.commandDir)

if __name__ == '__main__':
  main()
<commit_msg>fix #1<commit_after>#!/usr/bin/python

import os, sys
import json
import random
import sqlite3

from argparse import ArgumentParser

class Sentinel:
  def __init__(self, id=None, name=None):
    if id is None:
      id = hex(random.getrandbits(42*8))[2:]
    if name is None:
      name = '{0}, {1} {2} sentinel'.format(self.__color(),
                                            self.__adjective(),
                                            self.__generation())

    self.id = id
    self.name = name

  def __eq__(self, other):
    return (self.id == other.id) or (self.name == other.name)

  def __hash__(self):
    return hash(self.name)

  def __color(self):
    colors = ['aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', 'lime',
              'maroon', 'navy', 'navy', 'olive', 'orange', 'purple', 'red',
              'silver', 'teal', 'white', 'yellow']
    return colors[random.randint(0,len(colors)-1)]

  def __adjective(self):
    adjectives = ['abandoned', 'able', 'absolute', 'academic', 'acceptable',
                  'acclaimed', 'accomplished', 'accurate', 'aching', 'acidic',
                  'acrobatic', 'active', 'actual', 'adept', 'admirable',
                  'admired', 'adolescent', 'adorable', 'advanced',
                  'adventurous', 'affectionate', 'afraid']
    return adjectives[random.randint(0,len(adjectives)-1)]

  def __generation(self):
    generations = ['mark ii', 'composite', 'mark iii', 'mark iv', 'mark v',
                   'mark vi', 'mark vii', 'nimrod', 'prime', 'omega prime',
                   'wild', 'mark viii', 'bio']
    return generations[random.randint(0,len(generations)-1)]

  def is_applicable(self, command):
    if command.exists('targetdevice'):
      return command.get('targetdevice') == self.id
    else:
      return command.get('commandname') == 'refresh'

  def process_command(self, command):
    process = {
      'refresh': {'output': '{0}.report'.format(self.id),
                  'result':  {'device id': self.id,
                              'data source': 'trask',
                              'computer name': self.name}},
      0:         {'output': '{0}.json'.format(command.get('commandid')),
                  'result': {'CommandID': command.get('commandid'),
                             'DeviceID': self.id,
                             'Result': 'Completed'}}
    }

    name = command.get('commandname')
    if command.get('commandname') not in process:
      name = 0

    loc = os.path.join(command.get('outputdirectory'),
                       process[name]['output'])
    with open(loc, 'w') as f:
      json.dump(process[name]['result'], f, ensure_ascii=False)

    if name == 0:
      os.remove(command.location)

class Command:
  def __init__(self, location):
    self.location = location
    with open(location, 'r') as f:
      self.__command = json.load(f, object_hook=Command.__json_lower)
    self.__validate()

  @staticmethod
  def __json_lower(d):
     return dict((k.lower(), d[k]) for k in d)

  def __validate(self):
    is_valid = True

    for k in ['outputdirectory', 'commandname']:
      if not self.exists(k):
        is_valid = False

    if is_valid:
      if self.get('commandname') != 'refresh':
        for k in ['targetdevice', 'commandid']:
          if not self.exists(k):
            return False
      self.requires_result = True

  def exists(self, k):
    return k in self.__command

  def get(self, k):
    if self.exists(k):
      return self.__command[k]
    else:
      return ''

class MasterMold:
  def __init__(self):
    db = 'master_mold.db'
    initialize = False
    if not os.path.isfile(db):
      initialize = True

    self._connection = sqlite3.connect(db, isolation_level=None)
    self.cursor = self._connection.cursor()

    if initialize:
      self.initialize()

  def initialize(self):
    self.cursor.execute("""\
CREATE TABLE sentinels
(
  id TEXT NOT NULL,
  name TEXT NOT NULL,
  PRIMARY KEY ( id, name )
)
""")

  def add_sentinel(self, sentinel):
    self.cursor.execute('INSERT INTO sentinels (id, name) '
                        'VALUES (?, ?)',
                        (sentinel.id, sentinel.name))

  def get_sentinels(self, n=None):
    i = 0
    sentinels = []
    for row in self.cursor.execute('SELECT id, name from sentinels'):
      if (n is not None) and (i == n):
        break

      sentinels.append(Sentinel(row[0], row[1]))
      i += 1

    return sentinels

class Trask:
  def __init__(self, n):
    self.n = n
    self.master_mold = MasterMold()
    sentinels = self.master_mold.get_sentinels()

    m = n - len(sentinels)
    if m > 0:
      for i in range(m):
        sentinel = Sentinel()
        while sentinel in sentinels:
          sentinel = Sentinel()

        sentinels.append(sentinel)
        self.master_mold.add_sentinel(sentinel)

  def process_commands(self, location):
    for cf in os.listdir(location):
      command = Command(os.path.join(location, cf))
      if command.requires_result:
        for sentinel in self.master_mold.get_sentinels(self.n):
          if sentinel.is_applicable(command):
            sentinel.process_command(command)

def parse_args():
  description = """\
An IBM Endpoint Manager Proxy Agent plugin to simulate clients"""

  usage = """Usage: python trask.py [options]

{0}

Options:
  --configOptions OPTIONS  config options
  --commandDir LOCATION    command directory location

  -h, --help               print this help text and exit
  """.format(description)

  argparser = ArgumentParser(description=description,
                             usage=usage,
                             add_help=False)

  argparser.add_argument('--configOptions')
  argparser.add_argument('--commandDir', required=True)

  argparser.add_argument('-h', '--help')

  if '-h' in sys.argv or '--help' in sys.argv:
    print(usage)
    sys.exit()

  args = argparser.parse_args()

  return args

def main():
  args = parse_args()

  trask = Trask(42)
  trask.process_commands(args.commandDir)

if __name__ == '__main__':
  main()
<|endoftext|>


<commit_before>#!/usr/bin/env python

import pwd
import os
import sys
import json
import subprocess
import shutil
import re
import tempfile
from distutils.dir_util import copy_tree

__version__ = 1

# Config
VG_CONF_PATH = '/usr/local/etc/voltgrid.conf'

# Default ID for spawning and mounting, override in voltgrid.conf
DEFAULT_UID = 48
DEFAULT_GID = 48

# Magic Vars
CONFIG_EXCEPTION = 64
GIT_EXCEPTION = 128

class ConfigManager(object):
    """ Configuration Manager """

    config = {}
    environment = os.environ

    def __init__(self, cfg_file=None):
        # Assume same dir as voltgrid.py if not specified
        cfg_file = cfg_file or os.path.join(os.path.abspath(os.path.split(__file__)[0]), 'voltgrid.conf')
        self.local_config = self.load_local_config(cfg_file)
        self.spawn_uid = self.local_config.get('user', {}).get('uid', DEFAULT_UID)
        self.spawn_gid = self.local_config.get('user', {}).get('gid', DEFAULT_GID)
        super(self.__class__, self).__init__()

    def load_config(self):
        if os.getenv('CONFIG') is not None:
            self.config = json.loads(os.getenv('CONFIG'))
        else:
            self.config = {}
            for key in self.environment.keys():
                self.config[key] = os.environ[key]

    @staticmethod
    def load_local_config(cfg_file):
        # Ignore errors with loading config
        try:
            config = json.load(open(cfg_file))
        except ValueError:
            print('Could not parse config file %s' % cfg_file)
            config = {}
        except IOError:
            print('Could not open config file %s' % cfg_file)
            config = {}
        return config

    @staticmethod
    def write_file(data, path):
        with open(path, 'w') as f:
            for line in data:
                f.write(line + '\n')

    def load_git_conf(self):
        # Allow override git config from environment
        self.git_cfg = self.local_config.get('git', {})
        git = self.local_config.get('git', {})
        for key in self.environment.keys():
            if re.search('^GIT', key, re.IGNORECASE):
                self.git_cfg[key.lower()] = os.environ[key]
        # easier access
        self.git_url = self.git_cfg.get('git_url', None)
        self.git_dst = self.git_cfg.get('git_dst', None)
        self.git_branch = self.git_cfg.get('git_branch', None)
        self.git_tag = self.git_cfg.get('git_tag', None)
        self.git_hash = self.git_cfg.get('git_hash', None)

    def write_envs(self):
        env_file_path = self.local_config.get('env_file_path', None)
        if env_file_path is not None:
            envs = list()
            for key in self.environment.keys():
                # Don't set inherited environment variables that dont need to be configured
                if key not in ['HOME', 'PATH']:
                    envs.append("%s=%s" % (key, os.environ[key]))
            self.write_file(envs, env_file_path)

    def load_envs(self):
        env_file_path = self.local_config.get('env_file_path', None)
        if env_file_path is not None and os.path.exists(env_file_path):
            # Load environment variables from .env file
            reg = re.compile('(?P<key>\w+)(\=(?P<value>.+))')
            for line in open(env_file_path):
                m = reg.match(line)
                if m:
                    key = m.group('key')
                    if m.group('value'):
                        value = m.group('value')
                    else:
                        value = ''
                    os.environ[key] = value

class GitManager(object):
    """ Manager for git operations """

    def __init__(self, url, git_dst, git_branch=None, git_tag=None, git_hash=None):
        self.url = url
        self.git_dst = git_dst
        self.git_branch = git_branch
        self.git_tag = git_tag
        self.git_hash = git_hash
        super(self.__class__, self).__init__()

    class GitException(Exception):
        def getExitCode(self):
            return GIT_EXCEPTION

    def get_commands(self):
        git_commands = list()
        if self.git_hash is not None:
            # It's not possible to do a sparse clone when we need a specific commit hash
            git_commands.append(['git', 'clone', self.url, '.'])
            git_commands.append(['git', 'checkout', self.git_hash])
        elif self.git_tag is not None:
            git_commands.append(['git', 'clone', '--depth=1', '-b', '%s' % self.git_tag, self.url, '.'])  # detached HEAD
        elif self.git_branch is not None:
            git_commands.append(['git', 'clone', '--depth=1', '-b', '%s' % self.git_branch, self.url, '.'])
        else:
            git_commands.append(['git', 'clone', '--depth=1', self.url, '.'])
        # Update submodules
        git_commands.append(['git', 'submodule', 'update', '--init'])
        return git_commands

    @staticmethod
    def call(cmd, cwd):
        p = subprocess.Popen(cmd, cwd=cwd)
        return_code = p.wait()
        return return_code

    def run(self):
        for cmd in self.get_commands():
            return_code = self.call(cmd, cwd=self.git_dst)
            if return_code != 0:
                raise self.GitException('Git command failed: %s' % ' '.join(cmd))

class MountManager(object):
    """ Mountfile mounts manager """

    def __init__(self, base, remote, mount_uid, mount_gid, mount_file='Mountfile'):
        self.base = base
        self.remote = remote
        self.mount_uid = mount_uid
        self.mount_gid = mount_gid
        self.mount_file = mount_file
        super(self.__class__, self).__init__()

    @staticmethod
    def parse_mountfile_line(line):
        r = re.compile(r'^\s*/?([\.\-\w]*[ \.\-/\w]*[\.\-\w]+)/?\s*:\s*/?([\.\-\w]*[ \.\-/\w]*[\.\-\w]+)/?\s*(#.*)?$')
        match = r.search(line.strip())
        if match:
            remote = str(match.group(1))
            mount = str(match.group(2))
            match = True
        else:
            remote = None
            mount = None
            match = False
        return remote, mount, match

    def find_mount_file(self):
        if os.path.exists(os.path.join(self.base, self.mount_file)):
            return os.path.join(self.base, self.mount_file)
        elif os.path.exists(os.path.join(self.remote, self.mount_file)):
            return os.path.join(self.remote, self.mount_file)
        else:
            return False

    def get_mounts(self, mount_file):
        mounts_list = list()
        with open(mount_file, 'r') as mount_file:
            for line in mount_file:
                remote, mount, match = self.parse_mountfile_line(line)
                if match:
                    if remote == 'ephemeral':
                        remote_dir = tempfile.mkdtemp()
                        os.chown(remote_dir, self.mount_uid, self.mount_gid)
                        print("Using temporary remote %s" % remote_dir)
                    else:
                        remote_dir = os.path.join(self.remote, remote)
                    mount_dir = os.path.join(self.base, mount)
                    mounts_list.append((str(remote_dir), str(mount_dir)))
        return mounts_list

    def handle(self):
        # This creates symlinks to link share (or remote) content into the local code base.
        # Performs a number of checks to ensure we are not leaving the remote and local directories.
        # Will create folders on the remote side but will not delete anything.
        # Will not allow a link to be created in another link.
        # Will not create parent directories on the local side.
        mount_file_path = self.find_mount_file()
        if mount_file_path:
            mounts_list = self.get_mounts(mount_file_path)
        else:
            # Mount working_dir to remote_dir if empty
            if not os.listdir(self.base):
                mounts_list = list()
                mounts_list.append((self.remote, self.base))
            else:
                return False

        for (remote_dir, mount_dir) in mounts_list:
            # Some check to ensure mount_dir (local) is not going into a remote dir
            real_mount_dir = os.path.realpath(os.path.dirname(mount_dir))
            if re.match(self.remote, real_mount_dir):
                print("Looks like %s is in the remote" % mount_dir)
                continue
            # Create remote directories if they don't exist, set ownership
            if not os.path.exists(remote_dir):
                os.makedirs(remote_dir)
                if remote_dir != self.remote:
                    os.chown(remote_dir, self.mount_uid, self.mount_gid)
            # Copy mount_dir to remote, if remote is empty, and mount_dir has files
            if os.path.exists(mount_dir) and os.listdir(mount_dir) and not os.listdir(remote_dir):
                # use copy_tree because shutil.copytree doesn't like it when the dst exists
                copy_tree(mount_dir, remote_dir)
                # Fix permissions recursively in remote_dir
                for root, dirs, files in os.walk(remote_dir):
                    for item in dirs:
                        os.chown(os.path.join(root, item), self.mount_uid, self.mount_gid)
                    for item in files:
                        os.chown(os.path.join(root, item), self.mount_uid, self.mount_gid)
            # Delete mount_dir if exists. Create symlink to remote_dir
            if os.path.isfile(mount_dir) or os.path.islink(mount_dir):
                os.remove(mount_dir)
            elif os.path.isdir(mount_dir):
                shutil.rmtree(mount_dir)
            os.symlink(remote_dir, mount_dir)
            print("Link created %s to %s" % (mount_dir, remote_dir))

class TemplateManager(object):
    """ Templated Config File Manager """

    def __init__(self, files=None, context=None):
            self.files = files
            self.context = context
            super(self.__class__, self).__init__()

    @staticmethod
    def render(file, context):
        from jinja2 import Environment, FileSystemLoader, contextfunction
        environment = Environment(loader=FileSystemLoader(searchpath="/"))
        def from_json(value):
            return json.loads(value)
        @contextfunction
        def get_context(c):
            return c
        environment.filters['from_json'] = from_json
        # fix issue w/ UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 1: ordinal not in range(128)
        if (sys.version_info < (3, 0)):
            reload(sys)
            sys.setdefaultencoding("UTF-8")
        template = environment.get_template(file)
        template.globals['context'] = get_context
        template.globals['callable'] = callable
        return template.render(context)

    def render_files(self):
        from stat import S_IMODE
        for f in self.files:
            with tempfile.NamedTemporaryFile(mode='wb', delete=False) as tmp_f:
                output = self.render(f, self.context)
                if (sys.version_info > (3, 0)):
                    tmp_f.write(bytes(self.render(f, self.context), 'UTF-8'))  # python 3
                else:
                    tmp_f.write(output)  # python 2

            f_st = os.stat(f)
            root, ext = os.path.splitext(f)
            if ext == ".j2":
                f = root
            os.rename(tmp_f.name, f)
            os.chmod(f, S_IMODE(f_st.st_mode))
            os.chown(f, f_st.st_uid, f_st.st_gid)
            print("Rendered template %s with mode %d" % (f, int(oct(f_st.st_mode)[-4:])))

def main(argv):

    def execute(arg, user, group):
        # Update home to correct value incase it was inherited
        home = pwd.getpwuid(user)[5]  # Users home directory
        print("Updating HOME to %s" % home)
        os.environ['HOME'] = home
        print("Replacing current process with: %s" % arg[1])
        os.setgid(group)
        os.setuid(user)
        print("Running as %s:%s" % (os.geteuid(), os.getegid()))
        sys.stdout.flush()
        os.execvpe(arg[1], arg[1:], os.environ)  # replace current process, inherit environment

    # Unset inherited environment variables that dont need to be configured
    for e in ['HOME',]:
        print("Unsetting %s" % e)
        del os.environ[e]

    # Load config
    c = ConfigManager(VG_CONF_PATH)
    c.load_envs()
    c.write_envs()
    c.load_config()
    c.load_git_conf()
    local_config = c.local_config
    files = local_config.get('files', {})
    dirs = local_config.get('dirs', {})

    # Checkout Git
    if c.git_url is not None:
        g = GitManager(url=c.git_url, git_dst=c.git_dst, git_branch=c.git_branch, git_tag=c.git_tag, git_hash=c.git_hash)
        g.run()

    # Mount shares
    working_dir = dirs.get('working_dir', None)
    remote_dir = dirs.get('remote_dir', None)
    if working_dir is not None and remote_dir is not None:
        m = MountManager(working_dir, remote_dir, DEFAULT_UID, DEFAULT_GID)
        m.handle()

    # Write config from templates
    if len(files) > 0:
        t = TemplateManager(files, context=c.config)
        t.render_files()

    # Spawn Next Process
    if len(argv) > 1:
        execute(argv, c.spawn_uid, c.spawn_gid)

if __name__ == "__main__":
    main(argv=sys.argv)
<commit_msg>bump version 1.0.6<commit_after>#!/usr/bin/env python

import pwd
import os
import sys
import json
import subprocess
import shutil
import re
import tempfile
from distutils.dir_util import copy_tree

__version__ = '1.0.6'

# Config
VG_CONF_PATH = '/usr/local/etc/voltgrid.conf'

# Default ID for spawning and mounting, override in voltgrid.conf
DEFAULT_UID = 48
DEFAULT_GID = 48

# Magic Vars
CONFIG_EXCEPTION = 64
GIT_EXCEPTION = 128

class ConfigManager(object):
    """ Configuration Manager """

    config = {}
    environment = os.environ

    def __init__(self, cfg_file=None):
        # Assume same dir as voltgrid.py if not specified
        cfg_file = cfg_file or os.path.join(os.path.abspath(os.path.split(__file__)[0]), 'voltgrid.conf')
        self.local_config = self.load_local_config(cfg_file)
        self.spawn_uid = self.local_config.get('user', {}).get('uid', DEFAULT_UID)
        self.spawn_gid = self.local_config.get('user', {}).get('gid', DEFAULT_GID)
        super(self.__class__, self).__init__()

    def load_config(self):
        if os.getenv('CONFIG') is not None:
            self.config = json.loads(os.getenv('CONFIG'))
        else:
            self.config = {}
            for key in self.environment.keys():
                self.config[key] = os.environ[key]

    @staticmethod
    def load_local_config(cfg_file):
        # Ignore errors with loading config
        try:
            config = json.load(open(cfg_file))
        except ValueError:
            print('Could not parse config file %s' % cfg_file)
            config = {}
        except IOError:
            print('Could not open config file %s' % cfg_file)
            config = {}
        return config

    @staticmethod
    def write_file(data, path):
        with open(path, 'w') as f:
            for line in data:
                f.write(line + '\n')

    def load_git_conf(self):
        # Allow override git config from environment
        self.git_cfg = self.local_config.get('git', {})
        git = self.local_config.get('git', {})
        for key in self.environment.keys():
            if re.search('^GIT', key, re.IGNORECASE):
                self.git_cfg[key.lower()] = os.environ[key]
        # easier access
        self.git_url = self.git_cfg.get('git_url', None)
        self.git_dst = self.git_cfg.get('git_dst', None)
        self.git_branch = self.git_cfg.get('git_branch', None)
        self.git_tag = self.git_cfg.get('git_tag', None)
        self.git_hash = self.git_cfg.get('git_hash', None)

    def write_envs(self):
        env_file_path = self.local_config.get('env_file_path', None)
        if env_file_path is not None:
            envs = list()
            for key in self.environment.keys():
                # Don't set inherited environment variables that dont need to be configured
                if key not in ['HOME', 'PATH']:
                    envs.append("%s=%s" % (key, os.environ[key]))
            self.write_file(envs, env_file_path)

    def load_envs(self):
        env_file_path = self.local_config.get('env_file_path', None)
        if env_file_path is not None and os.path.exists(env_file_path):
            # Load environment variables from .env file
            reg = re.compile('(?P<key>\w+)(\=(?P<value>.+))')
            for line in open(env_file_path):
                m = reg.match(line)
                if m:
                    key = m.group('key')
                    if m.group('value'):
                        value = m.group('value')
                    else:
                        value = ''
                    os.environ[key] = value

class GitManager(object):
    """ Manager for git operations """

    def __init__(self, url, git_dst, git_branch=None, git_tag=None, git_hash=None):
        self.url = url
        self.git_dst = git_dst
        self.git_branch = git_branch
        self.git_tag = git_tag
        self.git_hash = git_hash
        super(self.__class__, self).__init__()

    class GitException(Exception):
        def getExitCode(self):
            return GIT_EXCEPTION

    def get_commands(self):
        git_commands = list()
        if self.git_hash is not None:
            # It's not possible to do a sparse clone when we need a specific commit hash
            git_commands.append(['git', 'clone', self.url, '.'])
            git_commands.append(['git', 'checkout', self.git_hash])
        elif self.git_tag is not None:
            git_commands.append(['git', 'clone', '--depth=1', '-b', '%s' % self.git_tag, self.url, '.'])  # detached HEAD
        elif self.git_branch is not None:
            git_commands.append(['git', 'clone', '--depth=1', '-b', '%s' % self.git_branch, self.url, '.'])
        else:
            git_commands.append(['git', 'clone', '--depth=1', self.url, '.'])
        # Update submodules
        git_commands.append(['git', 'submodule', 'update', '--init'])
        return git_commands

    @staticmethod
    def call(cmd, cwd):
        p = subprocess.Popen(cmd, cwd=cwd)
        return_code = p.wait()
        return return_code

    def run(self):
        for cmd in self.get_commands():
            return_code = self.call(cmd, cwd=self.git_dst)
            if return_code != 0:
                raise self.GitException('Git command failed: %s' % ' '.join(cmd))

class MountManager(object):
    """ Mountfile mounts manager """

    def __init__(self, base, remote, mount_uid, mount_gid, mount_file='Mountfile'):
        self.base = base
        self.remote = remote
        self.mount_uid = mount_uid
        self.mount_gid = mount_gid
        self.mount_file = mount_file
        super(self.__class__, self).__init__()

    @staticmethod
    def parse_mountfile_line(line):
        r = re.compile(r'^\s*/?([\.\-\w]*[ \.\-/\w]*[\.\-\w]+)/?\s*:\s*/?([\.\-\w]*[ \.\-/\w]*[\.\-\w]+)/?\s*(#.*)?$')
        match = r.search(line.strip())
        if match:
            remote = str(match.group(1))
            mount = str(match.group(2))
            match = True
        else:
            remote = None
            mount = None
            match = False
        return remote, mount, match

    def find_mount_file(self):
        if os.path.exists(os.path.join(self.base, self.mount_file)):
            return os.path.join(self.base, self.mount_file)
        elif os.path.exists(os.path.join(self.remote, self.mount_file)):
            return os.path.join(self.remote, self.mount_file)
        else:
            return False

    def get_mounts(self, mount_file):
        mounts_list = list()
        with open(mount_file, 'r') as mount_file:
            for line in mount_file:
                remote, mount, match = self.parse_mountfile_line(line)
                if match:
                    if remote == 'ephemeral':
                        remote_dir = tempfile.mkdtemp()
                        os.chown(remote_dir, self.mount_uid, self.mount_gid)
                        print("Using temporary remote %s" % remote_dir)
                    else:
                        remote_dir = os.path.join(self.remote, remote)
                    mount_dir = os.path.join(self.base, mount)
                    mounts_list.append((str(remote_dir), str(mount_dir)))
        return mounts_list

    def handle(self):
        # This creates symlinks to link share (or remote) content into the local code base.
        # Performs a number of checks to ensure we are not leaving the remote and local directories.
        # Will create folders on the remote side but will not delete anything.
        # Will not allow a link to be created in another link.
        # Will not create parent directories on the local side.
        mount_file_path = self.find_mount_file()
        if mount_file_path:
            mounts_list = self.get_mounts(mount_file_path)
        else:
            # Mount working_dir to remote_dir if empty
            if not os.listdir(self.base):
                mounts_list = list()
                mounts_list.append((self.remote, self.base))
            else:
                return False

        for (remote_dir, mount_dir) in mounts_list:
            # Some check to ensure mount_dir (local) is not going into a remote dir
            real_mount_dir = os.path.realpath(os.path.dirname(mount_dir))
            if re.match(self.remote, real_mount_dir):
                print("Looks like %s is in the remote" % mount_dir)
                continue
            # Create remote directories if they don't exist, set ownership
            if not os.path.exists(remote_dir):
                os.makedirs(remote_dir)
                if remote_dir != self.remote:
                    os.chown(remote_dir, self.mount_uid, self.mount_gid)
            # Copy mount_dir to remote, if remote is empty, and mount_dir has files
            if os.path.exists(mount_dir) and os.listdir(mount_dir) and not os.listdir(remote_dir):
                # use copy_tree because shutil.copytree doesn't like it when the dst exists
                copy_tree(mount_dir, remote_dir)
                # Fix permissions recursively in remote_dir
                for root, dirs, files in os.walk(remote_dir):
                    for item in dirs:
                        os.chown(os.path.join(root, item), self.mount_uid, self.mount_gid)
                    for item in files:
                        os.chown(os.path.join(root, item), self.mount_uid, self.mount_gid)
            # Delete mount_dir if exists. Create symlink to remote_dir
            if os.path.isfile(mount_dir) or os.path.islink(mount_dir):
                os.remove(mount_dir)
            elif os.path.isdir(mount_dir):
                shutil.rmtree(mount_dir)
            os.symlink(remote_dir, mount_dir)
            print("Link created %s to %s" % (mount_dir, remote_dir))

class TemplateManager(object):
    """ Templated Config File Manager """

    def __init__(self, files=None, context=None):
            self.files = files
            self.context = context
            super(self.__class__, self).__init__()

    @staticmethod
    def render(file, context):
        from jinja2 import Environment, FileSystemLoader, contextfunction
        environment = Environment(loader=FileSystemLoader(searchpath="/"))
        def from_json(value):
            return json.loads(value)
        @contextfunction
        def get_context(c):
            return c
        environment.filters['from_json'] = from_json
        # fix issue w/ UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 1: ordinal not in range(128)
        if (sys.version_info < (3, 0)):
            reload(sys)
            sys.setdefaultencoding("UTF-8")
        template = environment.get_template(file)
        template.globals['context'] = get_context
        template.globals['callable'] = callable
        return template.render(context)

    def render_files(self):
        from stat import S_IMODE
        for f in self.files:
            with tempfile.NamedTemporaryFile(mode='wb', delete=False) as tmp_f:
                output = self.render(f, self.context)
                if (sys.version_info > (3, 0)):
                    tmp_f.write(bytes(self.render(f, self.context), 'UTF-8'))  # python 3
                else:
                    tmp_f.write(output)  # python 2

            f_st = os.stat(f)
            root, ext = os.path.splitext(f)
            if ext == ".j2":
                f = root
            os.rename(tmp_f.name, f)
            os.chmod(f, S_IMODE(f_st.st_mode))
            os.chown(f, f_st.st_uid, f_st.st_gid)
            print("Rendered template %s with mode %d" % (f, int(oct(f_st.st_mode)[-4:])))

def main(argv):

    def execute(arg, user, group):
        # Update home to correct value incase it was inherited
        home = pwd.getpwuid(user)[5]  # Users home directory
        print("Updating HOME to %s" % home)
        os.environ['HOME'] = home
        print("Replacing current process with: %s" % arg[1])
        os.setgid(group)
        os.setuid(user)
        print("Running as %s:%s" % (os.geteuid(), os.getegid()))
        sys.stdout.flush()
        os.execvpe(arg[1], arg[1:], os.environ)  # replace current process, inherit environment

    # Unset inherited environment variables that dont need to be configured
    for e in ['HOME',]:
        print("Unsetting %s" % e)
        del os.environ[e]

    # Load config
    c = ConfigManager(VG_CONF_PATH)
    c.load_envs()
    c.write_envs()
    c.load_config()
    c.load_git_conf()
    local_config = c.local_config
    files = local_config.get('files', {})
    dirs = local_config.get('dirs', {})

    # Checkout Git
    if c.git_url is not None:
        g = GitManager(url=c.git_url, git_dst=c.git_dst, git_branch=c.git_branch, git_tag=c.git_tag, git_hash=c.git_hash)
        g.run()

    # Mount shares
    working_dir = dirs.get('working_dir', None)
    remote_dir = dirs.get('remote_dir', None)
    if working_dir is not None and remote_dir is not None:
        m = MountManager(working_dir, remote_dir, DEFAULT_UID, DEFAULT_GID)
        m.handle()

    # Write config from templates
    if len(files) > 0:
        t = TemplateManager(files, context=c.config)
        t.render_files()

    # Spawn Next Process
    if len(argv) > 1:
        execute(argv, c.spawn_uid, c.spawn_gid)

if __name__ == "__main__":
    main(argv=sys.argv)
<|endoftext|>