A Discrete-Event Network Simulator
API
trim-trailing-whitespace.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2
3# Copyright (c) 2022 Eduardo Almeida.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 2 as
7# published by the Free Software Foundation;
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17#
18# Author: Eduardo Almeida [@edalm] [INESC TEC and FEUP]
19
20"""
21Check and trim trailing whitespace in files indicated in the PATH argument.
22This script can be applied to all text files in a given path or to
23individual files.
24"""
25
26import argparse
27import os
28import sys
29
30FILE_EXTENSIONS_TO_CHECK = [
31 '.c',
32 '.cc',
33 '.click',
34 '.conf',
35 '.css',
36 '.dot',
37 '.gnuplot',
38 '.gp',
39 '.h',
40 '.html',
41 '.js',
42 '.json',
43 '.m',
44 '.md',
45 '.mob',
46 '.ns_params',
47 '.ns_movements',
48 '.params',
49 '.pl',
50 '.plt',
51 '.py',
52 '.rst',
53 '.seqdiag',
54 '.sh',
55 '.txt',
56 '.yml',
57]
58
59FILES_TO_CHECK = [
60 'Makefile',
61 'ns3',
62]
63
64DIRECTORIES_TO_SKIP = [
65 'bindings',
66 'build',
67 'cmake-cache',
68]
69
70def skip_file(filename: str) -> bool:
71 """
72 Check if a file should be skipped.
73
74 @param filename Name of the file.
75 @return Whether the directory should be skipped or not.
76 """
77
78 basename, extension = os.path.splitext(os.path.split(filename)[1])
79
80 return basename not in FILES_TO_CHECK and \
81 extension not in FILE_EXTENSIONS_TO_CHECK
82
83def skip_directory(dirpath: str) -> bool:
84 """
85 Check if a directory should be skipped.
86
87 @param dirpath Directory path.
88 @return Whether the directory should be skipped or not.
89 """
90
91 _, directory = os.path.split(dirpath)
92
93 return (directory.startswith('.') and directory != '.') or \
94 directory in DIRECTORIES_TO_SKIP
95
96def trim_trailing_whitespace(path: str, trim: bool) -> None:
97 """
98 Trim trailing whitespace in all text files in the given path.
99
100 @param path Path to the files.
101 @param trim Whether to trim the file (True) or
102 just check if the file has trailing whitespace (False).
103 """
104
105 files_with_trailing_whitespace = []
106
107 abs_path = os.path.normpath(os.path.abspath(os.path.expanduser(path)))
108
109 if os.path.isfile(abs_path):
110
111 if not skip_file(abs_path):
112 file_had_whitespace = trim_file(abs_path, trim)
113
114 if file_had_whitespace:
115 files_with_trailing_whitespace.append(path)
116
117 elif os.path.isdir(abs_path):
118
119 for dirpath, dirnames, filenames in os.walk(path, topdown=True):
120
121 # Check if directory and its subdirectories should be skipped
122 if skip_directory(dirpath):
123 dirnames[:] = []
124 continue
125
126 # Process files with trailing whitespace
127 filenames = [os.path.join(dirpath, f) for f in filenames]
128
129 for filename in filenames:
130
131 # Skip files that should not be checked
132 if skip_file(filename):
133 continue
134
135 file_had_whitespace = trim_file(
136 os.path.normpath(os.path.abspath(os.path.expanduser(filename))),
137 trim,
138 )
139
140 if file_had_whitespace:
141 files_with_trailing_whitespace.append(filename)
142
143 else:
144 print(f'Error: {path} is not a file nor a directory')
145 sys.exit(1)
146
147 # Output results
148 n_files_with_trailing_whitespace = len(files_with_trailing_whitespace)
149
150 if files_with_trailing_whitespace:
151 if trim:
152 print('Trimmed files with trailing whitespace:\n')
153 else:
154 print('Detected files with trailing whitespace:\n')
155
156 for f in files_with_trailing_whitespace:
157 print(f'- {f}')
158
159 if trim:
160 print(f'\n'
161 f'Number of files trimmed: {n_files_with_trailing_whitespace}')
162 else:
163 print(f'\n'
164 f'Number of files with trailing whitespace: {n_files_with_trailing_whitespace}')
165 sys.exit(1)
166
167 else:
168 print('No files detected with trailing whitespace')
169
170def trim_file(filename: str, trim: bool) -> bool:
171 """
172 Trim trailing whitespace in a given file.
173
174 @param filename Name of the file to be trimmed.
175 @param trim Whether to trim the file (True) or
176 just check if the file has trailing whitespace (False).
177 @return Whether the file had trailing whitespace (True) or not (False).
178 """
179
180 has_trailing_whitespace = False
181
182 try:
183 with open(filename, 'r') as f:
184 file_lines = f.readlines()
185
186 # Check if there are trailing whitespace and trim them
187 for (i, line) in enumerate(file_lines):
188 line_trimmed = line.rstrip() + '\n'
189
190 if line_trimmed != line:
191 has_trailing_whitespace = True
192
193 # Optimization: if only checking, skip the rest of the file,
194 # since it does have trailing whitespace
195 if not trim:
196 break
197
198 file_lines[i] = line_trimmed
199
200 # Update the file with the trimmed lines
201 if trim and has_trailing_whitespace:
202 with open(filename, 'w') as f:
203 f.writelines(file_lines)
204
205 except Exception as e:
206 print(e)
207 sys.exit(1)
208
209 return has_trailing_whitespace
210
211if __name__ == '__main__':
212
213 parser = argparse.ArgumentParser(description=
214 'Trim trailing whitespace in all text files in a given PATH.')
215
216 parser.add_argument('path', action='store', type=str, help=
217 'Path to the files.')
218
219 parser.add_argument('--check', action='store_true', help=
220 'Check if the files have trailing whitespace, without modifying them. '
221 'If they have, this process will exit with a non-zero exit code '
222 'and list them in the output.')
223
224 args = parser.parse_args()
225
226 trim_trailing_whitespace(args.path, trim=(not args.check))
None trim_trailing_whitespace(str path, bool trim)
bool trim_file(str filename, bool trim)