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