Test Failed
Push — master ( a5ee34...588b1f )
by Justin M.
34s queued 10s
created

pynvim.api.common.RemoteApi.__getattr__()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
ccs 2
cts 2
cp 1
crap 1
1
"""Code shared between the API classes."""
2 6
import functools
3
4 6
from msgpack import unpackb
5
6 6
from ..compat import unicode_errors_default
7
8 6
__all__ = ()
9
10
11 6
class NvimError(Exception):
12 6
    pass
13
14
15 6
class Remote(object):
16
17
    """Base class for Nvim objects(buffer/window/tabpage).
18
19
    Each type of object has it's own specialized class with API wrappers around
20
    the msgpack-rpc session. This implements equality which takes the remote
21
    object handle into consideration.
22
    """
23
24 6
    def __init__(self, session, code_data):
25
        """Initialize from session and code_data immutable object.
26
27
        The `code_data` contains serialization information required for
28
        msgpack-rpc calls. It must be immutable for Buffer equality to work.
29
        """
30 6
        self._session = session
31 6
        self.code_data = code_data
32 6
        self.handle = unpackb(code_data[1])
33 6
        self.api = RemoteApi(self, self._api_prefix)
34 6
        self.vars = RemoteMap(self, self._api_prefix + 'get_var',
35
                              self._api_prefix + 'set_var',
36
                              self._api_prefix + 'del_var')
37 6
        self.options = RemoteMap(self, self._api_prefix + 'get_option',
38
                                 self._api_prefix + 'set_option')
39
40 6
    def __repr__(self):
41
        """Get text representation of the object."""
42 6
        return '<%s(handle=%r)>' % (
43
            self.__class__.__name__,
44
            self.handle,
45
        )
46
47 6
    def __eq__(self, other):
48
        """Return True if `self` and `other` are the same object."""
49 6
        return (hasattr(other, 'code_data')
50
                and other.code_data == self.code_data)
51
52 6
    def __hash__(self):
53
        """Return hash based on remote object id."""
54 6
        return self.code_data.__hash__()
55
56 6
    def request(self, name, *args, **kwargs):
57
        """Wrapper for nvim.request."""
58 6
        return self._session.request(name, self, *args, **kwargs)
59
60
61 6
class RemoteApi(object):
62
63
    """Wrapper to allow api methods to be called like python methods."""
64
65 6
    def __init__(self, obj, api_prefix):
66
        """Initialize a RemoteApi with object and api prefix."""
67 6
        self._obj = obj
68 6
        self._api_prefix = api_prefix
69
70 6
    def __getattr__(self, name):
71
        """Return wrapper to named api method."""
72 6
        return functools.partial(self._obj.request, self._api_prefix + name)
73
74
75 6
def transform_keyerror(exc):
76 6
    if isinstance(exc, NvimError):
77 6
        if exc.args[0].startswith('Key not found:'):
78 6
            return KeyError(exc.args[0])
79 6
        if exc.args[0].startswith('Invalid option name:'):
80 6
            return KeyError(exc.args[0])
81
    return exc
82
83
84 6
class RemoteMap(object):
85
    """Represents a string->object map stored in Nvim.
86
87
    This is the dict counterpart to the `RemoteSequence` class, but it is used
88
    as a generic way of retrieving values from the various map-like data
89
    structures present in Nvim.
90
91
    It is used to provide a dict-like API to vim variables and options.
92
    """
93
94 6
    _set = None
95 6
    _del = None
96
97 6
    def __init__(self, obj, get_method, set_method=None, del_method=None):
98
        """Initialize a RemoteMap with session, getter/setter."""
99 6
        self._get = functools.partial(obj.request, get_method)
100 6
        if set_method:
101 6
            self._set = functools.partial(obj.request, set_method)
102 6
        if del_method:
103 6
            self._del = functools.partial(obj.request, del_method)
104
105 6
    def __getitem__(self, key):
106
        """Return a map value by key."""
107 6
        try:
108 6
            return self._get(key)
109 6
        except NvimError as exc:
110 6
            raise transform_keyerror(exc)
111
112 6
    def __setitem__(self, key, value):
113
        """Set a map value by key(if the setter was provided)."""
114 6
        if not self._set:
115
            raise TypeError('This dict is read-only')
116 6
        self._set(key, value)
117
118 6
    def __delitem__(self, key):
119
        """Delete a map value by associating None with the key."""
120 6
        if not self._del:
121
            raise TypeError('This dict is read-only')
122 6
        try:
123 6
            return self._del(key)
124 6
        except NvimError as exc:
125 6
            raise transform_keyerror(exc)
126
127 6
    def __contains__(self, key):
128
        """Check if key is present in the map."""
129
        try:
130
            self._get(key)
131
            return True
132
        except Exception:
133
            return False
134
135 6
    def get(self, key, default=None):
136
        """Return value for key if present, else a default value."""
137 6
        try:
138 6
            return self.__getitem__(key)
139 6
        except KeyError:
140 6
            return default
141
142
143 6
class RemoteSequence(object):
144
145
    """Represents a sequence of objects stored in Nvim.
146
147
    This class is used to wrap msgapck-rpc functions that work on Nvim
148
    sequences(of lines, buffers, windows and tabpages) with an API that
149
    is similar to the one provided by the python-vim interface.
150
151
    For example, the 'windows' property of the `Nvim` class is a RemoteSequence
152
    sequence instance, and the expression `nvim.windows[0]` is translated to
153
    session.request('nvim_list_wins')[0].
154
155
    One important detail about this class is that all methods will fetch the
156
    sequence into a list and perform the necessary manipulation
157
    locally(iteration, indexing, counting, etc).
158
    """
159
160 6
    def __init__(self, session, method):
161
        """Initialize a RemoteSequence with session, method."""
162 6
        self._fetch = functools.partial(session.request, method)
163
164 6
    def __len__(self):
165
        """Return the length of the remote sequence."""
166 6
        return len(self._fetch())
167
168 6
    def __getitem__(self, idx):
169
        """Return a sequence item by index."""
170 6
        if not isinstance(idx, slice):
171 6
            return self._fetch()[idx]
172
        return self._fetch()[idx.start:idx.stop]
173
174 6
    def __iter__(self):
175
        """Return an iterator for the sequence."""
176 6
        items = self._fetch()
177 6
        for item in items:
178 6
            yield item
179
180 6
    def __contains__(self, item):
181
        """Check if an item is present in the sequence."""
182
        return item in self._fetch()
183
184
185 6
def _identity(obj, session, method, kind):
186
    return obj
187
188
189 6
def decode_if_bytes(obj, mode=True):
190
    """Decode obj if it is bytes."""
191 6
    if mode is True:
192 6
        mode = unicode_errors_default
193 6
    if isinstance(obj, bytes):
194 6
        return obj.decode("utf-8", errors=mode)
195 4
    return obj
196
197
198 6
def walk(fn, obj, *args, **kwargs):
199
    """Recursively walk an object graph applying `fn`/`args` to objects."""
200 6
    if type(obj) in [list, tuple]:
201 6
        return list(walk(fn, o, *args) for o in obj)
202 6
    if type(obj) is dict:
203 6
        return dict((walk(fn, k, *args), walk(fn, v, *args)) for k, v in
204
                    obj.items())
205
    return fn(obj, *args, **kwargs)
206