Exemplo n.º 1
0
Arquivo: cmd.py Projeto: tek/tryp-nvim
 def __init__(
         self,
         fun: Callable[..., Any],
         name: str=None,
         nargs=None,
         min: int=None,
         max: int=None,
         **kw
 ) -> None:
     self._fun = fun
     self._argspec = inspect.getfullargspec(fun)  # type: ignore
     self._params = List.wrap(self._argspec.args)
     self._param_count = self._params.length - (
         1 if self._params.head.contains('self') else 0
     )
     self._name = Maybe(name)
     self._nargs = Maybe(try_int(nargs))
     self._min = Maybe(min)
     self._max = Maybe(max)
     self._kw = kw
Exemplo n.º 2
0
 def _send(self, data, msg: Message):
     return Maybe.from_call(self.loop_process, data, msg, exc=TransitionFailed) | TransitionResult.empty(data)
Exemplo n.º 3
0
 def __init__(self, name: str, parent: "Machine" = None) -> None:
     self.name = name
     self.parent = Maybe(parent)
     self._setup_handlers()
Exemplo n.º 4
0
class Machine(Logging):
    _data_type = Data

    def __init__(self, name: str, parent: "Machine" = None) -> None:
        self.name = name
        self.parent = Maybe(parent)
        self._setup_handlers()

    def _setup_handlers(self):
        handlers = inspect.getmembers(self, lambda a: hasattr(a, _machine_attr))
        handler_map = List.wrap(handlers).smap(Handler.create).map(lambda a: (a.message, a))
        self._message_handlers = Map(handler_map)

    @property
    def _default_handler(self):
        return Handler("unhandled", None, self.unhandled)

    def process(self, data: Data, msg) -> TransitionResult:
        self.prepare(msg)
        handler = self._resolve_handler(msg)
        result = self._execute_transition(handler, data, msg)
        return self._dispatch_transition_result(data, result)

    def prepare(self, msg):
        pass

    def _dispatch_transition_result(self, data, result):
        return result / self._process_result(data) | TransitionResult.empty(data)

    def loop_process(self, data, msg):
        sender = lambda z, m: z.accum(self.loop_process(z.data, m))
        return self.process(data, msg).fold(sender)

    def _resolve_handler(self, msg):
        return self._message_handlers.get(type(msg)).get_or_else(lambda: self._default_handler)

    def _execute_transition(self, handler, data, msg):
        try:
            return handler.run(data, msg)
        except Exception as e:
            return self._handle_transition_error(handler, msg, e)

    def _handle_transition_error(self, handler, msg, e):
        err = 'transition "{}" failed for {} in {}'
        self.log.exception(err.format(handler.name, msg, self.name))
        if tryp.development:
            raise TransitionFailed() from e
        return Empty()

    @curried
    def _process_result(self, old_data: Data, result) -> TransitionResult:
        if isinstance(result, Coroutine):
            return CoroTransitionResult(data=old_data, coro=result)
        elif isinstance(result, TransitionResult):
            return result
        elif isinstance(result, self._data_type):
            return TransitionResult.empty(result)
        elif isinstance(result, Message) or not is_seq(result):
            result = List(result)
        datas, rest = List.wrap(result).split_type(self._data_type)
        strict, rest = rest.split_type(Message)
        coro, rest = rest.split(iscoroutine)
        msgs = strict + coro.map(Coroutine).map(_.pub)
        if rest:
            tpl = "invalid transition result parts in {}: {}"
            msg = tpl.format(self.name, rest)
            if tryp.development:
                raise MachineError(msg)
            else:
                self.log.error(msg)
        new_data = datas.head | old_data
        return self._create_result(new_data, msgs)

    def _create_result(self, data, msgs):
        pub, resend = msgs.split_type(Publish)
        pub_msgs = pub.map(_.message)
        return StrictTransitionResult(data=data, pub=pub_msgs, resend=resend)

    @may
    def unhandled(self, data: Data, msg: Message):
        pass

    def _command_by_message_name(self, name: str):
        msg_name = camelcaseify(name)
        return self._message_handlers.find_key(lambda a: a.__name__ in [name, msg_name])

    def command(self, name: str, args: list):
        return (
            self._command_by_message_name(name)
            .map(lambda a: StateCommand(a[0]))
            .map(_.call("dispatch", self, args))
            .or_else(F(self._invalid_command, name))
        )

    def _invalid_command(self, name):
        self.log.error('plugin "{}" has no command "{}"'.format(self.name, name))
        return Empty()

    @may_handle(NvimIOTask)
    def message_nvim_io(self, data: Data, msg):
        msg.io.unsafe_perform_io(self.vim)

    @may_handle(RunTask)
    def message_run_task(self, data: Data, msg):
        success = lambda r: r if isinstance(r, Message) else None
        return msg.task.unsafe_perform_sync().cata(F(Error) >> _.pub, success)

    @may_handle(DataTask)
    def message_data_task(self, data: Data, msg):
        return either_msg(msg.cons(Task.now(data)).unsafe_perform_sync())

    def bubble(self, msg):
        self.parent.cata(_.bubble, lambda: self.send)(msg)
