Skip to content

Commit 4c17d38

Browse files
authored
Merge branch 'master' into master
2 parents ec51bd5 + 410326f commit 4c17d38

21 files changed

+349
-167
lines changed

.github/workflows/python-package-develop.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
fail-fast: false
2424
matrix:
2525
os: [ubuntu-latest]
26-
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
26+
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
2727

2828

2929
steps:

.github/workflows/python-package.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ jobs:
2727
strategy:
2828
fail-fast: false
2929
matrix:
30-
os: [ubuntu-latest, macos-11]
31-
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
30+
os: [ubuntu-latest] # , macos-11]
31+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
3232

3333

3434
steps:

README.md

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<br />
99

1010
<p align="center">
11-
<img width="65%" align="center" src="./Img/final_back.png">
11+
<img width="65%" align="center" src="./img/final_back.png">
1212
</p>
1313

1414
# c_formatter_42
@@ -19,32 +19,32 @@ It's just for convenience.
1919

2020
## Installation
2121

22-
Requires Python3.7+ (3.8, 3.9, 3.10, 3.11)
22+
Requires Python3.8+ (3.9, 3.10, 3.11, 3.12)
2323

2424
### From PyPI
2525

26-
```console
27-
$ pip3 install c-formatter-42
28-
$ pip3 install --user c-formatter-42 # If you don't have root privileges
26+
```sh
27+
pip3 install c-formatter-42
28+
pip3 install --user c-formatter-42 # If you don't have root privileges
2929
```
3030

3131
### From source
3232

33-
```console
34-
$ git clone https://github.com/cacharle/c_formatter_42
35-
$ cd c_formatter_42
36-
$ pip3 install -e .
33+
```sh
34+
git clone https://github.com/cacharle/c_formatter_42
35+
cd c_formatter_42
36+
pip3 install -e .
3737
```
3838

3939
## Usage
4040

4141
### Command line
4242

43-
```console
44-
$ c_formatter_42 < file.c
45-
$ python3 -m c_formatter_42 < file.c # If you get 'command not found' with the previous one
43+
```sh
44+
c_formatter_42 < file.c
45+
python3 -m c_formatter_42 < file.c # If you get 'command not found' with the previous one
4646

47-
$ c_formatter_42 --help
47+
c_formatter_42 --help
4848
usage: c_formatter_42 [-h] [-c] [FILE [FILE ...]]
4949

5050
Format C source according to the norm
@@ -80,3 +80,23 @@ Check out the [`c_formatter_42.vim`](https://github.com/cacharle/c_formatter_42.
8080
## Contributing
8181
8282
Feel free to report issues or contribute. :)
83+
84+
### Run the tests
85+
86+
```sh
87+
pip3 install tox
88+
tox
89+
tox -e py311 # for a specific python version
90+
```
91+
92+
### Deploy a new version
93+
94+
```sh
95+
pip3 install bumpversion
96+
bumpversion [major|minor|patch]
97+
git push
98+
git push --tags
99+
```
100+
101+
Go to: <https://github.com/dawnbeen/c_formatter_42/tags> and click on the tag you just created.
102+
Then click on `Create release from tag`, the pipeline will build and deploy that version for you.

c_formatter_42/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = "0.2.8"

c_formatter_42/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,4 @@ def main() -> int:
102102
return 0 if success else 1
103103

104104
if __name__ == "__main__":
105-
sys.exit(main())
105+
sys.exit(main())

c_formatter_42/data/.clang-format

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ BreakBeforeTernaryOperators: true
9292

9393
# ColumnLimit (unsigned)
9494
# The column limit.
95-
ColumnLimit: 0
95+
ColumnLimit: 1024
9696

9797

9898
# FixNamespaceComments (bool)
3.14 MB
Binary file not shown.

c_formatter_42/formatters/align.py

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,63 +10,69 @@
1010
# #
1111
# ############################################################################ #
1212

13+
from __future__ import annotations
1314

1415
import re
15-
from enum import Enum
16+
import typing
1617

17-
from c_formatter_42.formatters import helper
18+
if typing.TYPE_CHECKING:
19+
from typing import Literal
1820

21+
from c_formatter_42.formatters import helper
1922

20-
class Scope(Enum):
21-
LOCAL = 0
22-
GLOBAL = 1
23+
TYPEDECL_OPEN_REGEX = re.compile(
24+
r"""^(?P<prefix>\s*(typedef\s+)? # Maybe a typedef
25+
(struct|enum|union)) # Followed by a struct, enum or union
26+
\s*(?P<suffix>[a-zA-Z_]\w+)?$ # Name of the type declaration
27+
""",
28+
re.X,
29+
)
30+
TYPEDECL_CLOSE_REGEX = re.compile(
31+
r"""^(?P<prefix>\})\s* # Closing } followed by any amount of spaces
32+
(?P<suffix>([a-zA-Z_]\w+)?;)$ # Name of the type (if typedef used)
33+
""",
34+
re.X,
35+
)
2336

