codefile 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codefile
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A CLI tool to bundle entire projects into a single AI-friendly context file.
|
|
5
|
+
Author: Anthroberc
|
|
6
|
+
Keywords: ai,gemini,codefile,llm,context,prompt-engineering,bundler,devtools,chatgpt,claude
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
codefile – Python Project Packager
|
|
11
|
+
|
|
12
|
+
=====================================
|
|
13
|
+
What Is codefile?
|
|
14
|
+
=====================================
|
|
15
|
+
|
|
16
|
+
codefile is a Python package that scans your project directory
|
|
17
|
+
and packs everything into a single output file named:
|
|
18
|
+
|
|
19
|
+
CodeFile
|
|
20
|
+
|
|
21
|
+
It includes:
|
|
22
|
+
- Project folder structure (tree view)
|
|
23
|
+
- File metadata (SHA-256 ID)
|
|
24
|
+
- File contents
|
|
25
|
+
- Total size (in MB)
|
|
26
|
+
- UTC creation timestamp
|
|
27
|
+
|
|
28
|
+
This is useful for:
|
|
29
|
+
- Creating project snapshots
|
|
30
|
+
- Sharing full source code as one file
|
|
31
|
+
- Simple archiving
|
|
32
|
+
- Backup purposes
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
=====================================
|
|
36
|
+
Installation
|
|
37
|
+
=====================================
|
|
38
|
+
|
|
39
|
+
If installed from source:
|
|
40
|
+
|
|
41
|
+
pip install .
|
|
42
|
+
|
|
43
|
+
installing a package:
|
|
44
|
+
|
|
45
|
+
pip install codefile
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
=====================================
|
|
49
|
+
How To Use
|
|
50
|
+
=====================================
|
|
51
|
+
|
|
52
|
+
Step 1:
|
|
53
|
+
Open terminal inside your project folder.
|
|
54
|
+
|
|
55
|
+
Step 2:
|
|
56
|
+
Run:
|
|
57
|
+
|
|
58
|
+
codefile
|
|
59
|
+
|
|
60
|
+
Step 3:
|
|
61
|
+
After execution, a new file will appear:
|
|
62
|
+
|
|
63
|
+
CodeFile
|
|
64
|
+
|
|
65
|
+
That file contains your full project snapshot.
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
=====================================
|
|
69
|
+
What Gets Packed?
|
|
70
|
+
=====================================
|
|
71
|
+
|
|
72
|
+
✔ All regular files in the current directory
|
|
73
|
+
✔ Subdirectories
|
|
74
|
+
✔ File contents
|
|
75
|
+
✔ Directory structure
|
|
76
|
+
|
|
77
|
+
Automatically ignored:
|
|
78
|
+
- .git folder
|
|
79
|
+
- The generated CodeFile
|
|
80
|
+
- The package script itself
|
|
81
|
+
- Patterns listed in .gitignore (if present)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
=====================================
|
|
85
|
+
Output File Structure
|
|
86
|
+
=====================================
|
|
87
|
+
|
|
88
|
+
The generated file contains:
|
|
89
|
+
|
|
90
|
+
ROOT_NAME <project_name>
|
|
91
|
+
CREATED_UTC <timestamp>
|
|
92
|
+
TOTAL_SIZE_MB <size>
|
|
93
|
+
|
|
94
|
+
START_STRUCTURE
|
|
95
|
+
<folder tree>
|
|
96
|
+
END_STRUCTURE
|
|
97
|
+
|
|
98
|
+
FILE_START <relative_path> <file_id>
|
|
99
|
+
<file content>
|
|
100
|
+
FILE_END <relative_path> <file_id>
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
=====================================
|
|
104
|
+
Technical Details
|
|
105
|
+
=====================================
|
|
106
|
+
|
|
107
|
+
- Uses SHA-256 hashing (first 12 characters as ID)
|
|
108
|
+
- Reads files in chunks (memory safe)
|
|
109
|
+
- Skips unreadable or broken files safely
|
|
110
|
+
- Writes output using UTF-8 encoding
|
|
111
|
+
- Handles errors without crashing
|
|
112
|
+
|
|
113
|
+
=====================================
|
|
114
|
+
Requirements
|
|
115
|
+
=====================================
|
|
116
|
+
|
|
117
|
+
- Python 3.8+
|
|
118
|
+
- No external dependencies (uses standard library only)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
codefile.py,sha256=haYiYlKWLakguqivigOSAzPnoI7j5SfntqNhuoW6PeY,5423
|
|
2
|
+
codefile-1.0.0.dist-info/METADATA,sha256=eiZjpYNw-ZWu3i-bjmDLGf2IUY824l1KSUKy8ondudc,2457
|
|
3
|
+
codefile-1.0.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
4
|
+
codefile-1.0.0.dist-info/entry_points.txt,sha256=gF--gx0EZLvgBvHx5-XzaCf1vu7qlYkAhSRZ8dNmPwY,43
|
|
5
|
+
codefile-1.0.0.dist-info/top_level.txt,sha256=v3___6J6zX4HRyt39z6KOtx1ZIPPuoyHrRHtFW_T7Ew,9
|
|
6
|
+
codefile-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
codefile
|
codefile.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import hashlib
|
|
3
|
+
import datetime
|
|
4
|
+
import fnmatch
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
def main():
|
|
8
|
+
IO_CHUNK = 65536
|
|
9
|
+
OUT_NAME = "CodeFile"
|
|
10
|
+
SCRIPT_NAME = Path(__file__).name if '__file__' in globals() else "packager.py"
|
|
11
|
+
|
|
12
|
+
def get_ignore_patterns(root):
|
|
13
|
+
patterns = {OUT_NAME, SCRIPT_NAME, ".git", ".git/"}
|
|
14
|
+
gitignore = root / ".gitignore"
|
|
15
|
+
try:
|
|
16
|
+
if gitignore.is_file():
|
|
17
|
+
with gitignore.open("r", encoding="utf-8", errors="ignore") as f:
|
|
18
|
+
for line in f:
|
|
19
|
+
line = line.strip()
|
|
20
|
+
if line and not line.startswith("#"):
|
|
21
|
+
patterns.add(line)
|
|
22
|
+
except Exception as e:
|
|
23
|
+
print(f"Warning: Failed reading .gitignore: {e}")
|
|
24
|
+
return list(patterns)
|
|
25
|
+
|
|
26
|
+
def is_ignored(path, root, patterns):
|
|
27
|
+
try:
|
|
28
|
+
rel_path = path.relative_to(root).as_posix()
|
|
29
|
+
parts = rel_path.split('/')
|
|
30
|
+
for pattern in patterns:
|
|
31
|
+
clean_pattern = pattern.rstrip('/')
|
|
32
|
+
for part in parts:
|
|
33
|
+
if fnmatch.fnmatch(part, clean_pattern):
|
|
34
|
+
return True
|
|
35
|
+
if fnmatch.fnmatch(rel_path, pattern):
|
|
36
|
+
return True
|
|
37
|
+
except ValueError:
|
|
38
|
+
return True # If it fails relative resolution (e.g., weird symlink), ignore it
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
def build_visual_tree(root, all_relative_paths):
|
|
42
|
+
tree = {}
|
|
43
|
+
for path_str in sorted(all_relative_paths):
|
|
44
|
+
current = tree
|
|
45
|
+
for part in path_str.split('/'):
|
|
46
|
+
current = current.setdefault(part, {})
|
|
47
|
+
|
|
48
|
+
lines = [f"{root.name}/"]
|
|
49
|
+
def walk(node, prefix=""):
|
|
50
|
+
items = sorted(node.items(), key=lambda x: (not x[1], x[0]))
|
|
51
|
+
for i, (name, children) in enumerate(items):
|
|
52
|
+
is_last = (i == len(items) - 1)
|
|
53
|
+
connector = "\\-- " if is_last else "|-- "
|
|
54
|
+
lines.append(f"{prefix}{connector}{name}{'/' if children else ''}")
|
|
55
|
+
if children:
|
|
56
|
+
walk(children, prefix + (" " if is_last else "| "))
|
|
57
|
+
walk(tree)
|
|
58
|
+
return "\n".join(lines)
|
|
59
|
+
|
|
60
|
+
# 1. Resolve Environment Safely
|
|
61
|
+
try:
|
|
62
|
+
root = Path.cwd().resolve()
|
|
63
|
+
except Exception as e:
|
|
64
|
+
return print(f"Fatal Error: Cannot resolve current directory: {e}")
|
|
65
|
+
|
|
66
|
+
if not root.is_dir():
|
|
67
|
+
return print(f"Fatal Error: {root} is not a valid directory.")
|
|
68
|
+
|
|
69
|
+
patterns = get_ignore_patterns(root)
|
|
70
|
+
all_rel_paths = []
|
|
71
|
+
files_to_write = []
|
|
72
|
+
total_bytes = 0
|
|
73
|
+
|
|
74
|
+
print(f"Scanning directory: {root.name} ...")
|
|
75
|
+
|
|
76
|
+
# 2. Safely Walk Directory
|
|
77
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
78
|
+
try:
|
|
79
|
+
dp = Path(dirpath)
|
|
80
|
+
|
|
81
|
+
# Instantly skip git folder traversal
|
|
82
|
+
if ".git" in dp.parts:
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
for f in filenames:
|
|
86
|
+
p = dp / f
|
|
87
|
+
try:
|
|
88
|
+
if not p.is_file():
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
rel_path = p.relative_to(root).as_posix()
|
|
92
|
+
all_rel_paths.append(rel_path)
|
|
93
|
+
|
|
94
|
+
if not is_ignored(p, root, patterns):
|
|
95
|
+
total_bytes += p.stat().st_size
|
|
96
|
+
files_to_write.append(p)
|
|
97
|
+
except (OSError, ValueError):
|
|
98
|
+
continue # Skip unreadable files or broken symlinks
|
|
99
|
+
except Exception:
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
print(f"Found {len(files_to_write)} files to pack. Building structure...")
|
|
103
|
+
|
|
104
|
+
# 3. Safely Write Output
|
|
105
|
+
try:
|
|
106
|
+
tree_str = build_visual_tree(root, all_rel_paths)
|
|
107
|
+
timestamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
108
|
+
out_file = root / OUT_NAME
|
|
109
|
+
|
|
110
|
+
with out_file.open("w", encoding="utf-8", errors="replace", newline='\n') as out:
|
|
111
|
+
out.write(f"ROOT_NAME {root.name}\n")
|
|
112
|
+
out.write(f"CREATED_UTC {timestamp}\n")
|
|
113
|
+
out.write(f"TOTAL_SIZE_MB {total_bytes / (1024*1024):.2f}\n\n")
|
|
114
|
+
out.write("START_STRUCTURE\n" + tree_str + "\nEND_STRUCTURE\n\n")
|
|
115
|
+
|
|
116
|
+
for f_path in files_to_write:
|
|
117
|
+
try:
|
|
118
|
+
rel = f_path.relative_to(root).as_posix()
|
|
119
|
+
h = hashlib.sha256()
|
|
120
|
+
|
|
121
|
+
# Read 1: Hash Calculation
|
|
122
|
+
with f_path.open("rb") as rb:
|
|
123
|
+
while chunk := rb.read(IO_CHUNK):
|
|
124
|
+
h.update(chunk)
|
|
125
|
+
f_id = h.hexdigest()[:12]
|
|
126
|
+
|
|
127
|
+
# Read 2: Content Writing
|
|
128
|
+
out.write(f"FILE_START {rel} {f_id}\n")
|
|
129
|
+
with f_path.open("rb") as rb:
|
|
130
|
+
while chunk := rb.read(IO_CHUNK):
|
|
131
|
+
out.write(chunk.decode("utf-8", errors="replace"))
|
|
132
|
+
out.write(f"\nFILE_END {rel} {f_id}\n\n")
|
|
133
|
+
|
|
134
|
+
except Exception as e:
|
|
135
|
+
print(f"Warning: Skipped reading content of {f_path.name} ({e})")
|
|
136
|
+
continue
|
|
137
|
+
|
|
138
|
+
print(f"Success. Generated {OUT_NAME} ({total_bytes / (1024*1024):.2f} MB)")
|
|
139
|
+
|
|
140
|
+
except Exception as e:
|
|
141
|
+
print(f"Critical Failure writing output file: {e}")
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
main()
|