##// END OF EJS Templates
Add linebreak to docstring to fix markup and silence Sphinx waraning....
Doug Latornell -
Show More
@@ -1,589 +1,590 b''
1 1 """Patched version of standard library tokenize, to deal with various bugs.
2 2
3 3 Based on Python 3.2 code.
4 4
5 5 Patches:
6 6
7 7 - Gareth Rees' patch for Python issue #12691 (untokenizing)
8 8 - Except we don't encode the output of untokenize
9 9 - Python 2 compatible syntax, so that it can be byte-compiled at installation
10 10 - Newlines in comments and blank lines should be either NL or NEWLINE, depending
11 11 on whether they are in a multi-line statement. Filed as Python issue #17061.
12 12 - Export generate_tokens & TokenError
13 13 - u and rb literals are allowed under Python 3.3 and above.
14 14
15 15 ------------------------------------------------------------------------------
16
16 17 Tokenization help for Python programs.
17 18
18 19 tokenize(readline) is a generator that breaks a stream of bytes into
19 20 Python tokens. It decodes the bytes according to PEP-0263 for
20 21 determining source file encoding.
21 22
22 23 It accepts a readline-like method which is called repeatedly to get the
23 24 next line of input (or b"" for EOF). It generates 5-tuples with these
24 25 members:
25 26
26 27 the token type (see token.py)
27 28 the token (a string)
28 29 the starting (row, column) indices of the token (a 2-tuple of ints)
29 30 the ending (row, column) indices of the token (a 2-tuple of ints)
30 31 the original line (string)
31 32
32 33 It is designed to match the working of the Python tokenizer exactly, except
33 34 that it produces COMMENT tokens for comments and gives type OP for all
34 35 operators. Additionally, all token lists start with an ENCODING token
35 36 which tells you which encoding was used to decode the bytes stream.
36 37 """
37 38
38 39 __author__ = 'Ka-Ping Yee <ping@lfw.org>'
39 40 __credits__ = ('GvR, ESR, Tim Peters, Thomas Wouters, Fred Drake, '
40 41 'Skip Montanaro, Raymond Hettinger, Trent Nelson, '
41 42 'Michael Foord')
42 43 import builtins
43 44 import re
44 45 import sys
45 46 from token import *
46 47 from codecs import lookup, BOM_UTF8
47 48 import collections
48 49 from io import TextIOWrapper
49 50 cookie_re = re.compile("coding[:=]\s*([-\w.]+)")
50 51
51 52 import token
52 53 __all__ = token.__all__ + ["COMMENT", "tokenize", "detect_encoding",
53 54 "NL", "untokenize", "ENCODING", "TokenInfo"]
54 55 del token
55 56
56 57 __all__ += ["generate_tokens", "TokenError"]
57 58
58 59 COMMENT = N_TOKENS
59 60 tok_name[COMMENT] = 'COMMENT'
60 61 NL = N_TOKENS + 1
61 62 tok_name[NL] = 'NL'
62 63 ENCODING = N_TOKENS + 2
63 64 tok_name[ENCODING] = 'ENCODING'
64 65 N_TOKENS += 3
65 66
66 67 class TokenInfo(collections.namedtuple('TokenInfo', 'type string start end line')):
67 68 def __repr__(self):
68 69 annotated_type = '%d (%s)' % (self.type, tok_name[self.type])
69 70 return ('TokenInfo(type=%s, string=%r, start=%r, end=%r, line=%r)' %
70 71 self._replace(type=annotated_type))
71 72
72 73 def group(*choices): return '(' + '|'.join(choices) + ')'
73 74 def any(*choices): return group(*choices) + '*'
74 75 def maybe(*choices): return group(*choices) + '?'
75 76
76 77 # Note: we use unicode matching for names ("\w") but ascii matching for
77 78 # number literals.
78 79 Whitespace = r'[ \f\t]*'
79 80 Comment = r'#[^\r\n]*'
80 81 Ignore = Whitespace + any(r'\\\r?\n' + Whitespace) + maybe(Comment)
81 82 Name = r'\w+'
82 83
83 84 Hexnumber = r'0[xX][0-9a-fA-F]+'
84 85 Binnumber = r'0[bB][01]+'
85 86 Octnumber = r'0[oO][0-7]+'
86 87 Decnumber = r'(?:0+|[1-9][0-9]*)'
87 88 Intnumber = group(Hexnumber, Binnumber, Octnumber, Decnumber)
88 89 Exponent = r'[eE][-+]?[0-9]+'
89 90 Pointfloat = group(r'[0-9]+\.[0-9]*', r'\.[0-9]+') + maybe(Exponent)
90 91 Expfloat = r'[0-9]+' + Exponent
91 92 Floatnumber = group(Pointfloat, Expfloat)
92 93 Imagnumber = group(r'[0-9]+[jJ]', Floatnumber + r'[jJ]')
93 94 Number = group(Imagnumber, Floatnumber, Intnumber)
94 95 StringPrefix = r'(?:[bB][rR]?|[rR][bB]?|[uU])?'
95 96
96 97 # Tail end of ' string.
97 98 Single = r"[^'\\]*(?:\\.[^'\\]*)*'"
98 99 # Tail end of " string.
99 100 Double = r'[^"\\]*(?:\\.[^"\\]*)*"'
100 101 # Tail end of ''' string.
101 102 Single3 = r"[^'\\]*(?:(?:\\.|'(?!''))[^'\\]*)*'''"
102 103 # Tail end of """ string.
103 104 Double3 = r'[^"\\]*(?:(?:\\.|"(?!""))[^"\\]*)*"""'
104 105 Triple = group(StringPrefix + "'''", StringPrefix + '"""')
105 106 # Single-line ' or " string.
106 107 String = group(StringPrefix + r"'[^\n'\\]*(?:\\.[^\n'\\]*)*'",
107 108 StringPrefix + r'"[^\n"\\]*(?:\\.[^\n"\\]*)*"')
108 109
109 110 # Because of leftmost-then-longest match semantics, be sure to put the
110 111 # longest operators first (e.g., if = came before ==, == would get
111 112 # recognized as two instances of =).
112 113 Operator = group(r"\*\*=?", r">>=?", r"<<=?", r"!=",
113 114 r"//=?", r"->",
114 115 r"[+\-*/%&|^=<>]=?",
115 116 r"~")
116 117
117 118 Bracket = '[][(){}]'
118 119 Special = group(r'\r?\n', r'\.\.\.', r'[:;.,@]')
119 120 Funny = group(Operator, Bracket, Special)
120 121
121 122 PlainToken = group(Number, Funny, String, Name)
122 123 Token = Ignore + PlainToken
123 124
124 125 # First (or only) line of ' or " string.
125 126 ContStr = group(StringPrefix + r"'[^\n'\\]*(?:\\.[^\n'\\]*)*" +
126 127 group("'", r'\\\r?\n'),
127 128 StringPrefix + r'"[^\n"\\]*(?:\\.[^\n"\\]*)*' +
128 129 group('"', r'\\\r?\n'))
129 130 PseudoExtras = group(r'\\\r?\n', Comment, Triple)
130 131 PseudoToken = Whitespace + group(PseudoExtras, Number, Funny, ContStr, Name)
131 132
132 133 def _compile(expr):
133 134 return re.compile(expr, re.UNICODE)
134 135
135 136 tokenprog, pseudoprog, single3prog, double3prog = map(
136 137 _compile, (Token, PseudoToken, Single3, Double3))
137 138 endprogs = {"'": _compile(Single), '"': _compile(Double),
138 139 "'''": single3prog, '"""': double3prog,
139 140 "r'''": single3prog, 'r"""': double3prog,
140 141 "b'''": single3prog, 'b"""': double3prog,
141 142 "R'''": single3prog, 'R"""': double3prog,
142 143 "B'''": single3prog, 'B"""': double3prog,
143 144 "br'''": single3prog, 'br"""': double3prog,
144 145 "bR'''": single3prog, 'bR"""': double3prog,
145 146 "Br'''": single3prog, 'Br"""': double3prog,
146 147 "BR'''": single3prog, 'BR"""': double3prog,
147 148 'r': None, 'R': None, 'b': None, 'B': None}
148 149
149 150 triple_quoted = {}
150 151 for t in ("'''", '"""',
151 152 "r'''", 'r"""', "R'''", 'R"""',
152 153 "b'''", 'b"""', "B'''", 'B"""',
153 154 "br'''", 'br"""', "Br'''", 'Br"""',
154 155 "bR'''", 'bR"""', "BR'''", 'BR"""'):
155 156 triple_quoted[t] = t
156 157 single_quoted = {}
157 158 for t in ("'", '"',
158 159 "r'", 'r"', "R'", 'R"',
159 160 "b'", 'b"', "B'", 'B"',
160 161 "br'", 'br"', "Br'", 'Br"',
161 162 "bR'", 'bR"', "BR'", 'BR"' ):
162 163 single_quoted[t] = t
163 164
164 165 for _prefix in ['rb', 'rB', 'Rb', 'RB', 'u', 'U']:
165 166 _t2 = _prefix+'"""'
166 167 endprogs[_t2] = double3prog
167 168 triple_quoted[_t2] = _t2
168 169 _t1 = _prefix + "'''"
169 170 endprogs[_t1] = single3prog
170 171 triple_quoted[_t1] = _t1
171 172 single_quoted[_prefix+'"'] = _prefix+'"'
172 173 single_quoted[_prefix+"'"] = _prefix+"'"
173 174 del _prefix, _t2, _t1
174 175 endprogs['u'] = None
175 176 endprogs['U'] = None
176 177
177 178 del _compile
178 179
179 180 tabsize = 8
180 181
181 182 class TokenError(Exception): pass
182 183
183 184 class StopTokenizing(Exception): pass
184 185
185 186
186 187 class Untokenizer:
187 188
188 189 def __init__(self):
189 190 self.tokens = []
190 191 self.prev_row = 1
191 192 self.prev_col = 0
192 193 self.encoding = 'utf-8'
193 194
194 195 def add_whitespace(self, tok_type, start):
195 196 row, col = start
196 197 assert row >= self.prev_row
197 198 col_offset = col - self.prev_col
198 199 if col_offset > 0:
199 200 self.tokens.append(" " * col_offset)
200 201 elif row > self.prev_row and tok_type not in (NEWLINE, NL, ENDMARKER):
201 202 # Line was backslash-continued.
202 203 self.tokens.append(" ")
203 204
204 205 def untokenize(self, tokens):
205 206 iterable = iter(tokens)
206 207 for t in iterable:
207 208 if len(t) == 2:
208 209 self.compat(t, iterable)
209 210 break
210 211 tok_type, token, start, end = t[:4]
211 212 if tok_type == ENCODING:
212 213 self.encoding = token
213 214 continue
214 215 self.add_whitespace(tok_type, start)
215 216 self.tokens.append(token)
216 217 self.prev_row, self.prev_col = end
217 218 if tok_type in (NEWLINE, NL):
218 219 self.prev_row += 1
219 220 self.prev_col = 0
220 221 return "".join(self.tokens)
221 222
222 223 def compat(self, token, iterable):
223 224 # This import is here to avoid problems when the itertools
224 225 # module is not built yet and tokenize is imported.
225 226 from itertools import chain
226 227 startline = False
227 228 prevstring = False
228 229 indents = []
229 230 toks_append = self.tokens.append
230 231
231 232 for tok in chain([token], iterable):
232 233 toknum, tokval = tok[:2]
233 234 if toknum == ENCODING:
234 235 self.encoding = tokval
235 236 continue
236 237
237 238 if toknum in (NAME, NUMBER):
238 239 tokval += ' '
239 240
240 241 # Insert a space between two consecutive strings
241 242 if toknum == STRING:
242 243 if prevstring:
243 244 tokval = ' ' + tokval
244 245 prevstring = True
245 246 else:
246 247 prevstring = False
247 248
248 249 if toknum == INDENT:
249 250 indents.append(tokval)
250 251 continue
251 252 elif toknum == DEDENT:
252 253 indents.pop()
253 254 continue
254 255 elif toknum in (NEWLINE, NL):
255 256 startline = True
256 257 elif startline and indents:
257 258 toks_append(indents[-1])
258 259 startline = False
259 260 toks_append(tokval)
260 261
261 262
262 263 def untokenize(tokens):
263 264 """
264 265 Convert ``tokens`` (an iterable) back into Python source code. Return
265 266 a bytes object, encoded using the encoding specified by the last
266 267 ENCODING token in ``tokens``, or UTF-8 if no ENCODING token is found.
267 268
268 269 The result is guaranteed to tokenize back to match the input so that
269 270 the conversion is lossless and round-trips are assured. The
270 271 guarantee applies only to the token type and token string as the
271 272 spacing between tokens (column positions) may change.
272 273
273 274 :func:`untokenize` has two modes. If the input tokens are sequences
274 275 of length 2 (``type``, ``string``) then spaces are added as necessary to
275 276 preserve the round-trip property.
276 277
277 278 If the input tokens are sequences of length 4 or more (``type``,
278 279 ``string``, ``start``, ``end``), as returned by :func:`tokenize`, then
279 280 spaces are added so that each token appears in the result at the
280 281 position indicated by ``start`` and ``end``, if possible.
281 282 """
282 283 return Untokenizer().untokenize(tokens)
283 284
284 285
285 286 def _get_normal_name(orig_enc):
286 287 """Imitates get_normal_name in tokenizer.c."""
287 288 # Only care about the first 12 characters.
288 289 enc = orig_enc[:12].lower().replace("_", "-")
289 290 if enc == "utf-8" or enc.startswith("utf-8-"):
290 291 return "utf-8"
291 292 if enc in ("latin-1", "iso-8859-1", "iso-latin-1") or \
292 293 enc.startswith(("latin-1-", "iso-8859-1-", "iso-latin-1-")):
293 294 return "iso-8859-1"
294 295 return orig_enc
295 296
296 297 def detect_encoding(readline):
297 298 """
298 299 The detect_encoding() function is used to detect the encoding that should
299 300 be used to decode a Python source file. It requires one argment, readline,
300 301 in the same way as the tokenize() generator.
301 302
302 303 It will call readline a maximum of twice, and return the encoding used
303 304 (as a string) and a list of any lines (left as bytes) it has read in.
304 305
305 306 It detects the encoding from the presence of a utf-8 bom or an encoding
306 307 cookie as specified in pep-0263. If both a bom and a cookie are present,
307 308 but disagree, a SyntaxError will be raised. If the encoding cookie is an
308 309 invalid charset, raise a SyntaxError. Note that if a utf-8 bom is found,
309 310 'utf-8-sig' is returned.
310 311
311 312 If no encoding is specified, then the default of 'utf-8' will be returned.
312 313 """
313 314 bom_found = False
314 315 encoding = None
315 316 default = 'utf-8'
316 317 def read_or_stop():
317 318 try:
318 319 return readline()
319 320 except StopIteration:
320 321 return b''
321 322
322 323 def find_cookie(line):
323 324 try:
324 325 # Decode as UTF-8. Either the line is an encoding declaration,
325 326 # in which case it should be pure ASCII, or it must be UTF-8
326 327 # per default encoding.
327 328 line_string = line.decode('utf-8')
328 329 except UnicodeDecodeError:
329 330 raise SyntaxError("invalid or missing encoding declaration")
330 331
331 332 matches = cookie_re.findall(line_string)
332 333 if not matches:
333 334 return None
334 335 encoding = _get_normal_name(matches[0])
335 336 try:
336 337 codec = lookup(encoding)
337 338 except LookupError:
338 339 # This behaviour mimics the Python interpreter
339 340 raise SyntaxError("unknown encoding: " + encoding)
340 341
341 342 if bom_found:
342 343 if encoding != 'utf-8':
343 344 # This behaviour mimics the Python interpreter
344 345 raise SyntaxError('encoding problem: utf-8')
345 346 encoding += '-sig'
346 347 return encoding
347 348
348 349 first = read_or_stop()
349 350 if first.startswith(BOM_UTF8):
350 351 bom_found = True
351 352 first = first[3:]
352 353 default = 'utf-8-sig'
353 354 if not first:
354 355 return default, []
355 356
356 357 encoding = find_cookie(first)
357 358 if encoding:
358 359 return encoding, [first]
359 360
360 361 second = read_or_stop()
361 362 if not second:
362 363 return default, [first]
363 364
364 365 encoding = find_cookie(second)
365 366 if encoding:
366 367 return encoding, [first, second]
367 368
368 369 return default, [first, second]
369 370
370 371
371 372 def open(filename):
372 373 """Open a file in read only mode using the encoding detected by
373 374 detect_encoding().
374 375 """
375 376 buffer = builtins.open(filename, 'rb')
376 377 encoding, lines = detect_encoding(buffer.readline)
377 378 buffer.seek(0)
378 379 text = TextIOWrapper(buffer, encoding, line_buffering=True)
379 380 text.mode = 'r'
380 381 return text
381 382
382 383
383 384 def tokenize(readline):
384 385 """
385 386 The tokenize() generator requires one argment, readline, which
386 387 must be a callable object which provides the same interface as the
387 388 readline() method of built-in file objects. Each call to the function
388 389 should return one line of input as bytes. Alternately, readline
389 390 can be a callable function terminating with :class:`StopIteration`::
390 391
391 392 readline = open(myfile, 'rb').__next__ # Example of alternate readline
392 393
393 394 The generator produces 5-tuples with these members: the token type; the
394 395 token string; a 2-tuple (srow, scol) of ints specifying the row and
395 396 column where the token begins in the source; a 2-tuple (erow, ecol) of
396 397 ints specifying the row and column where the token ends in the source;
397 398 and the line on which the token was found. The line passed is the
398 399 logical line; continuation lines are included.
399 400
400 401 The first token sequence will always be an ENCODING token
401 402 which tells you which encoding was used to decode the bytes stream.
402 403 """
403 404 # This import is here to avoid problems when the itertools module is not
404 405 # built yet and tokenize is imported.
405 406 from itertools import chain, repeat
406 407 encoding, consumed = detect_encoding(readline)
407 408 rl_gen = iter(readline, b"")
408 409 empty = repeat(b"")
409 410 return _tokenize(chain(consumed, rl_gen, empty).__next__, encoding)
410 411
411 412
412 413 def _tokenize(readline, encoding):
413 414 lnum = parenlev = continued = 0
414 415 numchars = '0123456789'
415 416 contstr, needcont = '', 0
416 417 contline = None
417 418 indents = [0]
418 419
419 420 if encoding is not None:
420 421 if encoding == "utf-8-sig":
421 422 # BOM will already have been stripped.
422 423 encoding = "utf-8"
423 424 yield TokenInfo(ENCODING, encoding, (0, 0), (0, 0), '')
424 425 while True: # loop over lines in stream
425 426 try:
426 427 line = readline()
427 428 except StopIteration:
428 429 line = b''
429 430
430 431 if encoding is not None:
431 432 line = line.decode(encoding)
432 433 lnum += 1
433 434 pos, max = 0, len(line)
434 435
435 436 if contstr: # continued string
436 437 if not line:
437 438 raise TokenError("EOF in multi-line string", strstart)
438 439 endmatch = endprog.match(line)
439 440 if endmatch:
440 441 pos = end = endmatch.end(0)
441 442 yield TokenInfo(STRING, contstr + line[:end],
442 443 strstart, (lnum, end), contline + line)
443 444 contstr, needcont = '', 0
444 445 contline = None
445 446 elif needcont and line[-2:] != '\\\n' and line[-3:] != '\\\r\n':
446 447 yield TokenInfo(ERRORTOKEN, contstr + line,
447 448 strstart, (lnum, len(line)), contline)
448 449 contstr = ''
449 450 contline = None
450 451 continue
451 452 else:
452 453 contstr = contstr + line
453 454 contline = contline + line
454 455 continue
455 456
456 457 elif parenlev == 0 and not continued: # new statement
457 458 if not line: break
458 459 column = 0
459 460 while pos < max: # measure leading whitespace
460 461 if line[pos] == ' ':
461 462 column += 1
462 463 elif line[pos] == '\t':
463 464 column = (column//tabsize + 1)*tabsize
464 465 elif line[pos] == '\f':
465 466 column = 0
466 467 else:
467 468 break
468 469 pos += 1
469 470 if pos == max:
470 471 break
471 472
472 473 if line[pos] in '#\r\n': # skip comments or blank lines
473 474 if line[pos] == '#':
474 475 comment_token = line[pos:].rstrip('\r\n')
475 476 nl_pos = pos + len(comment_token)
476 477 yield TokenInfo(COMMENT, comment_token,
477 478 (lnum, pos), (lnum, pos + len(comment_token)), line)
478 479 yield TokenInfo(NEWLINE, line[nl_pos:],
479 480 (lnum, nl_pos), (lnum, len(line)), line)
480 481 else:
481 482 yield TokenInfo(NEWLINE, line[pos:],
482 483 (lnum, pos), (lnum, len(line)), line)
483 484 continue
484 485
485 486 if column > indents[-1]: # count indents or dedents
486 487 indents.append(column)
487 488 yield TokenInfo(INDENT, line[:pos], (lnum, 0), (lnum, pos), line)
488 489 while column < indents[-1]:
489 490 if column not in indents:
490 491 raise IndentationError(
491 492 "unindent does not match any outer indentation level",
492 493 ("<tokenize>", lnum, pos, line))
493 494 indents = indents[:-1]
494 495 yield TokenInfo(DEDENT, '', (lnum, pos), (lnum, pos), line)
495 496
496 497 else: # continued statement
497 498 if not line:
498 499 raise TokenError("EOF in multi-line statement", (lnum, 0))
499 500 continued = 0
500 501
501 502 while pos < max:
502 503 pseudomatch = pseudoprog.match(line, pos)
503 504 if pseudomatch: # scan for tokens
504 505 start, end = pseudomatch.span(1)
505 506 spos, epos, pos = (lnum, start), (lnum, end), end
506 507 token, initial = line[start:end], line[start]
507 508
508 509 if (initial in numchars or # ordinary number
509 510 (initial == '.' and token != '.' and token != '...')):
510 511 yield TokenInfo(NUMBER, token, spos, epos, line)
511 512 elif initial in '\r\n':
512 513 yield TokenInfo(NL if parenlev > 0 else NEWLINE,
513 514 token, spos, epos, line)
514 515 elif initial == '#':
515 516 assert not token.endswith("\n")
516 517 yield TokenInfo(COMMENT, token, spos, epos, line)
517 518 elif token in triple_quoted:
518 519 endprog = endprogs[token]
519 520 endmatch = endprog.match(line, pos)
520 521 if endmatch: # all on one line
521 522 pos = endmatch.end(0)
522 523 token = line[start:pos]
523 524 yield TokenInfo(STRING, token, spos, (lnum, pos), line)
524 525 else:
525 526 strstart = (lnum, start) # multiple lines
526 527 contstr = line[start:]
527 528 contline = line
528 529 break
529 530 elif initial in single_quoted or \
530 531 token[:2] in single_quoted or \
531 532 token[:3] in single_quoted:
532 533 if token[-1] == '\n': # continued string
533 534 strstart = (lnum, start)
534 535 endprog = (endprogs[initial] or endprogs[token[1]] or
535 536 endprogs[token[2]])
536 537 contstr, needcont = line[start:], 1
537 538 contline = line
538 539 break
539 540 else: # ordinary string
540 541 yield TokenInfo(STRING, token, spos, epos, line)
541 542 elif initial.isidentifier(): # ordinary name
542 543 yield TokenInfo(NAME, token, spos, epos, line)
543 544 elif initial == '\\': # continued stmt
544 545 continued = 1
545 546 else:
546 547 if initial in '([{':
547 548 parenlev += 1
548 549 elif initial in ')]}':
549 550 parenlev -= 1
550 551 yield TokenInfo(OP, token, spos, epos, line)
551 552 else:
552 553 yield TokenInfo(ERRORTOKEN, line[pos],
553 554 (lnum, pos), (lnum, pos+1), line)
554 555 pos += 1
555 556
556 557 for indent in indents[1:]: # pop remaining indent levels
557 558 yield TokenInfo(DEDENT, '', (lnum, 0), (lnum, 0), '')
558 559 yield TokenInfo(ENDMARKER, '', (lnum, 0), (lnum, 0), '')
559 560
560 561
561 562 # An undocumented, backwards compatible, API for all the places in the standard
562 563 # library that expect to be able to use tokenize with strings
563 564 def generate_tokens(readline):
564 565 return _tokenize(readline, None)
565 566
566 567 if __name__ == "__main__":
567 568 # Quick sanity check
568 569 s = b'''def parseline(self, line):
569 570 """Parse the line into a command name and a string containing
570 571 the arguments. Returns a tuple containing (command, args, line).
571 572 'command' and 'args' may be None if the line couldn't be parsed.
572 573 """
573 574 line = line.strip()
574 575 if not line:
575 576 return None, None, line
576 577 elif line[0] == '?':
577 578 line = 'help ' + line[1:]
578 579 elif line[0] == '!':
579 580 if hasattr(self, 'do_shell'):
580 581 line = 'shell ' + line[1:]
581 582 else:
582 583 return None, None, line
583 584 i, n = 0, len(line)
584 585 while i < n and line[i] in self.identchars: i = i+1
585 586 cmd, arg = line[:i], line[i:].strip()
586 587 return cmd, arg, line
587 588 '''
588 589 for tok in tokenize(iter(s.splitlines()).__next__):
589 590 print(tok)
General Comments 0
You need to be logged in to leave comments. Login now