-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathadd_comments_and_docstrings.py
More file actions
executable file
·391 lines (305 loc) · 13.8 KB
/
add_comments_and_docstrings.py
File metadata and controls
executable file
·391 lines (305 loc) · 13.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
#!/usr/bin/env python3
"""
Copyright (C) 2025 boardGPT Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
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, see <https://www.gnu.org/licenses/>.
"""
"""
Script to add GPLv3 license headers, docstrings, and end block comments to Python files.
This script processes all Python files in the boardGPT repository, adding:
1. GPLv3 license header to each file
2. File docstrings if missing
3. End block comments for control structures (if, for, while, def, class, etc.)
"""
import os
import re
import sys
import ast
import tokenize
from io import StringIO
from typing import List, Dict, Tuple, Set, Optional
# GPLv3 license header template
GPL_LICENSE = '''"""
Copyright (C) 2025 boardGPT Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
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, see <https://www.gnu.org/licenses/>.
"""
'''
# File docstring template
FILE_DOCSTRING_TEMPLATE = '''"""
{file_description}
This module provides {module_functionality}.
"""
'''
def find_python_files(directory: str) -> List[str]:
"""
Find all Python files in the given directory and its subdirectories.
Args:
directory (str): The directory to search in
Returns:
List[str]: List of paths to Python files
"""
python_files = []
for root, _, files in os.walk(directory):
for file in files:
if file.endswith('.py'):
python_files.append(os.path.join(root, file))
return python_files
# end def find_python_files
def has_license_header(content: str) -> bool:
"""
Check if the file already has a license header.
Args:
content (str): The content of the file
Returns:
bool: True if the file has a license header, False otherwise
"""
return "Copyright (C)" in content and "GNU General Public License" in content
# end def has_license_header
def has_file_docstring(content: str) -> bool:
"""
Check if the file already has a file docstring.
Args:
content (str): The content of the file
Returns:
bool: True if the file has a file docstring, False otherwise
"""
try:
tree = ast.parse(content)
return (ast.get_docstring(tree) is not None)
except SyntaxError:
return False
# end def has_file_docstring
def add_license_and_docstring(content: str, filename: str) -> str:
"""
Add license header and file docstring to the file content if they don't exist.
Args:
content (str): The content of the file
filename (str): The name of the file
Returns:
str: The updated content
"""
# Remove empty lines at the beginning of the file
content = content.lstrip()
# Add license header if it doesn't exist
if not has_license_header(content):
content = GPL_LICENSE + "\n" + content
# Add file docstring if it doesn't exist
if not has_file_docstring(content):
# Extract module name from filename
module_name = os.path.basename(filename).replace('.py', '')
# Create a simple file description and functionality
file_description = f"{module_name.replace('_', ' ').title()} module"
module_functionality = f"functionality related to {module_name.replace('_', ' ')}"
# Create the docstring
file_docstring = FILE_DOCSTRING_TEMPLATE.format(
file_description=file_description,
module_functionality=module_functionality
)
# Find the position to insert the docstring (after license header if it exists)
if '"""' in content:
# Find the end of the first docstring (which should be the license header)
end_of_license = content.find('"""', content.find('"""') + 3) + 3
content = content[:end_of_license] + "\n\n" + file_docstring + content[end_of_license:]
else:
content = file_docstring + "\n" + content
return content
# end def add_license_and_docstring
def add_end_block_comments(content: str) -> str:
"""
Add end block comments to control structures (if, for, while, def, class, etc.).
Args:
content (str): The content of the file
Returns:
str: The updated content with end block comments
"""
lines = content.split('\n')
result_lines = []
# Stack to keep track of indentation levels and block types
stack = [] # (indentation_level, block_type, block_name)
for i, line in enumerate(lines):
# Skip empty lines
if not line.strip():
result_lines.append(line)
continue
# Calculate indentation level
indentation = len(line) - len(line.lstrip())
# Check if this line closes any blocks
while stack and indentation <= stack[-1][0]:
indent_level, block_type, block_name = stack.pop()
# Don't add end comment if the previous line already has one
prev_line = result_lines[-1].strip()
if not prev_line.startswith('# end'):
# Add the end block comment to the previous line
if block_type == 'def' or block_type == 'class':
end_comment = f"# end {block_type} {block_name}"
else:
end_comment = f"# end {block_type}"
# If the previous line is not empty, add the comment
if prev_line:
result_lines[-1] = result_lines[-1] + " " + end_comment
else:
# If the previous line is empty, add the comment with proper indentation
result_lines[-1] = ' ' * indent_level + end_comment
# Check if this line starts a new block
stripped_line = line.strip()
# Check for function definitions
if stripped_line.startswith('def '):
# Extract function name
match = re.match(r'def\s+([a-zA-Z0-9_]+)', stripped_line)
if match:
function_name = match.group(1)
stack.append((indentation, 'def', function_name))
# Check for class definitions
elif stripped_line.startswith('class '):
# Extract class name
match = re.match(r'class\s+([a-zA-Z0-9_]+)', stripped_line)
if match:
class_name = match.group(1)
stack.append((indentation, 'class', class_name))
# Check for if statements
elif stripped_line.startswith('if ') and stripped_line.endswith(':'):
stack.append((indentation, 'if', ''))
# Check for elif statements
elif stripped_line.startswith('elif ') and stripped_line.endswith(':'):
# Close the previous if/elif block
if stack and stack[-1][1] in ('if', 'elif'):
indent_level, block_type, _ = stack.pop()
# Don't add end comment if the previous line already has one
prev_line = result_lines[-1].strip()
if not prev_line.startswith('# end'):
end_comment = f"# end {block_type}"
if prev_line:
result_lines[-1] = result_lines[-1] + " " + end_comment
else:
result_lines[-1] = ' ' * indent_level + end_comment
stack.append((indentation, 'elif', ''))
# Check for else statements
elif stripped_line.startswith('else:'):
# Close the previous if/elif block
if stack and stack[-1][1] in ('if', 'elif'):
indent_level, block_type, _ = stack.pop()
# Don't add end comment if the previous line already has one
prev_line = result_lines[-1].strip()
if not prev_line.startswith('# end'):
end_comment = f"# end {block_type}"
if prev_line:
result_lines[-1] = result_lines[-1] + " " + end_comment
else:
result_lines[-1] = ' ' * indent_level + end_comment
stack.append((indentation, 'else', ''))
# Check for for loops
elif stripped_line.startswith('for ') and stripped_line.endswith(':'):
stack.append((indentation, 'for', ''))
# Check for while loops
elif stripped_line.startswith('while ') and stripped_line.endswith(':'):
stack.append((indentation, 'while', ''))
# Check for try blocks
elif stripped_line == 'try:':
stack.append((indentation, 'try', ''))
# Check for except blocks
elif stripped_line.startswith('except ') and stripped_line.endswith(':'):
# Close the previous try/except block
if stack and stack[-1][1] in ('try', 'except'):
indent_level, block_type, _ = stack.pop()
# Don't add end comment if the previous line already has one
prev_line = result_lines[-1].strip()
if not prev_line.startswith('# end'):
end_comment = f"# end {block_type}"
if prev_line:
result_lines[-1] = result_lines[-1] + " " + end_comment
else:
result_lines[-1] = ' ' * indent_level + end_comment
stack.append((indentation, 'except', ''))
# Check for finally blocks
elif stripped_line == 'finally:':
# Close the previous try/except block
if stack and stack[-1][1] in ('try', 'except'):
indent_level, block_type, _ = stack.pop()
# Don't add end comment if the previous line already has one
prev_line = result_lines[-1].strip()
if not prev_line.startswith('# end'):
end_comment = f"# end {block_type}"
if prev_line:
result_lines[-1] = result_lines[-1] + " " + end_comment
else:
result_lines[-1] = ' ' * indent_level + end_comment
stack.append((indentation, 'finally', ''))
# Check for with blocks
elif stripped_line.startswith('with ') and stripped_line.endswith(':'):
stack.append((indentation, 'with', ''))
result_lines.append(line)
# Close any remaining blocks
while stack:
indent_level, block_type, block_name = stack.pop()
# Add the end block comment to the last line
if block_type == 'def' or block_type == 'class':
end_comment = f"# end {block_type} {block_name}"
else:
end_comment = f"# end {block_type}"
# If the last line is not empty, add the comment
if result_lines[-1].strip():
result_lines[-1] = result_lines[-1] + " " + end_comment
else:
# If the last line is empty, add the comment with proper indentation
result_lines[-1] = ' ' * indent_level + end_comment
return '\n'.join(result_lines)
# end def add_end_block_comments
def process_file(file_path: str) -> None:
"""
Process a single Python file to add license header, docstrings, and end block comments.
Args:
file_path (str): Path to the Python file
"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Add license header and file docstring
updated_content = add_license_and_docstring(content, file_path)
# Add end block comments
updated_content = add_end_block_comments(updated_content)
# Write the updated content back to the file
with open(file_path, 'w', encoding='utf-8') as f:
f.write(updated_content)
print(f"Processed: {file_path}")
except Exception as e:
print(f"Error processing {file_path}: {e}")
# end def process_file
def main() -> None:
"""
Main function to process all Python files in the boardGPT repository.
"""
# Get the directory containing this script
script_dir = os.path.dirname(os.path.abspath(__file__))
# Find the boardGPT directory (assuming this script is in the repository root)
boardgpt_dir = os.path.join(script_dir, 'boardGPT')
if not os.path.isdir(boardgpt_dir):
print(f"Error: boardGPT directory not found at {boardgpt_dir}")
sys.exit(1)
# Find all Python files in the boardGPT directory
python_files = find_python_files(boardgpt_dir)
print(f"Found {len(python_files)} Python files to process")
# Process each file
for file_path in python_files:
process_file(file_path)
print("Done!")
# end def main
if __name__ == "__main__":
main()
# end if