Skip to content

Commit

Permalink
Proper formatting of INSTALL(TARGETS) standalone keywords
Browse files Browse the repository at this point in the history
  • Loading branch information
BlankSpruce committed Jan 14, 2025
1 parent a845a1e commit f100dd7
Show file tree
Hide file tree
Showing 13 changed files with 301 additions and 35 deletions.
2 changes: 1 addition & 1 deletion .gersemirc.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/BlankSpruce/gersemi/0.18.0/gersemi/configuration.schema.json
# yaml-language-server: $schema=https://raw.githubusercontent.com/BlankSpruce/gersemi/0.18.1/gersemi/configuration.schema.json

definitions: []
disable_formatting: false
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# Changelog
## [0.18.1] 2025-01-13
### Fixed
- Proper formatting of first `<artifact-option>...` group in `INSTALL(TARGETS)` command. (#51)

## [0.18.0] 2025-01-10
### Added
- Add support for extensions and provide example extension as a template.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ You can use gersemi with a pre-commit hook by adding the following to `.pre-comm
```yaml
repos:
- repo: https://github.com/BlankSpruce/gersemi
rev: 0.18.0
rev: 0.18.1
hooks:
- id: gersemi
```
Expand All @@ -145,7 +145,7 @@ If you want to use extensions with pre-commit list them with [`additional_depend
```yaml
repos:
- repo: https://github.com/BlankSpruce/gersemi
rev: 0.18.0
rev: 0.18.1
hooks:
- id: gersemi
additional_dependencies:
Expand Down
29 changes: 29 additions & 0 deletions extension-example/code_to_format/favour-expansion.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,35 @@ example_unnamed_positional_arguments(
back_2
)

# command with common keywords in standalone and in section mode
example_common_keywords_in_standalone_and_in_section(
front_1
front_2
OPTION_1
OPTION_2
ONE_VALUE_KEYWORD_2 foo
ONE_VALUE_KEYWORD_1 foo
MULTI_VALUE_KEYWORD_1
foo
bar
baz
SECTION_KEYWORD
section_front_1
OPTION_1
OPTION_2
ONE_VALUE_KEYWORD_2 foo
ONE_VALUE_KEYWORD_1 foo
MULTI_VALUE_KEYWORD_1
foo
bar
baz
section_back_1
section_back_2
back_1
back_2
back_3
)

# command with nested sections
example_nested_sections(
foo # level_0_arg_1
Expand Down
23 changes: 23 additions & 0 deletions extension-example/code_to_format/favour-inlining.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,29 @@ example_unnamed_positional_arguments(
back_2
)

# command with common keywords in standalone and in section mode
example_common_keywords_in_standalone_and_in_section(
front_1
front_2
OPTION_1
OPTION_2
ONE_VALUE_KEYWORD_2 foo
ONE_VALUE_KEYWORD_1 foo
MULTI_VALUE_KEYWORD_1 foo bar baz
SECTION_KEYWORD
section_front_1
OPTION_1
OPTION_2
ONE_VALUE_KEYWORD_2 foo
ONE_VALUE_KEYWORD_1 foo
MULTI_VALUE_KEYWORD_1 foo bar baz
section_back_1
section_back_2
back_1
back_2
back_3
)

# command with nested sections
example_nested_sections(
foo # level_0_arg_1
Expand Down
36 changes: 36 additions & 0 deletions extension-example/extension/gersemi_extension_example/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,42 @@
},
},
#
# Sometimes keywords have broad scope when used standalone but narrow scope
# when used in section. Example:
#
# install(TARGETS <target>... [EXPORT <export-name>]
# [RUNTIME_DEPENDENCIES <arg>...|RUNTIME_DEPENDENCY_SET <set-name>]
# [<artifact-option>...]
# [<artifact-kind> <artifact-option>...]...
# [INCLUDES DESTINATION [<dir> ...]]
# )
#
# """
# The first <artifact-option>... group applies to target Output
# Artifacts that do not have a dedicated group specified later
# in the same call.
# """
#
"example_common_keywords_in_standalone_and_in_section": {
"front_positional_arguments": ["front_1", "front_2"],
"back_positional_arguments": ["back_1", "back_2", "back_3"],
"options": ["OPTION_1", "OPTION_2"],
"one_value_keywords": ["ONE_VALUE_KEYWORD_1", "ONE_VALUE_KEYWORD_2"],
"multi_value_keywords": [
"MULTI_VALUE_KEYWORD_1",
"SECTION_KEYWORD",
],
"sections": {
"SECTION_KEYWORD": {
"front_positional_arguments": ["section_front_1"],
"back_positional_arguments": ["section_back_1", "section_back_2"],
"options": ["OPTION_1", "OPTION_2"],
"one_value_keywords": ["ONE_VALUE_KEYWORD_1", "ONE_VALUE_KEYWORD_2"],
"multi_value_keywords": ["MULTI_VALUE_KEYWORD_1"],
}
},
},
#
# Nested sections are supported but perhaps it's better to avoid designing
# such commands.
#
Expand Down
2 changes: 1 addition & 1 deletion gersemi/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
__license__ = "MPL 2.0"
__title__ = "gersemi"
__url__ = "https://github.com/BlankSpruce/gersemi"
__version__ = "0.18.0"
__version__ = "0.18.1"
10 changes: 10 additions & 0 deletions gersemi/ast_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,13 @@ def __call__(self, other):

def is_one_of_keywords(keywords):
return KeywordMatcher(keywords)


def make_tree(name: str):
return lambda children: Tree(name, children)


option_argument = make_tree("option_argument")
one_value_argument = make_tree("one_value_argument")
multi_value_argument = make_tree("multi_value_argument")
positional_arguments = make_tree("positional_arguments")
29 changes: 10 additions & 19 deletions gersemi/builtin_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@
_FILE_SET_Any,
"CXX_MODULES_BMI",
]
_Install_TARGETS_artifact_option_group = {
"options": ["OPTIONAL", "EXCLUDE_FROM_ALL", "NAMELINK_ONLY", "NAMELINK_SKIP"],
"one_value_keywords": ["DESTINATION", "COMPONENT", "NAMELINK_COMPONENT"],
"multi_value_keywords": ["PERMISSIONS", "CONFIGURATIONS"],
}
_Install_IMPORTED_RUNTIME_ARTIFACTS_kinds = [
"LIBRARY",
"RUNTIME",
Expand Down Expand Up @@ -1275,27 +1280,17 @@
"signatures": {
"TARGETS": {
"sections": {
kind: {
"options": [
"OPTIONAL",
"EXCLUDE_FROM_ALL",
"NAMELINK_ONLY",
"NAMELINK_SKIP",
],
"one_value_keywords": [
"DESTINATION",
"COMPONENT",
"NAMELINK_COMPONENT",
],
"multi_value_keywords": ["PERMISSIONS", "CONFIGURATIONS"],
}
kind: _Install_TARGETS_artifact_option_group
for kind in _Install_TARGETS_kinds
},
"options": _Install_TARGETS_artifact_option_group["options"],
"one_value_keywords": [
*_Install_TARGETS_artifact_option_group["one_value_keywords"],
"EXPORT",
"RUNTIME_DEPENDENCY_SET",
],
"multi_value_keywords": [
*_Install_TARGETS_artifact_option_group["multi_value_keywords"],
"TARGETS",
_INCLUDES_DESTINATION,
"RUNTIME_DEPENDENCIES",
Expand Down Expand Up @@ -1398,11 +1393,6 @@
}
for kind in _Install_RUNTIME_DEPENDENCY_SET_kinds
},
"options": [
"LIBRARY",
"RUNTIME",
"FRAMEWORK",
],
"multi_value_keywords": [
"PRE_INCLUDE_REGEXES",
"PRE_EXCLUDE_REGEXES",
Expand All @@ -1411,6 +1401,7 @@
"POST_INCLUDE_FILES",
"POST_EXCLUDE_FILES",
"DIRECTORIES",
"RUNTIME_DEPENDENCY_SET",
*_Install_RUNTIME_DEPENDENCY_SET_kinds,
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
is_option_argument,
is_positional_arguments,
is_section,
option_argument,
one_value_argument,
multi_value_argument,
positional_arguments,
)
from gersemi.base_command_invocation_dumper import BaseCommandInvocationDumper
from gersemi.keywords import KeywordMatcher
Expand All @@ -28,16 +32,6 @@ def is_non_empty_group(group: Sized) -> bool:
return is_non_empty(group)


def make_tree(name: str):
return lambda children: Tree(name, children)


option_argument = make_tree("option_argument")
one_value_argument = make_tree("one_value_argument")
multi_value_argument = make_tree("multi_value_argument")
positional_arguments = make_tree("positional_arguments")


class PositionalArguments(list):
pass

Expand Down
66 changes: 64 additions & 2 deletions gersemi/specializations/section_aware_command_invocation_dumper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
from typing import Iterable, Mapping
from lark import Tree
from gersemi.ast_helpers import (
is_one_value_argument,
is_multi_value_argument,
is_one_of_keywords,
is_positional_arguments,
is_section,
positional_arguments,
)
from gersemi.keywords import KeywordMatcher
from .argument_aware_command_invocation_dumper import (
Expand Down Expand Up @@ -68,16 +71,75 @@ def _split_multi_value_argument(self, tree):

return Tree("section", subarguments)

def _is_among_section_keywords(self, section_matcher, argument):
if section_matcher is None:
return False

with self._update_section_characteristics(section_matcher):
for keywords in (
self.options,
self.one_value_keywords,
self.multi_value_keywords,
):
if is_one_of_keywords(keywords)(argument.children[0]):
return True

return False

def _fix_back_positional_arguments(self, section):
if not is_section(section):
return section

pivot = len(self.back_positional_arguments)
if pivot == 0:
return section

*front, last = section.children
if is_one_value_argument(last) or is_multi_value_argument(last):
last_front, *last_rest = last.children
left_in_place, back_positional_arguments = (
last_rest[:-pivot],
last_rest[-pivot:],
)
last.children = [last_front, *left_in_place]
section.children = [
*front,
last,
positional_arguments(back_positional_arguments),
]

return section

def _form_sections(self, arguments):
result = []
section_matcher = None
for argument in arguments:
if self._is_among_section_keywords(section_matcher, argument):
result[-1].children.append(argument)
else:
if section_matcher is not None:
with self._update_section_characteristics(section_matcher):
result[-1] = self._fix_back_positional_arguments(result[-1])

if len(argument.children) > 0:
section_matcher = self._get_matcher(argument.children[0])
else:
section_matcher = None

result.append(argument)

return self._fix_back_positional_arguments(result)

def _split_arguments(self, arguments):
preprocessed = super()._split_arguments(arguments)
return [
return self._form_sections(
(
self._split_multi_value_argument(child)
if is_multi_value_argument(child)
else child
)
for child in preprocessed
]
)

def section(self, tree):
result = self._try_to_format_into_single_line(tree.children, separator=" ")
Expand Down
25 changes: 25 additions & 0 deletions tests/formatter/issue_0051_install_targets.in.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
### {list_expansion: favour-expansion}
# https://cmake.org/cmake/help/latest/command/install.html#signatures
#
# The first <artifact-option>... group applies to target Output Artifacts
# that do not have a dedicated group specified later in the same call.

install(TARGETS ${LIB_NAME} COMPONENT TARGET_COMPONENT DESTINATION lib)

install(TARGETS ${LIB_NAME} ${LIB2_NAME} COMPONENT TARGET_COMPONENT DESTINATION lib)

install(TARGETS ${LIB_NAME} COMPONENT TARGET_COMPONENT DESTINATION lib PERMISSIONS foo bar baz)

install(TARGETS ${LIB_NAME} COMPONENT TARGET_COMPONENT DESTINATION lib RUNTIME COMPONENT TARGET_COMPONENT DESTINATION lib)

install(TARGETS ${LIB_NAME} COMPONENT TARGET_COMPONENT DESTINATION lib PERMISSIONS foo bar baz RUNTIME COMPONENT TARGET_COMPONENT DESTINATION lib PERMISSIONS foo bar baz)

install(TARGETS ${LIB_NAME} ${LIB2_NAME} COMPONENT TARGET_COMPONENT DESTINATION lib RUNTIME COMPONENT TARGET_COMPONENT DESTINATION lib)

install(TARGETS ${LIB_NAME} ${LIB2_NAME} COMPONENT TARGET_COMPONENT DESTINATION lib RUNTIME COMPONENT TARGET_COMPONENT DESTINATION lib LIBRARY COMPONENT TARGET_COMPONENT DESTINATION lib)

install(RUNTIME_DEPENDENCY_SET deps
RUNTIME DESTINATION bin COMPONENT bin2
LIBRARY DESTINATION lib COMPONENT lib2
FRAMEWORK DESTINATION fw COMPONENT fw2
)
Loading

0 comments on commit f100dd7

Please sign in to comment.