Skip to content

Commit 8e80895

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

File tree

3 files changed

+139
-56
lines changed

3 files changed

+139
-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: 62 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',
@@ -408,6 +384,7 @@ def select_pair_header(
408384
def sort_includes(
409385
includes: typing.List[Include], my_filename: str, config: 'Config',
410386
) -> typing.List[typing.List[str]]:
387+
411388
res: typing.List[typing.List[str]] = [[] for _ in config.rules]
412389

413390
if config.has_pair_header:
@@ -417,6 +394,7 @@ def sort_includes(
417394
line = inc.include_line
418395

419396
match = None
397+
420398
for i, matchers in enumerate(config.rules):
421399
match = None
422400
for matcher in matchers:
@@ -432,6 +410,11 @@ def sort_includes(
432410
break
433411
if match:
434412
# print(f'write {line}')
413+
quote = config.rule_quotes[i]
414+
if quote:
415+
current_quote = get_include_quote_style(line)
416+
if current_quote != quote:
417+
line = replace_include_quotes(line, quote)
435418
res[i].append(line)
436419
break
437420

@@ -440,7 +423,8 @@ def sort_includes(
440423
# print(res)
441424

442425
for group in res:
443-
group.sort(key=lambda x: (not x.endswith('.h>'), x))
426+
group.sort(key=lambda line: (get_include_quote_style(line), extract_file_relpath(line)))
427+
444428
return res
445429

446430

@@ -553,6 +537,26 @@ def include_realpath(
553537
f'broken compile_commands.json?',
554538
)
555539

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

557561
class Config:
558562
def __init__(self, contents: dict):
@@ -573,6 +577,8 @@ def __init__(self, contents: dict):
573577
out.append(MatcherPairHeader())
574578
elif vname == '@std-cpp':
575579
out.append(MatcherHardcoded(HEADERS_CXX))
580+
elif vname == '@posix':
581+
out.append(MatcherHardcoded(HEADERS_POSIX))
576582
else:
577583
raise Exception(f'Unknown "virtual: {vname}')
578584
else:
@@ -581,6 +587,7 @@ def __init__(self, contents: dict):
581587

582588
self.rules = result
583589
self._has_pair_header = has_pair_header
590+
self.rule_quotes = [rules.get("quote") for rules in rules_matrix]
584591

585592
def has_pair_header(self) -> bool:
586593
return self._has_pair_header
@@ -635,37 +642,39 @@ def do_handle_single_file(
635642

636643
assert orig_file_contents
637644

638-
i = -1 # for pylint
639645
has_pragma_once = False
640646
includes = []
647+
includes_end_line = len(orig_file_contents)
648+
641649
for i, line in enumerate(orig_file_contents):
642-
line = line.strip()
650+
stripped = line.strip()
643651

644-
if is_pragma_once(line):
652+
if is_pragma_once(stripped):
645653
has_pragma_once = True
646654
continue
647655

648-
if not is_include_or_empty(line):
656+
if not is_include_or_empty(stripped):
657+
includes_end_line = i
649658
break
650659

651-
if not line.strip():
660+
if not stripped:
652661
continue
662+
653663
abs_include = include_realpath_cached(
654664
filename,
655665
filename_for_cc, line, compile_commands, realpath_cache,
656666
)
657-
658667
orig_path = extract_file_relpath(line)
659668
includes.append(
660669
Include(
661-
include_line=line, orig_path=orig_path, real_path=abs_include,
670+
include_line=line,
671+
orig_path=orig_path,
672+
real_path=abs_include,
662673
),
663674
)
664675
if abs_include not in include_map.data:
665676
include_map.data[abs_include] = filename
666677

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

671680
tmp_filename = filename + '.tmp'
@@ -674,7 +683,7 @@ def do_handle_single_file(
674683
ofile.write('#pragma once\n\n')
675684
write_includes(sorted_includes, ofile)
676685

677-
for line in orig_file_contents[i + 1 :]:
686+
for line in orig_file_contents[includes_end_line:]:
678687
ofile.write(line)
679688
ofile.write('\n')
680689
os.rename(src=tmp_filename, dst=filename)
@@ -740,16 +749,17 @@ def main():
740749
'Path to sort. Can be a file or a directory. If it is '
741750
'a directory, recursively traverse it. Can be used multiple times.'
742751
),
752+
default=["."]
743753
)
744754
parser.add_argument(
745755
'--compile-commands',
746756
'-c',
747757
type=str,
748-
default='compile_commands.json',
758+
default='build_debug/compile_commands.json',
749759
help='Path to "compile_commands.json" file.',
750760
)
751761
parser.add_argument(
752-
'--config', '-d', type=str, help='Path to config file.',
762+
'--config', '-d', type=str, help='Path to config file.', default="/usr/local/bin/.sort-cpp-includes"
753763
)
754764
parser.add_argument(
755765
'--hpp-suffixes', '-p', type=str, default='.hpp,.h', help='TODO',
@@ -776,14 +786,15 @@ def process(args):
776786
config = read_config(args.config)
777787
include_map = IncludeMap(data={})
778788

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

781792
# process .cpp
782-
for hdr in headers:
783-
if has_suffix(hdr, suffixes):
793+
for file in files:
794+
if has_suffix(file, suffixes):
784795
handle_single_file(
785-
hdr,
786-
hdr,
796+
file,
797+
file,
787798
compile_commands,
788799
args,
789800
realpath_cache,
@@ -792,16 +803,16 @@ def process(args):
792803
)
793804

794805
# process .hpp
795-
for hdr in headers:
796-
if has_suffix(hdr, hpp_suffixes):
797-
abs_path = os.path.abspath(hdr)
806+
for file in files:
807+
if has_suffix(file, hpp_suffixes):
808+
abs_path = os.path.abspath(file)
798809
init_cpp = include_map.data.get(abs_path)
799810
if not init_cpp:
800-
print(f'Error: no .cpp file includes "{hdr}"')
811+
print(f'Error: no .cpp file includes "{file}"')
801812
continue
802813

803814
handle_single_file(
804-
hdr,
815+
file,
805816
init_cpp,
806817
compile_commands,
807818
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)