##// END OF EJS Templates
contrib: allow symbol imports from hgclient for tests
Augie Fackler -
r33916:2d64b2f1 default
parent child Browse files
Show More
@@ -1,767 +1,768 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2
2
3 from __future__ import absolute_import, print_function
3 from __future__ import absolute_import, print_function
4
4
5 import ast
5 import ast
6 import collections
6 import collections
7 import os
7 import os
8 import re
8 import re
9 import sys
9 import sys
10
10
11 # Import a minimal set of stdlib modules needed for list_stdlib_modules()
11 # Import a minimal set of stdlib modules needed for list_stdlib_modules()
12 # to work when run from a virtualenv. The modules were chosen empirically
12 # to work when run from a virtualenv. The modules were chosen empirically
13 # so that the return value matches the return value without virtualenv.
13 # so that the return value matches the return value without virtualenv.
14 if True: # disable lexical sorting checks
14 if True: # disable lexical sorting checks
15 try:
15 try:
16 import BaseHTTPServer as basehttpserver
16 import BaseHTTPServer as basehttpserver
17 except ImportError:
17 except ImportError:
18 basehttpserver = None
18 basehttpserver = None
19 import zlib
19 import zlib
20
20
21 # Whitelist of modules that symbols can be directly imported from.
21 # Whitelist of modules that symbols can be directly imported from.
22 allowsymbolimports = (
22 allowsymbolimports = (
23 '__future__',
23 '__future__',
24 'hgclient',
24 'mercurial',
25 'mercurial',
25 'mercurial.hgweb.common',
26 'mercurial.hgweb.common',
26 'mercurial.hgweb.request',
27 'mercurial.hgweb.request',
27 'mercurial.i18n',
28 'mercurial.i18n',
28 'mercurial.node',
29 'mercurial.node',
29 # for cffi modules to re-export pure functions
30 # for cffi modules to re-export pure functions
30 'mercurial.pure.base85',
31 'mercurial.pure.base85',
31 'mercurial.pure.bdiff',
32 'mercurial.pure.bdiff',
32 'mercurial.pure.diffhelpers',
33 'mercurial.pure.diffhelpers',
33 'mercurial.pure.mpatch',
34 'mercurial.pure.mpatch',
34 'mercurial.pure.osutil',
35 'mercurial.pure.osutil',
35 'mercurial.pure.parsers',
36 'mercurial.pure.parsers',
36 )
37 )
37
38
38 # Whitelist of symbols that can be directly imported.
39 # Whitelist of symbols that can be directly imported.
39 directsymbols = (
40 directsymbols = (
40 'demandimport',
41 'demandimport',
41 )
42 )
42
43
43 # Modules that must be aliased because they are commonly confused with
44 # Modules that must be aliased because they are commonly confused with
44 # common variables and can create aliasing and readability issues.
45 # common variables and can create aliasing and readability issues.
45 requirealias = {
46 requirealias = {
46 'ui': 'uimod',
47 'ui': 'uimod',
47 }
48 }
48
49
49 def usingabsolute(root):
50 def usingabsolute(root):
50 """Whether absolute imports are being used."""
51 """Whether absolute imports are being used."""
51 if sys.version_info[0] >= 3:
52 if sys.version_info[0] >= 3:
52 return True
53 return True
53
54
54 for node in ast.walk(root):
55 for node in ast.walk(root):
55 if isinstance(node, ast.ImportFrom):
56 if isinstance(node, ast.ImportFrom):
56 if node.module == '__future__':
57 if node.module == '__future__':
57 for n in node.names:
58 for n in node.names:
58 if n.name == 'absolute_import':
59 if n.name == 'absolute_import':
59 return True
60 return True
60
61
61 return False
62 return False
62
63
63 def walklocal(root):
64 def walklocal(root):
64 """Recursively yield all descendant nodes but not in a different scope"""
65 """Recursively yield all descendant nodes but not in a different scope"""
65 todo = collections.deque(ast.iter_child_nodes(root))
66 todo = collections.deque(ast.iter_child_nodes(root))
66 yield root, False
67 yield root, False
67 while todo:
68 while todo:
68 node = todo.popleft()
69 node = todo.popleft()
69 newscope = isinstance(node, ast.FunctionDef)
70 newscope = isinstance(node, ast.FunctionDef)
70 if not newscope:
71 if not newscope:
71 todo.extend(ast.iter_child_nodes(node))
72 todo.extend(ast.iter_child_nodes(node))
72 yield node, newscope
73 yield node, newscope
73
74
74 def dotted_name_of_path(path):
75 def dotted_name_of_path(path):
75 """Given a relative path to a source file, return its dotted module name.
76 """Given a relative path to a source file, return its dotted module name.
76
77
77 >>> dotted_name_of_path('mercurial/error.py')
78 >>> dotted_name_of_path('mercurial/error.py')
78 'mercurial.error'
79 'mercurial.error'
79 >>> dotted_name_of_path('zlibmodule.so')
80 >>> dotted_name_of_path('zlibmodule.so')
80 'zlib'
81 'zlib'
81 """
82 """
82 parts = path.replace(os.sep, '/').split('/')
83 parts = path.replace(os.sep, '/').split('/')
83 parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so
84 parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so
84 if parts[-1].endswith('module'):
85 if parts[-1].endswith('module'):
85 parts[-1] = parts[-1][:-6]
86 parts[-1] = parts[-1][:-6]
86 return '.'.join(parts)
87 return '.'.join(parts)
87
88
88 def fromlocalfunc(modulename, localmods):
89 def fromlocalfunc(modulename, localmods):
89 """Get a function to examine which locally defined module the
90 """Get a function to examine which locally defined module the
90 target source imports via a specified name.
91 target source imports via a specified name.
91
92
92 `modulename` is an `dotted_name_of_path()`-ed source file path,
93 `modulename` is an `dotted_name_of_path()`-ed source file path,
93 which may have `.__init__` at the end of it, of the target source.
94 which may have `.__init__` at the end of it, of the target source.
94
95
95 `localmods` is a set of absolute `dotted_name_of_path()`-ed source file
96 `localmods` is a set of absolute `dotted_name_of_path()`-ed source file
96 paths of locally defined (= Mercurial specific) modules.
97 paths of locally defined (= Mercurial specific) modules.
97
98
98 This function assumes that module names not existing in
99 This function assumes that module names not existing in
99 `localmods` are from the Python standard library.
100 `localmods` are from the Python standard library.
100
101
101 This function returns the function, which takes `name` argument,
102 This function returns the function, which takes `name` argument,
102 and returns `(absname, dottedpath, hassubmod)` tuple if `name`
103 and returns `(absname, dottedpath, hassubmod)` tuple if `name`
103 matches against locally defined module. Otherwise, it returns
104 matches against locally defined module. Otherwise, it returns
104 False.
105 False.
105
106
106 It is assumed that `name` doesn't have `.__init__`.
107 It is assumed that `name` doesn't have `.__init__`.
107
108
108 `absname` is an absolute module name of specified `name`
109 `absname` is an absolute module name of specified `name`
109 (e.g. "hgext.convert"). This can be used to compose prefix for sub
110 (e.g. "hgext.convert"). This can be used to compose prefix for sub
110 modules or so.
111 modules or so.
111
112
112 `dottedpath` is a `dotted_name_of_path()`-ed source file path
113 `dottedpath` is a `dotted_name_of_path()`-ed source file path
113 (e.g. "hgext.convert.__init__") of `name`. This is used to look
114 (e.g. "hgext.convert.__init__") of `name`. This is used to look
114 module up in `localmods` again.
115 module up in `localmods` again.
115
116
116 `hassubmod` is whether it may have sub modules under it (for
117 `hassubmod` is whether it may have sub modules under it (for
117 convenient, even though this is also equivalent to "absname !=
118 convenient, even though this is also equivalent to "absname !=
118 dottednpath")
119 dottednpath")
119
120
120 >>> localmods = {'foo.__init__', 'foo.foo1',
121 >>> localmods = {'foo.__init__', 'foo.foo1',
121 ... 'foo.bar.__init__', 'foo.bar.bar1',
122 ... 'foo.bar.__init__', 'foo.bar.bar1',
122 ... 'baz.__init__', 'baz.baz1'}
123 ... 'baz.__init__', 'baz.baz1'}
123 >>> fromlocal = fromlocalfunc('foo.xxx', localmods)
124 >>> fromlocal = fromlocalfunc('foo.xxx', localmods)
124 >>> # relative
125 >>> # relative
125 >>> fromlocal('foo1')
126 >>> fromlocal('foo1')
126 ('foo.foo1', 'foo.foo1', False)
127 ('foo.foo1', 'foo.foo1', False)
127 >>> fromlocal('bar')
128 >>> fromlocal('bar')
128 ('foo.bar', 'foo.bar.__init__', True)
129 ('foo.bar', 'foo.bar.__init__', True)
129 >>> fromlocal('bar.bar1')
130 >>> fromlocal('bar.bar1')
130 ('foo.bar.bar1', 'foo.bar.bar1', False)
131 ('foo.bar.bar1', 'foo.bar.bar1', False)
131 >>> # absolute
132 >>> # absolute
132 >>> fromlocal('baz')
133 >>> fromlocal('baz')
133 ('baz', 'baz.__init__', True)
134 ('baz', 'baz.__init__', True)
134 >>> fromlocal('baz.baz1')
135 >>> fromlocal('baz.baz1')
135 ('baz.baz1', 'baz.baz1', False)
136 ('baz.baz1', 'baz.baz1', False)
136 >>> # unknown = maybe standard library
137 >>> # unknown = maybe standard library
137 >>> fromlocal('os')
138 >>> fromlocal('os')
138 False
139 False
139 >>> fromlocal(None, 1)
140 >>> fromlocal(None, 1)
140 ('foo', 'foo.__init__', True)
141 ('foo', 'foo.__init__', True)
141 >>> fromlocal('foo1', 1)
142 >>> fromlocal('foo1', 1)
142 ('foo.foo1', 'foo.foo1', False)
143 ('foo.foo1', 'foo.foo1', False)
143 >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods)
144 >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods)
144 >>> fromlocal2(None, 2)
145 >>> fromlocal2(None, 2)
145 ('foo', 'foo.__init__', True)
146 ('foo', 'foo.__init__', True)
146 >>> fromlocal2('bar2', 1)
147 >>> fromlocal2('bar2', 1)
147 False
148 False
148 >>> fromlocal2('bar', 2)
149 >>> fromlocal2('bar', 2)
149 ('foo.bar', 'foo.bar.__init__', True)
150 ('foo.bar', 'foo.bar.__init__', True)
150 """
151 """
151 if not isinstance(modulename, str):
152 if not isinstance(modulename, str):
152 modulename = modulename.decode('ascii')
153 modulename = modulename.decode('ascii')
153 prefix = '.'.join(modulename.split('.')[:-1])
154 prefix = '.'.join(modulename.split('.')[:-1])
154 if prefix:
155 if prefix:
155 prefix += '.'
156 prefix += '.'
156 def fromlocal(name, level=0):
157 def fromlocal(name, level=0):
157 # name is false value when relative imports are used.
158 # name is false value when relative imports are used.
158 if not name:
159 if not name:
159 # If relative imports are used, level must not be absolute.
160 # If relative imports are used, level must not be absolute.
160 assert level > 0
161 assert level > 0
161 candidates = ['.'.join(modulename.split('.')[:-level])]
162 candidates = ['.'.join(modulename.split('.')[:-level])]
162 else:
163 else:
163 if not level:
164 if not level:
164 # Check relative name first.
165 # Check relative name first.
165 candidates = [prefix + name, name]
166 candidates = [prefix + name, name]
166 else:
167 else:
167 candidates = ['.'.join(modulename.split('.')[:-level]) +
168 candidates = ['.'.join(modulename.split('.')[:-level]) +
168 '.' + name]
169 '.' + name]
169
170
170 for n in candidates:
171 for n in candidates:
171 if n in localmods:
172 if n in localmods:
172 return (n, n, False)
173 return (n, n, False)
173 dottedpath = n + '.__init__'
174 dottedpath = n + '.__init__'
174 if dottedpath in localmods:
175 if dottedpath in localmods:
175 return (n, dottedpath, True)
176 return (n, dottedpath, True)
176 return False
177 return False
177 return fromlocal
178 return fromlocal
178
179
179 def populateextmods(localmods):
180 def populateextmods(localmods):
180 """Populate C extension modules based on pure modules"""
181 """Populate C extension modules based on pure modules"""
181 newlocalmods = set(localmods)
182 newlocalmods = set(localmods)
182 for n in localmods:
183 for n in localmods:
183 if n.startswith('mercurial.pure.'):
184 if n.startswith('mercurial.pure.'):
184 m = n[len('mercurial.pure.'):]
185 m = n[len('mercurial.pure.'):]
185 newlocalmods.add('mercurial.cext.' + m)
186 newlocalmods.add('mercurial.cext.' + m)
186 newlocalmods.add('mercurial.cffi._' + m)
187 newlocalmods.add('mercurial.cffi._' + m)
187 return newlocalmods
188 return newlocalmods
188
189
189 def list_stdlib_modules():
190 def list_stdlib_modules():
190 """List the modules present in the stdlib.
191 """List the modules present in the stdlib.
191
192
192 >>> py3 = sys.version_info[0] >= 3
193 >>> py3 = sys.version_info[0] >= 3
193 >>> mods = set(list_stdlib_modules())
194 >>> mods = set(list_stdlib_modules())
194 >>> 'BaseHTTPServer' in mods or py3
195 >>> 'BaseHTTPServer' in mods or py3
195 True
196 True
196
197
197 os.path isn't really a module, so it's missing:
198 os.path isn't really a module, so it's missing:
198
199
199 >>> 'os.path' in mods
200 >>> 'os.path' in mods
200 False
201 False
201
202
202 sys requires special treatment, because it's baked into the
203 sys requires special treatment, because it's baked into the
203 interpreter, but it should still appear:
204 interpreter, but it should still appear:
204
205
205 >>> 'sys' in mods
206 >>> 'sys' in mods
206 True
207 True
207
208
208 >>> 'collections' in mods
209 >>> 'collections' in mods
209 True
210 True
210
211
211 >>> 'cStringIO' in mods or py3
212 >>> 'cStringIO' in mods or py3
212 True
213 True
213
214
214 >>> 'cffi' in mods
215 >>> 'cffi' in mods
215 True
216 True
216 """
217 """
217 for m in sys.builtin_module_names:
218 for m in sys.builtin_module_names:
218 yield m
219 yield m
219 # These modules only exist on windows, but we should always
220 # These modules only exist on windows, but we should always
220 # consider them stdlib.
221 # consider them stdlib.
221 for m in ['msvcrt', '_winreg']:
222 for m in ['msvcrt', '_winreg']:
222 yield m
223 yield m
223 yield '__builtin__'
224 yield '__builtin__'
224 yield 'builtins' # python3 only
225 yield 'builtins' # python3 only
225 yield 'importlib.abc' # python3 only
226 yield 'importlib.abc' # python3 only
226 yield 'importlib.machinery' # python3 only
227 yield 'importlib.machinery' # python3 only
227 yield 'importlib.util' # python3 only
228 yield 'importlib.util' # python3 only
228 for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only
229 for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only
229 yield m
230 yield m
230 for m in 'cPickle', 'datetime': # in Python (not C) on PyPy
231 for m in 'cPickle', 'datetime': # in Python (not C) on PyPy
231 yield m
232 yield m
232 for m in ['cffi']:
233 for m in ['cffi']:
233 yield m
234 yield m
234 stdlib_prefixes = {sys.prefix, sys.exec_prefix}
235 stdlib_prefixes = {sys.prefix, sys.exec_prefix}
235 # We need to supplement the list of prefixes for the search to work
236 # We need to supplement the list of prefixes for the search to work
236 # when run from within a virtualenv.
237 # when run from within a virtualenv.
237 for mod in (basehttpserver, zlib):
238 for mod in (basehttpserver, zlib):
238 if mod is None:
239 if mod is None:
239 continue
240 continue
240 try:
241 try:
241 # Not all module objects have a __file__ attribute.
242 # Not all module objects have a __file__ attribute.
242 filename = mod.__file__
243 filename = mod.__file__
243 except AttributeError:
244 except AttributeError:
244 continue
245 continue
245 dirname = os.path.dirname(filename)
246 dirname = os.path.dirname(filename)
246 for prefix in stdlib_prefixes:
247 for prefix in stdlib_prefixes:
247 if dirname.startswith(prefix):
248 if dirname.startswith(prefix):
248 # Then this directory is redundant.
249 # Then this directory is redundant.
249 break
250 break
250 else:
251 else:
251 stdlib_prefixes.add(dirname)
252 stdlib_prefixes.add(dirname)
252 for libpath in sys.path:
253 for libpath in sys.path:
253 # We want to walk everything in sys.path that starts with
254 # We want to walk everything in sys.path that starts with
254 # something in stdlib_prefixes.
255 # something in stdlib_prefixes.
255 if not any(libpath.startswith(p) for p in stdlib_prefixes):
256 if not any(libpath.startswith(p) for p in stdlib_prefixes):
256 continue
257 continue
257 for top, dirs, files in os.walk(libpath):
258 for top, dirs, files in os.walk(libpath):
258 for i, d in reversed(list(enumerate(dirs))):
259 for i, d in reversed(list(enumerate(dirs))):
259 if (not os.path.exists(os.path.join(top, d, '__init__.py'))
260 if (not os.path.exists(os.path.join(top, d, '__init__.py'))
260 or top == libpath and d in ('hgdemandimport', 'hgext',
261 or top == libpath and d in ('hgdemandimport', 'hgext',
261 'mercurial')):
262 'mercurial')):
262 del dirs[i]
263 del dirs[i]
263 for name in files:
264 for name in files:
264 if not name.endswith(('.py', '.so', '.pyc', '.pyo', '.pyd')):
265 if not name.endswith(('.py', '.so', '.pyc', '.pyo', '.pyd')):
265 continue
266 continue
266 if name.startswith('__init__.py'):
267 if name.startswith('__init__.py'):
267 full_path = top
268 full_path = top
268 else:
269 else:
269 full_path = os.path.join(top, name)
270 full_path = os.path.join(top, name)
270 rel_path = full_path[len(libpath) + 1:]
271 rel_path = full_path[len(libpath) + 1:]
271 mod = dotted_name_of_path(rel_path)
272 mod = dotted_name_of_path(rel_path)
272 yield mod
273 yield mod
273
274
274 stdlib_modules = set(list_stdlib_modules())
275 stdlib_modules = set(list_stdlib_modules())
275
276
276 def imported_modules(source, modulename, f, localmods, ignore_nested=False):
277 def imported_modules(source, modulename, f, localmods, ignore_nested=False):
277 """Given the source of a file as a string, yield the names
278 """Given the source of a file as a string, yield the names
278 imported by that file.
279 imported by that file.
279
280
280 Args:
281 Args:
281 source: The python source to examine as a string.
282 source: The python source to examine as a string.
282 modulename: of specified python source (may have `__init__`)
283 modulename: of specified python source (may have `__init__`)
283 localmods: set of locally defined module names (may have `__init__`)
284 localmods: set of locally defined module names (may have `__init__`)
284 ignore_nested: If true, import statements that do not start in
285 ignore_nested: If true, import statements that do not start in
285 column zero will be ignored.
286 column zero will be ignored.
286
287
287 Returns:
288 Returns:
288 A list of absolute module names imported by the given source.
289 A list of absolute module names imported by the given source.
289
290
290 >>> f = 'foo/xxx.py'
291 >>> f = 'foo/xxx.py'
291 >>> modulename = 'foo.xxx'
292 >>> modulename = 'foo.xxx'
292 >>> localmods = {'foo.__init__': True,
293 >>> localmods = {'foo.__init__': True,
293 ... 'foo.foo1': True, 'foo.foo2': True,
294 ... 'foo.foo1': True, 'foo.foo2': True,
294 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
295 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
295 ... 'baz.__init__': True, 'baz.baz1': True }
296 ... 'baz.__init__': True, 'baz.baz1': True }
296 >>> # standard library (= not locally defined ones)
297 >>> # standard library (= not locally defined ones)
297 >>> sorted(imported_modules(
298 >>> sorted(imported_modules(
298 ... 'from stdlib1 import foo, bar; import stdlib2',
299 ... 'from stdlib1 import foo, bar; import stdlib2',
299 ... modulename, f, localmods))
300 ... modulename, f, localmods))
300 []
301 []
301 >>> # relative importing
302 >>> # relative importing
302 >>> sorted(imported_modules(
303 >>> sorted(imported_modules(
303 ... 'import foo1; from bar import bar1',
304 ... 'import foo1; from bar import bar1',
304 ... modulename, f, localmods))
305 ... modulename, f, localmods))
305 ['foo.bar.bar1', 'foo.foo1']
306 ['foo.bar.bar1', 'foo.foo1']
306 >>> sorted(imported_modules(
307 >>> sorted(imported_modules(
307 ... 'from bar.bar1 import name1, name2, name3',
308 ... 'from bar.bar1 import name1, name2, name3',
308 ... modulename, f, localmods))
309 ... modulename, f, localmods))
309 ['foo.bar.bar1']
310 ['foo.bar.bar1']
310 >>> # absolute importing
311 >>> # absolute importing
311 >>> sorted(imported_modules(
312 >>> sorted(imported_modules(
312 ... 'from baz import baz1, name1',
313 ... 'from baz import baz1, name1',
313 ... modulename, f, localmods))
314 ... modulename, f, localmods))
314 ['baz.__init__', 'baz.baz1']
315 ['baz.__init__', 'baz.baz1']
315 >>> # mixed importing, even though it shouldn't be recommended
316 >>> # mixed importing, even though it shouldn't be recommended
316 >>> sorted(imported_modules(
317 >>> sorted(imported_modules(
317 ... 'import stdlib, foo1, baz',
318 ... 'import stdlib, foo1, baz',
318 ... modulename, f, localmods))
319 ... modulename, f, localmods))
319 ['baz.__init__', 'foo.foo1']
320 ['baz.__init__', 'foo.foo1']
320 >>> # ignore_nested
321 >>> # ignore_nested
321 >>> sorted(imported_modules(
322 >>> sorted(imported_modules(
322 ... '''import foo
323 ... '''import foo
323 ... def wat():
324 ... def wat():
324 ... import bar
325 ... import bar
325 ... ''', modulename, f, localmods))
326 ... ''', modulename, f, localmods))
326 ['foo.__init__', 'foo.bar.__init__']
327 ['foo.__init__', 'foo.bar.__init__']
327 >>> sorted(imported_modules(
328 >>> sorted(imported_modules(
328 ... '''import foo
329 ... '''import foo
329 ... def wat():
330 ... def wat():
330 ... import bar
331 ... import bar
331 ... ''', modulename, f, localmods, ignore_nested=True))
332 ... ''', modulename, f, localmods, ignore_nested=True))
332 ['foo.__init__']
333 ['foo.__init__']
333 """
334 """
334 fromlocal = fromlocalfunc(modulename, localmods)
335 fromlocal = fromlocalfunc(modulename, localmods)
335 for node in ast.walk(ast.parse(source, f)):
336 for node in ast.walk(ast.parse(source, f)):
336 if ignore_nested and getattr(node, 'col_offset', 0) > 0:
337 if ignore_nested and getattr(node, 'col_offset', 0) > 0:
337 continue
338 continue
338 if isinstance(node, ast.Import):
339 if isinstance(node, ast.Import):
339 for n in node.names:
340 for n in node.names:
340 found = fromlocal(n.name)
341 found = fromlocal(n.name)
341 if not found:
342 if not found:
342 # this should import standard library
343 # this should import standard library
343 continue
344 continue
344 yield found[1]
345 yield found[1]
345 elif isinstance(node, ast.ImportFrom):
346 elif isinstance(node, ast.ImportFrom):
346 found = fromlocal(node.module, node.level)
347 found = fromlocal(node.module, node.level)
347 if not found:
348 if not found:
348 # this should import standard library
349 # this should import standard library
349 continue
350 continue
350
351
351 absname, dottedpath, hassubmod = found
352 absname, dottedpath, hassubmod = found
352 if not hassubmod:
353 if not hassubmod:
353 # "dottedpath" is not a package; must be imported
354 # "dottedpath" is not a package; must be imported
354 yield dottedpath
355 yield dottedpath
355 # examination of "node.names" should be redundant
356 # examination of "node.names" should be redundant
356 # e.g.: from mercurial.node import nullid, nullrev
357 # e.g.: from mercurial.node import nullid, nullrev
357 continue
358 continue
358
359
359 modnotfound = False
360 modnotfound = False
360 prefix = absname + '.'
361 prefix = absname + '.'
361 for n in node.names:
362 for n in node.names:
362 found = fromlocal(prefix + n.name)
363 found = fromlocal(prefix + n.name)
363 if not found:
364 if not found:
364 # this should be a function or a property of "node.module"
365 # this should be a function or a property of "node.module"
365 modnotfound = True
366 modnotfound = True
366 continue
367 continue
367 yield found[1]
368 yield found[1]
368 if modnotfound:
369 if modnotfound:
369 # "dottedpath" is a package, but imported because of non-module
370 # "dottedpath" is a package, but imported because of non-module
370 # lookup
371 # lookup
371 yield dottedpath
372 yield dottedpath
372
373
373 def verify_import_convention(module, source, localmods):
374 def verify_import_convention(module, source, localmods):
374 """Verify imports match our established coding convention.
375 """Verify imports match our established coding convention.
375
376
376 We have 2 conventions: legacy and modern. The modern convention is in
377 We have 2 conventions: legacy and modern. The modern convention is in
377 effect when using absolute imports.
378 effect when using absolute imports.
378
379
379 The legacy convention only looks for mixed imports. The modern convention
380 The legacy convention only looks for mixed imports. The modern convention
380 is much more thorough.
381 is much more thorough.
381 """
382 """
382 root = ast.parse(source)
383 root = ast.parse(source)
383 absolute = usingabsolute(root)
384 absolute = usingabsolute(root)
384
385
385 if absolute:
386 if absolute:
386 return verify_modern_convention(module, root, localmods)
387 return verify_modern_convention(module, root, localmods)
387 else:
388 else:
388 return verify_stdlib_on_own_line(root)
389 return verify_stdlib_on_own_line(root)
389
390
390 def verify_modern_convention(module, root, localmods, root_col_offset=0):
391 def verify_modern_convention(module, root, localmods, root_col_offset=0):
391 """Verify a file conforms to the modern import convention rules.
392 """Verify a file conforms to the modern import convention rules.
392
393
393 The rules of the modern convention are:
394 The rules of the modern convention are:
394
395
395 * Ordering is stdlib followed by local imports. Each group is lexically
396 * Ordering is stdlib followed by local imports. Each group is lexically
396 sorted.
397 sorted.
397 * Importing multiple modules via "import X, Y" is not allowed: use
398 * Importing multiple modules via "import X, Y" is not allowed: use
398 separate import statements.
399 separate import statements.
399 * Importing multiple modules via "from X import ..." is allowed if using
400 * Importing multiple modules via "from X import ..." is allowed if using
400 parenthesis and one entry per line.
401 parenthesis and one entry per line.
401 * Only 1 relative import statement per import level ("from .", "from ..")
402 * Only 1 relative import statement per import level ("from .", "from ..")
402 is allowed.
403 is allowed.
403 * Relative imports from higher levels must occur before lower levels. e.g.
404 * Relative imports from higher levels must occur before lower levels. e.g.
404 "from .." must be before "from .".
405 "from .." must be before "from .".
405 * Imports from peer packages should use relative import (e.g. do not
406 * Imports from peer packages should use relative import (e.g. do not
406 "import mercurial.foo" from a "mercurial.*" module).
407 "import mercurial.foo" from a "mercurial.*" module).
407 * Symbols can only be imported from specific modules (see
408 * Symbols can only be imported from specific modules (see
408 `allowsymbolimports`). For other modules, first import the module then
409 `allowsymbolimports`). For other modules, first import the module then
409 assign the symbol to a module-level variable. In addition, these imports
410 assign the symbol to a module-level variable. In addition, these imports
410 must be performed before other local imports. This rule only
411 must be performed before other local imports. This rule only
411 applies to import statements outside of any blocks.
412 applies to import statements outside of any blocks.
412 * Relative imports from the standard library are not allowed.
413 * Relative imports from the standard library are not allowed.
413 * Certain modules must be aliased to alternate names to avoid aliasing
414 * Certain modules must be aliased to alternate names to avoid aliasing
414 and readability problems. See `requirealias`.
415 and readability problems. See `requirealias`.
415 """
416 """
416 if not isinstance(module, str):
417 if not isinstance(module, str):
417 module = module.decode('ascii')
418 module = module.decode('ascii')
418 topmodule = module.split('.')[0]
419 topmodule = module.split('.')[0]
419 fromlocal = fromlocalfunc(module, localmods)
420 fromlocal = fromlocalfunc(module, localmods)
420
421
421 # Whether a local/non-stdlib import has been performed.
422 # Whether a local/non-stdlib import has been performed.
422 seenlocal = None
423 seenlocal = None
423 # Whether a local/non-stdlib, non-symbol import has been seen.
424 # Whether a local/non-stdlib, non-symbol import has been seen.
424 seennonsymbollocal = False
425 seennonsymbollocal = False
425 # The last name to be imported (for sorting).
426 # The last name to be imported (for sorting).
426 lastname = None
427 lastname = None
427 laststdlib = None
428 laststdlib = None
428 # Relative import levels encountered so far.
429 # Relative import levels encountered so far.
429 seenlevels = set()
430 seenlevels = set()
430
431
431 for node, newscope in walklocal(root):
432 for node, newscope in walklocal(root):
432 def msg(fmt, *args):
433 def msg(fmt, *args):
433 return (fmt % args, node.lineno)
434 return (fmt % args, node.lineno)
434 if newscope:
435 if newscope:
435 # Check for local imports in function
436 # Check for local imports in function
436 for r in verify_modern_convention(module, node, localmods,
437 for r in verify_modern_convention(module, node, localmods,
437 node.col_offset + 4):
438 node.col_offset + 4):
438 yield r
439 yield r
439 elif isinstance(node, ast.Import):
440 elif isinstance(node, ast.Import):
440 # Disallow "import foo, bar" and require separate imports
441 # Disallow "import foo, bar" and require separate imports
441 # for each module.
442 # for each module.
442 if len(node.names) > 1:
443 if len(node.names) > 1:
443 yield msg('multiple imported names: %s',
444 yield msg('multiple imported names: %s',
444 ', '.join(n.name for n in node.names))
445 ', '.join(n.name for n in node.names))
445
446
446 name = node.names[0].name
447 name = node.names[0].name
447 asname = node.names[0].asname
448 asname = node.names[0].asname
448
449
449 stdlib = name in stdlib_modules
450 stdlib = name in stdlib_modules
450
451
451 # Ignore sorting rules on imports inside blocks.
452 # Ignore sorting rules on imports inside blocks.
452 if node.col_offset == root_col_offset:
453 if node.col_offset == root_col_offset:
453 if lastname and name < lastname and laststdlib == stdlib:
454 if lastname and name < lastname and laststdlib == stdlib:
454 yield msg('imports not lexically sorted: %s < %s',
455 yield msg('imports not lexically sorted: %s < %s',
455 name, lastname)
456 name, lastname)
456
457
457 lastname = name
458 lastname = name
458 laststdlib = stdlib
459 laststdlib = stdlib
459
460
460 # stdlib imports should be before local imports.
461 # stdlib imports should be before local imports.
461 if stdlib and seenlocal and node.col_offset == root_col_offset:
462 if stdlib and seenlocal and node.col_offset == root_col_offset:
462 yield msg('stdlib import "%s" follows local import: %s',
463 yield msg('stdlib import "%s" follows local import: %s',
463 name, seenlocal)
464 name, seenlocal)
464
465
465 if not stdlib:
466 if not stdlib:
466 seenlocal = name
467 seenlocal = name
467
468
468 # Import of sibling modules should use relative imports.
469 # Import of sibling modules should use relative imports.
469 topname = name.split('.')[0]
470 topname = name.split('.')[0]
470 if topname == topmodule:
471 if topname == topmodule:
471 yield msg('import should be relative: %s', name)
472 yield msg('import should be relative: %s', name)
472
473
473 if name in requirealias and asname != requirealias[name]:
474 if name in requirealias and asname != requirealias[name]:
474 yield msg('%s module must be "as" aliased to %s',
475 yield msg('%s module must be "as" aliased to %s',
475 name, requirealias[name])
476 name, requirealias[name])
476
477
477 elif isinstance(node, ast.ImportFrom):
478 elif isinstance(node, ast.ImportFrom):
478 # Resolve the full imported module name.
479 # Resolve the full imported module name.
479 if node.level > 0:
480 if node.level > 0:
480 fullname = '.'.join(module.split('.')[:-node.level])
481 fullname = '.'.join(module.split('.')[:-node.level])
481 if node.module:
482 if node.module:
482 fullname += '.%s' % node.module
483 fullname += '.%s' % node.module
483 else:
484 else:
484 assert node.module
485 assert node.module
485 fullname = node.module
486 fullname = node.module
486
487
487 topname = fullname.split('.')[0]
488 topname = fullname.split('.')[0]
488 if topname == topmodule:
489 if topname == topmodule:
489 yield msg('import should be relative: %s', fullname)
490 yield msg('import should be relative: %s', fullname)
490
491
491 # __future__ is special since it needs to come first and use
492 # __future__ is special since it needs to come first and use
492 # symbol import.
493 # symbol import.
493 if fullname != '__future__':
494 if fullname != '__future__':
494 if not fullname or fullname in stdlib_modules:
495 if not fullname or fullname in stdlib_modules:
495 yield msg('relative import of stdlib module')
496 yield msg('relative import of stdlib module')
496 else:
497 else:
497 seenlocal = fullname
498 seenlocal = fullname
498
499
499 # Direct symbol import is only allowed from certain modules and
500 # Direct symbol import is only allowed from certain modules and
500 # must occur before non-symbol imports.
501 # must occur before non-symbol imports.
501 found = fromlocal(node.module, node.level)
502 found = fromlocal(node.module, node.level)
502 if found and found[2]: # node.module is a package
503 if found and found[2]: # node.module is a package
503 prefix = found[0] + '.'
504 prefix = found[0] + '.'
504 symbols = (n.name for n in node.names
505 symbols = (n.name for n in node.names
505 if not fromlocal(prefix + n.name))
506 if not fromlocal(prefix + n.name))
506 else:
507 else:
507 symbols = (n.name for n in node.names)
508 symbols = (n.name for n in node.names)
508 symbols = [sym for sym in symbols if sym not in directsymbols]
509 symbols = [sym for sym in symbols if sym not in directsymbols]
509 if node.module and node.col_offset == root_col_offset:
510 if node.module and node.col_offset == root_col_offset:
510 if symbols and fullname not in allowsymbolimports:
511 if symbols and fullname not in allowsymbolimports:
511 yield msg('direct symbol import %s from %s',
512 yield msg('direct symbol import %s from %s',
512 ', '.join(symbols), fullname)
513 ', '.join(symbols), fullname)
513
514
514 if symbols and seennonsymbollocal:
515 if symbols and seennonsymbollocal:
515 yield msg('symbol import follows non-symbol import: %s',
516 yield msg('symbol import follows non-symbol import: %s',
516 fullname)
517 fullname)
517 if not symbols and fullname not in stdlib_modules:
518 if not symbols and fullname not in stdlib_modules:
518 seennonsymbollocal = True
519 seennonsymbollocal = True
519
520
520 if not node.module:
521 if not node.module:
521 assert node.level
522 assert node.level
522
523
523 # Only allow 1 group per level.
524 # Only allow 1 group per level.
524 if (node.level in seenlevels
525 if (node.level in seenlevels
525 and node.col_offset == root_col_offset):
526 and node.col_offset == root_col_offset):
526 yield msg('multiple "from %s import" statements',
527 yield msg('multiple "from %s import" statements',
527 '.' * node.level)
528 '.' * node.level)
528
529
529 # Higher-level groups come before lower-level groups.
530 # Higher-level groups come before lower-level groups.
530 if any(node.level > l for l in seenlevels):
531 if any(node.level > l for l in seenlevels):
531 yield msg('higher-level import should come first: %s',
532 yield msg('higher-level import should come first: %s',
532 fullname)
533 fullname)
533
534
534 seenlevels.add(node.level)
535 seenlevels.add(node.level)
535
536
536 # Entries in "from .X import ( ... )" lists must be lexically
537 # Entries in "from .X import ( ... )" lists must be lexically
537 # sorted.
538 # sorted.
538 lastentryname = None
539 lastentryname = None
539
540
540 for n in node.names:
541 for n in node.names:
541 if lastentryname and n.name < lastentryname:
542 if lastentryname and n.name < lastentryname:
542 yield msg('imports from %s not lexically sorted: %s < %s',
543 yield msg('imports from %s not lexically sorted: %s < %s',
543 fullname, n.name, lastentryname)
544 fullname, n.name, lastentryname)
544
545
545 lastentryname = n.name
546 lastentryname = n.name
546
547
547 if n.name in requirealias and n.asname != requirealias[n.name]:
548 if n.name in requirealias and n.asname != requirealias[n.name]:
548 yield msg('%s from %s must be "as" aliased to %s',
549 yield msg('%s from %s must be "as" aliased to %s',
549 n.name, fullname, requirealias[n.name])
550 n.name, fullname, requirealias[n.name])
550
551
551 def verify_stdlib_on_own_line(root):
552 def verify_stdlib_on_own_line(root):
552 """Given some python source, verify that stdlib imports are done
553 """Given some python source, verify that stdlib imports are done
553 in separate statements from relative local module imports.
554 in separate statements from relative local module imports.
554
555
555 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, foo')))
556 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, foo')))
556 [('mixed imports\\n stdlib: sys\\n relative: foo', 1)]
557 [('mixed imports\\n stdlib: sys\\n relative: foo', 1)]
557 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, os')))
558 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, os')))
558 []
559 []
559 >>> list(verify_stdlib_on_own_line(ast.parse('import foo, bar')))
560 >>> list(verify_stdlib_on_own_line(ast.parse('import foo, bar')))
560 []
561 []
561 """
562 """
562 for node in ast.walk(root):
563 for node in ast.walk(root):
563 if isinstance(node, ast.Import):
564 if isinstance(node, ast.Import):
564 from_stdlib = {False: [], True: []}
565 from_stdlib = {False: [], True: []}
565 for n in node.names:
566 for n in node.names:
566 from_stdlib[n.name in stdlib_modules].append(n.name)
567 from_stdlib[n.name in stdlib_modules].append(n.name)
567 if from_stdlib[True] and from_stdlib[False]:
568 if from_stdlib[True] and from_stdlib[False]:
568 yield ('mixed imports\n stdlib: %s\n relative: %s' %
569 yield ('mixed imports\n stdlib: %s\n relative: %s' %
569 (', '.join(sorted(from_stdlib[True])),
570 (', '.join(sorted(from_stdlib[True])),
570 ', '.join(sorted(from_stdlib[False]))), node.lineno)
571 ', '.join(sorted(from_stdlib[False]))), node.lineno)
571
572
572 class CircularImport(Exception):
573 class CircularImport(Exception):
573 pass
574 pass
574
575
575 def checkmod(mod, imports):
576 def checkmod(mod, imports):
576 shortest = {}
577 shortest = {}
577 visit = [[mod]]
578 visit = [[mod]]
578 while visit:
579 while visit:
579 path = visit.pop(0)
580 path = visit.pop(0)
580 for i in sorted(imports.get(path[-1], [])):
581 for i in sorted(imports.get(path[-1], [])):
581 if len(path) < shortest.get(i, 1000):
582 if len(path) < shortest.get(i, 1000):
582 shortest[i] = len(path)
583 shortest[i] = len(path)
583 if i in path:
584 if i in path:
584 if i == path[0]:
585 if i == path[0]:
585 raise CircularImport(path)
586 raise CircularImport(path)
586 continue
587 continue
587 visit.append(path + [i])
588 visit.append(path + [i])
588
589
589 def rotatecycle(cycle):
590 def rotatecycle(cycle):
590 """arrange a cycle so that the lexicographically first module listed first
591 """arrange a cycle so that the lexicographically first module listed first
591
592
592 >>> rotatecycle(['foo', 'bar'])
593 >>> rotatecycle(['foo', 'bar'])
593 ['bar', 'foo', 'bar']
594 ['bar', 'foo', 'bar']
594 """
595 """
595 lowest = min(cycle)
596 lowest = min(cycle)
596 idx = cycle.index(lowest)
597 idx = cycle.index(lowest)
597 return cycle[idx:] + cycle[:idx] + [lowest]
598 return cycle[idx:] + cycle[:idx] + [lowest]
598
599
599 def find_cycles(imports):
600 def find_cycles(imports):
600 """Find cycles in an already-loaded import graph.
601 """Find cycles in an already-loaded import graph.
601
602
602 All module names recorded in `imports` should be absolute one.
603 All module names recorded in `imports` should be absolute one.
603
604
604 >>> from __future__ import print_function
605 >>> from __future__ import print_function
605 >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'],
606 >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'],
606 ... 'top.bar': ['top.baz', 'sys'],
607 ... 'top.bar': ['top.baz', 'sys'],
607 ... 'top.baz': ['top.foo'],
608 ... 'top.baz': ['top.foo'],
608 ... 'top.qux': ['top.foo']}
609 ... 'top.qux': ['top.foo']}
609 >>> print('\\n'.join(sorted(find_cycles(imports))))
610 >>> print('\\n'.join(sorted(find_cycles(imports))))
610 top.bar -> top.baz -> top.foo -> top.bar
611 top.bar -> top.baz -> top.foo -> top.bar
611 top.foo -> top.qux -> top.foo
612 top.foo -> top.qux -> top.foo
612 """
613 """
613 cycles = set()
614 cycles = set()
614 for mod in sorted(imports.keys()):
615 for mod in sorted(imports.keys()):
615 try:
616 try:
616 checkmod(mod, imports)
617 checkmod(mod, imports)
617 except CircularImport as e:
618 except CircularImport as e:
618 cycle = e.args[0]
619 cycle = e.args[0]
619 cycles.add(" -> ".join(rotatecycle(cycle)))
620 cycles.add(" -> ".join(rotatecycle(cycle)))
620 return cycles
621 return cycles
621
622
622 def _cycle_sortkey(c):
623 def _cycle_sortkey(c):
623 return len(c), c
624 return len(c), c
624
625
625 def embedded(f, modname, src):
626 def embedded(f, modname, src):
626 """Extract embedded python code
627 """Extract embedded python code
627
628
628 >>> def _forcestr(thing):
629 >>> def _forcestr(thing):
629 ... if not isinstance(thing, str):
630 ... if not isinstance(thing, str):
630 ... return thing.decode('ascii')
631 ... return thing.decode('ascii')
631 ... return thing
632 ... return thing
632 >>> def test(fn, lines):
633 >>> def test(fn, lines):
633 ... for s, m, f, l in embedded(fn, b"example", lines):
634 ... for s, m, f, l in embedded(fn, b"example", lines):
634 ... print("%s %s %d" % (_forcestr(m), _forcestr(f), l))
635 ... print("%s %s %d" % (_forcestr(m), _forcestr(f), l))
635 ... print(repr(_forcestr(s)))
636 ... print(repr(_forcestr(s)))
636 >>> lines = [
637 >>> lines = [
637 ... b'comment',
638 ... b'comment',
638 ... b' >>> from __future__ import print_function',
639 ... b' >>> from __future__ import print_function',
639 ... b" >>> ' multiline",
640 ... b" >>> ' multiline",
640 ... b" ... string'",
641 ... b" ... string'",
641 ... b' ',
642 ... b' ',
642 ... b'comment',
643 ... b'comment',
643 ... b' $ cat > foo.py <<EOF',
644 ... b' $ cat > foo.py <<EOF',
644 ... b' > from __future__ import print_function',
645 ... b' > from __future__ import print_function',
645 ... b' > EOF',
646 ... b' > EOF',
646 ... ]
647 ... ]
647 >>> test(b"example.t", lines)
648 >>> test(b"example.t", lines)
648 example[2] doctest.py 2
649 example[2] doctest.py 2
649 "from __future__ import print_function\\n' multiline\\nstring'\\n"
650 "from __future__ import print_function\\n' multiline\\nstring'\\n"
650 example[7] foo.py 7
651 example[7] foo.py 7
651 'from __future__ import print_function\\n'
652 'from __future__ import print_function\\n'
652 """
653 """
653 inlinepython = 0
654 inlinepython = 0
654 shpython = 0
655 shpython = 0
655 script = []
656 script = []
656 prefix = 6
657 prefix = 6
657 t = ''
658 t = ''
658 n = 0
659 n = 0
659 for l in src:
660 for l in src:
660 n += 1
661 n += 1
661 if not l.endswith(b'\n'):
662 if not l.endswith(b'\n'):
662 l += b'\n'
663 l += b'\n'
663 if l.startswith(b' >>> '): # python inlines
664 if l.startswith(b' >>> '): # python inlines
664 if shpython:
665 if shpython:
665 print("%s:%d: Parse Error" % (f, n))
666 print("%s:%d: Parse Error" % (f, n))
666 if not inlinepython:
667 if not inlinepython:
667 # We've just entered a Python block.
668 # We've just entered a Python block.
668 inlinepython = n
669 inlinepython = n
669 t = b'doctest.py'
670 t = b'doctest.py'
670 script.append(l[prefix:])
671 script.append(l[prefix:])
671 continue
672 continue
672 if l.startswith(b' ... '): # python inlines
673 if l.startswith(b' ... '): # python inlines
673 script.append(l[prefix:])
674 script.append(l[prefix:])
674 continue
675 continue
675 cat = re.search(br"\$ \s*cat\s*>\s*(\S+\.py)\s*<<\s*EOF", l)
676 cat = re.search(br"\$ \s*cat\s*>\s*(\S+\.py)\s*<<\s*EOF", l)
676 if cat:
677 if cat:
677 if inlinepython:
678 if inlinepython:
678 yield b''.join(script), (b"%s[%d]" %
679 yield b''.join(script), (b"%s[%d]" %
679 (modname, inlinepython)), t, inlinepython
680 (modname, inlinepython)), t, inlinepython
680 script = []
681 script = []
681 inlinepython = 0
682 inlinepython = 0
682 shpython = n
683 shpython = n
683 t = cat.group(1)
684 t = cat.group(1)
684 continue
685 continue
685 if shpython and l.startswith(b' > '): # sh continuation
686 if shpython and l.startswith(b' > '): # sh continuation
686 if l == b' > EOF\n':
687 if l == b' > EOF\n':
687 yield b''.join(script), (b"%s[%d]" %
688 yield b''.join(script), (b"%s[%d]" %
688 (modname, shpython)), t, shpython
689 (modname, shpython)), t, shpython
689 script = []
690 script = []
690 shpython = 0
691 shpython = 0
691 else:
692 else:
692 script.append(l[4:])
693 script.append(l[4:])
693 continue
694 continue
694 if inlinepython and l == b' \n':
695 if inlinepython and l == b' \n':
695 yield b''.join(script), (b"%s[%d]" %
696 yield b''.join(script), (b"%s[%d]" %
696 (modname, inlinepython)), t, inlinepython
697 (modname, inlinepython)), t, inlinepython
697 script = []
698 script = []
698 inlinepython = 0
699 inlinepython = 0
699 continue
700 continue
700
701
701 def sources(f, modname):
702 def sources(f, modname):
702 """Yields possibly multiple sources from a filepath
703 """Yields possibly multiple sources from a filepath
703
704
704 input: filepath, modulename
705 input: filepath, modulename
705 yields: script(string), modulename, filepath, linenumber
706 yields: script(string), modulename, filepath, linenumber
706
707
707 For embedded scripts, the modulename and filepath will be different
708 For embedded scripts, the modulename and filepath will be different
708 from the function arguments. linenumber is an offset relative to
709 from the function arguments. linenumber is an offset relative to
709 the input file.
710 the input file.
710 """
711 """
711 py = False
712 py = False
712 if not f.endswith('.t'):
713 if not f.endswith('.t'):
713 with open(f, 'rb') as src:
714 with open(f, 'rb') as src:
714 yield src.read(), modname, f, 0
715 yield src.read(), modname, f, 0
715 py = True
716 py = True
716 if py or f.endswith('.t'):
717 if py or f.endswith('.t'):
717 with open(f, 'rb') as src:
718 with open(f, 'rb') as src:
718 for script, modname, t, line in embedded(f, modname, src):
719 for script, modname, t, line in embedded(f, modname, src):
719 yield script, modname, t, line
720 yield script, modname, t, line
720
721
721 def main(argv):
722 def main(argv):
722 if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
723 if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
723 print('Usage: %s {-|file [file] [file] ...}')
724 print('Usage: %s {-|file [file] [file] ...}')
724 return 1
725 return 1
725 if argv[1] == '-':
726 if argv[1] == '-':
726 argv = argv[:1]
727 argv = argv[:1]
727 argv.extend(l.rstrip() for l in sys.stdin.readlines())
728 argv.extend(l.rstrip() for l in sys.stdin.readlines())
728 localmodpaths = {}
729 localmodpaths = {}
729 used_imports = {}
730 used_imports = {}
730 any_errors = False
731 any_errors = False
731 for source_path in argv[1:]:
732 for source_path in argv[1:]:
732 modname = dotted_name_of_path(source_path)
733 modname = dotted_name_of_path(source_path)
733 localmodpaths[modname] = source_path
734 localmodpaths[modname] = source_path
734 localmods = populateextmods(localmodpaths)
735 localmods = populateextmods(localmodpaths)
735 for localmodname, source_path in sorted(localmodpaths.items()):
736 for localmodname, source_path in sorted(localmodpaths.items()):
736 if not isinstance(localmodname, bytes):
737 if not isinstance(localmodname, bytes):
737 # This is only safe because all hg's files are ascii
738 # This is only safe because all hg's files are ascii
738 localmodname = localmodname.encode('ascii')
739 localmodname = localmodname.encode('ascii')
739 for src, modname, name, line in sources(source_path, localmodname):
740 for src, modname, name, line in sources(source_path, localmodname):
740 try:
741 try:
741 used_imports[modname] = sorted(
742 used_imports[modname] = sorted(
742 imported_modules(src, modname, name, localmods,
743 imported_modules(src, modname, name, localmods,
743 ignore_nested=True))
744 ignore_nested=True))
744 for error, lineno in verify_import_convention(modname, src,
745 for error, lineno in verify_import_convention(modname, src,
745 localmods):
746 localmods):
746 any_errors = True
747 any_errors = True
747 print('%s:%d: %s' % (source_path, lineno + line, error))
748 print('%s:%d: %s' % (source_path, lineno + line, error))
748 except SyntaxError as e:
749 except SyntaxError as e:
749 print('%s:%d: SyntaxError: %s' %
750 print('%s:%d: SyntaxError: %s' %
750 (source_path, e.lineno + line, e))
751 (source_path, e.lineno + line, e))
751 cycles = find_cycles(used_imports)
752 cycles = find_cycles(used_imports)
752 if cycles:
753 if cycles:
753 firstmods = set()
754 firstmods = set()
754 for c in sorted(cycles, key=_cycle_sortkey):
755 for c in sorted(cycles, key=_cycle_sortkey):
755 first = c.split()[0]
756 first = c.split()[0]
756 # As a rough cut, ignore any cycle that starts with the
757 # As a rough cut, ignore any cycle that starts with the
757 # same module as some other cycle. Otherwise we see lots
758 # same module as some other cycle. Otherwise we see lots
758 # of cycles that are effectively duplicates.
759 # of cycles that are effectively duplicates.
759 if first in firstmods:
760 if first in firstmods:
760 continue
761 continue
761 print('Import cycle:', c)
762 print('Import cycle:', c)
762 firstmods.add(first)
763 firstmods.add(first)
763 any_errors = True
764 any_errors = True
764 return any_errors != 0
765 return any_errors != 0
765
766
766 if __name__ == '__main__':
767 if __name__ == '__main__':
767 sys.exit(int(main(sys.argv)))
768 sys.exit(int(main(sys.argv)))
General Comments 0
You need to be logged in to leave comments. Login now