utils: Add new "check-style-clang-format.py"
- Remove deprecated scripts "trim-trailing-whitespace.py" and "check-style.py". - Remove deprecated "utils.h".
This commit is contained in:
626
utils/check-style-clang-format.py
Executable file
626
utils/check-style-clang-format.py
Executable file
@@ -0,0 +1,626 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (c) 2022 Eduardo Nuno Almeida.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License version 2 as
|
||||
# published by the Free Software Foundation;
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Author: Eduardo Nuno Almeida <enmsa@outlook.pt> [INESC TEC and FEUP, Portugal]
|
||||
|
||||
"""
|
||||
Check and apply the ns-3 coding style to all files in the PATH argument.
|
||||
|
||||
The coding style is defined with the clang-format tool, whose definitions are in
|
||||
the ".clang-format" file. This script performs the following checks / fixes:
|
||||
- Check / apply clang-format.
|
||||
- Check / trim trailing whitespace.
|
||||
- Check / replace tabs with spaces.
|
||||
|
||||
This script can be applied to all text files in a given path or to individual files.
|
||||
|
||||
NOTE: The formatting check requires clang-format (version >= 14) to be found on the path.
|
||||
Trimming of trailing whitespace and conversion of tabs to spaces (via the "--no-formatting"
|
||||
option) do not depend on clang-format.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import concurrent.futures
|
||||
import itertools
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import List, Tuple
|
||||
|
||||
|
||||
###########################################################
|
||||
# PARAMETERS
|
||||
###########################################################
|
||||
CLANG_FORMAT_VERSIONS = [
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
]
|
||||
|
||||
DIRECTORIES_TO_SKIP = [
|
||||
'__pycache__',
|
||||
'.vscode',
|
||||
'bindings',
|
||||
'build',
|
||||
'cmake-cache',
|
||||
'testpy-output',
|
||||
]
|
||||
|
||||
FILE_EXTENSIONS_TO_CHECK_FORMATTING = [
|
||||
'.c',
|
||||
'.cc',
|
||||
'.h',
|
||||
]
|
||||
|
||||
FILE_EXTENSIONS_TO_CHECK_WHITESPACE = [
|
||||
'.c',
|
||||
'.cc',
|
||||
'.click',
|
||||
'.conf',
|
||||
'.css',
|
||||
'.dot',
|
||||
'.gnuplot',
|
||||
'.gp',
|
||||
'.h',
|
||||
'.html',
|
||||
'.js',
|
||||
'.json',
|
||||
'.m',
|
||||
'.md',
|
||||
'.mob',
|
||||
'.ns_params',
|
||||
'.ns_movements',
|
||||
'.params',
|
||||
'.pl',
|
||||
'.plt',
|
||||
'.py',
|
||||
'.rst',
|
||||
'.seqdiag',
|
||||
'.sh',
|
||||
'.txt',
|
||||
'.yml',
|
||||
]
|
||||
|
||||
FILES_TO_CHECK_WHITESPACE = [
|
||||
'Makefile',
|
||||
'ns3',
|
||||
]
|
||||
|
||||
FILE_EXTENSIONS_TO_CHECK_TABS = [
|
||||
'.c',
|
||||
'.cc',
|
||||
'.h',
|
||||
'.md',
|
||||
'.py',
|
||||
'.rst',
|
||||
'.sh',
|
||||
'.yml',
|
||||
]
|
||||
TAB_SIZE = 4
|
||||
|
||||
|
||||
###########################################################
|
||||
# AUXILIARY FUNCTIONS
|
||||
###########################################################
|
||||
def skip_directory(dirpath: str) -> bool:
|
||||
"""
|
||||
Check if a directory should be skipped.
|
||||
|
||||
@param dirpath Directory path.
|
||||
@return Whether the directory should be skipped or not.
|
||||
"""
|
||||
|
||||
_, directory = os.path.split(dirpath)
|
||||
|
||||
return directory in DIRECTORIES_TO_SKIP or \
|
||||
(directory.startswith('.') and directory != '.')
|
||||
|
||||
|
||||
def skip_file_formatting(filename: str) -> bool:
|
||||
"""
|
||||
Check if a file should be skipped from formatting analysis.
|
||||
|
||||
@param filename Name of the file.
|
||||
@return Whether the file should be skipped or not.
|
||||
"""
|
||||
|
||||
_, extension = os.path.splitext(os.path.split(filename)[1])
|
||||
|
||||
return extension not in FILE_EXTENSIONS_TO_CHECK_FORMATTING
|
||||
|
||||
|
||||
def skip_file_whitespace(filename: str) -> bool:
|
||||
"""
|
||||
Check if a file should be skipped from trailing whitespace analysis.
|
||||
|
||||
@param filename Name of the file.
|
||||
@return Whether the file should be skipped or not.
|
||||
"""
|
||||
|
||||
basename, extension = os.path.splitext(os.path.split(filename)[1])
|
||||
|
||||
return basename not in FILES_TO_CHECK_WHITESPACE and \
|
||||
extension not in FILE_EXTENSIONS_TO_CHECK_WHITESPACE
|
||||
|
||||
|
||||
def skip_file_tabs(filename: str) -> bool:
|
||||
"""
|
||||
Check if a file should be skipped from tabs analysis.
|
||||
|
||||
@param filename Name of the file.
|
||||
@return Whether the file should be skipped or not.
|
||||
"""
|
||||
|
||||
_, extension = os.path.splitext(os.path.split(filename)[1])
|
||||
|
||||
return extension not in FILE_EXTENSIONS_TO_CHECK_TABS
|
||||
|
||||
|
||||
def find_files_to_check_style(path: str) -> Tuple[List[str], List[str], List[str]]:
|
||||
"""
|
||||
Find all files to be checked in a given path.
|
||||
|
||||
@param path Path to check.
|
||||
@return Tuple [List of files to check formatting,
|
||||
List of files to check trailing whitespace,
|
||||
List of files to check tabs].
|
||||
"""
|
||||
|
||||
files_to_check_formatting: List[str] = []
|
||||
files_to_check_whitespace: List[str] = []
|
||||
files_to_check_tabs: List[str] = []
|
||||
|
||||
abs_path = os.path.normpath(os.path.abspath(os.path.expanduser(path)))
|
||||
|
||||
if os.path.isfile(abs_path):
|
||||
if not skip_file_formatting(path):
|
||||
files_to_check_formatting.append(path)
|
||||
|
||||
if not skip_file_whitespace(path):
|
||||
files_to_check_whitespace.append(path)
|
||||
|
||||
if not skip_file_tabs(path):
|
||||
files_to_check_tabs.append(path)
|
||||
|
||||
elif os.path.isdir(abs_path):
|
||||
for dirpath, dirnames, filenames in os.walk(path, topdown=True):
|
||||
if skip_directory(dirpath):
|
||||
# Remove directory and its subdirectories
|
||||
dirnames[:] = []
|
||||
continue
|
||||
|
||||
filenames = [os.path.join(dirpath, f) for f in filenames]
|
||||
|
||||
for f in filenames:
|
||||
if not skip_file_formatting(f):
|
||||
files_to_check_formatting.append(f)
|
||||
|
||||
if not skip_file_whitespace(f):
|
||||
files_to_check_whitespace.append(f)
|
||||
|
||||
if not skip_file_tabs(f):
|
||||
files_to_check_tabs.append(f)
|
||||
|
||||
else:
|
||||
raise ValueError(f'Error: {path} is not a file nor a directory')
|
||||
|
||||
return (
|
||||
files_to_check_formatting,
|
||||
files_to_check_whitespace,
|
||||
files_to_check_tabs,
|
||||
)
|
||||
|
||||
|
||||
def find_clang_format_path() -> str:
|
||||
"""
|
||||
Find the path to one of the supported versions of clang-format.
|
||||
If no supported version of clang-format is found, raise an exception.
|
||||
|
||||
@return Path to clang-format.
|
||||
"""
|
||||
|
||||
for version in CLANG_FORMAT_VERSIONS:
|
||||
clang_format_path = shutil.which(f'clang-format-{version}')
|
||||
|
||||
if clang_format_path:
|
||||
return clang_format_path
|
||||
|
||||
raise RuntimeError(
|
||||
f'Could not find any supported version of clang-format installed on this system. '
|
||||
f'List of supported versions: {CLANG_FORMAT_VERSIONS}.'
|
||||
)
|
||||
|
||||
|
||||
###########################################################
|
||||
# CHECK STYLE
|
||||
###########################################################
|
||||
def check_style(path: str,
|
||||
enable_check_formatting: bool,
|
||||
enable_check_whitespace: bool,
|
||||
enable_check_tabs: bool,
|
||||
fix: bool,
|
||||
n_jobs: int = 1,
|
||||
) -> None:
|
||||
"""
|
||||
Check / fix the coding style of a list of files, including formatting and
|
||||
trailing whitespace.
|
||||
|
||||
@param path Path to the files.
|
||||
@param fix Whether to fix the style of the file (True) or
|
||||
just check if the file is well-formatted (False).
|
||||
@param enable_check_formatting Whether to enable code formatting checking.
|
||||
@param enable_check_whitespace Whether to enable trailing whitespace checking.
|
||||
@param enable_check_tabs Whether to enable tabs checking.
|
||||
@param n_jobs Number of parallel jobs.
|
||||
"""
|
||||
|
||||
(files_to_check_formatting,
|
||||
files_to_check_whitespace,
|
||||
files_to_check_tabs) = find_files_to_check_style(path)
|
||||
|
||||
check_formatting_successful = True
|
||||
check_whitespace_successful = True
|
||||
check_tabs_successful = True
|
||||
|
||||
if enable_check_formatting:
|
||||
check_formatting_successful = check_formatting(
|
||||
files_to_check_formatting, fix, n_jobs)
|
||||
|
||||
print('')
|
||||
|
||||
if enable_check_whitespace:
|
||||
check_whitespace_successful = check_trailing_whitespace(
|
||||
files_to_check_whitespace, fix, n_jobs)
|
||||
|
||||
print('')
|
||||
|
||||
if enable_check_tabs:
|
||||
check_whitespace_successful = check_tabs(
|
||||
files_to_check_tabs, fix, n_jobs)
|
||||
|
||||
if check_formatting_successful and \
|
||||
check_whitespace_successful and \
|
||||
check_tabs_successful:
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
###########################################################
|
||||
# CHECK FORMATTING
|
||||
###########################################################
|
||||
def check_formatting(filenames: List[str], fix: bool, n_jobs: int) -> bool:
|
||||
"""
|
||||
Check / fix the coding style of a list of files with clang-format.
|
||||
|
||||
@param filenames List of filenames to be checked.
|
||||
@param fix Whether to fix the formatting of the file (True) or
|
||||
just check if the file is well-formatted (False).
|
||||
@param n_jobs Number of parallel jobs.
|
||||
@return True if all files are well formatted after the check process.
|
||||
False if there are non-formatted files after the check process.
|
||||
"""
|
||||
|
||||
# Check files
|
||||
clang_format_path = find_clang_format_path()
|
||||
files_not_formatted: List[str] = []
|
||||
|
||||
with concurrent.futures.ProcessPoolExecutor(n_jobs) as executor:
|
||||
files_not_formatted_results = executor.map(
|
||||
check_formatting_file,
|
||||
filenames,
|
||||
itertools.repeat(clang_format_path),
|
||||
itertools.repeat(fix),
|
||||
)
|
||||
|
||||
for (filename, formatted) in files_not_formatted_results:
|
||||
if not formatted:
|
||||
files_not_formatted.append(filename)
|
||||
|
||||
files_not_formatted.sort()
|
||||
|
||||
# Output results
|
||||
if not files_not_formatted:
|
||||
print('All files are well formatted')
|
||||
return True
|
||||
|
||||
else:
|
||||
n_non_formatted_files = len(files_not_formatted)
|
||||
|
||||
if fix:
|
||||
print(f'Fixed formatting of the files ({n_non_formatted_files}):')
|
||||
else:
|
||||
print(f'Detected bad formatting in the files ({n_non_formatted_files}):')
|
||||
|
||||
for f in files_not_formatted:
|
||||
print(f'- {f}')
|
||||
|
||||
# Return True if all files were fixed
|
||||
return fix
|
||||
|
||||
|
||||
def check_formatting_file(filename: str,
|
||||
clang_format_path: str,
|
||||
fix: bool,
|
||||
) -> Tuple[str, bool]:
|
||||
"""
|
||||
Check / fix the coding style of a file with clang-format.
|
||||
|
||||
@param filename Name of the file to be checked.
|
||||
@param clang_format_path Path to clang-format.
|
||||
@param fix Whether to fix the style of the file (True) or
|
||||
just check if the file is well-formatted (False).
|
||||
@return Tuple [Filename, Whether the file is well-formatted].
|
||||
"""
|
||||
|
||||
# Check if the file is well formatted
|
||||
process = subprocess.run(
|
||||
[
|
||||
clang_format_path,
|
||||
filename,
|
||||
'-style=file',
|
||||
'--dry-run',
|
||||
'--Werror',
|
||||
# Optimization: Only 1 error is needed to check that the file is not formatted
|
||||
'--ferror-limit=1',
|
||||
],
|
||||
check=False,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
file_formatted = (process.returncode == 0)
|
||||
|
||||
# Fix file
|
||||
if fix and not file_formatted:
|
||||
process = subprocess.run(
|
||||
[
|
||||
clang_format_path,
|
||||
filename,
|
||||
'-style=file',
|
||||
'-i',
|
||||
],
|
||||
check=False,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
return (filename, file_formatted)
|
||||
|
||||
|
||||
###########################################################
|
||||
# CHECK TRAILING WHITESPACE
|
||||
###########################################################
|
||||
def check_trailing_whitespace(filenames: List[str], fix: bool, n_jobs: int) -> bool:
|
||||
"""
|
||||
Check / fix trailing whitespace in a list of files.
|
||||
|
||||
@param filename Name of the file to be checked.
|
||||
@param fix Whether to fix the file (True) or
|
||||
just check if it has trailing whitespace (False).
|
||||
@param n_jobs Number of parallel jobs.
|
||||
@return True if no files have trailing whitespace after the check process.
|
||||
False if there are trailing whitespace after the check process.
|
||||
"""
|
||||
|
||||
# Check files
|
||||
files_with_whitespace: List[str] = []
|
||||
|
||||
with concurrent.futures.ProcessPoolExecutor(n_jobs) as executor:
|
||||
files_with_whitespace_results = executor.map(
|
||||
check_trailing_whitespace_file,
|
||||
filenames,
|
||||
itertools.repeat(fix),
|
||||
)
|
||||
|
||||
for (filename, has_whitespace) in files_with_whitespace_results:
|
||||
if has_whitespace:
|
||||
files_with_whitespace.append(filename)
|
||||
|
||||
files_with_whitespace.sort()
|
||||
|
||||
# Output results
|
||||
if not files_with_whitespace:
|
||||
print('No files detected with trailing whitespace')
|
||||
return True
|
||||
|
||||
else:
|
||||
n_files_with_whitespace = len(files_with_whitespace)
|
||||
|
||||
if fix:
|
||||
print(
|
||||
f'Fixed trailing whitespace in the files ({n_files_with_whitespace}):')
|
||||
else:
|
||||
print(
|
||||
f'Detected trailing whitespace in the files ({n_files_with_whitespace}):')
|
||||
|
||||
for f in files_with_whitespace:
|
||||
print(f'- {f}')
|
||||
|
||||
# If all files were fixed, there are no more trailing whitespace
|
||||
return fix
|
||||
|
||||
|
||||
def check_trailing_whitespace_file(filename: str, fix: bool) -> Tuple[str, bool]:
|
||||
"""
|
||||
Check / fix trailing whitespace in a file.
|
||||
|
||||
@param filename Name of the file to be checked.
|
||||
@param fix Whether to fix the file (True) or
|
||||
just check if it has trailing whitespace (False).
|
||||
@return Tuple [Filename, Whether the file has trailing whitespace].
|
||||
"""
|
||||
|
||||
has_trailing_whitespace = False
|
||||
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
file_lines = f.readlines()
|
||||
|
||||
# Check if there are trailing whitespace and fix them
|
||||
for (i, line) in enumerate(file_lines):
|
||||
line_fixed = line.rstrip() + '\n'
|
||||
|
||||
if line_fixed != line:
|
||||
has_trailing_whitespace = True
|
||||
|
||||
# Optimization: if only checking, skip the rest of the file
|
||||
if not fix:
|
||||
break
|
||||
|
||||
file_lines[i] = line_fixed
|
||||
|
||||
# Update file with the fixed lines
|
||||
if fix and has_trailing_whitespace:
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
f.writelines(file_lines)
|
||||
|
||||
return (filename, has_trailing_whitespace)
|
||||
|
||||
|
||||
###########################################################
|
||||
# CHECK TABS
|
||||
###########################################################
|
||||
def check_tabs(filenames: List[str], fix: bool, n_jobs: int) -> bool:
|
||||
"""
|
||||
Check / fix tabs in a list of files.
|
||||
|
||||
@param filename Name of the file to be checked.
|
||||
@param fix Whether to fix the file (True) or just check if it has tabs (False).
|
||||
@param n_jobs Number of parallel jobs.
|
||||
@return True if no files have tabs after the check process.
|
||||
False if there are tabs after the check process.
|
||||
"""
|
||||
|
||||
# Check files
|
||||
files_with_tabs: List[str] = []
|
||||
|
||||
with concurrent.futures.ProcessPoolExecutor(n_jobs) as executor:
|
||||
files_with_tabs_results = executor.map(
|
||||
check_tabs_file,
|
||||
filenames,
|
||||
itertools.repeat(fix),
|
||||
)
|
||||
|
||||
for (filename, has_tabs) in files_with_tabs_results:
|
||||
if has_tabs:
|
||||
files_with_tabs.append(filename)
|
||||
|
||||
files_with_tabs.sort()
|
||||
|
||||
# Output results
|
||||
if not files_with_tabs:
|
||||
print('No files detected with tabs')
|
||||
return True
|
||||
|
||||
else:
|
||||
n_files_with_tabs = len(files_with_tabs)
|
||||
|
||||
if fix:
|
||||
print(
|
||||
f'Fixed tabs in the files ({n_files_with_tabs}):')
|
||||
else:
|
||||
print(
|
||||
f'Detected tabs in the files ({n_files_with_tabs}):')
|
||||
|
||||
for f in files_with_tabs:
|
||||
print(f'- {f}')
|
||||
|
||||
# If all files were fixed, there are no more trailing whitespace
|
||||
return fix
|
||||
|
||||
|
||||
def check_tabs_file(filename: str, fix: bool) -> Tuple[str, bool]:
|
||||
"""
|
||||
Check / fix tabs in a file.
|
||||
|
||||
@param filename Name of the file to be checked.
|
||||
@param fix Whether to fix the file (True) or just check if it has tabs (False).
|
||||
@return Tuple [Filename, Whether the file has tabs].
|
||||
"""
|
||||
|
||||
has_tabs = False
|
||||
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
file_lines = f.readlines()
|
||||
|
||||
# Check if there are tabs and fix them
|
||||
for (i, line) in enumerate(file_lines):
|
||||
if line.find('\t') != -1:
|
||||
has_tabs = True
|
||||
|
||||
# Optimization: if only checking, skip the rest of the file
|
||||
if not fix:
|
||||
break
|
||||
|
||||
file_lines[i] = line.expandtabs(TAB_SIZE)
|
||||
|
||||
# Update file with the fixed lines
|
||||
if fix and has_tabs:
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
f.writelines(file_lines)
|
||||
|
||||
return (filename, has_tabs)
|
||||
|
||||
|
||||
###########################################################
|
||||
# MAIN
|
||||
###########################################################
|
||||
if __name__ == '__main__':
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Check and apply the ns-3 coding style to all files in a given PATH. '
|
||||
'The script checks the formatting of the file with clang-format. '
|
||||
'Additionally, it checks the presence of trailing whitespace and tabs. '
|
||||
'When used in "check mode" (default), the script checks if all files are well '
|
||||
'formatted and do not have trailing whitespace nor tabs. '
|
||||
'If it detects non-formatted files, they will be printed and this process exits with a '
|
||||
'non-zero code. When used in "fix mode", this script automatically fixes the files.')
|
||||
|
||||
parser.add_argument('path', action='store', type=str,
|
||||
help='Path to the files to check')
|
||||
|
||||
parser.add_argument('--no-formatting', action='store_true',
|
||||
help='Do not check / fix code formatting')
|
||||
|
||||
parser.add_argument('--no-whitespace', action='store_true',
|
||||
help='Do not check / fix trailing whitespace')
|
||||
|
||||
parser.add_argument('--no-tabs', action='store_true',
|
||||
help='Do not check / fix tabs')
|
||||
|
||||
parser.add_argument('--fix', action='store_true',
|
||||
help='Fix coding style issues detected in the files')
|
||||
|
||||
parser.add_argument('-j', '--jobs', type=int, default=max(1, os.cpu_count() - 1),
|
||||
help='Number of parallel jobs')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
check_style(
|
||||
path=args.path,
|
||||
enable_check_formatting=(not args.no_formatting),
|
||||
enable_check_whitespace=(not args.no_whitespace),
|
||||
enable_check_tabs=(not args.no_tabs),
|
||||
fix=args.fix,
|
||||
n_jobs=args.jobs,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
sys.exit(1)
|
||||
@@ -1,568 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import sys
|
||||
import filecmp
|
||||
import optparse
|
||||
import shutil
|
||||
import difflib
|
||||
import re
|
||||
|
||||
def git_modified_files():
|
||||
files = os.popen('git diff --name-only')
|
||||
process = subprocess.Popen(["git","rev-parse","--show-toplevel"],
|
||||
stdout = subprocess.PIPE,
|
||||
stderr = subprocess.PIPE)
|
||||
root_dir, _ = process.communicate()
|
||||
if isinstance(root_dir, bytes):
|
||||
root_dir=root_dir.decode("utf-8")
|
||||
files_changed = [item.strip() for item in files.readlines()]
|
||||
files_changed = [item for item in files_changed if item.endswith('.h') or item.endswith('.cc')]
|
||||
return [root_dir[: -1] + "/" + filename.strip () for filename in files_changed]
|
||||
|
||||
def copy_file(filename):
|
||||
_, pathname = tempfile.mkstemp()
|
||||
with open(filename, 'r') as src, open(pathname, 'w') as dst:
|
||||
for line in src:
|
||||
dst.write(line)
|
||||
return pathname
|
||||
|
||||
# generate a temporary configuration file
|
||||
def uncrustify_config_file(level):
|
||||
level2 = """
|
||||
nl_if_brace=Add
|
||||
nl_brace_else=Add
|
||||
nl_elseif_brace=Add
|
||||
nl_else_brace=Add
|
||||
nl_while_brace=Add
|
||||
nl_do_brace=Add
|
||||
nl_for_brace=Add
|
||||
nl_brace_while=Add
|
||||
nl_switch_brace=Add
|
||||
nl_after_case=True
|
||||
nl_namespace_brace=ignore
|
||||
nl_after_brace_open=True
|
||||
nl_class_leave_one_liners=False
|
||||
nl_enum_leave_one_liners=False
|
||||
nl_func_leave_one_liners=False
|
||||
nl_if_leave_one_liners=False
|
||||
nl_class_colon=Ignore
|
||||
nl_before_access_spec=2
|
||||
nl_after_access_spec=0
|
||||
indent_access_spec=-indent_columns
|
||||
nl_after_semicolon=True
|
||||
pos_class_colon=Lead
|
||||
pos_class_comma=Trail
|
||||
indent_constr_colon=true
|
||||
pos_bool=Lead
|
||||
nl_class_init_args=Add
|
||||
nl_template_class=Add
|
||||
nl_class_brace=Add
|
||||
# does not work very well
|
||||
nl_func_type_name=Ignore
|
||||
nl_func_scope_name=Ignore
|
||||
nl_func_type_name_class=Ignore
|
||||
nl_func_proto_type_name=Ignore
|
||||
# function\\n(
|
||||
nl_func_paren=Remove
|
||||
nl_fdef_brace=Add
|
||||
nl_struct_brace=Add
|
||||
nl_enum_brace=Add
|
||||
nl_union_brace=Add
|
||||
mod_full_brace_do=Add
|
||||
mod_full_brace_for=Add
|
||||
mod_full_brace_if=Add
|
||||
mod_full_brace_while=Add
|
||||
mod_full_brace_for=Add
|
||||
mod_remove_extra_semicolon=True
|
||||
# max code width
|
||||
#code_width=128
|
||||
#ls_for_split_full=True
|
||||
#ls_func_split_full=True
|
||||
nl_cpp_lambda_leave_one_liners=True
|
||||
"""
|
||||
level1 = """
|
||||
# extra spaces here and there
|
||||
sp_brace_typedef=Add
|
||||
sp_enum_assign=Add
|
||||
sp_before_sparen=Add
|
||||
sp_after_semi_for=Add
|
||||
sp_arith=Add
|
||||
sp_assign=Add
|
||||
sp_compare=Add
|
||||
sp_func_class_paren=Add
|
||||
sp_after_type=Add
|
||||
sp_type_func=Add
|
||||
sp_angle_paren=Add
|
||||
"""
|
||||
level0 = """
|
||||
sp_func_proto_paren=Add
|
||||
sp_func_def_paren=Add
|
||||
sp_func_call_paren=Add
|
||||
sp_after_semi_for=Ignore
|
||||
sp_before_sparen=Ignore
|
||||
sp_before_ellipsis=Remove
|
||||
sp_type_func=Ignore
|
||||
sp_after_type=Ignore
|
||||
nl_class_leave_one_liners=True
|
||||
nl_enum_leave_one_liners=True
|
||||
nl_func_leave_one_liners=True
|
||||
nl_assign_leave_one_liners=True
|
||||
nl_collapse_empty_body=True
|
||||
nl_getset_leave_one_liners=True
|
||||
nl_if_leave_one_liners=True
|
||||
nl_fdef_brace=Ignore
|
||||
# finally, indentation configuration
|
||||
indent_with_tabs=0
|
||||
indent_namespace=false
|
||||
indent_columns=2
|
||||
indent_brace=2
|
||||
indent_case_brace=indent_columns
|
||||
indent_class=true
|
||||
indent_class_colon=True
|
||||
indent_switch_case=indent_columns
|
||||
# alignment
|
||||
indent_align_assign=False
|
||||
align_left_shift=True
|
||||
# comment reformating disabled
|
||||
cmt_reflow_mode=1 # do not touch comments at all
|
||||
cmt_indent_multi=False # really, do not touch them
|
||||
disable_processing_cmt= " *NS_CHECK_STYLE_OFF*"
|
||||
enable_processing_cmt= " *NS_CHECK_STYLE_ON*"
|
||||
"""
|
||||
_, pathname = tempfile.mkstemp()
|
||||
with open(pathname, 'w') as dst:
|
||||
dst.write(level0)
|
||||
if level >= 1:
|
||||
dst.write(level1)
|
||||
if level >= 2:
|
||||
dst.write(level2)
|
||||
return pathname
|
||||
|
||||
## PatchChunkLine class
|
||||
class PatchChunkLine:
|
||||
## @var __type
|
||||
# type
|
||||
## @var __line
|
||||
# line
|
||||
## @var SRC
|
||||
# Source
|
||||
SRC = 1
|
||||
## @var DST
|
||||
# Destination
|
||||
DST = 2
|
||||
## @var BOTH
|
||||
# Both
|
||||
BOTH = 3
|
||||
def __init__(self):
|
||||
"""! Initializer
|
||||
@param self The current class
|
||||
"""
|
||||
self.__type = 0
|
||||
self.__line = ''
|
||||
def set_src(self,line):
|
||||
"""! Set source
|
||||
@param self The current class
|
||||
@param line source line
|
||||
@return none
|
||||
"""
|
||||
self.__type = self.SRC
|
||||
self.__line = line
|
||||
def set_dst(self,line):
|
||||
"""! Set destination
|
||||
@param self The current class
|
||||
@param line destination line
|
||||
@return none
|
||||
"""
|
||||
self.__type = self.DST
|
||||
self.__line = line
|
||||
def set_both(self,line):
|
||||
"""! Set both
|
||||
@param self The current class
|
||||
@param line
|
||||
@return none
|
||||
"""
|
||||
self.__type = self.BOTH
|
||||
self.__line = line
|
||||
def append_to_line(self, s):
|
||||
"""! Append to line
|
||||
@param self The current class
|
||||
@param s line to append
|
||||
@return none
|
||||
"""
|
||||
self.__line = self.__line + s
|
||||
def line(self):
|
||||
"""! Get line
|
||||
@param self The current class
|
||||
@return line
|
||||
"""
|
||||
return self.__line
|
||||
def is_src(self):
|
||||
"""! Is source
|
||||
@param self The current class
|
||||
@return true if type is source
|
||||
"""
|
||||
return self.__type == self.SRC or self.__type == self.BOTH
|
||||
def is_dst(self):
|
||||
"""! Is destination
|
||||
@param self The current class
|
||||
@return true if type is destination
|
||||
"""
|
||||
return self.__type == self.DST or self.__type == self.BOTH
|
||||
def write(self, f):
|
||||
"""! Write to file
|
||||
@param self The current class
|
||||
@param f file
|
||||
@return exception if invalid type
|
||||
"""
|
||||
if self.__type == self.SRC:
|
||||
f.write('-%s\n' % self.__line)
|
||||
elif self.__type == self.DST:
|
||||
f.write('+%s\n' % self.__line)
|
||||
elif self.__type == self.BOTH:
|
||||
f.write(' %s\n' % self.__line)
|
||||
else:
|
||||
raise Exception('invalid patch')
|
||||
|
||||
## PatchChunk class
|
||||
class PatchChunk:
|
||||
## @var __lines
|
||||
# list of lines
|
||||
## @var __src_pos
|
||||
# source position
|
||||
## @var __dst_pos
|
||||
# destination position
|
||||
## @var src
|
||||
# source
|
||||
## @var dst
|
||||
# destination
|
||||
def __init__(self, src_pos, dst_pos):
|
||||
"""! Initializer
|
||||
@param self: this object
|
||||
@param src_pos: source position
|
||||
@param dst_pos: destination position
|
||||
"""
|
||||
self.__lines = []
|
||||
self.__src_pos = int(src_pos)
|
||||
self.__dst_pos = int(dst_pos)
|
||||
def src_start(self):
|
||||
"""! Source start function
|
||||
@param self this object
|
||||
@return source position
|
||||
"""
|
||||
return self.__src_pos
|
||||
def add_line(self,line):
|
||||
"""! Add line function
|
||||
@param self The current class
|
||||
@param line line to add
|
||||
@return none
|
||||
"""
|
||||
self.__lines.append(line)
|
||||
def src(self):
|
||||
"""! Get source lines
|
||||
@param self The current class
|
||||
@return the source lines
|
||||
"""
|
||||
src = []
|
||||
for line in self.__lines:
|
||||
if line.is_src():
|
||||
src.append(line)
|
||||
return src
|
||||
def dst(self):
|
||||
"""! Get destination lines
|
||||
@param self The current class
|
||||
@return the destination lines
|
||||
"""
|
||||
dst = []
|
||||
for line in self.__lines:
|
||||
if line.is_dst():
|
||||
dst.append(line)
|
||||
return dst
|
||||
def src_len(self):
|
||||
"""! Get number of source lines
|
||||
@param self The current class
|
||||
@return number of source lines
|
||||
"""
|
||||
return len(self.src())
|
||||
def dst_len(self):
|
||||
"""! Get number of destinaton lines
|
||||
@param self The current class
|
||||
@return number of destination lines
|
||||
"""
|
||||
return len(self.dst())
|
||||
def write(self,f):
|
||||
"""! Write lines to file
|
||||
@param self The current class
|
||||
@param f: file to write to
|
||||
@return none
|
||||
"""
|
||||
f.write('@@ -%d,%d +%d,%d @@\n' % (self.__src_pos, self.src_len(),
|
||||
self.__dst_pos, self.dst_len()))
|
||||
for line in self.__lines:
|
||||
line.write(f)
|
||||
|
||||
## Patch class
|
||||
class Patch:
|
||||
## @var __src
|
||||
# source
|
||||
## @var __dst
|
||||
# destination
|
||||
## @var __chunks
|
||||
# chunks
|
||||
def __init__(self):
|
||||
"""! Initializer
|
||||
@param self The current class
|
||||
"""
|
||||
self.__src = ''
|
||||
self.__dst = ''
|
||||
self.__chunks = []
|
||||
def add_chunk(self, chunk):
|
||||
"""! Add chunk
|
||||
@param self this object
|
||||
@param chunk chunk
|
||||
@return none
|
||||
"""
|
||||
self.__chunks.append(chunk)
|
||||
def chunks(self):
|
||||
"""! Get the chunks
|
||||
@param self The current class
|
||||
@return the chunks
|
||||
"""
|
||||
return self.__chunks
|
||||
def set_src(self,src):
|
||||
"""! Set source
|
||||
@param self this object
|
||||
@param src source
|
||||
@return none
|
||||
"""
|
||||
self.__src = src
|
||||
def set_dst(self,dst):
|
||||
"""! Set destination
|
||||
@param self this object
|
||||
@param dst destintion
|
||||
@return none
|
||||
"""
|
||||
self.__dst = dst
|
||||
def apply(self,filename):
|
||||
"""! Apply function
|
||||
@param self The current class
|
||||
@param filename file name
|
||||
@return none
|
||||
"""
|
||||
# XXX: not implemented
|
||||
return
|
||||
def write(self,f):
|
||||
"""! Write to file
|
||||
@param self The current class
|
||||
@param f the file
|
||||
@return none
|
||||
"""
|
||||
f.write('--- %s\n' % self.__src )
|
||||
f.write('+++ %s\n' % self.__dst )
|
||||
for chunk in self.__chunks:
|
||||
chunk.write(f)
|
||||
|
||||
def parse_patchset(generator):
|
||||
src_file = re.compile('^--- (.*)$')
|
||||
dst_file = re.compile('^\+\+\+ (.*)$')
|
||||
chunk_start = re.compile('^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@')
|
||||
src = re.compile('^-(.*)$')
|
||||
dst = re.compile('^\+(.*)$')
|
||||
both = re.compile('^ (.*)$')
|
||||
patchset = []
|
||||
current_patch = None
|
||||
for line in generator:
|
||||
m = src_file.search(line)
|
||||
if m is not None:
|
||||
current_patch = Patch()
|
||||
patchset.append(current_patch)
|
||||
current_patch.set_src(m.group(1))
|
||||
continue
|
||||
m = dst_file.search(line)
|
||||
if m is not None:
|
||||
current_patch.set_dst(m.group(1))
|
||||
continue
|
||||
m = chunk_start.search(line)
|
||||
if m is not None:
|
||||
current_chunk = PatchChunk(m.group(1), m.group(3))
|
||||
current_patch.add_chunk(current_chunk)
|
||||
continue
|
||||
m = src.search(line)
|
||||
if m is not None:
|
||||
l = PatchChunkLine()
|
||||
l.set_src(m.group(1))
|
||||
current_chunk.add_line(l)
|
||||
continue
|
||||
m = dst.search(line)
|
||||
if m is not None:
|
||||
l = PatchChunkLine()
|
||||
l.set_dst(m.group(1))
|
||||
current_chunk.add_line(l)
|
||||
continue
|
||||
m = both.search(line)
|
||||
if m is not None:
|
||||
l = PatchChunkLine()
|
||||
l.set_both(m.group(1))
|
||||
current_chunk.add_line(l)
|
||||
continue
|
||||
raise Exception()
|
||||
return patchset
|
||||
|
||||
def remove_trailing_whitespace_changes(patch_generator):
|
||||
whitespace = re.compile('^(.*)([ \t]+)$')
|
||||
patchset = parse_patchset(patch_generator)
|
||||
for patch in patchset:
|
||||
for chunk in patch.chunks():
|
||||
src = chunk.src()
|
||||
dst = chunk.dst()
|
||||
try:
|
||||
for i in range(0,len(src)):
|
||||
s = src[i]
|
||||
d = dst[i]
|
||||
m = whitespace.search(s.line())
|
||||
if m is not None and m.group(1) == d.line():
|
||||
d.append_to_line(m.group(2))
|
||||
except:
|
||||
return patchset
|
||||
return patchset
|
||||
|
||||
|
||||
def indent(source, debug, level):
|
||||
output = tempfile.mkstemp()[1]
|
||||
# apply uncrustify
|
||||
cfg = uncrustify_config_file(level)
|
||||
if debug:
|
||||
sys.stderr.write('original file=' + source + '\n')
|
||||
sys.stderr.write('uncrustify config file=' + cfg + '\n')
|
||||
sys.stderr.write('temporary file=' + output + '\n')
|
||||
try:
|
||||
uncrust = subprocess.Popen(['uncrustify', '-c', cfg, '-f', source, '-o', output],
|
||||
stdin = subprocess.PIPE,
|
||||
stdout = subprocess.PIPE,
|
||||
stderr = subprocess.PIPE,
|
||||
universal_newlines = True)
|
||||
(out, err) = uncrust.communicate('')
|
||||
if debug:
|
||||
sys.stderr.write(out)
|
||||
sys.stderr.write(err)
|
||||
except OSError:
|
||||
raise Exception ('uncrustify not installed')
|
||||
# generate a diff file
|
||||
with open(source, 'r') as src, open(output, 'r') as dst:
|
||||
diff = difflib.unified_diff(src.readlines(), dst.readlines(),
|
||||
fromfile=source, tofile=output)
|
||||
if debug:
|
||||
initial_diff = tempfile.mkstemp()[1]
|
||||
sys.stderr.write('initial diff file=' + initial_diff + '\n')
|
||||
with open(initial_diff, 'w') as tmp:
|
||||
tmp.writelines(diff)
|
||||
final_diff = tempfile.mkstemp()[1]
|
||||
if level < 3:
|
||||
patchset = remove_trailing_whitespace_changes(diff)
|
||||
if len(patchset) != 0:
|
||||
with open(final_diff, 'w') as dst:
|
||||
patchset[0].write(dst)
|
||||
else:
|
||||
with open(final_diff, 'w') as dst:
|
||||
dst.writelines(diff)
|
||||
|
||||
|
||||
# apply diff file
|
||||
if debug:
|
||||
sys.stderr.write('final diff file=' + final_diff + '\n')
|
||||
shutil.copyfile(source,output)
|
||||
patch = subprocess.Popen(['patch', '-p1', '-i', final_diff, output],
|
||||
stdin = subprocess.PIPE,
|
||||
stdout = subprocess.PIPE,
|
||||
stderr = subprocess.PIPE,
|
||||
universal_newlines = True)
|
||||
(out, err) = patch.communicate('')
|
||||
if debug:
|
||||
sys.stderr.write(out)
|
||||
sys.stderr.write(err)
|
||||
return output
|
||||
|
||||
|
||||
|
||||
def indent_files(files, diff=False, debug=False, level=0, inplace=False):
|
||||
output = []
|
||||
for f in files:
|
||||
dst = indent(f, debug=debug, level=level)
|
||||
output.append([f,dst])
|
||||
|
||||
# First, copy to inplace
|
||||
if inplace:
|
||||
for src,dst in output:
|
||||
shutil.copyfile(dst,src)
|
||||
return True
|
||||
|
||||
# now, compare
|
||||
failed = []
|
||||
for src,dst in output:
|
||||
if filecmp.cmp(src,dst) == 0:
|
||||
failed.append([src, dst])
|
||||
if len(failed) > 0:
|
||||
if not diff:
|
||||
print('Found %u badly indented files:' % len(failed))
|
||||
for src,dst in failed:
|
||||
print(' ' + src)
|
||||
else:
|
||||
for src,dst in failed:
|
||||
with open(src, 'r') as f_src, open(dst, 'r') as f_dst:
|
||||
s = f_src.readlines()
|
||||
d = f_dst.readlines()
|
||||
for line in difflib.unified_diff(s, d, fromfile=src, tofile=dst):
|
||||
sys.stdout.write(line)
|
||||
return False
|
||||
return True
|
||||
|
||||
def run_as_main():
|
||||
parser = optparse.OptionParser()
|
||||
parser.add_option('--debug', action='store_true', dest='debug', default=False,
|
||||
help='Output some debugging information')
|
||||
parser.add_option('-l', '--level', type='int', dest='level', default=0,
|
||||
help="Level of style conformance: higher levels include all lower levels. "
|
||||
"level=0: re-indent only. level=1: add extra spaces. level=2: insert extra newlines and "
|
||||
"extra braces around single-line statements. level=3: remove all trailing spaces")
|
||||
parser.add_option('--check-git', action='store_true', dest='git', default=False,
|
||||
help="Get the list of files to check from Git\'s list of modified and added files")
|
||||
parser.add_option('-f', '--check-file', action='store', dest='file', default='',
|
||||
help="Check a single file")
|
||||
parser.add_option('--diff', action='store_true', dest='diff', default=False,
|
||||
help="Generate a diff on stdout of the indented files")
|
||||
parser.add_option('-i', '--in-place', action='store_true', dest='in_place', default=False,
|
||||
help="Indent the input files in-place")
|
||||
options, _ = parser.parse_args()
|
||||
style_is_correct = False
|
||||
|
||||
if options.git:
|
||||
files = git_modified_files()
|
||||
style_is_correct = indent_files(files,
|
||||
diff=options.diff,
|
||||
debug=options.debug,
|
||||
level=options.level,
|
||||
inplace=options.in_place)
|
||||
elif options.file != '':
|
||||
file = options.file
|
||||
if not os.path.exists(file) or \
|
||||
not os.path.isfile(file):
|
||||
print('file %s does not exist' % file)
|
||||
sys.exit(1)
|
||||
style_is_correct = indent_files([file],
|
||||
diff=options.diff,
|
||||
debug=options.debug,
|
||||
level=options.level,
|
||||
inplace=options.in_place)
|
||||
|
||||
if not style_is_correct:
|
||||
sys.exit(1)
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
run_as_main()
|
||||
except Exception as e:
|
||||
sys.stderr.write(str(e) + '\n')
|
||||
sys.exit(1)
|
||||
@@ -1,226 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (c) 2022 Eduardo Almeida.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License version 2 as
|
||||
# published by the Free Software Foundation;
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Author: Eduardo Almeida [@edalm] [INESC TEC and FEUP]
|
||||
|
||||
"""
|
||||
Check and trim trailing whitespace in files indicated in the PATH argument.
|
||||
This script can be applied to all text files in a given path or to
|
||||
individual files.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
FILE_EXTENSIONS_TO_CHECK = [
|
||||
'.c',
|
||||
'.cc',
|
||||
'.click',
|
||||
'.conf',
|
||||
'.css',
|
||||
'.dot',
|
||||
'.gnuplot',
|
||||
'.gp',
|
||||
'.h',
|
||||
'.html',
|
||||
'.js',
|
||||
'.json',
|
||||
'.m',
|
||||
'.md',
|
||||
'.mob',
|
||||
'.ns_params',
|
||||
'.ns_movements',
|
||||
'.params',
|
||||
'.pl',
|
||||
'.plt',
|
||||
'.py',
|
||||
'.rst',
|
||||
'.seqdiag',
|
||||
'.sh',
|
||||
'.txt',
|
||||
'.yml',
|
||||
]
|
||||
|
||||
FILES_TO_CHECK = [
|
||||
'Makefile',
|
||||
'ns3',
|
||||
]
|
||||
|
||||
DIRECTORIES_TO_SKIP = [
|
||||
'bindings',
|
||||
'build',
|
||||
'cmake-cache',
|
||||
]
|
||||
|
||||
def skip_file(filename: str) -> bool:
|
||||
"""
|
||||
Check if a file should be skipped.
|
||||
|
||||
@param filename Name of the file.
|
||||
@return Whether the directory should be skipped or not.
|
||||
"""
|
||||
|
||||
basename, extension = os.path.splitext(os.path.split(filename)[1])
|
||||
|
||||
return basename not in FILES_TO_CHECK and \
|
||||
extension not in FILE_EXTENSIONS_TO_CHECK
|
||||
|
||||
def skip_directory(dirpath: str) -> bool:
|
||||
"""
|
||||
Check if a directory should be skipped.
|
||||
|
||||
@param dirpath Directory path.
|
||||
@return Whether the directory should be skipped or not.
|
||||
"""
|
||||
|
||||
_, directory = os.path.split(dirpath)
|
||||
|
||||
return (directory.startswith('.') and directory != '.') or \
|
||||
directory in DIRECTORIES_TO_SKIP
|
||||
|
||||
def trim_trailing_whitespace(path: str, trim: bool) -> None:
|
||||
"""
|
||||
Trim trailing whitespace in all text files in the given path.
|
||||
|
||||
@param path Path to the files.
|
||||
@param trim Whether to trim the file (True) or
|
||||
just check if the file has trailing whitespace (False).
|
||||
"""
|
||||
|
||||
files_with_trailing_whitespace = []
|
||||
|
||||
abs_path = os.path.normpath(os.path.abspath(os.path.expanduser(path)))
|
||||
|
||||
if os.path.isfile(abs_path):
|
||||
|
||||
if not skip_file(abs_path):
|
||||
file_had_whitespace = trim_file(abs_path, trim)
|
||||
|
||||
if file_had_whitespace:
|
||||
files_with_trailing_whitespace.append(path)
|
||||
|
||||
elif os.path.isdir(abs_path):
|
||||
|
||||
for dirpath, dirnames, filenames in os.walk(path, topdown=True):
|
||||
|
||||
# Check if directory and its subdirectories should be skipped
|
||||
if skip_directory(dirpath):
|
||||
dirnames[:] = []
|
||||
continue
|
||||
|
||||
# Process files with trailing whitespace
|
||||
filenames = [os.path.join(dirpath, f) for f in filenames]
|
||||
|
||||
for filename in filenames:
|
||||
|
||||
# Skip files that should not be checked
|
||||
if skip_file(filename):
|
||||
continue
|
||||
|
||||
file_had_whitespace = trim_file(
|
||||
os.path.normpath(os.path.abspath(os.path.expanduser(filename))),
|
||||
trim,
|
||||
)
|
||||
|
||||
if file_had_whitespace:
|
||||
files_with_trailing_whitespace.append(filename)
|
||||
|
||||
else:
|
||||
print(f'Error: {path} is not a file nor a directory')
|
||||
sys.exit(1)
|
||||
|
||||
# Output results
|
||||
n_files_with_trailing_whitespace = len(files_with_trailing_whitespace)
|
||||
|
||||
if files_with_trailing_whitespace:
|
||||
if trim:
|
||||
print('Trimmed files with trailing whitespace:\n')
|
||||
else:
|
||||
print('Detected files with trailing whitespace:\n')
|
||||
|
||||
for f in files_with_trailing_whitespace:
|
||||
print(f'- {f}')
|
||||
|
||||
if trim:
|
||||
print(f'\n'
|
||||
f'Number of files trimmed: {n_files_with_trailing_whitespace}')
|
||||
else:
|
||||
print(f'\n'
|
||||
f'Number of files with trailing whitespace: {n_files_with_trailing_whitespace}')
|
||||
sys.exit(1)
|
||||
|
||||
else:
|
||||
print('No files detected with trailing whitespace')
|
||||
|
||||
def trim_file(filename: str, trim: bool) -> bool:
|
||||
"""
|
||||
Trim trailing whitespace in a given file.
|
||||
|
||||
@param filename Name of the file to be trimmed.
|
||||
@param trim Whether to trim the file (True) or
|
||||
just check if the file has trailing whitespace (False).
|
||||
@return Whether the file had trailing whitespace (True) or not (False).
|
||||
"""
|
||||
|
||||
has_trailing_whitespace = False
|
||||
|
||||
try:
|
||||
with open(filename, 'r') as f:
|
||||
file_lines = f.readlines()
|
||||
|
||||
# Check if there are trailing whitespace and trim them
|
||||
for (i, line) in enumerate(file_lines):
|
||||
line_trimmed = line.rstrip() + '\n'
|
||||
|
||||
if line_trimmed != line:
|
||||
has_trailing_whitespace = True
|
||||
|
||||
# Optimization: if only checking, skip the rest of the file,
|
||||
# since it does have trailing whitespace
|
||||
if not trim:
|
||||
break
|
||||
|
||||
file_lines[i] = line_trimmed
|
||||
|
||||
# Update the file with the trimmed lines
|
||||
if trim and has_trailing_whitespace:
|
||||
with open(filename, 'w') as f:
|
||||
f.writelines(file_lines)
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
sys.exit(1)
|
||||
|
||||
return has_trailing_whitespace
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
parser = argparse.ArgumentParser(description=
|
||||
'Trim trailing whitespace in all text files in a given PATH.')
|
||||
|
||||
parser.add_argument('path', action='store', type=str, help=
|
||||
'Path to the files.')
|
||||
|
||||
parser.add_argument('--check', action='store_true', help=
|
||||
'Check if the files have trailing whitespace, without modifying them. '
|
||||
'If they have, this process will exit with a non-zero exit code '
|
||||
'and list them in the output.')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
trim_trailing_whitespace(args.path, trim=(not args.check))
|
||||
@@ -1,80 +0,0 @@
|
||||
/**
|
||||
* \ingroup utils
|
||||
* \defgroup CheckStyle check-style.py
|
||||
*
|
||||
* The check-style.py script will test and reformat code according to the
|
||||
* ns-3 coding style posted at https://www.nsnam.org/developers/contributing-code/coding-style/
|
||||
* It requires that you install 'uncrustify'
|
||||
*
|
||||
* It has multiple levels of conformance:
|
||||
* - level=0: the default: merely checks indentation
|
||||
* - level=1: checks also for missing spaces before parentheses
|
||||
* - level=2: checks also for missing newlines and braces around single-line statements
|
||||
* - level=3: checks also for missing trailing whitespaces
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* check a single file (level 0 by default):
|
||||
\verbatim
|
||||
./check-style.py -f src/core/object.h
|
||||
\endverbatim
|
||||
*
|
||||
* fix the style of a single file:
|
||||
\verbatim
|
||||
./check-style.py --level=2 --in-place -f src/core/object.h
|
||||
\endverbatim
|
||||
*
|
||||
* look at the changes needed for a single file:
|
||||
\verbatim
|
||||
./check-style.py --diff --level=1 -f src/core/object.h | less
|
||||
\endverbatim
|
||||
*
|
||||
* look at the status of all files modified in your mercurial repository:
|
||||
\verbatim
|
||||
./check-style.py --check-hg
|
||||
\endverbatim
|
||||
*
|
||||
* look at the changes needed for all modified files in your mercurial
|
||||
* repository:
|
||||
\verbatim
|
||||
./check-style.py --check-hg --diff |less
|
||||
\endverbatim
|
||||
*
|
||||
* Enable this script to run as a 'commit' hook in your repository and
|
||||
* disallow commits which contain files with invalid style:
|
||||
*
|
||||
\verbatim
|
||||
cat hgrc (can be appended to .hg/hgrc or ~/.hg/hgrc or /etc/hg/hgrc
|
||||
[hooks]
|
||||
# uncomment below line to enable: works only with mercurial >= 1.3
|
||||
#pretxncommit.indent = path-to-binary/check-indent.py --check-hg-hook
|
||||
# uncomment below line to enable: works with all (?) versions
|
||||
# of mercurial but requires that PYTHONPATH is defined to point to
|
||||
# the directory which contains check-indent.py
|
||||
#pretxncommit.indent = python:check-indent.run_as_hg_hook
|
||||
\endverbatim
|
||||
*
|
||||
* Usage:
|
||||
\verbatim
|
||||
Usage: check-style.py [options]
|
||||
|
||||
Options:
|
||||
-h, --help show this help message and exit
|
||||
--debug Output some debugging information
|
||||
-l LEVEL, --level=LEVEL
|
||||
Level of style conformance: higher levels include all
|
||||
lower levels. level=0: re-indent only. level=1: add
|
||||
extra spaces. level=2: insert extra newlines and extra
|
||||
braces around single-line statements. level=3: remove
|
||||
all trailing spaces
|
||||
--check-hg-hook Get the list of files to check from mercurial's list
|
||||
of modified and added files and assume that the script
|
||||
runs as a pretxncommit mercurial hook
|
||||
--check-hg Get the list of files to check from mercurial's list
|
||||
of modified and added files
|
||||
-f FILE, --check-file=FILE
|
||||
Check a single file
|
||||
--diff Generate a diff on stdout of the indented files
|
||||
-i, --in-place Indent the input files in-place
|
||||
\endverbatim
|
||||
*/
|
||||
Reference in New Issue
Block a user