Skip to content
Navigation Menu
{{ message }}
forked from foxalabs/all_code
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathall_code.py
More file actions
303 lines (231 loc) · 11.4 KB
/
Copy pathall_code.py
File metadata and controls
303 lines (231 loc) · 11.4 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
# Copyright 2024 Spencer Bentley
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”),
# to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
import os
import argparse
import sys
import subprocess
# Define the master file name
FULL_CODE_FILE_NAME = "full_code.txt"
FILES_TO_INCLUDE = {} # if empty, include all files
EXCLUDE_EXTENSIONS = set() # User-defined extensions to exclude
# FILES_TO_INCLUDE = {
# 'some_file.py',
# 'another_file.js',
# }
# Define programming-related file extensions (removed '.json' and '.md')
PROGRAMMING_EXTENSIONS = {
# General Programming Languages
'.py', '.java', '.c', '.cpp', '.h', '.hpp', '.cs', '.vb', '.r',
'.rb', '.go', '.php', '.swift', '.kt', '.rs', '.scala', '.pl', '.lua', '.jl'
# Web Development
'.js', '.jsx', '.ts', '.tsx', '.html', '.css', '.scss', '.less', '.sass',
# Shell & Automation
'.sh', '.zsh', '.fish', '.ps1', '.bat', '.cmd',
# Database & Query Languages
'.sql', '.psql', '.db', '.sqlite',
# Markup & Config Files
'.xml', '.json', '.toml', '.ini', '.yml', '.yaml', '.md', '.rst',
# Build & Make Systems
'.Makefile', '.gradle', '.cmake', '.ninja',
# Other
'.pqm', '.pq'
}
# Define directories to exclude during file aggregation and directory tree generation
EXCLUDE_DIRS = {
'venv',
'node_modules',
'__pycache__',
'.git',
'dist',
'build',
'temp',
'old_files',
'flask_session'
}
# Define the name of this script to exclude it
SCRIPT_NAME = os.path.basename(__file__)
# Define files to exclude from both aggregation and directory tree
EXCLUDE_FILES = {SCRIPT_NAME, 'package-lock.json', 'package.json', 'temp.py'}
def generate_directory_tree(startpath):
# Generates an ASCII directory tree.
# - Excluded directories and their subdirectories are only listed by name without their internal files.
# - The script itself is excluded from the tree.
tree = ""
for root, dirs, files in os.walk(startpath):
# Determine the relative path from the startpath
rel_path = os.path.relpath(root, startpath)
if rel_path == '.':
rel_path = ''
# Split the relative path into parts
path_parts = rel_path.split(os.sep) if rel_path else []
# Calculate the level of depth
level = len(path_parts)
indent = '│ ' * level + '├── ' if level > 0 else ''
# Add the current directory to the tree
current_dir = os.path.basename(root) if rel_path else os.path.basename(
startpath.rstrip(os.sep)) or startpath
# Check if the current directory is excluded
if current_dir in EXCLUDE_DIRS:
tree += f"{indent}{current_dir}/ [EXCLUDED]\n"
dirs[:] = [] # Prevent os.walk from traversing further
continue
tree += f"{indent}{current_dir}/\n"
# List files, excluding the script itself and any in EXCLUDE_FILES
for f in files:
if f in EXCLUDE_FILES:
continue # Skip excluded files
tree += f"{'│ ' * (level + 1)}├── {f}\n"
return tree
def is_programming_file(filename):
"""Checks if a file has a programming-related extension and is not in the exclude list."""
_, ext = os.path.splitext(filename)
ext = ext.lower()
return ext in PROGRAMMING_EXTENSIONS and ext not in EXCLUDE_EXTENSIONS
def should_exclude(path):
# Determines if a file should be excluded based on its path.
# - Excludes files in EXCLUDE_DIRS and their subdirectories.
# - Excludes files listed in EXCLUDE_FILES.
# Normalize path separators
normalized_path = os.path.normpath(path)
parts = normalized_path.split(os.sep)
# Check if any part of the path is in the EXCLUDE_DIRS
for part in parts[:-1]: # Exclude the file name itself
if part in EXCLUDE_DIRS:
return True
# Check if the file itself is in EXCLUDE_FILES
if parts[-1] in EXCLUDE_FILES:
return True
return False
def should_include_file(file_path):
# Determines if a file should be included based on FILES_TO_INCLUDE.
# If FILES_TO_INCLUDE is empty, include all files.
if not FILES_TO_INCLUDE:
return True # Include all files if the list is empty
rel_file_path = os.path.relpath(file_path)
return rel_file_path in FILES_TO_INCLUDE
def parse_arguments():
"""
Parses command-line arguments.
"""
parser = argparse.ArgumentParser(
description="Aggregate code files into a master file with a directory tree.")
parser.add_argument('-c', '--clipboard', action='store_true',
help="Copy the aggregated content to the clipboard instead of writing to a file.")
parser.add_argument('-d', '--directory', type=str, default=os.getcwd(),
help="Specify the directory to start aggregation from. Defaults to the current working directory.")
parser.add_argument('-o', '--output-file', type=str, default=FULL_CODE_FILE_NAME,
help="Name of the output file. Defaults to full_code.txt.")
parser.add_argument('-i', '--include-files', type=str, default="",
help="Comma-separated list of files to include. If not provided, all files are included.")
parser.add_argument('-x', '--extensions', type=str, default="",
help="Comma-separated list of programming extensions to use. Replaces the default set if provided.")
parser.add_argument('-e', '--exclude-dirs', type=str, default="",
help="Comma-separated list of directories to exclude. Replaces the default set if provided.")
parser.add_argument('-X', '--exclude-extensions', type=str, default="",
help="Comma-separated list of file extensions to exclude.")
return parser.parse_args()
# TODO: Works on Macos. Needs Windows and Linux support
def copy_to_clipboard(content):
try:
process = subprocess.Popen('pbcopy', env={'LANG': 'en_US.UTF-8'}, stdin=subprocess.PIPE)
process.communicate(content.encode('utf-8'))
return True
except Exception as e:
print(f"Error copying to clipboard: {e}")
return False
def main():
args = parse_arguments()
# Override the global options if command line arguments are provided.
global FULL_CODE_FILE_NAME, FILES_TO_INCLUDE, PROGRAMMING_EXTENSIONS, EXCLUDE_DIRS, EXCLUDE_EXTENSIONS
if args.output_file:
FULL_CODE_FILE_NAME = args.output_file
if args.include_files:
# Split the comma-separated string and remove any extra whitespace.
FILES_TO_INCLUDE = {f.strip() for f in args.include_files.split(',') if f.strip()}
if args.extensions:
PROGRAMMING_EXTENSIONS = {ext.strip() for ext in args.extensions.split(',') if ext.strip()}
if args.exclude_dirs:
EXCLUDE_DIRS = {d.strip() for d in args.exclude_dirs.split(',') if d.strip()}
if args.exclude_extensions:
EXCLUDE_EXTENSIONS = {ext.strip() for ext in args.exclude_extensions.split(',') if ext.strip()}
# Debugging print statement to verify exclusions
print(f"Excluding extensions: {EXCLUDE_EXTENSIONS}")
startpath = args.directory
if not os.path.isdir(startpath):
print(f"Error: The specified directory '{startpath}' does not exist or is not a directory.")
sys.exit(1)
if not os.path.isdir(startpath):
print(f"Error: The specified directory '{startpath}' does not exist or is not a directory.")
sys.exit(1)
# Generate directory tree
directory_tree = generate_directory_tree(startpath)
aggregated_content = "Directory Tree:\n" + directory_tree + "\n\n"
# Traverse the directory again to process files
for root, dirs, files in os.walk(startpath):
# Determine the relative path from the startpath
rel_path = os.path.relpath(root, startpath)
if rel_path == '.':
rel_path = ''
# Split the relative path into parts
path_parts = rel_path.split(os.sep) if rel_path else []
# Check if any parent directory is excluded
if any(part in EXCLUDE_DIRS for part in path_parts):
# Skip processing this directory and its subdirectories
dirs[:] = [] # Prevent os.walk from traversing further
continue
# Check if the current directory is excluded
current_dir = os.path.basename(root) if rel_path else os.path.basename(
startpath.rstrip(os.sep)) or startpath
if current_dir in EXCLUDE_DIRS:
dirs[:] = [] # Prevent os.walk from traversing further
continue
for file in files:
file_path = os.path.join(root, file)
# Get file extension
_, ext = os.path.splitext(file)
ext = ext.lower()
# Skip files with excluded extensions
if ext in EXCLUDE_EXTENSIONS:
continue
# Skip non-programming files
if not is_programming_file(file):
continue
# Get relative path for exclusion and headers
rel_file_path = os.path.relpath(file_path, startpath)
if should_exclude(rel_file_path) or not should_include_file(file_path):
continue # Skip excluded files or those not in FILES_TO_INCLUDE
header = f"\n\n# ======================\n# File: {rel_file_path}\n# ======================\n\n"
aggregated_content += header
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
aggregated_content += content
except Exception as e:
error_msg = f"\n# Error reading file {rel_file_path}: {e}\n"
aggregated_content += error_msg
if args.clipboard:
# Copy the aggregated content to the clipboard
success = copy_to_clipboard(aggregated_content)
if success:
print("Aggregated content has been copied to the clipboard successfully.")
else:
print("Failed to copy aggregated content to the clipboard.")
sys.exit(1)
else:
# Write the aggregated content to the master file
try:
with open(FULL_CODE_FILE_NAME, 'w', encoding='utf-8') as master_file:
master_file.write(aggregated_content)
print(f"Full code file '{FULL_CODE_FILE_NAME}' has been created successfully.")
except Exception as e:
print(f"Error writing to file '{FULL_CODE_FILE_NAME}': {e}")
sys.exit(1)
if __name__ == "__main__":
main()
You can’t perform that action at this time.
