diff --git a/docs/fixers.rst b/docs/fixers.rst index b2afd71..ed2a851 100644 --- a/docs/fixers.rst +++ b/docs/fixers.rst @@ -39,6 +39,17 @@ using ``-f``/``--fix``. The ``--six-unicode`` and ``--future-unicode`` options also disable fixers that are not applicable for those options. +Options affecting all fixers +---------------------------- + +Normally, output files are written with the usual line endings for the platform +that python-modernize is run on (LF for Unix / Mac OS X, or CRLF for Windows). + +The ``-U``/``--unix-line-endings`` option writes Unix line endings regardless of +the curent platform. Similarly, the ``-W``/``--windows-line-endings`` option +writes Windows line endings regardless of the current platform. + + Fixers requiring six ++++++++++++++++++++ diff --git a/libmodernize/main.py b/libmodernize/main.py index 4fe934c..f1e0fe2 100644 --- a/libmodernize/main.py +++ b/libmodernize/main.py @@ -8,6 +8,7 @@ from __future__ import absolute_import, print_function import sys +import os import logging import optparse @@ -28,6 +29,14 @@ def format_usage(usage): return usage def main(args=None): + old_os_linesep = os.linesep + try: + return actual_main(args) + finally: + if sys.version_info < (3, 0): + os.linesep = old_os_linesep + +def actual_main(args=None): """Main program. Returns a suggested exit status (0, 1, 2). @@ -63,6 +72,10 @@ def main(args=None): "(only useful for Python 2.6+).") parser.add_option("--no-six", action="store_true", default=False, help="Exclude fixes that depend on the six package.") + parser.add_option("-U", "--unix-line-endings", action="store_true", default=False, + help="Write files with Unix (LF) line endings.") + parser.add_option("-W", "--windows-line-endings", action="store_true", default=False, + help="Write files with Windows (CRLF) line endings.") fixer_pkg = 'libmodernize.fixes' avail_fixes = set(refactor.get_fixers_from_package(fixer_pkg)) @@ -94,6 +107,14 @@ def main(args=None): if options.print_function: flags["print_function"] = True + if options.unix_line_endings and options.windows_line_endings: + print("-U/--unix-line-endings and -W/--windows-line-endings options conflict.") + return 2 + if options.unix_line_endings: + fix_line_endings('\n') + if options.windows_line_endings: + fix_line_endings('\r\n') + # Set up logging handler level = logging.DEBUG if options.verbose else logging.INFO logging.basicConfig(format='%(name)s: %(message)s', level=level) @@ -145,3 +166,12 @@ def main(args=None): # Return error status (0 if rt.errors is zero) return int(bool(rt.errors)) + +def fix_line_endings(linesep): + if sys.version_info < (3, 0): + os.linesep = linesep + else: + def _to_system_newlines(s): + return s.replace(os.linesep, linesep) + + refactor._to_system_newlines = _to_system_newlines diff --git a/tests/test_newlines.py b/tests/test_newlines.py new file mode 100644 index 0000000..a15ffa5 --- /dev/null +++ b/tests/test_newlines.py @@ -0,0 +1,30 @@ +from __future__ import absolute_import + +import os +from utils import check_on_input, expect_error +from test_fix_basestring import BASESTRING + +TESTCASE = [x.encode('ascii') for x in BASESTRING] + + +def test_mixed_to_native_line_endings(): + check_on_input(b'#\r\n' + TESTCASE[0], + (b'#\n' + TESTCASE[1]).replace(b'\n', os.linesep.encode('ascii')), + mode="b") + +def test_windows_to_unix_line_endings(): + check_on_input(TESTCASE[0].replace(b'\n', b'\r\n'), + TESTCASE[1], + extra_flags=['--unix-line-endings'], + mode="b") + +def test_unix_to_windows_line_endings(): + check_on_input(TESTCASE[0], + TESTCASE[1].replace(b'\n', b'\r\n'), + extra_flags=['--windows-line-endings'], + mode="b") + +def test_options_conflict(): + expect_error(TESTCASE[0], + extra_flags=['--unix-line-endings', '--windows-line-endings'], + mode="b") diff --git a/tests/utils.py b/tests/utils.py index ed82bd3..ee248dd 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -7,7 +7,7 @@ from libmodernize.main import main as modernize_main -def check_on_input(input_content, expected_content, extra_flags = []): +def check_on_input(input_content, expected_content, extra_flags=[], mode="t"): """ Check that input_content is fixed to expected_content, idempotently. @@ -19,20 +19,24 @@ def check_on_input(input_content, expected_content, extra_flags = []): tmpdirname = tempfile.mkdtemp() try: test_input_name = os.path.join(tmpdirname, "input.py") - with open(test_input_name, "wt") as input_file: + with open(test_input_name, "w" + mode) as input_file: + if mode == "b": + input_file = getattr(input_file, 'buffer', input_file) input_file.write(input_content) def _check(this_input_content, which_check): - modernize_main(extra_flags + ["-w", test_input_name]) + ret = modernize_main(extra_flags + ["-w", test_input_name]) + if ret != 0: + raise AssertionError("didn't expect to fail (returned %r)" % (ret,)) - output_content = "" - with open(test_input_name, "rt") as output_file: - for line in output_file: - if line: - output_content += line + with open(test_input_name, "r" + mode) as output_file: + if mode == "b": + output_content = output_file.read() + else: + output_content = "".join([line for line in output_file if line]) if output_content != expected_content: - raise AssertionError("%s\nInput:\n%sOutput:\n%s\nExpecting:\n%s" % + raise AssertionError("%s\nInput:\n%s\nOutput:\n%s\nExpecting:\n%s" % (which_check, this_input_content, output_content, expected_content)) _check(input_content, "output check failed") @@ -40,3 +44,19 @@ def _check(this_input_content, which_check): _check(expected_content, "idempotence check failed") finally: shutil.rmtree(tmpdirname) + + +def expect_error(input_content, extra_flags=[], mode="t"): + tmpdirname = tempfile.mkdtemp() + try: + test_input_name = os.path.join(tmpdirname, "input.py") + with open(test_input_name, "w" + mode) as input_file: + if mode == "b": + input_file = getattr(input_file, 'buffer', input_file) + input_file.write(input_content) + + ret = modernize_main(extra_flags + ["-w", test_input_name]) + if ret == 0: + raise AssertionError("didn't expect to succeed") + finally: + shutil.rmtree(tmpdirname)