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