8d6d3568914e3227c074dd2830058c30284fc73f
[idea/community.git] / python / helpers / pycharm_generator_utils / util_methods.py
1 from pycharm_generator_utils.constants import *
2
3 try:
4     import inspect
5 except ImportError:
6     inspect = None
7
8 def create_named_tuple():   #TODO: user-skeleton
9         return """
10 class __namedtuple(tuple):
11     '''A mock base class for named tuples.'''
12
13     __slots__ = ()
14     _fields = ()
15
16     def __new__(cls, *args, **kwargs):
17         'Create a new instance of the named tuple.'
18         return tuple.__new__(cls, *args)
19
20     @classmethod
21     def _make(cls, iterable, new=tuple.__new__, len=len):
22         'Make a new named tuple object from a sequence or iterable.'
23         return new(cls, iterable)
24
25     def __repr__(self):
26         return ''
27
28     def _asdict(self):
29         'Return a new dict which maps field types to their values.'
30         return {}
31
32     def _replace(self, **kwargs):
33         'Return a new named tuple object replacing specified fields with new values.'
34         return self
35
36     def __getnewargs__(self):
37         return tuple(self)
38 """
39
40 def create_generator():
41         # Fake <type 'generator'>
42         if version[0] < 3:
43             next_name = "next"
44         else:
45             next_name = "__next__"
46         txt = """
47 class __generator(object):
48     '''A mock class representing the generator function type.'''
49     def __init__(self):
50         self.gi_code = None
51         self.gi_frame = None
52         self.gi_running = 0
53
54     def __iter__(self):
55         '''Defined to support iteration over container.'''
56         pass
57
58     def %s(self):
59         '''Return the next item from the container.'''
60         pass
61 """ % (next_name,)
62         if version[0] >= 3 or (version[0] == 2 and version[1] >= 5):
63             txt += """
64     def close(self):
65         '''Raises new GeneratorExit exception inside the generator to terminate the iteration.'''
66         pass
67
68     def send(self, value):
69         '''Resumes the generator and "sends" a value that becomes the result of the current yield-expression.'''
70         pass
71
72     def throw(self, type, value=None, traceback=None):
73         '''Used to raise an exception inside the generator.'''
74         pass
75 """
76         return txt
77
78 def _searchbases(cls, accum):
79 # logic copied from inspect.py
80     if cls not in accum:
81         accum.append(cls)
82         for x in cls.__bases__:
83             _searchbases(x, accum)
84
85
86 def get_mro(a_class):
87 # logic copied from inspect.py
88     """Returns a tuple of MRO classes."""
89     if hasattr(a_class, "__mro__"):
90         return a_class.__mro__
91     elif hasattr(a_class, "__bases__"):
92         bases = []
93         _searchbases(a_class, bases)
94         return tuple(bases)
95     else:
96         return tuple()
97
98
99 def get_bases(a_class): # TODO: test for classes that don't fit this scheme
100     """Returns a sequence of class's bases."""
101     if hasattr(a_class, "__bases__"):
102         return a_class.__bases__
103     else:
104         return ()
105
106
107 def is_callable(x):
108     return hasattr(x, '__call__')
109
110
111 def sorted_no_case(p_array):
112     """Sort an array case insensitevely, returns a sorted copy"""
113     p_array = list(p_array)
114     p_array = sorted(p_array, key=lambda x: x.upper())
115     return p_array
116
117
118 def cleanup(value):
119     result = []
120     prev = i = 0
121     length = len(value)
122     last_ascii = chr(127)
123     while i < length:
124         char = value[i]
125         replacement = None
126         if char == '\n':
127             replacement = '\\n'
128         elif char == '\r':
129             replacement = '\\r'
130         elif char < ' ' or char > last_ascii:
131             replacement = '?' # NOTE: such chars are rare; long swaths could be precessed differently
132         if replacement:
133             result.append(value[prev:i])
134             result.append(replacement)
135         i += 1
136     return "".join(result)
137
138
139 _prop_types = [type(property())]
140 #noinspection PyBroadException
141 try:
142     _prop_types.append(types.GetSetDescriptorType)
143 except:
144     pass
145
146 #noinspection PyBroadException
147 try:
148     _prop_types.append(types.MemberDescriptorType)
149 except:
150     pass
151
152 _prop_types = tuple(_prop_types)
153
154
155 def is_property(x):
156     return isinstance(x, _prop_types)
157
158
159 def sanitize_ident(x, is_clr=False):
160     """Takes an identifier and returns it sanitized"""
161     if x in ("class", "object", "def", "list", "tuple", "int", "float", "str", "unicode" "None"):
162         return "p_" + x
163     else:
164         if is_clr:
165             # it tends to have names like "int x", turn it to just x
166             xs = x.split(" ")
167             if len(xs) == 2:
168                 return sanitize_ident(xs[1])
169         return x.replace("-", "_").replace(" ", "_").replace(".", "_") # for things like "list-or-tuple" or "list or tuple"
170
171
172 def reliable_repr(value):
173     # some subclasses of built-in types (see PyGtk) may provide invalid __repr__ implementations,
174     # so we need to sanitize the output
175     if type(bool) == type and isinstance(value, bool):
176         return repr(bool(value))
177     for num_type in NUM_TYPES:
178         if isinstance(value, num_type):
179             return repr(num_type(value))
180     return repr(value)
181
182
183 def sanitize_value(p_value):
184     """Returns p_value or its part if it represents a sane simple value, else returns 'None'"""
185     if isinstance(p_value, STR_TYPES):
186         match = SIMPLE_VALUE_RE.match(p_value)
187         if match:
188             return match.groups()[match.lastindex - 1]
189         else:
190             return 'None'
191     elif isinstance(p_value, NUM_TYPES):
192         return reliable_repr(p_value)
193     elif p_value is None:
194         return 'None'
195     else:
196         if hasattr(p_value, "__name__") and hasattr(p_value, "__module__") and p_value.__module__ == BUILTIN_MOD_NAME:
197             return p_value.__name__ # float -> "float"
198         else:
199             return repr(repr(p_value)) # function -> "<function ...>", etc
200
201
202 def extract_alpha_prefix(p_string, default_prefix="some"):
203     """Returns 'foo' for things like 'foo1' or 'foo2'; if prefix cannot be found, the default is returned"""
204     match = NUM_IDENT_PATTERN.match(p_string)
205     prefix = match and match.groups()[match.lastindex - 1] or None
206     return prefix or default_prefix
207
208
209 def report(msg, *data):
210     """Say something at error level (stderr)"""
211     sys.stderr.write(msg % data)
212     sys.stderr.write("\n")
213
214
215 def say(msg, *data):
216     """Say something at info level (stdout)"""
217     sys.stdout.write(msg % data)
218     sys.stdout.write("\n")
219
220
221 def transform_seq(results, toplevel=True):
222     """Transforms a tree of ParseResults into a param spec string."""
223     is_clr = sys.platform == "cli"
224     ret = [] # add here token to join
225     for token in results:
226         token_type = token[0]
227         if token_type is T_SIMPLE:
228             token_name = token[1]
229             if len(token) == 3: # name with value
230                 if toplevel:
231                     ret.append(sanitize_ident(token_name, is_clr) + "=" + sanitize_value(token[2]))
232                 else:
233                     # smth like "a, (b1=1, b2=2)", make it "a, p_b"
234                     return ["p_" + results[0][1]] # NOTE: for each item of tuple, return the same name of its 1st item.
235             elif token_name == TRIPLE_DOT:
236                 if toplevel and not has_item_starting_with(ret, "*"):
237                     ret.append("*more")
238                 else:
239                 # we're in a "foo, (bar1, bar2, ...)"; make it "foo, bar_tuple"
240                     return extract_alpha_prefix(results[0][1]) + "_tuple"
241             else: # just name
242                 ret.append(sanitize_ident(token_name, is_clr))
243         elif token_type is T_NESTED:
244             inner = transform_seq(token[1:], False)
245             if len(inner) != 1:
246                 ret.append(inner)
247             else:
248                 ret.append(inner[0]) # [foo] -> foo
249         elif token_type is T_OPTIONAL:
250             ret.extend(transform_optional_seq(token))
251         elif token_type is T_RETURN:
252             pass # this is handled elsewhere
253         else:
254             raise Exception("This cannot be a token type: " + repr(token_type))
255     return ret
256
257
258 def transform_optional_seq(results):
259     """
260     Produces a string that describes the optional part of parameters.
261     @param results must start from T_OPTIONAL.
262     """
263     assert results[0] is T_OPTIONAL, "transform_optional_seq expects a T_OPTIONAL node, sees " + \
264                                      repr(results[0])
265     is_clr = sys.platform == "cli"
266     ret = []
267     for token in results[1:]:
268         token_type = token[0]
269         if token_type is T_SIMPLE:
270             token_name = token[1]
271             if len(token) == 3: # name with value; little sense, but can happen in a deeply nested optional
272                 ret.append(sanitize_ident(token_name, is_clr) + "=" + sanitize_value(token[2]))
273             elif token_name == '...':
274             # we're in a "foo, [bar, ...]"; make it "foo, *bar"
275                 return ["*" + extract_alpha_prefix(
276                     results[1][1])] # we must return a seq; [1] is first simple, [1][1] is its name
277             else: # just name
278                 ret.append(sanitize_ident(token_name, is_clr) + "=None")
279         elif token_type is T_OPTIONAL:
280             ret.extend(transform_optional_seq(token))
281             # maybe handle T_NESTED if such cases ever occur in real life
282             # it can't be nested in a sane case, really
283     return ret
284
285
286 def flatten(seq):
287     """Transforms tree lists like ['a', ['b', 'c'], 'd'] to strings like '(a, (b, c), d)', enclosing each tree level in parens."""
288     ret = []
289     for one in seq:
290         if type(one) is list:
291             ret.append(flatten(one))
292         else:
293             ret.append(one)
294     return "(" + ", ".join(ret) + ")"
295
296
297 def make_names_unique(seq, name_map=None):
298     """
299     Returns a copy of tree list seq where all clashing names are modified by numeric suffixes:
300     ['a', 'b', 'a', 'b'] becomes ['a', 'b', 'a_1', 'b_1'].
301     Each repeating name has its own counter in the name_map.
302     """
303     ret = []
304     if not name_map:
305         name_map = {}
306     for one in seq:
307         if type(one) is list:
308             ret.append(make_names_unique(one, name_map))
309         else:
310             one_key = lstrip(one, "*") # starred parameters are unique sans stars
311             if one_key in name_map:
312                 old_one = one_key
313                 one = one + "_" + str(name_map[old_one])
314                 name_map[old_one] += 1
315             else:
316                 name_map[one_key] = 1
317             ret.append(one)
318     return ret
319
320
321 def has_item_starting_with(p_seq, p_start):
322     for item in p_seq:
323         if isinstance(item, STR_TYPES) and item.startswith(p_start):
324             return True
325     return False
326
327
328 def out_docstring(out_func, docstring, indent):
329     if not isinstance(docstring, str): return
330     lines = docstring.strip().split("\n")
331     if lines:
332         if len(lines) == 1:
333             out_func(indent, '""" ' + lines[0] + ' """')
334         else:
335             out_func(indent, '"""')
336             for line in lines:
337                 try:
338                     out_func(indent, line)
339                 except UnicodeEncodeError:
340                     continue
341             out_func(indent, '"""')
342
343 def out_doc_attr(out_func, p_object, indent, p_class=None):
344     the_doc = getattr(p_object, "__doc__", None)
345     if the_doc:
346         if p_class and the_doc == object.__init__.__doc__ and p_object is not object.__init__ and p_class.__doc__:
347             the_doc = str(p_class.__doc__) # replace stock init's doc with class's; make it a certain string.
348             the_doc += "\n# (copied from class doc)"
349         out_docstring(out_func, the_doc, indent)
350     else:
351         out_func(indent, "# no doc")
352
353 def is_skipped_in_module(p_module, p_value):
354     """
355     Returns True if p_value's value must be skipped for module p_module.
356     """
357     skip_list = SKIP_VALUE_IN_MODULE.get(p_module, [])
358     if p_value in skip_list:
359         return True
360     skip_list = SKIP_VALUE_IN_MODULE.get("*", [])
361     if p_value in skip_list:
362         return True
363     return False
364
365 def restore_predefined_builtin(class_name, func_name):
366     spec = func_name + PREDEFINED_BUILTIN_SIGS[(class_name, func_name)]
367     note = "known special case of " + (class_name and class_name + "." or "") + func_name
368     return (spec, note)
369
370 def restore_by_inspect(p_func):
371     """
372     Returns paramlist restored by inspect.
373     """
374     args, varg, kwarg, defaults = inspect.getargspec(p_func)
375     spec = []
376     if defaults:
377         dcnt = len(defaults) - 1
378     else:
379         dcnt = -1
380     args = args or []
381     args.reverse() # backwards, for easier defaults handling
382     for arg in args:
383         if dcnt >= 0:
384             arg += "=" + sanitize_value(defaults[dcnt])
385             dcnt -= 1
386         spec.insert(0, arg)
387     if varg:
388         spec.append("*" + varg)
389     if kwarg:
390         spec.append("**" + kwarg)
391     return flatten(spec)
392
393 def restore_parameters_for_overloads(parameter_lists):
394     param_index = 0
395     star_args = False
396     optional = False
397     params = []
398     while True:
399         parameter_lists_copy = [pl for pl in parameter_lists]
400         for pl in parameter_lists_copy:
401             if param_index >= len(pl):
402                 parameter_lists.remove(pl)
403                 optional = True
404         if not parameter_lists:
405             break
406         name = parameter_lists[0][param_index]
407         for pl in parameter_lists[1:]:
408             if pl[param_index] != name:
409                 star_args = True
410                 break
411         if star_args: break
412         if optional and not '=' in name:
413             params.append(name + '=None')
414         else:
415             params.append(name)
416         param_index += 1
417     if star_args:
418         params.append("*__args")
419     return params
420
421 def build_signature(p_name, params):
422     return p_name + '(' + ', '.join(params) + ')'
423
424
425 def propose_first_param(deco):
426     """@return: name of missing first paramater, considering a decorator"""
427     if deco is None:
428         return "self"
429     if deco == "classmethod":
430         return "cls"
431         # if deco == "staticmethod":
432     return None
433
434 def qualifier_of(cls, qualifiers_to_skip):
435     m = getattr(cls, "__module__", None)
436     if m in qualifiers_to_skip:
437         return ""
438     return m
439
440 def handle_error_func(item_name, out):
441     exctype, value = sys.exc_info()[:2]
442     msg = "Error generating skeleton for function %s: %s"
443     args = item_name, value
444     report(msg, *args)
445     out(0, "# " + msg % args)
446     out(0, "")
447
448 def format_accessors(accessor_line, getter, setter, deleter):
449     """Nicely format accessors, like 'getter, fdel=deleter'"""
450     ret = []
451     consecutive = True
452     for key, arg, par in (('r', 'fget', getter), ('w', 'fset', setter), ('d', 'fdel', deleter)):
453         if key in accessor_line:
454             if consecutive:
455                 ret.append(par)
456             else:
457                 ret.append(arg + "=" + par)
458         else:
459             consecutive = False
460     return ", ".join(ret)
461
462
463 def has_regular_python_ext(file_name):
464     """Does name end with .py?"""
465     return file_name.endswith(".py")
466     # Note that the standard library on MacOS X 10.6 is shipped only as .pyc files, so we need to
467     # have them processed by the generator in order to have any code insight for the standard library.
468
469
470 def detect_constructor(p_class):
471     # try to inspect the thing
472     constr = getattr(p_class, "__init__")
473     if constr and inspect and inspect.isfunction(constr):
474         args, _, _, _ = inspect.getargspec(constr)
475         return ", ".join(args)
476     else:
477         return None
478
479 ##############  notes, actions #################################################################
480 _is_verbose = False # controlled by -v
481
482 CURRENT_ACTION = "nothing yet"
483
484 def action(msg, *data):
485     global CURRENT_ACTION
486     CURRENT_ACTION = msg % data
487     note(msg, *data)
488
489 def note(msg, *data):
490     """Say something at debug info level (stderr)"""
491     global _is_verbose
492     if _is_verbose:
493         sys.stderr.write(msg % data)
494         sys.stderr.write("\n")
495
496
497 ##############  plaform-specific methods    #######################################################
498 import sys
499 if sys.platform == 'cli':
500     #noinspection PyUnresolvedReferences
501     import clr
502
503 # http://blogs.msdn.com/curth/archive/2009/03/29/an-ironpython-profiler.aspx
504 def print_profile():
505     data = []
506     data.extend(clr.GetProfilerData())
507     data.sort(lambda x, y: -cmp(x.ExclusiveTime, y.ExclusiveTime))
508
509     for pd in data:
510         say('%s\t%d\t%d\t%d', pd.Name, pd.InclusiveTime, pd.ExclusiveTime, pd.Calls)
511
512 def is_clr_type(clr_type):
513     if not clr_type: return False
514     try:
515         clr.GetClrType(clr_type)
516         return True
517     except TypeError:
518         return False
519
520 def restore_clr(p_name, p_class):
521     """
522     Restore the function signature by the CLR type signature
523     """
524     clr_type = clr.GetClrType(p_class)
525     if p_name == '__new__':
526         methods = [c for c in clr_type.GetConstructors()]
527         if not methods:
528             return p_name + '(*args)', 'cannot find CLR constructor'
529     else:
530         methods = [m for m in clr_type.GetMethods() if m.Name == p_name]
531         if not methods:
532             bases = p_class.__bases__
533             if len(bases) == 1 and p_name in dir(bases[0]):
534             # skip inherited methods
535                 return None, None
536             return p_name + '(*args)', 'cannot find CLR method'
537
538     parameter_lists = []
539     for m in methods:
540         parameter_lists.append([p.Name for p in m.GetParameters()])
541     params = restore_parameters_for_overloads(parameter_lists)
542     if not methods[0].IsStatic:
543         params = ['self'] + params
544     return build_signature(p_name, params), None