Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Line ending options #130

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ A note about handling text literals
mark bytestrings with ``b''`` and native strings in ``str('')``
or something similar that survives the transformation.

Line endings
============

Normally, changed 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 ``--unix-line-endings`` option writes Unix line endings regardless of
the curent platform. Similarly, the ``--windows-line-endings`` option
writes Windows line endings regardless of the current platform.

Indices and tables
//////////////////

Expand Down
44 changes: 42 additions & 2 deletions libmodernize/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from __future__ import absolute_import, print_function

import io
import sys
import logging
import optparse
Expand All @@ -17,6 +18,28 @@
from libmodernize import __version__
from libmodernize.fixes import lib2to3_fix_names, six_fix_names, opt_in_fix_names


class LineEndingsRefactoringTool(StdoutRefactoringTool):
'''2to3 refactoring tool that rewrites files with specified line endings'''
def __init__(self, *args, **kwargs):
self.newline = kwargs.pop('newline', None)
super(LineEndingsRefactoringTool, self).__init__(*args, **kwargs)

def write_file(self, new_text, filename, old_text, encoding):
super(LineEndingsRefactoringTool, self).write_file(new_text, filename,
old_text, encoding)

if self.newline is not None:
self.log_debug("Rewriting %s with line endings %r",
filename, self.newline)
with io.open(filename, 'r', encoding=encoding) as f:
contents = f.read()

with io.open(filename, 'w', encoding=encoding,
newline=self.newline) as f:
f.write(contents)


usage = __doc__ + """\
%s

Expand Down Expand Up @@ -63,6 +86,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("--unix-line-endings", action="store_true", default=False,
help="Write files with Unix (LF) line endings.")
parser.add_option("--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))
Expand All @@ -82,6 +109,10 @@ def main(args=None):
print(fixname)
if not args:
return 0
if options.unix_line_endings and options.windows_line_endings:
print("--unix-line-endings and --windows-line-endings are mutually exclusive",
file=sys.stderr)
return 2
if not args:
print("At least one file or directory argument required.", file=sys.stderr)
print("Use --help to show usage.", file=sys.stderr)
Expand Down Expand Up @@ -125,8 +156,17 @@ def main(args=None):
else:
requested = default_fixes
fixer_names = requested.difference(unwanted_fixes)
rt = StdoutRefactoringTool(sorted(fixer_names), flags, sorted(explicit),
options.nobackups, not options.no_diffs)

if options.unix_line_endings:
newline = '\n'
elif options.windows_line_endings:
newline = '\r\n'
else:
newline = None

rt = LineEndingsRefactoringTool(sorted(fixer_names), flags, sorted(explicit),
options.nobackups, not options.no_diffs,
newline=newline)

# Refactor all files and directories passed as arguments
if not rt.errors:
Expand Down
34 changes: 34 additions & 0 deletions tests/test_newlines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from __future__ import absolute_import

import os
from utils import check_on_input, expect_error

TESTCASE = ("""\
# sample code
isinstance(x, basestring)
""", """\
# sample code
from __future__ import absolute_import
import six
isinstance(x, six.string_types)
""")


def test_to_native_line_endings():
foreign_linesep = '\r\n' if (os.linesep == '\n') else '\n'
check_on_input(TESTCASE[0], TESTCASE[1].replace('\n', os.linesep),
write_newline=foreign_linesep, read_newline='')

def test_windows_to_unix_line_endings():
check_on_input(TESTCASE[0], TESTCASE[1],
extra_flags=['--unix-line-endings'],
write_newline='\r\n', read_newline='')

def test_unix_to_windows_line_endings():
check_on_input(TESTCASE[0], TESTCASE[1].replace('\n', '\r\n'),
extra_flags=['--windows-line-endings'],
write_newline='\n', read_newline='')

def test_options_conflict():
expect_error(TESTCASE[0],
extra_flags=['--unix-line-endings', '--windows-line-endings'])
28 changes: 25 additions & 3 deletions tests/utils.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
from __future__ import absolute_import

import io
import os.path
import tempfile
import shutil
import sys

PY3 = sys.version_info[0] >= 3

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 = [],
write_newline=None, read_newline=None):
"""
Check that input_content is fixed to expected_content, idempotently.

Expand All @@ -16,17 +21,21 @@ def check_on_input(input_content, expected_content, extra_flags = []):
matches expected_content. Then, runs modernize again with any extra arguments,
and asserts that the second run makes no changes.
"""
if not PY3 and isinstance(input_content, bytes):
# Allow native strings as input on Python 2
input_content = input_content.decode('ascii')

tmpdirname = tempfile.mkdtemp()
try:
test_input_name = os.path.join(tmpdirname, "input.py")
with open(test_input_name, "wt") as input_file:
with io.open(test_input_name, "wt", newline=write_newline) as input_file:
input_file.write(input_content)

def _check(this_input_content, which_check):
modernize_main(extra_flags + ["-w", test_input_name])

output_content = ""
with open(test_input_name, "rt") as output_file:
with io.open(test_input_name, "rt", newline=read_newline) as output_file:
for line in output_file:
if line:
output_content += line
Expand All @@ -40,3 +49,16 @@ def _check(this_input_content, which_check):
_check(expected_content, "idempotence check failed")
finally:
shutil.rmtree(tmpdirname)

def expect_error(input_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:
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)