Skip to content

Add collapse-nested-brackets option #8

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

## [Unreleased]

## [0.1.3] - 2023-05-06

- Added
- A new configurable option: `--collapse-nested-brackets`
- A helper script and corresponding tox testenv to check version and changelog

## [0.1.2] - 2023-05-04
Expand Down
76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,82 @@ var3 = "not wrapped, if the line gets too long"
| `pyproject.toml` usage | `wrap-line-with-long-string = true` under `[tool.cercis]` |
| `pre-commit` usage | `args: [--wrap-line-with-long-string=False]` |

### 3.4. Collapse nested brackets

_Cercis_ by default collapses nested brackets to make the code more compact.

<table>
<tr>
<td>

```python
# Cercis's default style

# If line length limit is 30
value = np.array([
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 0],
])



# If line length limit is 10
value = function({
1,
2,
3,
4,
5,
})


```

</td>

<td>

```python
# Black's style (not configurable)

# If line length limit is 30
value = np.array(
[
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 0],
]
)

# If line length limit is 10
value = function(
{
1,
2,
3,
4,
5,
}
)
```

</td>

</tr>
</table>

| Option | |
| ---------------------- | ------------------------------------------------------- |
| Name | `--collapse-nested-brackets` |
| Abbreviation | `-cnb` |
| Default | `True` |
| Black's style | `False` |
| Command line usage | `cercis -cnb=True myScript.py` |
| `pyproject.toml` usage | `collapse-nested-brackets = true` under `[tool.cercis]` |
| `pre-commit` usage | `args: [--collapse-nested-brackets=False]` |