2437

25-
def align_scope(content: str, scope: Scope) -> str:
38+
def align_scope(content: str, scope: Literal["local", "global"]) -> str:
2639
"""Align content
2740
scope can be either local or global
2841
local: for variable declarations in function
2942
global: for function prototypes
3043
"""
3144

3245
lines = content.split("\n")
33-
aligned = []
3446
# select regex according to scope
35-
if scope is Scope.LOCAL:
47+
if scope == "local":
3648
align_regex = "^\t" r"(?P<prefix>{type})\s+" r"(?P<suffix>\**{decl};)$"
37-
elif scope is Scope.GLOBAL:
49+
elif scope == "global":
3850
align_regex = (
3951
r"^(?P<prefix>{type})\s+"
4052
r"(?P<suffix>({name}\(.*\)?;?)|({decl}(;|(\s+=\s+.*))))$"
4153
)
4254
align_regex = align_regex.format(
4355
type=helper.REGEX_TYPE, name=helper.REGEX_NAME, decl=helper.REGEX_DECL_NAME
4456
)
45-
# get the lines to be aligned
46-
matches = [re.match(align_regex, line) for line in lines]
57+
lines_to_be_aligned = [re.match(align_regex, line) for line in lines]
4758
aligned = [
4859
(i, match.group("prefix"), match.group("suffix"))
49-
for i, match in enumerate(matches)
60+
for i, match in enumerate(lines_to_be_aligned)
5061
if match is not None
5162
and match.group("prefix") not in ["struct", "union", "enum"]
5263
]
5364

54-
# global type declaration (struct/union/enum)
55-
if scope is Scope.GLOBAL:
56-
typedecl_open_regex = (
57-
r"^(?P<prefix>\s*(typedef\s+)?(struct|enum|union))"
58-
r"\s*(?P<suffix>[a-zA-Z_]\w+)?$"
59-
)
60-
typedecl_close_regex = r"^(?P<prefix>\})\s*(?P<suffix>([a-zA-Z_]\w+)?;)$"
65+
# Global type declaration (struct/union/enum)
66+
if scope == "global":
6167
in_type_scope = False
6268
for i, line in enumerate(lines):
63-
m = re.match(typedecl_open_regex, line)
69+
m = TYPEDECL_OPEN_REGEX.match(line)
6470
if m is not None:
6571
in_type_scope = True
6672
if m.group("suffix") is not None and "typedef" not in m.group("prefix"):
6773
aligned.append((i, m.group("prefix"), m.group("suffix")))
6874
continue
69-
m = re.match(typedecl_close_regex, line)
75+
m = TYPEDECL_CLOSE_REGEX.match(line)
7076
if m is not None:
7177
in_type_scope = False
7278
if line != "};":
@@ -83,27 +89,28 @@ def align_scope(content: str, scope: Scope) -> str:
8389
if m is not None:
8490
aligned.append((i, m.group("prefix"), m.group("suffix")))
8591

