@@ -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(
408384def 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
557561class 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 ,
0 commit comments