diff --git a/cmake_format/BUILD b/cmake_format/BUILD
index 6ef457d..7bd3806 100644
--- a/cmake_format/BUILD
+++ b/cmake_format/BUILD
@@ -3,49 +3,48 @@ package(default_visibility=["//visibility:public"])
py_library(
name="cmake_format",
srcs=[
- "doc/gendoc_sources.py",
- "doc/conf.py",
- "screw_users_test.py",
+ "__init__.py",
"__main__.py",
- "parser_tests.py",
+ "annotate.py",
+ "commands.py",
"common.py",
- "markup_tests.py",
- "format_tests.py",
- "layout_tests.py",
- "invocation_tests.py",
- "__init__.py",
- "formatter.py",
"configuration.py",
- "command_tests/file_tests.py",
- "command_tests/add_library_tests.py",
- "command_tests/install_tests.py",
- "command_tests/add_executable_tests.py",
- "command_tests/__init__.py",
- "command_tests/export_tests.py",
- "command_tests/conditional_tests.py",
- "command_tests/set_tests.py",
- "command_tests/add_custom_command_tests.py",
+ "doc/conf.py",
+ "doc/gendoc_sources.py",
+ "formatter.py",
+ "invocation_tests.py",
+ "layout_tests.py",
+ "lexer.py",
+ "lexer_tests.py",
+ "markup.py",
+ "markup_tests.py",
+ "parse_funs/add_executable.py",
+ "parse_funs/add_library.py",
+ "parse_funs/add_xxx.py",
"parse_funs/external_project.py",
+ "parse_funs/fetch_content.py",
"parse_funs/file.py",
- "parse_funs/add_library.py",
"parse_funs/__init__.py",
- "parse_funs/add_executable.py",
- "parse_funs/add_xxx.py",
"parse_funs/install.py",
- "parse_funs/fetch_content.py",
- "tests.py",
- "commands.py",
+ "parser.py",
+ "parser_tests.py",
"pypi/setup.py",
"render.py",
- "parser.py",
- "lexer_tests.py",
+ "screw_users_test.py",
"test/cmake-format.py",
- "lexer.py",
- "annotate.py",
- "markup.py"],
+ "test/cmake-format-split-1.py",
+ "test/cmake-format-split-2.py",
+ "tests.py"],
data=["templates/layout.html.tpl",
"templates/style.css"])
+py_library(
+ name="testdata",
+ data=glob(["test/*"]),
+)
+
+# -- Python 2 --
+
py_binary(
name="cmake-format",
srcs=["__main__.py"],
@@ -53,19 +52,10 @@ py_binary(
main="__main__.py"
)
-py_test(
- name="format_tests",
- srcs=["format_tests.py"],
- deps=[":cmake_format"],
- data=glob(["test/*"]),
- python_version="PY2",
- )
-
py_test(
name="invocation_tests",
srcs=["invocation_tests.py"],
- deps=[":cmake_format"],
- data=glob(["test/*"]),
+ deps=[":cmake_format", ":testdata"],
python_version="PY2",
)
@@ -97,22 +87,13 @@ py_test(
python_version="PY2",
)
-
-py_test(
- name="format_tests_py3",
- srcs=["format_tests.py"],
- main="format_tests.py",
- deps=[":cmake_format"],
- data=glob(["test/*"]),
- python_version="PY3",
- )
+# -- Python 3 --
py_test(
name="invocation_tests_py3",
srcs=["invocation_tests.py"],
main="invocation_tests.py",
- deps=[":cmake_format"],
- data=glob(["test/*"]),
+ deps=[":cmake_format", ":testdata"],
python_version="PY3",
)
diff --git a/cmake_format/CMakeLists.txt b/cmake_format/CMakeLists.txt
index d2e452b..cd5afd9 100644
--- a/cmake_format/CMakeLists.txt
+++ b/cmake_format/CMakeLists.txt
@@ -1,84 +1,72 @@
-format_and_lint(cmake_format
- # cmake-format: sort
- __init__.py
- __main__.py
- annotate.py
- commands.py
- command_tests/add_custom_command_tests.py
- command_tests/add_executable_tests.py
- command_tests/add_library_tests.py
- command_tests/conditional_tests.py
- command_tests/export_tests.py
- command_tests/file_tests.py
- command_tests/__init__.py
- command_tests/install_tests.py
- command_tests/set_tests.py
- common.py
- configuration.py
- doc/conf.py
- doc/gendoc_sources.py
- formatter.py
- format_tests.py
- invocation_tests.py
- layout_tests.py
- lexer.py
- lexer_tests.py
- markup.py
- markup_tests.py
- parse_funs/add_executable.py
- parse_funs/add_library.py
- parse_funs/add_xxx.py
- parse_funs/external_project.py
- parse_funs/fetch_content.py
- parse_funs/file.py
- parse_funs/__init__.py
- parse_funs/install.py
- parser.py
- parser_tests.py
- pypi/setup.py
- render.py
- screw_users_test.py
- test/cmake-format.py
- tests.py)
+format_and_lint(
+ cmake_format
+ # cmake-format: sort
+ __init__.py
+ __main__.py
+ annotate.py
+ commands.py
+ command_tests/add_custom_command_tests.py
+ command_tests/add_executable_tests.py
+ command_tests/add_library_tests.py
+ command_tests/conditional_tests.py
+ command_tests/export_tests.py
+ command_tests/file_tests.py
+ command_tests/__init__.py
+ command_tests/install_tests.py
+ command_tests/__main__.py
+ command_tests/misc_tests.py
+ command_tests/set_tests.py
+ common.py
+ configuration.py
+ doc/conf.py
+ doc/gendoc_sources.py
+ formatter.py
+ invocation_tests.py
+ layout_tests.py
+ lexer.py
+ lexer_tests.py
+ markup.py
+ markup_tests.py
+ parse_funs/add_executable.py
+ parse_funs/add_library.py
+ parse_funs/add_xxx.py
+ parse_funs/external_project.py
+ parse_funs/fetch_content.py
+ parse_funs/file.py
+ parse_funs/__init__.py
+ parse_funs/install.py
+ parser.py
+ parser_tests.py
+ pypi/setup.py
+ render.py
+ screw_users_test.py
+ test/cmake-format.py
+ test/cmake-format-split-1.py
+ test/cmake-format-split-2.py
+ tests.py)
-add_test(NAME cmake_format-format_tests
- COMMAND python -m cmake_format.format_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
-add_test(NAME cmake_format-invocation_tests
- COMMAND python -m cmake_format.invocation_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
-add_test(NAME cmake_format-layout_tests
- COMMAND python -m cmake_format.layout_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
-add_test(NAME cmake_format-lexer_tests
- COMMAND python -m cmake_format.lexer_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
-add_test(NAME cmake_format-markup_tests
- COMMAND python -m cmake_format.markup_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
-add_test(NAME cmake_format-parser_tests
- COMMAND python -m cmake_format.parser_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
+set(
+ _testnames
+ invocation_tests
+ layout_tests
+ lexer_tests
+ markup_tests
+ parser_tests)
+
+foreach(_testname ${_testnames})
+ add_test(
+ NAME cmake_format-${_testname}
+ COMMAND python -Bm cmake_format.${_testname}
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
+endforeach()
if(NOT IS_TRAVIS_CI)
- add_test(NAME cmake_format-format_tests_py3
- COMMAND python3 -m cmake_format.format_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
- add_test(NAME cmake_format-invocation_tests_py3
- COMMAND python3 -m cmake_format.invocation_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
- add_test(NAME cmake_format-layout_tests_py3
- COMMAND python3 -m cmake_format.layout_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
- add_test(NAME cmake_format-lexer_tests_py3
- COMMAND python3 -m cmake_format.lexer_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
- add_test(NAME cmake_format-markup_tests_py3
- COMMAND python3 -m cmake_format.markup_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
- add_test(NAME cmake_format-parser_tests_py3
- COMMAND python3 -m cmake_format.parser_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
+ foreach(_testname ${_testnames})
+ add_test(
+ NAME cmake_format-${_testname}_py3
+ COMMAND python3 -Bm cmake_format.${_testname}
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
+ endforeach()
endif()
add_subdirectory(doc)
diff --git a/cmake_format/__init__.py b/cmake_format/__init__.py
index f3ec111..5bed871 100644
--- a/cmake_format/__init__.py
+++ b/cmake_format/__init__.py
@@ -3,4 +3,4 @@
"""
from __future__ import unicode_literals
-VERSION = '0.5.5'
+VERSION = '0.6.0'
diff --git a/cmake_format/__main__.py b/cmake_format/__main__.py
index 943ed6d..f37dc22 100644
--- a/cmake_format/__main__.py
+++ b/cmake_format/__main__.py
@@ -421,7 +421,7 @@ def setup_argparser(arg_parser):
'Default is stdout.')
arg_parser.add_argument(
- '-c', '--config-file', '--config-files', nargs='+',
+ '-c', '--config-file', '--config-files', '--config', nargs='+',
help='path to configuration file(s)')
arg_parser.add_argument('infilepaths', nargs='*')
add_config_options(arg_parser)
diff --git a/cmake_format/command_tests/BUILD b/cmake_format/command_tests/BUILD
new file mode 100644
index 0000000..3293020
--- /dev/null
+++ b/cmake_format/command_tests/BUILD
@@ -0,0 +1,163 @@
+package(default_visibility=["//visibility:public"])
+
+py_library(
+ name="command_tests",
+ srcs=[
+ "__init__.py",
+ "add_custom_command_tests.py",
+ "add_executable_tests.py",
+ "add_library_tests.py",
+ "conditional_tests.py",
+ "export_tests.py",
+ "file_tests.py",
+ "install_tests.py",
+ "misc_tests.py",
+ "set_tests.py"],
+ data=[
+ "conditional_tests.cmake",
+ "misc_tests.cmake"])
+
+# -- Python 2 --
+
+py_test(
+ name="add_custom_command_tests",
+ srcs=["add_custom_command_tests.py"],
+ deps=["//cmake_format:cmake_format", ":command_tests"],
+ python_version="PY2",
+ )
+
+py_test(
+ name="add_executable_tests",
+ srcs=["add_executable_tests.py"],
+ deps=["//cmake_format:cmake_format", ":command_tests"],
+ python_version="PY2",
+ )
+
+py_test(
+ name="add_library_tests",
+ srcs=["add_library_tests.py"],
+ deps=["//cmake_format:cmake_format", ":command_tests"],
+ python_version="PY2",
+ )
+
+py_test(
+ name="conditional_tests",
+ srcs=["conditional_tests.py"],
+ deps=["//cmake_format:cmake_format", ":command_tests"],
+ python_version="PY2",
+ )
+
+py_test(
+ name="export_tests",
+ srcs=["export_tests.py"],
+ deps=["//cmake_format:cmake_format", ":command_tests"],
+ python_version="PY2",
+ )
+
+py_test(
+ name="file_tests",
+ srcs=["file_tests.py"],
+ deps=["//cmake_format:cmake_format", ":command_tests"],
+ python_version="PY2",
+ )
+
+py_test(
+ name="install_tests",
+ srcs=["install_tests.py"],
+ deps=["//cmake_format:cmake_format", ":command_tests"],
+ python_version="PY2",
+ )
+
+py_test(
+ name="misc_tests",
+ srcs=["misc_tests.py"],
+ deps=[
+ "//cmake_format:cmake_format",
+ "//cmake_format:testdata",
+ ":command_tests"],
+ python_version="PY2",
+ )
+
+py_test(
+ name="set_tests",
+ srcs=["set_tests.py"],
+ deps=["//cmake_format:cmake_format", ":command_tests"],
+ python_version="PY2",
+ )
+
+# -- Python 3 --
+
+py_test(
+ name="add_custom_command_tests_py3",
+ srcs=["add_custom_command_tests.py"],
+ main="add_custom_command_tests.py",
+ deps=["//cmake_format:cmake_format", ":command_tests"],
+ python_version="PY3",
+ )
+
+py_test(
+ name="add_executable_tests_py3",
+ srcs=["add_executable_tests.py"],
+ main="add_executable_tests.py",
+ deps=["//cmake_format:cmake_format", ":command_tests"],
+ python_version="PY3",
+ )
+
+py_test(
+ name="add_library_tests_py3",
+ srcs=["add_library_tests.py"],
+ main="add_library_tests.py",
+ deps=["//cmake_format:cmake_format", ":command_tests"],
+ python_version="PY3",
+ )
+
+py_test(
+ name="conditional_tests_py3",
+ srcs=["conditional_tests.py"],
+ main="conditional_tests.py",
+ deps=["//cmake_format:cmake_format", ":command_tests"],
+ python_version="PY3",
+ )
+
+py_test(
+ name="export_tests_py3",
+ srcs=["export_tests.py"],
+ main="export_tests.py",
+ deps=["//cmake_format:cmake_format", ":command_tests"],
+ python_version="PY3",
+ )
+
+py_test(
+ name="file_tests_py3",
+ srcs=["file_tests.py"],
+ main="file_tests.py",
+ deps=["//cmake_format:cmake_format", ":command_tests"],
+ python_version="PY3",
+ )
+
+py_test(
+ name="install_tests_py3",
+ srcs=["install_tests.py"],
+ main="install_tests.py",
+ deps=["//cmake_format:cmake_format", ":command_tests"],
+ python_version="PY3",
+ )
+
+py_test(
+ name="misc_tests_py3",
+ srcs=["misc_tests.py"],
+ main="misc_tests.py",
+ deps=[
+ "//cmake_format:cmake_format",
+ "//cmake_format:testdata",
+ ":command_tests"],
+ python_version="PY3",
+ )
+
+py_test(
+ name="set_tests_py3",
+ srcs=["set_tests.py"],
+ main="set_tests.py",
+ deps=["//cmake_format:cmake_format", ":command_tests"],
+ python_version="PY3",
+ )
\ No newline at end of file
diff --git a/cmake_format/command_tests/CMakeLists.txt b/cmake_format/command_tests/CMakeLists.txt
index 1c2ceb2..ea1e6b0 100644
--- a/cmake_format/command_tests/CMakeLists.txt
+++ b/cmake_format/command_tests/CMakeLists.txt
@@ -1,53 +1,30 @@
set(MODPREFIX cmake_format.command_tests)
-add_test(NAME cmake_format-add_custom_command_tests
- COMMAND python -m ${MODPREFIX}.add_custom_command_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
-add_test(NAME cmake_format-add_executable_tests
- COMMAND python -m ${MODPREFIX}.add_executable_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
-add_test(NAME cmake_format-add_library_tests
- COMMAND python -m ${MODPREFIX}.add_library_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
-add_test(NAME cmake_format-conditional_tests
- COMMAND python -m ${MODPREFIX}.conditional_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
-add_test(NAME cmake_format-export_tests
- COMMAND python -m ${MODPREFIX}.export_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
-add_test(NAME cmake_format-file_tests
- COMMAND python -m ${MODPREFIX}.file_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
-add_test(NAME cmake_format-install_tests
- COMMAND python -m ${MODPREFIX}.install_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
-add_test(NAME cmake_format-set_tests
- COMMAND python -m ${MODPREFIX}.set_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
+set(
+ _testnames
+ add_custom_command_tests
+ add_executable_tests
+ add_library_tests
+ conditional_tests
+ export_tests
+ file_tests
+ install_tests
+ misc_tests
+ set_tests
+)
+
+foreach(_testname ${_testnames})
+ add_test(
+ NAME cmake_format-${_testname}
+ COMMAND python -Bm ${MODPREFIX}.${_testname}
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
+endforeach()
if(NOT IS_TRAVIS_CI)
- add_test(NAME cmake_format-add_custom_command_tests_py3
- COMMAND python3 -m ${MODPREFIX}.add_custom_command_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
- add_test(NAME cmake_format-add_executable_tests_py3
- COMMAND python3 -m ${MODPREFIX}.add_executable_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
- add_test(NAME cmake_format-add_library_tests_py3
- COMMAND python3 -m ${MODPREFIX}.add_library_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
- add_test(NAME cmake_format-conditional_tests_py3
- COMMAND python3 -m ${MODPREFIX}.conditional_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
- add_test(NAME cmake_format-export_tests_py3
- COMMAND python3 -m ${MODPREFIX}.export_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
- add_test(NAME cmake_format-file_tests_py3
- COMMAND python3 -m ${MODPREFIX}.file_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
- add_test(NAME cmake_format-install_tests_py3
- COMMAND python3 -m ${MODPREFIX}.install_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
- add_test(NAME cmake_format-set_tests_py3
- COMMAND python3 -m ${MODPREFIX}.set_tests
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
+ foreach(_testname ${_testnames})
+ add_test(
+ NAME cmake_format-${_testname}_py3
+ COMMAND python -Bm ${MODPREFIX}.${_testname}
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
+ endforeach()
endif()
diff --git a/cmake_format/command_tests/__init__.py b/cmake_format/command_tests/__init__.py
index ca665b0..87c2dc0 100644
--- a/cmake_format/command_tests/__init__.py
+++ b/cmake_format/command_tests/__init__.py
@@ -2,8 +2,12 @@
# pylint: disable=R1708
from __future__ import unicode_literals
+import contextlib
import difflib
+import functools
+import inspect
import io
+import os
import sys
import unittest
@@ -15,6 +19,86 @@
from cmake_format import parse_funs
from cmake_format.parser import NodeType
+# NOTE(josh): backport from functools.py in python 3.6 so that we can use it in
+# python 2.7
+if sys.version_info < (3, 5, 0):
+ # pylint: disable=all
+ class partialmethod(object):
+ """Method descriptor with partial application of the given arguments
+ and keywords.
+
+ Supports wrapping existing descriptors and handles non-descriptor
+ callables as instance methods.
+ """
+
+ def __init__(self, func, *args, **keywords):
+ if not callable(func) and not hasattr(func, "__get__"):
+ raise TypeError("{!r} is not callable or a descriptor"
+ .format(func))
+
+ # func could be a descriptor like classmethod which isn't callable,
+ # so we can't inherit from partial (it verifies func is callable)
+ if isinstance(func, partialmethod):
+ # flattening is mandatory in order to place cls/self before all
+ # other arguments
+ # it's also more efficient since only one function will be called
+ self.func = func.func
+ self.args = func.args + args
+ self.keywords = func.keywords.copy()
+ self.keywords.update(keywords)
+ else:
+ self.func = func
+ self.args = args
+ self.keywords = keywords
+
+ def __repr__(self):
+ args = ", ".join(map(repr, self.args))
+ keywords = ", ".join("{}={!r}".format(k, v)
+ for k, v in self.keywords.items())
+ format_string = "{module}.{cls}({func}, {args}, {keywords})"
+ return format_string.format(module=self.__class__.__module__,
+ cls=self.__class__.__qualname__,
+ func=self.func,
+ args=args,
+ keywords=keywords)
+
+ def _make_unbound_method(self):
+ def _method(*args, **keywords):
+ call_keywords = self.keywords.copy()
+ call_keywords.update(keywords)
+ cls_or_self = args[0]
+ rest = args[:]
+ call_args = (cls_or_self,) + self.args + tuple(rest)
+ return self.func(*call_args, **call_keywords)
+ _method.__isabstractmethod__ = self.__isabstractmethod__
+ _method._partialmethod = self
+ return _method
+
+ def __get__(self, obj, cls):
+ get = getattr(self.func, "__get__", None)
+ result = None
+ if get is not None:
+ new_func = get(obj, cls)
+ if new_func is not self.func:
+ # Assume __get__ returning something new indicates the
+ # creation of an appropriate callable
+ result = functools.partial(new_func, *self.args, **self.keywords)
+ try:
+ result.__self__ = new_func.__self__
+ except AttributeError:
+ pass
+ if result is None:
+ # If the underlying descriptor didn't do anything, treat this
+ # like an instance method
+ result = self._make_unbound_method().__get__(obj, cls)
+ return result
+
+ @property
+ def __isabstractmethod__(self):
+ return getattr(self.func, "__isabstractmethod__", False)
+else:
+ from functools import partialmethod
+
def strip_indent(content, indent=6):
"""
@@ -150,7 +234,7 @@ def assert_layout_tree(test, nodes, tups, tree=None, history=None):
assert_layout_tree(test, node.children, expect_children, tree, subhistory)
-def assert_layout(test, input_str, expect_tree, strip_len=6):
+def assert_layout(test, input_str, expect_tree, strip_len=0):
"""
Run the formatter on the input string and assert that the result matches
the output string
@@ -163,11 +247,13 @@ def assert_layout(test, input_str, expect_tree, strip_len=6):
assert_layout_tree(test, [box_tree], expect_tree)
-def assert_format(test, input_str, output_str, strip_len=0):
+def assert_format(test, input_str, output_str=None, strip_len=0):
"""
Run the formatter on the input string and assert that the result matches
the output string
"""
+ if output_str is None:
+ output_str = input_str
input_str = strip_indent(input_str, strip_len)
output_str = strip_indent(output_str, strip_len)
@@ -217,6 +303,37 @@ class TestBase(unittest.TestCase):
Given a bunch of example usages of a particular command, ensure that they
lex, parse, layout, and format the same as expected.
"""
+ kNumSidecarTests = 0
+
+ @classmethod
+ def load_sidecar_tests(cls):
+ cmake_sidecar = inspect.getfile(cls)[:-3] + ".cmake"
+ if not os.path.exists(cmake_sidecar):
+ return
+ with io.open(cmake_sidecar, "r", encoding="utf-8") as infile:
+ lines = infile.read().split("\n")
+
+ test_name = None
+ line_buffer = []
+ num_sidecar_tests = 0
+
+ for lineno, line in enumerate(lines):
+ if line.startswith("# cmftest-begin: "):
+ test_name = line[17:]
+ line_buffer = []
+ elif line.endswith("# cmftest-end"):
+ if test_name is None:
+ raise ValueError(
+ "Malformed sidecar {}:{}".format(cmake_sidecar, lineno))
+ test_content = "\n".join(line_buffer) + "\n"
+ closure = partialmethod(assert_format, test_content)
+ setattr(cls, test_name, closure)
+ num_sidecar_tests += 1
+ test_name = None
+ line_buffer = []
+ else:
+ line_buffer.append(line)
+ setattr(cls, "kNumSidecarTests", num_sidecar_tests)
def __init__(self, *args, **kwargs):
super(TestBase, self).__init__(*args, **kwargs)
@@ -250,34 +367,28 @@ def setUp(self):
self.parse_db.update(
parse_funs.get_legacy_parse(self.config.fn_spec).kwargs)
- for name, value in vars(self).items():
- if callable(value) and name.startswith("test_"):
- setattr(self, name, WrapTestWithRunFun(self, value))
+ @contextlib.contextmanager
+ def subTest(self, msg=None, **params):
+ # pylint: disable=no-member
+ if sys.version_info < (3, 4, 0):
+ yield None
+ else:
+ yield super(TestBase, self).subTest(msg=msg, **params)
def assertExpectations(self):
# Empty source_str is shorthand for "assertInvariant"
if self.source_str is None:
self.source_str = self.expect_format
- if sys.version_info < (3, 0, 0):
- if self.expect_lex is not None:
+ if self.expect_lex is not None:
+ with self.subTest(phase="lex"): # pylint: disable=no-member
assert_lex(self, self.source_str, self.expect_lex)
- if self.expect_parse is not None:
+ if self.expect_parse is not None:
+ with self.subTest(phase="parse"): # pylint: disable=no-member
assert_parse(self, self.source_str, self.expect_parse)
- if self.expect_layout is not None:
+ if self.expect_layout is not None:
+ with self.subTest(phase="layout"): # pylint: disable=no-member
assert_layout(self, self.source_str, self.expect_layout)
- if self.expect_format is not None:
+ if self.expect_format is not None:
+ with self.subTest(phase="format"): # pylint: disable=no-member
assert_format(self, self.source_str, self.expect_format)
- else:
- if self.expect_lex is not None:
- with self.subTest(phase="lex"): # pylint: disable=no-member
- assert_lex(self, self.source_str, self.expect_lex)
- if self.expect_parse is not None:
- with self.subTest(phase="parse"): # pylint: disable=no-member
- assert_parse(self, self.source_str, self.expect_parse)
- if self.expect_layout is not None:
- with self.subTest(phase="layout"): # pylint: disable=no-member
- assert_layout(self, self.source_str, self.expect_layout)
- if self.expect_format is not None:
- with self.subTest(phase="format"): # pylint: disable=no-member
- assert_format(self, self.source_str, self.expect_format)
diff --git a/cmake_format/command_tests/__main__.py b/cmake_format/command_tests/__main__.py
new file mode 100644
index 0000000..6347291
--- /dev/null
+++ b/cmake_format/command_tests/__main__.py
@@ -0,0 +1,29 @@
+import unittest
+
+# pylint: disable=unused-import
+from cmake_format.command_tests.add_custom_command_tests \
+ import TestAddCustomCommand
+from cmake_format.command_tests.add_executable_tests \
+ import TestAddExecutableCommand
+from cmake_format.command_tests.add_library_tests \
+ import TestAddLibraryCommand
+from cmake_format.command_tests.conditional_tests \
+ import TestConditionalCommands
+from cmake_format.command_tests.export_tests \
+ import TestExportCommand
+from cmake_format.command_tests.file_tests \
+ import TestFileCommands
+from cmake_format.command_tests.install_tests \
+ import TestInstallCommands
+from cmake_format.command_tests.misc_tests \
+ import TestMiscFormatting
+from cmake_format.command_tests.set_tests \
+ import TestSetCommand
+
+
+def main():
+ unittest.main()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/cmake_format/command_tests/add_custom_command_tests.py b/cmake_format/command_tests/add_custom_command_tests.py
index f11bfe3..d40582f 100644
--- a/cmake_format/command_tests/add_custom_command_tests.py
+++ b/cmake_format/command_tests/add_custom_command_tests.py
@@ -28,19 +28,23 @@ def test_single_argument(self):
]),
(NodeType.KWARGGROUP, [
(NodeType.KEYWORD, []),
- (NodeType.PARGGROUP, [
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
+ (NodeType.ARGGROUP, [
+ (NodeType.PARGGROUP, [
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ ]),
]),
]),
(NodeType.KWARGGROUP, [
(NodeType.KEYWORD, []),
- (NodeType.PARGGROUP, [
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
+ (NodeType.ARGGROUP, [
+ (NodeType.PARGGROUP, [
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ ]),
]),
]),
(NodeType.KWARGGROUP, [
@@ -63,12 +67,13 @@ def test_single_argument(self):
]
self.expect_format = """\
-add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/foobar_doc.stamp
- COMMAND sphinx-build -M html ${CMAKE_CURRENT_SOURCE_DIR}
- ${CMAKE_CURRENT_BINARY_DIR}
- COMMAND touch ${CMAKE_CURRENT_BINARY_DIR}/foobar_doc.stamp
- DEPENDS ${foobar_docs}
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
+add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/foobar_doc.stamp
+ COMMAND sphinx-build -M html ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_BINARY_DIR}
+ COMMAND touch ${CMAKE_CURRENT_BINARY_DIR}/foobar_doc.stamp
+ DEPENDS ${foobar_docs}
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
"""
diff --git a/cmake_format/command_tests/add_executable_tests.py b/cmake_format/command_tests/add_executable_tests.py
index 4b10da7..0a1f57c 100644
--- a/cmake_format/command_tests/add_executable_tests.py
+++ b/cmake_format/command_tests/add_executable_tests.py
@@ -74,45 +74,30 @@ def test_all_arguments(self):
]
self.expect_format = """\
-add_executable(foobar WIN32 EXCLUDE_FROM_ALL
- sourcefile_01.cc
- sourcefile_02.cc
- sourcefile_03.cc
- sourcefile_04.cc
- sourcefile_05.cc
- sourcefile_06.cc
- sourcefile_07.cc)
+add_executable(
+ foobar WIN32 EXCLUDE_FROM_ALL
+ sourcefile_01.cc
+ sourcefile_02.cc
+ sourcefile_03.cc
+ sourcefile_04.cc
+ sourcefile_05.cc
+ sourcefile_06.cc
+ sourcefile_07.cc)
"""
def test_sort_arguments(self):
self.config.autosort = True
- self.source_str = """\
-add_executable(foobar WIN32 sourcefile_04.cc
- sourcefile_03.cc sourcefile_01.cc sourcefile_02.cc)
-"""
-
self.expect_format = """\
-add_executable(foobar WIN32
- sourcefile_01.cc
- sourcefile_02.cc
- sourcefile_03.cc
- sourcefile_04.cc)
+add_executable(foobar WIN32 sourcefile_01.cc sourcefile_02.cc sourcefile_03.cc
+ sourcefile_04.cc)
"""
def test_disable_autosort_with_tag(self):
self.config.autosort = True
- self.source_str = """\
-add_executable(foobar WIN32 # cmake-format: unsort
- sourcefile_04.cc sourcefile_03.cc sourcefile_01.cc sourcefile_02.cc)
-"""
-
self.expect_format = """\
-add_executable(foobar WIN32
- # cmake-format: unsort
- sourcefile_04.cc
- sourcefile_03.cc
- sourcefile_01.cc
- sourcefile_02.cc)
+add_executable(
+ foobar WIN32 # cmake-format: unsort
+ sourcefile_04.cc sourcefile_03.cc sourcefile_01.cc sourcefile_02.cc)
"""
def test_imported_form(self):
diff --git a/cmake_format/command_tests/add_library_tests.py b/cmake_format/command_tests/add_library_tests.py
index c10f813..7c41ee7 100644
--- a/cmake_format/command_tests/add_library_tests.py
+++ b/cmake_format/command_tests/add_library_tests.py
@@ -74,14 +74,15 @@ def test_all_arguments(self):
]
self.expect_format = """\
-add_library(foobar STATIC EXCLUDE_FROM_ALL
- sourcefile_01.cc
- sourcefile_02.cc
- sourcefile_03.cc
- sourcefile_04.cc
- sourcefile_05.cc
- sourcefile_06.cc
- sourcefile_07.cc)
+add_library(
+ foobar STATIC EXCLUDE_FROM_ALL
+ sourcefile_01.cc
+ sourcefile_02.cc
+ sourcefile_03.cc
+ sourcefile_04.cc
+ sourcefile_05.cc
+ sourcefile_06.cc
+ sourcefile_07.cc)
"""
def test_parse_with_concluding_comments(self):
@@ -121,11 +122,8 @@ def test_sort_arguments(self):
"""
self.expect_format = """\
-add_library(foobar STATIC
- sourcefile_01.cc
- sourcefile_02.cc
- sourcefile_03.cc
- sourcefile_04.cc)
+add_library(foobar STATIC sourcefile_01.cc sourcefile_02.cc sourcefile_03.cc
+ sourcefile_04.cc)
"""
def test_disable_autosort_with_tag(self):
@@ -136,12 +134,8 @@ def test_disable_autosort_with_tag(self):
"""
self.expect_format = """\
-add_library(foobar STATIC
- # cmake-format: unsort
- sourcefile_04.cc
- sourcefile_03.cc
- sourcefile_01.cc
- sourcefile_02.cc)
+add_library(foobar STATIC # cmake-format: unsort
+ sourcefile_04.cc sourcefile_03.cc sourcefile_01.cc sourcefile_02.cc)
"""
def test_imported_form(self):
diff --git a/cmake_format/command_tests/conditional_tests.cmake b/cmake_format/command_tests/conditional_tests.cmake
new file mode 100644
index 0000000..acceb1a
--- /dev/null
+++ b/cmake_format/command_tests/conditional_tests.cmake
@@ -0,0 +1,45 @@
+# cmftest-begin: test_complicated_boolean
+set(matchme
+ "_DATA_\\|_CMAKE_\\|INTRA_PRED\\|_COMPILED\\|_HOSTING\\|_PERF_\\|CODER_")
+if(("${var}" MATCHES "_TEST_" AND NOT "${var}" MATCHES "${matchme}")
+ OR (CONFIG_AV1_ENCODER
+ AND CONFIG_ENCODE_PERF_TESTS
+ AND "${var}" MATCHES "_ENCODE_PERF_TEST_")
+ OR (CONFIG_AV1_DECODER
+ AND CONFIG_DECODE_PERF_TESTS
+ AND "${var}" MATCHES "_DECODE_PERF_TEST_")
+ OR (CONFIG_AV1_ENCODER AND "${var}" MATCHES "_TEST_ENCODER_")
+ OR (CONFIG_AV1_DECODER AND "${var}" MATCHES "_TEST_DECODER_"))
+ list(APPEND aom_test_source_vars ${var})
+endif()
+# cmftest-end
+
+# cmftest-begin: test_less_complicated_boolean
+set(matchme
+ "_DATA_\\|_CMAKE_\\|INTRA_PRED\\|_COMPILED\\|_HOSTING\\|_PERF_\\|CODER_")
+if(("${var}" MATCHES "_TEST_" AND NOT "${var}" MATCHES "${matchme}")
+ OR (CONFIG_AV1_ENCODER
+ AND CONFIG_ENCODE_PERF_TESTS
+ AND "${var}" MATCHES "_ENCODE_PERF_TEST_"))
+ list(APPEND aom_test_source_vars ${var})
+endif()
+# cmftest-end
+
+# cmftest-begin: test_nested_parens
+if((NOT HELLO) OR (NOT EXISTS ${WORLD}))
+ message(WARNING "something is wrong")
+ set(foobar FALSE)
+endif()
+# cmftest-end
+
+# cmftest-begin: test_negated_single_nested_parens
+if(NOT ("" STREQUALS ""))
+ # pass
+endif()
+# cmftest-end
+
+# cmftest-begin: test_conditional_in_if_and_endif
+if(SOMETHING AND (NOT SOMETHING_ELSE STREQUAL ""))
+ # pass
+endif(SOMETHING AND (NOT SOMETHING_ELSE STREQUAL ""))
+# cmftest-end
diff --git a/cmake_format/command_tests/conditional_tests.py b/cmake_format/command_tests/conditional_tests.py
index 674e9f5..482e7bc 100644
--- a/cmake_format/command_tests/conditional_tests.py
+++ b/cmake_format/command_tests/conditional_tests.py
@@ -9,46 +9,16 @@ class TestConditionalCommands(TestBase):
"""
Test various examples of commands that take conditional statements
"""
+ kNumSidecarTests = 0
- def test_complicated_boolean(self):
- self.config.max_subargs_per_line = 10
- self.expect_format = """\
-set(matchme "_DATA_\\|_CMAKE_\\|INTRA_PRED\\|_COMPILED\\|_HOSTING\\|_PERF_\\|CODER_")
-if(("${var}" MATCHES "_TEST_" AND NOT "${var}" MATCHES "${matchme}")
- OR (CONFIG_AV1_ENCODER
- AND CONFIG_ENCODE_PERF_TESTS
- AND "${var}" MATCHES "_ENCODE_PERF_TEST_")
- OR (CONFIG_AV1_DECODER
- AND CONFIG_DECODE_PERF_TESTS
- AND "${var}" MATCHES "_DECODE_PERF_TEST_")
- OR (CONFIG_AV1_ENCODER AND "${var}" MATCHES "_TEST_ENCODER_")
- OR (CONFIG_AV1_DECODER AND "${var}" MATCHES "_TEST_DECODER_"))
- list(APPEND aom_test_source_vars ${var})
-endif()
-"""
+ def test_numsidecar(self):
+ """
+ Sanity check to makesure all sidecar tests are run.
+ """
+ self.assertEqual(5, self.kNumSidecarTests)
- def test_nested_parens(self):
- self.expect_format = """\
-if((NOT HELLO) OR (NOT EXISTS ${WORLD}))
- message(WARNING "something is wrong")
- set(foobar FALSE)
-endif()
-"""
-
- def test_negated_single_nested_parens(self):
- self.expect_format = """\
-if(NOT ("" STREQUALS ""))
- # pass
-endif()
-"""
-
- def test_conditional_in_if_and_endif(self):
- self.expect_format = """\
-if(SOMETHING AND (NOT SOMETHING_ELSE STREQUAL ""))
- # pass
-endif(SOMETHING AND (NOT SOMETHING_ELSE STREQUAL ""))
-"""
+TestConditionalCommands.load_sidecar_tests()
if __name__ == '__main__':
unittest.main()
diff --git a/cmake_format/command_tests/file_tests.py b/cmake_format/command_tests/file_tests.py
index 530738c..8dd71f6 100644
--- a/cmake_format/command_tests/file_tests.py
+++ b/cmake_format/command_tests/file_tests.py
@@ -46,28 +46,33 @@ def test_file_append(self):
"""
def test_file_generate_output(self):
+ # TODO(josh): "file content line three" should probably be on the next line
self.expect_format = """\
-file(GENERATE
- OUTPUT foobar.baz
- CONTENT "file content line one" #
- "file content line two"
- "file content line three"
- CONDITION (FOO AND BAR) OR BAZ)
+file(
+ GENERATE
+ OUTPUT foobar.baz
+ CONTENT "file content line one" #
+ "file content line two" "file content line three"
+ CONDITION (FOO AND BAR) OR BAZ)
"""
def test_file_glob(self):
self.expect_format = """\
-file(GLOB globout RELATIVE foo/bar/baz "*.py" "*.txt")
+file(
+ GLOB globout
+ RELATIVE foo/bar/baz
+ "*.py" "*.txt")
"""
def test_file_copy(self):
self.expect_format = r"""
-file(COPY foo bar baz
- DESTINATION foobar
- FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
- FILES_MATCHING
- PATTERN "*.h"
- REGEX ".*\\.cc")
+file(
+ COPY foo bar baz
+ DESTINATION foobar
+ FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
+ FILES_MATCHING
+ PATTERN "*.h"
+ REGEX ".*\\.cc")
"""[1:]
def test_file_write_a(self):
diff --git a/cmake_format/command_tests/install_tests.py b/cmake_format/command_tests/install_tests.py
index 016a487..26c26f4 100644
--- a/cmake_format/command_tests/install_tests.py
+++ b/cmake_format/command_tests/install_tests.py
@@ -97,11 +97,12 @@ def test_install_targets(self):
]
self.expect_format = """\
-install(TARGETS ${PROJECT_NAME}
- EXPORT ${CMAKE_PROJECT_NAME}Targets
- ARCHIVE DESTINATION lib COMPONENT install-app
- LIBRARY DESTINATION lib COMPONENT install-app
- RUNTIME DESTINATION bin COMPONENT install-app)
+install(
+ TARGETS ${PROJECT_NAME}
+ EXPORT ${CMAKE_PROJECT_NAME}Targets
+ ARCHIVE DESTINATION lib COMPONENT install-app
+ LIBRARY DESTINATION lib COMPONENT install-app
+ RUNTIME DESTINATION bin COMPONENT install-app)
"""
def test_kwarg_match_consumes(self):
@@ -109,8 +110,8 @@ def test_kwarg_match_consumes(self):
install(TARGETS myprog RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime)
"""
self.expect_format = """\
-install(TARGETS myprog
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime)
+install(TARGETS myprog RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ COMPONENT runtime)
"""
diff --git a/cmake_format/command_tests/misc_tests.cmake b/cmake_format/command_tests/misc_tests.cmake
new file mode 100644
index 0000000..440612a
--- /dev/null
+++ b/cmake_format/command_tests/misc_tests.cmake
@@ -0,0 +1,18 @@
+# cmftest-begin: test_nonargument_terminal_comments
+add_library(
+ foo
+ # This comment is not attached to an argument
+ bar.cc foo.cc)
+
+find_package(
+ foobar REQUIRED
+ COMPONENTS some_component # some_other_component
+ # This is a very long comment, and actually the second comment in
+ # this row.
+)
+# cmftest-end
+
+# cmftest-begin: test_arg_just_fits_two
+message(
+ FATAL_ERROR "81 character line ----------------------------------------")
+# cmftest-end
diff --git a/cmake_format/command_tests/misc_tests.py b/cmake_format/command_tests/misc_tests.py
new file mode 100644
index 0000000..99a5cc2
--- /dev/null
+++ b/cmake_format/command_tests/misc_tests.py
@@ -0,0 +1,1245 @@
+# -*- coding: utf-8 -*-
+# pylint: disable=bad-continuation
+# pylint: disable=too-many-lines
+from __future__ import unicode_literals
+
+import io
+import os
+import unittest
+
+from cmake_format import configuration
+from cmake_format.command_tests import assert_format, TestBase
+
+
+class TestMiscFormatting(TestBase):
+ """
+ Ensure that various inputs format the way we want them to
+ """
+
+ def test_numsidecar(self):
+ """
+ Sanity check to makesure all sidecar tests are run.
+ """
+ self.assertEqual(2, self.kNumSidecarTests)
+
+ def test_collapse_additional_newlines(self):
+ self.source_str = """\
+# The following multiple newlines should be collapsed into a single newline
+
+
+
+
+cmake_minimum_required(VERSION 2.8.11)
+project(cmake_format_test)
+"""
+
+ self.expect_format = """\
+# The following multiple newlines should be collapsed into a single newline
+
+cmake_minimum_required(VERSION 2.8.11)
+project(cmake_format_test)
+"""
+
+ def test_multiline_reflow(self):
+ self.source_str = """\
+# This multiline-comment should be reflowed
+# into a single comment
+# on one line
+"""
+ self.expect_format = """\
+# This multiline-comment should be reflowed into a single comment on one line
+"""
+
+ def test_comment_before_command(self):
+ self.expect_format = """\
+# This comment should remain right before the command call. Furthermore, the
+# command call should be formatted to a single line.
+add_subdirectories(foo bar baz foo2 bar2 baz2)
+"""
+
+ def test_long_args_command_split(self):
+ self.source_str = """\
+# This very long command should be split to multiple lines
+set(HEADERS very_long_header_name_a.h very_long_header_name_b.h very_long_header_name_c.h)
+"""
+ self.expect_format = """\
+# This very long command should be split to multiple lines
+set(HEADERS very_long_header_name_a.h very_long_header_name_b.h
+ very_long_header_name_c.h)
+"""
+
+ def test_lots_of_args_command_split(self):
+ self.source_str = """\
+# This command should be split into one line per entry because it has a long
+# argument list.
+set(SOURCES source_a.cc source_b.cc source_d.cc source_e.cc source_f.cc source_g.cc)
+"""
+ self.expect_format = """\
+# This command should be split into one line per entry because it has a long
+# argument list.
+set(SOURCES source_a.cc source_b.cc source_d.cc source_e.cc source_f.cc
+ source_g.cc)
+"""
+
+ def test_string_preserved_during_split(self):
+ self.source_str = """\
+# The string in this command should not be split
+set_target_properties(foo bar baz PROPERTIES COMPILE_FLAGS "-std=c++11 -Wall -Wextra")
+"""
+ self.expect_format = """\
+# The string in this command should not be split
+set_target_properties(foo bar baz PROPERTIES COMPILE_FLAGS
+ "-std=c++11 -Wall -Wextra")
+"""
+
+ def test_long_arg_on_newline(self):
+ self.source_str = """\
+# This command has a very long argument and can't be aligned with the command
+# end, so it should be moved to a new line with block indent + 1.
+some_long_command_name("Some very long argument that really needs to be on the next line.")
+"""
+ self.expect_format = """\
+# This command has a very long argument and can't be aligned with the command
+# end, so it should be moved to a new line with block indent + 1.
+some_long_command_name(
+ "Some very long argument that really needs to be on the next line.")
+"""
+
+ def test_long_kwargarg_on_newline(self):
+ self.source_str = """\
+# This situation is similar but the argument to a KWARG needs to be on a
+# newline instead.
+set(CMAKE_CXX_FLAGS "-std=c++11 -Wall -Wno-sign-compare -Wno-unused-parameter -xx")
+"""
+ self.expect_format = """\
+# This situation is similar but the argument to a KWARG needs to be on a newline
+# instead.
+set(CMAKE_CXX_FLAGS
+ "-std=c++11 -Wall -Wno-sign-compare -Wno-unused-parameter -xx")
+"""
+
+ def test_argcomment_preserved_and_reflowed(self):
+ self.expect_format = """\
+set(HEADERS
+ header_a.h header_b.h # This comment should be preserved, moreover it should
+ # be split across two lines.
+ header_c.h header_d.h)
+"""
+
+ def test_argcomments_force_reflow(self):
+ self.config.line_width = 140
+ self.source_str = """\
+cmake_parse_arguments(ARG
+ "SILENT" # optional keywords
+ "" # one value keywords
+ "" # multi value keywords
+ ${ARGN})
+"""
+ self.expect_format = """\
+cmake_parse_arguments(
+ ARG
+ "SILENT" # optional keywords
+ "" # one value keywords
+ "" # multi value keywords
+ ${ARGN})
+"""
+
+ def test_format_off(self):
+ self.source_str = """\
+# This part of the comment should
+# be formatted
+# but...
+# cmake-format: off
+# This bunny should remain untouched:
+# . _ ∩
+# レヘヽ| |
+# (・x・)
+# c( uu}
+# cmake-format: on
+# while this part should
+# be formatted again
+"""
+ self.expect_format = """\
+# This part of the comment should be formatted but...
+# cmake-format: off
+# This bunny should remain untouched:
+# . _ ∩
+# レヘヽ| |
+# (・x・)
+# c( uu}
+# cmake-format: on
+# while this part should be formatted again
+"""
+
+ def test_paragraphs_preserved(self):
+ self.source_str = """\
+# This is a paragraph
+#
+# This is a second paragraph
+#
+# This is a third paragraph
+"""
+ self.expect_format = """\
+# This is a paragraph
+#
+# This is a second paragraph
+#
+# This is a third paragraph
+"""
+
+ def test_todo_preserved(self):
+ self.source_str = """\
+# This is a comment
+# that should be joined but
+# TODO(josh): This todo should not be joined with the previous line.
+# NOTE(josh): Also this should not be joined with the todo.
+"""
+ self.expect_format = """\
+# This is a comment that should be joined but
+# TODO(josh): This todo should not be joined with the previous line.
+# NOTE(josh): Also this should not be joined with the todo.
+"""
+
+ def test_complex_nested_stuff(self):
+ self.config.autosort = False
+ self.expect_format = """\
+if(foo)
+ if(sbar)
+ # This comment is in-scope.
+ add_library(
+ foo_bar_baz
+ foo.cc bar.cc # this is a comment for arg2 this is more comment for arg2,
+ # it should be joined with the first.
+ baz.cc) # This comment is part of add_library
+
+ other_command(
+ some_long_argument some_long_argument) # this comment is very long and
+ # gets split across some lines
+
+ other_command(
+ some_long_argument some_long_argument some_long_argument) # this comment
+ # is even longer
+ # and wouldn't
+ # make sense to
+ # pack at the
+ # end of the
+ # command so it
+ # gets it's own
+ # lines
+ endif()
+endif()
+"""
+
+ def test_custom_command(self):
+ self.expect_format = """\
+# This very long command should be broken up along keyword arguments
+foo(nonkwarg_a nonkwarg_b
+ HEADERS a.h b.h c.h d.h e.h f.h
+ SOURCES a.cc b.cc d.cc
+ DEPENDS foo
+ bar baz)
+"""
+
+ def test_always_wrap(self):
+ self.source_str = """\
+foo(nonkwarg_a HEADERS a.h SOURCES a.cc DEPENDS foo)
+"""
+
+ with self.subTest(always_wrap=False):
+ # assert_format(self, self.source_str)
+ pass
+
+ self.config.always_wrap = ['foo']
+ with self.subTest(always_wrap=True):
+ assert_format(self, self.source_str, """\
+foo(nonkwarg_a
+ HEADERS a.h
+ SOURCES a.cc
+ DEPENDS foo)
+""")
+
+ def test_multiline_string(self):
+ self.expect_format = """\
+foo(some_arg some_arg "
+ This string is on multiple lines
+")
+"""
+
+ def test_some_string_stuff(self):
+ self.source_str = """\
+# This command uses a string with escaped quote chars
+foo(some_arg some_arg "This is a \\"string\\" within a string")
+
+# This command uses an empty string
+foo(some_arg some_arg "")
+
+# This command uses a multiline string
+foo(some_arg some_arg "
+ This string is on multiple lines
+")
+"""
+ self.expect_format = """\
+# This command uses a string with escaped quote chars
+foo(some_arg some_arg "This is a \\"string\\" within a string")
+
+# This command uses an empty string
+foo(some_arg some_arg "")
+
+# This command uses a multiline string
+foo(some_arg some_arg "
+ This string is on multiple lines
+")
+"""
+
+ def test_format_off_code(self):
+ self.source_str = """\
+# No, I really want this to look ugly
+# cmake-format: off
+add_library(a b.cc
+ c.cc d.cc
+ e.cc)
+# cmake-format: on
+"""
+ self.expect_format = """\
+# No, I really want this to look ugly
+# cmake-format: off
+add_library(a b.cc
+ c.cc d.cc
+ e.cc)
+# cmake-format: on
+"""
+
+ def test_multiline_statement_comment_idempotent(self):
+ self.source_str = """\
+set(HELLO hello world!) # TODO(josh): fix this bad code with some change that
+ # takes mutiple lines to explain
+"""
+ self.expect_format = """\
+set(HELLO hello world!) # TODO(josh): fix this bad code with some change that
+ # takes mutiple lines to explain
+"""
+
+ def test_function_def(self):
+ self.source_str = """\
+function(forbarbaz arg1)
+ do_something(arg1 ${ARGN})
+endfunction()
+"""
+ self.expect_format = """\
+function(forbarbaz arg1)
+ do_something(arg1 ${ARGN})
+endfunction()
+"""
+
+ def test_macro_def(self):
+ self.source_str = """\
+macro(forbarbaz arg1)
+ do_something(arg1 ${ARGN})
+endmacro()
+"""
+ self.expect_format = """\
+macro(forbarbaz arg1)
+ do_something(arg1 ${ARGN})
+endmacro()
+"""
+
+ def test_foreach(self):
+ self.config.max_subargs_per_line = 6
+ self.source_str = """\
+foreach(forbarbaz arg1 arg2 arg3)
+ message(hello ${foobarbaz})
+endforeach()
+"""
+ self.expect_format = """\
+foreach(forbarbaz arg1 arg2 arg3)
+ message(hello ${foobarbaz})
+endforeach()
+"""
+
+ def test_while(self):
+ self.config.max_subargs_per_line = 6
+ self.source_str = """\
+
+while(forbarbaz arg1 arg2 arg3)
+ message(hello ${foobarbaz})
+endwhile()
+"""
+ self.expect_format = """\
+while(forbarbaz arg1 arg2 arg3)
+ message(hello ${foobarbaz})
+endwhile()
+"""
+
+ def test_ctrl_space(self):
+ self.config.separate_ctrl_name_with_space = True
+ self.source_str = """\
+if(foo)
+ myfun(foo bar baz)
+endif()
+"""
+ self.expect_format = """\
+if (foo)
+ myfun(foo bar baz)
+endif ()
+"""
+
+ def test_fn_space(self):
+ self.config.separate_fn_name_with_space = True
+ self.source_str = """\
+myfun(foo bar baz)
+"""
+ self.expect_format = """\
+myfun (foo bar baz)
+"""
+
+ def test_preserve_separator(self):
+ self.source_str = """\
+# --------------------
+# This is some
+# text that I expect
+# to reflow
+# --------------------
+"""
+ self.expect_format = """\
+# --------------------
+# This is some text that I expect to reflow
+# --------------------
+"""
+
+ self.source_str = """\
+# !@#$^&*!@#$%^&*!@#$%^&*!@#$%^&*
+# This is some
+# text that I expect
+# to reflow
+# !@#$^&*!@#$%^&*!@#$%^&*!@#$%^&*
+"""
+ self.expect_format = """\
+# !@#$^&*!@#$%^&*!@#$%^&*!@#$%^&*
+# This is some text that I expect to reflow
+# !@#$^&*!@#$%^&*!@#$%^&*!@#$%^&*
+"""
+
+ self.source_str = """\
+# ----Not Supported----
+# This is some
+# text that I expect
+# to reflow
+# ----Not Supported----
+"""
+ self.expect_format = """\
+# ----Not Supported----
+# This is some text that I expect to reflow
+# ----Not Supported----
+"""
+
+ def test_bullets(self):
+ self.source_str = """\
+# This is a bulleted list:
+#
+# * item 1
+# * item 2
+# this line gets merged with item 2
+# * item 3 is really long and needs to be wrapped to a second line because it wont all fit on one line without wrapping.
+#
+# But the list has ended and this line is free. And
+# * this is not a bulleted list
+# * and it will be
+# * merged
+"""
+ self.expect_format = """\
+# This is a bulleted list:
+#
+# * item 1
+# * item 2 this line gets merged with item 2
+# * item 3 is really long and needs to be wrapped to a second line because it
+# wont all fit on one line without wrapping.
+#
+# But the list has ended and this line is free. And * this is not a bulleted
+# list * and it will be * merged
+"""
+
+ def test_enum_lists(self):
+ self.source_str = """\
+# This is a bulleted list:
+#
+# 1. item
+# 2. item
+# 3. item
+#
+# 4. item
+# 5. item
+# 6. item
+#
+# 1. item
+# 3. item
+# 5. item
+# 6. item
+# 6. item is really long and needs to be wrapped to a second line because it wont all fit on one line without wrapping.
+# 7. item
+# 9. item
+# 9. item
+# 9. item
+# 9. item
+# 9. item
+#
+"""
+ self.expect_format = """\
+# This is a bulleted list:
+#
+# 1. item
+# 2. item
+# 3. item
+#
+# 1. item
+# 2. item
+# 3. item
+#
+# 1. item
+# 2. item
+# 3. item
+# 4. item
+# 5. item is really long and needs to be wrapped to a second line because it wont
+# all fit on one line without wrapping.
+# 6. item
+# 7. item
+# 8. item
+# 9. item
+# 10. item
+# 11. item
+#
+"""
+
+ def test_nested_bullets(self):
+ self.source_str = """\
+# This is a bulleted list:
+#
+# * item 1
+# * item 2
+#
+# * item 3
+# * item 4
+#
+# * item 5
+# * item 6
+#
+# * item 7
+# * item 8
+"""
+ self.expect_format = """\
+# This is a bulleted list:
+#
+# * item 1
+# * item 2
+#
+# * item 3
+# * item 4
+#
+# * item 5
+# * item 6
+#
+# * item 7
+# * item 8
+"""
+
+ def test_comment_fence(self):
+ self.source_str = """\
+# ~~~~~~
+# This is some
+# verbatim text
+# that should not be
+# formatted
+# ```````
+"""
+ self.expect_format = """\
+# ~~~
+# This is some
+# verbatim text
+# that should not be
+# formatted
+# ~~~
+"""
+
+ def test_bracket_comments(self):
+
+ self.source_str = """\
+# [[This is a bracket comment.
+It is preserved verbatim, but trailing whitespace is removed.
+So things like --this-- Are fine:]]
+"""
+ self.expect_format = """\
+# [[This is a bracket comment.
+It is preserved verbatim, but trailing whitespace is removed.
+So things like --this-- Are fine:]]
+"""
+
+ self.source_str = """\
+if(foo)
+ # [==[This is a bracket comment at some nested level
+ # it is preserved verbatim, but trailing
+ # whitespace is removed.]==]
+endif()
+"""
+ self.expect_format = """\
+if(foo)
+ # [==[This is a bracket comment at some nested level
+ # it is preserved verbatim, but trailing
+ # whitespace is removed.]==]
+endif()
+"""
+
+ # Make sure bracket comments are kept inline in their function call
+ self.source_str = """\
+message("First Argument" #[[Bracket Comment]] "Second Argument")
+"""
+ self.expect_format = """\
+message("First Argument" #[[Bracket Comment]] "Second Argument")
+"""
+
+ def test_comment_after_command(self):
+ self.source_str = """\
+foo_command() # comment
+"""
+ self.expect_format = """\
+foo_command() # comment
+"""
+
+ self.source_str = """\
+foo_command() # this is a long comment that exceeds the desired page width and will be wrapped to a newline
+"""
+ self.expect_format = """\
+foo_command() # this is a long comment that exceeds the desired page width and
+ # will be wrapped to a newline
+"""
+
+ def test_arg_just_fits(self):
+ """
+ Ensure that if an argument *just* fits that it isn't superfluously wrapped
+"""
+
+ self.source_str = """\
+message(FATAL_ERROR "81 character line ----------------------------------------")
+"""
+ self.expect_format = """\
+message(
+ FATAL_ERROR "81 character line ----------------------------------------")
+"""
+ with self.subTest():
+ assert_format(self, self.source_str, self.expect_format)
+
+ self.source_str = """\
+message(FATAL_ERROR
+ "100 character line ----------------------------------------------------------"
+) # Closing parenthesis is indented one space!
+"""
+
+ self.expect_format = """\
+message(
+ FATAL_ERROR
+ "100 character line ----------------------------------------------------------"
+) # Closing parenthesis is indented one space!
+"""
+ with self.subTest():
+ assert_format(self, self.source_str, self.expect_format)
+
+ self.source_str = """\
+message(
+ "100 character line ----------------------------------------------------------------------"
+) # Closing parenthesis is indented one space!
+"""
+
+ self.expect_format = """\
+message(
+ "100 character line ----------------------------------------------------------------------"
+) # Closing parenthesis is indented one space!
+"""
+
+ with self.subTest():
+ assert_format(self, self.source_str, self.expect_format)
+ self.source_str = self.expect_format = None
+
+ def test_dangle_parens(self):
+ self.config.dangle_parens = True
+ self.config.max_subargs_per_line = 6
+
+ with self.subTest():
+ assert_format(self, """\
+foo_command()
+foo_command(arg1)
+foo_command(arg1) # comment
+""", """\
+foo_command()
+foo_command(arg1)
+foo_command(arg1) # comment
+""")
+
+ with self.subTest():
+ assert_format(self, """\
+some_long_command_name(longargname longargname longargname longargname longargname)
+""", """\
+some_long_command_name(
+ longargname longargname longargname longargname longargname
+)
+""")
+
+ with self.subTest():
+ assert_format(self, """\
+if(foo)
+ some_long_command_name(longargname longargname longargname longargname longargname)
+endif()
+""", """\
+if(foo)
+ some_long_command_name(
+ longargname longargname longargname longargname longargname
+ )
+endif()
+""")
+
+ with self.subTest():
+ assert_format(self, """\
+some_long_command_name(longargname longargname longargname longargname longargname longargname longargname longargname)
+""", """\
+some_long_command_name(
+ longargname
+ longargname
+ longargname
+ longargname
+ longargname
+ longargname
+ longargname
+ longargname
+)
+""")
+
+ with self.subTest():
+ assert_format(self, """\
+target_include_directories(target INTERFACE $)
+""", """\
+target_include_directories(
+ target INTERFACE $
+)
+""")
+
+ def test_windows_line_endings_input(self):
+ self.source_str = (
+ "#[[*********************************************\r\n"
+ "* Information line 1\r\n"
+ "* Information line 2\r\n"
+ "************************************************]]\r\n")
+
+ self.expect_format = """\
+#[[*********************************************
+* Information line 1
+* Information line 2
+************************************************]]
+"""
+
+ def test_windows_line_endings_output(self):
+ config_dict = self.config.as_dict()
+ config_dict['line_ending'] = 'windows'
+ self.config = configuration.Configuration(**config_dict)
+
+ self.source_str = """\
+#[[*********************************************
+* Information line 1
+* Information line 2
+************************************************]]"""
+
+ self.expect_format = (
+ "#[[*********************************************\r\n"
+ "* Information line 1\r\n"
+ "* Information line 2\r\n"
+ "************************************************]]\r\n")
+
+ def test_auto_line_endings(self):
+ config_dict = self.config.as_dict()
+ config_dict['line_ending'] = 'auto'
+ self.config = configuration.Configuration(**config_dict)
+
+ self.source_str = (
+ "#[[*********************************************\r\n"
+ "* Information line 1\r\n"
+ "* Information line 2\r\n"
+ "************************************************]]\r\n")
+
+ self.expect_format = (
+ "#[[*********************************************\r\n"
+ "* Information line 1\r\n"
+ "* Information line 2\r\n"
+ "************************************************]]\r\n")
+
+ def test_keyword_case(self):
+ config_dict = self.config.as_dict()
+ config_dict['keyword_case'] = 'upper'
+ self.config = configuration.Configuration(**config_dict)
+
+ with self.subTest():
+ assert_format(self, """\
+foo(bar baz)
+""", """\
+foo(BAR BAZ)
+""")
+
+ config_dict = self.config.as_dict()
+ config_dict['keyword_case'] = 'lower'
+ self.config = configuration.Configuration(**config_dict)
+
+ with self.subTest():
+ assert_format(self, """\
+foo(bar baz)
+""", """\
+foo(bar baz)
+""")
+
+ config_dict = self.config.as_dict()
+ config_dict['command_case'] = 'unchanged'
+ self.config = configuration.Configuration(**config_dict)
+ with self.subTest():
+ assert_format(self, """\
+foo(BaR bAz)
+""", """\
+foo(bar baz)
+""")
+
+ def test_command_case(self):
+ with self.subTest():
+ assert_format(self, """\
+FOO(bar baz)
+""", """\
+foo(bar baz)
+""")
+
+ config_dict = self.config.as_dict()
+ config_dict['command_case'] = 'upper'
+ self.config = configuration.Configuration(**config_dict)
+ with self.subTest():
+ assert_format(self, """\
+foo(bar baz)
+""", """\
+FOO(bar baz)
+""")
+
+ config_dict = self.config.as_dict()
+ config_dict['command_case'] = 'unchanged'
+ self.config = configuration.Configuration(**config_dict)
+ with self.subTest():
+ assert_format(self, """\
+FoO(bar baz)
+""", """\
+FoO(bar baz)
+""")
+
+ def test_comment_in_statement(self):
+ self.expect_format = """\
+add_library(foo # This comment is not attached to an argument
+ bar.cc foo.cc)
+"""
+
+ def test_comment_at_end_of_statement(self):
+ with self.subTest():
+ assert_format(self, """\
+add_library(foo bar.cc foo.cc # This comment is not attached to an argument
+)
+""")
+
+ with self.subTest():
+ assert_format(self, """\
+target_link_libraries(
+ libraryname PUBLIC ${COMMON_LIBRARIES} # add more library dependencies here
+)
+""")
+
+ with self.subTest():
+ assert_format(self, """\
+find_package(
+ foobar REQUIRED
+ COMPONENTS some_component # some_other_component
+ # This is a very long comment, and actually the second comment in
+ # this row.
+)
+""", """\
+find_package(
+ foobar REQUIRED
+ COMPONENTS some_component # some_other_component
+ # This is a very long comment, and actually the second comment in
+ # this row.
+)
+""")
+
+ def test_comment_in_kwarg(self):
+ self.source_str = """\
+install(TARGETS foob
+ ARCHIVE DESTINATION foobar
+ # this is a line comment, not a comment on foobar
+ COMPONENT baz)
+"""
+ self.expect_format = """\
+install(
+ TARGETS foob
+ ARCHIVE DESTINATION foobar # this is a line comment, not a comment on foobar
+ COMPONENT baz)
+"""
+
+ def test_algoorder_preference(self):
+ self.config.max_subargs_per_line = 10
+ self.source_str = """\
+some_long_command_name(longargument longargument longargument longargument
+ longargument longargument)
+"""
+ self.expect_format = """\
+some_long_command_name(longargument longargument longargument longargument
+ longargument longargument)
+"""
+
+ def test_elseif(self):
+ self.expect_format = """\
+if(MSVC)
+
+elseif(
+ (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+ OR CMAKE_COMPILER_IS_GNUCC
+ OR CMAKE_COMPILER_IS_GNUCXX)
+
+endif()
+"""
+
+ def test_elseif_else_control_space(self):
+ self.config.separate_ctrl_name_with_space = True
+ self.source_str = """\
+if(foo)
+elseif(bar)
+else()
+endif()
+"""
+ self.expect_format = """\
+if (foo)
+
+elseif (bar)
+
+else ()
+
+endif ()
+"""
+
+ def test_disable_markup(self):
+ self.config.enable_markup = False
+ self.source_str = """\
+# don't reflow
+# or parse markup
+# for these lines
+"""
+ self.expect_format = """\
+# don't reflow
+# or parse markup
+# for these lines
+"""
+
+ def test_literal_first_comment(self):
+ self.source_str = """\
+# This comment
+# is reflowed
+
+# This comment
+# is reflowed
+"""
+ self.expect_format = """\
+# This comment is reflowed
+
+# This comment is reflowed
+"""
+
+ self.config.first_comment_is_literal = True
+ self.source_str = """\
+# This comment
+# is not reflowed
+
+# This comment
+# is reflowed
+"""
+ self.expect_format = """\
+# This comment
+# is not reflowed
+
+# This comment is reflowed
+"""
+
+ def test_shebang_preserved(self):
+ self.source_str = """\
+#!/usr/bin/cmake -P
+"""
+ self.expect_format = """\
+#!/usr/bin/cmake -P
+"""
+
+ def test_preserve_copyright(self):
+ self.source_str = """\
+# Copyright 2018: Josh Bialkowski
+# This text should not be reflowed
+# because it's a copyright
+"""
+ self.expect_format = """\
+# Copyright 2018: Josh Bialkowski This text should not be reflowed because it's
+# a copyright
+"""
+
+ self.config.literal_comment_pattern = " Copyright.*"
+ self.source_str = """\
+# Copyright 2018: Josh Bialkowski
+# This text should not be reflowed
+# because it's a copyright
+"""
+ self.expect_format = """\
+# Copyright 2018: Josh Bialkowski
+# This text should not be reflowed
+# because it's a copyright
+"""
+
+ def test_kwarg_match_consumes(self):
+ self.source_str = """\
+add_test(NAME myTestName COMMAND testCommand --run_test=@quick)
+"""
+ self.expect_format = """\
+add_test(NAME myTestName COMMAND testCommand --run_test=@quick)
+"""
+
+ def test_byte_order_mark(self):
+ self.source_str = """\
+\ufeffcmake_minimum_required(VERSION 2.8.11)
+project(cmake_format_test)
+"""
+ self.expect_format = """\
+cmake_minimum_required(VERSION 2.8.11)
+project(cmake_format_test)
+"""
+
+ self.config.emit_byteorder_mark = True
+ self.source_str = """\
+cmake_minimum_required(VERSION 2.8.11)
+project(cmake_format_test)
+"""
+ self.expect_format = """\
+\ufeffcmake_minimum_required(VERSION 2.8.11)
+project(cmake_format_test)
+"""
+
+ def test_percommand_override(self):
+ self.source_str = """\
+FoO(bar baz)
+"""
+ self.expect_format = """\
+foo(bar baz)
+"""
+
+ self.config.per_command["foo"] = {
+ "command_case": "unchanged"
+ }
+ self.source_str = """\
+FoO(bar baz)
+"""
+ self.expect_format = """\
+FoO(bar baz)
+"""
+
+ def test_quoted_assignment_literal(self):
+ self.source_str = """\
+target_compile_definitions(foo PUBLIC BAR="Quoted String" BAZ_______________________Z)
+"""
+ self.expect_format = """\
+target_compile_definitions(foo PUBLIC BAR="Quoted String"
+ BAZ_______________________Z)
+"""
+
+ def test_keyword_comment(self):
+ self.source_str = """\
+find_package(package REQUIRED
+ COMPONENTS # --------------------------------------
+ # @TODO: This has to be filled manually
+ # --------------------------------------
+ this_is_a_really_long_word_foo)
+"""
+
+ self.expect_format = """\
+find_package(
+ package REQUIRED
+ COMPONENTS # --------------------------------------
+ # @TODO: This has to be filled manually
+ # --------------------------------------
+ this_is_a_really_long_word_foo)
+"""
+
+ def test_example_file(self):
+ thisdir = os.path.dirname(__file__)
+ infile_path = os.path.join(thisdir, '..', 'test', 'test_in.cmake')
+ outfile_path = os.path.join(thisdir, '..', 'test', 'test_out.cmake')
+
+ with io.open(infile_path, 'r', encoding='utf8') as infile:
+ infile_text = infile.read()
+ with io.open(outfile_path, 'r', encoding='utf8') as outfile:
+ outfile_text = outfile.read()
+
+ self.source_str = infile_text
+ self.expect_format = outfile_text
+
+ def test_one_char_short_hpack_rparen_case(self):
+ # This was a particularly rare edge case. The situation is that the
+ # the arguments are one character shy of fitting in the configured line
+ # width, the statement column is the same as the indent column, and
+ # all the arguments are positional. The problem was that the hpack if
+ # possible logic did not account for the final paren. A fix is in place.
+ self.config.line_width = 132
+ self.config.tab_size = 4
+ self.source_str = """
+set(cubepp_HDRS
+ ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cubepp/AppDelegate.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cubepp/DemoViewController.h)
+"""
+ self.expect_format = """\
+set(cubepp_HDRS ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cubepp/AppDelegate.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cubepp/DemoViewController.h)
+"""
+
+ def test_layout_passes(self):
+ self.config.layout_passes = {
+ "StatementNode": [(0, True)],
+ "ArgGroupNode": [(0, True)],
+ "PargGroupNode": [(0, True)],
+ }
+ self.expect_format = """\
+add_library(
+ foobar
+ STATIC
+ sourcefile_01.cc
+ sourcefile_02.cc)
+"""
+
+ def test_rulers_preserved_without_markup(self):
+ self.config.enable_markup = False
+ self.source_str = """
+#########################################################################
+# Custom targets
+#########################################################################
+"""
+ self.expect_format = """\
+#########################################################################
+# Custom targets
+#########################################################################
+"""
+
+ def test_canonical_spelling(self):
+ self.expect_format = """\
+ExternalProject_Add(
+ foobar
+ URL https://foobar.baz/latest.tar.gz
+ TLS_VERIFY TRUE
+ CONFIGURE_COMMAND configure
+ BUILD_COMMAND make
+ INSTALL_COMMAND make install)
+"""
+
+ def test_comment_hashrulers(self):
+ self.config.line_width = 74
+ with self.subTest():
+ assert_format(self, """
+##################
+# This comment has a long block before it.
+#############
+""", """\
+# ########################################################################
+# This comment has a long block before it.
+# ########################################################################
+""")
+
+ with self.subTest():
+ assert_format(self, """
+###############################################
+# This is a section in the CMakeLists.txt file.
+############
+# This stuff below here
+# should get re-flowed like
+# normal comments. Across multiple
+# lines and
+# beyond.
+""", """\
+# ########################################################################
+# This is a section in the CMakeLists.txt file.
+# ########################################################################
+# This stuff below here should get re-flowed like normal comments. Across
+# multiple lines and beyond.
+""")
+
+ # verify the original behavior is as described (they truncate to one #)
+ self.config.hashruler_min_length = 1000
+ with self.subTest():
+ assert_format(self, """
+##########################################################################
+# This comment has a long block before it.
+##########################################################################
+""", """\
+#
+# This comment has a long block before it.
+#
+""")
+
+ with self.subTest():
+ assert_format(self, """
+##########################################################################
+# This is a section in the CMakeLists.txt file.
+##########################################################################
+# This stuff below here
+# should get re-flowed like
+# normal comments. Across multiple
+# lines and
+# beyond.
+""", """\
+#
+# This is a section in the CMakeLists.txt file.
+#
+# This stuff below here should get re-flowed like normal comments. Across
+# multiple lines and beyond.
+""")
+
+ # make sure changing hashruler_min_length works correctly
+ # self.config.enable_markup = False
+ for min_width in {3, 5, 7, 9}:
+ self.config.hashruler_min_length = min_width
+
+ # NOTE(josh): these tests use short rulers that wont be picked up by
+ # the default pattern
+ self.config.ruler_pattern = (r'#{%d}#*' % (min_width - 1))
+
+ just_shy = '#' * (min_width - 1)
+ just_right = '#' * min_width
+ longer = '#' * (min_width + 2)
+ full_line = '#' * (self.config.line_width - 2)
+
+ assert_format(self, """
+# A comment: min_width={min_width}, just_shy
+{just_shy}
+""".format(min_width=min_width, just_shy=just_shy),
+ """\
+# A comment: min_width={min_width}, just_shy
+#
+""".format(min_width=min_width))
+
+ assert_format(self, """
+# A comment: min_width={min_width}, just_right
+{just_right}
+""".format(min_width=min_width, just_right=just_right),
+ """\
+# A comment: min_width={min_width}, just_right
+# {full_line}
+""".format(min_width=min_width, full_line=full_line))
+
+ assert_format(self, """
+# A comment: min_width={min_width}, longer
+{longer}
+""".format(min_width=min_width, longer=longer),
+ """\
+# A comment: min_width={min_width}, longer
+# {full_line}
+""".format(min_width=min_width, full_line=full_line))
+
+
+TestMiscFormatting.load_sidecar_tests()
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/cmake_format/command_tests/set_tests.py b/cmake_format/command_tests/set_tests.py
index c66bdf6..c73178f 100644
--- a/cmake_format/command_tests/set_tests.py
+++ b/cmake_format/command_tests/set_tests.py
@@ -59,38 +59,25 @@ def test_tag_respected_if_autosort_enabled(self):
def test_bracket_comment_is_not_trailing_comment_of_kwarg(self):
self.config.autosort = True
- self.config.max_subargs_per_line = 3
- self.source_str = """\
-set(SOURCES #[[cmf:sortable]] foo.cc bar.cc baz.cc)
-"""
self.expect_format = """\
-set(SOURCES
- #[[cmf:sortable]]
- bar.cc baz.cc foo.cc)
+set(SOURCES #[[cmf:sortable]] #
+ bar.cc baz.cc foo.cc)
"""
def test_bracket_comment_short_tag(self):
self.config.autosort = True
- self.source_str = self.expect_format = """\
-set(SOURCES
- #[[cmf:sort]]
- bar.cc baz.cc foo.cc)
+ self.expect_format = """\
+set(SOURCES #[[cmf:sort]] bar.cc baz.cc foo.cc)
"""
def test_line_comment_long_tag(self):
self.config.autosort = True
- self.source_str = self.expect_format = """\
-set(sources
- # cmake-format: sortable
- bar.cc baz.cc foo.cc)
+ self.expect_format = """\
+set(sources # cmake-format: sortable
+ bar.cc baz.cc foo.cc)
"""
def test_long_args_command_split(self):
- self.source_str = """\
-# This very long command should be split to multiple lines
-set(HEADERS very_long_header_name_a.h very_long_header_name_b.h very_long_header_name_c.h)
-"""
-
self.expect_format = """\
# This very long command should be split to multiple lines
set(HEADERS very_long_header_name_a.h very_long_header_name_b.h
@@ -98,12 +85,7 @@ def test_long_args_command_split(self):
"""
def test_lots_of_args_command_split(self):
- self.source_str = """\
-# This command should be split into one line per entry because it has a long
-# argument list.
-set(SOURCES source_a.cc source_b.cc source_d.cc source_e.cc source_f.cc source_g.cc)
-"""
-
+ # TODO(josh): repair this
self.expect_format = """\
# This command should be split into one line per entry because it has a long
# argument list.
@@ -113,7 +95,8 @@ def test_lots_of_args_command_split(self):
source_d.cc
source_e.cc
source_f.cc
- source_g.cc)
+ source_g.cc
+ source_h.cc)
"""
diff --git a/cmake_format/configuration.py b/cmake_format/configuration.py
index 5f14a84..e733d63 100644
--- a/cmake_format/configuration.py
+++ b/cmake_format/configuration.py
@@ -160,11 +160,14 @@ def as_odict(self, with_comments=False):
return out
def __init__(self, line_width=80, tab_size=2,
- max_subargs_per_line=3,
+ max_subgroups_hwrap=2,
+ max_pargs_hwrap=6,
separate_ctrl_name_with_space=False,
separate_fn_name_with_space=False,
dangle_parens=False,
- max_prefix_chars=2,
+ dangle_align=None,
+ min_prefix_chars=4,
+ max_prefix_chars=10,
max_lines_hwrap=2,
bullet_char=None,
enum_char=None,
@@ -173,8 +176,6 @@ def __init__(self, line_width=80, tab_size=2,
keyword_case=None,
additional_commands=None,
always_wrap=None,
- # TODO(josh): remove algorithm_order
- algorithm_order=None,
enable_sort=True,
autosort=False,
enable_markup=True,
@@ -188,21 +189,21 @@ def __init__(self, line_width=80, tab_size=2,
input_encoding=None,
output_encoding=None,
per_command=None,
+ layout_passes=None,
**_): # pylint: disable=W0613
# pylint: disable=too-many-locals
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
- # flags the way we do kwargs.
- self.max_subargs_per_line = max_subargs_per_line
+ self.max_subgroups_hwrap = max_subgroups_hwrap
+ self.max_pargs_hwrap = max_pargs_hwrap
self.separate_ctrl_name_with_space = separate_ctrl_name_with_space
self.separate_fn_name_with_space = separate_fn_name_with_space
self.dangle_parens = dangle_parens
+ self.dangle_align = get_default(dangle_align, "prefix")
+ self.min_prefix_chars = min_prefix_chars
self.max_prefix_chars = max_prefix_chars
self.max_lines_hwrap = max_lines_hwrap
@@ -233,7 +234,6 @@ def __init__(self, line_width=80, tab_size=2,
})
self.always_wrap = get_default(always_wrap, [])
- self.algorithm_order = get_default(algorithm_order, [0, 1, 2, 3, 4])
self.enable_sort = enable_sort
self.autosort = autosort
self.enable_markup = enable_markup
@@ -245,6 +245,8 @@ def __init__(self, line_width=80, tab_size=2,
self.hashruler_min_length = hashruler_min_length
self.canonicalize_hashrulers = canonicalize_hashrulers
+ self.layout_passes = get_default(layout_passes, {})
+
self.input_encoding = get_default(input_encoding, "utf-8")
self.output_encoding = get_default(output_encoding, "utf-8")
@@ -307,6 +309,7 @@ def linewidth(self):
VARCHOICES = {
+ "dangle_align": ["prefix", "prefix-indent", "child", "off"],
'line_ending': ['windows', 'unix', 'auto'],
'command_case': ['lower', 'upper', 'canonical', 'unchanged'],
'keyword_case': ['lower', 'upper', 'unchanged'],
@@ -315,8 +318,12 @@ def linewidth(self):
VARDOCS = {
"line_width": "How wide to allow formatted cmake files",
"tab_size": "How many spaces to tab for indent",
- "max_subargs_per_line": (
- "If arglists are longer than this, break them always"),
+ "max_subgroups_hwrap": (
+ "If an argument group contains more than this many sub-groups "
+ "(parg or kwarg groups), then force it to a vertical layout. "),
+ "max_pargs_hwrap": (
+ "If a positinal argument group contains more than this many arguments, "
+ "then force it to a vertical layout. "),
"separate_ctrl_name_with_space": (
"If true, separate flow control names from their parentheses with a"
" space"),
@@ -324,7 +331,12 @@ def linewidth(self):
"If true, separate function names from parentheses with a space"),
"dangle_parens": (
"If a statement is wrapped to more than one line, than dangle the"
- " closing parenthesis on it's own line"),
+ " closing parenthesis on it's own line."),
+ "dangle_align": (
+ "If the trailing parenthesis must be 'dangled' on it's on line, then"
+ " align it to this reference: `prefix`: the start of the statement, "
+ " `prefix-indent`: the start of the statement, plus one indentation "
+ " level, `child`: align to the column of the arguments"),
"max_prefix_chars": (
"If the statement spelling length (including space and parenthesis"
" is larger than the tab width by more than this amoung, then"
@@ -341,9 +353,6 @@ def linewidth(self):
"Format command names consistently as 'lower' or 'upper' case"),
"keyword_case": "Format keywords consistently as 'lower' or 'upper' case",
"always_wrap": "A list of command names which should always be wrapped",
- "algorithm_order": (
- "Specify the order of wrapping algorithms during successive reflow "
- "attempts"),
"enable_sort": (
"If true, the argument lists which are known to be sortable will be "
"sorted lexicographicall"),
@@ -384,5 +393,9 @@ def linewidth(self):
" anything else"),
"per_command": (
"A dictionary containing any per-command configuration overrides."
- " Currently only `command_case` is supported.")
+ " Currently only `command_case` is supported."),
+ "layout_passes": (
+ "A dictionary mapping layout nodes to a list of wrap decisions. See"
+ " the documentation for more information."
+ )
}
diff --git a/cmake_format/doc/README.rst b/cmake_format/doc/README.rst
index 05f4578..7378e87 100644
--- a/cmake_format/doc/README.rst
+++ b/cmake_format/doc/README.rst
@@ -69,7 +69,7 @@ Usage
-i, --in-place
-o OUTFILE_PATH, --outfile-path OUTFILE_PATH
Where to write the formatted file. Default is stdout.
- -c CONFIG_FILE [CONFIG_FILE ...], --config-file CONFIG_FILE [CONFIG_FILE ...], --config-files CONFIG_FILE [CONFIG_FILE ...]
+ -c CONFIG_FILE [CONFIG_FILE ...], --config-file CONFIG_FILE [CONFIG_FILE ...], --config-files CONFIG_FILE [CONFIG_FILE ...], --config CONFIG_FILE [CONFIG_FILE ...]
path to configuration file(s)
Formatter Configuration:
@@ -78,8 +78,13 @@ Usage
--line-width LINE_WIDTH
How wide to allow formatted cmake files
--tab-size TAB_SIZE How many spaces to tab for indent
- --max-subargs-per-line MAX_SUBARGS_PER_LINE
- If arglists are longer than this, break them always
+ --max-subgroups-hwrap MAX_SUBGROUPS_HWRAP
+ If an argument group contains more than this many sub-
+ groups (parg or kwarg groups), then force it to a
+ vertical layout.
+ --max-pargs-hwrap MAX_PARGS_HWRAP
+ If a positinal argument group contains more than this
+ many arguments, then force it to a vertical layout.
--separate-ctrl-name-with-space [SEPARATE_CTRL_NAME_WITH_SPACE]
If true, separate flow control names from their
parentheses with a space
@@ -88,7 +93,14 @@ Usage
a space
--dangle-parens [DANGLE_PARENS]
If a statement is wrapped to more than one line, than
- dangle the closing parenthesis on it's own line
+ dangle the closing parenthesis on it's own line.
+ --dangle-align {prefix,prefix-indent,child,off}
+ If the trailing parenthesis must be 'dangled' on it's
+ on line, then align it to this reference: `prefix`:
+ the start of the statement, `prefix-indent`: the start
+ of the statement, plus one indentation level, `child`:
+ align to the column of the arguments
+ --min-prefix-chars MIN_PREFIX_CHARS
--max-prefix-chars MAX_PREFIX_CHARS
If the statement spelling length (including space and
parenthesis is larger than the tab width by more than
@@ -106,9 +118,6 @@ Usage
case
--always-wrap [ALWAYS_WRAP [ALWAYS_WRAP ...]]
A list of command names which should always be wrapped
- --algorithm-order [ALGORITHM_ORDER [ALGORITHM_ORDER ...]]
- Specify the order of wrapping algorithms during
- successive reflow attempts
--enable-sort [ENABLE_SORT]
If true, the argument lists which are known to be
sortable will be sorted lexicographicall
@@ -189,8 +198,13 @@ pleasant way.
# How many spaces to tab for indent
tab_size = 2
- # If arglists are longer than this, break them always
- max_subargs_per_line = 3
+ # If an argument group contains more than this many sub-groups (parg or kwarg
+ # groups), then force it to a vertical layout.
+ max_subgroups_hwrap = 2
+
+ # If a positinal argument group contains more than this many arguments, then
+ # force it to a vertical layout.
+ max_pargs_hwrap = 6
# If true, separate flow control names from their parentheses with a space
separate_ctrl_name_with_space = False
@@ -199,13 +213,21 @@ pleasant way.
separate_fn_name_with_space = False
# If a statement is wrapped to more than one line, than dangle the closing
- # parenthesis on it's own line
+ # parenthesis on it's own line.
dangle_parens = False
+ # If the trailing parenthesis must be 'dangled' on it's on line, then align it
+ # to this reference: `prefix`: the start of the statement, `prefix-indent`: the
+ # start of the statement, plus one indentation level, `child`: align to the
+ # column of the arguments
+ dangle_align = 'prefix'
+
+ min_prefix_chars = 4
+
# If the statement spelling length (including space and parenthesis is larger
# than the tab width by more than this amoung, then force reject un-nested
# layouts.
- max_prefix_chars = 2
+ max_prefix_chars = 10
# If a candidate layout is wrapped horizontally but it exceeds this many lines,
# then reject the layout.
@@ -232,9 +254,6 @@ pleasant way.
# A list of command names which should always be wrapped
always_wrap = []
- # Specify the order of wrapping algorithms during successive reflow attempts
- algorithm_order = [0, 1, 2, 3, 4]
-
# If true, the argument lists which are known to be sortable will be sorted
# lexicographicall
enable_sort = True
@@ -252,6 +271,10 @@ pleasant way.
# only `command_case` is supported.
per_command = {}
+ # A dictionary mapping layout nodes to a list of wrap decisions. See the
+ # documentation for more information.
+ layout_passes = {}
+
# --------------------------
# Comment Formatting Options
@@ -474,9 +497,9 @@ and you can globally disable sorting by making setting this configuration to
Custom Commands
---------------
-Due to the fact that cmake is a macro language, `cmake-format` is, by necessity,
-a *semantic* source code formatter. In general it tries to make smart
-formatting decisions based on the meaning of arguments in an otherwise
+Due to the fact that cmake is a macro language, `cmake-format` is, by
+necessity, a *semantic* source code formatter. In general it tries to make
+smart formatting decisions based on the meaning of arguments in an otherwise
unstructured list of arguments in a cmake statement. `cmake-format` can
intelligently format your custom commands, but you will need to tell it how
to interpret your arguments.
@@ -513,8 +536,8 @@ fields:
* ``kwargs``: a dictionary mapping keywords to sub-specifications. A
sub-specification may be a complete dictionary of ``pargs``, ``flags``, and
``kwargs`` (nested, all the way down). Or, if the keyword argument accepts
- only positionals, then it can be simply the ``pargs`` specification (as in the
- example above).
+ only positionals, then it can be simply the ``pargs`` specification (as in
+ the example above).
For the example specification above, the custom command would look somehing
like this:
@@ -592,12 +615,11 @@ Will turn this:
add_subdirectories(foo bar baz
foo2 bar2 baz2)
- # This very long command should be split to multiple lines
+ # This very long command should be wrapped
set(HEADERS very_long_header_name_a.h very_long_header_name_b.h very_long_header_name_c.h)
- # This command should be split into one line per entry because it has a long
- # argument list.
- set(SOURCES source_a.cc source_b.cc source_d.cc source_e.cc source_f.cc source_g.cc)
+ # This command should be split into one line per entry because it has a long argument list.
+ set(SOURCES source_a.cc source_b.cc source_d.cc source_e.cc source_f.cc source_g.cc source_h.cc)
# The string in this command should not be split
set_target_properties(foo bar baz PROPERTIES COMPILE_FLAGS "-std=c++11 -Wall -Wextra")
@@ -644,7 +666,7 @@ Will turn this:
if(sbar)
# This comment is in-scope.
add_library(foo_bar_baz foo.cc bar.cc # this is a comment for arg2
- # this is more comment for arg2, it should be joined with the first.
+ # this is more comment for arg2, it should be joined with the first.
baz.cc) # This comment is part of add_library
other_command(some_long_argument some_long_argument) # this comment is very long and gets split across some lines
@@ -692,14 +714,9 @@ into this:
# This comment should remain right before the command call. Furthermore, the
# command call should be formatted to a single line.
- add_subdirectories(foo
- bar
- baz
- foo2
- bar2
- baz2)
-
- # This very long command should be split to multiple lines
+ add_subdirectories(foo bar baz foo2 bar2 baz2)
+
+ # This very long command should be wrapped
set(HEADERS very_long_header_name_a.h very_long_header_name_b.h
very_long_header_name_c.h)
@@ -711,11 +728,12 @@ into this:
source_d.cc
source_e.cc
source_f.cc
- source_g.cc)
+ source_g.cc
+ source_h.cc)
# The string in this command should not be split
- set_target_properties(foo bar baz
- PROPERTIES COMPILE_FLAGS "-std=c++11 -Wall -Wextra")
+ set_target_properties(foo bar baz PROPERTIES COMPILE_FLAGS
+ "-std=c++11 -Wall -Wextra")
# This command has a very long argument and can't be aligned with the command
# end, so it should be moved to a new line with block indent + 1.
@@ -728,11 +746,9 @@ into this:
"-std=c++11 -Wall -Wno-sign-compare -Wno-unused-parameter -xx")
set(HEADERS
- header_a.h
- header_b.h # This comment should be preserved, moreover it should be split
- # across two lines.
- header_c.h
- header_d.h)
+ header_a.h header_b.h # This comment should be preserved, moreover it should
+ # be split across two lines.
+ header_c.h header_d.h)
# This part of the comment should be formatted but...
# cmake-format: off
@@ -757,30 +773,32 @@ into this:
if(foo)
if(sbar)
# This comment is in-scope.
- add_library(foo_bar_baz
- foo.cc
- bar.cc # this is a comment for arg2 this is more comment for
- # arg2, it should be joined with the first.
- baz.cc) # This comment is part of add_library
-
- other_command(some_long_argument some_long_argument) # this comment is very
- # long and gets split
- # across some lines
-
- other_command(some_long_argument some_long_argument some_long_argument)
- # this comment is even longer and wouldn't make sense to pack at the end of
- # the command so it gets it's own lines
+ add_library(
+ foo_bar_baz
+ foo.cc bar.cc # this is a comment for arg2 this is more comment for arg2,
+ # it should be joined with the first.
+ baz.cc) # This comment is part of add_library
+
+ other_command(
+ some_long_argument some_long_argument) # this comment is very long and
+ # gets split across some lines
+
+ other_command(
+ some_long_argument some_long_argument some_long_argument) # this comment
+ # is even longer
+ # and wouldn't
+ # make sense to
+ # pack at the
+ # end of the
+ # command so it
+ # gets it's own
+ # lines
endif()
endif()
# This very long command should be broken up along keyword arguments
foo(nonkwarg_a nonkwarg_b
- HEADERS a.h
- b.h
- c.h
- d.h
- e.h
- f.h
+ HEADERS a.h b.h c.h d.h e.h f.h
SOURCES a.cc b.cc d.cc
DEPENDS foo
bar baz)
diff --git a/cmake_format/doc/case_studies.rst b/cmake_format/doc/case_studies.rst
index f4ae785..78023e8 100644
--- a/cmake_format/doc/case_studies.rst
+++ b/cmake_format/doc/case_studies.rst
@@ -20,7 +20,8 @@ Also doesn't look too bad when wrapped horizontally::
foo bar baz foo2 bar2 baz2 foo3 bar3 baz3 foo4 bar4 baz4 foo5 bar5 baz5
foo6 bar6 baz6 foo7 bar7 baz7 foo8 bar8 baz8 foo9 bar9 baz9)
-Though probably matches expectations better if it is wrapped vertically::
+Though probably matches expectations better if it is wrapped vertically,
+even if it does look like shit::
add_subdirectories(
foo
@@ -63,6 +64,34 @@ and looks better wrapped vertically, horizontally nested::
very_long_header_name_b.h
very_long_header_name_c.h)
+also looks pretty good packed after the first argument::
+
+ set(HEADERS very_long_header_name_a.h
+ very_long_header_name_b.h
+ very_long_header_name_c.h)
+
+or possibly nested::
+
+ set(HEADERS
+ very_long_header_name_a.h
+ very_long_header_name_b.h
+ very_long_header_name_c.h)
+
+ set(
+ HEADERS
+ very_long_header_name_a.h
+ very_long_header_name_b.h
+ very_long_header_name_c.h)
+
+ but this starts to look a little inconsistent when other arguments are
+ used::
+
+ set(
+ HEADERS PARENT_SCOPE
+ very_long_header_name_a.h
+ very_long_header_name_b.h
+ very_long_header_name_c.h)
+
Lots of medium-length args, looks good vertical, horizontally nested::
set(SOURCES
@@ -295,6 +324,24 @@ for this purpose. This could be a sandard "microtag" format including the
ability to set the list sortable. For example: ``#v,s`` would be
"vertical, sortable"
+Another interesting case is if we have an argument comment on a keyword
+argument, or a prefix group. For example::
+
+ set(foobarbaz # comment about foobarbaz
+ value_one value_two value_three value_four value_five value_six
+ value_seven value_eight)
+
+Should that be formatted as above, or as::
+
+ set(foobarbaz # comment about foobarbaz
+ value_one value_two value_three value_four value_five
+ value_six value_seven value_eight)
+
+If we're already formatting set as::
+
+ set(foobarbaz value_one value_two value_three value_four value_five
+ value_six value_seven value_eight)
+
-------
Nesting
-------
@@ -409,3 +456,124 @@ I don't think there's any reason to add structure for the internal operators
like ``MATCHES``. In particular children of a boolean operator can be simple
positional argument groups (horizontally-wrapped). We can tag the internal
operator as a keyword but we don't need to create a KWARGGROUP for it.
+
+------------------------------
+Internally Wrapped Positionals
+------------------------------
+
+The third kwarg (AND) in this statement looks bad because it is Internally
+wrapped. The second option looks better:
+
+.. code:: cmake
+
+ set(matchme "_DATA_\|_CMAKE_\|INTRA_PRED\|_COMPILED\|_HOSTING\|_PERF_\|CODER_")
+ if(("${var}" MATCHES "_TEST_" AND NOT "${var}" MATCHES "${matchme}")
+ OR (CONFIG_AV1_ENCODER AND CONFIG_ENCODE_PERF_TESTS AND "${var}" MATCHES
+ "_ENCODE_PERF_TEST_"
+ ))
+ list(APPEND aom_test_source_vars ${var})
+ endif()
+
+ set(matchme "_DATA_\|_CMAKE_\|INTRA_PRED\|_COMPILED\|_HOSTING\|_PERF_\|CODER_")
+ if(("${var}" MATCHES "_TEST_" AND NOT "${var}" MATCHES "${matchme}")
+ OR (CONFIG_AV1_ENCODER
+ AND CONFIG_ENCODE_PERF_TESTS
+ AND "${var}" MATCHES "_ENCODE_PERF_TEST_"))
+ list(APPEND aom_test_source_vars ${var})
+ endif()
+
+However, this short :code:`set()` statement looks better if we don't push the
+internally wrapped argument to the next line:
+
+.. code:: cmake
+
+ set(sources # cmake-format: sortable
+ bar.cc baz.cc foo.cc)
+
+Perhaps the difference is that in the latter case it's going to consume two
+lines anyway... whereas in the former case it would only consume one
+line.
+
+--------------------
+Columnized arguments
+--------------------
+
+Some very long statements with a large number of keywords might look nice
+and organized if we columize the child argument groups. For example:
+
+.. code:: cmake
+
+ ExternalProject_Add(
+ FOO
+ PREFIX ${FOO_PREFIX}
+ TMP_DIR ${TMP_DIR}
+ STAMP_DIR ${FOO_PREFIX}/stamp
+ # Download
+ DOWNLOAD_DIR ${DOWNLOAD_DIR}
+ DOWNLOAD_NAME ${FOO_ARCHIVE_FILE_NAME}
+ URL ${STORAGE_URL}/${FOO_ARCHIVE_FILE_NAME}
+ URL_MD5 ${FOO_MD5}
+ # Patch
+ PATCH_COMMAND ${PATCH_COMMAND} ${PROJECT_SOURCE_DIR}/patch.diff
+ # Configure
+ SOURCE_DIR ${SRC_DIR}
+ CMAKE_ARGS ${CMAKE_OPTS}
+ # Build
+ BUILD_IN_SOURCE 1
+ BUILD_BYPRODUCTS ${CUR_COMPONENT_ARTIFACTS}
+ # Logging
+ LOG_CONFIGURE 1
+ LOG_BUILD 1
+ LOG_INSTALL 1
+ )
+
+Note what :code:`clang-format` does for these cases. If two consecutive
+keywords are more than :code:`n` characters different in length, then break
+columns, which might come out something like this:
+
+.. code:: cmake
+
+ ExternalProject_Add(
+ FOO
+ PREFIX ${FOO_PREFIX}
+ TMP_DIR ${TMP_DIR}
+ STAMP_DIR ${FOO_PREFIX}/stamp
+ # Download
+ DOWNLOAD_DIR ${DOWNLOAD_DIR}
+ DOWNLOAD_NAME ${FOO_ARCHIVE_FILE_NAME}
+ URL ${STORAGE_URL}/${FOO_ARCHIVE_FILE_NAME}
+ URL_MD5 ${FOO_MD5}
+ # Patch
+ PATCH_COMMAND ${PATCH_COMMAND} ${PROJECT_SOURCE_DIR}/patch.diff
+ # Configure
+ SOURCE_DIR ${SRC_DIR}
+ CMAKE_ARGS ${CMAKE_OPTS}
+ # Build
+ BUILD_IN_SOURCE 1
+ BUILD_BYPRODUCTS ${CUR_COMPONENT_ARTIFACTS}
+ # Logging
+ LOG_CONFIGURE 1
+ LOG_BUILD 1
+ LOG_INSTALL 1
+ )
+
+As an experimental feature, we could require a tag :code:`# cmf: columnize`
+to enable this formatting.
+
+-------------------------
+Algorithm Ideas and Notes
+-------------------------
+
+Layout Passes
+=============
+
+Up through version 0.5.2 each node would lay itself out using pass numbers
+``[0, ]``. This worked pretty well, but actually I would like
+the nesting to be a little more depth dependant. For example I would like
+depth 0 (statement) to nest rather early, while I would like higher depths
+(i.e. KWARGS) to nest later, but go vertical earlier.
+
+One alternative is to have a global ``passno`` and apply different rules at
+each pass until things fit, but the probem with this option is that two
+subtrees might require fastly different passes. We don't want to
+vertically wrap one all kwargs just because one needs to.
diff --git a/cmake_format/doc/changelog.rst b/cmake_format/doc/changelog.rst
index 5ac950b..40f97e8 100644
--- a/cmake_format/doc/changelog.rst
+++ b/cmake_format/doc/changelog.rst
@@ -2,6 +2,27 @@
Changelog
=========
+-----------
+v0.6 series
+-----------
+
+v0.6.0
+------
+
+Significant refactor of the formatting logic.
+
+* Move ``format_tests`` into ``command_tests.misc_tests``
+* Prototype sidecar tests for easier readability/maintainability
+* ArgGroupNodes gain representation in the layout tree
+* Get rid of ``WrapAlgo``
+* Eliminate vertical/nest as separate decisions. Nesting is just the wrap
+ decision for StatementNode and KwargNode wheras vertical is the wrap
+ decision for PargGroupnode and ArgGroupNode.
+* Replace ``algorithm_order`` with ``_layout_passes``
+* Get rid of ``default_accept_layout`` and move logic into a member function
+* Move configuration and ``node_path`` into new ``StackContext``
+* Stricter valid-child-set for most layout nodes
+
-----------
v0.5 series
-----------
@@ -22,12 +43,12 @@ v0.5.5
* Closes `#129`_: cmakeFormat.args in settings.json yields Incorrect type
* Closes `#131`_: cmakeFormat.args is an array of items of type string
-.. __#121: https://github.com/cheshirekow/cmake_format/issues/121
-.. __#123: https://github.com/cheshirekow/cmake_format/issues/123
-.. __#125: https://github.com/cheshirekow/cmake_format/issues/125
-.. __#128: https://github.com/cheshirekow/cmake_format/issues/128
-.. __#129: https://github.com/cheshirekow/cmake_format/issues/129
-.. __#131: https://github.com/cheshirekow/cmake_format/issues/131
+.. _#121: https://github.com/cheshirekow/cmake_format/issues/121
+.. _#123: https://github.com/cheshirekow/cmake_format/issues/123
+.. _#125: https://github.com/cheshirekow/cmake_format/issues/125
+.. _#128: https://github.com/cheshirekow/cmake_format/issues/128
+.. _#129: https://github.com/cheshirekow/cmake_format/issues/129
+.. _#131: https://github.com/cheshirekow/cmake_format/issues/131
v0.5.4
@@ -45,11 +66,11 @@ v0.5.4
* Closes `#119`_: Fix missing newline argument
* Closes `#120`_: auto-line ending option not working correctly under Windows
-.. __#114: https://github.com/cheshirekow/cmake_format/issues/114
-.. __#117: https://github.com/cheshirekow/cmake_format/issues/117
-.. __#118: https://github.com/cheshirekow/cmake_format/issues/118
-.. __#119: https://github.com/cheshirekow/cmake_format/issues/119
-.. __#120: https://github.com/cheshirekow/cmake_format/issues/120
+.. _#114: https://github.com/cheshirekow/cmake_format/issues/114
+.. _#117: https://github.com/cheshirekow/cmake_format/issues/117
+.. _#118: https://github.com/cheshirekow/cmake_format/issues/118
+.. _#119: https://github.com/cheshirekow/cmake_format/issues/119
+.. _#120: https://github.com/cheshirekow/cmake_format/issues/120
v0.5.3
------
diff --git a/cmake_format/doc/configopts.rst b/cmake_format/doc/configopts.rst
new file mode 100644
index 0000000..7af814c
--- /dev/null
+++ b/cmake_format/doc/configopts.rst
@@ -0,0 +1,25 @@
+=====================
+Configuration Options
+=====================
+
+-------------
+layout_passes
+-------------
+
+See the :ref:`Formatting Algorithm ` section for more
+information on how `cmake-format` uses multiple passes to converge on the
+final layout of the listfile source code. This option can be used to override
+the default behavior. The format of this option is a dictionary, where the keys
+are the names of the different layout node classes:
+
+* StatementNode
+* ArgGroupNode
+* KWargGroupNode
+* PargGroupNode
+* ParenGroupNode
+
+The dictionary values are a list of pairs (2-tuples) in the form of
+:code:`(passno, wrap-decision)`. Where :code:`passno` is the pass number at
+which the wrap-decision becomes active, and :code:`wrap-decision` is a boolean
+:code:`(true/false)`. For each layout pass, the decision of whether or not the
+node should wrap (either nested, or vertical) is looked-up from this map.
diff --git a/cmake_format/doc/example.rst b/cmake_format/doc/example.rst
index 3af6e50..1060585 100644
--- a/cmake_format/doc/example.rst
+++ b/cmake_format/doc/example.rst
@@ -26,12 +26,11 @@ Will turn this:
add_subdirectories(foo bar baz
foo2 bar2 baz2)
- # This very long command should be split to multiple lines
+ # This very long command should be wrapped
set(HEADERS very_long_header_name_a.h very_long_header_name_b.h very_long_header_name_c.h)
- # This command should be split into one line per entry because it has a long
- # argument list.
- set(SOURCES source_a.cc source_b.cc source_d.cc source_e.cc source_f.cc source_g.cc)
+ # This command should be split into one line per entry because it has a long argument list.
+ set(SOURCES source_a.cc source_b.cc source_d.cc source_e.cc source_f.cc source_g.cc source_h.cc)
# The string in this command should not be split
set_target_properties(foo bar baz PROPERTIES COMPILE_FLAGS "-std=c++11 -Wall -Wextra")
@@ -78,7 +77,7 @@ Will turn this:
if(sbar)
# This comment is in-scope.
add_library(foo_bar_baz foo.cc bar.cc # this is a comment for arg2
- # this is more comment for arg2, it should be joined with the first.
+ # this is more comment for arg2, it should be joined with the first.
baz.cc) # This comment is part of add_library
other_command(some_long_argument some_long_argument) # this comment is very long and gets split across some lines
@@ -126,14 +125,9 @@ into this:
# This comment should remain right before the command call. Furthermore, the
# command call should be formatted to a single line.
- add_subdirectories(foo
- bar
- baz
- foo2
- bar2
- baz2)
-
- # This very long command should be split to multiple lines
+ add_subdirectories(foo bar baz foo2 bar2 baz2)
+
+ # This very long command should be wrapped
set(HEADERS very_long_header_name_a.h very_long_header_name_b.h
very_long_header_name_c.h)
@@ -145,11 +139,12 @@ into this:
source_d.cc
source_e.cc
source_f.cc
- source_g.cc)
+ source_g.cc
+ source_h.cc)
# The string in this command should not be split
- set_target_properties(foo bar baz
- PROPERTIES COMPILE_FLAGS "-std=c++11 -Wall -Wextra")
+ set_target_properties(foo bar baz PROPERTIES COMPILE_FLAGS
+ "-std=c++11 -Wall -Wextra")
# This command has a very long argument and can't be aligned with the command
# end, so it should be moved to a new line with block indent + 1.
@@ -162,11 +157,9 @@ into this:
"-std=c++11 -Wall -Wno-sign-compare -Wno-unused-parameter -xx")
set(HEADERS
- header_a.h
- header_b.h # This comment should be preserved, moreover it should be split
- # across two lines.
- header_c.h
- header_d.h)
+ header_a.h header_b.h # This comment should be preserved, moreover it should
+ # be split across two lines.
+ header_c.h header_d.h)
# This part of the comment should be formatted but...
# cmake-format: off
@@ -191,30 +184,32 @@ into this:
if(foo)
if(sbar)
# This comment is in-scope.
- add_library(foo_bar_baz
- foo.cc
- bar.cc # this is a comment for arg2 this is more comment for
- # arg2, it should be joined with the first.
- baz.cc) # This comment is part of add_library
-
- other_command(some_long_argument some_long_argument) # this comment is very
- # long and gets split
- # across some lines
-
- other_command(some_long_argument some_long_argument some_long_argument)
- # this comment is even longer and wouldn't make sense to pack at the end of
- # the command so it gets it's own lines
+ add_library(
+ foo_bar_baz
+ foo.cc bar.cc # this is a comment for arg2 this is more comment for arg2,
+ # it should be joined with the first.
+ baz.cc) # This comment is part of add_library
+
+ other_command(
+ some_long_argument some_long_argument) # this comment is very long and
+ # gets split across some lines
+
+ other_command(
+ some_long_argument some_long_argument some_long_argument) # this comment
+ # is even longer
+ # and wouldn't
+ # make sense to
+ # pack at the
+ # end of the
+ # command so it
+ # gets it's own
+ # lines
endif()
endif()
# This very long command should be broken up along keyword arguments
foo(nonkwarg_a nonkwarg_b
- HEADERS a.h
- b.h
- c.h
- d.h
- e.h
- f.h
+ HEADERS a.h b.h c.h d.h e.h f.h
SOURCES a.cc b.cc d.cc
DEPENDS foo
bar baz)
diff --git a/cmake_format/doc/features.rst b/cmake_format/doc/features.rst
index 00e0a93..04b1f3f 100644
--- a/cmake_format/doc/features.rst
+++ b/cmake_format/doc/features.rst
@@ -158,9 +158,9 @@ and you can globally disable sorting by making setting this configuration to
Custom Commands
---------------
-Due to the fact that cmake is a macro language, `cmake-format` is, by necessity,
-a *semantic* source code formatter. In general it tries to make smart
-formatting decisions based on the meaning of arguments in an otherwise
+Due to the fact that cmake is a macro language, `cmake-format` is, by
+necessity, a *semantic* source code formatter. In general it tries to make
+smart formatting decisions based on the meaning of arguments in an otherwise
unstructured list of arguments in a cmake statement. `cmake-format` can
intelligently format your custom commands, but you will need to tell it how
to interpret your arguments.
@@ -197,8 +197,8 @@ fields:
* ``kwargs``: a dictionary mapping keywords to sub-specifications. A
sub-specification may be a complete dictionary of ``pargs``, ``flags``, and
``kwargs`` (nested, all the way down). Or, if the keyword argument accepts
- only positionals, then it can be simply the ``pargs`` specification (as in the
- example above).
+ only positionals, then it can be simply the ``pargs`` specification (as in
+ the example above).
For the example specification above, the custom command would look somehing
like this:
diff --git a/cmake_format/doc/format_algorithm.rst b/cmake_format/doc/format_algorithm.rst
index 8d22917..15a52f0 100644
--- a/cmake_format/doc/format_algorithm.rst
+++ b/cmake_format/doc/format_algorithm.rst
@@ -1,18 +1,23 @@
+.. _formatting-algorithm:
+
====================
Formatting Algorithm
====================
-The formatter works by attempting to select an
-appropriate ``position`` and ``wrap`` (collectively referred to as a
-"layout") for each node in the layout tree. Positions are represented by
-``(row, col)`` pairs and the wrap dictates how childen of that node
-are positioned.
+The formatter works by attempting to select an appropriate ``position`` and
+``wrap`` (collectively referred to as a "layout") for each node in the layout
+tree. Positions are represented by ``(row, col)`` pairs and the wrap dictates
+how childen of that node are positioned.
--------
Wrapping
--------
-``cmake-format`` implements two styles of wrapping:
+``cmake-format`` implements three styles of wrapping.
+The default wrapping for all nodes is horizontal wrapping. If horizontal
+wrapping fails to emit an admissible layout, then a node will advance to
+either vertical wrapping or nested wrapping (which one depends on the type of
+node).
Horizontal Wrapping
===================
@@ -82,7 +87,7 @@ Vertical wrapping assigns each child to the next row::
████
Again, note that this happens at the depth of the layout tree. In particular
-children may be wrapped horizontally internally::
+children may be wrapped horizontally within the subtrees::
| ▒▒▒▒▒▒ ███ ██████ |<- col-limit
| ▒▒▒ ██████ ██ |
@@ -91,37 +96,11 @@ children may be wrapped horizontally internally::
| ████ ██████████ |
| ▒▒ ███ ████ |
--------
-Nesting
--------
-
-In addition to wrapping, ``cmake-format`` also must decide how to nest children
-of a layout node.
-
-Horizontal Nesting
-==================
-
-Horizontal nesting places children in a column immediately following the
-terminal cursor of the parent. For example::
- | |<- col-limit
- | ▒▒▒ ██ ███ ██ █████ |
- | ████████████████ |
- | █████████ ████ |
-
-In a more deeply nested layout tree, we might see the following::
-
- | |<- col-limit
- | ▓▓▓ ▒▒▒ ██ ███ ██ █████ |
- | ████████████████ |
- | █████████ ████ |
- | ▒▒▒ ████ ███ █ |
- | ▒▒▒▒▒▒ ████ ███ █ |
-
-Vertical nesting
-================
+Nesting
+=======
-Vertical nesting places children in a column which is one tabwidth to the
+Nesting places children in a column which is one ``tab_width`` to the
right of the parent node's position, and one line below. For example::
| |<- col-limit
@@ -154,6 +133,9 @@ may be nested differently. For example::
| ▒▒▒ ████ ███ █ |
| ▒▒▒▒▒▒ ████ ███ █ |
+Note that the only nodes that can nest are ``STATEMENT`` and ``KWARGGROUP``
+nodes. These nodes necessarily only have one child, an ``ARGGROUP`` node.
+Therefore there really isn't a notion of "wrapping" for these nodes.
--------------------
Formatting algorithm
@@ -166,115 +148,64 @@ first line after the output cursor of it's predecessor, and at a column
``config.tab_size`` to the right of it's parent.
``STATEMENTS`` however, are laid out over several passes until the
-text for that subtree lies is accepted. Each pass is governed by a
-specification mapping node depth to a layout algorithm (i.e. a
-``(nesting,wrapping)`` pair, as well as a condition of acceptance.
-
-
-Ideas
-=====
-
-* If the name of the command (plus parenthesis) is less than or equal to
- tab-width then vertical nesting is off the table.
-* If the name of the command (plus parenthesis) is "very long" then horizontal
- nesting is off the table.
-* If a PARGGROUP is a "list" then we should probably have some options for
- deciding whether or not it is wrapped horizontally or vertically:
-
- * vertical wrap if number of arguments > some threshold
- * wrap always
- * wrap always for specific statments/kwargs/paths
-
-* Multiple KWARGGROUPS should always be nested vertically (optionally)
-* Can implement that guys idea about column-aligning kwarg arguments,
- perhaps if kwargs are within some threshold of the same size
-* If a statement interior ends with a comment we must dangle the parenthesis
-* configuration to decide whether or not to dangle a parenthesis always
-* Algorithm order for PARGGROUP (currently)::
-
- (nest:H, wrap:H), valid if numlines == 1
- (nest:H, wrap:V), valid if columns are not exceeded
- (nest:V, wrap:V)
-
-* I think that I might prefer::
-
- (nest:H, wrap:H), valid if numlines <= [config-option=1]
- (nest:V, wrap:H), valid if numlines <= [config-option=1]
- (nest:V, wrap:V)
-
-* Algorithm order for ARGGROUP with at least [n=2] KWARGGROUPS:
-
- (nest:H, wrap:H), valid if numlines < [config-option=0] (i.e. never)
- (nest:V, wrap:V),
+text for that subtree is accepted. Each pass is governed by a
+specification mapping pass number to a wrap decision (i.e. a
+boolean indicating whether or not to wrap vertical or nest children)
+
+Layout Passes
+=============
+
+The current algorithm works in a kind of top-down refinement. When a node is
+laid out by calling it's ``reflow()`` method, it is informed of its parent's
+current pass number (``passno``). It then iterates through its own ``passno``
+from zero up to it's parent's ``passno`` and terminates at the first admissible
+layout. Note that within the layout of the node itself, it's current
+``passno`` can only affect its ``wrap`` decision. However, because each of its
+children will advance through their own passes, the overall layout of a subtree
+between two different passes may change, even if the node at the subtree root
+didn't change it's ``wrap`` decision between those passes.
+
+This approach seems to work well even for
+:ref:`deeply nested ` or
+:ref:`complex ` statements.
-* So should we use some kind of overridable function to get
-* If we do limit HWRAP to at most 2 lines, are there any cases where this
- would do something we don't want? Generally we don't have lots of
- unstructured positional arguments that aren't lists. Also, if we had
- such a thing, could we not tag it that way?
+Newline decision
+================
+When a node is in horizontal layout mode (``wrap=False``), there are a couple
+of reasons why the algorithm might choose to insert a newline between two
+of it's children.
+
+1. If a token would overflow the column limit, insert a newline (e.g. the
+ usual notion of wrapping)
+2. If the token is the last token before a closing parenthesis, and the
+ token plus the parenthesis would overflow the column limit, then insert a
+ newline.
+3. If a token is preceeded by a line comment, then the token cannot be placed
+ on the same line as the comment (or it will become part of the comment) so
+ a newline is inserted between them.
+4. If a token is a line comment which is not associated with an argument (e.g.
+ it is a "free" comment at the current scope) then it will not be placed
+ on the same line as a preceeding argument token. If it was, then subsequent
+ parses would associate this comment with that argument. In such a case, a
+ newline is inserted between the preceeding argument and the line comment.
+5. If the node is an interior node, and one of it's children is internally
+ wrapped (i.e. consumes more than two lines) then it will not be placed
+ on the same line as another node. In such a case a newlines is inserted.
+6. If the node is an interior node and a child fails to find an admissible
+ layout at the current cursor, a newline is inserted and a new layout attempt
+ is made for the child.
+
+Admissible layouts
+==================
-Positional Arguments
-====================
+There are a couple of reasons why a layout may be deemed inadmissible:
-Candidate algorithm:
-
-First, wrap horizontally. If they all fit on [n=2] lines, and if the
-statement spelling does not exceed [k=2] characters above the tab-width, choose
-that layout. Note that the previous rule can be enforced by this rule with
-[n=1], so perhaps we can exclude a special case for the previous attempt::
-
- foobarbaz_hello(argument_one argument_two argument_three argument_four
- argument_five argument_six)
-
-If not, nest vertically, and if they fit on [n=2] lines, choose that layout::
-
- foobarbaz_hello(
- argument_one argument_two argument_three argument_four argument_five
- argument_six)
-
-Note that, if the statement spelling (plus optional space and paren) are less
-than the tab width, then vertical nesting is disabled. In which case we proceed
-directly to the next attempt: vertical wrap::
-
- foobarbaz_hello(
- argument_one
- argument_two
- argument_three
- argument_four
- argument_five
- argument_six)
-
-In order to match expectation and current behavior, I think by default we can
-use values of (2, 2) but I think that personally I will want values of (1,1).
-We can describe this sequence by the following escallation of attempts. If
-each attempt fails we move onto the nest.
-
-1. initially horizontal nesting, horizontal wrapping
-2. switch to vertical nesting, if allowed
-3. switch to vertical wrapping if allowed
-
-This differs a little from the current algorithm and I'm not sure what the best
-way to unify them is, perhaps it's to continue
-specifying an "algorithm order" kind of thing. We could change it to
-something like:
-
-0. ``(vertical-nest, vertical-wrap)``
-1. ``(False, False)``
-2. ``(False, True)``
-3. ``(True, False)``
-4. ``(True, True)``
-
-And then pair each with a configurable approval function. For instance I would
-always reject #2. The problem here is that for short statement names like
-``set()`` or for large tab-widths, vertical nesting is not allowed. Perhaps
-that is just something we need to specify in the approval function though. For
-what I want personally, The approval function for #2 would be to reject always
-unless the statement spelling too small to vertically wrap.
-
-Note also, though, that my desired algorithm order is not consistent at depth.
-I generally want to avoid #2 for statement groups, but I generally want to
-avoid #3 for keyword groups. And how about deeply nested kwarg groups?
+1. If the bounding box of a node overflows the column limit
+2. If a node is horizontally wrapped at the current ``passno`` but consumes
+ more than ``max_lines_hwrap`` lines
+3. If the node is horizontally wrapped at the current ``passno`` but the node
+ path is marked as ``always_wrap``
Comments
========
@@ -322,49 +253,3 @@ minimum width of the comment block is given by::
Which would preclude it from being crammed into the right-most slot.
-Special-case Positional Arguments
-=================================
-
-There are some situations in which we might want to apply a different threshold
-set than the default. ``COMMAND`` kwargs in particular we probably never want
-to wrap vertically.
-
-Interior groups
-===============
-
-Keyword subtrees (``KWARGGROUP``) themselves are laid out the same as
-statements, but interior nodes (``ARGGROUP``) in the parse/layout tree follow
-a slightly different set of rules. The reasoning for this separate rule set is
-that the syntatic boundary between children in an ``ARGGROUP`` are also
-significant semantic boundaries and so breaking a line on these boundaries is
-cheaper than breaking a line within a positional argument group.
-
-Candidate Algorithm:
-
-Wrap horizonally, if they all fit on [n=1] lines and if there are at most [n=2]
-non-empty groups. For example::
-
- foobarbaz_hello(argument_one argument_two KEYWORD_ONE kwarg_one)
-
-Otherwise, nest vertically, if they all fit on [n=1] lines and if there are
-at most [n=2] non-empty groups::
-
- foobarbaz_hello(
- argument_one argument_two argument_three KEYWORD_ONE kwarg_one kwarg_two)
-
-Otherwise, wrap vertically::
-
- foobarbaz_hello(
- argument_one argument_two argument_three
- KEYWORD_ONE kwarg_one kwarg_two
- KEYWORD_TWO kwarg_three kwarg_four)
-
-------------
-Search Order
-------------
-
-The current algorithm does a kind of top-down implementation. Each node is
-allowed to try layouts in "algorithm order" from start up to their parent's
-current algorithm. This seems to work well even for
-:ref:`deeply nested ` or
-:ref:`complex ` statements.
diff --git a/cmake_format/doc/index.rst b/cmake_format/doc/index.rst
index 2e0ec57..d0622af 100644
--- a/cmake_format/doc/index.rst
+++ b/cmake_format/doc/index.rst
@@ -23,6 +23,7 @@ like crap.
format_algorithm
case_studies
render_html
+ configopts
release_notes
changelog
diff --git a/cmake_format/doc/parse_tree.rst b/cmake_format/doc/parse_tree.rst
index dca58a0..9fbac8b 100644
--- a/cmake_format/doc/parse_tree.rst
+++ b/cmake_format/doc/parse_tree.rst
@@ -228,9 +228,10 @@ You can inspect the parse tree of a listfile by ``cmake-format`` with
│ │ ├─ KEYWORD: 2:23
│ │ │ └─ Token(type=WORD, content='VERSION', line=2, col=23)
│ │ ├─ Token(type=WHITESPACE, content=' ', line=2, col=30)
- │ │ └─ PARGGROUP: 2:31
- │ │ └─ ARGUMENT: 2:31
- │ │ └─ Token(type=UNQUOTED_LITERAL, content='3.5', line=2, col=31)
+ │ │ └─ ARGGROUP: 2:31
+ │ │ └─ PARGGROUP: 2:31
+ │ │ └─ ARGUMENT: 2:31
+ │ │ └─ Token(type=UNQUOTED_LITERAL, content='3.5', line=2, col=31)
│ └─ RPAREN: 2:34
│ └─ Token(type=RIGHT_PAREN, content=')', line=2, col=34)
├─ WHITESPACE: 2:35
@@ -255,9 +256,10 @@ You can inspect the parse tree of a listfile by ``cmake-format`` with
│ │ ├─ LPAREN: 4:2
│ │ │ └─ Token(type=LEFT_PAREN, content='(', line=4, col=2)
│ │ ├─ ARGGROUP: 4:3
- │ │ │ ├─ ARGUMENT: 4:3
- │ │ │ │ └─ Token(type=WORD, content='FOO', line=4, col=3)
- │ │ │ ├─ Token(type=WHITESPACE, content=' ', line=4, col=6)
+ │ │ │ ├─ PARGGROUP: 4:3
+ │ │ │ │ ├─ ARGUMENT: 4:3
+ │ │ │ │ │ └─ Token(type=WORD, content='FOO', line=4, col=3)
+ │ │ │ │ └─ Token(type=WHITESPACE, content=' ', line=4, col=6)
│ │ │ └─ KWARGGROUP: 4:7
│ │ │ ├─ KEYWORD: 4:7
│ │ │ │ └─ Token(type=WORD, content='AND', line=4, col=7)
@@ -267,16 +269,18 @@ You can inspect the parse tree of a listfile by ``cmake-format`` with
│ │ │ ├─ LPAREN: 4:11
│ │ │ │ └─ Token(type=LEFT_PAREN, content='(', line=4, col=11)
│ │ │ ├─ ARGGROUP: 4:12
- │ │ │ │ ├─ ARGUMENT: 4:12
- │ │ │ │ │ └─ Token(type=WORD, content='BAR', line=4, col=12)
- │ │ │ │ ├─ Token(type=WHITESPACE, content=' ', line=4, col=15)
+ │ │ │ │ ├─ PARGGROUP: 4:12
+ │ │ │ │ │ ├─ ARGUMENT: 4:12
+ │ │ │ │ │ │ └─ Token(type=WORD, content='BAR', line=4, col=12)
+ │ │ │ │ │ └─ Token(type=WHITESPACE, content=' ', line=4, col=15)
│ │ │ │ └─ KWARGGROUP: 4:16
│ │ │ │ ├─ KEYWORD: 4:16
│ │ │ │ │ └─ Token(type=WORD, content='OR', line=4, col=16)
│ │ │ │ ├─ Token(type=WHITESPACE, content=' ', line=4, col=18)
│ │ │ │ └─ ARGGROUP: 4:19
- │ │ │ │ └─ ARGUMENT: 4:19
- │ │ │ │ └─ Token(type=WORD, content='BAZ', line=4, col=19)
+ │ │ │ │ └─ PARGGROUP: 4:19
+ │ │ │ │ └─ ARGUMENT: 4:19
+ │ │ │ │ └─ Token(type=WORD, content='BAZ', line=4, col=19)
│ │ │ └─ RPAREN: 4:22
│ │ │ └─ Token(type=RIGHT_PAREN, content=')', line=4, col=22)
│ │ └─ RPAREN: 4:23
@@ -337,52 +341,61 @@ You can inspect the layout tree of a listfile by ``cmake-format`` with
.. code:: text
- └─ BODY,HPACK(0) p(0,0) ce:35
- ├─ STATEMENT,HPACK(0) p(0,0) ce:35
- │ ├─ FUNNAME,HPACK(0) p(0,0) ce:22
- │ ├─ LPAREN,HPACK(0) p(0,22) ce:23
- │ ├─ KWARGGROUP,HPACK(0) p(0,23) ce:34
- │ │ ├─ KEYWORD,HPACK(0) p(0,23) ce:30
- │ │ └─ PARGGROUP,HPACK(0) p(0,31) ce:34
- │ │ └─ ARGUMENT,HPACK(0) p(0,31) ce:34
- │ └─ RPAREN,HPACK(0) p(0,34) ce:35
- ├─ STATEMENT,HPACK(0) p(1,0) ce:13
- │ ├─ FUNNAME,HPACK(0) p(1,0) ce:7
- │ ├─ LPAREN,HPACK(0) p(1,7) ce:8
- │ ├─ PARGGROUP,HPACK(0) p(1,8) ce:12
- │ │ └─ ARGUMENT,HPACK(0) p(1,8) ce:12
- │ └─ RPAREN,HPACK(0) p(1,12) ce:13
- └─ FLOW_CONTROL,HPACK(0) p(2,0) ce:29
- ├─ STATEMENT,HPACK(0) p(2,0) ce:24
- │ ├─ FUNNAME,HPACK(0) p(2,0) ce:2
- │ ├─ LPAREN,HPACK(0) p(2,2) ce:3
- │ ├─ ARGUMENT,HPACK(0) p(2,3) ce:6
- │ ├─ KWARGGROUP,HPACK(0) p(2,7) ce:23
- │ │ ├─ KEYWORD,HPACK(0) p(2,7) ce:10
- │ │ └─ ARGGROUP,HPACK(0) p(2,11) ce:23
- │ │ └─ PARENGROUP,HPACK(0) p(2,11) ce:23
- │ │ ├─ LPAREN,HPACK(0) p(2,11) ce:12
- │ │ ├─ ARGGROUP,HPACK(0) p(2,12) ce:22
- │ │ │ ├─ ARGUMENT,HPACK(0) p(2,12) ce:15
- │ │ │ └─ KWARGGROUP,HPACK(0) p(2,16) ce:22
- │ │ │ ├─ KEYWORD,HPACK(0) p(2,16) ce:18
- │ │ │ └─ ARGGROUP,HPACK(0) p(2,19) ce:22
- │ │ │ └─ ARGUMENT,HPACK(0) p(2,19) ce:22
- │ │ └─ RPAREN,HPACK(0) p(2,22) ce:23
- │ └─ RPAREN,HPACK(0) p(2,23) ce:24
- ├─ BODY,HPACK(0) p(3,2) ce:29
- │ └─ STATEMENT,HPACK(0) p(3,2) ce:29
- │ ├─ FUNNAME,HPACK(0) p(3,2) ce:13
- │ ├─ LPAREN,HPACK(0) p(3,13) ce:14
- │ ├─ PARGGROUP,HPACK(0) p(3,14) ce:19
- │ │ └─ ARGUMENT,HPACK(0) p(3,14) ce:19
- │ ├─ PARGGROUP,HPACK(0) p(3,20) ce:28
- │ │ └─ ARGUMENT,HPACK(0) p(3,20) ce:28
- │ └─ RPAREN,HPACK(0) p(3,28) ce:29
- └─ STATEMENT,HPACK(0) p(4,0) ce:7
- ├─ FUNNAME,HPACK(0) p(4,0) ce:5
- ├─ LPAREN,HPACK(0) p(4,5) ce:6
- └─ RPAREN,HPACK(0) p(4,6) ce:7
+ └─ BODY,(passno=0,wrap=F) pos:(0,0) colextent:35
+ ├─ STATEMENT,(passno=0,wrap=F) pos:(0,0) colextent:35
+ │ ├─ FUNNAME,(passno=0,wrap=F) pos:(0,0) colextent:22
+ │ ├─ LPAREN,(passno=0,wrap=F) pos:(0,22) colextent:23
+ │ ├─ ARGGROUP,(passno=0,wrap=F) pos:(0,23) colextent:34
+ │ │ └─ KWARGGROUP,(passno=0,wrap=F) pos:(0,23) colextent:34
+ │ │ ├─ KEYWORD,(passno=0,wrap=F) pos:(0,23) colextent:30
+ │ │ └─ ARGGROUP,(passno=0,wrap=F) pos:(0,31) colextent:34
+ │ │ └─ PARGGROUP,(passno=0,wrap=F) pos:(0,31) colextent:34
+ │ │ └─ ARGUMENT,(passno=0,wrap=F) pos:(0,31) colextent:34
+ │ └─ RPAREN,(passno=0,wrap=F) pos:(0,34) colextent:35
+ ├─ STATEMENT,(passno=0,wrap=F) pos:(1,0) colextent:13
+ │ ├─ FUNNAME,(passno=0,wrap=F) pos:(1,0) colextent:7
+ │ ├─ LPAREN,(passno=0,wrap=F) pos:(1,7) colextent:8
+ │ ├─ ARGGROUP,(passno=0,wrap=F) pos:(1,8) colextent:12
+ │ │ └─ PARGGROUP,(passno=0,wrap=F) pos:(1,8) colextent:12
+ │ │ └─ ARGUMENT,(passno=0,wrap=F) pos:(1,8) colextent:12
+ │ └─ RPAREN,(passno=0,wrap=F) pos:(1,12) colextent:13
+ └─ FLOW_CONTROL,(passno=0,wrap=F) pos:(2,0) colextent:29
+ ├─ STATEMENT,(passno=0,wrap=F) pos:(2,0) colextent:24
+ │ ├─ FUNNAME,(passno=0,wrap=F) pos:(2,0) colextent:2
+ │ ├─ LPAREN,(passno=0,wrap=F) pos:(2,2) colextent:3
+ │ ├─ ARGGROUP,(passno=0,wrap=F) pos:(2,3) colextent:23
+ │ │ ├─ PARGGROUP,(passno=0,wrap=F) pos:(2,3) colextent:6
+ │ │ │ └─ ARGUMENT,(passno=0,wrap=F) pos:(2,3) colextent:6
+ │ │ └─ KWARGGROUP,(passno=0,wrap=F) pos:(2,7) colextent:23
+ │ │ ├─ KEYWORD,(passno=0,wrap=F) pos:(2,7) colextent:10
+ │ │ └─ ARGGROUP,(passno=0,wrap=F) pos:(2,11) colextent:23
+ │ │ └─ PARENGROUP,(passno=0,wrap=F) pos:(2,11) colextent:23
+ │ │ ├─ LPAREN,(passno=0,wrap=F) pos:(2,11) colextent:12
+ │ │ ├─ ARGGROUP,(passno=0,wrap=F) pos:(2,12) colextent:22
+ │ │ │ ├─ PARGGROUP,(passno=0,wrap=F) pos:(2,12) colextent:15
+ │ │ │ │ └─ ARGUMENT,(passno=0,wrap=F) pos:(2,12) colextent:15
+ │ │ │ └─ KWARGGROUP,(passno=0,wrap=F) pos:(2,16) colextent:22
+ │ │ │ ├─ KEYWORD,(passno=0,wrap=F) pos:(2,16) colextent:18
+ │ │ │ └─ ARGGROUP,(passno=0,wrap=F) pos:(2,19) colextent:22
+ │ │ │ └─ PARGGROUP,(passno=0,wrap=F) pos:(2,19) colextent:22
+ │ │ │ └─ ARGUMENT,(passno=0,wrap=F) pos:(2,19) colextent:22
+ │ │ └─ RPAREN,(passno=0,wrap=F) pos:(2,22) colextent:23
+ │ └─ RPAREN,(passno=0,wrap=F) pos:(2,23) colextent:24
+ ├─ BODY,(passno=0,wrap=F) pos:(3,2) colextent:29
+ │ └─ STATEMENT,(passno=0,wrap=F) pos:(3,2) colextent:29
+ │ ├─ FUNNAME,(passno=0,wrap=F) pos:(3,2) colextent:13
+ │ ├─ LPAREN,(passno=0,wrap=F) pos:(3,13) colextent:14
+ │ ├─ ARGGROUP,(passno=0,wrap=F) pos:(3,14) colextent:28
+ │ │ ├─ PARGGROUP,(passno=0,wrap=F) pos:(3,14) colextent:19
+ │ │ │ └─ ARGUMENT,(passno=0,wrap=F) pos:(3,14) colextent:19
+ │ │ └─ PARGGROUP,(passno=0,wrap=F) pos:(3,20) colextent:28
+ │ │ └─ ARGUMENT,(passno=0,wrap=F) pos:(3,20) colextent:28
+ │ └─ RPAREN,(passno=0,wrap=F) pos:(3,28) colextent:29
+ └─ STATEMENT,(passno=0,wrap=F) pos:(4,0) colextent:7
+ ├─ FUNNAME,(passno=0,wrap=F) pos:(4,0) colextent:5
+ ├─ LPAREN,(passno=0,wrap=F) pos:(4,5) colextent:6
+ ├─ ARGGROUP,(passno=0,wrap=F) pos:(4,6) colextent:6
+ └─ RPAREN,(passno=0,wrap=F) pos:(4,6) colextent:7
.. dynamic: dump-example-layout-end
diff --git a/cmake_format/doc/release_notes.rst b/cmake_format/doc/release_notes.rst
index a4185dc..8c534df 100644
--- a/cmake_format/doc/release_notes.rst
+++ b/cmake_format/doc/release_notes.rst
@@ -5,6 +5,55 @@ Release Notes
Details of changes can be found in the changelog, but this file will contain
some high level notes and highlights from each release.
+v0.6 series
+===========
+
+------
+v0.6.0
+------
+
+This release includes a significant refactor of the formatting logic. Details
+of the new algorithm are described in the documentation__. As a result of the
+algorithm changes, some config options have changed too. The following
+config options are removed:
+
+* ``max_subargs_per_line`` (see ``max_pargs_hwrap``)
+* ``nest_threshold`` (see ``min_prefix_chars``)
+* ``algorithm_order`` (see ``layout_passes``)
+
+.. __: https://cmake-format.readthedocs.io/en/latest/format_algorithm.html
+
+And the following config options have been added:
+
+* ``max_subgroups_hwrap``
+* ``max_pargs_hwrap``
+* ``dangle_align``
+* ``min_prefix_chars``
+* ``max_prefix_chars``
+* ``max_lines_hwrap``
+* ``layout_passes``
+* ``enable_sort``
+
+Also as a result of the algorithm changes, the default layout has changed. By
+default, ``cmake-format`` will now prefer to nest long lists rather than
+aligning them to the opening parenthesis of a statement. Also, due to the new
+configuration options, the output of ``cmake-format`` is likely to be different
+with your current configs.
+
+Additionally, ``cmake-format`` will now tend to prefer a normal "horizontal"
+wrap for relatively long lists of positional arguments (e.g. source files in
+``add_library``) whereas it would previously prefer a vertical layout (one-entry
+per line). This is a consequence of an ambiguity between which positional
+arguments should be vertical versus which should be wrapped. Two planned
+features (layout tags and positional semantics) should help to provide enough
+control to get the layout you want in these lists.
+
+I acknowledge that it is not ideal for formatting to change between releases
+but this is an unfortunate inevitability at this stage of development. The
+changes in this release elminate a number of inconsistencies and also adds the
+groundwork for future planned features and options. Hopefully we are getting
+close to a stable state and a 1.0 release.
+
v0.5 series
===========
@@ -16,7 +65,6 @@ This is a maintenance release fixing a few minor bugs and enhancements. One
new feature is that the ``--config`` command line option now accepts a list of
config files, which should allow for including multiple databases of command
specifications
-
------
v0.5.4
------
@@ -26,6 +74,15 @@ documentation. One notable feature added is that, during in-place formatting,
if the file content is unchanged ``cmake-format`` will no-longer write the
file.
+------
+v0.5.3
+------
+
+This hotfix release fixes a bug that would crash cmake-format if no
+configuration file was present. It also includes some small under-the-hood
+changes in preparation for an overhaul of the formatting logic.
+
+
------
v0.5.2
------
diff --git a/cmake_format/doc/todo.rst b/cmake_format/doc/todo.rst
index eb315a7..d7a6245 100644
--- a/cmake_format/doc/todo.rst
+++ b/cmake_format/doc/todo.rst
@@ -2,37 +2,6 @@
TODO
====
-* Allow option to infer keywords for commands which don't have a specification
-* Add option to break long strings to make them fit
-* Use cmake --help-command --help-property --help-variable --help-module
- and parse the output to get the list of commands, properties, variable
- names, etc. This has been around since at least v2.8.8 so it's pretty
- available. It can definitely be used to filter available commands.
-* Consider getting rid of config.endl and instead using
- ``io.open(newline='\n')`` or ``io.open(newline='\r\n')`` depending on config.
- Then just write ``\n`` and let the streamwriter translation take care of
- line endings.
-* Make a distinction between argument comments and blocklevel or statement
- comments. Argument comments must be reflowed to (config.linewidth-1) if
- dangle_parens is false, while block level and statement comments may reflow
- up to (config.linewidth). Can probably just make this a flag in the
- CommentNode class rather than implementing a separate class for it. This
- can help to eliminate the need for, or at least simplify, the
- has_terminal_comment() hack.
-* Deal with the case that the command name is so long or that the statement is
- nested so far that the open paren doesn't fit on the line and needs to be
- wrapped.
-* Improve error messages for exceptions/assertions caused by malformed input.
-* Implement per-command algorithm order allowing to change the wrap preferences
- at a fine-grained level.
-* Implement kwarg canonical ordering. Each kwarg parser has a canonical order
- associated with it. The formatter can re-order arguments when formatting to
- ensure that they are always written in the same order.
-* Add a generic CMAKE_FORMAT_TAG token type matching ``# cmake-format: XXX``
- or ``# cmf: XXX`` strings.
-* Implement an ``unpad_hashruler`` configuration option. If true, dont separate
- hashrulers from the leading comment character by a space.
-
cmake-lint
==========
@@ -45,6 +14,9 @@ cmake-lint
* improper capitalization of statements, kwargs, or flags, keywords
* optional kwargs not in canonical order
* extra positional arguments
+ * local variable used but not assigned
+ * local variable/global variable doesn't match regex
+ * syntax hidden by variable expansion
VS Code
=======
@@ -56,6 +28,7 @@ VS Code
* https://code.visualstudio.com/docs/extensions/example-language-server
* https://microsoft.github.io/language-server-protocol/specification#textDocument_onTypeFormatting
+* Implement partial formatting (i.e. format highlight).
* VScode language server protocol does not currently support semantic
highlighting: https://github.com/Microsoft/language-server-protocol/issues/18
* But it can be implemented using custom messages to the language server such
@@ -63,8 +36,9 @@ VS Code
* An existing cmake extension is pretty good, but I think we can do better
on code-completion and semantic highlighting
-Sortabe Arguments
-=================
+
+Sortable Arguments
+==================
* Don't treat tag comment specially with regard to trailing comment assignment,
allow them to be attached to a preceeding parg or kwarg or whatever. This
@@ -87,33 +61,6 @@ Sortabe Arguments
might be tagged sortable. In this case, break the pair of argument groups
at the comment tag instead of after library/executable name.
-Parser Refactor
-===============
-
-* Deal with the hack in parse_positionals working around
- ``install(RUNTIME COMPONENT runtime)``. The current solution is to only break
- if the subparser isn't expecting an exact number of tokens. The problem with
- this is that we will consume an RPAREN if a statement is malformed... and
- we probably shouldn't do that.
-* Cleanup the HWRAP logic. Currently it's distributed in a number of places:
-
- * reflow() for everything but a StatementNode reduces it's linewidth by one
- in HPACK
- * PArgGroupNode._reflow() reduces the final linewidth by one if it's a
- _statement_terminal and we're in HWRAP mode
- * most nodes do the same work during HPACK and HWRAP
- * many nodes like StatementNode have old code that isn't needed now that
- ArgGroup exists
-
-* Replace the rest of the legacy cmdspec tree with new style map of statement
- parse functions
-* Add tests for all the different forms of ``install()`` and ``file()`` that
- we've implemented.
-* Add a config option for users to specify custom commands using custom
- parse functions, rather than just the legacy dictionary specification.
-* Split parse_funs into modules to better organize custom parsers
-* Implement custom parser for the different forms of ``list()``
-
Current Issues
==============
@@ -121,17 +68,12 @@ Current Issues
argument is a variable dereference. For instance `file(${descr})`. What
should we do in that case? Should we infer based on remaining arguments,
fallback to a standard parser with a large set of kwargs and flags?
-* Right now adding a line comment forces the line into HVPACK, but for a
- COMMAND we probably actually want HWRAP so that we can manually pack our
- shell commands
-* Idea: an empty line comment after the first argument forces VPACK. An empty
- line comment after any other argument forces HPACK. Not perfect, but might
- work OK.
Cost Function
=============
-Implement something better than max-subargs per line and algorithm order.
+Implement something better than :code:`max-subgroups-hwrap` and
+:code:`layout-passes`.
Probably both of these can be rolled up into a some kind of cost function
that accounts for both issues. For instance, a cost function which
penalizes number of lines, number of arguments on a line, and indentation
@@ -139,48 +81,129 @@ would generally perfer a single line, would perhaps allow HWRAP but only
if it was at most two lines, would allow VPACK but only if the statment
name is not too long.
-Format Refactor
-===============
-
-* Separate the layout algorithm between two separate decisions for "nesting"
- and "wrapping". The order in which to apply them will depend on the type of
- node we are at, and can be influenced by the parser. See notes in case
- studies.
-* Implement additional criteria for triggering a wrap:
-
- * arguments overflow the column width
- * exceed threshold in number or size of arguments
- * presence of a line comment
- * is an `always_wrap` node
-
-* Add a configuration option for nesting preference. The current order is
- basically ``(nest,wrap)``:
-
- * (horizontal, horizontal)
- * (horizontal, vertical)
- * (vertical, vertical)
-
- and, in particular, I think that I want it to nest vertically almost always.
- The one case not to nest vertically is if the command name is less than or
- equal to tab width. We might include some configuration option for how many
- characters over tab-width to continue allowing horizontal nesting. We should
- always be able to fall back to vertical nesting in the case that horizontal
- just can't fit.
-
-* Do a scan of TODO's in formatter.py. I'm leaving a bunch in there.
-* Should we add a configuration option for maximum sub-groups per line
- (like max-subargs per line?)
-* Should we add a configuration option to prevent horizontal wrapping a
- child PARGGROUP after another argument? See the current format failures
- in cmake_format.command_tests.conditional_tests
-
Release Process
===============
-Add cmake rules for ``release`` and ``test-release`` that will double check
-certain things:
+Add cmake rules for ``prep-release`` and ``test-release`` that will
+automatically create a release commit and double check certain things:
-1. Closes issues in changelog are also closed in the commit message
+1. Closed issues in changelog are also closed in the commit message
2. Version number is not ``dev``
3. Version number is incremented
+4. Execute the screw-users test
+
+Parse Stack Refactor
+====================
+
+There are a couple of things that are a bit clunky about the current
+implementation of the parser stack context. Currently we only have the
+:code:`breakstack` in each parse function. I think we want some more context.
+One thing that we should probably add is the stack of parse nodes that have
+been opened up to the current context. The initiating token for each parse
+node can be used to infer some properties of tokens as we process them.
+
+* Github issue #122: ambiguity in the nesting level of a line comment which
+ might belong to the end of a nested argument list, or might belong to the
+ parent argument list.
+* Look-ahead at the next semantic token when deciding whether or not to
+ break out of the current scope. Otherwise comments will get globbed up in
+ the nested scope when they were originally written in outer scope.
+* If the look-ahead indicates that the next semantic token would break us
+ up the stack, then any comment between "here" and that token belongs to
+ the child set of one of the nodes somewhere in that stack region. One way
+ of associating it consistently is to find the latest parse node that starts
+ no later than the current comment node.
+* Deal with the hack in parse_positionals working around
+ ``install(RUNTIME COMPONENT runtime)``. The current solution is to only break
+ if the subparser isn't expecting an exact number of tokens. The problem with
+ this is that we will consume an RPAREN if a statement is malformed... and
+ we probably shouldn't do that.
+
+
+After Refactor
+==============
+
+01. Move as many tests as possible into :code:`.cmake` sidecar files.
+
+02. Split :code:`formatter.py` into :code:`format_tree.py` and
+ :code:`formatter.py`
+
+03. Implement :code:`wrap` tags. I'm not sure what the tag string should
+ be, maybe :code:`wrap`, :code:`list`, :code:`vertical/nest`, but whatever
+ it is, it should record within the parse node a forced wrap decition and
+ override the trial/error logic. That way an empty comment can be used to
+ force a line-wrap, but a specific comment can be used to force a more
+ specific decision.
+04. There are still many more cmake functions that need parser mappings.
+
+ * :code:`list()`
+ * :code:`target_compile_definitions()`
+
+05. Make a test that executes cmake to get the
+ list of function names and make sure they're all in the database.
+06. Replace the rest of the legacy cmdspec tree with new style map of statement
+ parse functions
+07. Add tests for all the different forms of ``install()`` and ``file()`` that
+ we've implemented.
+08. Add a config option for users to specify custom commands using custom
+ parse functions, rather than just the legacy dictionary specification.
+09. Add option to infer keywords for commands which don't have a specification
+10. Add option to break long strings to make them fit
+11. Use cmake --help-command --help-property --help-variable --help-module
+ and parse the output to get the list of commands, properties, variable
+ names, etc. This has been around since at least v2.8.8 so it's pretty
+ available. It can definitely be used to filter available commands.
+12. Consider getting rid of config.endl and instead using
+ ``io.open(newline='\n')`` or ``io.open(newline='\r\n')`` depending on
+ config. Then just write ``\n`` and let the streamwriter translation take
+ care of line endings.
+13. Deal with the case that the command name is so long or that the statement
+ is nested so far that the open paren doesn't fit on the line and needs to
+ be wrapped.
+14. Improve error messages for exceptions/assertions caused by malformed input.
+15. Implement kwarg canonical ordering. Each kwarg parser has a canonical order
+ associated with it. The formatter can re-order arguments when formatting to
+ ensure that they are always written in the same order.
+16. Add a generic CMAKE_FORMAT_TAG token type matching ``# cmake-format: XXX``
+ or ``# cmf: XXX`` strings and don't necessarily treat them like comments.
+17. Implement an ``unpad_hashruler`` configuration option. If true, dont
+ separate hashrulers from the leading comment character by a space.
+18. Enable an option to parse sentinel comments `#< comment here` as argument
+ comments (instead of relying on existing columnization to merge
+ multiple argument comment lines).
+19. Deduplicate code in in :code:`consume_comment` and
+ :code:`consume_trailing_comment` which are pretty much the same now.
+20. Figure out what to do the :code:`needs_wrap=True` logic in
+ :code:`ArgGroupNode._reflow` if an argument wraps internally. In some cases
+ it looks bad, but in some cases it looks good. See case studies for
+ internally wrapped positionals.
+21. The _statement_terminal hack is insufficient for dealing with parengroups.
+ We need some other mechanism to deal with multiple closing parentheses.
+ Probably we need to pass down some kind of :code:`StackContext` including
+ a member of :code:`n_open_parens`.
+22. Deduplicate the common reflow logic of :code:`StatementNode` and
+ :code:`KwargGroupNode` (both of which nest). Also potentially the reflow
+ logic of :code:`ArgGroupNode` and :code:`PargGroupNode` (both of which
+ verticalize).
+23. Rename :code:`ParseNode.node_type` to :code:`ParseNode.type`
+24. There are a couple more TODO's in :code:`formatter.py`
+25. Currently it's rather challenging to know in the formatter whether or not
+ a particular comment would be re-parsed as an argument comment if we were
+ to move it. This tag/mark might be moot if we just allow special comments
+ to be argument comments (which would allow them to be attached to a
+ particular argument). See the logic in PargGroupNode :code:`_reflow()`
+ where a comment is matched.
+26. :code:`max_prefix_chars` isn't exactly the right thing to switch on. What
+ we really want is something that depends on the current indentation level.
+ I think what we really want is :code:`min_suffix_chars`, or, rather, given
+ a :code:`prefix_length` and a current indentation, look at how many chars
+ are available for content. If that number is too small, then force nesting.
+ Think about this more and look at some cases based on a selection of
+ statement names and keywords. Maybe compute some statistics on these and
+ use that to inform the selection.
+27. Implement columnization, (successive kwarg children share are vertically
+ aligned, share a column).
+28. Implement per-command :code:`layout_passes`
+29. Add :code:`include` config option, allowing to refer to an external
+ configuration file.
diff --git a/cmake_format/doc/usage.rst b/cmake_format/doc/usage.rst
index 0ecc3a6..f05bde8 100644
--- a/cmake_format/doc/usage.rst
+++ b/cmake_format/doc/usage.rst
@@ -34,8 +34,13 @@ pleasant way.
# How many spaces to tab for indent
tab_size = 2
- # If arglists are longer than this, break them always
- max_subargs_per_line = 3
+ # If an argument group contains more than this many sub-groups (parg or kwarg
+ # groups), then force it to a vertical layout.
+ max_subgroups_hwrap = 2
+
+ # If a positinal argument group contains more than this many arguments, then
+ # force it to a vertical layout.
+ max_pargs_hwrap = 6
# If true, separate flow control names from their parentheses with a space
separate_ctrl_name_with_space = False
@@ -44,13 +49,21 @@ pleasant way.
separate_fn_name_with_space = False
# If a statement is wrapped to more than one line, than dangle the closing
- # parenthesis on it's own line
+ # parenthesis on it's own line.
dangle_parens = False
+ # If the trailing parenthesis must be 'dangled' on it's on line, then align it
+ # to this reference: `prefix`: the start of the statement, `prefix-indent`: the
+ # start of the statement, plus one indentation level, `child`: align to the
+ # column of the arguments
+ dangle_align = 'prefix'
+
+ min_prefix_chars = 4
+
# If the statement spelling length (including space and parenthesis is larger
# than the tab width by more than this amoung, then force reject un-nested
# layouts.
- max_prefix_chars = 2
+ max_prefix_chars = 10
# If a candidate layout is wrapped horizontally but it exceeds this many lines,
# then reject the layout.
@@ -77,9 +90,6 @@ pleasant way.
# A list of command names which should always be wrapped
always_wrap = []
- # Specify the order of wrapping algorithms during successive reflow attempts
- algorithm_order = [0, 1, 2, 3, 4]
-
# If true, the argument lists which are known to be sortable will be sorted
# lexicographicall
enable_sort = True
@@ -97,6 +107,10 @@ pleasant way.
# only `command_case` is supported.
per_command = {}
+ # A dictionary mapping layout nodes to a list of wrap decisions. See the
+ # documentation for more information.
+ layout_passes = {}
+
# --------------------------
# Comment Formatting Options
@@ -203,7 +217,7 @@ Usage
-i, --in-place
-o OUTFILE_PATH, --outfile-path OUTFILE_PATH
Where to write the formatted file. Default is stdout.
- -c CONFIG_FILE [CONFIG_FILE ...], --config-file CONFIG_FILE [CONFIG_FILE ...], --config-files CONFIG_FILE [CONFIG_FILE ...]
+ -c CONFIG_FILE [CONFIG_FILE ...], --config-file CONFIG_FILE [CONFIG_FILE ...], --config-files CONFIG_FILE [CONFIG_FILE ...], --config CONFIG_FILE [CONFIG_FILE ...]
path to configuration file(s)
Formatter Configuration:
@@ -212,8 +226,13 @@ Usage
--line-width LINE_WIDTH
How wide to allow formatted cmake files
--tab-size TAB_SIZE How many spaces to tab for indent
- --max-subargs-per-line MAX_SUBARGS_PER_LINE
- If arglists are longer than this, break them always
+ --max-subgroups-hwrap MAX_SUBGROUPS_HWRAP
+ If an argument group contains more than this many sub-
+ groups (parg or kwarg groups), then force it to a
+ vertical layout.
+ --max-pargs-hwrap MAX_PARGS_HWRAP
+ If a positinal argument group contains more than this
+ many arguments, then force it to a vertical layout.
--separate-ctrl-name-with-space [SEPARATE_CTRL_NAME_WITH_SPACE]
If true, separate flow control names from their
parentheses with a space
@@ -222,7 +241,14 @@ Usage
a space
--dangle-parens [DANGLE_PARENS]
If a statement is wrapped to more than one line, than
- dangle the closing parenthesis on it's own line
+ dangle the closing parenthesis on it's own line.
+ --dangle-align {prefix,prefix-indent,child,off}
+ If the trailing parenthesis must be 'dangled' on it's
+ on line, then align it to this reference: `prefix`:
+ the start of the statement, `prefix-indent`: the start
+ of the statement, plus one indentation level, `child`:
+ align to the column of the arguments
+ --min-prefix-chars MIN_PREFIX_CHARS
--max-prefix-chars MAX_PREFIX_CHARS
If the statement spelling length (including space and
parenthesis is larger than the tab width by more than
@@ -240,9 +266,6 @@ Usage
case
--always-wrap [ALWAYS_WRAP [ALWAYS_WRAP ...]]
A list of command names which should always be wrapped
- --algorithm-order [ALGORITHM_ORDER [ALGORITHM_ORDER ...]]
- Specify the order of wrapping algorithms during
- successive reflow attempts
--enable-sort [ENABLE_SORT]
If true, the argument lists which are known to be
sortable will be sorted lexicographicall
diff --git a/cmake_format/format_tests.py b/cmake_format/format_tests.py
deleted file mode 100644
index fdb6d1c..0000000
--- a/cmake_format/format_tests.py
+++ /dev/null
@@ -1,1284 +0,0 @@
-# -*- coding: utf-8 -*-
-# pylint: disable=too-many-lines
-
-from __future__ import unicode_literals
-import difflib
-import io
-import logging
-import os
-import sys
-import unittest
-
-from cmake_format import __main__
-from cmake_format import configuration
-from cmake_format import parse_funs
-
-
-def strip_indent(content, indent=6):
- """
- Strings used in this file are indented by 6-spaces to keep them readable
- within the python code that they are embedded. Remove those 6-spaces from
- the front of each line before running the tests.
- """
-
- # NOTE(josh): don't use splitlines() so that we get the same result
- # regardless of windows or unix line endings in content.
- return '\n'.join([line[indent:] for line in content.split('\n')])
-
-
-class TestCanonicalFormatting(unittest.TestCase):
- """
- Given a bunch of example inputs, ensure that the output is as expected.
- """
-
- def __init__(self, *args, **kwargs):
- super(TestCanonicalFormatting, self).__init__(*args, **kwargs)
- self.config = configuration.Configuration()
- self.parse_db = parse_funs.get_parse_db()
-
- def setUp(self):
- self.config.fn_spec.add(
- 'foo',
- flags=['BAR', 'BAZ'],
- kwargs={
- "HEADERS": '*',
- "SOURCES": '*',
- "DEPENDS": '*'
- })
- self.parse_db.update(
- parse_funs.get_legacy_parse(self.config.fn_spec).kwargs)
-
- def tearDown(self):
- pass
-
- def do_format_test(self, input_str, output_str, strip_len=6):
- """
- Run the formatter on the input string and assert that the result matches
- the output string
- """
-
- input_str = strip_indent(input_str, strip_len)
- output_str = strip_indent(output_str, strip_len)
-
- if sys.version_info[0] < 3:
- assert isinstance(input_str, unicode)
- actual_str = __main__.process_file(self.config, input_str)
- delta_lines = list(difflib.unified_diff(output_str.split('\n'),
- actual_str.split('\n')))
- delta = '\n'.join(delta_lines[2:])
-
- if actual_str != output_str:
- message = ('Input text:\n-----------------\n{}\n'
- 'Output text:\n-----------------\n{}\n'
- 'Expected Output:\n-----------------\n{}\n'
- 'Diff:\n-----------------\n{}'
- .format(input_str,
- actual_str,
- output_str,
- delta))
- if sys.version_info[0] < 3:
- message = message.encode('utf-8')
- raise AssertionError(message)
-
-
-class TestSomeExamples(TestCanonicalFormatting):
- """
- Given a bunch of example inputs, ensure that the output is as expected.
- """
-
- def test_collapse_additional_newlines(self):
- self.do_format_test("""\
- # The following multiple newlines should be collapsed into a single newline
-
-
-
-
- cmake_minimum_required(VERSION 2.8.11)
- project(cmake_format_test)
- """, """\
- # The following multiple newlines should be collapsed into a single newline
-
- cmake_minimum_required(VERSION 2.8.11)
- project(cmake_format_test)
- """)
-
- def test_multiline_reflow(self):
- self.do_format_test("""\
- # This multiline-comment should be reflowed
- # into a single comment
- # on one line
- """, """\
- # This multiline-comment should be reflowed into a single comment on one line
- """)
-
- def test_comment_before_command(self):
- self.config.max_subargs_per_line = 6
- self.do_format_test("""\
- # This comment should remain right before the command call.
- # Furthermore, the command call should be formatted
- # to a single line.
- add_subdirectories(foo bar baz
- foo2 bar2 baz2)
- """, """\
- # This comment should remain right before the command call. Furthermore, the
- # command call should be formatted to a single line.
- add_subdirectories(foo bar baz foo2 bar2 baz2)
- """)
-
- def test_long_args_command_split(self):
- self.do_format_test("""\
- # This very long command should be split to multiple lines
- set(HEADERS very_long_header_name_a.h very_long_header_name_b.h very_long_header_name_c.h)
- """, """\
- # This very long command should be split to multiple lines
- set(HEADERS very_long_header_name_a.h very_long_header_name_b.h
- very_long_header_name_c.h)
- """)
-
- def test_lots_of_args_command_split(self):
- self.do_format_test("""\
- # This command should be split into one line per entry because it has a long
- # argument list.
- set(SOURCES source_a.cc source_b.cc source_d.cc source_e.cc source_f.cc source_g.cc)
- """, """\
- # This command should be split into one line per entry because it has a long
- # argument list.
- set(SOURCES
- source_a.cc
- source_b.cc
- source_d.cc
- source_e.cc
- source_f.cc
- source_g.cc)
- """)
-
- # TODO(josh): figure out why this test elicits different behavior than the
- # whole-file demo.
- def test_string_preserved_during_split(self):
- self.do_format_test("""\
- # The string in this command should not be split
- set_target_properties(foo bar baz PROPERTIES COMPILE_FLAGS "-std=c++11 -Wall -Wextra")
- """, """\
- # The string in this command should not be split
- set_target_properties(foo bar baz
- PROPERTIES COMPILE_FLAGS "-std=c++11 -Wall -Wextra")
- """)
-
- def test_long_arg_on_newline(self):
- self.do_format_test("""\
- # This command has a very long argument and can't be aligned with the command
- # end, so it should be moved to a new line with block indent + 1.
- some_long_command_name("Some very long argument that really needs to be on the next line.")
- """, """\
- # This command has a very long argument and can't be aligned with the command
- # end, so it should be moved to a new line with block indent + 1.
- some_long_command_name(
- "Some very long argument that really needs to be on the next line.")
- """)
-
- def test_long_kwargarg_on_newline(self):
- self.do_format_test("""\
- # This situation is similar but the argument to a KWARG needs to be on a
- # newline instead.
- set(CMAKE_CXX_FLAGS "-std=c++11 -Wall -Wno-sign-compare -Wno-unused-parameter -xx")
- """, """\
- # This situation is similar but the argument to a KWARG needs to be on a newline
- # instead.
- set(CMAKE_CXX_FLAGS
- "-std=c++11 -Wall -Wno-sign-compare -Wno-unused-parameter -xx")
- """)
-
- def test_argcomment_preserved_and_reflowed(self):
- self.do_format_test("""\
- set(HEADERS header_a.h header_b.h # This comment should
- # be preserved, moreover it should be split
- # across two lines.
- header_c.h header_d.h)
- """, """\
- set(HEADERS
- header_a.h
- header_b.h # This comment should be preserved, moreover it should be split
- # across two lines.
- header_c.h
- header_d.h)
- """)
-
- def test_argcomments_force_reflow(self):
- self.config.line_width = 140
- self.do_format_test("""\
- cmake_parse_arguments(ARG
- "SILENT" # optional keywords
- "" # one value keywords
- "" # multi value keywords
- ${ARGN})
- """, """\
- cmake_parse_arguments(ARG
- "SILENT" # optional keywords
- "" # one value keywords
- "" # multi value keywords
- ${ARGN})
- """)
-
- def test_format_off(self):
- self.do_format_test("""\
- # This part of the comment should
- # be formatted
- # but...
- # cmake-format: off
- # This bunny should remain untouched:
- # . _ ∩
- # レヘヽ| |
- # (・x・)
- # c( uu}
- # cmake-format: on
- # while this part should
- # be formatted again
- """, """\
- # This part of the comment should be formatted but...
- # cmake-format: off
- # This bunny should remain untouched:
- # . _ ∩
- # レヘヽ| |
- # (・x・)
- # c( uu}
- # cmake-format: on
- # while this part should be formatted again
- """)
-
- def test_paragraphs_preserved(self):
- self.do_format_test("""\
- # This is a paragraph
- #
- # This is a second paragraph
- #
- # This is a third paragraph
- """, """\
- # This is a paragraph
- #
- # This is a second paragraph
- #
- # This is a third paragraph
- """)
-
- def test_todo_preserved(self):
- self.do_format_test("""\
- # This is a comment
- # that should be joined but
- # TODO(josh): This todo should not be joined with the previous line.
- # NOTE(josh): Also this should not be joined with the todo.
- """, """\
- # This is a comment that should be joined but
- # TODO(josh): This todo should not be joined with the previous line.
- # NOTE(josh): Also this should not be joined with the todo.
- """)
-
- def test_complex_nested_stuff(self):
- self.config.autosort = False
- self.do_format_test("""\
- if(foo)
- if(sbar)
- # This comment is in-scope.
- add_library(foo_bar_baz foo.cc bar.cc # this is a comment for arg2
- # this is more comment for arg2, it should be joined with the first.
- baz.cc) # This comment is part of add_library
-
- other_command(some_long_argument some_long_argument) # this comment is very long and gets split across some lines
-
- other_command(some_long_argument some_long_argument some_long_argument) # this comment is even longer and wouldn't make sense to pack at the end of the command so it gets it's own lines
- endif()
- endif()
- """, """\
- if(foo)
- if(sbar)
- # This comment is in-scope.
- add_library(foo_bar_baz
- foo.cc
- bar.cc # this is a comment for arg2 this is more comment for
- # arg2, it should be joined with the first.
- baz.cc) # This comment is part of add_library
-
- other_command(some_long_argument some_long_argument) # this comment is very
- # long and gets split
- # across some lines
-
- other_command(some_long_argument some_long_argument some_long_argument)
- # this comment is even longer and wouldn't make sense to pack at the end of
- # the command so it gets it's own lines
- endif()
- endif()
- """)
-
- def test_custom_command(self):
- self.do_format_test("""\
- # This very long command should be broken up along keyword arguments
- foo(nonkwarg_a nonkwarg_b HEADERS a.h b.h c.h d.h e.h f.h SOURCES a.cc b.cc d.cc DEPENDS foo bar baz)
- """, """\
- # This very long command should be broken up along keyword arguments
- foo(nonkwarg_a nonkwarg_b
- HEADERS a.h
- b.h
- c.h
- d.h
- e.h
- f.h
- SOURCES a.cc b.cc d.cc
- DEPENDS foo
- bar baz)
- """)
-
- def test_always_wrap(self):
- self.do_format_test("""\
- foo(nonkwarg_a HEADERS a.h SOURCES a.cc DEPENDS foo)
- """, """\
- foo(nonkwarg_a HEADERS a.h SOURCES a.cc DEPENDS foo)
- """)
- self.config.always_wrap = ['foo']
- self.do_format_test("""\
- foo(nonkwarg_a HEADERS a.h SOURCES a.cc DEPENDS foo)
- """, """\
- foo(nonkwarg_a
- HEADERS a.h
- SOURCES a.cc
- DEPENDS foo)
- """)
-
- def test_multiline_string(self):
- self.do_format_test("""\
- foo(some_arg some_arg "
- This string is on multiple lines
- ")
- """, """\
- foo(some_arg some_arg "
- This string is on multiple lines
- ")
- """)
-
- def test_some_string_stuff(self):
- self.do_format_test("""\
- # This command uses a string with escaped quote chars
- foo(some_arg some_arg "This is a \\"string\\" within a string")
-
- # This command uses an empty string
- foo(some_arg some_arg "")
-
- # This command uses a multiline string
- foo(some_arg some_arg "
- This string is on multiple lines
- ")
- """, """\
- # This command uses a string with escaped quote chars
- foo(some_arg some_arg "This is a \\"string\\" within a string")
-
- # This command uses an empty string
- foo(some_arg some_arg "")
-
- # This command uses a multiline string
- foo(some_arg some_arg "
- This string is on multiple lines
- ")
- """)
-
- def test_format_off_code(self):
- self.do_format_test("""\
- # No, I really want this to look ugly
- # cmake-format: off
- add_library(a b.cc
- c.cc d.cc
- e.cc)
- # cmake-format: on
- """, """\
- # No, I really want this to look ugly
- # cmake-format: off
- add_library(a b.cc
- c.cc d.cc
- e.cc)
- # cmake-format: on
- """)
-
- def test_multiline_statement_comment_idempotent(self):
- self.do_format_test("""\
- set(HELLO hello world!) # TODO(josh): fix this bad code with some change that
- # takes mutiple lines to explain
- """, """\
- set(HELLO hello world!) # TODO(josh): fix this bad code with some change that
- # takes mutiple lines to explain
- """)
-
- def test_function_def(self):
- self.do_format_test("""\
- function(forbarbaz arg1)
- do_something(arg1 ${ARGN})
- endfunction()
- """, """\
- function(forbarbaz arg1)
- do_something(arg1 ${ARGN})
- endfunction()
- """)
-
- def test_macro_def(self):
- self.do_format_test("""\
- macro(forbarbaz arg1)
- do_something(arg1 ${ARGN})
- endmacro()
- """, """\
- macro(forbarbaz arg1)
- do_something(arg1 ${ARGN})
- endmacro()
- """)
-
- def test_foreach(self):
- self.config.max_subargs_per_line = 6
- self.do_format_test("""\
- foreach(forbarbaz arg1 arg2 arg3)
- message(hello ${foobarbaz})
- endforeach()
- """, """\
- foreach(forbarbaz arg1 arg2 arg3)
- message(hello ${foobarbaz})
- endforeach()
- """)
-
- def test_while(self):
- self.config.max_subargs_per_line = 6
- self.do_format_test("""\
-
- while(forbarbaz arg1 arg2 arg3)
- message(hello ${foobarbaz})
- endwhile()
- """, """\
- while(forbarbaz arg1 arg2 arg3)
- message(hello ${foobarbaz})
- endwhile()
- """)
-
- def test_ctrl_space(self):
- self.config.separate_ctrl_name_with_space = True
- self.do_format_test("""\
- if(foo)
- myfun(foo bar baz)
- endif()
- """, """\
- if (foo)
- myfun(foo bar baz)
- endif ()
- """)
-
- def test_fn_space(self):
- self.config.separate_fn_name_with_space = True
- self.do_format_test("""\
- myfun(foo bar baz)
- """, """\
- myfun (foo bar baz)
- """)
-
- def test_preserve_separator(self):
- self.do_format_test("""\
- # --------------------
- # This is some
- # text that I expect
- # to reflow
- # --------------------
- """, """\
- # --------------------
- # This is some text that I expect to reflow
- # --------------------
- """)
-
- self.do_format_test("""\
- # !@#$^&*!@#$%^&*!@#$%^&*!@#$%^&*
- # This is some
- # text that I expect
- # to reflow
- # !@#$^&*!@#$%^&*!@#$%^&*!@#$%^&*
- """, """\
- # !@#$^&*!@#$%^&*!@#$%^&*!@#$%^&*
- # This is some text that I expect to reflow
- # !@#$^&*!@#$%^&*!@#$%^&*!@#$%^&*
- """)
-
- self.do_format_test("""\
- # ----Not Supported----
- # This is some
- # text that I expect
- # to reflow
- # ----Not Supported----
- """, """\
- # ----Not Supported----
- # This is some text that I expect to reflow
- # ----Not Supported----
- """)
-
- def test_bullets(self):
- self.do_format_test("""\
- # This is a bulleted list:
- #
- # * item 1
- # * item 2
- # this line gets merged with item 2
- # * item 3 is really long and needs to be wrapped to a second line because it wont all fit on one line without wrapping.
- #
- # But the list has ended and this line is free. And
- # * this is not a bulleted list
- # * and it will be
- # * merged
- """, """\
- # This is a bulleted list:
- #
- # * item 1
- # * item 2 this line gets merged with item 2
- # * item 3 is really long and needs to be wrapped to a second line because it
- # wont all fit on one line without wrapping.
- #
- # But the list has ended and this line is free. And * this is not a bulleted
- # list * and it will be * merged
- """)
-
- def test_enum_lists(self):
- self.do_format_test("""\
- # This is a bulleted list:
- #
- # 1. item
- # 2. item
- # 3. item
- #
- # 4. item
- # 5. item
- # 6. item
- #
- # 1. item
- # 3. item
- # 5. item
- # 6. item
- # 6. item is really long and needs to be wrapped to a second line because it wont all fit on one line without wrapping.
- # 7. item
- # 9. item
- # 9. item
- # 9. item
- # 9. item
- # 9. item
- #
- """, """\
- # This is a bulleted list:
- #
- # 1. item
- # 2. item
- # 3. item
- #
- # 1. item
- # 2. item
- # 3. item
- #
- # 1. item
- # 2. item
- # 3. item
- # 4. item
- # 5. item is really long and needs to be wrapped to a second line because it wont
- # all fit on one line without wrapping.
- # 6. item
- # 7. item
- # 8. item
- # 9. item
- # 10. item
- # 11. item
- #
- """)
-
- def test_nested_bullets(self):
- self.do_format_test("""\
- # This is a bulleted list:
- #
- # * item 1
- # * item 2
- #
- # * item 3
- # * item 4
- #
- # * item 5
- # * item 6
- #
- # * item 7
- # * item 8
- """, """\
- # This is a bulleted list:
- #
- # * item 1
- # * item 2
- #
- # * item 3
- # * item 4
- #
- # * item 5
- # * item 6
- #
- # * item 7
- # * item 8
- """)
-
- def test_comment_fence(self):
- self.do_format_test("""\
- # ~~~~~~
- # This is some
- # verbatim text
- # that should not be
- # formatted
- # ```````
- """, """\
- # ~~~
- # This is some
- # verbatim text
- # that should not be
- # formatted
- # ~~~
- """)
-
- def test_bracket_comments(self):
-
- self.do_format_test("""\
- #[[This is a bracket comment.
- It is preserved verbatim, but trailing whitespace is removed.
- So things like --this-- Are fine:]]
- """, """\
- #[[This is a bracket comment.
- It is preserved verbatim, but trailing whitespace is removed.
- So things like --this-- Are fine:]]
- """)
-
- self.do_format_test("""\
- if(foo)
- #[==[This is a bracket comment at some nested level
- # it is preserved verbatim, but trailing
- # whitespace is removed.]==]
- endif()
- """, """\
- if(foo)
- #[==[This is a bracket comment at some nested level
- # it is preserved verbatim, but trailing
- # whitespace is removed.]==]
- endif()
- """)
-
- # Make sure bracket comments are kept inline in their function call
- self.do_format_test("""\
- message("First Argument" #[[Bracket Comment]] "Second Argument")
- """, """\
- message("First Argument" #[[Bracket Comment]] "Second Argument")
- """)
-
- def test_comment_after_command(self):
- self.do_format_test("""\
- foo_command() # comment
- """, """\
- foo_command() # comment
- """)
-
- self.do_format_test("""\
- foo_command() # this is a long comment that exceeds the desired page width and will be wrapped to a newline
- """, """\
- foo_command() # this is a long comment that exceeds the desired page width and
- # will be wrapped to a newline
- """)
-
- def test_arg_just_fits(self):
- """
- Ensure that if an argument *just* fits that it isn't superfluously wrapped
- """
-
- self.do_format_test("""\
- message(FATAL_ERROR "81 character line ----------------------------------------")
- """, """\
- message(
- FATAL_ERROR "81 character line ----------------------------------------")
- """)
-
- self.do_format_test("""\
- message(FATAL_ERROR
- "100 character line ----------------------------------------------------------"
- ) # Closing parenthesis is indented one space!
- """, """\
- message(
- FATAL_ERROR
- "100 character line ----------------------------------------------------------"
- ) # Closing parenthesis is indented one space!
- """)
-
- self.do_format_test("""\
- message(
- "100 character line ----------------------------------------------------------------------"
- ) # Closing parenthesis is indented one space!
- """, """\
- message(
- "100 character line ----------------------------------------------------------------------"
- ) # Closing parenthesis is indented one space!
- """)
-
- def test_dangle_parens(self):
- self.config.dangle_parens = True
- self.config.max_subargs_per_line = 6
- self.do_format_test("""\
- foo_command()
- foo_command(arg1)
- foo_command(arg1) # comment
- """, """\
- foo_command()
- foo_command(arg1)
- foo_command(arg1) # comment
- """)
-
- self.do_format_test("""\
- some_long_command_name(longargname longargname longargname longargname longargname)
- """, """\
- some_long_command_name(longargname longargname longargname longargname
- longargname)
- """)
-
- self.do_format_test("""\
- if(foo)
- some_long_command_name(longargname longargname longargname longargname longargname)
- endif()
- """, """\
- if(foo)
- some_long_command_name(longargname longargname longargname longargname
- longargname)
- endif()
- """)
-
- self.do_format_test("""\
- some_long_command_name(longargname longargname longargname longargname longargname longargname longargname longargname)
- """, """\
- some_long_command_name(
- longargname
- longargname
- longargname
- longargname
- longargname
- longargname
- longargname
- longargname
- )
- """)
-
- self.do_format_test("""\
- target_include_directories(target INTERFACE $)
- """, """\
- target_include_directories(
- target
- INTERFACE $
- )
- """)
-
- def test_windows_line_endings_input(self):
- self.do_format_test(
- " #[[*********************************************\r\n"
- " * Information line 1\r\n"
- " * Information line 2\r\n"
- " ************************************************]]\r\n", """\
- #[[*********************************************
- * Information line 1
- * Information line 2
- ************************************************]]\n""")
-
- def test_windows_line_endings_output(self):
- config_dict = self.config.as_dict()
- config_dict['line_ending'] = 'windows'
- self.config = configuration.Configuration(**config_dict)
-
- self.do_format_test(
- """\
- #[[*********************************************
- * Information line 1
- * Information line 2
- ************************************************]]""",
- " #[[*********************************************\r\n"
- " * Information line 1\r\n"
- " * Information line 2\r\n"
- " ************************************************]]\r\n")
-
- 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(
- " #[[*********************************************\r\n"
- " * Information line 1\r\n"
- " * Information line 2\r\n"
- " ************************************************]]\r\n",
- " #[[*********************************************\r\n"
- " * Information line 1\r\n"
- " * Information line 2\r\n"
- " ************************************************]]\r\n")
-
- def test_keyword_case(self):
- config_dict = self.config.as_dict()
- config_dict['keyword_case'] = 'upper'
- self.config = configuration.Configuration(**config_dict)
- self.do_format_test(
- """\
- foo(bar baz)
- """, """\
- foo(BAR BAZ)
- """)
-
- config_dict = self.config.as_dict()
- config_dict['keyword_case'] = 'lower'
- self.config = configuration.Configuration(**config_dict)
-
- self.do_format_test(
- """\
- 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("""\
- foo(BaR bAz)
- """, """\
- foo(bar baz)
- """)
-
- def test_command_case(self):
- self.do_format_test(
- """\
- 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(
- """\
- 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("""\
- FoO(bar baz)
- """, """\
- FoO(bar baz)
- """)
-
- def test_comment_in_statement(self):
- self.do_format_test("""\
- add_library(foo
- # This comment is not attached to an argument
- bar.cc
- foo.cc)
- """, """\
- add_library(foo
- # This comment is not attached to an argument
- bar.cc foo.cc)
- """)
-
- def test_comment_at_end_of_statement(self):
- self.do_format_test("""\
- add_library(foo bar.cc foo.cc
- # This comment is not attached to an argument
- )
- """, """\
- add_library(foo
- bar.cc foo.cc
- # This comment is not attached to an argument
- )
- """)
-
- self.do_format_test("""\
- target_link_libraries(libraryname PUBLIC
- ${COMMON_LIBRARIES}
- # add more library dependencies here
- )
- """, """\
- target_link_libraries(libraryname
- PUBLIC ${COMMON_LIBRARIES}
- # add more library dependencies here
- )
- """)
-
- self.do_format_test("""\
- find_package(foobar REQUIRED
- COMPONENTS some_component
- # some_other_component
- # This is a very long comment, and actually the second comment in this row.
- )
- """, """\
- find_package(foobar REQUIRED
- COMPONENTS some_component
- # some_other_component
- # This is a very long comment, and actually the second
- # comment in this row.
- )
- """)
-
- def test_comment_in_kwarg(self):
- self.do_format_test("""\
- install(TARGETS foob
- ARCHIVE DESTINATION foobar
- # this is a line comment, not a comment on foobar
- COMPONENT baz)
- """, """\
- install(TARGETS foob
- ARCHIVE DESTINATION foobar
- # this is a line comment, not a comment on foobar
- COMPONENT baz)
- """)
-
- def test_algoorder_preference(self):
- self.config.max_subargs_per_line = 10
- self.do_format_test("""\
- some_long_command_name(longargument longargument longargument longargument
- longargument longargument)
- """, """\
- some_long_command_name(longargument longargument longargument longargument
- longargument longargument)
- """)
-
- self.config.algorithm_order = [0, 3]
- self.do_format_test("""\
- some_long_command_name(longargument longargument longargument longargument
- longargument longargument)
- """, """\
- some_long_command_name(
- longargument longargument longargument longargument longargument longargument)
- """)
-
- def test_elseif(self):
- self.do_format_test("""\
- if(MSVC)
-
- elseif((CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
- OR CMAKE_COMPILER_IS_GNUCC
- OR CMAKE_COMPILER_IS_GNUCXX)
-
- endif()
- """, """\
- if(MSVC)
-
- elseif((CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
- OR CMAKE_COMPILER_IS_GNUCC
- OR CMAKE_COMPILER_IS_GNUCXX)
-
- endif()
- """)
-
- def test_elseif_else_control_space(self):
- self.config.separate_ctrl_name_with_space = True
- self.do_format_test("""\
- if(foo)
- elseif(bar)
- else()
- endif()
- """, """\
- if (foo)
-
- elseif (bar)
-
- else ()
-
- endif ()
- """)
-
- def test_disable_markup(self):
- self.config.enable_markup = False
- self.do_format_test("""\
- # don't reflow
- # or parse markup
- # for these lines
- """, """\
- # don't reflow
- # or parse markup
- # for these lines
- """)
-
- def test_literal_first_comment(self):
- self.do_format_test("""\
- # This comment
- # is reflowed
-
- # This comment
- # is reflowed
- """, """\
- # This comment is reflowed
-
- # This comment is reflowed
- """)
-
- self.config.first_comment_is_literal = True
- self.do_format_test("""\
- # This comment
- # is not reflowed
-
- # This comment
- # is reflowed
- """, """\
- # This comment
- # is not reflowed
-
- # This comment is reflowed
- """)
-
- def test_shebang_preserved(self):
- self.do_format_test("""\
- #!/usr/bin/cmake -P
- """, """\
- #!/usr/bin/cmake -P
- """)
-
- def test_preserve_copyright(self):
- self.do_format_test("""\
- # Copyright 2018: Josh Bialkowski
- # This text should not be reflowed
- # because it's a copyright
- """, """\
- # Copyright 2018: Josh Bialkowski This text should not be reflowed because it's
- # a copyright
- """)
-
- self.config.literal_comment_pattern = " Copyright.*"
- self.do_format_test("""\
- # Copyright 2018: Josh Bialkowski
- # This text should not be reflowed
- # because it's a copyright
- """, """\
- # Copyright 2018: Josh Bialkowski
- # This text should not be reflowed
- # because it's a copyright
- """)
-
- def test_kwarg_match_consumes(self):
- self.do_format_test("""\
- add_test(NAME myTestName COMMAND testCommand --run_test=@quick)
- """, """\
- add_test(NAME myTestName COMMAND testCommand --run_test=@quick)
- """)
-
- def test_byte_order_mark(self):
- self.do_format_test("""\
- \ufeffcmake_minimum_required(VERSION 2.8.11)
- project(cmake_format_test)
- """, """\
- cmake_minimum_required(VERSION 2.8.11)
- project(cmake_format_test)
- """)
-
- self.config.emit_byteorder_mark = True
- self.do_format_test("""\
- cmake_minimum_required(VERSION 2.8.11)
- project(cmake_format_test)
- """, """\
- \ufeffcmake_minimum_required(VERSION 2.8.11)
- project(cmake_format_test)
- """)
-
- def test_percommand_override(self):
- self.do_format_test("""\
- FoO(bar baz)
- """, """\
- foo(bar baz)
- """)
-
- self.config.per_command["foo"] = {
- "command_case": "unchanged"
- }
- self.do_format_test("""\
- FoO(bar baz)
- """, """\
- FoO(bar baz)
- """)
-
- def test_quoted_assignment_literal(self):
- self.do_format_test("""\
- target_compile_definitions(foo PUBLIC BAR="Quoted String" BAZ_______________________Z)
- """, """\
- target_compile_definitions(foo
- PUBLIC
- BAR="Quoted String"
- BAZ_______________________Z)
- """)
-
- def test_keyword_comment(self):
- self.do_format_test("""\
- find_package(package REQUIRED
- COMPONENTS # --------------------------------------
- # @TODO: This has to be filled manually
- # --------------------------------------
- this_is_a_really_long_word_foo)
- """, """\
- find_package(package REQUIRED
- COMPONENTS # --------------------------------------
- # @TODO: This has to be filled manually
- # --------------------------------------
- this_is_a_really_long_word_foo)
- """)
-
- def test_example_file(self):
- thisdir = os.path.dirname(__file__)
- infile_path = os.path.join(thisdir, 'test', 'test_in.cmake')
- outfile_path = os.path.join(thisdir, 'test', 'test_out.cmake')
-
- with io.open(infile_path, 'r', encoding='utf8') as infile:
- infile_text = infile.read()
- with io.open(outfile_path, 'r', encoding='utf8') as outfile:
- outfile_text = outfile.read()
-
- self.do_format_test(infile_text, outfile_text, strip_len=0)
-
- def test_one_char_short_hpack_rparen_case(self):
- # This was a particularly rare edge case. The situation is that the
- # the arguments are one character shy of fitting in the configured line
- # width, the statement column is the same as the indent column, and
- # all the arguments are positional. The problem was that the hpack if
- # possible logic did not account for the final paren. A fix is in place.
- self.config.line_width = 132
- self.config.tab_size = 4
- self.do_format_test("""
- set(cubepp_HDRS
- ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cubepp/AppDelegate.h
- ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cubepp/DemoViewController.h)
- """, """\
- set(cubepp_HDRS ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cubepp/AppDelegate.h
- ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cubepp/DemoViewController.h)
- """)
-
- def test_rulers_preserved_without_markup(self):
- self.config.enable_markup = False
- self.do_format_test("""
- #########################################################################
- # Custom targets
- #########################################################################
- """, """\
- #########################################################################
- # Custom targets
- #########################################################################
- """)
-
- def test_canonical_spelling(self):
- self.do_format_test("""\
- ExternalProject_Add(
- foobar
- URL https://foobar.baz/latest.tar.gz
- TLS_VERIFY TRUE
- CONFIGURE_COMMAND configure
- BUILD_COMMAND make
- INSTALL_COMMAND make install)
- """, """
- ExternalProject_Add(foobar
- URL https://foobar.baz/latest.tar.gz
- TLS_VERIFY TRUE
- CONFIGURE_COMMAND configure
- BUILD_COMMAND make
- INSTALL_COMMAND make install)
- """[1:])
-
- def test_comment_hashrulers(self):
- self.config.line_width = 74
- self.do_format_test("""
- ##################
- # This comment has a long block before it.
- #############
- """, """\
- # ########################################################################
- # This comment has a long block before it.
- # ########################################################################
- """)
- self.do_format_test("""
- ###############################################
- # This is a section in the CMakeLists.txt file.
- ############
- # This stuff below here
- # should get re-flowed like
- # normal comments. Across multiple
- #lines and
- # beyond.
- """, """\
- # ########################################################################
- # This is a section in the CMakeLists.txt file.
- # ########################################################################
- # This stuff below here should get re-flowed like normal comments. Across
- # multiple lines and beyond.
- """)
-
- # verify the original behavior is as described (they truncate to one #)
- self.config.hashruler_min_length = 1000
- self.do_format_test("""
- ##########################################################################
- # This comment has a long block before it.
- ##########################################################################
- """, """\
- #
- # This comment has a long block before it.
- #
- """)
- self.do_format_test("""
- ##########################################################################
- # This is a section in the CMakeLists.txt file.
- ##########################################################################
- # This stuff below here
- # should get re-flowed like
- # normal comments. Across multiple
- #lines and
- # beyond.
- """, """\
- #
- # This is a section in the CMakeLists.txt file.
- #
- # This stuff below here should get re-flowed like normal comments. Across
- # multiple lines and beyond.
- """)
-
- # make sure changing hashruler_min_length works correctly
- # self.config.enable_markup = False
- for min_width in {3, 5, 7, 9}:
- self.config.hashruler_min_length = min_width
-
- # NOTE(josh): these tests use short rulers that wont be picked up by
- # the default pattern
- self.config.ruler_pattern = (r'#{%d}#*' % (min_width - 1))
-
- just_shy = '#' * (min_width - 1)
- just_right = '#' * min_width
- longer = '#' * (min_width + 2)
- full_line = '#' * (self.config.line_width - 2)
-
- self.do_format_test("""
- # A comment: min_width={min_width}, just_shy
- {just_shy}
- """.format(min_width=min_width, just_shy=just_shy), """\
- # A comment: min_width={min_width}, just_shy
- #
- """.format(min_width=min_width))
-
- self.do_format_test("""
- # A comment: min_width={min_width}, just_right
- {just_right}
- """.format(min_width=min_width, just_right=just_right), """\
- # A comment: min_width={min_width}, just_right
- # {full_line}
- """.format(min_width=min_width, full_line=full_line))
-
- self.do_format_test("""
- # A comment: min_width={min_width}, longer
- {longer}
- """.format(min_width=min_width, longer=longer), """\
- # A comment: min_width={min_width}, longer
- # {full_line}
- """.format(min_width=min_width, full_line=full_line))
-
-
-if __name__ == '__main__':
- format_str = '[%(levelname)-4s] %(filename)s:%(lineno)-3s: %(message)s'
- logging.basicConfig(level=logging.DEBUG,
- format=format_str,
- datefmt='%Y-%m-%d %H:%M:%S',
- filemode='w')
- unittest.main()
diff --git a/cmake_format/formatter.py b/cmake_format/formatter.py
index 34d496b..a81cd93 100644
--- a/cmake_format/formatter.py
+++ b/cmake_format/formatter.py
@@ -3,12 +3,12 @@
from __future__ import print_function
from __future__ import unicode_literals
+import contextlib
import io
import logging
import re
import sys
-from cmake_format import common
from cmake_format import lexer
from cmake_format import markup
from cmake_format import parser
@@ -28,9 +28,6 @@
MATCH_TYPES = BLOCK_TYPES + GROUP_TYPES + SCALAR_TYPES + PAREN_TYPES
-LAYOUT_PASS_COUNT = 4
-USE_NEW_ALGORITHM = False
-
def clamp(value, min_value, max_value):
"""Simple double-ended saturation function."""
@@ -90,22 +87,6 @@ def normalize_line_endings(instr):
return re.sub('[ \t\f\v]*((\r?\n)|(\r\n?))', '\n', instr)
-# TODO(josh): remove this
-class WrapAlgo(common.EnumObject):
- """
- Packing algorithm used
- """
- _id_map = {}
-
-
-WrapAlgo.HPACK = WrapAlgo(0) # Horizontal packing: no wrapping
-WrapAlgo.HWRAP = WrapAlgo(1) # Horizontal wrapping
-WrapAlgo.VPACK = WrapAlgo(2) # Vertical packing: each element on it's own line
-WrapAlgo.KWNVPACK = WrapAlgo(3) # keyword nested vertical packing
-WrapAlgo.PNVPACK = WrapAlgo(4) # parentheses nested vertical packing
-WrapAlgo.COUNT = WrapAlgo(5)
-
-
def need_paren_space(spelling, config):
"""
Return whether or not we need a space between the statement name and the
@@ -124,6 +105,23 @@ def need_paren_space(spelling, config):
return config.separate_fn_name_with_space
+def is_line_comment(node):
+ """
+ Return true if the node is a pure parser node holding a line comment (i.e.
+ not a bracket comment)
+ """
+ if isinstance(node, CommentNode):
+ node = node.pnode
+
+ if not isinstance(node, parser.TreeNode):
+ return False
+
+ if not node.children:
+ return False
+
+ return node.children[-1].type == TokenType.COMMENT
+
+
class Cursor(object):
"""
Lightweight class to encode integer positions in a 2d grid.
@@ -137,12 +135,19 @@ def __init__(self, x, y):
self.y = y # col
def __add__(self, other):
+ """Cursor addition is element-wise (i.e. vector) addition"""
return Cursor(self.x + other[0], self.y + other[1])
def __sub__(self, other):
+ """Cursor subtraction is element-wise (i.e. vector) subtraction"""
return Cursor(self.x - other[0], self.y - other[1])
def __getitem__(self, idx):
+ """
+ A cursor can be accessed as a two-element array. For a cursor `c`,
+ `c[0]` refers to the row (`x`) and `c[1]` refers to the column (`y`).
+ """
+
if idx == 0:
return self.x
if idx == 1:
@@ -151,6 +156,11 @@ def __getitem__(self, idx):
raise IndexError('Cursor indices must be 0 or 1')
def __setitem__(self, idx, value):
+ """
+ Cursor elements can be assigned as a two-element array. For a cursor `c`,
+ `c[0]` refers to the row (`x`) and `c[1]` refers to the column (`y`).
+ """
+
if idx == 0:
self.x = value
return
@@ -161,79 +171,37 @@ def __setitem__(self, idx, value):
raise IndexError('Cursor indices must be 0 or 1')
def __repr__(self):
+ """
+ String representation is like "Cursor(12,34)"
+ """
return "Cursor({},{})".format(self.x, self.y)
+ def clone(self):
+ """
+ Return a new `Cursor` object with the same value as this one.
+ """
+ return Cursor(*self)
-def default_accept_layout(
- config, node_path, nested, vertical, start_extent, end_extent):
+
+class StackContext(object):
"""
- Return true if the given layout is acceptable.
+ Aggregate information about the current stack. This object is passed down
+ through all of the nested :code:`reflow()` function calls.
"""
- # If the bounding box overflows the column limit then the layout is
- # automatically voided
- if end_extent[1] > config.linewidth:
- return False
-
- # Commands are never wrapped vertically
- if node_path[-1] == "COMMAND":
- if vertical:
- return False
-
- size = end_extent - start_extent
- depth = len(node_path) - 1
-
- prefix_width = len(node_path[-1])
- if depth == 0:
- # If depth is zero, then we are at statement-depth, and we need to account
- # for extra characters when determining the width of the prefix
- prefix_width += 1 # For the left-paren
- if need_paren_space(node_path[-1], config):
- prefix_width += 1 # For the space before the paren
-
- if not vertical:
- # Regardless of nesting, if the content is wrapped horizontally but it
- # exceeds the configured maximum number of lines we must reject it
- # TODO(josh): figure out how to subtract out any terminal comment
- # contributions to the size, as noted in the algorithm doc.
- if size[0] > config.max_lines_hwrap:
- return False
-
- # Or if this nodepath is marked to always be vertical layout
- pathstr = "/".join(node_path)
- if pathstr in config.always_wrap:
- return False
-
- if nested:
- # If the statement or keyword spelling is too short, then nesting doesn't
- # make sense because (nest + tab-width) will take us right back to the
- # same column as without nesting.
- if prefix_width <= config.tab_size:
- return False
- else:
- # If the statement or keyword spelling is too long, then any wrapping also
- # requires nesting.
- if size[0] > 1:
- if prefix_width - config.tab_size > config.max_prefix_chars:
- return False
-
- return True
-
+ def __init__(self, config):
+ self.config = config
+ self.node_path = []
-def get_nest_wrap(passno):
- """
- Return (nest, wrap) booleans depending on the integer passno
- """
- if passno > 3:
- logger.warning("Invalid passno: %d", passno)
- passno = 3
- # raise ValueError("Invalid passno: {}".format(passno))
- return [
- (False, False),
- (False, True),
- (True, False),
- (True, True)
- ][passno]
+ @contextlib.contextmanager
+ def push_node(self, node):
+ """
+ Push `node` onto the `node_path` and yield a context manager. Pop `node`
+ off of `node_path` when the context manager `__exit__()s`
+ """
+ self.node_path.append(node)
+ yield None
+ self.node_path.pop(-1)
class LayoutNode(object):
@@ -246,7 +214,6 @@ class LayoutNode(object):
def __init__(self, pnode):
self.pnode = pnode
- self._wrap = WrapAlgo.HPACK
self._position = Cursor(0, 0) # NOTE(josh): (row, col)
self._size = Cursor(0, 0) # NOTE(josh): (rows, cols)
@@ -278,48 +245,88 @@ def __init__(self, pnode):
# `subtree_depth` have been computed and stored.
self._locked = False
+ # Map subpass number to (passno, wrap) decisions
+ self._layout_passes = [(0, False)]
+
+ # Set to true if this node's wrap is activated. Note that _wrap may
+ # refer to nesting or vertical layout, depending on what type of node
+ # we are. This is value only really needs to be communicated between
+ # `reflow()` and `_reflow()` but we store it so that we can render it
+ # when viewing the tree for debugging.
+ self._wrap = False
+
assert isinstance(pnode, parser.TreeNode)
@property
def name(self):
- # pylint: disable=protected-access
+ """
+ The class name of the derived node type.
+ """
return self.__class__.__name__
+ @property
+ def passno(self):
+ """
+ The active pass-number which contributed the current layout of the
+ subtree rooted at this node.
+ """
+ return self._passno
+
@property
def colextent(self):
+ """
+ The column index of the right-most character in the layout of the
+ subtree rooted at this node. In other words, the width of the
+ bounding box for the subtree rooted at this node.
+ """
return self._colextent
@property
def reflow_valid(self):
+ """
+ A boolean flag indicating whether or not the current layout is accepted.
+ If False, then further layout passes are required.
+ """
return self._reflow_valid
@property
def position(self):
+ """
+ A cursor with the (row,col) of the first (i.e. top,left) character in the
+ subtree rooted at this node.
+ """
return Cursor(*self._position)
- # TODO(josh): rename this
@property
- def type(self):
+ def node_type(self):
+ """
+ Return the `parser.NodeType` of the corresponding parser node that generated
+ this layout node.
+ """
return self.pnode.node_type
# NOTE(josh): making children a property disallows direct assignment
@property
def children(self):
+ """
+ A list of children layout nodes
+ """
return self._children
- @property
- def wrap(self):
- return self._wrap
-
def __repr__(self):
- return "{},{}({}) p({},{}) ce:{}".format(
- self.type.name,
- self._wrap.name,
- self._passno,
+ boolmap = {True: "T", False: "F"}
+ return "{},(passno={},wrap={}) pos:({},{}) colextent:{}".format(
+ self.node_type.name,
+ self._passno, boolmap[self._wrap],
self.position[0], self.position[1],
self.colextent)
def has_terminal_comment(self):
+ """
+ Return true if this node has a terminal line comment. In particular, this
+ implies that no other node may be packed at the output cursor of this
+ node's layout, and a line-wrap is required.
+ """
return False
def get_depth(self):
@@ -336,12 +343,18 @@ def get_depth(self):
# Compute `stmt_depth` and `subtree_depth`. Replace the children list with
# a tuple.
def lock(self, config, stmt_depth=0):
+ """
+ Lock the tree structure (topology) and prevent further updates. This is
+ mostly for sanity checking. It also computes topological statistics such
+ as `stmt_depth` and `subtree_depth`, and replaces the mutable list of
+ children with an immuatable tuple.
+ """
self._stmt_depth = stmt_depth
self._subtree_depth = self.get_depth()
self._children = tuple(self._children)
self._locked = True
- if self.type == NodeType.STATEMENT:
+ if self.node_type == NodeType.STATEMENT:
nextdepth = 1
elif stmt_depth > 0:
nextdepth = stmt_depth + 1
@@ -351,63 +364,88 @@ def lock(self, config, stmt_depth=0):
for child in self._children:
child.lock(config, nextdepth)
- # TODO(josh): get rid of this
- def _get_wrap(self, config, passno):
- algoidx = clamp(passno, 0, len(config.algorithm_order))
- algoid = config.algorithm_order[algoidx]
+ def _reflow(self, stack_context, cursor, passno):
+ """
+ Overridden by concrete classes to implement the layout of characters.
+ """
+ raise NotImplementedError()
+
+ def _validate_layout(
+ self, stack_context, start_extent, end_extent):
+ """
+ Return true if the layout is acceptable according to several checks. For
+ example, returns false if the content overflows the columnt limit.
+ """
- # No vpack if dangling parens
- if config.dangle_parens and algoid == WrapAlgo.VPACK.value:
- algoidx += 1
- algoid = config.algorithm_order[algoidx]
- return WrapAlgo.from_id(algoid)
+ config = stack_context.config
- def _reflow(self, config, cursor, passno):
- raise NotImplementedError()
+ # If the bounding box overflows the column limit then the layout is
+ # automatically voided
+ if end_extent[1] > config.linewidth:
+ return False
- def _reflow_new(self, config, cursor, passno):
- return self._reflow(config, cursor, passno)
+ size = end_extent - start_extent
- def reflow(self, config, cursor, passno=0):
+ if not self._wrap:
+ # Regardless of nesting, if the content is wrapped horizontally but it
+ # exceeds the configured maximum number of lines we must reject it
+ # TODO(josh): figure out how to subtract out any terminal comment
+ # contributions to the size, as noted in the algorithm doc.
+ if size[0] > config.max_lines_hwrap:
+ return False
+
+ # Or if this nodepath is marked to always be vertical layout
+ pathstr = "/".join(node.name for node in stack_context.node_path)
+ if pathstr in config.always_wrap:
+ return False
+
+ return True
+
+ def reflow(self, stack_context, cursor, parent_passno=0):
"""
(re-)compute the layout of this node under the assumption that it should
- be placed at the given `cursor` on the current `passno`. The wrap algorithm
- used is given by the node's statement depth and the current `passno`.
+ be placed at the given `cursor` on the current `parent_passno`.
"""
assert self._locked
assert isinstance(self.pnode, parser.TreeNode)
- self._position = Cursor(*cursor)
+ self._position = cursor.clone()
outcursor = None
- for repass in range(passno + 1):
- self._passno = repass
- self._wrap = self._get_wrap(config, repass)
- self._reflow_valid = True
- if USE_NEW_ALGORITHM:
- outcursor = self._reflow_new(config, Cursor(*cursor), repass)
- else:
- outcursor = self._reflow(config, Cursor(*cursor), repass)
- # TODO(josh): this is too conservative. We don't need every row to
- # reserve a character for the final parenthesis, we just need the final
- # row to reserve a character.
- if self._wrap == WrapAlgo.HPACK:
- linewidth = config.linewidth - 1
- else:
- linewidth = config.linewidth
- if self._reflow_valid and self._colextent <= linewidth:
- break
+
+ layout_passes = \
+ stack_context.config.layout_passes.get(
+ self.__class__.__name__, self._layout_passes)
+
+ with stack_context.push_node(self):
+ for passno, wrap in layout_passes:
+ if passno > parent_passno:
+ break
+ self._passno = passno
+ self._wrap = wrap
+ self._reflow_valid = True
+ start_extent = cursor.clone()
+ outcursor = self._reflow(
+ stack_context, cursor.clone(), passno)
+ end_extent = Cursor(outcursor[0], self._colextent)
+ self._reflow_valid &= self._validate_layout(
+ stack_context, start_extent, end_extent)
+ if self._reflow_valid:
+ break
assert outcursor is not None
return outcursor
- def _write(self, config, ctx):
+ def write(self, config, ctx):
+ """
+ Output text content given the currently configured layout.
+ """
for child in self._children:
child.write(config, ctx)
- def write(self, config, ctx):
- self._write(config, ctx)
-
@staticmethod
def create(pnode):
+ """
+ Create a new layout node associated with then given parser node.
+ """
# pylint: disable=too-many-return-statements
if pnode.node_type in SCALAR_TYPES:
return ScalarNode(pnode)
@@ -443,17 +481,17 @@ class ParenNode(LayoutNode):
@property
def name(self):
- return self.type.name
+ return self.node_type.name
- def _reflow(self, config, cursor, passno):
+ def _reflow(self, stack_context, cursor, passno):
"""There is only one possible layout for this node."""
self._colextent = cursor[1] + 1
return cursor + (0, 1)
- def _write(self, config, ctx):
- if self.type == NodeType.LPAREN:
+ def write(self, config, ctx):
+ if self.node_type == NodeType.LPAREN:
ctx.outfile.write_at(self.position, '(')
- elif self.type == NodeType.RPAREN:
+ elif self.node_type == NodeType.RPAREN:
ctx.outfile.write_at(self.position, ')')
else:
raise ValueError("Unrecognized paren type")
@@ -461,7 +499,7 @@ def _write(self, config, ctx):
class ScalarNode(LayoutNode):
"""
- Holdes scalar tokens such as statement names, parentheses, or keyword or
+ Holds scalar tokens such as statement names, parentheses, or keyword or
positional arguments.
"""
@@ -475,69 +513,11 @@ def has_terminal_comment(self):
# Note: bracket comments do not count as terminal comments
return self.children[-1].pnode.children[0].type == TokenType.COMMENT
- def _reflow(self, config, cursor, passno):
- """
- Reflow is pretty trivial for a scalar node. We don't have any choices to
- make, there is only one possible rendering.
- """
-
- assert self.pnode.children
- token = self.pnode.children[0]
- assert isinstance(token, lexer.Token)
-
- # This might be a multiline string or a multiline bracket argument. In
- # that case we need to normalize line endings and flow each line
- lines = normalize_line_endings(token.spelling).split('\n')
- line = lines.pop(0)
- cursor[1] += len(line)
- self._colextent = cursor[1]
-
- while lines:
- cursor[0] += 1
- cursor[1] = 0
- line = lines.pop(0)
- cursor[1] += len(line)
- self._colextent = max(self._colextent, cursor[1])
-
- # Scalar nodes might have terminal comments associated with them. They show
- # up as children in the layout graph. This is the only possible child of
- # a scalar node.
- children = list(self.children)
- if children:
- child = children.pop(0)
-
- # We should not have more than one terminal comment associated with a
- # given scalar node
- assert not children
-
- # The only kind of children we store for a scalar node are argument
- # comments.
- assert child.type == NodeType.COMMENT
-
- # Reflow the comment after the scalar
- cursor = child.reflow(config, cursor + (0, 1), passno)
- self._reflow_valid &= child.reflow_valid
- self._colextent = max(self._colextent, child.colextent)
-
- if child.pnode.children[0].type == TokenType.BRACKET_COMMENT:
- # If the argument comment is a bracket comment then we are done
- return cursor
-
- # If the argument commment is a line comment then we must invalidate
- # HPACK since there is no way we can render a single line.
- if self.wrap in (WrapAlgo.HPACK, WrapAlgo.HWRAP):
- # NOTE(josh): if we have a non-bracket comment child associated with
- # this argument, then we cannot hpack
- self._reflow_valid = False
-
- return cursor
-
- def _reflow_new(self, config, cursor, passno):
+ def _reflow(self, stack_context, cursor, passno):
"""
Reflow is pretty trivial for a scalar node. We don't have any choices to
make, there is only one possible rendering.
"""
-
assert self.pnode.children
token = self.pnode.children[0]
assert isinstance(token, lexer.Token)
@@ -559,31 +539,23 @@ def _reflow_new(self, config, cursor, passno):
# Scalar nodes might have terminal comments associated with them. They show
# up as children in the layout graph. This is the only possible child of
# a scalar node.
- children = list(self.children)
- if children:
- child = children.pop(0)
-
+ if self.children:
# We should not have more than one terminal comment associated with a
# given scalar node
- assert not children
+ assert len(self.children) == 1
# The only kind of children we store for a scalar node are argument
# comments.
- assert child.type == NodeType.COMMENT
+ child = self.children[0]
+ assert child.node_type == NodeType.COMMENT
# Reflow the comment after the scalar
- cursor = child.reflow(config, cursor + (0, 1), passno)
+ cursor = child.reflow(stack_context, cursor + (0, 1), passno)
self._reflow_valid &= child.reflow_valid
self._colextent = max(self._colextent, child.colextent)
-
- # TODO(josh): should scalar nodes have a notion of acceptance?
- # self._reflow_valid = \
- # default_accept_layout(
- # config, [], False, False, start_cursor,
- # Cursor(cursor[0], self._colextent))
return cursor
- def _write(self, config, ctx):
+ def write(self, config, ctx):
if not ctx.is_active():
return
@@ -592,7 +564,7 @@ def _write(self, config, ctx):
assert isinstance(token, lexer.Token)
spelling = normalize_line_endings(token.spelling)
- if self.type == NodeType.FUNNAME:
+ if self.node_type == NodeType.FUNNAME:
command_case = config.resolve_for_command(token.spelling, "command_case")
if command_case in ("lower", "upper"):
spelling = getattr(token.spelling, command_case)()
@@ -602,7 +574,7 @@ def _write(self, config, ctx):
else:
assert command_case == "unchanged", (
"Unrecognized command case {}".format(command_case))
- elif (self.type in (NodeType.KEYWORD, NodeType.FLAG)
+ elif (self.node_type in (NodeType.KEYWORD, NodeType.FLAG)
and config.keyword_case in ("lower", "upper")):
spelling = getattr(token.spelling, config.keyword_case)()
@@ -610,7 +582,7 @@ def _write(self, config, ctx):
children = list(self.children)
if children:
child = children.pop(0)
- assert child.type == NodeType.COMMENT
+ assert child.node_type == NodeType.COMMENT
child.write(config, ctx)
assert not children
@@ -629,7 +601,7 @@ def name(self):
def has_terminal_comment(self):
return True
- def _reflow(self, config, cursor, passno):
+ def _reflow(self, stack_context, cursor, passno):
"""
There is only one possible flow as this is a single-token
"""
@@ -648,7 +620,7 @@ def _reflow(self, config, cursor, passno):
self._colextent = cursor[1]
return cursor
- def _write(self, config, ctx):
+ def write(self, config, ctx):
assert self.pnode.children
token = self.pnode.children[0]
assert isinstance(token, lexer.Token)
@@ -674,488 +646,341 @@ def _write(self, config, ctx):
ctx.outfile.write_at(self.position, spelling)
-# TODO(josh): add more than just the primary algorithm to the passes that we
-# take. These four should be the first four passes, but we also want to
-# add a pass where we move statement comment to the next line, or we dangle
-# the paren. The statement comment pass may be something we want to do *foreach*
-# of the other options, while the dangle paren is probably the last thing we
-# want to do ever (i.e. if we're already wrapped as much as possible and the
-# paren still wont fit).
class StatementNode(LayoutNode):
+ """
+ Top-level node for a statement.
+ """
+ def __init__(self, pnode):
+ super(StatementNode, self).__init__(pnode)
+ self._layout_passes = [
+ (0, False),
+ (1, True),
+ (2, True),
+ (3, True),
+ (4, True),
+ (5, True),
+ ]
+
+ def reflow(self, stack_context, cursor, _=0): # pylint: disable=unused-argument
+ return super(StatementNode, self).reflow(
+ stack_context, cursor,
+ max(passno for passno, _ in self._layout_passes))
+
+ def get_prefix_width(self, config):
+ prefix_width = len(self.name) + 1
+ if need_paren_space(self.name, config):
+ prefix_width += 1 # For the space before the paren
+ return prefix_width
- # NOTE(josh): StatementNode is the only node that overrides reflow(), the
- # rest override just _reflow()
- # TODO(josh): at this point the difference in the override is pretty minimal
- # so we should probably figure out how to remove it.
- def reflow(self, config, cursor, _=0): # pylint: disable=unused-argument
- assert self._locked
- assert isinstance(self.pnode, parser.TreeNode)
+ @property
+ def name(self):
+ return self.children[0].pnode.children[0].spelling.lower()
- self._position = Cursor(*cursor)
- outcursor = None
+ def _validate_layout(
+ self, stack_context, start_extent, end_extent):
+ config = stack_context.config
- for repass in range(0, WrapAlgo.COUNT.value):
- self._passno = repass
- self._wrap = self._get_wrap(config, repass)
- self._reflow_valid = True
- if USE_NEW_ALGORITHM:
- outcursor = self._reflow_new(config, Cursor(*cursor), repass)
- else:
- outcursor = self._reflow(config, Cursor(*cursor), repass)
- if self._reflow_valid and self.colextent <= config.linewidth:
- break
- assert outcursor is not None
- return outcursor
+ # If the bounding box overflows the column limit then the layout is
+ # automatically voided
+ if end_extent[1] > config.linewidth:
+ return False
- def _reflow_horizontal(self, config, cursor, passno, children):
+ size = end_extent - start_extent
+ if not self._wrap:
+ # If the statement spelling is very long and there is enough content that
+ # the content is forced to wrap, then we require the statement content to
+ # nest.
+ if (size[0] > 1 and
+ self.get_prefix_width(config) > config.max_prefix_chars):
+ return False
+ return True
+
+ def _reflow(self, stack_context, cursor, passno):
+ # pylint: disable=too-many-statements
+ config = stack_context.config
+ start_cursor = cursor.clone()
+ self._colextent = cursor[1]
+
+ # Layout of the statement name is always the same, we just write it out
+ # at the current cursor
+ children = list(self.children)
assert children
child = children.pop(0)
- assert child.type == NodeType.LPAREN
- cursor = child.reflow(config, cursor, passno)
+ assert child.node_type == NodeType.FUNNAME
+
+ cursor = child.reflow(stack_context, cursor, passno)
self._reflow_valid &= child.reflow_valid
self._colextent = max(self._colextent, child.colextent)
- while children:
- prev = child
- child = children.pop(0)
-
- if (child.type == NodeType.COMMENT and
- child.pnode.children[0].type is TokenType.COMMENT
- and prev.type != NodeType.RPAREN):
- self._reflow_valid = False
-
- # TODO(josh): RESUME HERE -- the following is only valid for HPACK.
- # For HWRAP we need to try the cursor + '0', and if that fails then
- # try the column_corsor + '\n'.
-
- # The first node after an LPAREN and the RPAREN itself are not padded
- if not(prev.type == NodeType.LPAREN or child.type == NodeType.RPAREN):
- cursor[1] += len(' ')
+ funname = child
+ token = funname.pnode.children[0]
- if (children and children[0].type == NodeType.RPAREN):
- child.statement_terminal = True
+ assert isinstance(token, lexer.Token)
+ if need_paren_space(token.spelling.lower(), config):
+ cursor[1] += 1
- cursor = child.reflow(config, cursor, passno)
- self._reflow_valid &= child.reflow_valid
- # NOTE(josh): we must keep updating the extent after each child because
- # the child might be an argument with a multiline string or a bracket
- # argument... in which case HPACK might actually wrap to a newline.
- self._colextent = max(self._colextent, child.colextent)
- return cursor
+ if self.get_prefix_width(config) <= config.min_prefix_chars:
+ # If the statement or keyword spelling is too short, then nesting doesn't
+ # make sense because (nest + tab-width) will take us right back to the
+ # same column as without nesting.
+ self._wrap = False
- def _reflow_vertical(self, config, cursor, passno, children, start_cursor):
- # pylint: disable=too-many-statements
assert children
child = children.pop(0)
- assert child.type == NodeType.LPAREN
- cursor = child.reflow(config, cursor, passno)
+ assert child.node_type == NodeType.LPAREN
+ cursor = child.reflow(stack_context, cursor, passno)
self._reflow_valid &= child.reflow_valid
self._colextent = max(self._colextent, child.colextent)
- if self._wrap == WrapAlgo.VPACK:
- column_cursor = cursor
- elif self._wrap in (WrapAlgo.KWNVPACK, WrapAlgo.PNVPACK):
- column_cursor = start_cursor + Cursor(1, config.tab_size)
+ if self._wrap:
+ column_cursor = start_cursor + (1, config.tab_size)
+ cursor = Cursor(*column_cursor)
else:
- raise RuntimeError("Unexepected wrap algorithm")
-
- # NOTE(josh): this logic is common to both VPACK and NVPACK, the only
- # difference is the starting position of the column_cursor
- prev = None
- cursor = Cursor(*column_cursor)
- scalar_seq = analyze_scalar_sequence(children)
-
- while children:
- if children[0].type == NodeType.RPAREN:
- break
-
- if (prev is None) or (prev.type not in SCALAR_TYPES):
- scalar_seq = analyze_scalar_sequence(children)
- child = children.pop(0)
-
- # TODO(josh): remove scalar_squence code
- # If both the previous and current nodes are scalar nodes and the two
- # are not part of a particularly long (by a configurable margin) sequence
- # of scalar nodes, then advance the cursor horizontally by one space and
- # try to pack the next child on the same line as the current one
- if prev is None:
- cursor = child.reflow(config, cursor, passno)
- elif (prev.type in SCALAR_TYPES
- and child.type in SCALAR_TYPES
- and scalar_seq.length <= config.max_subargs_per_line
- and not scalar_seq.has_comment):
- cursor[1] += len(' ')
-
- # But if the cursor has overflowed the line width allocation, then
- # we cannot. Note that if this is the last argument in a statement,
- # then we will pack an RPAREN at it's end... so we should include the
- # size of that RPAREN when determining if we overflow.
- cursor = child.reflow(config, cursor, passno)
- realized_extent = child.colextent
- if children[0].type == NodeType.RPAREN:
- realized_extent += 1
- if realized_extent > config.linewidth:
- column_cursor[0] += 1
- cursor = Cursor(*column_cursor)
- cursor = child.reflow(config, cursor, passno)
- else:
- # Otherwise the node is not special and we vpack as usual
- column_cursor[0] += 1
- cursor = Cursor(*column_cursor)
- cursor = child.reflow(config, cursor, passno)
+ column_cursor = cursor.clone()
- self._reflow_valid &= child.reflow_valid
- self._colextent = max(self._colextent, child.colextent)
- column_cursor[0] = cursor[0]
- prev = child
+ # NOTE(josh): STATEMENTs should have at most one ARGGROUP child between
+ # parentheses.
+ child = children.pop(0)
+ assert child.node_type is NodeType.ARGGROUP, (
+ "Expected ARGGROUP node, but got {} at {}"
+ .format(child.node_type, child.pnode))
+ # Whether or not an ARGGROUP is statement terminal is kind of a weird
+ # notion, but as a sentinel for the future "number of open parens" we'll
+ # go ahead and mark it.
+ child.statement_terminal = True
+ cursor = child.reflow(stack_context, cursor, passno)
+ self._reflow_valid &= child.reflow_valid
+ # We must keep updating the extent after each child because
+ # the child might be an argument with a multiline string or a bracket
+ # argument... in which case HPACK might actually wrap to a newline.
+ self._colextent = max(self._colextent, child.colextent)
+ column_cursor[0] = cursor[0]
assert children
+ prev = child
child = children.pop(0)
- assert child.type == NodeType.RPAREN, \
- "Expected RPAREN but got {}".format(child.type)
+ assert child.node_type == NodeType.RPAREN, \
+ "Expected RPAREN but got {}".format(child.node_type)
# NOTE(josh): dangle parens if it wont fit on the current line or
# if the user has requested us to always do so
+ dangle_parens = False
if config.dangle_parens and cursor[0] > start_cursor[0]:
- column_cursor[0] += 1
- cursor = Cursor(column_cursor[0], start_cursor[1])
- elif (cursor[1] >= config.linewidth
- and self._wrap.value > WrapAlgo.VPACK.value):
- column_cursor[0] += 1
- cursor = Cursor(*column_cursor)
- elif prev.type == NodeType.COMMENT:
- column_cursor[0] += 1
- cursor = Cursor(*column_cursor)
+ # If the configuration requests dangling parentheses, then honor that
+ # request so long as the statement doesn't fit on a single line
+ dangle_parens = True
+ elif cursor[1] >= config.linewidth:
+ # If the child reflow was unable to reserve a column for us to place our
+ # parenthesis, then we must dangle it
+ dangle_parens = True
+ # But we really want to nest first in this case
+ if not self._wrap:
+ self._reflow_valid = False
elif prev.has_terminal_comment():
- column_cursor[0] += 1
- cursor = Cursor(*column_cursor)
+ # If the final token in an argument list is a line comment, then we must
+ # dangle the parenthesis, or it will become part of the line comment
+ dangle_parens = True
+
+ column_cursor[0] += 1
+ if config.dangle_align == "prefix":
+ dangle_cursor = Cursor(column_cursor[0], start_cursor[1])
+ elif config.dangle_align == "prefix-indent":
+ dangle_cursor = Cursor(
+ column_cursor[0], start_cursor[1] + config.tab_size)
+ elif config.dangle_align == "child":
+ dangle_cursor = Cursor(*column_cursor)
+ else:
+ raise ValueError(
+ "Unexpected config.dangle_align: {}".format(config.dangle_align))
- cursor = child.reflow(config, cursor, passno)
- self._reflow_valid &= child.reflow_valid
- self._colextent = max(self._colextent, child.colextent)
+ if dangle_parens:
+ cursor = dangle_cursor.clone()
+
+ rparen = child
+ initial_rparen_cursor = cursor.clone()
+ cursor = rparen.reflow(stack_context, initial_rparen_cursor, passno)
+ self._reflow_valid &= rparen.reflow_valid
+ # NOTE(josh): don't max rparen.colextent here, since we may decide to
+ # dangle it below
# Trailing comment
if children:
cursor[1] += 1
child = children.pop(0)
- assert child.type == NodeType.COMMENT, \
- "Expected COMMENT after RPAREN but got {}".format(child.type)
+ assert child.node_type == NodeType.COMMENT, \
+ "Expected COMMENT after RPAREN but got {}".format(child.node_type)
assert not children
- savecursor = Cursor(*cursor)
- cursor = child.reflow(config, cursor, passno)
-
+ savecursor = cursor.clone()
+ cursor = child.reflow(stack_context, cursor, passno)
+
+ # If the statement trailing comment does not fit in the column after the
+ # rparen, then dangle the rparen and try again.
+ if not dangle_parens and child.colextent > config.linewidth:
+ cursor = rparen.reflow(stack_context, dangle_cursor, passno)
+ self._reflow_valid &= rparen.reflow_valid
+ # NOTE(josh): don't max rparen.colextent here, since we may reverse our
+ # dangle decision in the next if block
+ savecursor = cursor.clone()
+ cursor = child.reflow(stack_context, cursor, passno)
+
+ # If the statement trailing comment still does not fit in the current
+ # column then just move it to the next line.
if child.colextent > config.linewidth:
- cursor = child.reflow(config, (savecursor[0] + 1, start_cursor[1]),
- passno)
+ # NOTE(josh): potentially undangle the paren: if the only reason to
+ # dangle it was due to the oversized comment line, then we need to
+ # undangle it since the oversized comment didn't fit anyway.
+ cursor = rparen.reflow(stack_context, initial_rparen_cursor, passno)
+ cursor = child.reflow(
+ stack_context, (savecursor[0] + 1, start_cursor[1]),
+ passno)
self._reflow_valid &= child.reflow_valid
self._colextent = max(self._colextent, child.colextent)
+ self._colextent = max(self._colextent, rparen.colextent)
return cursor
+ def write(self, config, ctx):
+ if not ctx.is_active():
+ return
+ super(StatementNode, self).write(config, ctx)
+
+
+class KwargGroupNode(LayoutNode):
+ """
+ A keyword argument group. Contains a keyword, followed by an argument group.
+ """
+ def __init__(self, pnode):
+ super(KwargGroupNode, self).__init__(pnode)
+ self._layout_passes = [
+ (0, False),
+ (1, False),
+ (2, False),
+ (3, False),
+ (4, False),
+ (5, True),
+ ]
+
+ def has_terminal_comment(self):
+ if self.children[-1].node_type is NodeType.COMMENT:
+ return True
+ return self.children[-1].has_terminal_comment()
+
@property
def name(self):
- return self.children[0].pnode.children[0].spelling.lower()
+ return self.children[0].pnode.children[0].spelling.upper()
- def _reflow(self, config, cursor, passno):
- """
- Compute the size of a statement which is nominally allocated `linewidth`
- columns for packing. `linewidth` is only considered for hpacking of
- consecutive scalar arguments
- """
- funname = self.children[0]
- assert funname.type == NodeType.FUNNAME
+ def _validate_layout(
+ self, stack_context, start_extent, end_extent):
+ config = stack_context.config
- token = funname.pnode.children[0]
- assert isinstance(token, lexer.Token)
+ # If the bounding box overflows the column limit then the layout is
+ # automatically voided
+ if end_extent[1] > config.linewidth:
+ return False
+
+ size = end_extent - start_extent
+ if not self._wrap:
+ # If the statement spelling is very long and there is enough content that
+ # the content is forced to wrap, then we require the statement content to
+ # nest.
+ if size[0] > 1 and len(self.name) > config.max_prefix_chars:
+ return False
+ return True
- start_cursor = Cursor(*cursor)
+ def _reflow(self, stack_context, cursor, passno):
+ config = stack_context.config
+ start_cursor = cursor.clone()
self._colextent = cursor[1]
- # Layout of the statement name is always the same, we just write it out
+ # Keyword node should have exactly two children, the keyword and the
+ # argument group.
+ assert len(self.children) <= 2, (
+ "Expected at most 2 children in KWargGroup, got {}"
+ .format(len(self.children))
+ )
+
+ # Layout of the keyword is always the same, we just write it out
# at the current cursor
- children = list(self.children)
- assert children
- child = children.pop(0)
- assert child.type == NodeType.FUNNAME
- cursor = child.reflow(config, cursor, passno)
- self._reflow_valid &= child.reflow_valid
- self._colextent = max(self._colextent, child.colextent)
-
- if need_paren_space(token.spelling, config):
- cursor[1] += 1
-
- if self._wrap in (WrapAlgo.HPACK, WrapAlgo.HWRAP):
- if token.spelling.lower() in config.always_wrap:
- self._reflow_valid = False
- return self._reflow_horizontal(config, cursor, passno, children)
-
- return self._reflow_vertical(config, cursor, passno, children, start_cursor)
-
- def _reflow_new(self, config, cursor, passno):
- """
- Compute the size of a statement which is nominally allocated `linewidth`
- columns for packing. `linewidth` is only considered for hpacking of
- consecutive scalar arguments
- """
- # pylint: disable=too-many-statements
- funname = self.children[0]
- assert funname.type == NodeType.FUNNAME
- token = funname.pnode.children[0]
- assert isinstance(token, lexer.Token)
+ child = self.children[0]
+ assert child.node_type == NodeType.KEYWORD
- start_cursor = Cursor(*cursor)
- self._colextent = cursor[1]
+ if len(self.name) <= config.min_prefix_chars:
+ # If the statement or keyword spelling is too short, then nesting doesn't
+ # make sense because (nest + tab-width) will take us right back to the
+ # same column as without nesting.
+ self._wrap = False
- # Layout of the statement name is always the same, we just write it out
- # at the current cursor
- children = list(self.children)
- assert children
- child = children.pop(0)
- assert child.type == NodeType.FUNNAME
- cursor = child.reflow(config, cursor, passno)
+ cursor = child.reflow(stack_context, cursor, passno)
self._reflow_valid &= child.reflow_valid
- self._colextent = max(self._colextent, child.colextent)
-
- if need_paren_space(token.spelling, config):
- cursor[1] += 1
+ self._colextent = child.colextent
- assert children
- child = children.pop(0)
- assert child.type == NodeType.LPAREN
- cursor = child.reflow(config, cursor, passno)
- self._reflow_valid &= child.reflow_valid
- self._colextent = max(self._colextent, child.colextent)
+ # NOTE(josh): Empty kwarg. These are rare, and I think they might actually
+ # be syntax errors, but we currently support it so let's not limit the
+ # possibility for now
+ if len(self.children) == 1:
+ return cursor
- nest, vertical = get_nest_wrap(passno)
- if nest:
- column_cursor = start_cursor + (1, config.tab_size)
- cursor = Cursor(*column_cursor)
+ # keyword = parser.get_normalized_kwarg(child.pnode.children[0])
+ if self._wrap:
+ column_cursor = Cursor(cursor[0] + 1, start_cursor[1] + config.tab_size)
else:
- column_cursor = Cursor(*cursor)
-
- # NOTE(josh): STATEMENTs should have at most one ARGGROUP child between
- # parentheses. This logic is redundant, but it is left so that we can
- # compare against other nodes and hopefully unify them.
- while children and children[0].type != NodeType.RPAREN:
- prev = child
- child = children.pop(0)
-
- if prev.type == NodeType.LPAREN:
- # This is the first child of the statement the cursor is already in the
- # right position, regardless of current nesting or wrapping
- pass
- elif (prev.type == NodeType.COMMENT
- or prev.has_terminal_comment()
- or vertical):
- cursor[1] = column_cursor[1]
- cursor[0] += 1
- else:
- cursor[1] += 1
-
- if (children and children[0].type == NodeType.RPAREN):
- child.statement_terminal = True
+ column_cursor = cursor + (0, 1)
- cursor = child.reflow(config, cursor, passno)
- if not vertical:
- # If we are in horizontal wrapping mode, then we need to check if the
- # child has overflowed the column. If so, then we need to move to the
- # next line and try again
- realized_extent = child.colextent
- if children[0].type == NodeType.RPAREN:
- # If this is the last node before the parenthesis then we need to
- # account for one extra character (the closing parenthesis)
- realized_extent += 1
- if realized_extent > config.linewidth:
- # If the realized extent overflows the column limit then we need to
- # insert a newline and try again
- column_cursor[0] += 1
- cursor = Cursor(*column_cursor)
- cursor = child.reflow(config, cursor, passno)
-
- self._reflow_valid &= child.reflow_valid
- # We must keep updating the extent after each child because
- # the child might be an argument with a multiline string or a bracket
- # argument... in which case HPACK might actually wrap to a newline.
- self._colextent = max(self._colextent, child.colextent)
- column_cursor[0] = cursor[0]
-
- assert children
- prev = child
- child = children.pop(0)
- assert child.type == NodeType.RPAREN, \
- "Expected RPAREN but got {}".format(child.type)
+ child = self.children[1]
+ cursor = Cursor(*column_cursor)
- # NOTE(josh): dangle parens if it wont fit on the current line or
- # if the user has requested us to always do so
- if config.dangle_parens and cursor[0] > start_cursor[0]:
- column_cursor[0] += 1
- cursor = Cursor(column_cursor[0], start_cursor[1])
- elif (cursor[1] >= config.linewidth
- and self._wrap.value > WrapAlgo.VPACK.value):
- column_cursor[0] += 1
- cursor = Cursor(*column_cursor)
- elif prev.type == NodeType.COMMENT:
- column_cursor[0] += 1
- cursor = Cursor(*column_cursor)
- elif prev.has_terminal_comment():
- column_cursor[0] += 1
- cursor = Cursor(*column_cursor)
+ if self.statement_terminal:
+ child.statement_terminal = True
- cursor = child.reflow(config, cursor, passno)
+ cursor = child.reflow(stack_context, cursor, passno)
self._reflow_valid &= child.reflow_valid
self._colextent = max(self._colextent, child.colextent)
-
- # Trailing comment
- if children:
- cursor[1] += 1
- child = children.pop(0)
- assert child.type == NodeType.COMMENT, \
- "Expected COMMENT after RPAREN but got {}".format(child.type)
- assert not children
- savecursor = Cursor(*cursor)
- cursor = child.reflow(config, cursor, passno)
-
- if child.colextent > config.linewidth:
- # If the statement trailing comment does not fit in the current column
- # just move it to the next line.
- # TODO(josh): actually, if the RPAREN is not already dangling then
- # we should dangle the paren and keep the comment trailing the statement
- cursor = child.reflow(config, (savecursor[0] + 1, start_cursor[1]),
- passno)
-
- self._reflow_valid &= child.reflow_valid
- self._colextent = max(self._colextent, child.colextent)
-
- self._reflow_valid = \
- default_accept_layout(
- config, [token.spelling], nest, vertical,
- start_cursor, Cursor(cursor[0], self._colextent))
+ column_cursor[0] = cursor[0]
return cursor
- def _write(self, config, ctx):
- if not ctx.is_active():
- return
- super(StatementNode, self)._write(config, ctx)
+def filename_node_key(layout_node):
+ """
+ Return the sort key for sortable arguments nodes. This is the
+ case-insensitive spelling of the first token in the node.
+ """
+ return layout_node.pnode.children[0].spelling.lower()
-class KwargGroupNode(LayoutNode):
-
- def has_terminal_comment(self):
- if self.children[-1].type is NodeType.COMMENT:
- return True
- return self.children[-1].has_terminal_comment()
-
- @property
- def name(self):
- self.children[0].pnode.chidren[0].spelling.upper()
-
- def _reflow(self, config, cursor, passno):
- """
- Compute the size of a keyword argument group which is nominally allocated
- `linewidth` columns for packing. `linewidth` is only considered for hpacking
- of consecutive scalar arguments
- """
-
- start_cursor = Cursor(*cursor)
-
- children = list(self.children)
- assert children
- child = children.pop(0)
- assert child.type == NodeType.KEYWORD
- cursor = child.reflow(config, cursor, passno)
- self._reflow_valid &= child.reflow_valid
- keyword = parser.get_normalized_kwarg(child.pnode.children[0])
- self._colextent = child.colextent
-
- # KWARG groups cannot be HWRAPPED. If they need to wrap, they must
- # do so vertically.
- if self._wrap == WrapAlgo.HWRAP:
- self._reflow_valid = False
-
- if self._wrap in (WrapAlgo.HPACK, WrapAlgo.HWRAP):
- if len(children) > config.max_subargs_per_line:
- self._reflow_valid = False
-
- while children:
- cursor[1] += len(' ')
- child = children.pop(0)
-
- if (child.type == NodeType.COMMENT and
- child.pnode.children[0].type is TokenType.COMMENT):
- self._reflow_valid = False
-
- cursor = child.reflow(config, cursor, passno)
- self._reflow_valid &= child.reflow_valid
- self._colextent = max(self._colextent, child.colextent)
-
- return cursor
- if self._wrap == WrapAlgo.VPACK:
- cursor[1] += len(' ')
- column_cursor = Cursor(*cursor)
- elif self._wrap in (WrapAlgo.KWNVPACK, WrapAlgo.PNVPACK):
- column_cursor = start_cursor + Cursor(1, config.tab_size)
+def count_arguments(children):
+ """
+ Count the number of positional arguments (excluding line comments and
+ whitespace) within a parg group.
+ """
+ count = 0
+ for child in children:
+ if child.node_type is NodeType.COMMENT:
+ continue
else:
- raise RuntimeError("Unexepected wrap algorithm")
-
- # NOTE(josh): this logic is common to both VPACK and NVPACK, the only
- # difference is the starting position of the column_cursor
- prev = None
- cursor = Cursor(*column_cursor)
- scalar_seq = analyze_scalar_sequence(children)
-
- while children:
- if (prev is None) or (prev.type not in SCALAR_TYPES):
- scalar_seq = analyze_scalar_sequence(children)
- child = children.pop(0)
-
- # If both the previous and current nodes are scalar nodes and the two
- # are not part of a particularly long (by a configurable margin) sequence
- # of scalar nodes, then advance the cursor horizontally by one space and
- # try to pack the next child on the same line as the current one
- if prev is None:
- cursor = child.reflow(config, cursor, passno)
- self._reflow_valid &= child.reflow_valid
- elif (prev.type in SCALAR_TYPES
- and child.type in SCALAR_TYPES
- and (scalar_seq.length <= config.max_subargs_per_line
- or keyword == 'COMMAND'
- or keyword.startswith('--'))
- and not scalar_seq.has_comment):
- cursor[1] += len(' ')
-
- # But if the cursor has overflowed the line width allocation, then
- # we cannot
- cursor = child.reflow(config, cursor, passno)
- if child.colextent > config.linewidth:
- column_cursor[0] += 1
- cursor = Cursor(*column_cursor)
- cursor = child.reflow(config, cursor, passno)
- # Otherwise we fall back to vpack
- else:
- column_cursor[0] += 1
- cursor = Cursor(*column_cursor)
- cursor = child.reflow(config, cursor, passno)
-
- self._reflow_valid &= child.reflow_valid
- self._colextent = max(self._colextent, child.colextent)
- column_cursor[0] = cursor[0]
- prev = child
-
- return cursor
-
-
-def filename_node_key(layout_node):
- return layout_node.pnode.children[0].spelling
+ count += 1
+ return count
class PargGroupNode(LayoutNode):
+ """
+ A group of positional arguments.
+ """
+
+ def __init__(self, pnode):
+ super(PargGroupNode, self).__init__(pnode)
+ self._layout_passes = [
+ (0, False),
+ (1, False),
+ (2, False),
+ (3, False),
+ (4, True),
+ ]
def has_terminal_comment(self):
if not self.children:
return False
- if self.children[-1].type is NodeType.COMMENT:
+ if self.children[-1].node_type is NodeType.COMMENT:
return True
return self.children[-1].has_terminal_comment()
@@ -1184,178 +1009,77 @@ def lock(self, config, stmt_depth=0):
super(PargGroupNode, self).lock(config, stmt_depth)
- def _reflow_hpack(self, config, cursor, passno):
- if len(self.children) > config.max_subargs_per_line:
- self._reflow_valid = False
-
- children = list(self.children)
- prev = None
-
- while children:
- if prev is not None:
- cursor[1] += len(' ')
- child = children.pop(0)
-
- if (child.type == NodeType.COMMENT and
- child.pnode.children[0].type is TokenType.COMMENT):
- self._reflow_valid = False
-
- cursor = child.reflow(config, cursor, passno)
- self._reflow_valid &= child.reflow_valid
- self._colextent = max(self._colextent, child.colextent)
- prev = child
- return cursor
-
- def _reflow_hwrap(self, config, cursor, passno):
- # TODO(josh): max_subargs_per_line doesn't invalidate hwrap
- if len(self.children) > config.max_subargs_per_line:
- self._reflow_valid = False
-
- column_cursor = Cursor(*cursor)
- children = list(self.children)
- prev = None
- while children:
- if prev is not None:
- cursor[1] += len(' ')
- child = children.pop(0)
-
- if (child.type == NodeType.COMMENT and
- child.pnode.children[0].type is TokenType.COMMENT):
- self._reflow_valid = False
-
- cursor = child.reflow(config, cursor, passno)
- linewidth = config.linewidth
-
- # Reserve an extra character if we are the positional group of a
- # statement and we are in wrap mode
- if self.statement_terminal and not children:
- linewidth -= 1
-
- if child.colextent > linewidth:
- column_cursor[0] += 1
- cursor = Cursor(*column_cursor)
- cursor = child.reflow(config, cursor, passno)
-
- self._reflow_valid &= child.reflow_valid
- self._colextent = max(self._colextent, child.colextent)
- prev = child
-
- return cursor
-
- def _reflow_vpack(self, config, cursor, passno):
- # NOTE(josh): this logic is common to both VPACK and NVPACK, the only
- # difference is the starting position of the column_cursor
- children = list(self.children)
- prev = None
- column_cursor = Cursor(*cursor)
- scalar_seq = analyze_scalar_sequence(children)
-
- while children:
- if (prev is None) or (prev.type not in SCALAR_TYPES):
- scalar_seq = analyze_scalar_sequence(children)
- child = children.pop(0)
-
- # If both the previous and current nodes are scalar nodes and the two
- # are not part of a particularly long (by a configurable margin) sequence
- # of scalar nodes, then advance the cursor horizontally by one space and
- # try to pack the next child on the same line as the current one
- if prev is None:
- cursor = child.reflow(config, cursor, passno)
- elif (prev.type in SCALAR_TYPES
- and child.type in SCALAR_TYPES
- and (scalar_seq.length <= config.max_subargs_per_line)
- and not scalar_seq.has_comment):
- cursor[1] += len(' ')
-
- # But if the cursor has overflowed the line width allocation, then
- # we cannot
- cursor = child.reflow(config, cursor, passno)
- if child.colextent > config.linewidth:
- column_cursor[0] += 1
- cursor = Cursor(*column_cursor)
- cursor = child.reflow(config, cursor, passno)
- # Otherwise we fall back to vpack
- else:
- column_cursor[0] += 1
- cursor = Cursor(*column_cursor)
- cursor = child.reflow(config, cursor, passno)
-
- self._reflow_valid &= child.reflow_valid
- self._colextent = max(self._colextent, child.colextent)
- column_cursor[0] = cursor[0]
- prev = child
-
- return cursor
-
- def _reflow(self, config, cursor, passno):
- """
- Compute the size of a positional argument group
- """
- self._colextent = cursor.y
-
- if self._wrap is WrapAlgo.HPACK:
- return self._reflow_hpack(config, cursor, passno)
-
- if self._wrap is WrapAlgo.HWRAP:
- return self._reflow_hwrap(config, cursor, passno)
-
- return self._reflow_vpack(config, cursor, passno)
-
- def _reflow_new(self, config, cursor, passno):
- """
- Compute the size of the group of all children
- """
+ def _reflow(self, stack_context, cursor, passno):
+ config = stack_context.config
children = list(self.children)
self._colextent = cursor.y
prev = None
child = None
- # PARG groups do not nest, they nest by their parents
- _, vertical = get_nest_wrap(passno)
- column_cursor = Cursor(*cursor)
+ column_cursor = cursor.clone()
+ numpargs = count_arguments(children)
+
+ # "COMMAND" arguments are never columnized, since there is no way for us
+ # to make that look good
+ if stack_context.node_path[-3].name == "COMMAND":
+ self._wrap = False
- numgroups = 0
while children:
prev = child
child = children.pop(0)
+ cursor_is_at_column = True
if prev is None:
# This is the first child of the arg group so the cursor is already
# at the right location and theres nothing for us to update
pass
- elif (prev.type == NodeType.COMMENT
+ elif (is_line_comment(prev)
or prev.has_terminal_comment()
- or vertical):
- cursor[1] = column_cursor[1]
- cursor[0] += 1
+ or self._wrap):
+ column_cursor[0] = cursor[0] + 1
+ cursor = Cursor(*column_cursor)
else:
+ cursor_is_at_column = False
cursor[1] += 1
if self.statement_terminal and not children:
child.statement_terminal = True
- cursor = child.reflow(config, cursor, passno)
- if not vertical:
- # If we are in horizontal wrapping mode, then we need to check if the
- # child has overflowed the available column width. If so, then we need
- # to wrap to the next line and try again
+ cursor = child.reflow(stack_context, cursor, passno)
+ if (not cursor_is_at_column) and (not self._wrap):
+ # If we are in horizontal wrapping mode, then we need to look at a
+ # couple of things thaty may cause us to wrap.
needs_wrap = False
+
+ # The obvious case is overflow:
+ # If the realized extent overflows the column limit then we need to
+ # insert a newline and try again
+ if child.colextent > config.linewidth:
+ needs_wrap = True
+
+ # If this is the last node before the closing parenthesis of the
+ # statement then we need to extra character (the closing parenthesis)
+ # on the last line. If we overlow that extra character slot, then
+ # wrap to a new line
if self.statement_terminal:
- # If this is the last node before the parenthesis then we need to
- # account for one extra character (the closing parenthesis)
if cursor[1] + 1 > config.linewidth:
needs_wrap = True
- if child.colextent > config.linewidth:
- # If the realized extent overflows the column limit then we need to
- # insert a newline and try again
+ # If the current node is a comment, then we must have parsed it as a
+ # line comment (not an argument comment) and so we should preserve
+ # it's status as a line comment and not put it after another argument.
+ # Note that tag comments are always line comments, and never consumed
+ # as argument comments.
+ if ((prev and prev.node_type in SCALAR_TYPES) and
+ child.node_type is NodeType.COMMENT and
+ not child.is_tag()):
needs_wrap = True
if needs_wrap:
- column_cursor[0] += 1
+ column_cursor[0] = cursor[0] + 1
cursor = Cursor(*column_cursor)
- cursor = child.reflow(config, cursor, passno)
+ cursor = child.reflow(stack_context, cursor, passno)
# NOTE(josh): we must keep updating the extent after each child because
# the child might be an argument with a multiline string or a bracket
@@ -1363,16 +1087,54 @@ def _reflow_new(self, config, cursor, passno):
self._reflow_valid &= child.reflow_valid
self._colextent = max(self._colextent, child.colextent)
- if isinstance(child, (PargGroupNode, KwargGroupNode)):
- numgroups += 1
+ # NOTE(josh): there is a subtle distinction between invalidating a reflow
+ # and forcing mode=vertical. The difference is whether or not a parent
+ # node has to advance it's decision state. If we force to vertical at
+ # the start of this function, the parent Statement wont nest this
+ # ArgGroup. Therefore, we must invalidate here, rather than forcing
+ # _vertical above.
+ if numpargs > config.max_pargs_hwrap:
+ self._reflow_valid &= self._wrap
- # TODO(josh): Should this number 2 be a configuration parameter?
- if numgroups > 2:
- self._reflow_valid = False
return cursor
+def count_subgroups(children):
+ """
+ Count the number of positional or kwarg sub groups in an argument group.
+ Ignore comments, and assert that no other types of children are found.
+ """
+ numgroups = 0
+ for child in children:
+ if child.node_type in (NodeType.KWARGGROUP, NodeType.PARGGROUP,
+ NodeType.PARENGROUP):
+ numgroups += 1
+ elif child.node_type is NodeType.COMMENT:
+ continue
+ else:
+ raise ValueError(
+ "Unexpected node type {} as child of ArgGroupNode"
+ .format(child.node_type))
+ return numgroups
+
+
class ArgGroupNode(LayoutNode):
+ """
+ A group of arguments. This is the single child node of either a
+ `StatementNode` or `KwargGroupNode` which then contains any further
+ group nodes.
+ """
+
+ def __init__(self, pnode):
+ super(ArgGroupNode, self).__init__(pnode)
+ self._layout_passes = [
+ (0, False),
+ (1, False),
+ (2, False),
+ (3, False),
+ (4, True),
+ (5, True),
+ ]
def has_terminal_comment(self):
"""
@@ -1380,71 +1142,20 @@ def has_terminal_comment(self):
KWARGGROUP subtrees. Any terminal comment will belong to one of
it's children.
"""
- return self.children and (self.children[-1].type is NodeType.COMMENT or
+ return self.children and (self.children[-1].node_type is NodeType.COMMENT or
self.children[-1].has_terminal_comment())
- def _reflow(self, config, cursor, passno):
- """
- Compute the size of the group of all children
- """
- # TODO(josh): breakup this function
- # pylint: disable=too-many-statements
-
- children = list(self.children)
- self._colextent = cursor.y
-
- prev = None
- if self._wrap in (WrapAlgo.HPACK, WrapAlgo.HWRAP):
- while children:
- child = children.pop(0)
-
- if (child.type == NodeType.COMMENT and
- child.pnode.children[0].type is TokenType.COMMENT):
- self._reflow_valid = False
-
- if prev is not None:
- cursor[1] += len(' ')
-
- if self.statement_terminal and not children:
- child.statement_terminal = True
-
- cursor = child.reflow(config, cursor, passno)
- prev = child
-
- # NOTE(josh): we must keep updating the extent after each child because
- # the child might be an argument with a multiline string or a bracket
- # argument... in which case HPACK might actually wrap to a newline.
- self._reflow_valid &= child.reflow_valid
- self._colextent = max(self._colextent, child.colextent)
- return cursor
-
- column_cursor = cursor
- while children:
- child = children.pop(0)
- cursor = Cursor(*column_cursor)
- cursor = child.reflow(config, cursor, passno)
- self._reflow_valid &= child.reflow_valid
- self._colextent = max(self._colextent, child.colextent)
- column_cursor[0] = cursor[0] + 1
- prev = child
- return cursor
-
- def _reflow_new(self, config, cursor, passno):
- """
- Compute the size of the group of all children
- """
+ def _reflow(self, stack_context, cursor, passno):
+ config = stack_context.config
children = list(self.children)
self._colextent = cursor.y
prev = None
child = None
- # Argument groups cannot nest since they have no prefix, they are nested by
- # their parent
- _, vertical = get_nest_wrap(passno)
- column_cursor = Cursor(*cursor)
+ column_cursor = cursor.clone()
+ numgroups = count_subgroups(children)
- numgroups = 0
while children:
prev = child
child = children.pop(0)
@@ -1452,24 +1163,26 @@ def _reflow_new(self, config, cursor, passno):
if prev is None:
# This is the first child of the arg group so the cursor is already
# at the right location and theres nothing for us to update
- pass
- elif (prev.type == NodeType.COMMENT
+ is_first_in_row = True
+ elif (is_line_comment(prev)
or prev.has_terminal_comment()
- or vertical):
- cursor[1] = column_cursor[1]
- cursor[0] += 1
+ or self._wrap):
+ column_cursor[0] += 1
+ cursor = column_cursor.clone()
+ is_first_in_row = True
else:
cursor[1] += 1
+ is_first_in_row = False
if self.statement_terminal and not children:
child.statement_terminal = True
- cursor = child.reflow(config, cursor, passno)
- if not vertical:
+ start_cursor = cursor
+ cursor = child.reflow(stack_context, cursor, passno)
+ if not is_first_in_row and not self._wrap:
# If we are in horizontal wrapping mode, then we need to check if the
- # child has overflowed the available columnt width. If so, then we need
+ # child has overflowed the available column width. If so, then we need
# to wrap to the next line and try again
-
needs_wrap = False
if child.statement_terminal:
# If this is the last node before the parenthesis then we need to
@@ -1482,39 +1195,62 @@ def _reflow_new(self, config, cursor, passno):
# insert a newline and try again
needs_wrap = True
+ if (cursor - start_cursor)[0] > 1:
+ # If the argument is wrapped internally (i.e. has more than two lines)
+ # then move it to it's own line
+ # TODO(josh): Instead of needs_wrap = True unconditionally in this
+ # case, let's layout both options and pick which ever one is best
+ needs_wrap = True
+
if needs_wrap:
column_cursor[0] += 1
cursor = Cursor(*column_cursor)
- cursor = child.reflow(config, cursor, passno)
+ cursor = child.reflow(stack_context, cursor, passno)
# NOTE(josh): we must keep updating the extent after each child because
# the child might be an argument with a multiline string or a bracket
# argument... in which case HPACK might actually wrap to a newline.
self._reflow_valid &= child.reflow_valid
self._colextent = max(self._colextent, child.colextent)
+ column_cursor[0] = cursor[0]
- if isinstance(child, (PargGroupNode, KwargGroupNode)):
- numgroups += 1
+ # NOTE(josh): there is a subtle distinction between invalidating a reflow
+ # and forcing mode=vertical. The difference is whether or not a parent
+ # node has to advance it's decision state. If we force to vertical at
+ # the start of this function, the parent Statement wont nest this
+ # ArgGroup. Therefore, we must invalidate here, rather than forcing
+ # _vertical above.
+ if numgroups > config.max_subgroups_hwrap:
+ self._reflow_valid &= self._wrap
- # TODO(josh): Should this number 2 be a configuration parameter?
- if numgroups > 2:
- self._reflow_valid = False
return cursor
- def _write(self, config, ctx):
+ def write(self, config, ctx):
if not ctx.is_active():
return
- super(ArgGroupNode, self)._write(config, ctx)
+ super(ArgGroupNode, self).write(config, ctx)
class ParenGroupNode(LayoutNode):
- # TODO(josh): does ParenGroupNode also need to override reflow() to make
- # sure that the trailing paren has a column slot in HPACK mode? i.e. the
- # same as StatementNode.
+ """
+ A parenthetical group. According to cmake syntax rules, this necessarily
+ implies a boolean logical expression.
+ """
+
+ def __init__(self, pnode):
+ super(ParenGroupNode, self).__init__(pnode)
+ self._layout_passes = [
+ (0, False),
+ (1, False),
+ (2, False),
+ (3, False),
+ (4, False),
+ (5, True),
+ ]
def has_terminal_comment(self):
children = list(self.children)
- while children and children[0].type != NodeType.RPAREN:
+ while children and children[0].node_type != NodeType.RPAREN:
children.pop(0)
if children:
@@ -1523,178 +1259,24 @@ def has_terminal_comment(self):
return (children
and children[-1].pnode.children[0].type == TokenType.COMMENT)
- def _reflow_hwrap(self, config, cursor, passno):
- """
- Logic is the same for HPACK and HWRAP
- """
- self._colextent = cursor[1]
- children = list(self.children)
- if not children:
- return cursor
-
- assert children
- child = children.pop(0)
- assert child.type == NodeType.LPAREN
- cursor = child.reflow(config, cursor, passno)
- self._reflow_valid &= child.reflow_valid
- self._colextent = max(self._colextent, child.colextent)
-
- while children:
- prev = child
- child = children.pop(0)
-
- if (child.type == NodeType.COMMENT and
- child.pnode.children[0].type is TokenType.COMMENT):
- self._reflow_valid = False
-
- # The first node after an LPAREN and the RPAREN itself are not padded
- if not(prev.type == NodeType.LPAREN or child.type == NodeType.RPAREN):
- cursor[1] += len(' ')
-
- cursor = child.reflow(config, cursor, passno)
- # NOTE(josh): we must keep updating the extent after each child because
- # the child might be an argument with a multiline string or a bracket
- # argument... in which case HPACK might actually wrap to a newline.
- self._reflow_valid &= child.reflow_valid
- self._colextent = max(self._colextent, child.colextent)
- return cursor
-
- def _reflow_vertical(self, config, cursor, passno):
- # pylint: disable=too-many-statements
- start_cursor = Cursor(*cursor)
- self._colextent = cursor[1]
-
- children = list(self.children)
- if not children:
- return cursor
-
- assert children
- child = children.pop(0)
- assert child.type == NodeType.LPAREN
- cursor = child.reflow(config, cursor, passno)
- self._reflow_valid &= child.reflow_valid
- self._colextent = max(self._colextent, child.colextent)
-
- if self._wrap in (WrapAlgo.VPACK, WrapAlgo.KWNVPACK):
- column_cursor = cursor
- elif self._wrap == WrapAlgo.PNVPACK:
- column_cursor = start_cursor + Cursor(1, config.tab_size)
- else:
- raise RuntimeError("Unexpected wrap algorithm")
-
- # NOTE(josh): this logic is common to both VPACK and NVPACK, the only
- # difference is the starting position of the column_cursor
- prev = None
- cursor = Cursor(*column_cursor)
- scalar_seq = analyze_scalar_sequence(children)
-
- while children:
- if children[0].type == NodeType.RPAREN:
- break
-
- if (prev is None) or (prev.type not in SCALAR_TYPES):
- scalar_seq = analyze_scalar_sequence(children)
- child = children.pop(0)
-
- # If both the previous and current nodes are scalar nodes and the two
- # are not part of a particularly long (by a configurable margin) sequence
- # of scalar nodes, then advance the cursor horizontally by one space and
- # try to pack the next child on the same line as the current one
- if prev is None:
- cursor = child.reflow(config, cursor, passno)
- elif (prev.type in SCALAR_TYPES
- and child.type in SCALAR_TYPES
- and scalar_seq.length <= config.max_subargs_per_line
- and not scalar_seq.has_comment):
- cursor[1] += len(' ')
-
- # But if the cursor has overflowed the line width allocation, then
- # we cannot
- cursor = child.reflow(config, cursor, passno)
- if child.colextent > config.linewidth:
- column_cursor[0] += 1
- cursor = Cursor(*column_cursor)
- cursor = child.reflow(config, cursor, passno)
- # Otherwise we fall back to vpack
- else:
- column_cursor[0] += 1
- cursor = Cursor(*column_cursor)
- cursor = child.reflow(config, cursor, passno)
-
- self._reflow_valid &= child.reflow_valid
- self._colextent = max(self._colextent, child.colextent)
- column_cursor[0] = cursor[0]
- prev = child
-
- assert children
- child = children.pop(0)
- assert child.type == NodeType.RPAREN, \
- "Expected RPAREN but got {}".format(child.type)
-
- # NOTE(josh); dangle parens if it wont fit on the current line or
- # if the user has requested us to always do so
- if config.dangle_parens and cursor[0] > start_cursor[0]:
- column_cursor[0] += 1
- cursor = Cursor(*column_cursor)
- elif (cursor[1] >= config.linewidth
- and self._wrap.value > WrapAlgo.VPACK.value):
- column_cursor[0] += 1
- cursor = Cursor(*column_cursor)
- elif prev.type == NodeType.COMMENT:
- column_cursor[0] += 1
- cursor = Cursor(*column_cursor)
- elif prev.has_terminal_comment():
- column_cursor[0] += 1
- cursor = Cursor(*column_cursor)
-
- cursor = child.reflow(config, cursor, passno)
- self._reflow_valid &= child.reflow_valid
- self._colextent = max(self._colextent, child.colextent)
-
- # Trailing comment
- if children:
- cursor[1] += 1
- child = children.pop(0)
- assert child.type == NodeType.COMMENT, \
- "Expected COMMENT after RPAREN but got {}".format(child.type)
- assert not children
- cursor = child.reflow(config, cursor, passno)
-
- if child.colextent > config.linewidth:
- cursor[0] += 1
- cursor[1] = start_cursor[1]
- cursor = child.reflow(config, cursor, passno)
-
- self._reflow_valid &= child.reflow_valid
- self._colextent = max(self._colextent, child.colextent)
- return cursor
-
- def _reflow(self, config, cursor, passno):
- """
- Compute the size of a parenthtetical argument group which is nominally
- allocated `linewidth` columns for packing. `linewidth` is only considered
- for hpacking of consecutive scalar arguments
- """
- if self._wrap in (WrapAlgo.HPACK, WrapAlgo.HWRAP):
- return self._reflow_hwrap(config, cursor, passno)
-
- return self._reflow_vertical(config, cursor, passno)
-
- def _reflow_new(self, config, cursor, passno):
- """
- Compute the size of the group of all children
- """
+ def _reflow(self, stack_context, cursor, passno):
+ config = stack_context.config
children = list(self.children)
self._colextent = cursor.y
+ # ParenGroupNode is composed of:
+ # * a left parenthensis
+ # * an argument group
+ # * a right parenthesis
+ # * an optional trailing comment
+ assert len(children) in (3, 4)
+
prev = None
child = None
- # Argument groups cannot nest since they have no prefix, they are nested by
- # their parent
- _, vertical = get_nest_wrap(passno)
- column_cursor = Cursor(*cursor)
+ # Paren groups do not themselves nest or wrap, they only have one real
+ # child: the primary argument group.
+ column_cursor = cursor.clone()
- numgroups = 0
while children:
prev = child
child = children.pop(0)
@@ -1703,19 +1285,25 @@ def _reflow_new(self, config, cursor, passno):
# This is the first child of the arg group so the cursor is already
# at the right location and theres nothing for us to update
pass
- elif (prev.type == NodeType.COMMENT
+ elif prev.node_type == NodeType.LPAREN:
+ # No space after LParen
+ pass
+ elif (is_line_comment(prev)
or prev.has_terminal_comment()
- or vertical):
+ or self._wrap):
cursor[1] = column_cursor[1]
cursor[0] += 1
+ elif child.node_type == NodeType.RPAREN:
+ # No space before RPAREN
+ pass
else:
cursor[1] += 1
if self.statement_terminal and not children:
child.statement_terminal = True
- cursor = child.reflow(config, cursor, passno)
- if not vertical:
+ cursor = child.reflow(stack_context, cursor, passno)
+ if not self._wrap:
# If we are in horizontal wrapping mode, then we need to check if the
# child has overflowed the available columnt width. If so, then we need
# to wrap to the next line and try again
@@ -1732,41 +1320,39 @@ def _reflow_new(self, config, cursor, passno):
# insert a newline and try again
needs_wrap = True
+ if not child.reflow_valid:
+ needs_wrap = True
+
if needs_wrap:
column_cursor[0] += 1
cursor = Cursor(*column_cursor)
- cursor = child.reflow(config, cursor, passno)
+ cursor = child.reflow(stack_context, cursor, passno)
- # NOTE(josh): we must keep updating the extent after each child because
- # the child might be an argument with a multiline string or a bracket
- # argument... in which case HPACK might actually wrap to a newline.
self._reflow_valid &= child.reflow_valid
self._colextent = max(self._colextent, child.colextent)
-
- if isinstance(child, (PargGroupNode, KwargGroupNode)):
- numgroups += 1
-
- # TODO(josh): Should this number 2 be a configuration parameter?
- if numgroups > 2:
- self._reflow_valid = False
+ column_cursor[0] = cursor[0]
return cursor
- def _write(self, config, ctx):
+ def write(self, config, ctx):
if not ctx.is_active():
return
- super(ParenGroupNode, self)._write(config, ctx)
+ super(ParenGroupNode, self).write(config, ctx)
class BodyNode(LayoutNode):
+ """
+ Top-level node for a given "scope" depth. This node is the root of a document,
+ or the root of any nested statement scopes.
+ """
- def _reflow(self, config, cursor, passno):
+ def _reflow(self, stack_context, cursor, passno):
"""
Compute the size of a body block
"""
- column_cursor = cursor
+ column_cursor = cursor.clone()
self._colextent = 0
for child in self.children:
- cursor = child.reflow(config, column_cursor, passno)
+ cursor = child.reflow(stack_context, column_cursor, passno)
self._reflow_valid &= child.reflow_valid
self._colextent = max(self._colextent, child.colextent)
column_cursor[0] = cursor[0] + 1
@@ -1775,27 +1361,33 @@ def _reflow(self, config, cursor, passno):
class FlowControlNode(LayoutNode):
+ """
+ Top-Level node composed of a flow-control statement and it's associated
+ `BodyNodes`.
+ """
- def _reflow(self, config, cursor, passno):
+ def _reflow(self, stack_context, cursor, passno):
"""
Compute the size of a flowcontrol block
"""
+ config = stack_context.config
self._colextent = 0
- column_cursor = Cursor(*cursor)
+ column_cursor = cursor.clone()
children = list(self.children)
assert children
child = children.pop(0)
- assert child.type == NodeType.STATEMENT
- cursor = child.reflow(config, column_cursor, passno)
+ assert child.node_type == NodeType.STATEMENT
+ cursor = child.reflow(stack_context, column_cursor, passno)
self._reflow_valid &= child.reflow_valid
self._colextent = max(self._colextent, child.colextent)
column_cursor[0] = cursor[0] + 1
assert children
child = children.pop(0)
- assert child.type == NodeType.BODY
- cursor = child.reflow(config, column_cursor + (0, config.tab_size), passno)
+ assert child.node_type == NodeType.BODY
+ cursor = child.reflow(
+ stack_context, column_cursor + (0, config.tab_size), passno)
self._reflow_valid &= child.reflow_valid
self._colextent = max(self._colextent, child.colextent)
column_cursor[0] = cursor[0] + 1
@@ -1803,8 +1395,8 @@ def _reflow(self, config, cursor, passno):
while True:
assert children
child = children.pop(0)
- assert child.type == NodeType.STATEMENT
- cursor = child.reflow(config, column_cursor, passno)
+ assert child.node_type == NodeType.STATEMENT
+ cursor = child.reflow(stack_context, column_cursor, passno)
self._reflow_valid &= child.reflow_valid
self._colextent = max(self._colextent, child.colextent)
column_cursor[0] = cursor[0] + 1
@@ -1812,8 +1404,8 @@ def _reflow(self, config, cursor, passno):
if not children:
break
child = children.pop(0)
- assert child.type == NodeType.BODY
- cursor = child.reflow(config, column_cursor +
+ assert child.node_type == NodeType.BODY
+ cursor = child.reflow(stack_context, column_cursor +
(0, config.tab_size), passno)
self._reflow_valid &= child.reflow_valid
self._colextent = max(self._colextent, child.colextent)
@@ -1823,11 +1415,20 @@ def _reflow(self, config, cursor, passno):
class CommentNode(LayoutNode):
+ """
+ A line comment or bracket comment. If parented by a group node then this
+ comment acts as an argument. If parented by a scalar node, then this comment
+ acts like an argument comment.
+ """
- def _reflow(self, config, cursor, passno):
+ def is_tag(self):
+ return parser.comment_is_tag(self.pnode.children[0])
+
+ def _reflow(self, stack_context, cursor, passno):
"""
Compute the size of a comment block
"""
+ config = stack_context.config
if (len(self.pnode.children) == 1
and isinstance(self.pnode.children[0], lexer.Token)
and self.pnode.children[0].type == TokenType.BRACKET_COMMENT):
@@ -1855,7 +1456,7 @@ def _reflow(self, config, cursor, passno):
increment = (len(lines) - 1, len(lines[-1]))
return cursor + increment
- def _write(self, config, ctx):
+ def write(self, config, ctx):
if not ctx.is_active():
return
@@ -1872,15 +1473,18 @@ def _write(self, config, ctx):
class WhitespaceNode(LayoutNode):
+ """
+ A series of newlines
+ """
- def _reflow(self, config, cursor, passno):
+ def _reflow(self, stack_context, cursor, passno):
"""
Compute the size of a whitespace block
"""
self._colextent = 0
return cursor
- def _write(self, config, ctx):
+ def write(self, config, ctx):
return
@@ -1888,38 +1492,12 @@ def get_scalar_sequence_len(box_children):
length = 0
for child in box_children:
# Child is not a scalar type
- if child.type not in SCALAR_TYPES:
+ if child.node_type not in SCALAR_TYPES:
return length
length += 1
return length
-def get_scalar_sequence_has_comment(box_children):
- # TODO(josh): rename this function. It really just indicates whether or
- # not a scalar is allowed to be hpacked.
-
- for child in box_children:
- # Child is not a scalar type
- if child.type not in SCALAR_TYPES:
- return False
-
- if len(child.pnode.children) > 1:
- return True
-
- return False
-
-
-class ScalarSeq(object):
- def __init__(self, length, has_comment):
- self.length = length
- self.has_comment = has_comment
-
-
-def analyze_scalar_sequence(box_children):
- return ScalarSeq(get_scalar_sequence_len(box_children),
- get_scalar_sequence_has_comment(box_children))
-
-
def create_box_tree(pnode):
"""
Recursively construct a layout tree from the given parse tree
@@ -1928,21 +1506,11 @@ def create_box_tree(pnode):
layout_root = LayoutNode.create(pnode)
child_queue = list(pnode.children)
- found_primary_arggroup = False
while child_queue:
pchild = child_queue.pop(0)
if not isinstance(pchild, parser.TreeNode):
continue
- # NOTE(josh); the primary argument group gets formatted specially because
- # the parens belong to the statement, not the group.
- if (pnode.node_type == NodeType.STATEMENT
- and pchild.node_type == NodeType.ARGGROUP
- and not found_primary_arggroup):
- child_queue = list(pchild.children) + child_queue
- found_primary_arggroup = True
- continue
-
if (pchild.node_type == NodeType.WHITESPACE
and pchild.count_newlines() < 2):
continue
@@ -1953,12 +1521,19 @@ def create_box_tree(pnode):
def layout_tree(parsetree_root, config, linewidth=None):
+ """
+ Top-level function to construct a layout tree from a parse tree, and then
+ iterate through layout passes until the entire tree is satisfactory. Returns
+ the root of the layout tree.
+ """
+
if linewidth is None:
linewidth = config.line_width
root_box = create_box_tree(parsetree_root)
root_box.lock(config)
- root_box.reflow(config, (0, 0))
+ stack_context = StackContext(config)
+ root_box.reflow(stack_context, Cursor(0, 0))
return root_box
@@ -2069,7 +1644,7 @@ def dump_tree_for_test(nodes, outfile=None, indent=None, increment=None):
for node in nodes:
outfile.write(indent)
outfile.write("({}, {}, {}, {}, {}, [".format(
- node.type, node.wrap,
+ node.node_type, node.passno,
node.position[0], node.position[1], node.colextent))
if hasattr(node, 'children') and node.children:
@@ -2161,7 +1736,7 @@ def getvalue(self):
return self._fobj.getvalue() + self._config.endl
-class Global(object):
+class WriteContext(object):
"""
Global state for the writing functions
"""
@@ -2186,7 +1761,7 @@ def write_tree(root_box, config, infile_content):
"""
Format the tree for size only, then print all of the boxes to outfile
"""
- ctx = Global(config, infile_content)
+ ctx = WriteContext(config, infile_content)
root_box.write(config, ctx)
if not ctx.is_active():
diff --git a/cmake_format/invocation_tests.py b/cmake_format/invocation_tests.py
index 2dc638a..0a6ec09 100644
--- a/cmake_format/invocation_tests.py
+++ b/cmake_format/invocation_tests.py
@@ -32,7 +32,7 @@ def setUp(self):
@contextlib.contextmanager
def subTest(self, msg=None, **params):
# pylint: disable=no-member
- if sys.version_info < (3, 0, 0):
+ if sys.version_info < (3, 4, 0):
yield None
else:
yield super(TestInvocations, self).subTest(msg=msg, **params)
diff --git a/cmake_format/layout_tests.py b/cmake_format/layout_tests.py
index 57770fd..8fed5ba 100644
--- a/cmake_format/layout_tests.py
+++ b/cmake_format/layout_tests.py
@@ -1,8 +1,10 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
+import contextlib
import logging
import unittest
+import sys
from cmake_format import __main__
from cmake_format import configuration
@@ -11,7 +13,6 @@
from cmake_format import parse_funs
from cmake_format import formatter
from cmake_format.parser import NodeType
-from cmake_format.formatter import WrapAlgo
def strip_indent(content, indent=6):
@@ -65,7 +66,7 @@ def assert_tree(test, nodes, tups, tree=None, history=None):
subhistory = history + [node]
message = (" for node {} at\n {} \n\n\n"
"If the actual result is expected, then update the test with"
- " this:\n{}"
+ " this:\n# pylint: disable=bad-continuation\n# noqa: E122\n{}"
.format(node,
formatter.tree_string(tree, subhistory),
formatter.test_string(tree)))
@@ -73,12 +74,12 @@ def assert_tree(test, nodes, tups, tree=None, history=None):
test.assertIsNotNone(tup, msg="Extra node" + message)
if len(tup) == 6:
ntype, wrap, row, col, colextent, expect_children = tup
- test.assertEqual(node.wrap, wrap,
+ test.assertEqual(node.passno, wrap,
msg="Expected wrap={}".format(wrap) + message)
else:
ntype, row, col, colextent, expect_children = tup
- test.assertEqual(node.type, ntype,
+ test.assertEqual(node.node_type, ntype,
msg="Expected type={}".format(ntype) + message)
test.assertEqual(node.position[0], row,
msg="Expected row={}".format(row) + message)
@@ -115,6 +116,14 @@ def setUp(self):
def tearDown(self):
pass
+ @contextlib.contextmanager
+ def subTest(self, msg=None, **params):
+ # pylint: disable=no-member
+ if sys.version_info < (3, 4, 0):
+ yield None
+ else:
+ yield super(TestCanonicalLayout, self).subTest(msg=msg, **params)
+
def do_layout_test(self, input_str, expect_tree, strip_len=6):
"""
Run the formatter on the input string and assert that the result matches
@@ -131,19 +140,25 @@ def test_simple_statement(self):
self.do_layout_test("""\
cmake_minimum_required(VERSION 2.8.11)
""", [
- (NodeType.BODY, WrapAlgo.HPACK, 0, 0, 38, [
- (NodeType.STATEMENT, WrapAlgo.HPACK, 0, 0, 38, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 0, 0, 22, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 0, 22, 23, []),
- (NodeType.KWARGGROUP, WrapAlgo.HPACK, 0, 23, 37, [
- (NodeType.KEYWORD, WrapAlgo.HPACK, 0, 23, 30, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 0, 31, 37, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 0, 31, 37, []),
- ]),
- ]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 0, 37, 38, []),
- ]),
+# pylint: disable=bad-continuation
+# noqa: E122
+(NodeType.BODY, 0, 0, 0, 38, [
+ (NodeType.STATEMENT, 0, 0, 0, 38, [
+ (NodeType.FUNNAME, 0, 0, 0, 22, []),
+ (NodeType.LPAREN, 0, 0, 22, 23, []),
+ (NodeType.ARGGROUP, 0, 0, 23, 37, [
+ (NodeType.KWARGGROUP, 0, 0, 23, 37, [
+ (NodeType.KEYWORD, 0, 0, 23, 30, []),
+ (NodeType.ARGGROUP, 0, 0, 31, 37, [
+ (NodeType.PARGGROUP, 0, 0, 31, 37, [
+ (NodeType.ARGUMENT, 0, 0, 31, 37, []),
]),
+ ]),
+ ]),
+ ]),
+ (NodeType.RPAREN, 0, 0, 37, 38, []),
+ ]),
+]),
])
def test_collapse_additional_newlines(self):
@@ -155,21 +170,27 @@ def test_collapse_additional_newlines(self):
cmake_minimum_required(VERSION 2.8.11)
""", [
- (NodeType.BODY, WrapAlgo.HPACK, 0, 0, 75, [
- (NodeType.COMMENT, WrapAlgo.HPACK, 0, 0, 75, []),
- (NodeType.WHITESPACE, WrapAlgo.HPACK, 1, 0, 0, []),
- (NodeType.STATEMENT, WrapAlgo.HPACK, 2, 0, 38, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 2, 0, 22, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 2, 22, 23, []),
- (NodeType.KWARGGROUP, WrapAlgo.HPACK, 2, 23, 37, [
- (NodeType.KEYWORD, WrapAlgo.HPACK, 2, 23, 30, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 2, 31, 37, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 2, 31, 37, []),
- ]),
- ]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 2, 37, 38, []),
- ]),
+# pylint: disable=bad-continuation
+# noqa: E122
+(NodeType.BODY, 0, 0, 0, 75, [
+ (NodeType.COMMENT, 0, 0, 0, 75, []),
+ (NodeType.WHITESPACE, 0, 1, 0, 0, []),
+ (NodeType.STATEMENT, 0, 2, 0, 38, [
+ (NodeType.FUNNAME, 0, 2, 0, 22, []),
+ (NodeType.LPAREN, 0, 2, 22, 23, []),
+ (NodeType.ARGGROUP, 0, 2, 23, 37, [
+ (NodeType.KWARGGROUP, 0, 2, 23, 37, [
+ (NodeType.KEYWORD, 0, 2, 23, 30, []),
+ (NodeType.ARGGROUP, 0, 2, 31, 37, [
+ (NodeType.PARGGROUP, 0, 2, 31, 37, [
+ (NodeType.ARGUMENT, 0, 2, 31, 37, []),
]),
+ ]),
+ ]),
+ ]),
+ (NodeType.RPAREN, 0, 2, 37, 38, []),
+ ]),
+]),
])
def test_multiline_reflow(self):
@@ -188,22 +209,27 @@ def test_long_args_command_split(self):
# This very long command should be split to multiple lines
set(HEADERS very_long_header_name_a.h very_long_header_name_b.h very_long_header_name_c.h)
""", [
- (NodeType.BODY, WrapAlgo.HPACK, 0, 0, 63, [
- (NodeType.COMMENT, WrapAlgo.HPACK, 0, 0, 58, []),
- (NodeType.STATEMENT, WrapAlgo.HWRAP, 1, 0, 63, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 1, 0, 3, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 1, 3, 4, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 1, 4, 11, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 1, 4, 11, []),
- ]),
- (NodeType.PARGGROUP, WrapAlgo.HWRAP, 1, 12, 63, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 1, 12, 37, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 1, 38, 63, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 2, 12, 37, []),
- ]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 2, 37, 38, []),
- ]),
- ]),
+# pylint: disable=bad-continuation
+# pylint: disable=bad-continuation
+# noqa: E122
+(NodeType.BODY, 0, 0, 0, 63, [
+ (NodeType.COMMENT, 0, 0, 0, 58, []),
+ (NodeType.STATEMENT, 0, 1, 0, 63, [
+ (NodeType.FUNNAME, 0, 1, 0, 3, []),
+ (NodeType.LPAREN, 0, 1, 3, 4, []),
+ (NodeType.ARGGROUP, 0, 1, 4, 63, [
+ (NodeType.PARGGROUP, 0, 1, 4, 11, [
+ (NodeType.ARGUMENT, 0, 1, 4, 11, []),
+ ]),
+ (NodeType.PARGGROUP, 0, 1, 12, 63, [
+ (NodeType.ARGUMENT, 0, 1, 12, 37, []),
+ (NodeType.ARGUMENT, 0, 1, 38, 63, []),
+ (NodeType.ARGUMENT, 0, 2, 12, 37, []),
+ ]),
+ ]),
+ (NodeType.RPAREN, 0, 2, 37, 38, []),
+ ]),
+]),
])
def test_long_arg_on_newline(self):
@@ -212,17 +238,21 @@ def test_long_arg_on_newline(self):
# end, so it should be moved to a new line with block indent + 1.
some_long_command_name("Some very long argument that really needs to be on the next line.")
""", [
- (NodeType.BODY, WrapAlgo.HPACK, 0, 0, 77, [
- (NodeType.COMMENT, WrapAlgo.HPACK, 0, 0, 77, []),
- (NodeType.STATEMENT, WrapAlgo.KWNVPACK, 2, 0, 70, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 2, 0, 22, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 2, 22, 23, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 3, 2, 69, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 3, 2, 69, []),
- ]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 3, 69, 70, []),
- ]),
- ]),
+# pylint: disable=bad-continuation
+# noqa: E122
+(NodeType.BODY, 0, 0, 0, 77, [
+ (NodeType.COMMENT, 0, 0, 0, 77, []),
+ (NodeType.STATEMENT, 1, 2, 0, 70, [
+ (NodeType.FUNNAME, 0, 2, 0, 22, []),
+ (NodeType.LPAREN, 0, 2, 22, 23, []),
+ (NodeType.ARGGROUP, 0, 3, 2, 69, [
+ (NodeType.PARGGROUP, 0, 3, 2, 69, [
+ (NodeType.ARGUMENT, 0, 3, 2, 69, []),
+ ]),
+ ]),
+ (NodeType.RPAREN, 0, 3, 69, 70, []),
+ ]),
+]),
])
def test_argcomment_preserved_and_reflowed(self):
@@ -232,24 +262,28 @@ def test_argcomment_preserved_and_reflowed(self):
# across two lines.
header_c.h header_d.h)
""", [
- (NodeType.BODY, WrapAlgo.HPACK, 0, 0, 78, [
- (NodeType.STATEMENT, WrapAlgo.VPACK, 0, 0, 78, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 0, 0, 3, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 0, 3, 4, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 0, 4, 11, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 0, 4, 11, []),
- ]),
- (NodeType.PARGGROUP, WrapAlgo.VPACK, 1, 4, 78, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 1, 4, 14, []),
- (NodeType.ARGUMENT, WrapAlgo.VPACK, 2, 4, 78, [
- (NodeType.COMMENT, WrapAlgo.HPACK, 2, 15, 78, []),
- ]),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 4, 4, 14, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 5, 4, 14, []),
- ]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 5, 14, 15, []),
- ]),
- ]),
+# pylint: disable=bad-continuation
+# noqa: E122
+(NodeType.BODY, 0, 0, 0, 80, [
+ (NodeType.STATEMENT, 4, 0, 0, 80, [
+ (NodeType.FUNNAME, 0, 0, 0, 3, []),
+ (NodeType.LPAREN, 0, 0, 3, 4, []),
+ (NodeType.ARGGROUP, 4, 0, 4, 80, [
+ (NodeType.PARGGROUP, 0, 0, 4, 11, [
+ (NodeType.ARGUMENT, 0, 0, 4, 11, []),
+ ]),
+ (NodeType.PARGGROUP, 0, 1, 4, 80, [
+ (NodeType.ARGUMENT, 0, 1, 4, 14, []),
+ (NodeType.ARGUMENT, 0, 1, 15, 80, [
+ (NodeType.COMMENT, 0, 1, 26, 80, []),
+ ]),
+ (NodeType.ARGUMENT, 0, 3, 4, 14, []),
+ (NodeType.ARGUMENT, 0, 3, 15, 25, []),
+ ]),
+ ]),
+ (NodeType.RPAREN, 0, 3, 25, 26, []),
+ ]),
+]),
])
def test_complex_nested_stuff(self):
@@ -270,75 +304,92 @@ def test_complex_nested_stuff(self):
""", [
# pylint: disable=bad-continuation
# noqa: E122
-(NodeType.BODY, WrapAlgo.HPACK, 0, 0, 79, [
- (NodeType.FLOW_CONTROL, WrapAlgo.HPACK, 0, 0, 79, [
- (NodeType.STATEMENT, WrapAlgo.HPACK, 0, 0, 7, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 0, 0, 2, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 0, 2, 3, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 0, 3, 6, []),
- (NodeType.RPAREN, WrapAlgo.HPACK, 0, 6, 7, []),
- ]),
- (NodeType.BODY, WrapAlgo.HPACK, 1, 2, 79, [
- (NodeType.FLOW_CONTROL, WrapAlgo.HPACK, 1, 2, 79, [
- (NodeType.STATEMENT, WrapAlgo.HPACK, 1, 2, 10, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 1, 2, 4, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 1, 4, 5, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 1, 5, 9, []),
- (NodeType.RPAREN, WrapAlgo.HPACK, 1, 9, 10, []),
+(NodeType.BODY, 0, 0, 0, 80, [
+ (NodeType.FLOW_CONTROL, 0, 0, 0, 80, [
+ (NodeType.STATEMENT, 0, 0, 0, 7, [
+ (NodeType.FUNNAME, 0, 0, 0, 2, []),
+ (NodeType.LPAREN, 0, 0, 2, 3, []),
+ (NodeType.ARGGROUP, 0, 0, 3, 6, [
+ (NodeType.PARGGROUP, 0, 0, 3, 6, [
+ (NodeType.ARGUMENT, 0, 0, 3, 6, []),
]),
- (NodeType.BODY, WrapAlgo.HPACK, 2, 4, 79, [
- (NodeType.COMMENT, WrapAlgo.HPACK, 2, 4, 31, []),
- (NodeType.STATEMENT, WrapAlgo.VPACK, 3, 4, 76, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 3, 4, 15, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 3, 15, 16, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 3, 16, 27, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 3, 16, 27, []),
+ ]),
+ (NodeType.RPAREN, 0, 0, 6, 7, []),
+ ]),
+ (NodeType.BODY, 0, 1, 2, 80, [
+ (NodeType.FLOW_CONTROL, 0, 1, 2, 80, [
+ (NodeType.STATEMENT, 0, 1, 2, 10, [
+ (NodeType.FUNNAME, 0, 1, 2, 4, []),
+ (NodeType.LPAREN, 0, 1, 4, 5, []),
+ (NodeType.ARGGROUP, 0, 1, 5, 9, [
+ (NodeType.PARGGROUP, 0, 1, 5, 9, [
+ (NodeType.ARGUMENT, 0, 1, 5, 9, []),
]),
- (NodeType.PARGGROUP, WrapAlgo.VPACK, 4, 16, 76, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 4, 16, 22, []),
- (NodeType.ARGUMENT, WrapAlgo.VPACK, 5, 16, 76, [
- (NodeType.COMMENT, WrapAlgo.HPACK, 5, 23, 76, []),
+ ]),
+ (NodeType.RPAREN, 0, 1, 9, 10, []),
+ ]),
+ (NodeType.BODY, 0, 2, 4, 80, [
+ (NodeType.COMMENT, 0, 2, 4, 31, []),
+ (NodeType.STATEMENT, 4, 3, 4, 74, [
+ (NodeType.FUNNAME, 0, 3, 4, 15, []),
+ (NodeType.LPAREN, 0, 3, 15, 16, []),
+ (NodeType.ARGGROUP, 4, 4, 6, 74, [
+ (NodeType.PARGGROUP, 0, 4, 6, 17, [
+ (NodeType.ARGUMENT, 0, 4, 6, 17, []),
+ ]),
+ (NodeType.PARGGROUP, 0, 5, 6, 74, [
+ (NodeType.ARGUMENT, 0, 5, 6, 12, []),
+ (NodeType.ARGUMENT, 0, 5, 13, 48, [
+ (NodeType.COMMENT, 0, 5, 20, 48, []),
+ ]),
+ (NodeType.COMMENT, 0, 6, 6, 74, []),
+ (NodeType.ARGUMENT, 0, 7, 6, 12, []),
]),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 7, 16, 22, []),
]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 7, 22, 23, []),
- (NodeType.COMMENT, WrapAlgo.HPACK, 7, 24, 61, []),
+ (NodeType.RPAREN, 0, 7, 12, 13, []),
+ (NodeType.COMMENT, 0, 7, 14, 51, []),
]),
- (NodeType.WHITESPACE, WrapAlgo.HPACK, 8, 4, 0, []),
- (NodeType.STATEMENT, WrapAlgo.HPACK, 9, 4, 79, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 9, 4, 17, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 9, 17, 18, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 9, 18, 55, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 9, 18, 36, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 9, 37, 55, []),
+ (NodeType.WHITESPACE, 0, 8, 4, 0, []),
+ (NodeType.STATEMENT, 1, 9, 4, 76, [
+ (NodeType.FUNNAME, 0, 9, 4, 17, []),
+ (NodeType.LPAREN, 0, 9, 17, 18, []),
+ (NodeType.ARGGROUP, 0, 10, 6, 43, [
+ (NodeType.PARGGROUP, 0, 10, 6, 43, [
+ (NodeType.ARGUMENT, 0, 10, 6, 24, []),
+ (NodeType.ARGUMENT, 0, 10, 25, 43, []),
+ ]),
]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 9, 55, 56, []),
- (NodeType.COMMENT, WrapAlgo.HPACK, 9, 57, 79, []),
+ (NodeType.RPAREN, 0, 10, 43, 44, []),
+ (NodeType.COMMENT, 0, 10, 45, 76, []),
]),
- (NodeType.WHITESPACE, WrapAlgo.HPACK, 12, 4, 0, []),
- (NodeType.STATEMENT, WrapAlgo.VPACK, 13, 4, 79, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 13, 4, 17, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 13, 17, 18, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 13, 18, 74, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 13, 18, 36, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 13, 37, 55, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 13, 56, 74, []),
+ (NodeType.WHITESPACE, 0, 12, 4, 0, []),
+ (NodeType.STATEMENT, 5, 13, 4, 80, [
+ (NodeType.FUNNAME, 0, 13, 4, 17, []),
+ (NodeType.LPAREN, 0, 13, 17, 18, []),
+ (NodeType.ARGGROUP, 0, 14, 6, 62, [
+ (NodeType.PARGGROUP, 0, 14, 6, 62, [
+ (NodeType.ARGUMENT, 0, 14, 6, 24, []),
+ (NodeType.ARGUMENT, 0, 14, 25, 43, []),
+ (NodeType.ARGUMENT, 0, 14, 44, 62, []),
+ ]),
]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 13, 74, 75, []),
- (NodeType.COMMENT, WrapAlgo.HPACK, 14, 4, 79, []),
+ (NodeType.RPAREN, 0, 14, 62, 63, []),
+ (NodeType.COMMENT, 0, 14, 64, 80, []),
]),
]),
- (NodeType.STATEMENT, WrapAlgo.HPACK, 16, 2, 9, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 16, 2, 7, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 16, 7, 8, []),
- (NodeType.RPAREN, WrapAlgo.HPACK, 16, 8, 9, []),
+ (NodeType.STATEMENT, 0, 23, 2, 9, [
+ (NodeType.FUNNAME, 0, 23, 2, 7, []),
+ (NodeType.LPAREN, 0, 23, 7, 8, []),
+ (NodeType.ARGGROUP, 0, 23, 8, 8, []),
+ (NodeType.RPAREN, 0, 23, 8, 9, []),
]),
]),
]),
- (NodeType.STATEMENT, WrapAlgo.HPACK, 17, 0, 7, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 17, 0, 5, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 17, 5, 6, []),
- (NodeType.RPAREN, WrapAlgo.HPACK, 17, 6, 7, []),
+ (NodeType.STATEMENT, 0, 24, 0, 7, [
+ (NodeType.FUNNAME, 0, 24, 0, 5, []),
+ (NodeType.LPAREN, 0, 24, 5, 6, []),
+ (NodeType.ARGGROUP, 0, 24, 6, 6, []),
+ (NodeType.RPAREN, 0, 24, 6, 7, []),
]),
]),
]),
@@ -349,47 +400,57 @@ def test_custom_command(self):
# This very long command should be broken up along keyword arguments
foo(nonkwarg_a nonkwarg_b HEADERS a.h b.h c.h d.h e.h f.h SOURCES a.cc b.cc d.cc DEPENDS foo bar baz)
""", [
- (NodeType.BODY, WrapAlgo.HPACK, 0, 0, 68, [
- (NodeType.COMMENT, WrapAlgo.HPACK, 0, 0, 68, []),
- (NodeType.STATEMENT, WrapAlgo.VPACK, 1, 0, 26, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 1, 0, 3, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 1, 3, 4, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 1, 4, 25, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 1, 4, 14, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 1, 15, 25, []),
- ]),
- (NodeType.KWARGGROUP, WrapAlgo.VPACK, 2, 4, 15, [
- (NodeType.KEYWORD, WrapAlgo.HPACK, 2, 4, 11, []),
- (NodeType.PARGGROUP, WrapAlgo.VPACK, 2, 12, 15, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 2, 12, 15, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 3, 12, 15, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 4, 12, 15, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 5, 12, 15, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 6, 12, 15, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 7, 12, 15, []),
- ]),
- ]),
- (NodeType.KWARGGROUP, WrapAlgo.HPACK, 8, 4, 26, [
- (NodeType.KEYWORD, WrapAlgo.HPACK, 8, 4, 11, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 8, 12, 26, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 8, 12, 16, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 8, 17, 21, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 8, 22, 26, []),
- ]),
- ]),
- (NodeType.KWARGGROUP, WrapAlgo.HPACK, 9, 4, 15, [
- (NodeType.KEYWORD, WrapAlgo.HPACK, 9, 4, 11, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 9, 12, 15, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 9, 12, 15, []),
- ]),
- ]),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 10, 4, 11, [
- (NodeType.FLAG, WrapAlgo.HPACK, 10, 4, 7, []),
- (NodeType.FLAG, WrapAlgo.HPACK, 10, 8, 11, []),
- ]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 10, 11, 12, []),
- ]),
+# pylint: disable=bad-continuation
+# noqa: E122
+(NodeType.BODY, 0, 0, 0, 68, [
+ (NodeType.COMMENT, 0, 0, 0, 68, []),
+ (NodeType.STATEMENT, 4, 1, 0, 35, [
+ (NodeType.FUNNAME, 0, 1, 0, 3, []),
+ (NodeType.LPAREN, 0, 1, 3, 4, []),
+ (NodeType.ARGGROUP, 4, 1, 4, 35, [
+ (NodeType.PARGGROUP, 0, 1, 4, 25, [
+ (NodeType.ARGUMENT, 0, 1, 4, 14, []),
+ (NodeType.ARGUMENT, 0, 1, 15, 25, []),
+ ]),
+ (NodeType.KWARGGROUP, 0, 2, 4, 35, [
+ (NodeType.KEYWORD, 0, 2, 4, 11, []),
+ (NodeType.ARGGROUP, 0, 2, 12, 35, [
+ (NodeType.PARGGROUP, 0, 2, 12, 35, [
+ (NodeType.ARGUMENT, 0, 2, 12, 15, []),
+ (NodeType.ARGUMENT, 0, 2, 16, 19, []),
+ (NodeType.ARGUMENT, 0, 2, 20, 23, []),
+ (NodeType.ARGUMENT, 0, 2, 24, 27, []),
+ (NodeType.ARGUMENT, 0, 2, 28, 31, []),
+ (NodeType.ARGUMENT, 0, 2, 32, 35, []),
+ ]),
+ ]),
+ ]),
+ (NodeType.KWARGGROUP, 0, 3, 4, 26, [
+ (NodeType.KEYWORD, 0, 3, 4, 11, []),
+ (NodeType.ARGGROUP, 0, 3, 12, 26, [
+ (NodeType.PARGGROUP, 0, 3, 12, 26, [
+ (NodeType.ARGUMENT, 0, 3, 12, 16, []),
+ (NodeType.ARGUMENT, 0, 3, 17, 21, []),
+ (NodeType.ARGUMENT, 0, 3, 22, 26, []),
+ ]),
+ ]),
+ ]),
+ (NodeType.KWARGGROUP, 0, 4, 4, 15, [
+ (NodeType.KEYWORD, 0, 4, 4, 11, []),
+ (NodeType.ARGGROUP, 0, 4, 12, 15, [
+ (NodeType.PARGGROUP, 0, 4, 12, 15, [
+ (NodeType.ARGUMENT, 0, 4, 12, 15, []),
]),
+ ]),
+ ]),
+ (NodeType.PARGGROUP, 0, 5, 4, 11, [
+ (NodeType.FLAG, 0, 5, 4, 7, []),
+ (NodeType.FLAG, 0, 5, 8, 11, []),
+ ]),
+ ]),
+ (NodeType.RPAREN, 0, 5, 11, 12, []),
+ ]),
+]),
])
def test_multiline_string(self):
@@ -398,18 +459,22 @@ def test_multiline_string(self):
This string is on multiple lines
")
""", [
- (NodeType.BODY, WrapAlgo.HPACK, 0, 0, 36, [
- (NodeType.STATEMENT, WrapAlgo.HPACK, 0, 0, 36, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 0, 0, 3, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 0, 3, 4, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 0, 4, 36, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 0, 4, 12, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 0, 13, 21, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 0, 22, 36, []),
- ]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 2, 1, 2, []),
- ]),
- ]),
+# pylint: disable=bad-continuation
+# noqa: E122
+(NodeType.BODY, 0, 0, 0, 36, [
+ (NodeType.STATEMENT, 0, 0, 0, 36, [
+ (NodeType.FUNNAME, 0, 0, 0, 3, []),
+ (NodeType.LPAREN, 0, 0, 3, 4, []),
+ (NodeType.ARGGROUP, 0, 0, 4, 36, [
+ (NodeType.PARGGROUP, 0, 0, 4, 36, [
+ (NodeType.ARGUMENT, 0, 0, 4, 12, []),
+ (NodeType.ARGUMENT, 0, 0, 13, 21, []),
+ (NodeType.ARGUMENT, 0, 0, 22, 36, []),
+ ]),
+ ]),
+ (NodeType.RPAREN, 0, 2, 1, 2, []),
+ ]),
+]),
])
def test_nested_parens(self):
@@ -421,157 +486,196 @@ def test_nested_parens(self):
""", [
# pylint: disable=bad-continuation
# noqa: E122
-(NodeType.BODY, WrapAlgo.HPACK, 0, 0, 40, [
- (NodeType.FLOW_CONTROL, WrapAlgo.HPACK, 0, 0, 40, [
- (NodeType.STATEMENT, WrapAlgo.HPACK, 0, 0, 40, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 0, 0, 2, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 0, 2, 3, []),
- (NodeType.PARENGROUP, WrapAlgo.HPACK, 0, 3, 14, [
- (NodeType.LPAREN, WrapAlgo.HPACK, 0, 3, 4, []),
- (NodeType.ARGGROUP, WrapAlgo.HPACK, 0, 4, 13, [
- (NodeType.FLAG, WrapAlgo.HPACK, 0, 4, 7, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 0, 8, 13, []),
+(NodeType.BODY, 0, 0, 0, 40, [
+ (NodeType.FLOW_CONTROL, 0, 0, 0, 40, [
+ (NodeType.STATEMENT, 0, 0, 0, 40, [
+ (NodeType.FUNNAME, 0, 0, 0, 2, []),
+ (NodeType.LPAREN, 0, 0, 2, 3, []),
+ (NodeType.ARGGROUP, 0, 0, 3, 39, [
+ (NodeType.PARENGROUP, 0, 0, 3, 14, [
+ (NodeType.LPAREN, 0, 0, 3, 4, []),
+ (NodeType.ARGGROUP, 0, 0, 4, 13, [
+ (NodeType.PARGGROUP, 0, 0, 4, 13, [
+ (NodeType.FLAG, 0, 0, 4, 7, []),
+ (NodeType.ARGUMENT, 0, 0, 8, 13, []),
+ ]),
+ ]),
+ (NodeType.RPAREN, 0, 0, 13, 14, []),
]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 0, 13, 14, []),
- ]),
- (NodeType.KWARGGROUP, WrapAlgo.HPACK, 0, 15, 39, [
- (NodeType.KEYWORD, WrapAlgo.HPACK, 0, 15, 17, []),
- (NodeType.ARGGROUP, WrapAlgo.HPACK, 0, 18, 39, [
- (NodeType.PARENGROUP, WrapAlgo.HPACK, 0, 18, 39, [
- (NodeType.LPAREN, WrapAlgo.HPACK, 0, 18, 19, []),
- (NodeType.ARGGROUP, WrapAlgo.HPACK, 0, 19, 38, [
- (NodeType.FLAG, WrapAlgo.HPACK, 0, 19, 22, []),
- (NodeType.FLAG, WrapAlgo.HPACK, 0, 23, 29, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 0, 30, 38, []),
+ (NodeType.KWARGGROUP, 0, 0, 15, 39, [
+ (NodeType.KEYWORD, 0, 0, 15, 17, []),
+ (NodeType.ARGGROUP, 0, 0, 18, 39, [
+ (NodeType.PARENGROUP, 0, 0, 18, 39, [
+ (NodeType.LPAREN, 0, 0, 18, 19, []),
+ (NodeType.ARGGROUP, 0, 0, 19, 38, [
+ (NodeType.PARGGROUP, 0, 0, 19, 38, [
+ (NodeType.FLAG, 0, 0, 19, 22, []),
+ (NodeType.FLAG, 0, 0, 23, 29, []),
+ (NodeType.ARGUMENT, 0, 0, 30, 38, []),
+ ]),
+ ]),
+ (NodeType.RPAREN, 0, 0, 38, 39, []),
]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 0, 38, 39, []),
]),
]),
]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 0, 39, 40, []),
+ (NodeType.RPAREN, 0, 0, 39, 40, []),
]),
- (NodeType.BODY, WrapAlgo.HPACK, 1, 2, 39, [
- (NodeType.STATEMENT, WrapAlgo.HPACK, 1, 2, 39, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 1, 2, 9, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 1, 9, 10, []),
- (NodeType.KWARGGROUP, WrapAlgo.HPACK, 1, 10, 38, [
- (NodeType.KEYWORD, WrapAlgo.HPACK, 1, 10, 17, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 1, 18, 38, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 1, 18, 38, []),
+ (NodeType.BODY, 0, 1, 2, 39, [
+ (NodeType.STATEMENT, 0, 1, 2, 39, [
+ (NodeType.FUNNAME, 0, 1, 2, 9, []),
+ (NodeType.LPAREN, 0, 1, 9, 10, []),
+ (NodeType.ARGGROUP, 0, 1, 10, 38, [
+ (NodeType.KWARGGROUP, 0, 1, 10, 38, [
+ (NodeType.KEYWORD, 0, 1, 10, 17, []),
+ (NodeType.ARGGROUP, 0, 1, 18, 38, [
+ (NodeType.PARGGROUP, 0, 1, 18, 38, [
+ (NodeType.ARGUMENT, 0, 1, 18, 38, []),
+ ]),
+ ]),
]),
]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 1, 38, 39, []),
+ (NodeType.RPAREN, 0, 1, 38, 39, []),
]),
- (NodeType.STATEMENT, WrapAlgo.HPACK, 2, 2, 19, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 2, 2, 5, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 2, 5, 6, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 2, 6, 12, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 2, 6, 12, []),
- ]),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 2, 13, 18, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 2, 13, 18, []),
+ (NodeType.STATEMENT, 0, 2, 2, 19, [
+ (NodeType.FUNNAME, 0, 2, 2, 5, []),
+ (NodeType.LPAREN, 0, 2, 5, 6, []),
+ (NodeType.ARGGROUP, 0, 2, 6, 18, [
+ (NodeType.PARGGROUP, 0, 2, 6, 12, [
+ (NodeType.ARGUMENT, 0, 2, 6, 12, []),
+ ]),
+ (NodeType.PARGGROUP, 0, 2, 13, 18, [
+ (NodeType.ARGUMENT, 0, 2, 13, 18, []),
+ ]),
]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 2, 18, 19, []),
+ (NodeType.RPAREN, 0, 2, 18, 19, []),
]),
]),
- (NodeType.STATEMENT, WrapAlgo.HPACK, 3, 0, 7, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 3, 0, 5, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 3, 5, 6, []),
- (NodeType.RPAREN, WrapAlgo.HPACK, 3, 6, 7, []),
+ (NodeType.STATEMENT, 0, 3, 0, 7, [
+ (NodeType.FUNNAME, 0, 3, 0, 5, []),
+ (NodeType.LPAREN, 0, 3, 5, 6, []),
+ (NodeType.ARGGROUP, 0, 3, 6, 6, []),
+ (NodeType.RPAREN, 0, 3, 6, 7, []),
]),
]),
]),
])
def test_comment_after_command(self):
- self.do_layout_test("""\
- foo_command() # comment
- """, [
- (NodeType.BODY, WrapAlgo.HPACK, 0, 0, 23, [
- (NodeType.STATEMENT, WrapAlgo.HPACK, 0, 0, 23, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 0, 0, 11, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 0, 11, 12, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 0, 12, 12, []),
- (NodeType.RPAREN, WrapAlgo.HPACK, 0, 12, 13, []),
- (NodeType.COMMENT, WrapAlgo.HPACK, 0, 14, 23, []),
- ]),
- ]),
- ])
+ with self.subTest(sub=1):
+ self.do_layout_test("""\
+ foo_command() # comment
+ """, [
+# pylint: disable=bad-continuation
+# noqa: E122
+(NodeType.BODY, 0, 0, 0, 23, [
+ (NodeType.STATEMENT, 0, 0, 0, 23, [
+ (NodeType.FUNNAME, 0, 0, 0, 11, []),
+ (NodeType.LPAREN, 0, 0, 11, 12, []),
+ (NodeType.ARGGROUP, 0, 0, 12, 12, []),
+ (NodeType.RPAREN, 0, 0, 12, 13, []),
+ (NodeType.COMMENT, 0, 0, 14, 23, []),
+ ]),
+]),
+ ])
- self.do_layout_test("""\
- foo_command() # this is a long comment that exceeds the desired page width and will be wrapped to a newline
- """, [
- (NodeType.BODY, WrapAlgo.HPACK, 0, 0, 78, [
- (NodeType.STATEMENT, WrapAlgo.HPACK, 0, 0, 78, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 0, 0, 11, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 0, 11, 12, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 0, 12, 12, []),
- (NodeType.RPAREN, WrapAlgo.HPACK, 0, 12, 13, []),
- (NodeType.COMMENT, WrapAlgo.HPACK, 0, 14, 78, []),
- ]),
- ]),
- ])
+ with self.subTest(sub=2):
+ self.do_layout_test("""\
+ foo_command() # this is a long comment that exceeds the desired page width and will be wrapped to a newline
+ """, [
+# pylint: disable=bad-continuation
+# noqa: E122
+(NodeType.BODY, 0, 0, 0, 78, [
+ (NodeType.STATEMENT, 0, 0, 0, 78, [
+ (NodeType.FUNNAME, 0, 0, 0, 11, []),
+ (NodeType.LPAREN, 0, 0, 11, 12, []),
+ (NodeType.ARGGROUP, 0, 0, 12, 12, []),
+ (NodeType.RPAREN, 0, 0, 12, 13, []),
+ (NodeType.COMMENT, 0, 0, 14, 78, []),
+ ]),
+]),
+
+ ])
def test_arg_just_fits(self):
"""
Ensure that if an argument *just* fits that it isn't superfluously wrapped
"""
- self.do_layout_test("""\
+ with self.subTest(chars=81):
+ self.do_layout_test("""\
message(FATAL_ERROR "81 character line ----------------------------------------")
""", [
- (NodeType.BODY, WrapAlgo.HPACK, 0, 0, 75, [
- (NodeType.STATEMENT, WrapAlgo.KWNVPACK, 0, 0, 75, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 0, 0, 7, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 0, 7, 8, []),
- (NodeType.KWARGGROUP, WrapAlgo.HPACK, 1, 2, 74, [
- (NodeType.KEYWORD, WrapAlgo.HPACK, 1, 2, 13, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 1, 14, 74, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 1, 14, 74, []),
- ]),
- ]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 1, 74, 75, []),
- ]),
+# pylint: disable=bad-continuation
+# noqa: E122
+(NodeType.BODY, 0, 0, 0, 75, [
+ (NodeType.STATEMENT, 1, 0, 0, 75, [
+ (NodeType.FUNNAME, 0, 0, 0, 7, []),
+ (NodeType.LPAREN, 0, 0, 7, 8, []),
+ (NodeType.ARGGROUP, 0, 1, 2, 74, [
+ (NodeType.KWARGGROUP, 0, 1, 2, 74, [
+ (NodeType.KEYWORD, 0, 1, 2, 13, []),
+ (NodeType.ARGGROUP, 0, 1, 14, 74, [
+ (NodeType.PARGGROUP, 0, 1, 14, 74, [
+ (NodeType.ARGUMENT, 0, 1, 14, 74, []),
+ ]),
]),
+ ]),
+ ]),
+ (NodeType.RPAREN, 0, 1, 74, 75, []),
+ ]),
+]),
])
- self.do_layout_test("""\
+ with self.subTest(chars=100, with_prefix=True):
+ self.do_layout_test("""\
message(FATAL_ERROR
"100 character line ----------------------------------------------------------"
) # Closing parenthesis is indented one space!
""", [
- (NodeType.BODY, WrapAlgo.HPACK, 0, 0, 83, [
- (NodeType.STATEMENT, WrapAlgo.PNVPACK, 0, 0, 83, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 0, 0, 7, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 0, 7, 8, []),
- (NodeType.KWARGGROUP, WrapAlgo.PNVPACK, 1, 2, 83, [
- (NodeType.KEYWORD, WrapAlgo.HPACK, 1, 2, 13, []),
- (NodeType.PARGGROUP, WrapAlgo.PNVPACK, 2, 4, 83, [
- (NodeType.ARGUMENT, WrapAlgo.PNVPACK, 2, 4, 83, []),
- ]),
- ]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 3, 2, 3, []),
- (NodeType.COMMENT, WrapAlgo.HPACK, 3, 4, 48, []),
- ]),
+# pylint: disable=bad-continuation
+# noqa: E122
+(NodeType.BODY, 0, 0, 0, 83, [
+ (NodeType.STATEMENT, 5, 0, 0, 83, [
+ (NodeType.FUNNAME, 0, 0, 0, 7, []),
+ (NodeType.LPAREN, 0, 0, 7, 8, []),
+ (NodeType.ARGGROUP, 5, 1, 2, 83, [
+ (NodeType.KWARGGROUP, 5, 1, 2, 83, [
+ (NodeType.KEYWORD, 0, 1, 2, 13, []),
+ (NodeType.ARGGROUP, 5, 2, 4, 83, [
+ (NodeType.PARGGROUP, 4, 2, 4, 83, [
+ (NodeType.ARGUMENT, 0, 2, 4, 83, []),
+ ]),
]),
+ ]),
+ ]),
+ (NodeType.RPAREN, 0, 3, 0, 1, []),
+ (NodeType.COMMENT, 0, 3, 2, 46, []),
+ ]),
+]),
])
- self.do_layout_test("""\
+ with self.subTest(chars=100):
+ self.do_layout_test("""\
message(
"100 character line ----------------------------------------------------------------------"
) # Closing parenthesis is indented one space!
""", [
- (NodeType.BODY, WrapAlgo.HPACK, 0, 0, 93, [
- (NodeType.STATEMENT, WrapAlgo.PNVPACK, 0, 0, 93, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 0, 0, 7, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 0, 7, 8, []),
- (NodeType.PARGGROUP, WrapAlgo.PNVPACK, 1, 2, 93, [
- (NodeType.ARGUMENT, WrapAlgo.PNVPACK, 1, 2, 93, []),
- ]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 2, 2, 3, []),
- (NodeType.COMMENT, WrapAlgo.HPACK, 2, 4, 48, []),
- ]),
- ]),
+# pylint: disable=bad-continuation
+# noqa: E122
+(NodeType.BODY, 0, 0, 0, 93, [
+ (NodeType.STATEMENT, 5, 0, 0, 93, [
+ (NodeType.FUNNAME, 0, 0, 0, 7, []),
+ (NodeType.LPAREN, 0, 0, 7, 8, []),
+ (NodeType.ARGGROUP, 5, 1, 2, 93, [
+ (NodeType.PARGGROUP, 4, 1, 2, 93, [
+ (NodeType.ARGUMENT, 0, 1, 2, 93, []),
+ ]),
+ ]),
+ (NodeType.RPAREN, 0, 2, 0, 1, []),
+ (NodeType.COMMENT, 0, 2, 2, 46, []),
+ ]),
+]),
])
def test_string_preserved_during_split(self):
@@ -579,26 +683,32 @@ def test_string_preserved_during_split(self):
# The string in this command should not be split
set_target_properties(foo bar baz PROPERTIES COMPILE_FLAGS "-std=c++11 -Wall -Wextra")
""", [
- (NodeType.BODY, WrapAlgo.HPACK, 0, 0, 74, [
- (NodeType.COMMENT, WrapAlgo.HPACK, 0, 0, 48, []),
- (NodeType.STATEMENT, WrapAlgo.VPACK, 1, 0, 74, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 1, 0, 21, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 1, 21, 22, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 1, 22, 33, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 1, 22, 25, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 1, 26, 29, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 1, 30, 33, []),
- ]),
- (NodeType.KWARGGROUP, WrapAlgo.HPACK, 2, 22, 73, [
- (NodeType.KEYWORD, WrapAlgo.HPACK, 2, 22, 32, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 2, 33, 73, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 2, 33, 46, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 2, 47, 73, []),
- ]),
- ]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 2, 73, 74, []),
- ]),
+# pylint: disable=bad-continuation
+# noqa: E122
+(NodeType.BODY, 0, 0, 0, 72, [
+ (NodeType.COMMENT, 0, 0, 0, 48, []),
+ (NodeType.STATEMENT, 0, 1, 0, 72, [
+ (NodeType.FUNNAME, 0, 1, 0, 21, []),
+ (NodeType.LPAREN, 0, 1, 21, 22, []),
+ (NodeType.ARGGROUP, 0, 1, 22, 71, [
+ (NodeType.PARGGROUP, 0, 1, 22, 33, [
+ (NodeType.ARGUMENT, 0, 1, 22, 25, []),
+ (NodeType.ARGUMENT, 0, 1, 26, 29, []),
+ (NodeType.ARGUMENT, 0, 1, 30, 33, []),
+ ]),
+ (NodeType.KWARGGROUP, 0, 1, 34, 71, [
+ (NodeType.KEYWORD, 0, 1, 34, 44, []),
+ (NodeType.ARGGROUP, 0, 1, 45, 71, [
+ (NodeType.PARGGROUP, 0, 1, 45, 71, [
+ (NodeType.ARGUMENT, 0, 1, 45, 58, []),
+ (NodeType.ARGUMENT, 0, 2, 45, 71, []),
]),
+ ]),
+ ]),
+ ]),
+ (NodeType.RPAREN, 0, 2, 71, 72, []),
+ ]),
+]),
])
def test_while(self):
@@ -609,32 +719,39 @@ def test_while(self):
""", [
# pylint: disable=bad-continuation
# noqa: E122
-(NodeType.BODY, WrapAlgo.HPACK, 0, 0, 32, [
- (NodeType.FLOW_CONTROL, WrapAlgo.HPACK, 0, 0, 32, [
- (NodeType.STATEMENT, WrapAlgo.HPACK, 0, 0, 32, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 0, 0, 5, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 0, 5, 6, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 0, 6, 15, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 0, 16, 20, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 0, 21, 26, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 0, 27, 31, []),
- (NodeType.RPAREN, WrapAlgo.HPACK, 0, 31, 32, []),
+(NodeType.BODY, 0, 0, 0, 32, [
+ (NodeType.FLOW_CONTROL, 0, 0, 0, 32, [
+ (NodeType.STATEMENT, 0, 0, 0, 32, [
+ (NodeType.FUNNAME, 0, 0, 0, 5, []),
+ (NodeType.LPAREN, 0, 0, 5, 6, []),
+ (NodeType.ARGGROUP, 0, 0, 6, 31, [
+ (NodeType.PARGGROUP, 0, 0, 6, 31, [
+ (NodeType.ARGUMENT, 0, 0, 6, 15, []),
+ (NodeType.ARGUMENT, 0, 0, 16, 20, []),
+ (NodeType.ARGUMENT, 0, 0, 21, 26, []),
+ (NodeType.ARGUMENT, 0, 0, 27, 31, []),
+ ]),
+ ]),
+ (NodeType.RPAREN, 0, 0, 31, 32, []),
]),
- (NodeType.BODY, WrapAlgo.HPACK, 1, 2, 29, [
- (NodeType.STATEMENT, WrapAlgo.HPACK, 1, 2, 29, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 1, 2, 9, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 1, 9, 10, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 1, 10, 28, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 1, 10, 15, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 1, 16, 28, []),
+ (NodeType.BODY, 0, 1, 2, 29, [
+ (NodeType.STATEMENT, 0, 1, 2, 29, [
+ (NodeType.FUNNAME, 0, 1, 2, 9, []),
+ (NodeType.LPAREN, 0, 1, 9, 10, []),
+ (NodeType.ARGGROUP, 0, 1, 10, 28, [
+ (NodeType.PARGGROUP, 0, 1, 10, 28, [
+ (NodeType.ARGUMENT, 0, 1, 10, 15, []),
+ (NodeType.ARGUMENT, 0, 1, 16, 28, []),
+ ]),
]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 1, 28, 29, []),
+ (NodeType.RPAREN, 0, 1, 28, 29, []),
]),
]),
- (NodeType.STATEMENT, WrapAlgo.HPACK, 2, 0, 10, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 2, 0, 8, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 2, 8, 9, []),
- (NodeType.RPAREN, WrapAlgo.HPACK, 2, 9, 10, []),
+ (NodeType.STATEMENT, 0, 2, 0, 10, [
+ (NodeType.FUNNAME, 0, 2, 0, 8, []),
+ (NodeType.LPAREN, 0, 2, 8, 9, []),
+ (NodeType.ARGGROUP, 0, 2, 9, 9, []),
+ (NodeType.RPAREN, 0, 2, 9, 10, []),
]),
]),
]),
@@ -648,26 +765,32 @@ def test_keyword_comment(self):
# --------------------------------------
this_is_a_really_long_word_foo)
""", [
- (NodeType.BODY, WrapAlgo.HPACK, 0, 0, 64, [
- (NodeType.STATEMENT, WrapAlgo.VPACK, 0, 0, 64, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 0, 0, 12, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 0, 12, 13, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 0, 13, 29, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 0, 13, 20, []),
- (NodeType.FLAG, WrapAlgo.HPACK, 0, 21, 29, []),
- ]),
- (NodeType.KWARGGROUP, WrapAlgo.VPACK, 1, 13, 64, [
- (NodeType.KEYWORD, WrapAlgo.HPACK, 1, 13, 23, []),
- (NodeType.PARGGROUP, WrapAlgo.VPACK, 1, 24, 64, [
- (NodeType.COMMENT, WrapAlgo.HPACK, 1, 24, 64, []),
- (NodeType.COMMENT, WrapAlgo.HPACK, 2, 24, 63, []),
- (NodeType.COMMENT, WrapAlgo.HPACK, 3, 24, 64, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 4, 24, 54, []),
- ]),
- ]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 4, 54, 55, []),
- ]),
+# pylint: disable=bad-continuation
+# noqa: E122
+(NodeType.BODY, 0, 0, 0, 53, [
+ (NodeType.STATEMENT, 4, 0, 0, 53, [
+ (NodeType.FUNNAME, 0, 0, 0, 12, []),
+ (NodeType.LPAREN, 0, 0, 12, 13, []),
+ (NodeType.ARGGROUP, 4, 1, 2, 53, [
+ (NodeType.PARGGROUP, 0, 1, 2, 18, [
+ (NodeType.ARGUMENT, 0, 1, 2, 9, []),
+ (NodeType.FLAG, 0, 1, 10, 18, []),
+ ]),
+ (NodeType.KWARGGROUP, 4, 2, 2, 53, [
+ (NodeType.KEYWORD, 0, 2, 2, 12, []),
+ (NodeType.ARGGROUP, 4, 2, 13, 53, [
+ (NodeType.COMMENT, 0, 2, 13, 53, []),
+ (NodeType.COMMENT, 0, 3, 13, 52, []),
+ (NodeType.COMMENT, 0, 4, 13, 53, []),
+ (NodeType.PARGGROUP, 0, 5, 13, 43, [
+ (NodeType.ARGUMENT, 0, 5, 13, 43, []),
]),
+ ]),
+ ]),
+ ]),
+ (NodeType.RPAREN, 0, 5, 43, 44, []),
+ ]),
+]),
])
def test_sortable_set(self):
@@ -675,22 +798,26 @@ def test_sortable_set(self):
self.do_layout_test("""\
set(SOURCES #[[cmf:sortable]] foo.cc bar.cc baz.cc)
""", [
- (NodeType.BODY, WrapAlgo.HPACK, 0, 0, 51, [
- (NodeType.STATEMENT, WrapAlgo.HPACK, 0, 0, 51, [
- (NodeType.FUNNAME, WrapAlgo.HPACK, 0, 0, 3, []),
- (NodeType.LPAREN, WrapAlgo.HPACK, 0, 3, 4, []),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 0, 4, 11, [
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 0, 4, 11, []),
- ]),
- (NodeType.PARGGROUP, WrapAlgo.HPACK, 0, 12, 50, [
- (NodeType.COMMENT, WrapAlgo.HPACK, 0, 12, 29, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 0, 30, 36, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 0, 37, 43, []),
- (NodeType.ARGUMENT, WrapAlgo.HPACK, 0, 44, 50, []),
- ]),
- (NodeType.RPAREN, WrapAlgo.HPACK, 0, 50, 51, []),
- ]),
- ]),
+# pylint: disable=bad-continuation
+# noqa: E122
+(NodeType.BODY, 0, 0, 0, 51, [
+ (NodeType.STATEMENT, 0, 0, 0, 51, [
+ (NodeType.FUNNAME, 0, 0, 0, 3, []),
+ (NodeType.LPAREN, 0, 0, 3, 4, []),
+ (NodeType.ARGGROUP, 0, 0, 4, 50, [
+ (NodeType.PARGGROUP, 0, 0, 4, 11, [
+ (NodeType.ARGUMENT, 0, 0, 4, 11, []),
+ ]),
+ (NodeType.PARGGROUP, 0, 0, 12, 50, [
+ (NodeType.COMMENT, 0, 0, 12, 29, []),
+ (NodeType.ARGUMENT, 0, 0, 30, 36, []),
+ (NodeType.ARGUMENT, 0, 0, 37, 43, []),
+ (NodeType.ARGUMENT, 0, 0, 44, 50, []),
+ ]),
+ ]),
+ (NodeType.RPAREN, 0, 0, 50, 51, []),
+ ]),
+]),
])
diff --git a/cmake_format/parse_funs/__init__.py b/cmake_format/parse_funs/__init__.py
index db569ef..1870d19 100644
--- a/cmake_format/parse_funs/__init__.py
+++ b/cmake_format/parse_funs/__init__.py
@@ -46,7 +46,7 @@ def split_legacy_spec(cmdspec):
elif kwarg == "COMMAND":
subparser = parser.parse_shell_command
elif isinstance(subspec, parser.IMPLICIT_PARG_TYPES):
- subparser = parser.PositionalParser(subspec, [])
+ subparser = parser.StandardParser(subspec)
elif isinstance(subspec, (commands.CommandSpec)):
subparser = get_legacy_parse(subspec)
else:
diff --git a/cmake_format/parse_funs/file.py b/cmake_format/parse_funs/file.py
index 85d26c2..2ad8f3f 100644
--- a/cmake_format/parse_funs/file.py
+++ b/cmake_format/parse_funs/file.py
@@ -9,6 +9,7 @@
parse_positionals,
parse_standard,
PositionalParser,
+ StandardParser,
TreeNode,
)
@@ -375,23 +376,23 @@ def parse_file(tokens, breakstack):
"TIMESTAMP": parse_file_timestamp,
"WRITE": parse_file_write,
"APPEND": parse_file_write,
- "TOUCH": PositionalParser('+', ["TOUCH"]),
- "TOUCH_NO_CREATE": PositionalParser('+', ["TOUCH_NO_CREATE"]),
+ "TOUCH": StandardParser('+', flags=["TOUCH"]),
+ "TOUCH_NO_CREATE": StandardParser('+', flags=["TOUCH_NO_CREATE"]),
"GENERATE": parse_file_generate_output,
"GLOB": parse_file_glob,
"GLOB_RECURSE": parse_file_glob,
- "RENAME": PositionalParser(3, ["RENAME"]),
- "REMOVE": PositionalParser('+', ["REMOVE"]),
- "REMOVE_RECURSE": PositionalParser('+', ["REMOVE_RECURSE"]),
- "MAKE_DIRECTORY": PositionalParser('+', ["MAKE_DIRECTORY"]),
+ "RENAME": StandardParser(3, flags=["RENAME"]),
+ "REMOVE": StandardParser('+', flags=["REMOVE"]),
+ "REMOVE_RECURSE": StandardParser('+', flags=["REMOVE_RECURSE"]),
+ "MAKE_DIRECTORY": StandardParser('+', flags=["MAKE_DIRECTORY"]),
"COPY": parse_file_copy,
"INSTALL": parse_file_copy,
- "SIZE": PositionalParser(3, ["SIZE"]),
- "READ_SYMLINK": PositionalParser(3, ["READ_SYMLINK"]),
+ "SIZE": StandardParser(3, flags=["SIZE"]),
+ "READ_SYMLINK": StandardParser(3, flags=["READ_SYMLINK"]),
"CREATE_LINK": parse_file_create_link,
- "RELATIVE_PATH": PositionalParser(4, ["RELATIVE_PATH"]),
- "TO_CMAKE_PATH": PositionalParser(3, ["TO_CMAKE_PATH"]),
- "TO_NATIVE_PATH": PositionalParser(3, ["TO_NATIVE_PATH"]),
+ "RELATIVE_PATH": StandardParser(4, flags=["RELATIVE_PATH"]),
+ "TO_CMAKE_PATH": StandardParser(3, flags=["TO_CMAKE_PATH"]),
+ "TO_NATIVE_PATH": StandardParser(3, flags=["TO_NATIVE_PATH"]),
"DOWNLOAD": parse_file_xfer,
"UPLOAD": parse_file_xfer,
"LOCK": parse_file_lock
diff --git a/cmake_format/parser.py b/cmake_format/parser.py
index e4a953d..6ec9c99 100644
--- a/cmake_format/parser.py
+++ b/cmake_format/parser.py
@@ -134,29 +134,40 @@ def consume_comment(tokens):
"""
node = TreeNode(NodeType.COMMENT)
- comment_tokens = node.children
+ if tokens[0].type == lexer.TokenType.BRACKET_COMMENT:
+ node.children.append(tokens.pop(0))
+ return node
+
+ comment_tokens = []
while tokens and tokens[0].type in COMMENT_TOKENS:
- comment_tokens.append(tokens.pop(0))
+ # If the next comment token is not column-aligned, then we don't
+ # merge it with the previous comment line
+ if (comment_tokens and
+ not are_column_aligned(comment_tokens[-1], tokens[0])):
+ break
+
+ comment_token = tokens.pop(0)
+ comment_tokens.append(comment_token)
+ node.children.append(comment_token)
# Multiple comments separated by only one newline are joined together into
# a single block
if (len(tokens) > 1
# pylint: disable=bad-continuation
and tokens[0].type == lexer.TokenType.NEWLINE
- and tokens[1].type in COMMENT_TOKENS):
- comment_tokens.append(tokens.pop(0))
+ and tokens[1].type in COMMENT_TOKENS):
+ node.children.append(tokens.pop(0))
# Multiple comments separated only by one newline and some whitespace are
# joined together into a single block
- # TODO(josh): maybe match only on comment tokens that start at the same
- # column
elif (len(tokens) > 2
# pylint: disable=bad-continuation
and tokens[0].type == lexer.TokenType.NEWLINE
and tokens[1].type == lexer.TokenType.WHITESPACE
and tokens[2].type in COMMENT_TOKENS):
- comment_tokens.append(tokens.pop(0))
- comment_tokens.append(tokens.pop(0))
+ node.children.append(tokens.pop(0))
+ node.children.append(tokens.pop(0))
+
return node
@@ -179,6 +190,13 @@ def is_valid_trailing_comment(token):
not comment_is_tag(token))
+def are_column_aligned(token_a, token_b):
+ """
+ Return true if both tokens are on the same column.
+ """
+ return token_a.begin.col == token_b.begin.col
+
+
def next_is_trailing_comment(tokens):
"""
Return true if there is a trailing comment in the token stream
@@ -214,15 +232,22 @@ def consume_trailing_comment(parent, tokens):
parent.children.append(node)
comment_tokens = node.children
+ comment_tokens = []
while tokens and is_valid_trailing_comment(tokens[0]):
- comment_tokens.append(tokens.pop(0))
+ if (comment_tokens and
+ not are_column_aligned(comment_tokens[-1], tokens[0])):
+ break
+
+ comment_token = tokens.pop(0)
+ comment_tokens.append(comment_token)
+ node.children.append(comment_token)
# Multiple comments separated by only one newline are joined together into
# a single block
if (len(tokens) > 1 and
tokens[0].type == lexer.TokenType.NEWLINE and
is_valid_trailing_comment(tokens[1])):
- comment_tokens.append(tokens.pop(0))
+ node.children.append(tokens.pop(0))
# Multiple comments separated only by one newline and some whitespace are
# joined together into a single block
@@ -232,8 +257,8 @@ def consume_trailing_comment(parent, tokens):
tokens[0].type == lexer.TokenType.NEWLINE and
tokens[1].type == lexer.TokenType.WHITESPACE and
is_valid_trailing_comment(tokens[2])):
- comment_tokens.append(tokens.pop(0))
- comment_tokens.append(tokens.pop(0))
+ node.children.append(tokens.pop(0))
+ node.children.append(tokens.pop(0))
def get_normalized_kwarg(token):
@@ -356,6 +381,29 @@ def consume_whitespace_and_comments(tokens, tree):
break
+def iter_syntactic_tokens(tokens):
+ """
+ Return a generator over the list of tokens yielding only those that are
+ not whitespace
+ """
+ skip_tokens = (lexer.TokenType.WHITESPACE,
+ lexer.TokenType.NEWLINE)
+
+ for token in tokens:
+ if token.type in skip_tokens:
+ continue
+ yield token
+
+
+def get_next_syntactic_token(tokens):
+ """
+ return the first non-whitespace token in the list
+ """
+ for token in iter_syntactic_tokens(tokens):
+ return token
+ return None
+
+
def iter_semantic_tokens(tokens):
"""
Return a generator over the list of tokens yielding only those that
@@ -457,31 +505,30 @@ def parse_positionals(tokens, npargs, flags, breakstack, sortable=False):
tree.children.append(subtree)
continue
- # Otherwise we will consume the token
- token = tokens.pop(0)
-
# If it is a whitespace token then put it directly in the parse tree at
# the current depth
- if token.type in WHITESPACE_TOKENS:
- tree.children.append(token)
+ if tokens[0].type in WHITESPACE_TOKENS:
+ tree.children.append(tokens.pop(0))
continue
# If it's a comment token not associated with an argument, then put it
# directly into the parse tree at the current depth
- if token.type in (lexer.TokenType.COMMENT,
- lexer.TokenType.BRACKET_COMMENT):
- child = TreeNode(NodeType.COMMENT)
+ if tokens[0].type in (lexer.TokenType.COMMENT,
+ lexer.TokenType.BRACKET_COMMENT):
+ before = len(tokens)
+ child = consume_comment(tokens)
+ assert len(tokens) < before, \
+ "consume_comment didn't consume any tokens"
tree.children.append(child)
- child.children.append(token)
continue
# Otherwise is it is a positional argument, so add it to the tree as such
- if get_normalized_kwarg(token) in flags:
+ if get_normalized_kwarg(tokens[0]) in flags:
child = TreeNode(NodeType.FLAG)
else:
child = TreeNode(NodeType.ARGUMENT)
- child.children.append(token)
+ child.children.append(tokens.pop(0))
consume_trailing_comment(child, tokens)
tree.children.append(child)
nconsumed += 1
@@ -797,14 +844,15 @@ def parse_conditional(tokens, breakstack):
continue
# Otherwise is it is a positional argument, so add it to the tree as such
- token = tokens.pop(0)
- if get_normalized_kwarg(token) in flags:
- child = TreeNode(NodeType.FLAG)
- else:
- child = TreeNode(NodeType.ARGUMENT)
-
- child.children.append(token)
- consume_trailing_comment(child, tokens)
+ child = parse_positionals(tokens, '+', flags, child_breakstack)
+ # token = tokens.pop(0)
+ # if get_normalized_kwarg(token) in flags:
+ # child = TreeNode(NodeType.FLAG)
+ # else:
+ # child = TreeNode(NodeType.ARGUMENT)
+
+ # child.children.append(token)
+ # consume_trailing_comment(child, tokens)
tree.children.append(child)
return tree
@@ -849,24 +897,6 @@ def is_shell_flag(token):
return token.spelling.startswith("-")
-def parse_shell_kwarg(tokens, breakstack):
- """
- Parse a standard `--long-flag foo bar baz` sequence
- """
- assert tokens[0].spelling.startswith("--")
-
- tree = TreeNode(NodeType.KWARGGROUP)
- kwnode = TreeNode(NodeType.KEYWORD)
- kwnode.children.append(tokens.pop(0))
- tree.children.append(kwnode)
-
- ntokens = len(tokens)
- subtree = parse_positionals(tokens, "*", [], breakstack + [is_shell_flag])
- if len(tokens) < ntokens:
- tree.children.append(subtree)
- return tree
-
-
def parse_shell_command(tokens, breakstack):
"""
Parser for the COMMAND kwarg lists in the form of::
@@ -876,54 +906,7 @@ def parse_shell_command(tokens, breakstack):
The parser acts very similar to a standard parser where `--xxx` is treated
as a keyword argument and `-x` is treated as a flag.
"""
- return parse_positionals(tokens, '*', [], breakstack)
-
-
-def deprecated_parse_shell_command(tokens, breakstack):
- """
- Parser for the COMMAND kwarg lists in the form of::
-
- COMMAND foo --long-flag1 arg1 arg2 --long-flag2 -a -b -c arg3 arg4
-
- The parser acts very similar to a standard parser where `--xxx` is treated
- as a keyword argument and `-x` is treated as a flag.
- """
-
- # TODO(josh): remove dead code, or figure out when to enable it
- tree = TreeNode(NodeType.ARGGROUP)
-
- # If it is a whitespace token then put it directly in the parse tree at
- # the current depth
- while tokens and tokens[0].type in WHITESPACE_TOKENS:
- tree.children.append(tokens.pop(0))
- continue
-
- while tokens:
- # Break if the next token belongs to a parent parser, i.e. if it
- # matches a keyword argument of something higher in the stack, or if
- # it closes a parent group.
- if should_break(tokens[0], breakstack):
- break
-
- # If it is a whitespace token then put it directly in the parse tree at
- # the current depth
- if tokens[0].type in WHITESPACE_TOKENS:
- tree.children.append(tokens.pop(0))
- continue
-
- ntokens = len(tokens)
- if tokens[0].spelling == "--":
- subtree = parse_positionals(tokens, "*", [], breakstack)
- elif tokens[0].spelling.startswith("--"):
- subtree = parse_shell_kwarg(tokens, breakstack)
- elif tokens[0].spelling.startswith("-"):
- subtree = parse_shell_flags(tokens, breakstack)
- else:
- subtree = parse_positionals(tokens, "*", [], breakstack + [is_shell_flag])
-
- assert len(tokens) < ntokens, "at {}".format(tokens[0])
- tree.children.append(subtree)
- return tree
+ return parse_standard(tokens, '*', {}, [], breakstack)
class PositionalParser(object):
@@ -1034,7 +1017,7 @@ def consume_statement(tokens, parse_db):
continue
breakstack = [ParenBreaker()]
- parse_fun = parse_db.get(fnname, PositionalParser())
+ parse_fun = parse_db.get(fnname, StandardParser())
subtree = parse_fun(tokens, breakstack)
node.children.append(subtree)
diff --git a/cmake_format/parser_tests.py b/cmake_format/parser_tests.py
index f27f5a2..c328971 100644
--- a/cmake_format/parser_tests.py
+++ b/cmake_format/parser_tests.py
@@ -127,8 +127,10 @@ def test_collapse_additional_newlines(self):
(NodeType.ARGGROUP, [
(NodeType.KWARGGROUP, [
(NodeType.KEYWORD, []),
- (NodeType.PARGGROUP, [
- (NodeType.ARGUMENT, []),
+ (NodeType.ARGGROUP, [
+ (NodeType.PARGGROUP, [
+ (NodeType.ARGUMENT, []),
+ ]),
]),
]),
]),
@@ -180,19 +182,23 @@ def test_nested_kwargs(self):
]),
(NodeType.KWARGGROUP, [
(NodeType.KEYWORD, []),
- (NodeType.PARGGROUP, [
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
+ (NodeType.ARGGROUP, [
+ (NodeType.PARGGROUP, [
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ ]),
]),
]),
(NodeType.KWARGGROUP, [
(NodeType.KEYWORD, []),
- (NodeType.PARGGROUP, [
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
+ (NodeType.ARGGROUP, [
+ (NodeType.PARGGROUP, [
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ ]),
]),
]),
]),
@@ -221,27 +227,33 @@ def test_custom_command(self):
]),
(NodeType.KWARGGROUP, [
(NodeType.KEYWORD, []),
- (NodeType.PARGGROUP, [
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
+ (NodeType.ARGGROUP, [
+ (NodeType.PARGGROUP, [
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ ]),
]),
]),
(NodeType.KWARGGROUP, [
(NodeType.KEYWORD, []),
- (NodeType.PARGGROUP, [
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
+ (NodeType.ARGGROUP, [
+ (NodeType.PARGGROUP, [
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ ]),
]),
]),
(NodeType.KWARGGROUP, [
(NodeType.KEYWORD, []),
- (NodeType.PARGGROUP, [
- (NodeType.ARGUMENT, []),
+ (NodeType.ARGGROUP, [
+ (NodeType.PARGGROUP, [
+ (NodeType.ARGUMENT, []),
+ ]),
]),
]),
(NodeType.PARGGROUP, [
@@ -269,8 +281,10 @@ def test_shellcommand_parse(self):
(NodeType.ARGGROUP, [
(NodeType.KWARGGROUP, [
(NodeType.KEYWORD, []),
- (NodeType.PARGGROUP, [
- (NodeType.ARGUMENT, []),
+ (NodeType.ARGGROUP, [
+ (NodeType.PARGGROUP, [
+ (NodeType.ARGUMENT, []),
+ ]),
]),
]),
(NodeType.KWARGGROUP, [
@@ -278,21 +292,25 @@ def test_shellcommand_parse(self):
]),
(NodeType.KWARGGROUP, [
(NodeType.KEYWORD, []),
- (NodeType.PARGGROUP, [
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
- (NodeType.ARGUMENT, []),
+ (NodeType.ARGGROUP, [
+ (NodeType.PARGGROUP, [
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ (NodeType.ARGUMENT, []),
+ ]),
]),
]),
(NodeType.KWARGGROUP, [
(NodeType.KEYWORD, []),
- (NodeType.PARGGROUP, [
- (NodeType.ARGUMENT, []),
+ (NodeType.ARGGROUP, [
+ (NodeType.PARGGROUP, [
+ (NodeType.ARGUMENT, []),
+ ]),
]),
]),
]),
diff --git a/cmake_format/screw_users_test.py b/cmake_format/screw_users_test.py
index d0be5c8..126ab3e 100644
--- a/cmake_format/screw_users_test.py
+++ b/cmake_format/screw_users_test.py
@@ -147,6 +147,17 @@ def assertCanFormat(self):
self.assertFalse(failed_files)
+ def test_thisrepo(self):
+ thisdir = os.path.realpath(os.path.dirname(__file__))
+ head, _ = os.path.split(thisdir)
+ head, _ = os.path.split(thisdir)
+ self.repository = head
+ self.configpath = ".cmake-format.py"
+ self.last_known_good = "efb0d6256fcbc4a3cfad9f5ebdaa34172d230ea2"
+ self.exclude_patterns += [
+ r"test_latin.*\.cmake"
+ ]
+
def test_elektra(self):
self.repository = "https://github.com/sanssecours/elektra"
self.configpath = ".cmake-format.yaml"
@@ -161,7 +172,8 @@ def test_arrow(self):
self.configpath = "cmake-format.py"
self.last_known_good = "e32c0b3279df76d68c93724f476dfcf0c1c44fa5"
self.exclude_patterns += [
- r".*\.h\.cmake"
+ r".*\.h\.cmake",
+ r".*Dockerfile\.cmake",
]
def test_aom(self):
@@ -176,8 +188,8 @@ def test_vulkan_tools(self):
def test_vulkan_validation_layers(self):
self.repository = "https://github.com/KhronosGroup/Vulkan-ValidationLayers"
- self.configpath = ".cmake-format.py"
- self.last_known_good = "7212065b5a78afb13e7527379175a3b2ee939a14"
+ self.configpath = "scripts/cmake-format.py"
+ self.last_known_good = "adbfa85caafb5f240474dd2ae2414f0b78026a3f"
self.exclude_patterns += [
".*build-android/cmake/layerlib/CMakeLists.txt"
]
diff --git a/cmake_format/test/test_in.cmake b/cmake_format/test/test_in.cmake
index 8ea598d..a588b34 100644
--- a/cmake_format/test/test_in.cmake
+++ b/cmake_format/test/test_in.cmake
@@ -16,12 +16,11 @@ project(cmake_format_test)
add_subdirectories(foo bar baz
foo2 bar2 baz2)
-# This very long command should be split to multiple lines
+# This very long command should be wrapped
set(HEADERS very_long_header_name_a.h very_long_header_name_b.h very_long_header_name_c.h)
-# This command should be split into one line per entry because it has a long
-# argument list.
-set(SOURCES source_a.cc source_b.cc source_d.cc source_e.cc source_f.cc source_g.cc)
+# This command should be split into one line per entry because it has a long argument list.
+set(SOURCES source_a.cc source_b.cc source_d.cc source_e.cc source_f.cc source_g.cc source_h.cc)
# The string in this command should not be split
set_target_properties(foo bar baz PROPERTIES COMPILE_FLAGS "-std=c++11 -Wall -Wextra")
@@ -68,7 +67,7 @@ if(foo)
if(sbar)
# This comment is in-scope.
add_library(foo_bar_baz foo.cc bar.cc # this is a comment for arg2
- # this is more comment for arg2, it should be joined with the first.
+ # this is more comment for arg2, it should be joined with the first.
baz.cc) # This comment is part of add_library
other_command(some_long_argument some_long_argument) # this comment is very long and gets split across some lines
diff --git a/cmake_format/test/test_out.cmake b/cmake_format/test/test_out.cmake
index d6d5bed..0633af7 100644
--- a/cmake_format/test/test_out.cmake
+++ b/cmake_format/test/test_out.cmake
@@ -7,14 +7,9 @@ project(cmake_format_test)
# This comment should remain right before the command call. Furthermore, the
# command call should be formatted to a single line.
-add_subdirectories(foo
- bar
- baz
- foo2
- bar2
- baz2)
-
-# This very long command should be split to multiple lines
+add_subdirectories(foo bar baz foo2 bar2 baz2)
+
+# This very long command should be wrapped
set(HEADERS very_long_header_name_a.h very_long_header_name_b.h
very_long_header_name_c.h)
@@ -26,11 +21,12 @@ set(SOURCES
source_d.cc
source_e.cc
source_f.cc
- source_g.cc)
+ source_g.cc
+ source_h.cc)
# The string in this command should not be split
-set_target_properties(foo bar baz
- PROPERTIES COMPILE_FLAGS "-std=c++11 -Wall -Wextra")
+set_target_properties(foo bar baz PROPERTIES COMPILE_FLAGS
+ "-std=c++11 -Wall -Wextra")
# This command has a very long argument and can't be aligned with the command
# end, so it should be moved to a new line with block indent + 1.
@@ -43,11 +39,9 @@ set(CMAKE_CXX_FLAGS
"-std=c++11 -Wall -Wno-sign-compare -Wno-unused-parameter -xx")
set(HEADERS
- header_a.h
- header_b.h # This comment should be preserved, moreover it should be split
- # across two lines.
- header_c.h
- header_d.h)
+ header_a.h header_b.h # This comment should be preserved, moreover it should
+ # be split across two lines.
+ header_c.h header_d.h)
# This part of the comment should be formatted but...
# cmake-format: off
@@ -72,30 +66,32 @@ set(HEADERS
if(foo)
if(sbar)
# This comment is in-scope.
- add_library(foo_bar_baz
- foo.cc
- bar.cc # this is a comment for arg2 this is more comment for
- # arg2, it should be joined with the first.
- baz.cc) # This comment is part of add_library
-
- other_command(some_long_argument some_long_argument) # this comment is very
- # long and gets split
- # across some lines
-
- other_command(some_long_argument some_long_argument some_long_argument)
- # this comment is even longer and wouldn't make sense to pack at the end of
- # the command so it gets it's own lines
+ add_library(
+ foo_bar_baz
+ foo.cc bar.cc # this is a comment for arg2 this is more comment for arg2,
+ # it should be joined with the first.
+ baz.cc) # This comment is part of add_library
+
+ other_command(
+ some_long_argument some_long_argument) # this comment is very long and
+ # gets split across some lines
+
+ other_command(
+ some_long_argument some_long_argument some_long_argument) # this comment
+ # is even longer
+ # and wouldn't
+ # make sense to
+ # pack at the
+ # end of the
+ # command so it
+ # gets it's own
+ # lines
endif()
endif()
# This very long command should be broken up along keyword arguments
foo(nonkwarg_a nonkwarg_b
- HEADERS a.h
- b.h
- c.h
- d.h
- e.h
- f.h
+ HEADERS a.h b.h c.h d.h e.h f.h
SOURCES a.cc b.cc d.cc
DEPENDS foo
bar baz)
diff --git a/cmake_format/tests.py b/cmake_format/tests.py
index b9c1700..511fe35 100644
--- a/cmake_format/tests.py
+++ b/cmake_format/tests.py
@@ -4,7 +4,6 @@
# pylint: disable=unused-wildcard-import
# pylint: disable=unused-import
-from cmake_format.format_tests import *
from cmake_format.invocation_tests import *
from cmake_format.layout_tests import *
from cmake_format.lexer_tests import *
@@ -19,14 +18,16 @@
import TestAddLibraryCommand
from cmake_format.command_tests.conditional_tests \
import TestConditionalCommands
-from cmake_format.command_tests.set_tests \
- import TestSetCommand
+from cmake_format.command_tests.export_tests \
+ import TestExportCommand
from cmake_format.command_tests.file_tests \
import TestFileCommands
from cmake_format.command_tests.install_tests \
import TestInstallCommands
-from cmake_format.command_tests.export_tests \
- import TestExportCommand
+from cmake_format.command_tests.misc_tests \
+ import TestMiscFormatting
+from cmake_format.command_tests.set_tests \
+ import TestSetCommand
if __name__ == '__main__':
unittest.main()
diff --git a/cmake_format/vscode_extension/CHANGELOG.md b/cmake_format/vscode_extension/CHANGELOG.md
index f214729..20e4f6c 100644
--- a/cmake_format/vscode_extension/CHANGELOG.md
+++ b/cmake_format/vscode_extension/CHANGELOG.md
@@ -1,5 +1,23 @@
# Change Log
+Note that the vscode extension release version matches the
+cmake-format release version on [pypi][2].
+
+### 0.6.0
+
+No functional changes, just documentation update.
+
+### 0.5.5
+
+- Modify vscode extension cwd to better support subtree configuration files
+- Fix vscode extension args type configuration
+
+### 0.4.2
+
+Fixed bug with using workspace path as `cwd` when calling `cmake-foramt`.
+
## 0.4.1
- Initial release
-- Working callout to cmake-format with configuration options.
\ No newline at end of file
+- Working callout to cmake-format with configuration options.
+
+[2]: https://pypi.org/project/cmake_format/
diff --git a/cmake_format/vscode_extension/README.md b/cmake_format/vscode_extension/README.md
index d0584c7..5301bf1 100644
--- a/cmake_format/vscode_extension/README.md
+++ b/cmake_format/vscode_extension/README.md
@@ -41,19 +41,5 @@ This extension contributes the following settings:
You can find known issues with `cmake-format` on [github][3].
Feel free to file issues with the vscode extension there as well.
-## Release Notes
-
-### 0.4.2
-
-Fixed bug with using workspace path as `cwd` when calling `cmake-foramt`.
-
-### 0.4.1
-
-Initial release. Note that the vscode extension release version matches the
-cmake-format release version on [pypi][2].
-
-
-
[1]: https://github.com/cheshirekow/cmake_format
-[2]: https://pypi.org/project/cmake_format/
[3]: https://github.com/cheshirekow/cmake_format/issues
diff --git a/cmake_format/vscode_extension/package-lock.json b/cmake_format/vscode_extension/package-lock.json
index 11b89f6..b46a4ad 100644
--- a/cmake_format/vscode_extension/package-lock.json
+++ b/cmake_format/vscode_extension/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "cmake-format",
- "version": "0.4.2",
+ "version": "0.6.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -481,22 +481,6 @@
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
"dev": true
},
- "event-stream": {
- "version": "3.3.6",
- "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.6.tgz",
- "integrity": "sha512-dGXNg4F/FgVzlApjzItL+7naHutA3fDqbV/zAZqDDlXTjiMnQmZKu+prImWKszeBM5UQeGvAl3u1wBiKeDh61g==",
- "dev": true,
- "requires": {
- "duplexer": "^0.1.1",
- "flatmap-stream": "^0.1.0",
- "from": "^0.1.7",
- "map-stream": "0.0.7",
- "pause-stream": "^0.0.11",
- "split": "^1.0.1",
- "stream-combiner": "^0.2.2",
- "through": "^2.3.8"
- }
- },
"expand-brackets": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz",
diff --git a/cmake_format/vscode_extension/package.json b/cmake_format/vscode_extension/package.json
index 6b538e8..555b12c 100644
--- a/cmake_format/vscode_extension/package.json
+++ b/cmake_format/vscode_extension/package.json
@@ -2,7 +2,7 @@
"name": "cmake-format",
"displayName": "cmake-format",
"description": "Format listfiles so they don't look like crap",
- "version": "0.5.5",
+ "version": "0.6.0",
"publisher": "cheshirekow",
"repository": "https://github.com/cheshirekow/cmake_format",
"icon": "images/cmake-format-logo.png",
diff --git a/doc/find_rst.py b/doc/find_rst.py
new file mode 100644
index 0000000..3b540b4
--- /dev/null
+++ b/doc/find_rst.py
@@ -0,0 +1,64 @@
+"""
+Find all restructured text files and write them out to a manifest
+"""
+
+import argparse
+import os
+
+EXCLUDE_DIRS = [
+ ".build",
+ ".git"
+]
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("-m", "--manifest-path",
+ help="Path to the manifest file to create/update")
+ parser.add_argument("-t", "--touch", action="store_true",
+ help="Touch the manifest if any rst files are newer")
+ parser.add_argument("rootdir")
+ args = parser.parse_args()
+
+ rootdir = os.path.realpath(args.rootdir)
+
+ latest_mtime = 0
+
+ fileset = set()
+ for parent, dirnames, filenames in os.walk(rootdir):
+ dirnames[:] = sorted(dirnames)
+ relpath_parent = os.path.relpath(parent, rootdir)
+ if relpath_parent in EXCLUDE_DIRS:
+ dirnames[:] = []
+ continue
+
+ for filename in filenames:
+ if filename.endswith(".rst"):
+ if relpath_parent == ".":
+ relpath_file = filename
+ else:
+ relpath_file = os.path.join(relpath_parent, filename)
+ file_mtime = os.path.getmtime(os.path.join(rootdir, relpath_file))
+ latest_mtime = max(latest_mtime, file_mtime)
+ fileset.add(relpath_file)
+
+ manifest_mtime = 0
+ manifest_path = os.path.realpath(args.manifest_path)
+ manifest_set = set()
+ if os.path.exists(manifest_path):
+ manifest_mtime = os.path.getmtime(manifest_path)
+ with open(manifest_path, "r") as infile:
+ for line in infile:
+ manifest_set.add(line.strip())
+
+ if manifest_set != fileset:
+ print("RST manifest has changed")
+ with open(manifest_path, "w") as outfile:
+ for relpath_file in sorted(fileset):
+ outfile.write(relpath_file)
+ outfile.write("\n")
+ elif args.touch and latest_mtime > manifest_mtime:
+ print("An RST file has changed")
+ os.utime(manifest_path, None)
+
+if __name__ == "__main__":
+ main()