86-
# get the minimum alignment required for each line
92+
# Minimum alignment required for each line
8793
min_alignment = max(
88-
(len(prefix.replace("\t", " " * 4)) // 4 + 1 for _, prefix, _ in aligned),
89-
default=1,
94+
(len(prefix.expandtabs(4)) // 4 + 1 for _, prefix, _ in aligned), default=1
9095
)
9196
for i, prefix, suffix in aligned:
92-
alignment = len(prefix.replace("\t", " " * 4)) // 4
97+
alignment = len(prefix.expandtabs(4)) // 4
9398
lines[i] = prefix + "\t" * (min_alignment - alignment) + suffix
94-
if scope is Scope.LOCAL:
95-
lines[i] = "\t" + lines[i]
99+
if scope == "local":
100+
lines[i] = (
101+
"\t" + lines[i]
102+
) # Adding one more indent for inside the type declaration
96103
return "\n".join(lines)
97104

98105

99106
@helper.locally_scoped
100107
def align_local(content: str) -> str:
101108
"""Wrapper for align_scope to use local_scope decorator"""
102-
return align_scope(content, scope=Scope.LOCAL)
109+
return align_scope(content, scope="local")
103110

104111

105112
def align(content: str) -> str:
106113
"""Align the content in global and local scopes"""
107-
content = align_scope(content, scope=Scope.GLOBAL)
114+
content = align_scope(content, scope="global")
108115
content = align_local(content)
109116
return content

c_formatter_42/formatters/clang_format.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,19 @@
1010
# #
1111
# ############################################################################ #
1212

13+
import contextlib
14+
import platform
1315
import subprocess
1416
import sys
15-
from contextlib import contextmanager
1617
from pathlib import Path
1718

1819
import c_formatter_42.data
1920

2021
CONFIG_FILENAME = Path(".clang-format")
21-
2222
DATA_DIR = Path(c_formatter_42.data.__file__).parent
2323

2424

25-
@contextmanager
25+
@contextlib.contextmanager
2626
def _config_context():
2727
"""Temporarly place .clang-format config file in the current directory
2828
If there already is a config in the current directory, it's backed up
@@ -48,7 +48,12 @@ def _config_context():
4848
if sys.platform == "linux":
4949
CLANG_FORMAT_EXEC = DATA_DIR / "clang-format-linux"
5050
elif sys.platform == "darwin":
51-
CLANG_FORMAT_EXEC = DATA_DIR / "clang-format-darwin"
51+
if platform.machine() == "arm64":
52+
# macOS M1 or Apple Silicon
53+
CLANG_FORMAT_EXEC = DATA_DIR / "clang-format-darwin-arm64"
54+
elif platform.machine() == "x86_64":
55+
# macOS Intel
56+
CLANG_FORMAT_EXEC = DATA_DIR / "clang-format-darwin"
5257
elif sys.platform == "win32":
5358
CLANG_FORMAT_EXEC = DATA_DIR / "clang-format-win32.exe"
5459
else:

c_formatter_42/formatters/helper.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,38 @@
1-
# ############################################################################ #
1+
# **************************************************************************** #
22
# #
33
# ::: :::::::: #
44
# helper.py :+: :+: :+: #
55
# +:+ +:+ +:+ #
6-
# By: cacharle <me@cacharle.xyz> +#+ +:+ +#+ #
6+
# By: leo <leo@student.42.fr> +#+ +:+ +#+ #
77
# +#+#+#+#+#+ +#+ #
88
# Created: 2020/10/04 11:38:00 by cacharle #+# #+# #
9-
# Updated: 2021/02/08 18:22:06 by charles ### ########.fr #
9+
# Updated: 2023/09/22 15:21:49 by leo ### ########.fr #
1010
# #
11-
# ############################################################################ #
11+
# **************************************************************************** #
12+
13+
from __future__ import annotations
1214

1315
import re
16+
import typing
17+
18+
if typing.TYPE_CHECKING:
19+
from typing import Callable
1420

1521
# regex for a type
16-
REGEX_TYPE = r"(?!return)([a-z]+\s+)*[a-zA-Z_]\w*"
22+
REGEX_TYPE = r"(?!return|goto)([a-z]+\s+)*[a-zA-Z_]\w*"
1723
# regex for a c variable/function name
1824
REGEX_NAME = r"\**[a-zA-Z_*()]\w*"
1925
# regex for a name in a declaration context (with array and function ptr)
20-
REGEX_DECL_NAME = r"\(?{name}(\[.*\])*(\)\(.*\))?".format(name=REGEX_NAME)
26+
REGEX_DECL_NAME = r"\(?{name}(\[.*\])*(\s\=\s.*)?(\)\(.*\))?".format(name=REGEX_NAME)
2127

2228

23-
def locally_scoped(func):
29+
def locally_scoped(func: Callable[[str], str]) -> Callable[[str], str]:
2430
"""Apply the formatter on every local scopes of the content"""
2531

2632
def wrapper(content: str) -> str:
27-
def get_replacement(match):
28-
body = match.group("body").strip("\n")
29-
result = func(body)
33+
def replacement_func(match: re.Match) -> str:
34+
result = func(match.group("body").strip("\n"))
35+
# Edge case for functions with empty bodies (See PR#31)
3036
if result.strip() == "":
3137
return ")\n{\n}\n"
3238
return ")\n{\n" + result + "\n}\n"
@@ -35,7 +41,7 @@ def get_replacement(match):
3541
# `*?` is the non greedy version of `*`
3642
# https://docs.python.org/3/howto/regex.html#greedy-versus-non-greedy
3743
r"\)\n\{(?P<body>.*?)\n\}\n".replace(r"\n", "\n"),
38-
get_replacement,
44+
replacement_func,
3945
content,
4046
flags=re.DOTALL,
4147
)

0 commit comments

Comments
 (0)