Skip to content

Commit

Permalink
auto line ending, comand case, stdin
Browse files Browse the repository at this point in the history
* Implement "auto" line ending option
* Implement command casing
* Implement stdin as an input file

Closes #27
Closes #29
Closes #30
  • Loading branch information
Josh Bialkowski committed Apr 10, 2018
1 parent 3c5ab66 commit 2e2aff2
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 18 deletions.
2 changes: 0 additions & 2 deletions cmake_format/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/cmake_format_lint.stamp
add_custom_target(cmake_format_lint
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/cmake_format_lint.stamp)

# NOTE(josh): format before lint-check, avoid choking on style lint
add_dependencies(cmake_format_lint cmake_format_format)
add_dependencies(lint cmake_format_lint)

add_test(NAME cmake_format-format_tests
Expand Down
5 changes: 5 additions & 0 deletions cmake_format/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
Parse cmake listfiles and format them nicely
"""

VERSION = '0.3.6'
46 changes: 39 additions & 7 deletions cmake_format/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,20 @@
import tempfile
import textwrap

import cmake_format
from cmake_format import configuration
from cmake_format import formatter
from cmake_format import lexer
from cmake_format import parser

VERSION = '0.3.5'

def detect_line_endings(infile_content):
windows_count = infile_content.count('\r\n')
unix_count = infile_content.count('\n') - windows_count
if windows_count > unix_count:
return 'windows'

return 'unix'


def process_file(config, infile, outfile):
Expand All @@ -37,7 +45,11 @@ def process_file(config, infile, outfile):
"""

pretty_printer = formatter.TreePrinter(config, outfile)
tokens = lexer.tokenize(infile.read())
infile_content = infile.read()
if config.line_ending == 'auto':
detected = detect_line_endings(infile_content)
config.set_line_ending(detected)
tokens = lexer.tokenize(infile_content)
tok_seqs = parser.digest_tokens(tokens)
fst = parser.construct_fst(tok_seqs)
pretty_printer.print_node(fst)
Expand All @@ -49,7 +61,11 @@ def find_config_file(infile_path):
one exists.
"""
realpath = os.path.realpath(infile_path)
head, _ = os.path.split(realpath)
if os.path.isdir(infile_path):
head = infile_path
else:
head, _ = os.path.split(realpath)

while head:
for filename in ['.cmake-format',
'.cmake-format.py',
Expand Down Expand Up @@ -184,7 +200,7 @@ def main():
usage=USAGE_STRING)

arg_parser.add_argument('-v', '--version', action='version',
version=VERSION)
version=cmake_format.VERSION)

mutex = arg_parser.add_mutually_exclusive_group()
mutex.add_argument('--dump-config', choices=['yaml', 'json', 'python'],
Expand Down Expand Up @@ -242,11 +258,22 @@ def main():
or (args.in_place is True or args.outfile_path == '-')), \
("if more than one input file is specified, then formatting must be done"
" in-place or written to stdout")

if args.outfile_path is None:
args.outfile_path = '-'

if '-' in args.infilepaths:
assert len(args.infilepaths) == 1, \
"You cannot mix stdin as an input with other input files"
assert args.outfile_path == '-', \
"If stdin is the input file, then stdout must be the output file"

for infile_path in args.infilepaths:
config_dict = get_config(infile_path, args.config_file)
if infile_path == '-':
config_dict = get_config(os.getcwd(), args.config_file)
else:
config_dict = get_config(infile_path, args.config_file)

for key, value in vars(args).items():
if (key in configuration.Configuration.get_field_names()
# pylint: disable=bad-continuation
Expand All @@ -273,14 +300,19 @@ def main():
newline='')

parse_ok = True
if infile_path == '-':
infile = io.open(sys.stdin.fileno(), mode='r', encoding='utf-8',
newline='')
else:
infile = io.open(infile_path, 'r', encoding='utf-8')

try:
with io.open(infile_path, 'r', encoding='utf-8') as infile:
with infile:
try:
process_file(cfg, infile, outfile)
except:
sys.stderr.write('Error while processing {}\n'.format(infile_path))
raise

except:
parse_ok = False
sys.stderr.write('While processing {}\n'.format(infile_path))
Expand Down
21 changes: 17 additions & 4 deletions cmake_format/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,12 @@ def __init__(self, line_width=80, tab_size=2,
bullet_char=None,
enum_char=None,
line_ending=None,
command_case=None,
additional_commands=None, **_):

self.line_width = line_width
self.tab_size = tab_size

# TODO(josh): make this conditioned on certain commands / kwargs
# because things like execute_process(COMMAND...) are less readable
# formatted as a single list. In fact... special case COMMAND to break on
Expand All @@ -106,6 +108,9 @@ def __init__(self, line_width=80, tab_size=2,
self.enum_char = u'.'

self.line_ending = get_default(line_ending, u"unix")
self.command_case = get_default(command_case, u"lower")
assert self.command_case in (u"lower", u"upper", u"unchanged")

self.additional_commands = get_default(additional_commands, {
'foo': {
'flags': ['BAR', 'BAZ'],
Expand All @@ -122,20 +127,26 @@ def __init__(self, line_width=80, tab_size=2,
for command_name, spec in additional_commands.items():
commands.decl_command(self.fn_spec, command_name, **spec)

assert self.line_ending in (u"windows", u"unix"), \
r"Line ending must be either 'windows' or 'unix'"
assert self.line_ending in (u"windows", u"unix", u"auto"), \
r"Line ending must be either 'windows', 'unix', or 'auto'"
self.endl = {u'windows': u'\r\n',
u'unix': u'\n'}[self.line_ending]
u'unix': u'\n',
u'auto': u'\n'}[self.line_ending]

def clone(self):
"""
Return a copy of self.
"""
return Configuration(**self.as_dict())

def set_line_ending(self, detected):
self.endl = {u'windows': u'\r\n',
u'unix': u'\n'}[detected]


VARCHOICES = {
'line_ending': ['windows', 'unix']
'line_ending': ['windows', 'unix', 'auto'],
'command_case': ['lower', 'upper', 'unchanged'],
}

VARDOCS = {
Expand All @@ -158,6 +169,8 @@ def clone(self):
" list",
"line_ending":
"What style line endings to use in the output.",
"command_case":
"Format command names consistently as 'lower' or 'upper' case",
"additional_commands":
"Specify structure for custom cmake functions"
}
4 changes: 4 additions & 0 deletions cmake_format/doc/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ pleasant way.
# What style line endings to use in the output.
line_ending = u'unix'
# Format command names with this case. Options are "lower", "upper",
# "unchanged"
command_case = u'lower'
# Specify structure for custom cmake functions
additional_commands = {
"foo": {
Expand Down
11 changes: 11 additions & 0 deletions cmake_format/doc/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ v0.3.5

.. _cheshirekow/cmake_format#28: https://github.com/cheshirekow/cmake_format/issues/28

v0.3.6
------

* Implement "auto" line ending option `cheshirekow/cmake_format#27`
* Implement command casing `cheshirekow/cmake_format#29`
* Implement stdin as an input file `cheshirekow/cmake_format#30`

.. _cheshirekow/cmake_format#27: https://github.com/cheshirekow/cmake_format/issues/27
.. _cheshirekow/cmake_format#29: https://github.com/cheshirekow/cmake_format/issues/29
.. _cheshirekow/cmake_format#30: https://github.com/cheshirekow/cmake_format/issues/30

------
v0.2.0
------
Expand Down
45 changes: 45 additions & 0 deletions cmake_format/format_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,51 @@ def test_windows_line_endings_output(self):
u" * Information line 2\r\n"
u" ************************************************]]")

def test_auto_line_endings(self):
config_dict = self.config.as_dict()
config_dict['line_ending'] = 'auto'
self.config = configuration.Configuration(**config_dict)

self.do_format_test(
u" #[[*********************************************\r\n"
u" * Information line 1\r\n"
u" * Information line 2\r\n"
u" ************************************************]]",
u" #[[*********************************************\r\n"
u" * Information line 1\r\n"
u" * Information line 2\r\n"
u" ************************************************]]")

def test_command_case(self):
self.do_format_test(
u"""\
FOO(bar baz)
""", """\
foo(bar baz)
""")

config_dict = self.config.as_dict()
config_dict['command_case'] = 'upper'
self.config = configuration.Configuration(**config_dict)

self.do_format_test(
u"""\
foo(bar baz)
""", """\
FOO(bar baz)
""")

config_dict = self.config.as_dict()
config_dict['command_case'] = 'unchanged'
self.config = configuration.Configuration(**config_dict)

self.do_format_test(
u"""\
FoO(bar baz)
""", """\
FoO(bar baz)
""")

def test_example_file(self):
thisdir = os.path.dirname(__file__)
infile_path = os.path.join(thisdir, 'test', 'test_in.cmake')
Expand Down
14 changes: 12 additions & 2 deletions cmake_format/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,19 +346,29 @@ def is_control_statement(command_name):
]


def format_command_name(config, command_name):
if config.command_case == u"lower":
return command_name.lower()
elif config.command_case == u"upper":
return command_name.upper()

return command_name


def format_command(config, command, line_width):
"""
Formats a cmake command call into a block with at most line_width chars.
Returns a list of lines.
"""
command_start = format_command_name(config, command.name)

if (config.separate_fn_name_with_space or
# pylint: disable=bad-continuation
(is_control_statement(command.name)
and config.separate_ctrl_name_with_space)):
command_start = command.name + u' ('
command_start += u' ('
else:
command_start = command.name + u'('
command_start += u'('

# If there are no args then return just the command
if len(command.body) < 1:
Expand Down
54 changes: 53 additions & 1 deletion cmake_format/invocation_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ def setUp(self):
def tearDown(self):
shutil.rmtree(self.tempdir)

def test_pipe_invocation(self):
def test_pipeout_invocation(self):
"""
Test invocation with an infile path and output to stdout.
"""

thisdir = os.path.realpath(os.path.dirname(__file__))
infile_path = os.path.join(thisdir, 'test', 'test_in.cmake')
expectfile_path = os.path.join(thisdir, 'test', 'test_out.cmake')
Expand All @@ -50,6 +54,10 @@ def test_pipe_invocation(self):
raise AssertionError('\n'.join(delta_lines[2:]))

def test_fileout_invocation(self):
"""
Test invocation with an infile path and outfile path
"""

thisdir = os.path.realpath(os.path.dirname(__file__))
infile_path = os.path.join(thisdir, 'test', 'test_in.cmake')
expectfile_path = os.path.join(thisdir, 'test', 'test_out.cmake')
Expand All @@ -70,6 +78,10 @@ def test_fileout_invocation(self):
raise AssertionError('\n'.join(delta_lines[2:]))

def test_inplace_invocation(self):
"""
Test invocation for inplace format of a file
"""

thisdir = os.path.realpath(os.path.dirname(__file__))
infile_path = os.path.join(thisdir, 'test', 'test_in.cmake')
expectfile_path = os.path.join(thisdir, 'test', 'test_out.cmake')
Expand All @@ -91,6 +103,46 @@ def test_inplace_invocation(self):
if delta_lines:
raise AssertionError('\n'.join(delta_lines[2:]))

def test_stream_invocation(self):
"""
Test invocation with stdin as the infile and stdout as the outifle
"""

thisdir = os.path.realpath(os.path.dirname(__file__))
infile_path = os.path.join(thisdir, 'test', 'test_in.cmake')
expectfile_path = os.path.join(thisdir, 'test', 'test_out.cmake')

stdinpipe = os.pipe()
stdoutpipe = os.pipe()

def preexec():
os.close(stdinpipe[1])
os.close(stdoutpipe[0])

proc = subprocess.Popen([sys.executable, '-Bm', 'cmake_format', '-'],
stdin=stdinpipe[0], stdout=stdoutpipe[1],
cwd=self.tempdir, env=self.env, preexec_fn=preexec)
os.close(stdinpipe[0])
os.close(stdoutpipe[1])

with io.open(infile_path, 'r', encoding='utf-8') as infile:
with io.open(stdinpipe[1], 'w', encoding='utf-8') as outfile:
for line in infile:
outfile.write(line)

with io.open(stdoutpipe[0], 'r', encoding='utf-8') as infile:
actual_text = infile.read()

proc.wait()

with io.open(expectfile_path, 'r', encoding='utf8') as infile:
expected_text = infile.read()

delta_lines = list(difflib.unified_diff(actual_text.split('\n'),
expected_text.split('\n')))
if delta_lines:
raise AssertionError('\n'.join(delta_lines[2:]))


if __name__ == '__main__':
unittest.main()
13 changes: 11 additions & 2 deletions cmake_format/pypi/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@
from setuptools import setup

GITHUB_URL = 'https://github.com/cheshirekow/cmake_format'
VERSION = '0.3.5'

with io.open('README.rst', encoding='utf8') as infile:
VERSION = None
with io.open('cmake_format/__init__.py', encoding='utf-8') as infile:
for line in infile:
line = line.strip()
if line.startswith('VERSION ='):
VERSION = line.split('=', 1)[1].strip().strip("'")

assert VERSION is not None


with io.open('README.rst', encoding='utf-8') as infile:
long_description = infile.read()

setup(
Expand Down

0 comments on commit 2e2aff2

Please sign in to comment.