Show More
@@ -18,6 +18,7 b' import io' | |||
|
18 | 18 | import os |
|
19 | 19 | import re |
|
20 | 20 | import sys |
|
21 | import ast | |
|
21 | 22 | from itertools import chain |
|
22 | 23 | |
|
23 | 24 | # Our own packages |
@@ -47,7 +48,6 b' range_re = re.compile(r"""' | |||
|
47 | 48 | (?P<end>\d+)?)? |
|
48 | 49 | $""", re.VERBOSE) |
|
49 | 50 | |
|
50 | ||
|
51 | 51 | def extract_code_ranges(ranges_str): |
|
52 | 52 | """Turn a string of range for %%load into 2-tuples of (start, stop) |
|
53 | 53 | ready to use as a slice of the content splitted by lines. |
@@ -76,6 +76,40 b' def extract_code_ranges(ranges_str):' | |||
|
76 | 76 | start = int(start) - 1 |
|
77 | 77 | yield (start, end) |
|
78 | 78 | |
|
79 | def extract_symbols(code, symbols): | |
|
80 | """ | |
|
81 | Return a list of code fragments for each symbol parsed from code | |
|
82 | For example, suppose code is:: | |
|
83 | ||
|
84 | a = 10 | |
|
85 | ||
|
86 | def b(): return 42 | |
|
87 | ||
|
88 | class A: pass | |
|
89 | ||
|
90 | >>> extract_symbols(code, 'A,b'): | |
|
91 | class A: pass | |
|
92 | ||
|
93 | def b(): return 42 | |
|
94 | """ | |
|
95 | try: | |
|
96 | py_code = ast.parse(code) | |
|
97 | except SyntaxError: | |
|
98 | return [] | |
|
99 | marks = [(getattr(s, 'name', None), s.lineno) for s in py_code.body] | |
|
100 | end = None | |
|
101 | symbols_lines = {} | |
|
102 | for name, start in reversed(marks): | |
|
103 | if name: | |
|
104 | symbols_lines[name] = (start - 1, end) | |
|
105 | end = start - 1 | |
|
106 | blocks = [] | |
|
107 | code = code.split('\n') | |
|
108 | for symbol in symbols.split(','): | |
|
109 | if symbol in symbols_lines: | |
|
110 | blocks.append('\n'.join(code[slice(*symbols_lines[symbol])])) | |
|
111 | return blocks | |
|
112 | ||
|
79 | 113 | |
|
80 | 114 | class InteractivelyDefined(Exception): |
|
81 | 115 | """Exception for interactively defined variable in magic_edit""" |
@@ -217,6 +251,8 b' class CodeMagics(Magics):' | |||
|
217 | 251 | (x..(y-1)). Both limits x and y can be left blank (meaning the |
|
218 | 252 | beginning and end of the file, respectively). |
|
219 | 253 | |
|
254 | -s <symbols>: Specify function or classes to load from python source. Could be | |
|
255 | ||
|
220 | 256 | -y : Don't ask confirmation for loading source above 200 000 characters. |
|
221 | 257 | |
|
222 | 258 | This magic command can either take a local filename, a URL, an history |
@@ -230,8 +266,9 b' class CodeMagics(Magics):' | |||
|
230 | 266 | %load http://www.example.com/myscript.py |
|
231 | 267 | %load -r 5-10 myscript.py |
|
232 | 268 | %load -r 10-20,30,40: foo.py |
|
269 | %load -s MyClass,wonder_function myscript.py | |
|
233 | 270 | """ |
|
234 | opts,args = self.parse_options(arg_s,'yr:') | |
|
271 | opts,args = self.parse_options(arg_s,'ys:r:') | |
|
235 | 272 | |
|
236 | 273 | if not args: |
|
237 | 274 | raise UsageError('Missing filename, URL, input history range, ' |
@@ -239,6 +276,9 b' class CodeMagics(Magics):' | |||
|
239 | 276 | |
|
240 | 277 | contents = self.shell.find_user_code(args) |
|
241 | 278 | |
|
279 | if 's' in opts: | |
|
280 | contents = '\n'.join(extract_symbols(contents, opts['s'])) | |
|
281 | ||
|
242 | 282 | if 'r' in opts: |
|
243 | 283 | ranges = opts['r'].replace(',', ' ') |
|
244 | 284 | lines = contents.split('\n') |
@@ -248,7 +288,6 b' class CodeMagics(Magics):' | |||
|
248 | 288 | |
|
249 | 289 | l = len(contents) |
|
250 | 290 | |
|
251 | ||
|
252 | 291 | # 200 000 is ~ 2500 full 80 caracter lines |
|
253 | 292 | # so in average, more than 5000 lines |
|
254 | 293 | if l > 200000 and 'y' not in opts: |
@@ -60,6 +60,28 b' def test_extract_code_ranges():' | |||
|
60 | 60 | actual = list(code.extract_code_ranges(instr)) |
|
61 | 61 | nt.assert_equal(actual, expected) |
|
62 | 62 | |
|
63 | ||
|
64 | def test_extract_symbols(): | |
|
65 | source = """import foo\na = 10\ndef b():\n return 42\nclass A: pass\n""" | |
|
66 | symbols_args = ["a", "b", "A", "A,b", "A,a", "z"] | |
|
67 | expected = [[], | |
|
68 | ["def b():\n return 42"], | |
|
69 | ["class A: pass\n"], | |
|
70 | ["class A: pass\n", "def b():\n return 42"], | |
|
71 | ["class A: pass\n"], | |
|
72 | []] | |
|
73 | for symbols, exp in zip(symbols_args, expected): | |
|
74 | nt.assert_equal(code.extract_symbols(source, symbols), exp) | |
|
75 | ||
|
76 | ||
|
77 | def test_extract_symbols_ignores_non_python_code(): | |
|
78 | source = ("=begin A Ruby program :)=end\n" | |
|
79 | "def hello\n" | |
|
80 | "puts 'Hello world'\n" | |
|
81 | "end") | |
|
82 | nt.assert_equal(code.extract_symbols(source, "hello"), []) | |
|
83 | ||
|
84 | ||
|
63 | 85 | def test_rehashx(): |
|
64 | 86 | # clear up everything |
|
65 | 87 | _ip = get_ipython() |
General Comments 0
You need to be logged in to leave comments.
Login now