3
# Copyright 2014 Hewlett-Packard Development Company, L.P.
5
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6
# not use this file except in compliance with the License. You may obtain
7
# a copy of the License at
9
# http://www.apache.org/licenses/LICENSE-2.0
11
# Unless required by applicable law or agreed to in writing, software
12
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
# License for the specific language governing permissions and limitations
23
"""Various helper functions."""
26
def ast_args_to_str(args):
27
res = ('\n\tArgument/s:\n\t\t%s' %
28
'\n\t\t'.join([ast.dump(arg) for arg in args]))
32
def _get_attr_qual_name(node, aliases):
33
'''Get a the full name for the attribute node.
35
This will resolve a pseudo-qualified name for the attribute
36
rooted at node as long as all the deeper nodes are Names or
37
Attributes. This will give you how the code referenced the name but
38
will not tell you what the name actually refers to. If we
39
encounter a node without a static name we punt with an
40
empty string. If this encounters something more comples, such as
41
foo.mylist[0](a,b) we just return empty string.
43
:param node: AST Name or Attribute node
44
:param aliases: Import aliases dictionary
45
:returns: Qualified name refered to by the attribute or name.
47
if type(node) == _ast.Name:
48
if node.id in aliases:
49
return aliases[node.id]
51
elif type(node) == _ast.Attribute:
52
name = '%s.%s' % (_get_attr_qual_name(node.value, aliases), node.attr)
60
def get_call_name(node, aliases):
61
if type(node.func) == _ast.Name:
62
if deepgetattr(node, 'func.id') in aliases:
63
return aliases[deepgetattr(node, 'func.id')]
64
return(deepgetattr(node, 'func.id'))
65
elif type(node.func) == _ast.Attribute:
66
return _get_attr_qual_name(node.func, aliases)
71
def get_func_name(node):
72
return node.name # TODO(tkelsey): get that qualname using enclosing scope
75
def get_qual_attr(node, aliases):
77
if type(node) == _ast.Attribute:
79
val = deepgetattr(node, 'value.id')
83
prefix = deepgetattr(node, 'value.id')
85
# NOTE(tkelsey): degrade gracefully when we cant get the fully
86
# qualified name for an attr, just return its base name.
89
return("%s.%s" % (prefix, node.attr))
91
return "" # TODO(tkelsey): process other node types
94
def deepgetattr(obj, attr):
95
"""Recurses through an attribute chain to get the ultimate value."""
96
for key in attr.split('.'):
97
obj = getattr(obj, key)
101
def describe_symbol(sym):
102
assert type(sym) == symtable.Symbol
103
print("Symbol:", sym.get_name())
106
'referenced', 'imported', 'parameter',
107
'global', 'declared_global', 'local',
108
'free', 'assigned', 'namespace']:
109
if getattr(sym, 'is_' + prop)():
113
def lines_with_context(line_no, line_range, max_lines, file_len):
114
'''Get affected lines, plus context
116
This function takes a list of line numbers, adds one line
117
before the specified range, and two lines after, to provide
118
a bit more context. It then limits the number of lines to
119
the specified max_lines value.
120
:param line_no: The line of interest (trigger line)
121
:param line_range: The lines that make up the whole statement
122
:param max_lines: The maximum number of lines to output
123
:return l_range: A list of line numbers to output
126
# Catch a 0 or negative max lines, don't display any code
130
l_range = sorted(line_range)
132
# add one line before before and after, to make sure we don't miss
134
l_range.append(l_range[-1] + 1)
135
l_range.append(l_range[0] - 1)
137
l_range = sorted(l_range)
142
# limit scope to max_lines
143
if len(l_range) > max_lines:
144
# figure out a sane distribution of scope (extra lines after)
145
after = (max_lines - 1) / 2
146
before = max_lines - (after + 1)
147
target = l_range.index(line_no)
149
# skew things if the code is at the start or end of the statement
152
extra = before - target
156
gap = file_len - (target + 1)
166
start = target - before
169
if target + after > len(l_range) - 1:
170
end = len(l_range) - 1
175
l_range = l_range[start:end + 1]
180
class InvalidModulePath(Exception):
184
def get_module_qualname_from_path(path):
185
'''Get the module's qualified name by analysis of the path.
187
Resolve the absolute pathname and eliminate symlinks. This could result in
188
an incorrect name if symlinks are used to restructure the python lib
191
Starting from the right-most directory component look for __init__.py in
192
the directory component. If it exists then the directory name is part of
193
the module name. Move left to the subsequent directory components until a
194
directory is found without __init__.py.
196
:param: Path to module file. Relative paths will be resolved relative to
197
current working directory.
198
:return: fully qualified module name
201
(head, tail) = os.path.split(path)
202
if head == '' or tail == '':
203
raise InvalidModulePath('Invalid python file path: "%s"'
204
' Missing path or file name' % (path))
206
qname = [os.path.splitext(tail)[0]]
208
if os.path.isfile(os.path.join(head, '__init__.py')):
209
(head, tail) = os.path.split(head)
210
qname.insert(0, tail)
214
qualname = '.'.join(qname)
218
def namespace_path_join(base, name):
219
'''Extend the current namespace path with an additional name
221
Take a namespace path (i.e., package.module.class) and extends it
222
with an additional name (i.e., package.module.class.subclass).
223
This is similar to how os.path.join works.
225
:param base: (String) The base namespace path.
226
:param name: (String) The new name to append to the base path.
227
:returns: (String) A new namespace path resulting from combination of
230
return '%s.%s' % (base, name)
233
def namespace_path_split(path):
234
'''Split the namespace path into a pair (head, tail).
236
Tail will be the last namespace path component and head will
237
be everything leading up to that in the path. This is similar to
240
:param path: (String) A namespace path.
241
:returns: (String, String) A tuple where the first component is the base
242
path and the second is the last path component.
244
return tuple(path.rsplit('.', 1))
247
def safe_unicode(obj, *args):
248
'''return the unicode representation of obj.'''
250
return unicode(obj, *args)
251
except UnicodeDecodeError:
253
ascii_text = str(obj).encode('string_escape')
254
return unicode(ascii_text)
258
'''return the byte string representation of obj.'''
261
except UnicodeEncodeError:
263
return unicode(obj).encode('unicode_escape')