Skip to content

Commit 1bebeb2

Browse files
author
Андрей Будиловский
committed
Add @posix group and quotes settings for matchers
1 parent 18e133e commit 1bebeb2

File tree

3 files changed

+140
-56
lines changed

3 files changed

+140
-56
lines changed

.gitignore

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
tags
1+
2+
*.egg-info
23
*.pyc
4+
35
.mypy_cache/
4-
dist/
5-
*.egg-info
6+
7+
build
8+
dist
9+
export*
10+
tags
11+
12+

src/sort_cpp_includes/sort_cpp_includes.py

Lines changed: 63 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -137,59 +137,40 @@ def extract_file_relpath(line: str) -> str:
137137
'stdatomic.h',
138138
'stdnoreturn.h',
139139
'threads.h',
140-
'uchar.h',
141-
#
142-
# POSIX
143-
#
140+
'uchar.h'
141+
]
142+
143+
HEADERS_POSIX = [
144144
'aio.h',
145145
'libgen.h',
146146
'spawn.h',
147147
'sys/time.h',
148148
'arpa/inet.h',
149-
'limits.h',
150-
'stdarg.h',
151149
'sys/times.h',
152-
'assert.h',
153-
'locale.h',
154-
'stdbool.h',
155150
'sys/types.h',
156-
'complex.h',
157-
'math.h',
158-
'stddef.h',
159151
'sys/uio.h',
160152
'cpio.h',
161153
'monetary.h',
162-
'stdint.h',
163154
'sys/un.h',
164-
'ctype.h',
165155
'mqueue.h',
166-
'stdio.h',
167156
'sys/utsname.h',
168157
'dirent.h',
169158
'ndbm.h',
170-
'stdlib.h',
171159
'sys/wait.h',
172160
'dlfcn.h',
173161
'net/if.h',
174-
'string.h',
175162
'syslog.h',
176-
'errno.h',
177163
'netdb.h',
178164
'strings.h',
179165
'tar.h',
180166
'fcntl.h',
181167
'netinet/in.h',
182168
'stropts.h',
183169
'termios.h',
184-
'fenv.h',
185170
'netinet/tcp.h',
186171
'sys/ipc.h',
187-
'tgmath.h',
188-
'float.h',
189172
'nl_types.h',
190173
'sys/mman.h',
191-
'time.h',
192-
'fmtmsg.h',
193174
'poll.h',
194175
'sys/msg.h',
195176
'trace.h',
@@ -212,13 +193,8 @@ def extract_file_relpath(line: str) -> str:
212193
'iconv.h',
213194
'search.h',
214195
'sys/socket.h',
215-
'wchar.h',
216-
'inttypes.h',
217196
'semaphore.h',
218197
'sys/stat.h',
219-
'wctype.h',
220-
'iso646.h',
221-
'setjmp.h',
222198
'sys/statvfs.h',
223199
'wordexp.h',
224200
'langinfo.h',
@@ -287,6 +263,7 @@ def extract_file_relpath(line: str) -> str:
287263
'functional',
288264
'new',
289265
'string_view',
266+
'format'
290267
#
291268
# C compatible headers in C++
292269
#
@@ -408,6 +385,7 @@ def select_pair_header(
408385
def sort_includes(
409386
includes: typing.List[Include], my_filename: str, config: 'Config',
410387
) -> typing.List[typing.List[str]]:
388+
411389
res: typing.List[typing.List[str]] = [[] for _ in config.rules]
412390

413391
if config.has_pair_header:
@@ -417,6 +395,7 @@ def sort_includes(
417395
line = inc.include_line
418396

419397
match = None
398+
420399
for i, matchers in enumerate(config.rules):
421400
match = None
422401
for matcher in matchers:
@@ -432,6 +411,11 @@ def sort_includes(
432411
break
433412
if match:
434413
# print(f'write {line}')
414+
quote = config.rule_quotes[i]
415+
if quote:
416+
current_quote = get_include_quote_style(line)
417+
if current_quote != quote:
418+
line = replace_include_quotes(line, quote)
435419
res[i].append(line)
436420
break
437421

@@ -440,7 +424,8 @@ def sort_includes(
440424
# print(res)
441425

442426
for group in res:
443-
group.sort(key=lambda x: (not x.endswith('.h>'), x))
427+
group.sort(key=lambda line: (get_include_quote_style(line), extract_file_relpath(line)))
428+
444429
return res
445430

446431

@@ -553,6 +538,26 @@ def include_realpath(
553538
f'broken compile_commands.json?',
554539
)
555540

541+
def get_include_quote_style(line: str) -> str:
542+
if '<' in line and '>' in line:
543+
return 'angle'
544+
elif '"' in line:
545+
return 'quote'
546+
else:
547+
raise ValueError(f'Unknown quote style in line: {line}')
548+
549+
def replace_include_quotes(line: str, quote: str) -> str:
550+
match = re.match(r'^\s*#include\s*([<"])([^>"]+)[>"]', line)
551+
if not match:
552+
return line # невалидный #include — возвращаем как есть
553+
554+
filename = match.group(2)
555+
if quote == 'angle':
556+
return f'#include <{filename}>'
557+
elif quote == 'quote':
558+
return f'#include "{filename}"'
559+
else:
560+
raise ValueError(f'Invalid quote specifier: {quote}')
556561

557562
class Config:
558563
def __init__(self, contents: dict):
@@ -573,6 +578,8 @@ def __init__(self, contents: dict):
573578
out.append(MatcherPairHeader())
574579
elif vname == '@std-cpp':
575580
out.append(MatcherHardcoded(HEADERS_CXX))
581+
elif vname == '@posix':
582+
out.append(MatcherHardcoded(HEADERS_POSIX))
576583
else:
577584
raise Exception(f'Unknown "virtual: {vname}')
578585
else:
@@ -581,6 +588,7 @@ def __init__(self, contents: dict):
581588

582589
self.rules = result
583590
self._has_pair_header = has_pair_header
591+
self.rule_quotes = [rules.get("quote") for rules in rules_matrix]
584592

585593
def has_pair_header(self) -> bool:
586594
return self._has_pair_header
@@ -635,37 +643,39 @@ def do_handle_single_file(
635643

636644
assert orig_file_contents
637645

638-
i = -1 # for pylint
639646
has_pragma_once = False
640647
includes = []
648+
includes_end_line = len(orig_file_contents)
649+
641650
for i, line in enumerate(orig_file_contents):
642-
line = line.strip()
651+
stripped = line.strip()
643652

644-
if is_pragma_once(line):
653+
if is_pragma_once(stripped):
645654
has_pragma_once = True
646655
continue
647656

648-
if not is_include_or_empty(line):
657+
if not is_include_or_empty(stripped):
658+
includes_end_line = i
649659
break
650660

651-
if not line.strip():
661+
if not stripped:
652662
continue
663+
653664
abs_include = include_realpath_cached(
654665
filename,
655666
filename_for_cc, line, compile_commands, realpath_cache,
656667
)
657-
658668
orig_path = extract_file_relpath(line)
659669
includes.append(
660670
Include(
661-
include_line=line, orig_path=orig_path, real_path=abs_include,
671+
include_line=line,
672+
orig_path=orig_path,
673+
real_path=abs_include,
662674
),
663675
)
664676
if abs_include not in include_map.data:
665677
include_map.data[abs_include] = filename
666678

667-
assert i != -1
668-
669679
sorted_includes = sort_includes(includes, filename, config)
670680

671681
tmp_filename = filename + '.tmp'
@@ -674,7 +684,7 @@ def do_handle_single_file(
674684
ofile.write('#pragma once\n\n')
675685
write_includes(sorted_includes, ofile)
676686

677-
for line in orig_file_contents[i + 1 :]:
687+
for line in orig_file_contents[includes_end_line:]:
678688
ofile.write(line)
679689
ofile.write('\n')
680690
os.rename(src=tmp_filename, dst=filename)
@@ -740,16 +750,17 @@ def main():
740750
'Path to sort. Can be a file or a directory. If it is '
741751
'a directory, recursively traverse it. Can be used multiple times.'
742752
),
753+
default=["."]
743754
)
744755
parser.add_argument(
745756
'--compile-commands',
746757
'-c',
747758
type=str,
748-
default='compile_commands.json',
759+
default='build_debug/compile_commands.json',
749760
help='Path to "compile_commands.json" file.',
750761
)
751762
parser.add_argument(
752-
'--config', '-d', type=str, help='Path to config file.',
763+
'--config', '-d', type=str, help='Path to config file.', default="/usr/local/bin/.sort-cpp-includes"
753764
)
754765
parser.add_argument(
755766
'--hpp-suffixes', '-p', type=str, default='.hpp,.h', help='TODO',
@@ -776,14 +787,15 @@ def process(args):
776787
config = read_config(args.config)
777788
include_map = IncludeMap(data={})
778789

779-
headers = collect_all_files(args.paths, suffixes + hpp_suffixes)
790+
files = collect_all_files(args.paths, suffixes + hpp_suffixes)
791+
print(f'files: {files}\n')
780792

781793
# process .cpp
782-
for hdr in headers:
783-
if has_suffix(hdr, suffixes):
794+
for file in files:
795+
if has_suffix(file, suffixes):
784796
handle_single_file(
785-
hdr,
786-
hdr,
797+
file,
798+
file,
787799
compile_commands,
788800
args,
789801
realpath_cache,
@@ -792,16 +804,16 @@ def process(args):
792804
)
793805

794806
# process .hpp
795-
for hdr in headers:
796-
if has_suffix(hdr, hpp_suffixes):
797-
abs_path = os.path.abspath(hdr)
807+
for file in files:
808+
if has_suffix(file, hpp_suffixes):
809+
abs_path = os.path.abspath(file)
798810
init_cpp = include_map.data.get(abs_path)
799811
if not init_cpp:
800-
print(f'Error: no .cpp file includes "{hdr}"')
812+
print(f'Error: no .cpp file includes "{file}"')
801813
continue
802814

803815
handle_single_file(
804-
hdr,
816+
file,
805817
init_cpp,
806818
compile_commands,
807819
args,

src/sort_cpp_includes/test_sort.py

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class FakeArgs:
1616

1717

1818
# TODO: ad-hoc
19-
COMPILER = '/usr/bin/clang++-9'
19+
COMPILER = '/usr/bin/clang++'
2020

2121

2222
def compose_compile_commands(args):
@@ -58,7 +58,9 @@ def _check(input_cpp: str, expected_output: str, rules: dict):
5858

5959
with open(input_fname, 'r') as ifile:
6060
contents = ifile.read()
61-
61+
print("======\n")
62+
print(f"{contents}")
63+
print("======\n")
6264
# print(contents)
6365
# print(expected_output)
6466
assert contents.strip() == expected_output.strip()
@@ -123,3 +125,66 @@ def test_multiple_groups(tmp_path, check):
123125
],
124126
}
125127
check(input_cpp, expected_output, rules)
128+
129+
def test_quote_conversion(tmp_path, check):
130+
input_cpp = '''
131+
#include "unistd.h"
132+
#include "stdio.h"
133+
#include <vector>
134+
#include <iostream>
135+
'''
136+
137+
expected_output = '''
138+
#include <unistd.h>
139+
140+
#include <stdio.h>
141+
142+
#include "iostream"
143+
#include "vector"
144+
'''
145+
146+
rules = {
147+
'rules': [
148+
{
149+
'matchers': [{'virtual': '@posix'}],
150+
'quote': 'angle'
151+
},
152+
{
153+
'matchers': [{'virtual': '@std-c'}],
154+
'quote': 'angle'
155+
},
156+
{
157+
'matchers': [{'virtual': '@std-cpp'}],
158+
'quote': 'quote'
159+
},
160+
]
161+
}
162+
163+
check(input_cpp, expected_output, rules)
164+
165+
def test_include_eats_first_code_line(tmp_path, check):
166+
input_cpp = """
167+
#include <iostream>
168+
#include <vector>
169+
170+
int main() {
171+
return 42;
172+
}
173+
"""
174+
175+
expected_output = """
176+
#include <iostream>
177+
#include <vector>
178+
179+
int main() {
180+
return 42;
181+
}
182+
"""
183+
184+
rules = {
185+
'rules': [
186+
{'matchers': [{'regex': '.*'}]},
187+
]
188+
}
189+
190+
check(input_cpp, expected_output, rules)

0 commit comments

Comments
 (0)