The code implementation of this option comes from
[Pyink](https://github.com/google/pyink), another forked project from Black.

## 4. How to configure _Cercis_

### 4.1. Dynamically in the command line
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
line-length = 88
function-definition-extra-indent = true
single-quote = false
collapse-nested-brackets = false # use Black style to reduce chances of future merge conflicts
target-version = ['py37', 'py38']
include = '\.pyi?$'
extend-exclude = '''
Expand All @@ -33,7 +34,7 @@ build-backend = "hatchling.build"

[project]
name = "cercis"
version = "0.1.2"
version = "0.1.3"
description = "A more configurable Python code formatter"
license = { text = "MIT" }
requires-python = ">=3.7"
Expand Down
14 changes: 14 additions & 0 deletions src/cercis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from cercis.cache import Cache, get_cache_info, read_cache, write_cache
from cercis.comments import normalize_fmt_off
from cercis.const import (
DEFAULT_COLLAPSE_NESTED_BRACKETS,
DEFAULT_EXCLUDES,
DEFAULT_FUNCTION_DEFINITION_EXTRA_INDENT,
DEFAULT_INCLUDES,
Expand Down Expand Up @@ -246,6 +247,17 @@ def validate_regex(
" wrap that line with the string put in parentheses."
),
)
@click.option(
"-cnb",
"--collapse-nested-brackets",
type=bool,
show_default=True,
default=DEFAULT_COLLAPSE_NESTED_BRACKETS,
help=(
"If True, collapse nested brackets on one line, instead of exploding"
" into multiple levels in multiple lines"
),
)
@click.option(
"-t",
"--target-version",
Expand Down Expand Up @@ -466,6 +478,7 @@ def main( # noqa: C901
function_definition_extra_indent: bool,
single_quote: bool,
wrap_line_with_long_string: bool,
collapse_nested_brackets: bool,
target_version: List[TargetVersion],
check: bool,
diff: bool,
Expand Down Expand Up @@ -591,6 +604,7 @@ def main( # noqa: C901
function_definition_extra_indent=function_definition_extra_indent,
single_quote=single_quote,
wrap_line_with_long_string=wrap_line_with_long_string,
collapse_nested_brackets=collapse_nested_brackets,
)

if code is not None:
Expand Down
1 change: 1 addition & 0 deletions src/cercis/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
DEFAULT_FUNCTION_DEFINITION_EXTRA_INDENT = True
DEFAULT_SINGLE_QUOTE = False
DEFAULT_WRAP_LINE_WITH_LONG_STRING = False
DEFAULT_COLLAPSE_NESTED_BRACKETS = True
35 changes: 29 additions & 6 deletions src/cercis/linegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
hug_power_op,
)
from cercis.utils_line_wrapping import check_eligibility_to_opt_out_of_line_wrapping
from cercis.utils_linegen import perform_collapse_nested_brackets

# types
LeafID = int
Expand Down Expand Up @@ -747,19 +748,41 @@ def _first_right_hand_split(
tail_leaves.reverse()
body_leaves.reverse()
head_leaves.reverse()

collapse_nested_brackets_return_values = perform_collapse_nested_brackets(
line=line,
opening_bracket=opening_bracket,
closing_bracket=closing_bracket,
head_leaves=head_leaves,
body_leaves=body_leaves,
tail_leaves=tail_leaves,
mode=mode,
bracket_split_build_line_func=bracket_split_build_line,
bracket_split_component=_BracketSplitComponent,
)

head_leaves = collapse_nested_brackets_return_values.head_leaves
body_leaves = collapse_nested_brackets_return_values.body_leaves
tail_leaves = collapse_nested_brackets_return_values.tail_leaves
body = collapse_nested_brackets_return_values.body

head = bracket_split_build_line(
head_leaves,
line,
opening_bracket,
component=_BracketSplitComponent.head,
mode=mode,
)
body = bracket_split_build_line(
body_leaves,
line,
opening_bracket,
component=_BracketSplitComponent.body,
mode=mode,
body = (
body
if body is not None
else bracket_split_build_line(
body_leaves,
line,
opening_bracket,
component=_BracketSplitComponent.body,
mode=mode,
)
)
tail = bracket_split_build_line(
tail_leaves,
Expand Down
2 changes: 2 additions & 0 deletions src/cercis/mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from typing import Final

from cercis.const import (
DEFAULT_COLLAPSE_NESTED_BRACKETS,
DEFAULT_FUNCTION_DEFINITION_EXTRA_INDENT,
DEFAULT_LINE_LENGTH,
DEFAULT_SINGLE_QUOTE,
Expand Down Expand Up @@ -192,6 +193,7 @@ class Mode:
function_definition_extra_indent: bool = DEFAULT_FUNCTION_DEFINITION_EXTRA_INDENT
single_quote: bool = DEFAULT_SINGLE_QUOTE
wrap_line_with_long_string: bool = DEFAULT_WRAP_LINE_WITH_LONG_STRING
collapse_nested_brackets: bool = DEFAULT_COLLAPSE_NESTED_BRACKETS

def __post_init__(self) -> None:
if self.experimental_string_processing:
Expand Down
85 changes: 85 additions & 0 deletions src/cercis/utils_linegen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING, Callable, List, Optional, Type

from blib2to3.pgen2 import token
from blib2to3.pytree import Leaf
from cercis.lines import Line
from cercis.nodes import CLOSING_BRACKETS, OPENING_BRACKETS

if TYPE_CHECKING:
from cercis.linegen import _BracketSplitComponent
from cercis.mode import Mode


@dataclass
class CollapseNestedBracketsReturnValues:
head_leaves: List[Leaf]
body_leaves: List[Leaf]
tail_leaves: List[Leaf]
body: Optional[Line]


def perform_collapse_nested_brackets(
*,
line: Line,
opening_bracket: Leaf,
closing_bracket: Leaf,
head_leaves: List[Leaf],
body_leaves: List[Leaf],
tail_leaves: List[Leaf],
mode: "Mode",
bracket_split_build_line_func: Callable[..., Line],
bracket_split_component: "Type[_BracketSplitComponent]",
) -> CollapseNestedBracketsReturnValues:
"""
The code of this function comes from a wonderful implementation
from another Black fork: https://github.com/google/pyink/blob/f93771c02e9a26ce9508c59d69c9337c95797eac/src/pyink/linegen.py#L778-L815 # noqa: B950

Pyink inherits the license of Black, which is already included in this
repo (the file OLD_LICENSE).
"""
opening_brackets: List[Leaf] = [opening_bracket]
body: Optional[Line] = None
if mode.collapse_nested_brackets and not (
# Only look inside when it doesn't start with invisible parens.
opening_bracket.type == token.LPAR
and not opening_bracket.value
and closing_bracket.type == token.RPAR
and not closing_bracket.value
):
# Find an inner body...
inner_body_leaves = list(body_leaves)
inner_opening_brackets: List[Leaf] = []
inner_closing_brackets: List[Leaf] = []
while (
len(inner_body_leaves) >= 2
and inner_body_leaves[0].type in OPENING_BRACKETS
and inner_body_leaves[-1].type in CLOSING_BRACKETS
and inner_body_leaves[-1].opening_bracket is inner_body_leaves[0]
):
inner_opening_brackets.append(inner_body_leaves.pop(0))
inner_closing_brackets.insert(0, inner_body_leaves.pop())
if len(inner_body_leaves) < len(body_leaves):
inner_body = bracket_split_build_line_func(
inner_body_leaves,
line,
opening_brackets[0],
component=bracket_split_component.body,
mode=mode,
)
if inner_body.should_split_rhs or (
inner_body_leaves and inner_body_leaves[-1].type == token.COMMA
):
# Only when the inner body itself will be split or ends with a comma,
# should we prefer not break immediately nested brackets.
body_leaves = inner_body_leaves
head_leaves.extend(inner_opening_brackets)
tail_leaves = inner_closing_brackets + tail_leaves
body = inner_body # No need to re-calculate body.

return CollapseNestedBracketsReturnValues(
head_leaves=head_leaves,
body_leaves=body_leaves,
tail_leaves=tail_leaves,
body=body,
)
Loading