Pack a directory of text files into one self-extracting Python script, and unpack it back.
Project description
utfbundle
Packs a directory of text files into a single Python script, and unpacks it back into an identical directory tree.
Useful for uploading a folder to GitHub Gist, ChatGPT, Claude Projects, or any other tool that only accepts flat files, single files, or text blocks. These interfaces typically flatten directory structure on upload, so anything nested under subdirectories loses its path once it's in context.
Reaching for an archiver might be an option in some of these cases, unless text files inside the archive need to be read by a bot or AI integration.
utfbundle writes a directory's contents into one .py file, with
each original file's path and content stored inside it as plain text
where possible, or base64 for anything that isn't valid UTF-8. Running
that file writes every file back out, unchanged, to a directory of your
choosing. Packing and unpacking are inverses. Files come back out
byte-for-byte identical, whichever form they were stored in.
Not intended to be a general-purpose archiver. No compression, no
encryption. tar or zip already cover those use cases. This is
specifically for the case where the constraint is "one plain text file,
exactly reversible" including directories that have a few binary files
mixed in with the text.
Install
pip install utfbundle
This installs the utfbundle command (and pulls in pathspec as a
dependency automatically).
Requirements
- Python 3.8+
pathspec(installed automatically with the package above;pip install pathspecif running from source)
Quick start
Pack the current directory:
[python3] utfbundle
This writes <dirname>_bundle.py in the current directory.
Reconstruct the tree later by running the bundle:
python3 <dirname>_bundle.py
With no arguments, it writes back into .. Packing . and running the
resulting bundle from the same location are complements. Pass a path to
extract elsewhere instead:
python3 <dirname>_bundle.py /path/to/restore/into
You can also pack a specific directory rather than the current one:
[python3] utfbundle path/to/project -o project_bundle.py
Here, since a named directory was packed rather than ., the bundle's
default destination is a subdirectory named after it. Running
project_bundle.py with no arguments elsewhere recreates
path/to/project/ as project/.
Choosing what's included: .bundlespec
By default, packing includes everything except common junk (.git/,
node_modules/, target/, __pycache__/, .DS_Store, prior bundle
files). To control this directly, add a .bundlespec file at the root
of the directory you're packing.
The syntax is .gitignore's glob syntax, inverted. A
.gitignore excludes by default, so a bare line excludes and !
re-includes. A .bundlespec includes by default, so a bare line
includes, and ! excludes something an earlier line already
matched:
# what to bundle
src/**
assets/**
Cargo.toml
# exceptions
!target/
!node_modules/
!*.log
!secrets.rs
Rules apply top to bottom; the last matching rule for a given path wins.
List broad includes first, then narrow with !.
Command-line reference
[python3] utfbundle [src_dir] [-o bundle.py] [-s SPEC] [-p PATTERN ...] [-n NAME]
| Flag | Meaning |
|---|---|
src_dir |
Directory to pack. Defaults to . |
-o, --output |
Output bundle path. Defaults to <name>_bundle.py |
-s, --spec |
Path to a patterns file. Defaults to <src_dir>/.bundlespec if present |
-p, --pattern |
An extra pattern, may be repeated; applied after the spec file |
-n, --project-name |
Name used for the bundle file and default extract destination |
Limitations
- Binary files are supported, but not free. Each file is read as UTF-8 text; if that fails, it's stored instead as base64 inside the bundle (and a note is printed to stderr) and decoded back to exact bytes on extract. Either way the round-trip is exact, but base64 content isn't meant to be read by eye the way the text entries are, and it inflates that file's size by roughly a third.
- No secret-scanning. It bundles whatever the patterns match. Keep
credentials and
.envfiles out via.bundlespec, the same way you would for a commit. - No compression. Bundle size is roughly the sum of the included files' sizes (plus the base64 overhead on any binary files).
- Non-UTF-8 text is treated as binary. A file in Latin-1, Windows-1252, UTF-16, etc. will fail the UTF-8 decode check and get stored as base64 rather than as a readable literal. It still round-trips exactly. This only affects whether it's human-readable inside the bundle file.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file utfbundle-0.1.1.tar.gz.
File metadata
- Download URL: utfbundle-0.1.1.tar.gz
- Upload date:
- Size: 8.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
661b989fa7c2cca3180022e5505aadd4a296187ab3c224b37593049bfb1faea3
|
|
| MD5 |
c36fdb62faed2ae2d4d0cf0a13f35f5d
|
|
| BLAKE2b-256 |
e2ec2841683ec6b32c7cbce9b4997f3e2ad43b95a77c6f0f885dadb47945b5c0
|
File details
Details for the file utfbundle-0.1.1-py3-none-any.whl.
File metadata
- Download URL: utfbundle-0.1.1-py3-none-any.whl
- Upload date:
- Size: 8.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f6a5f9073e1dd8d32bc90819d46889ca8b58f5d1cd6f6d6ac1855743362d412f
|
|
| MD5 |
5bc859412876a88c5dfc4784fb19c57b
|
|
| BLAKE2b-256 |
b5b2aaddfb2f634d0a06a4f50d0958b7c7163b24c421b44a3c021267b2f2414b
|