1import rope.base.exceptions
2import rope.base.pyobjects
3from rope.base.builtins import Lambda
4from rope.base import worder
5
6
7class DefinitionInfo(object):
8
9    def __init__(self, function_name, is_method, args_with_defaults,
10                 args_arg, keywords_arg):
11        self.function_name = function_name
12        self.is_method = is_method
13        self.args_with_defaults = args_with_defaults
14        self.args_arg = args_arg
15        self.keywords_arg = keywords_arg
16
17    def to_string(self):
18        return '%s(%s)' % (self.function_name, self.arguments_to_string())
19
20    def arguments_to_string(self, from_index=0):
21        params = []
22        for arg, default in self.args_with_defaults:
23            if default is not None:
24                params.append('%s=%s' % (arg, default))
25            else:
26                params.append(arg)
27        if self.args_arg is not None:
28            params.append('*' + self.args_arg)
29        if self.keywords_arg:
30            params.append('**' + self.keywords_arg)
31        return ', '.join(params[from_index:])
32
33    @staticmethod
34    def _read(pyfunction, code):
35        kind = pyfunction.get_kind()
36        is_method = kind == 'method'
37        is_lambda = kind == 'lambda'
38        info = _FunctionParser(code, is_method, is_lambda)
39        args, keywords = info.get_parameters()
40        args_arg = None
41        keywords_arg = None
42        if args and args[-1].startswith('**'):
43            keywords_arg = args[-1][2:]
44            del args[-1]
45        if args and args[-1].startswith('*'):
46            args_arg = args[-1][1:]
47            del args[-1]
48        args_with_defaults = [(name, None) for name in args]
49        args_with_defaults.extend(keywords)
50        return DefinitionInfo(info.get_function_name(), is_method,
51                              args_with_defaults, args_arg, keywords_arg)
52
53    @staticmethod
54    def read(pyfunction):
55        pymodule = pyfunction.get_module()
56        word_finder = worder.Worder(pymodule.source_code)
57        lineno = pyfunction.get_ast().lineno
58        start = pymodule.lines.get_line_start(lineno)
59        if isinstance(pyfunction, Lambda):
60            call = word_finder.get_lambda_and_args(start)
61        else:
62            call = word_finder.get_function_and_args_in_header(start)
63        return DefinitionInfo._read(pyfunction, call)
64
65
66class CallInfo(object):
67
68    def __init__(self, function_name, args, keywords, args_arg,
69                 keywords_arg, implicit_arg, constructor):
70        self.function_name = function_name
71        self.args = args
72        self.keywords = keywords
73        self.args_arg = args_arg
74        self.keywords_arg = keywords_arg
75        self.implicit_arg = implicit_arg
76        self.constructor = constructor
77
78    def to_string(self):
79        function = self.function_name
80        if self.implicit_arg:
81            function = self.args[0] + '.' + self.function_name
82        params = []
83        start = 0
84        if self.implicit_arg or self.constructor:
85            start = 1
86        if self.args[start:]:
87            params.extend(self.args[start:])
88        if self.keywords:
89            params.extend(['%s=%s' % (name, value)
90                          for name, value in self.keywords])
91        if self.args_arg is not None:
92            params.append('*' + self.args_arg)
93        if self.keywords_arg:
94            params.append('**' + self.keywords_arg)
95        return '%s(%s)' % (function, ', '.join(params))
96
97    @staticmethod
98    def read(primary, pyname, definition_info, code):
99        is_method_call = CallInfo._is_method_call(primary, pyname)
100        is_constructor = CallInfo._is_class(pyname)
101        is_classmethod = CallInfo._is_classmethod(pyname)
102        info = _FunctionParser(code, is_method_call or is_classmethod)
103        args, keywords = info.get_parameters()
104        args_arg = None
105        keywords_arg = None
106        if args and args[-1].startswith('**'):
107            keywords_arg = args[-1][2:]
108            del args[-1]
109        if args and args[-1].startswith('*'):
110            args_arg = args[-1][1:]
111            del args[-1]
112        if is_constructor:
113            args.insert(0, definition_info.args_with_defaults[0][0])
114        return CallInfo(info.get_function_name(), args, keywords, args_arg,
115                        keywords_arg, is_method_call or is_classmethod,
116                        is_constructor)
117
118    @staticmethod
119    def _is_method_call(primary, pyname):
120        return primary is not None and \
121            isinstance(primary.get_object().get_type(),
122                       rope.base.pyobjects.PyClass) and \
123            CallInfo._is_method(pyname)
124
125    @staticmethod
126    def _is_class(pyname):
127        return pyname is not None and \
128            isinstance(pyname.get_object(),
129                       rope.base.pyobjects.PyClass)
130
131    @staticmethod
132    def _is_method(pyname):
133        if pyname is not None and \
134           isinstance(pyname.get_object(), rope.base.pyobjects.PyFunction):
135            return pyname.get_object().get_kind() == 'method'
136        return False
137
138    @staticmethod
139    def _is_classmethod(pyname):
140        if pyname is not None and \
141           isinstance(pyname.get_object(), rope.base.pyobjects.PyFunction):
142            return pyname.get_object().get_kind() == 'classmethod'
143        return False
144
145
146class ArgumentMapping(object):
147
148    def __init__(self, definition_info, call_info):
149        self.call_info = call_info
150        self.param_dict = {}
151        self.keyword_args = []
152        self.args_arg = []
153        for index, value in enumerate(call_info.args):
154            if index < len(definition_info.args_with_defaults):
155                name = definition_info.args_with_defaults[index][0]
156                self.param_dict[name] = value
157            else:
158                self.args_arg.append(value)
159        for name, value in call_info.keywords:
160            index = -1
161            for pair in definition_info.args_with_defaults:
162                if pair[0] == name:
163                    self.param_dict[name] = value
164                    break
165            else:
166                self.keyword_args.append((name, value))
167
168    def to_call_info(self, definition_info):
169        args = []
170        keywords = []
171        for index in range(len(definition_info.args_with_defaults)):
172            name = definition_info.args_with_defaults[index][0]
173            if name in self.param_dict:
174                args.append(self.param_dict[name])
175            else:
176                for i in range(index, len(definition_info.args_with_defaults)):
177                    name = definition_info.args_with_defaults[i][0]
178                    if name in self.param_dict:
179                        keywords.append((name, self.param_dict[name]))
180                break
181        args.extend(self.args_arg)
182        keywords.extend(self.keyword_args)
183        return CallInfo(self.call_info.function_name, args, keywords,
184                        self.call_info.args_arg, self.call_info.keywords_arg,
185                        self.call_info.implicit_arg,
186                        self.call_info.constructor)
187
188
189class _FunctionParser(object):
190
191    def __init__(self, call, implicit_arg, is_lambda=False):
192        self.call = call
193        self.implicit_arg = implicit_arg
194        self.word_finder = worder.Worder(self.call)
195        if is_lambda:
196            self.last_parens = self.call.rindex(':')
197        else:
198            self.last_parens = self.call.rindex(')')
199        self.first_parens = self.word_finder._find_parens_start(
200            self.last_parens)
201
202    def get_parameters(self):
203        args, keywords = self.word_finder.get_parameters(self.first_parens,
204                                                         self.last_parens)
205        if self.is_called_as_a_method():
206            instance = self.call[:self.call.rindex('.', 0, self.first_parens)]
207            args.insert(0, instance.strip())
208        return args, keywords
209
210    def get_instance(self):
211        if self.is_called_as_a_method():
212            return self.word_finder.get_primary_at(
213                self.call.rindex('.', 0, self.first_parens) - 1)
214
215    def get_function_name(self):
216        if self.is_called_as_a_method():
217            return self.word_finder.get_word_at(self.first_parens - 1)
218        else:
219            return self.word_finder.get_primary_at(self.first_parens - 1)
220
221    def is_called_as_a_method(self):
222        return self.implicit_arg and '.' in self.call[:self.first_parens]
223