Welcome to mirror list, hosted at ThFree Co, Russian Federation.

complete_namespace.py « console « modules « scripts « release - git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 68c06e641dc8c8dddddd909535e14c85beeb16c1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

# Copyright (c) 2009 www.stani.be

# <pep8-80 compliant>

"""Autocomplete with the standard library"""

import re
import rlcompleter


RE_INCOMPLETE_INDEX = re.compile(r'(.*?)\[[^\]]+$')

TEMP = '__tEmP__'  # only \w characters are allowed!
TEMP_N = len(TEMP)


def is_dict(obj):
    """Returns whether obj is a dictionary"""
    return hasattr(obj, 'keys') and hasattr(getattr(obj, 'keys'), '__call__')


def is_struct_seq(obj):
    """Returns whether obj is a structured sequence subclass: sys.float_info"""
    return isinstance(obj, tuple) and hasattr(obj, 'n_fields')


def complete_names(word, namespace):
    """Complete variable names or attributes

    :param word: word to be completed
    :type word: str
    :param namespace: namespace
    :type namespace: dict
    :returns: completion matches
    :rtype: list of str

    >>> complete_names('fo', {'foo': 'bar'})
    ['foo', 'for', 'format(']
    """
    # start completer
    completer = rlcompleter.Completer(namespace)
    # find matches with std library (don't try to implement this yourself)
    completer.complete(word, 0)
    return sorted(set(completer.matches))


def complete_indices(word, namespace, *, obj=None, base=None):
    """Complete a list or dictionary with its indices:

    * integer numbers for list
    * any keys for dictionary

    :param word: word to be completed
    :type word: str
    :param namespace: namespace
    :type namespace: dict
    :param obj: object evaluated from base
    :param base: sub-string which can be evaluated into an object.
    :type base: str
    :returns: completion matches
    :rtype: list of str

    >>> complete_indices('foo', {'foo': range(5)})
    ['foo[0]', 'foo[1]', 'foo[2]', 'foo[3]', 'foo[4]']
    >>> complete_indices('foo', {'foo': {'bar':0, 1:2}})
    ['foo[1]', "foo['bar']"]
    >>> complete_indices("foo['b", {'foo': {'bar':0, 1:2}}, base='foo')
    ["foo['bar']"]
    """
    # FIXME: 'foo["b'
    if base is None:
        base = word
    if obj is None:
        try:
            obj = eval(base, namespace)
        except Exception:
            return []
    if not hasattr(obj, '__getitem__'):
        # obj is not a list or dictionary
        return []

    obj_is_dict = is_dict(obj)

    # rare objects have a __getitem__ but no __len__ (eg. BMEdge)
    if not obj_is_dict:
        try:
            obj_len = len(obj)
        except TypeError:
            return []

    if obj_is_dict:
        # dictionary type
        matches = ['%s[%r]' % (base, key) for key in sorted(obj.keys())]
    else:
        # list type
        matches = ['%s[%d]' % (base, idx) for idx in range(obj_len)]
    if word != base:
        matches = [match for match in matches if match.startswith(word)]
    return matches


def complete(word, namespace, *, private=True):
    """Complete word within a namespace with the standard rlcompleter
    module. Also supports index or key access [].

    :param word: word to be completed
    :type word: str
    :param namespace: namespace
    :type namespace: dict
    :param private: whether private attribute/methods should be returned
    :type private: bool
    :returns: completion matches
    :rtype: list of str

    >>> complete('foo[1', {'foo': range(14)})
    ['foo[1]', 'foo[10]', 'foo[11]', 'foo[12]', 'foo[13]']
    >>> complete('foo[0]', {'foo': [range(5)]})
    ['foo[0][0]', 'foo[0][1]', 'foo[0][2]', 'foo[0][3]', 'foo[0][4]']
    >>> complete('foo[0].i', {'foo': [range(5)]})
    ['foo[0].index(', 'foo[0].insert(']
    >>> complete('rlcompleter', {'rlcompleter': rlcompleter})
    ['rlcompleter.']
    """
    #
    # if word is empty -> nothing to complete
    if not word:
        return []

    re_incomplete_index = RE_INCOMPLETE_INDEX.search(word)
    if re_incomplete_index:
        # ignore incomplete index at the end, e.g 'a[1' -> 'a'
        matches = complete_indices(word, namespace,
                                   base=re_incomplete_index.group(1))

    elif not('[' in word):
        matches = complete_names(word, namespace)

    elif word[-1] == ']':
        matches = [word]

    elif '.' in word:
        # brackets are normally not allowed -> work around

        # remove brackets by using a temp var without brackets
        obj, attr = word.rsplit('.', 1)
        try:
            # do not run the obj expression in the console
            namespace[TEMP] = eval(obj, namespace)
        except Exception:
            return []
        matches = complete_names(TEMP + '.' + attr, namespace)
        matches = [obj + match[TEMP_N:] for match in matches]
        del namespace[TEMP]

    else:
        # safety net, but when would this occur?
        return []

    if not matches:
        return []

    # add '.', '('  or '[' if no match has been found
    elif len(matches) == 1 and matches[0] == word:

        # try to retrieve the object
        try:
            obj = eval(word, namespace)
        except Exception:
            return []
        # ignore basic types
        if type(obj) in {bool, float, int, str}:
            return []
        # an extra char '[', '(' or '.' will be added
        if hasattr(obj, '__getitem__') and not is_struct_seq(obj):
            # list or dictionary
            matches = complete_indices(word, namespace, obj=obj)
        elif hasattr(obj, '__call__'):
            # callables
            matches = [word + '(']
        else:
            # any other type
            matches = [word + '.']

    # separate public from private
    public_matches = [match for match in matches if not('._' in match)]
    if private:
        private_matches = [match for match in matches if '._' in match]
        return public_matches + private_matches
    else:
        return public_matches