# PackItUp
## ๐ฆ Project Structure Generator and Documentation Tool
PackItUp is a powerful and flexible command-line tool designed to generate comprehensive documentation of your project's structure. It creates a detailed overview of your project's files and directories, making it easier to understand, share, and maintain your codebase.
## ๐ Features
- ๐ณ Generate project structure in tree format
- ๐ Include file contents (optional)
- ๐ Provide file metadata (size, last modified date, permissions)
- ๐จ Multiple output formats (Markdown, JSON, YAML)
- ๐งน Respect .gitignore patterns
- ๐ซ Customizable file/directory exclusions
- ๐ฆ Compress output (optional)
- ๐ Customizable file size limits
- ๐ Automatic clipboard copy of output
## ๐ Installation
PackItUp is available on PyPI and can be installed with pip:
```
pip install packitup
```
This will install PackItUp and all its dependencies.
## ๐ Usage
After installation, you can use PackItUp directly from the command line:
```
packitup [OPTIONS] [PATH]
```
If no path is specified, PackItUp will use the current directory.
### ๐ Options
- `-t, --tree`: Generate file tree only (no file contents)
- `-l, --list`: List files and directories in the specified directory
- `-s, --singlefile`: Ignore file splitting and return as a single file
- `-p, --purge`: Purge all saved PackItUp data
- `-f, --format {markdown,json,yaml}`: Output format (default: markdown)
- `-m, --max-size BYTES`: Maximum file size in bytes (default: 5MB)
- `-c, --compress`: Compress the output files
- `-e, --exclude PATTERN [PATTERN ...]`: Additional patterns to exclude
- `--no-content`: Exclude file contents, only include metadata
## ๐ Examples
### 1. Basic Usage
Generate a Markdown report of the current directory:
```
packitup
```
This will create a Markdown file with the project structure, including file contents (up to 5MB per file) and metadata.
### 2. Specify a Different Directory
Generate a report for a specific project:
```
packitup /path/to/your/project
```
### 3. Generate Only the File Tree
To get a quick overview without file contents:
```
packitup -t
```
### 4. Change Output Format
Generate a JSON report:
```
packitup -f json
```
Or a YAML report:
```
packitup -f yaml
```
### 5. Customize File Size Limit
Set the maximum file size to 10MB:
```
packitup -m 10000000
```
### 6. Exclude Specific Patterns
Exclude all .log and .tmp files:
```
packitup -e *.log *.tmp
```
### 7. Generate a Compressed Output
Create a zip file containing the report:
```
packitup -c
```
### 8. Exclude File Contents
Generate a report with only metadata, no file contents:
```
packitup --no-content
```
# Project Structure
- __init__.py
```
```
- packitup.py
```
import os
import mimetypes
import pyperclip
import argparse
from datetime import datetime
import shutil
import colorama
from colorama import Fore, Style, Back
import json
import yaml
import fnmatch
from tqdm import tqdm
import zipfile
SKIP_FOLDERS = [
# Version control
".git",
".svn",
".hg",
".bzr",
# Dependencies and package managers
"node_modules",
"bower_components",
"jspm_packages",
"vendor",
"packages",
"composer.phar",
# Python
"venv",
".venv",
"env",
".env",
"__pycache__",
".pytest_cache",
"pip-wheel-metadata",
".eggs",
"*.egg-info",
# Build outputs
"dist",
"build",
"out",
"target",
"bin",
"obj",
# IDE and editor specific
".idea",
".vscode",
".vs",
"*.sublime-*",
".atom",
".eclipse",
".settings",
# OS specific
".DS_Store",
"Thumbs.db",
# Logs and temporary files
"logs",
"*.log",
"tmp",
"temp",
# Configuration and local settings
".env.local",
".env.*.local",
# Docker
".docker",
# Coverage reports
"coverage",
".coverage",
"htmlcov",
# Documentation builds
"docs/_build",
"site",
# Compiled source
"*.com",
"*.class",
"*.dll",
"*.exe",
"*.o",
"*.so",
]
MAX_FILE_SIZE = 5 * 1024 * 1024 # 5 MB
CHUNK_SIZE = 1 * 1024 * 1024 # 1 MB
def load_gitignore(path):
gitignore_patterns = []
gitignore_path = os.path.join(path, ".gitignore")
if os.path.exists(gitignore_path):
with open(gitignore_path, "r") as f:
gitignore_patterns = [
line.strip() for line in f if line.strip() and not line.startswith("#")
]
return gitignore_patterns
def should_skip(path, skip_patterns, gitignore_patterns):
name = os.path.basename(path)
if any(
fnmatch.fnmatch(name, pattern) for pattern in skip_patterns + gitignore_patterns
):
return True
return False
def get_file_metadata(file_path):
stats = os.stat(file_path)
return {
"size": stats.st_size,
"modified": datetime.fromtimestamp(stats.st_mtime).isoformat(),
"permissions": oct(stats.st_mode)[-3:],
}
def get_file_content(
file_path, output_dir, relative_path, single_file=False, include_content=True
):
if not include_content:
return get_file_metadata(file_path)
file_size = os.path.getsize(file_path)
if file_size > MAX_FILE_SIZE and not single_file:
return handle_large_file(file_path, output_dir, relative_path)
mime_type, _ = mimetypes.guess_type(file_path)
if mime_type and mime_type.startswith("text"):
try:
with open(file_path, "r", encoding="utf-8") as file:
content = file.read()
return {**get_file_metadata(file_path), "content": content}
except UnicodeDecodeError:
try:
with open(file_path, "r", encoding="iso-8859-1") as file:
content = file.read()
return {**get_file_metadata(file_path), "content": content}
except Exception:
return {
**get_file_metadata(file_path),
"content": f"[Unable to read {os.path.basename(file_path)}]",
}
else:
return {
**get_file_metadata(file_path),
"content": f"[{os.path.splitext(file_path)[1]} file]",
}
def handle_large_file(file_path, output_dir, relative_path):
file_name = os.path.basename(file_path)
parts_dir = os.path.join(output_dir, f"{file_name}_parts")
os.makedirs(parts_dir, exist_ok=True)
part_num = 1
with open(file_path, "rb") as f:
while True:
chunk = f.read(CHUNK_SIZE)
if not chunk:
break
part_file = os.path.join(parts_dir, f"{file_name}.part{part_num}")
with open(part_file, "wb") as part:
part.write(chunk)
part_num += 1
return {
**get_file_metadata(file_path),
"content": f"[Large file split into {part_num - 1} parts. See {os.path.relpath(parts_dir, output_dir)}]",
}
def generate_content(
path,
output_dir,
skip_patterns,
gitignore_patterns,
tree_only=False,
single_file=False,
include_content=True,
):
content = {}
for item in sorted(os.listdir(path)):
item_path = os.path.join(path, item)
if should_skip(item_path, skip_patterns, gitignore_patterns):
continue
if os.path.isdir(item_path):
content[item] = generate_content(
item_path,
output_dir,
skip_patterns,
gitignore_patterns,
tree_only,
single_file,
include_content,
)
else:
if tree_only:
content[item] = None
else:
content[item] = get_file_content(
item_path, output_dir, item, single_file, include_content
)
return content
def content_to_markdown(content, indent=""):
md_content = []
for key, value in content.items():
if value is None or isinstance(value, dict) and "content" not in value:
md_content.append(f"{indent}- {key}/")
if value:
md_content.extend(content_to_markdown(value, indent + " "))
else:
md_content.append(f"{indent}- {key}")
if isinstance(value, dict) and "content" in value:
md_content.append(f"{indent} ```")
md_content.append(f"{indent} {value['content']}")
md_content.append(f"{indent} ```")
return md_content
def compress_output(output_dir, compressed_file):
with zipfile.ZipFile(compressed_file, "w", zipfile.ZIP_DEFLATED) as zipf:
for root, _, files in os.walk(output_dir):
for file in files:
zipf.write(
os.path.join(root, file),
os.path.relpath(os.path.join(root, file), output_dir),
)
def print_styled_header(text):
print(f"\n{Back.CYAN}{Fore.BLACK}{text:^50}{Style.RESET_ALL}")
def print_styled_section(title, content):
print(f"\n{Fore.CYAN}{title}:")
for line in content:
print(f" {line}")
def make_clickable(path):
return f"\033]8;;file://{path}\033\\{path}\033]8;;\033\\"
def list_directory(path):
print(f"\n{Fore.CYAN}Contents of {Fore.YELLOW}{path}{Fore.RESET}:")
for item in sorted(os.listdir(path)):
if os.path.isdir(os.path.join(path, item)):
print(f" {Fore.BLUE}๐ {item}/{Fore.RESET}")
else:
print(f" {Fore.GREEN}๐ {item}{Fore.RESET}")
print()
def purge_data():
home_dir = os.path.expanduser("~")
home_packitup_dir = os.path.join(home_dir, ".packitup")
if os.path.exists(home_packitup_dir):
try:
shutil.rmtree(home_packitup_dir)
print(
f"{Fore.GREEN}โ
Successfully purged all PackItUp data from {home_packitup_dir}"
)
except Exception as e:
print(f"{Fore.RED}โ Error while purging data: {e}")
else:
print(f"{Fore.YELLOW}โ ๏ธ No PackItUp data found to purge.")
# Also remove any local output directories in the current working directory
cwd = os.getcwd()
for item in os.listdir(cwd):
if os.path.isdir(item) and item.endswith("_structure_"):
try:
shutil.rmtree(item)
print(f"{Fore.GREEN}โ
Removed local output directory: {item}")
except Exception as e:
print(f"{Fore.RED}โ Error while removing {item}: {e}")
def main():
colorama.init(autoreset=True)
parser = argparse.ArgumentParser(
description="Generate project structure and contents."
)
parser.add_argument(
"path", nargs="?", default=".", help="Path to pack (default: current directory)"
)
parser.add_argument(
"-t",
"--tree",
action="store_true",
help="Generate file tree only (no file contents)",
)
parser.add_argument(
"-l",
"--list",
action="store_true",
help="List files and directories in the specified directory",
)
parser.add_argument(
"-s",
"--singlefile",
action="store_true",
help="Ignore file splitting and return as a single file",
)
parser.add_argument(
"-p", "--purge", action="store_true", help="Purge all saved PackItUp data"
)
parser.add_argument(
"-f",
"--format",
choices=["markdown", "json", "yaml"],
default="markdown",
help="Output format (default: markdown)",
)
parser.add_argument(
"-m", "--max-size", type=int, help="Maximum file size in bytes (default: 5MB)"
)
parser.add_argument(
"-c", "--compress", action="store_true", help="Compress the output files"
)
parser.add_argument(
"-e", "--exclude", nargs="+", help="Additional patterns to exclude"
)
parser.add_argument(
"--no-content",
action="store_true",
help="Exclude file contents, only include metadata",
)
args = parser.parse_args()
if args.purge:
purge_data()
return
if args.max_size:
global MAX_FILE_SIZE
MAX_FILE_SIZE = args.max_size
root_dir = os.path.abspath(args.path)
if args.list:
list_directory(root_dir)
return
gitignore_patterns = load_gitignore(root_dir)
skip_patterns = SKIP_FOLDERS + (args.exclude or [])
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
root_folder_name = os.path.basename(root_dir)
output_dir = f"{root_folder_name}_structure_{timestamp}"
os.makedirs(output_dir, exist_ok=True)
output_file = os.path.join(
output_dir, f"{root_folder_name}_structure_{timestamp}.{args.format}"
)
print(f"Generating project structure for {root_dir}...")
content = generate_content(
root_dir,
output_dir,
skip_patterns,
gitignore_patterns,
args.tree,
args.singlefile,
not args.no_content,
)
if args.format == "markdown":
output_content = "\n".join(
["# Project Structure", ""] + content_to_markdown(content)
)
elif args.format == "json":
output_content = json.dumps(content, indent=2)
else: # yaml
output_content = yaml.dump(content, default_flow_style=False)
with open(output_file, "w", encoding="utf-8") as f:
f.write(output_content)
if args.compress:
compressed_file = f"{output_file}.zip"
print(f"Compressing output to {compressed_file}...")
compress_output(output_dir, compressed_file)
print(f"Compressed file saved to {compressed_file}")
pyperclip.copy(output_content)
print_styled_header("PackItUp Completed Successfully!")
print_styled_section(
"๐ Output Location",
[f"๐ Local: {Fore.YELLOW}{make_clickable(os.path.abspath(output_dir))}"],
)
print_styled_section(
"๐ Main File",
[f"๐ Local: {Fore.YELLOW}{make_clickable(os.path.abspath(output_file))}"],
)
print_styled_section(
"โน๏ธ Info",
[
f"๐ Content has been copied to clipboard.",
f"๐ Large files have been split and saved in parts if necessary.",
f"๐๏ธ Skipped patterns: {', '.join(skip_patterns + gitignore_patterns)}",
],
)
if __name__ == "__main__":
main()
```
### 9. Combine Multiple Options
Generate a compressed JSON report with a 10MB file size limit, excluding .log files and file contents:
```
packitup -f json -m 10000000 -c -e *.log --no-content
```
## ๐ Output
PackItUp generates its output in a new directory named `<project_name>_structure_<timestamp>`. The main output file will be in this directory, named `<project_name>_structure_<timestamp>.<format>`.
If the compress option is used, a zip file will be created in the same directory.
The generated content is automatically copied to your clipboard for easy sharing.
## ๐งน Cleaning Up
To remove all PackItUp generated data:
```
packitup -p
```
This will delete the `.packitup` directory in your home folder and any local output directories.
## ๐ Updating
To update PackItUp to the latest version, use pip:
```
pip install --upgrade packitup
```
## ๐ Troubleshooting
If you encounter any issues while using PackItUp, try the following:
1. Ensure you have the latest version installed.
2. Check that you have the necessary permissions to read the files and directories you're trying to document.
3. If you're having issues with specific file types, try using the `--no-content` option to exclude file contents.
If problems persist, please open an issue on our GitHub repository.
## ๐ค Contributing
Contributions are welcome! Please feel free to submit a Pull Request on our [GitHub repository](https://github.com/yourusername/packitup).
## ๐ License
This project is licensed under the MIT License - see the [LICENSE](https://github.com/yourusername/packitup/blob/main/LICENSE) file for details.
## ๐ Acknowledgements
- Thanks to all the open-source libraries that made this project possible.
- Special thanks to the Python community for their continuous support and inspiration.
---
Happy documenting! ๐โจ
Raw data
{
"_id": null,
"home_page": "https://github.com/jamesfincher/packitup",
"name": "packitup",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3",
"maintainer_email": null,
"keywords": null,
"author": "James Fincher",
"author_email": "james@fincher.dev",
"download_url": "https://files.pythonhosted.org/packages/ac/c2/24697e57cf6e2631a4e4dd9daafc879abb01f3b5026504ac221e991290b4/packitup-1.0.9.tar.gz",
"platform": null,
"description": "# PackItUp\n\n## \ud83d\udce6 Project Structure Generator and Documentation Tool\n\nPackItUp is a powerful and flexible command-line tool designed to generate comprehensive documentation of your project's structure. It creates a detailed overview of your project's files and directories, making it easier to understand, share, and maintain your codebase.\n\n## \ud83c\udf1f Features\n\n- \ud83c\udf33 Generate project structure in tree format\n- \ud83d\udcc4 Include file contents (optional)\n- \ud83d\udcca Provide file metadata (size, last modified date, permissions)\n- \ud83c\udfa8 Multiple output formats (Markdown, JSON, YAML)\n- \ud83e\uddf9 Respect .gitignore patterns\n- \ud83d\udeab Customizable file/directory exclusions\n- \ud83d\udce6 Compress output (optional)\n- \ud83d\udccf Customizable file size limits\n- \ud83d\udccb Automatic clipboard copy of output\n\n## \ud83d\udee0 Installation\n\nPackItUp is available on PyPI and can be installed with pip:\n\n```\npip install packitup\n```\n\nThis will install PackItUp and all its dependencies.\n\n## \ud83d\ude80 Usage\n\nAfter installation, you can use PackItUp directly from the command line:\n\n```\npackitup [OPTIONS] [PATH]\n```\n\nIf no path is specified, PackItUp will use the current directory.\n\n### \ud83c\udf9b Options\n\n- `-t, --tree`: Generate file tree only (no file contents)\n- `-l, --list`: List files and directories in the specified directory\n- `-s, --singlefile`: Ignore file splitting and return as a single file\n- `-p, --purge`: Purge all saved PackItUp data\n- `-f, --format {markdown,json,yaml}`: Output format (default: markdown)\n- `-m, --max-size BYTES`: Maximum file size in bytes (default: 5MB)\n- `-c, --compress`: Compress the output files\n- `-e, --exclude PATTERN [PATTERN ...]`: Additional patterns to exclude\n- `--no-content`: Exclude file contents, only include metadata\n\n## \ud83d\udcda Examples\n\n### 1. Basic Usage\n\nGenerate a Markdown report of the current directory:\n\n```\npackitup\n```\n\nThis will create a Markdown file with the project structure, including file contents (up to 5MB per file) and metadata.\n\n### 2. Specify a Different Directory\n\nGenerate a report for a specific project:\n\n```\npackitup /path/to/your/project\n```\n\n### 3. Generate Only the File Tree\n\nTo get a quick overview without file contents:\n\n```\npackitup -t\n```\n\n### 4. Change Output Format\n\nGenerate a JSON report:\n\n```\npackitup -f json\n```\n\nOr a YAML report:\n\n```\npackitup -f yaml\n```\n\n### 5. Customize File Size Limit\n\nSet the maximum file size to 10MB:\n\n```\npackitup -m 10000000\n```\n\n### 6. Exclude Specific Patterns\n\nExclude all .log and .tmp files:\n\n```\npackitup -e *.log *.tmp\n```\n\n### 7. Generate a Compressed Output\n\nCreate a zip file containing the report:\n\n```\npackitup -c\n```\n\n### 8. Exclude File Contents\n\nGenerate a report with only metadata, no file contents:\n\n```\npackitup --no-content\n```\n # Project Structure\n\n- __init__.py\n ```\n \n ```\n- packitup.py\n ```\n import os\nimport mimetypes\nimport pyperclip\nimport argparse\nfrom datetime import datetime\nimport shutil\nimport colorama\nfrom colorama import Fore, Style, Back\nimport json\nimport yaml\nimport fnmatch\nfrom tqdm import tqdm\nimport zipfile\n\nSKIP_FOLDERS = [\n # Version control\n \".git\",\n \".svn\",\n \".hg\",\n \".bzr\",\n # Dependencies and package managers\n \"node_modules\",\n \"bower_components\",\n \"jspm_packages\",\n \"vendor\",\n \"packages\",\n \"composer.phar\",\n # Python\n \"venv\",\n \".venv\",\n \"env\",\n \".env\",\n \"__pycache__\",\n \".pytest_cache\",\n \"pip-wheel-metadata\",\n \".eggs\",\n \"*.egg-info\",\n # Build outputs\n \"dist\",\n \"build\",\n \"out\",\n \"target\",\n \"bin\",\n \"obj\",\n # IDE and editor specific\n \".idea\",\n \".vscode\",\n \".vs\",\n \"*.sublime-*\",\n \".atom\",\n \".eclipse\",\n \".settings\",\n # OS specific\n \".DS_Store\",\n \"Thumbs.db\",\n # Logs and temporary files\n \"logs\",\n \"*.log\",\n \"tmp\",\n \"temp\",\n # Configuration and local settings\n \".env.local\",\n \".env.*.local\",\n # Docker\n \".docker\",\n # Coverage reports\n \"coverage\",\n \".coverage\",\n \"htmlcov\",\n # Documentation builds\n \"docs/_build\",\n \"site\",\n # Compiled source\n \"*.com\",\n \"*.class\",\n \"*.dll\",\n \"*.exe\",\n \"*.o\",\n \"*.so\",\n]\n\nMAX_FILE_SIZE = 5 * 1024 * 1024 # 5 MB\nCHUNK_SIZE = 1 * 1024 * 1024 # 1 MB\n\n\ndef load_gitignore(path):\n gitignore_patterns = []\n gitignore_path = os.path.join(path, \".gitignore\")\n if os.path.exists(gitignore_path):\n with open(gitignore_path, \"r\") as f:\n gitignore_patterns = [\n line.strip() for line in f if line.strip() and not line.startswith(\"#\")\n ]\n return gitignore_patterns\n\n\ndef should_skip(path, skip_patterns, gitignore_patterns):\n name = os.path.basename(path)\n if any(\n fnmatch.fnmatch(name, pattern) for pattern in skip_patterns + gitignore_patterns\n ):\n return True\n return False\n\n\ndef get_file_metadata(file_path):\n stats = os.stat(file_path)\n return {\n \"size\": stats.st_size,\n \"modified\": datetime.fromtimestamp(stats.st_mtime).isoformat(),\n \"permissions\": oct(stats.st_mode)[-3:],\n }\n\n\ndef get_file_content(\n file_path, output_dir, relative_path, single_file=False, include_content=True\n):\n if not include_content:\n return get_file_metadata(file_path)\n\n file_size = os.path.getsize(file_path)\n if file_size > MAX_FILE_SIZE and not single_file:\n return handle_large_file(file_path, output_dir, relative_path)\n\n mime_type, _ = mimetypes.guess_type(file_path)\n\n if mime_type and mime_type.startswith(\"text\"):\n try:\n with open(file_path, \"r\", encoding=\"utf-8\") as file:\n content = file.read()\n return {**get_file_metadata(file_path), \"content\": content}\n except UnicodeDecodeError:\n try:\n with open(file_path, \"r\", encoding=\"iso-8859-1\") as file:\n content = file.read()\n return {**get_file_metadata(file_path), \"content\": content}\n except Exception:\n return {\n **get_file_metadata(file_path),\n \"content\": f\"[Unable to read {os.path.basename(file_path)}]\",\n }\n else:\n return {\n **get_file_metadata(file_path),\n \"content\": f\"[{os.path.splitext(file_path)[1]} file]\",\n }\n\n\ndef handle_large_file(file_path, output_dir, relative_path):\n file_name = os.path.basename(file_path)\n parts_dir = os.path.join(output_dir, f\"{file_name}_parts\")\n os.makedirs(parts_dir, exist_ok=True)\n part_num = 1\n with open(file_path, \"rb\") as f:\n while True:\n chunk = f.read(CHUNK_SIZE)\n if not chunk:\n break\n part_file = os.path.join(parts_dir, f\"{file_name}.part{part_num}\")\n with open(part_file, \"wb\") as part:\n part.write(chunk)\n part_num += 1\n\n return {\n **get_file_metadata(file_path),\n \"content\": f\"[Large file split into {part_num - 1} parts. See {os.path.relpath(parts_dir, output_dir)}]\",\n }\n\n\ndef generate_content(\n path,\n output_dir,\n skip_patterns,\n gitignore_patterns,\n tree_only=False,\n single_file=False,\n include_content=True,\n):\n content = {}\n for item in sorted(os.listdir(path)):\n item_path = os.path.join(path, item)\n if should_skip(item_path, skip_patterns, gitignore_patterns):\n continue\n if os.path.isdir(item_path):\n content[item] = generate_content(\n item_path,\n output_dir,\n skip_patterns,\n gitignore_patterns,\n tree_only,\n single_file,\n include_content,\n )\n else:\n if tree_only:\n content[item] = None\n else:\n content[item] = get_file_content(\n item_path, output_dir, item, single_file, include_content\n )\n return content\n\n\ndef content_to_markdown(content, indent=\"\"):\n md_content = []\n for key, value in content.items():\n if value is None or isinstance(value, dict) and \"content\" not in value:\n md_content.append(f\"{indent}- {key}/\")\n if value:\n md_content.extend(content_to_markdown(value, indent + \" \"))\n else:\n md_content.append(f\"{indent}- {key}\")\n if isinstance(value, dict) and \"content\" in value:\n md_content.append(f\"{indent} ```\")\n md_content.append(f\"{indent} {value['content']}\")\n md_content.append(f\"{indent} ```\")\n return md_content\n\n\ndef compress_output(output_dir, compressed_file):\n with zipfile.ZipFile(compressed_file, \"w\", zipfile.ZIP_DEFLATED) as zipf:\n for root, _, files in os.walk(output_dir):\n for file in files:\n zipf.write(\n os.path.join(root, file),\n os.path.relpath(os.path.join(root, file), output_dir),\n )\n\n\ndef print_styled_header(text):\n print(f\"\\n{Back.CYAN}{Fore.BLACK}{text:^50}{Style.RESET_ALL}\")\n\n\ndef print_styled_section(title, content):\n print(f\"\\n{Fore.CYAN}{title}:\")\n for line in content:\n print(f\" {line}\")\n\n\ndef make_clickable(path):\n return f\"\\033]8;;file://{path}\\033\\\\{path}\\033]8;;\\033\\\\\"\n\n\ndef list_directory(path):\n print(f\"\\n{Fore.CYAN}Contents of {Fore.YELLOW}{path}{Fore.RESET}:\")\n for item in sorted(os.listdir(path)):\n if os.path.isdir(os.path.join(path, item)):\n print(f\" {Fore.BLUE}\ud83d\udcc1 {item}/{Fore.RESET}\")\n else:\n print(f\" {Fore.GREEN}\ud83d\udcc4 {item}{Fore.RESET}\")\n print()\n\n\ndef purge_data():\n home_dir = os.path.expanduser(\"~\")\n home_packitup_dir = os.path.join(home_dir, \".packitup\")\n\n if os.path.exists(home_packitup_dir):\n try:\n shutil.rmtree(home_packitup_dir)\n print(\n f\"{Fore.GREEN}\u2705 Successfully purged all PackItUp data from {home_packitup_dir}\"\n )\n except Exception as e:\n print(f\"{Fore.RED}\u274c Error while purging data: {e}\")\n else:\n print(f\"{Fore.YELLOW}\u26a0\ufe0f No PackItUp data found to purge.\")\n\n # Also remove any local output directories in the current working directory\n cwd = os.getcwd()\n for item in os.listdir(cwd):\n if os.path.isdir(item) and item.endswith(\"_structure_\"):\n try:\n shutil.rmtree(item)\n print(f\"{Fore.GREEN}\u2705 Removed local output directory: {item}\")\n except Exception as e:\n print(f\"{Fore.RED}\u274c Error while removing {item}: {e}\")\n\n\ndef main():\n colorama.init(autoreset=True)\n parser = argparse.ArgumentParser(\n description=\"Generate project structure and contents.\"\n )\n parser.add_argument(\n \"path\", nargs=\"?\", default=\".\", help=\"Path to pack (default: current directory)\"\n )\n parser.add_argument(\n \"-t\",\n \"--tree\",\n action=\"store_true\",\n help=\"Generate file tree only (no file contents)\",\n )\n parser.add_argument(\n \"-l\",\n \"--list\",\n action=\"store_true\",\n help=\"List files and directories in the specified directory\",\n )\n parser.add_argument(\n \"-s\",\n \"--singlefile\",\n action=\"store_true\",\n help=\"Ignore file splitting and return as a single file\",\n )\n parser.add_argument(\n \"-p\", \"--purge\", action=\"store_true\", help=\"Purge all saved PackItUp data\"\n )\n parser.add_argument(\n \"-f\",\n \"--format\",\n choices=[\"markdown\", \"json\", \"yaml\"],\n default=\"markdown\",\n help=\"Output format (default: markdown)\",\n )\n parser.add_argument(\n \"-m\", \"--max-size\", type=int, help=\"Maximum file size in bytes (default: 5MB)\"\n )\n parser.add_argument(\n \"-c\", \"--compress\", action=\"store_true\", help=\"Compress the output files\"\n )\n parser.add_argument(\n \"-e\", \"--exclude\", nargs=\"+\", help=\"Additional patterns to exclude\"\n )\n parser.add_argument(\n \"--no-content\",\n action=\"store_true\",\n help=\"Exclude file contents, only include metadata\",\n )\n args = parser.parse_args()\n\n if args.purge:\n purge_data()\n return\n\n if args.max_size:\n global MAX_FILE_SIZE\n MAX_FILE_SIZE = args.max_size\n\n root_dir = os.path.abspath(args.path)\n\n if args.list:\n list_directory(root_dir)\n return\n\n gitignore_patterns = load_gitignore(root_dir)\n skip_patterns = SKIP_FOLDERS + (args.exclude or [])\n\n timestamp = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n root_folder_name = os.path.basename(root_dir)\n\n output_dir = f\"{root_folder_name}_structure_{timestamp}\"\n os.makedirs(output_dir, exist_ok=True)\n output_file = os.path.join(\n output_dir, f\"{root_folder_name}_structure_{timestamp}.{args.format}\"\n )\n\n print(f\"Generating project structure for {root_dir}...\")\n content = generate_content(\n root_dir,\n output_dir,\n skip_patterns,\n gitignore_patterns,\n args.tree,\n args.singlefile,\n not args.no_content,\n )\n\n if args.format == \"markdown\":\n output_content = \"\\n\".join(\n [\"# Project Structure\", \"\"] + content_to_markdown(content)\n )\n elif args.format == \"json\":\n output_content = json.dumps(content, indent=2)\n else: # yaml\n output_content = yaml.dump(content, default_flow_style=False)\n\n with open(output_file, \"w\", encoding=\"utf-8\") as f:\n f.write(output_content)\n\n if args.compress:\n compressed_file = f\"{output_file}.zip\"\n print(f\"Compressing output to {compressed_file}...\")\n compress_output(output_dir, compressed_file)\n print(f\"Compressed file saved to {compressed_file}\")\n\n pyperclip.copy(output_content)\n\n print_styled_header(\"PackItUp Completed Successfully!\")\n\n print_styled_section(\n \"\ud83d\udcc1 Output Location\",\n [f\"\ud83d\udccc Local: {Fore.YELLOW}{make_clickable(os.path.abspath(output_dir))}\"],\n )\n\n print_styled_section(\n \"\ud83d\udcc4 Main File\",\n [f\"\ud83d\udccc Local: {Fore.YELLOW}{make_clickable(os.path.abspath(output_file))}\"],\n )\n\n print_styled_section(\n \"\u2139\ufe0f Info\",\n [\n f\"\ud83d\udccb Content has been copied to clipboard.\",\n f\"\ud83d\udcc2 Large files have been split and saved in parts if necessary.\",\n f\"\ud83d\uddc2\ufe0f Skipped patterns: {', '.join(skip_patterns + gitignore_patterns)}\",\n ],\n )\n\n\nif __name__ == \"__main__\":\n main()\n\n ```\n### 9. Combine Multiple Options\n\nGenerate a compressed JSON report with a 10MB file size limit, excluding .log files and file contents:\n\n```\npackitup -f json -m 10000000 -c -e *.log --no-content\n```\n\n## \ud83d\udcc2 Output\n\nPackItUp generates its output in a new directory named `<project_name>_structure_<timestamp>`. The main output file will be in this directory, named `<project_name>_structure_<timestamp>.<format>`.\n\nIf the compress option is used, a zip file will be created in the same directory.\n\nThe generated content is automatically copied to your clipboard for easy sharing.\n\n## \ud83e\uddf9 Cleaning Up\n\nTo remove all PackItUp generated data:\n\n```\npackitup -p\n```\n\nThis will delete the `.packitup` directory in your home folder and any local output directories.\n\n## \ud83d\udd04 Updating\n\nTo update PackItUp to the latest version, use pip:\n\n```\npip install --upgrade packitup\n```\n\n## \ud83d\udc1b Troubleshooting\n\nIf you encounter any issues while using PackItUp, try the following:\n\n1. Ensure you have the latest version installed.\n2. Check that you have the necessary permissions to read the files and directories you're trying to document.\n3. If you're having issues with specific file types, try using the `--no-content` option to exclude file contents.\n\nIf problems persist, please open an issue on our GitHub repository.\n\n## \ud83e\udd1d Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request on our [GitHub repository](https://github.com/yourusername/packitup).\n\n## \ud83d\udcc4 License\n\nThis project is licensed under the MIT License - see the [LICENSE](https://github.com/yourusername/packitup/blob/main/LICENSE) file for details.\n\n## \ud83d\ude4f Acknowledgements\n\n- Thanks to all the open-source libraries that made this project possible.\n- Special thanks to the Python community for their continuous support and inspiration.\n\n---\n\nHappy documenting! \ud83d\udcda\u2728\n",
"bugtrack_url": null,
"license": null,
"summary": "A tool to generate project structure and contents as Markdown",
"version": "1.0.9",
"project_urls": {
"Homepage": "https://github.com/jamesfincher/packitup"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "08e04a44293eefab78fcb8b8279526c671da34018e0e1c9fbc66a17e49ed01ef",
"md5": "1b1884b27148336abe19fd504f87a4cc",
"sha256": "22d4bf8e3193281db9090440329c0fa4ceb043df54f40669c3f1465e69b522aa"
},
"downloads": -1,
"filename": "packitup-1.0.9-py3-none-any.whl",
"has_sig": false,
"md5_digest": "1b1884b27148336abe19fd504f87a4cc",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3",
"size": 10765,
"upload_time": "2024-08-18T22:59:13",
"upload_time_iso_8601": "2024-08-18T22:59:13.479584Z",
"url": "https://files.pythonhosted.org/packages/08/e0/4a44293eefab78fcb8b8279526c671da34018e0e1c9fbc66a17e49ed01ef/packitup-1.0.9-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "acc224697e57cf6e2631a4e4dd9daafc879abb01f3b5026504ac221e991290b4",
"md5": "10817df2fb4a974a5e6c233dcadf2b6c",
"sha256": "474ce936e934de020d12791ff21b4c7fae068db919251072d1bb048b861b183e"
},
"downloads": -1,
"filename": "packitup-1.0.9.tar.gz",
"has_sig": false,
"md5_digest": "10817df2fb4a974a5e6c233dcadf2b6c",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3",
"size": 11623,
"upload_time": "2024-08-18T22:59:15",
"upload_time_iso_8601": "2024-08-18T22:59:15.915956Z",
"url": "https://files.pythonhosted.org/packages/ac/c2/24697e57cf6e2631a4e4dd9daafc879abb01f3b5026504ac221e991290b4/packitup-1.0.9.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-08-18 22:59:15",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "jamesfincher",
"github_project": "packitup",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "packitup"
}