Exemplo n.º 5
0
 def call(self, name, *a, **kw):
     return (
         Maybe.from_call(self.vim.call, name, *a, exc=NvimError, **kw)
         .to_either(lambda: 'vim call failed: {}'.format(
             format_funcall(name, a, kw)))
     )
Exemplo n.º 6
0
Arquivo: cmd.py Projeto: tek/tryp-nvim
class Command(Logging):

    def __init__(
            self,
            fun: Callable[..., Any],
            name: str=None,
            nargs=None,
            min: int=None,
            max: int=None,
            **kw
    ) -> None:
        self._fun = fun
        self._argspec = inspect.getfullargspec(fun)  # type: ignore
        self._params = List.wrap(self._argspec.args)
        self._param_count = self._params.length - (
            1 if self._params.head.contains('self') else 0
        )
        self._name = Maybe(name)
        self._nargs = Maybe(try_int(nargs))
        self._min = Maybe(min)
        self._max = Maybe(max)
        self._kw = kw

    @property
    def nargs(self):
        return self._nargs\
            .map(lambda a: '+' if numeric(a) and a > 1 else a)\
            .get_or_else(self._infer_nargs)

    @property
    def _infer_nargs(self):
        if self.min > 1:
            return '+'
        elif self.min == 1:
            return 1 if self.max.contains(1) else '+'
        else:
            if self.max.contains(0):
                return 0
            if self.max.contains(1):
                return '?'
            else:
                return '*'

    @property
    def min(self):
        return self._min.get_or_else(self._infer_min)

    @property
    def _infer_min(self):
        s = self._argspec
        return self._param_count - len(s.defaults or [])

    @property
    def max(self):
        return self._max.or_else(self._infer_max)

    @property  # type: ignore
    @may
    def _infer_max(self):
        s = self._argspec
        if not (s.varargs or s.varkw):
            return self._param_count

    @property
    def name(self):
        return self._name.get_or_else(self._infer_name)

    @property
    def _infer_name(self):
        return camelcaseify(self._fun.__name__)

    @property  # type: ignore
    @may
    def exact_count(self):
        if self.max.contains(self.min):
            return self.min

    def check_length(self, args):
        l = len(args)
        return (
            self.min <= l and
            not self.max.exists(_ < l)
        )

    @property
    def count_spec(self):
        return self.exact_count\
            .map(lambda a: 'exactly {}'.format(a) if a else 'none')\
            .get_or_else(
                Just(self.min)
                .zip(self.max)
                .smap('between {} and {}'.format)
                .get_or_else('at least {}'.format(self.min))
            )

    def error(self, args):
        msg = 'argument count for command "{}" is {}, must be {} ({})'
        err = msg.format(self.name, len(args), self.count_spec, args)
        self.log.error(err)
        return err

    def _call_fun(self, obj, *args):
        return self._fun(obj, *args)

    def dispatch(self, obj, rpc_args):
        args = List.wrap(rpc_args).lift(0).get_or_else(List())
        if self.check_length(args):
            return self._call_fun(obj, *args)
        else:
            return self.error(args)

    @property
    def neovim_cmd(self):
        @neovim.command(self.name, nargs=self.nargs, **self._kw)
        def neovim_cmd_wrapper(obj, *rpc_args):
            return self.dispatch(obj, rpc_args)
        return neovim_cmd_wrapper