Index: Lib/inspect.py =================================================================== --- Lib/inspect.py (revision 67665) +++ Lib/inspect.py (working copy) @@ -620,57 +620,43 @@ comments[-1:] = [] return string.join(comments, '') -class EndOfBlock(Exception): pass - -class BlockFinder: - """Provide a tokeneater() method to detect the end of a code block.""" - def __init__(self): - self.indent = 0 - self.islambda = False - self.started = False - self.passline = False - self.last = 1 - - def tokeneater(self, type, token, srow_scol, erow_ecol, line): - srow, scol = srow_scol - erow, ecol = erow_ecol - if not self.started: - # look for the first "def", "class" or "lambda" - if token in ("def", "class", "lambda"): - if token == "lambda": - self.islambda = True - self.started = True - self.passline = True # skip to the end of the line - elif type == tokenize.NEWLINE: - self.passline = False # stop skipping when a NEWLINE is seen - self.last = srow - if self.islambda: # lambdas always end at the first NEWLINE - raise EndOfBlock - elif self.passline: - pass - elif type == tokenize.INDENT: - self.indent = self.indent + 1 - self.passline = True - elif type == tokenize.DEDENT: - self.indent = self.indent - 1 - # the end of matching indent/dedent pairs end a block - # (note that this only works for "def"/"class" blocks, - # not e.g. for "if: else:" or "try: finally:" blocks) - if self.indent <= 0: - raise EndOfBlock - elif self.indent == 0 and type not in (tokenize.COMMENT, tokenize.NL): - # any other token on the same indentation level end the previous - # block as well, except the pseudo-tokens COMMENT and NL. - raise EndOfBlock - def getblock(lines): """Extract the block of code at the top of the given list of lines.""" - blockfinder = BlockFinder() + indent = 0 + islambda = False + started = False + passline = False + last = 1 + try: - tokenize.tokenize(iter(lines).next, blockfinder.tokeneater) - except (EndOfBlock, IndentationError): + for (type, token, (srow, scol), (erow, ecol), line) \ + in tokenize.generate_tokens(iter(lines).next): + if not started: + # look for the first "def", "class" or "lambda" + if token in ("def", "class", "lambda"): + if token == "lambda": + islambda = True + started = True + passline = True # skip to the end of the line + elif type in (tokenize.NEWLINE, tokenize.ENDMARKER): + passline = False # stop skipping when a NEWLINE is seen + last = srow + if islambda: # lambdas always end at the first NEWLINE + break + elif passline: + pass + elif type == tokenize.INDENT: + indent = indent + 1 + passline = True + elif type == tokenize.DEDENT: + indent = indent - 1 + elif indent <= 0 and type not in (tokenize.COMMENT, tokenize.NL): + # any other token on the same indentation level end the previous + # block as well, except the pseudo-tokens COMMENT and NL. + break + except IndentationError: pass - return lines[:blockfinder.last] + return lines[:last] def getsourcelines(object): """Return a list of source lines and starting line number for an object. Index: Lib/test/test_inspect.py =================================================================== --- Lib/test/test_inspect.py (revision 67665) +++ Lib/test/test_inspect.py (working copy) @@ -8,6 +8,7 @@ from test import inspect_fodder as mod from test import inspect_fodder2 as mod2 +from test import inspect_fodder3 as mod3 # Functions tested in this suite: # ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode, @@ -142,11 +143,10 @@ def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) - self.source = file(inspect.getsourcefile(self.fodderFile)).read() + self.source = file(inspect.getsourcefile(self.fodderFile)).readlines() def sourcerange(self, top, bottom): - lines = self.source.split("\n") - return "\n".join(lines[top-1:bottom]) + "\n" + return ''.join(self.source[top-1:bottom]) def assertSourceEqual(self, obj, top, bottom): self.assertEqual(inspect.getsource(obj), @@ -304,6 +304,12 @@ def test_method_in_dynamic_class(self): self.assertSourceEqual(mod2.method_in_dynamic_class, 95, 97) +class TestNoEOF(GetSourceBase): + fodderFile = mod3 + + def test_class(self): + self.assertSourceEqual(mod3.X, 1, 2) + # Helper for testing classify_class_attrs. def attrs_wo_objs(cls): return [t[:3] for t in inspect.classify_class_attrs(cls)] @@ -488,10 +494,10 @@ self.assert_(('m', 'method', B) in attrs, 'missing plain method') self.assert_(('m1', 'method', D) in attrs, 'missing plain method') self.assert_(('datablob', 'data', A) in attrs, 'missing data') - + def test_main(): run_unittest(TestDecorators, TestRetrievingSourceCode, TestOneliners, - TestBuggyCases, + TestBuggyCases, TestNoEOF, TestInterpreterStack, TestClassesAndFunctions, TestPredicates) if __name__ == "__main__": Index: Lib/test/inspect_fodder3.py =================================================================== --- Lib/test/inspect_fodder3.py (revision 0) +++ Lib/test/inspect_fodder3.py (revision 0) @@ -0,0 +1,2 @@ +class X: + pass \ No newline at end of file