21Check and apply the ns-3 coding style to all files in the PATH argument.
23The coding style is defined with the clang-format tool, whose definitions are in
24the ".clang-format" file. This script performs the following checks / fixes:
25- Check / apply clang-format.
26- Check / trim trailing whitespace.
27- Check / replace tabs with spaces.
29The clang-format and tabs checks respect clang-format guards, which mark code blocks
30that should not be checked. Trailing whitespace is always checked regardless of
33This script can be applied to all text files in a given path or to individual files.
35NOTE: The formatting check requires clang-format (version >= 14) to be found on the path.
36Trimming of trailing whitespace and conversion of tabs to spaces (via the "--no-formatting"
37option) do not depend on clang-format.
41import concurrent.futures
48from typing
import Dict, List, Tuple
53CLANG_FORMAT_VERSIONS = [
59CLANG_FORMAT_GUARD_ON =
'// clang-format on'
60CLANG_FORMAT_GUARD_OFF =
'// clang-format off'
62DIRECTORIES_TO_SKIP = [
77FILE_EXTENSIONS_TO_CHECK_FORMATTING = [
83FILE_EXTENSIONS_TO_CHECK_WHITESPACE = [
113FILES_TO_CHECK_WHITESPACE = [
118FILE_EXTENSIONS_TO_CHECK_TABS = [
136 Check if a directory should be skipped.
138 @param dirpath Directory path.
139 @return Whether the directory should be skipped
or not.
142 _, directory = os.path.split(dirpath)
144 return (directory
in DIRECTORIES_TO_SKIP
or
145 (directory.startswith(
'.')
and directory !=
'.'))
150 Check if a file should be skipped
from formatting analysis.
152 @param path Path to the file.
153 @return Whether the file should be skipped
or not.
156 filename = os.path.split(path)[1]
158 if filename
in FILES_TO_SKIP:
161 _, extension = os.path.splitext(filename)
163 return extension
not in FILE_EXTENSIONS_TO_CHECK_FORMATTING
168 Check if a file should be skipped
from trailing whitespace analysis.
170 @param path Path to the file.
171 @return Whether the file should be skipped
or not.
174 filename = os.path.split(path)[1]
176 if filename
in FILES_TO_SKIP:
179 basename, extension = os.path.splitext(filename)
181 return (basename
not in FILES_TO_CHECK_WHITESPACE
and
182 extension
not in FILE_EXTENSIONS_TO_CHECK_WHITESPACE)
187 Check if a file should be skipped
from tabs analysis.
189 @param path Path to the file.
190 @return Whether the file should be skipped
or not.
193 filename = os.path.split(path)[1]
195 if filename
in FILES_TO_SKIP:
198 _, extension = os.path.splitext(filename)
200 return extension
not in FILE_EXTENSIONS_TO_CHECK_TABS
205 Find all files to be checked in a given path.
207 @param path Path to check.
208 @return Tuple [List of files to check formatting,
209 List of files to check trailing whitespace,
210 List of files to check tabs].
213 files_to_check_formatting: List[str] = []
214 files_to_check_whitespace: List[str] = []
215 files_to_check_tabs: List[str] = []
217 abs_path = os.path.normpath(os.path.abspath(os.path.expanduser(path)))
219 if os.path.isfile(abs_path):
221 files_to_check_formatting.append(path)
224 files_to_check_whitespace.append(path)
227 files_to_check_tabs.append(path)
229 elif os.path.isdir(abs_path):
230 for dirpath, dirnames, filenames
in os.walk(path, topdown=
True):
236 filenames = [os.path.join(dirpath, f)
for f
in filenames]
240 files_to_check_formatting.append(f)
243 files_to_check_whitespace.append(f)
246 files_to_check_tabs.append(f)
249 raise ValueError(f
'Error: {path} is not a file nor a directory')
252 files_to_check_formatting,
253 files_to_check_whitespace,
260 Find the path to one of the supported versions of clang-format.
261 If no supported version of clang-format is found,
raise an exception.
263 @return Path to clang-format.
267 for version
in CLANG_FORMAT_VERSIONS:
268 clang_format_path = shutil.which(f
'clang-format-{version}')
270 if clang_format_path:
271 return clang_format_path
274 clang_format_path = shutil.which(
'clang-format')
276 if clang_format_path:
277 process = subprocess.run(
278 [clang_format_path,
'--version'],
284 version = process.stdout.strip().split(
' ')[-1]
285 major_version =
int(version.split(
'.')[0])
287 if major_version
in CLANG_FORMAT_VERSIONS:
288 return clang_format_path
292 f
'Could not find any supported version of clang-format installed on this system. '
293 f
'List of supported versions: {CLANG_FORMAT_VERSIONS}.'
301 enable_check_formatting: bool,
302 enable_check_whitespace: bool,
303 enable_check_tabs: bool,
309 Check / fix the coding style of a list of files, including formatting and
312 @param path Path to the files.
313 @param fix Whether to fix the style of the file (
True)
or
314 just check
if the file
is well-formatted (
False).
315 @param enable_check_formatting Whether to enable code formatting checking.
316 @param enable_check_whitespace Whether to enable trailing whitespace checking.
317 @param enable_check_tabs Whether to enable tabs checking.
318 @param verbose Show the lines that are
not well-formatted.
319 @param n_jobs Number of parallel jobs.
322 (files_to_check_formatting,
323 files_to_check_whitespace,
326 check_formatting_successful = True
327 check_whitespace_successful =
True
328 check_tabs_successful =
True
330 if enable_check_formatting:
332 files_to_check_formatting,
340 if enable_check_whitespace:
342 files_to_check_whitespace,
350 if enable_check_tabs:
359 check_formatting_successful,
360 check_whitespace_successful,
361 check_tabs_successful,
377 Check / fix the coding style of a list of files with clang-format.
379 @param filenames List of filenames to be checked.
380 @param fix Whether to fix the formatting of the file (
True)
or
381 just check
if the file
is well-formatted (
False).
382 @param verbose Show the lines that are
not well-formatted.
383 @param n_jobs Number of parallel jobs.
384 @return True if all files are well formatted after the check process.
385 False if there are non-formatted files after the check process.
390 files_not_formatted: List[str] = []
391 files_verbose_infos: Dict[str, List[str]] = {}
393 with concurrent.futures.ProcessPoolExecutor(n_jobs)
as executor:
394 files_not_formatted_results = executor.map(
395 check_formatting_file,
397 itertools.repeat(clang_format_path),
398 itertools.repeat(fix),
399 itertools.repeat(verbose),
402 for (filename, formatted, verbose_infos)
in files_not_formatted_results:
404 files_not_formatted.append(filename)
407 files_verbose_infos[filename] = verbose_infos
410 if not files_not_formatted:
411 print(
'- All files are well formatted')
415 n_non_formatted_files = len(files_not_formatted)
418 print(f
'- Fixed formatting of the files ({n_non_formatted_files}):')
420 print(f
'- Detected bad formatting in the files ({n_non_formatted_files}):')
422 for f
in files_not_formatted:
424 print(*[f
' {l}' for l
in files_verbose_infos[f]], sep=
'\n')
433 clang_format_path: str,
436 ) -> Tuple[str, bool, List[str]]:
438 Check / fix the coding style of a file with clang-format.
440 @param filename Name of the file to be checked.
441 @param clang_format_path Path to clang-format.
442 @param fix Whether to fix the style of the file (
True)
or
443 just check
if the file
is well-formatted (
False).
444 @param verbose Show the lines that are
not well-formatted.
445 @return Tuple [Filename, Whether the file
is well-formatted, Verbose information].
448 verbose_infos: List[str] = []
451 process = subprocess.run(
459 f
'--ferror-limit={0 if verbose else 1}',
466 file_formatted = (process.returncode == 0)
469 verbose_infos = process.stderr.splitlines()
472 if fix
and not file_formatted:
473 process = subprocess.run(
481 stdout=subprocess.DEVNULL,
482 stderr=subprocess.DEVNULL,
485 return (filename, file_formatted, verbose_infos)
497 Check / fix trailing whitespace in a list of files.
499 @param filename Name of the file to be checked.
500 @param fix Whether to fix the file (
True)
or
501 just check
if it has trailing whitespace (
False).
502 @param verbose Show the lines that are
not well-formatted.
503 @param n_jobs Number of parallel jobs.
504 @return True if no files have trailing whitespace after the check process.
505 False if there are trailing whitespace after the check process.
509 files_with_whitespace: List[str] = []
510 files_verbose_infos: Dict[str, List[str]] = {}
512 with concurrent.futures.ProcessPoolExecutor(n_jobs)
as executor:
513 files_with_whitespace_results = executor.map(
514 check_trailing_whitespace_file,
516 itertools.repeat(fix),
517 itertools.repeat(verbose),
520 for (filename, has_whitespace, verbose_infos)
in files_with_whitespace_results:
522 files_with_whitespace.append(filename)
525 files_verbose_infos[filename] = verbose_infos
528 if not files_with_whitespace:
529 print(
'- No files detected with trailing whitespace')
533 n_files_with_whitespace = len(files_with_whitespace)
537 f
'- Fixed trailing whitespace in the files ({n_files_with_whitespace}):')
540 f
'- Detected trailing whitespace in the files ({n_files_with_whitespace}):')
542 for f
in files_with_whitespace:
544 print(*[f
' {l}' for l
in files_verbose_infos[f]], sep=
'\n')
555 ) -> Tuple[str, bool, List[str]]:
557 Check / fix trailing whitespace in a file.
559 @param filename Name of the file to be checked.
560 @param fix Whether to fix the file (
True)
or
561 just check
if it has trailing whitespace (
False).
562 @param verbose Show the lines that are
not well-formatted.
563 @return Tuple [Filename, Whether the file has trailing whitespace, Verbose information].
566 has_trailing_whitespace = False
567 verbose_infos: List[str] = []
569 with open(filename,
'r', encoding=
'utf-8')
as f:
570 file_lines = f.readlines()
573 for (i, line)
in enumerate(file_lines):
574 line_fixed = line.rstrip() +
'\n'
576 if line_fixed != line:
577 has_trailing_whitespace =
True
578 file_lines[i] = line_fixed
581 verbose_infos.extend([
582 f
'{filename}:{i + 1}: error: Trailing whitespace detected',
583 f
' {line_fixed.rstrip()}',
584 f
' {"":{len(line_fixed) - 1}}^',
588 if not fix
and not verbose:
592 if fix
and has_trailing_whitespace:
593 with open(filename,
'w', encoding=
'utf-8')
as f:
594 f.writelines(file_lines)
596 return (filename, has_trailing_whitespace, verbose_infos)
608 Check / fix tabs in a list of files.
610 @param filename Name of the file to be checked.
611 @param fix Whether to fix the file (
True)
or just check
if it has tabs (
False).
612 @param verbose Show the lines that are
not well-formatted.
613 @param n_jobs Number of parallel jobs.
614 @return True if no files have tabs after the check process.
615 False if there are tabs after the check process.
619 files_with_tabs: List[str] = []
620 files_verbose_infos: Dict[str, List[str]] = {}
622 with concurrent.futures.ProcessPoolExecutor(n_jobs)
as executor:
623 files_with_tabs_results = executor.map(
626 itertools.repeat(fix),
627 itertools.repeat(verbose),
630 for (filename, has_tabs, verbose_infos)
in files_with_tabs_results:
632 files_with_tabs.append(filename)
635 files_verbose_infos[filename] = verbose_infos
638 if not files_with_tabs:
639 print(
'- No files detected with tabs')
643 n_files_with_tabs = len(files_with_tabs)
647 f
'- Fixed tabs in the files ({n_files_with_tabs}):')
650 f
'- Detected tabs in the files ({n_files_with_tabs}):')
652 for f
in files_with_tabs:
654 print(*[f
' {l}' for l
in files_verbose_infos[f]], sep=
'\n')
665 ) -> Tuple[str, bool, List[str]]:
667 Check / fix tabs in a file.
669 @param filename Name of the file to be checked.
670 @param fix Whether to fix the file (
True)
or just check
if it has tabs (
False).
671 @param verbose Show the lines that are
not well-formatted.
672 @return Tuple [Filename, Whether the file has tabs, Verbose information].
676 clang_format_enabled =
True
678 verbose_infos: List[str] = []
680 with open(filename,
'r', encoding=
'utf-8')
as f:
681 file_lines = f.readlines()
683 for (i, line)
in enumerate(file_lines):
686 line_stripped = line.strip()
688 if line_stripped == CLANG_FORMAT_GUARD_ON:
689 clang_format_enabled =
True
690 elif line_stripped == CLANG_FORMAT_GUARD_OFF:
691 clang_format_enabled =
False
693 if (
not clang_format_enabled
and
694 line_stripped
not in (CLANG_FORMAT_GUARD_ON, CLANG_FORMAT_GUARD_OFF)):
698 tab_index = line.find(
'\t')
702 file_lines[i] = line.expandtabs(TAB_SIZE)
705 verbose_infos.extend([
706 f
'{filename}:{i + 1}:{tab_index + 1}: error: Tab detected',
708 f
' {"":{tab_index}}^',
712 if not fix
and not verbose:
717 with open(filename,
'w', encoding=
'utf-8')
as f:
718 f.writelines(file_lines)
720 return (filename, has_tabs, verbose_infos)
726if __name__ ==
'__main__':
728 parser = argparse.ArgumentParser(
729 description=
'Check and apply the ns-3 coding style to all files in a given PATH. '
730 'The script checks the formatting of the file with clang-format. '
731 'Additionally, it checks the presence of trailing whitespace and tabs. '
732 'Formatting and tabs checks respect clang-format guards. '
733 'When used in "check mode" (default), the script checks if all files are well '
734 'formatted and do not have trailing whitespace nor tabs. '
735 'If it detects non-formatted files, they will be printed and this process exits with a '
736 'non-zero code. When used in "fix mode", this script automatically fixes the files.')
738 parser.add_argument(
'path', action=
'store', type=str,
739 help=
'Path to the files to check')
741 parser.add_argument(
'--no-formatting', action=
'store_true',
742 help=
'Do not check / fix code formatting')
744 parser.add_argument(
'--no-whitespace', action=
'store_true',
745 help=
'Do not check / fix trailing whitespace')
747 parser.add_argument(
'--no-tabs', action=
'store_true',
748 help=
'Do not check / fix tabs')
750 parser.add_argument(
'--fix', action=
'store_true',
751 help=
'Fix coding style issues detected in the files')
753 parser.add_argument(
'-v',
'--verbose', action=
'store_true',
754 help=
'Show the lines that are not well-formatted')
756 parser.add_argument(
'-j',
'--jobs', type=int, default=
max(1, os.cpu_count() - 1),
757 help=
'Number of parallel jobs')
759 args = parser.parse_args()
764 enable_check_formatting=(
not args.no_formatting),
765 enable_check_whitespace=(
not args.no_whitespace),
766 enable_check_tabs=(
not args.no_tabs),
768 verbose=args.verbose,
772 except Exception
as e: