diff -r ec81edc30221 Lib/distutils/command/sdist.py --- a/Lib/distutils/command/sdist.py Sat Nov 08 22:41:00 2014 +0200 +++ b/Lib/distutils/command/sdist.py Sun Nov 09 11:20:46 2014 +0200 @@ -300,16 +300,10 @@ class sdist(Command): 'self.filelist', which updates itself accordingly. """ log.info("reading manifest template '%s'", self.template) - template = TextFile(self.template, strip_comments=1, skip_blanks=1, - join_lines=1, lstrip_ws=1, rstrip_ws=1, - collapse_join=1) - - try: - while True: - line = template.readline() - if line is None: # end of file - break - + with TextFile(self.template, strip_comments=True, skip_blanks=True, + join_lines=True, lstrip_ws=True, rstrip_ws=True, + collapse_join=True) as template: + for line in template: try: self.filelist.process_template_line(line) # the call above can raise a DistutilsTemplateError for @@ -319,8 +313,6 @@ class sdist(Command): self.warn("%s, line %d: %s" % (template.filename, template.current_line, msg)) - finally: - template.close() def prune_file_list(self): """Prune off branches that might slip into the file list as created diff -r ec81edc30221 Lib/distutils/extension.py --- a/Lib/distutils/extension.py Sat Nov 08 22:41:00 2014 +0200 +++ b/Lib/distutils/extension.py Sun Nov 09 11:20:46 2014 +0200 @@ -152,16 +151,12 @@ def read_setup_file(filename): # Second pass to gobble up the real content: lines of the form # ... [ ...] [ ...] [ ...] - file = TextFile(filename, - strip_comments=1, skip_blanks=1, join_lines=1, - lstrip_ws=1, rstrip_ws=1) - try: + with TextFile(filename, + strip_comments=True, skip_blanks=True, join_lines=True, + lstrip_ws=True, rstrip_ws=True) as file: extensions = [] - while True: - line = file.readline() - if line is None: # eof - break + for line in file: if _variable_rx.match(line): # VAR=VALUE, handled in first pass continue @@ -235,7 +230,5 @@ def read_setup_file(filename): file.warn("unrecognized argument '%s'" % word) extensions.append(ext) - finally: - file.close() return extensions diff -r ec81edc30221 Lib/distutils/sysconfig.py --- a/Lib/distutils/sysconfig.py Sat Nov 08 22:41:00 2014 +0200 +++ b/Lib/distutils/sysconfig.py Sun Nov 09 11:20:46 2014 +0200 @@ -296,35 +293,31 @@ def parse_makefile(fn, g=None): optional dictionary is passed in as the second argument, it is used instead of a new dictionary. """ - from distutils.text_file import TextFile - fp = TextFile(fn, strip_comments=1, skip_blanks=1, join_lines=1, errors="surrogateescape") - if g is None: g = {} done = {} notdone = {} - while True: - line = fp.readline() - if line is None: # eof - break - m = _variable_rx.match(line) - if m: - n, v = m.group(1, 2) - v = v.strip() - # `$$' is a literal `$' in make - tmpv = v.replace('$$', '') + from distutils.text_file import TextFile + with TextFile(fn, strip_comments=True, skip_blanks=True, join_lines=True, errors="surrogateescape") as fp: + for line in fp: + m = _variable_rx.match(line) + if m: + n, v = m.group(1, 2) + v = v.strip() + # `$$' is a literal `$' in make + tmpv = v.replace('$$', '') - if "$" in tmpv: - notdone[n] = v - else: - try: - v = int(v) - except ValueError: - # insert literal `$' - done[n] = v.replace('$$', '$') + if "$" in tmpv: + notdone[n] = v else: - done[n] = v + try: + v = int(v) + except ValueError: + # insert literal `$' + done[n] = v.replace('$$', '$') + else: + done[n] = v # Variables with a 'PY_' prefix in the makefile. These need to # be made available without that prefix through sysconfig. @@ -383,8 +376,6 @@ def parse_makefile(fn, g=None): # bogus variable reference; just drop it since we can't deal del notdone[name] - fp.close() - # strip spurious spaces for k, v in done.items(): if isinstance(v, str): diff -r ec81edc30221 Lib/distutils/tests/test_text_file.py --- a/Lib/distutils/tests/test_text_file.py Sat Nov 08 22:41:00 2014 +0200 +++ b/Lib/distutils/tests/test_text_file.py Sun Nov 09 11:20:46 2014 +0200 @@ -53,52 +53,31 @@ class TextFileTestCase(support.TempdirMa tmpdir = self.mkdtemp() filename = os.path.join(tmpdir, "test.txt") - out_file = open(filename, "w") - try: + with open(filename, "w") as out_file: out_file.write(TEST_DATA) - finally: - out_file.close() - in_file = TextFile(filename, strip_comments=0, skip_blanks=0, - lstrip_ws=0, rstrip_ws=0) - try: + with TextFile(filename, strip_comments=False, skip_blanks=False, + lstrip_ws=False, rstrip_ws=False) as in_file: test_input(1, "no processing", in_file, result1) - finally: - in_file.close() - in_file = TextFile(filename, strip_comments=1, skip_blanks=0, - lstrip_ws=0, rstrip_ws=0) - try: + with TextFile(filename, strip_comments=True, skip_blanks=False, + lstrip_ws=False, rstrip_ws=False) as in_file: test_input(2, "strip comments", in_file, result2) - finally: - in_file.close() - in_file = TextFile(filename, strip_comments=0, skip_blanks=1, - lstrip_ws=0, rstrip_ws=0) - try: + with TextFile(filename, strip_comments=False, skip_blanks=True, + lstrip_ws=False, rstrip_ws=False) as in_file: test_input(3, "strip blanks", in_file, result3) - finally: - in_file.close() - in_file = TextFile(filename) - try: + with TextFile(filename) as in_file: test_input(4, "default processing", in_file, result4) - finally: - in_file.close() - in_file = TextFile(filename, strip_comments=1, skip_blanks=1, - join_lines=1, rstrip_ws=1) - try: + with TextFile(filename, strip_comments=True, skip_blanks=True, + join_lines=True, rstrip_ws=True) as in_file: test_input(5, "join lines without collapsing", in_file, result5) - finally: - in_file.close() - in_file = TextFile(filename, strip_comments=1, skip_blanks=1, - join_lines=1, rstrip_ws=1, collapse_join=1) - try: + with TextFile(filename, strip_comments=True, skip_blanks=True, + join_lines=True, rstrip_ws=True, collapse_join=True) as in_file: test_input(6, "join lines with collapsing", in_file, result6) - finally: - in_file.close() def test_suite(): return unittest.makeSuite(TextFileTestCase) diff -r ec81edc30221 Lib/distutils/text_file.py --- a/Lib/distutils/text_file.py Sat Nov 08 22:41:00 2014 +0200 +++ b/Lib/distutils/text_file.py Sun Nov 09 11:20:46 2014 +0200 @@ -66,16 +66,15 @@ class TextFile: an all-whitespace line), if 'rstrip_ws' is true but 'skip_blanks' is not.""" - default_options = { 'strip_comments': 1, - 'skip_blanks': 1, - 'lstrip_ws': 0, - 'rstrip_ws': 1, - 'join_lines': 0, - 'collapse_join': 0, - 'errors': 'strict', - } - - def __init__(self, filename=None, file=None, **options): + def __init__(self, filename=None, file=None, *, + strip_comments=True, + skip_blanks=True, + lstrip_ws=False, + rstrip_ws=True, + join_lines=False, + collapse_join=False, + errors='strict' + ): """Construct a new TextFile object. At least one of 'filename' (a string) and 'file' (a file-like object) must be supplied. They keyword argument options are described above and affect @@ -83,18 +82,13 @@ class TextFile: if filename is None and file is None: raise RuntimeError("you must supply either or both of 'filename' and 'file'") - # set values for all options -- either from client option hash - # or fallback to default_options - for opt in self.default_options.keys(): - if opt in options: - setattr(self, opt, options[opt]) - else: - setattr(self, opt, self.default_options[opt]) - - # sanity check client option hash - for opt in options.keys(): - if opt not in self.default_options: - raise KeyError("invalid TextFile option '%s'" % opt) + self.strip_comments = bool(strip_comments) + self.skip_blanks = bool(skip_blanks) + self.lstrip_ws = bool(lstrip_ws) + self.rstrip_ws = bool(rstrip_ws) + self.join_lines = bool(join_lines) + self.collapse_join = bool(collapse_join) + self.errors = errors if file is None: self.open(filename) @@ -122,6 +116,7 @@ class TextFile: self.file = None self.filename = None self.current_line = None + self.linebuf = [] def gen_error(self, msg, line=None): outmsg = [] @@ -163,9 +158,7 @@ class TextFile: # get put in 'linebuf' if the client explicitly does an # 'unreadline()'. if self.linebuf: - line = self.linebuf[-1] - del self.linebuf[-1] - return line + return self.linebuf.pop() buildup_line = '' @@ -198,7 +191,7 @@ class TextFile: # (NB. this means that if the final line is all comment # and has no trailing newline, we will think that it's # EOF; I think that's OK.) - eol = (line[-1] == '\n') and '\n' or '' + eol = '\n' if line[-1] == '\n' else '' line = line[0:pos] + eol # If all that's left is whitespace, then skip line @@ -283,3 +276,20 @@ class TextFile: checked by future 'readline()' calls. Handy for implementing a parser with line-at-a-time lookahead.""" self.linebuf.append(line) + + def __enter__(self): + """Context management protocol. Returns self.""" + return self + + def __exit__(self, *args): + """Context management protocol. Calls close()""" + self.close() + + def __iter__(self): + return self + + def __next__(self): + line = self.readline() + if line is None: + raise StopIteration + return line