Skip to content

Instantly share code, notes, and snippets.

@RndmCodeGuy20
Created July 18, 2025 09:44
Show Gist options
  • Select an option

  • Save RndmCodeGuy20/cc9aaf4c7dbeb7aeddc79b47cb560338 to your computer and use it in GitHub Desktop.

Select an option

Save RndmCodeGuy20/cc9aaf4c7dbeb7aeddc79b47cb560338 to your computer and use it in GitHub Desktop.
A powerful directory structure cloning tool that captures and recreates folder hierarchies across projects.

TreeClone 🌳

A powerful directory structure cloning tool that captures and recreates folder hierarchies across projects.

TreeClone lets you extract the skeleton of any project and replicate it elsewhere - perfect for project templates, maintaining consistent structures across teams, or quickly scaffolding new projects based on existing ones.

✨ Features

  • πŸ“ Structure Capture: Scan any directory and save its structure as portable JSON
  • πŸ”„ Perfect Recreation: Rebuild identical folder hierarchies anywhere
  • 🚫 Smart Exclusions: .noclone files with glob pattern support (like .gitignore for structure)
  • 🎯 Flexible Options: Include/exclude files, set depth limits, custom patterns
  • πŸ“ Template Ready: Create reusable project templates with ease
  • πŸ”§ Cross-Platform: Works on Windows, macOS, and Linux

πŸš€ Quick Start

# Initialize a project with sample .noclone file
python treeclone.py init /my/project

# Capture your project structure
python treeclone.py capture /my/project -o my_template.json

# Recreate it anywhere
python treeclone.py recreate my_template.json /new/location

πŸ’‘ Perfect For

  • Project Templates: Create reusable scaffolds for new projects
  • Team Consistency: Ensure everyone uses the same folder structure
  • Migration Planning: Map out directory changes before major refactors
  • Documentation: Visual project structure for onboarding
  • Backup Structure: Preserve organizational patterns without file content

πŸŽ›οΈ Advanced Usage

# Capture only directories (no files)
python treeclone.py capture /project --no-files

# Limit depth for large projects
python treeclone.py capture /project --max-depth 3

# Recreate structure only (no placeholder files)
python treeclone.py recreate template.json /target --no-files

🚫 .noclone File Support

Create .noclone files to exclude specific patterns:

# Common exclusions
*.log
*.tmp
.env*

# Build outputs
build/
dist/
node_modules/

# IDE files
.vscode/
.idea/

πŸ—οΈ Use Cases

  • Frontend Frameworks: Share React/Vue project structures
  • Backend APIs: Standardize Express/Django layouts
  • Documentation: Create consistent docs folder structures
  • Configuration: Replicate complex config hierarchies
  • Testing: Set up identical test environments

πŸ“¦ What Gets Saved

The JSON structure includes:

  • Directory hierarchy and names
  • File presence (optional)
  • Exclusion patterns from .noclone files
  • Metadata (timestamps, root info)

File contents are never captured - only the structural skeleton.


TreeClone turns messy project setup into a one-command operation. Stop manually creating folders and start cloning structures! 🎯

#!/usr/bin/env python3
import os
import json
import subprocess
import argparse
from pathlib import Path
import fnmatch
class TreeClone:
def __init__(self):
self.default_exclude_dirs = {'.git', '__pycache__', 'node_modules', '.DS_Store', '.vscode'}
self.noclone_filename = '.noclone'
def parse_noclone_file(self, noclone_path):
"""Parse .noclone file and return patterns to exclude."""
patterns = []
try:
with open(noclone_path, 'r') as f:
for line in f:
line = line.strip()
# Skip empty lines and comments
if line and not line.startswith('#'):
patterns.append(line)
except FileNotFoundError:
pass
return patterns
def collect_noclone_patterns(self, root_path):
"""Collect all .noclone patterns from root and subdirectories."""
all_patterns = {}
root = Path(root_path)
# Find all .noclone files in the directory tree
for noclone_file in root.rglob(self.noclone_filename):
# Get patterns from this .noclone file
patterns = self.parse_noclone_file(noclone_file)
if patterns:
# Store patterns with their directory context
relative_dir = noclone_file.parent.relative_to(root)
all_patterns[str(relative_dir)] = patterns
return all_patterns
def should_exclude_item(self, item_path, root_path, noclone_patterns):
"""Check if an item should be excluded based on .noclone patterns."""
# Check default exclusions
if any(excluded in item_path.parts for excluded in self.default_exclude_dirs):
return True
root = Path(root_path)
relative_path = item_path.relative_to(root)
# Check patterns from all applicable .noclone files
for pattern_dir, patterns in noclone_patterns.items():
pattern_path = Path(pattern_dir)
# Check if this pattern directory applies to our item
try:
# If the item is in this directory or a subdirectory
if pattern_path == Path('.') or pattern_path in relative_path.parents or pattern_path == relative_path.parent:
# Test each pattern
for pattern in patterns:
# Support both file names and paths
if fnmatch.fnmatch(item_path.name, pattern) or fnmatch.fnmatch(str(relative_path), pattern):
return True
except ValueError:
# relative_path calculation failed, skip this pattern
continue
return False
def capture_with_tree(self, directory, output_file):
"""Capture using tree command if available."""
try:
result = subprocess.run(
['tree', '-J', directory],
capture_output=True,
text=True,
check=True
)
structure = json.loads(result.stdout)
with open(output_file, 'w') as f:
json.dump(structure, f, indent=2)
return True
except (subprocess.CalledProcessError, FileNotFoundError, json.JSONDecodeError):
return False
def capture_manual(self, directory, output_file, include_files=True, max_depth=None):
"""Manual capture without tree command, respecting .noclone files."""
root = Path(directory)
# Collect all .noclone patterns
noclone_patterns = self.collect_noclone_patterns(root)
print(f"Found .noclone patterns: {noclone_patterns}")
def scan_directory(path, current_depth=0):
if max_depth is not None and current_depth > max_depth:
return []
items = []
try:
for item in sorted(path.iterdir()):
# Check if item should be excluded
if self.should_exclude_item(item, root, noclone_patterns):
print(f"Excluding: {item.relative_to(root)}")
continue
if item.is_dir():
children = scan_directory(item, current_depth + 1)
items.append({
'name': item.name,
'type': 'directory',
'children': children
})
elif include_files:
items.append({
'name': item.name,
'type': 'file'
})
except PermissionError:
pass
return items
structure = {
'root': root.name,
'noclone_patterns': noclone_patterns,
'structure': scan_directory(root)
}
with open(output_file, 'w') as f:
json.dump(structure, f, indent=2)
def recreate_structure(self, structure_file, target_directory, create_files=True):
"""Recreate directory structure from JSON."""
with open(structure_file, 'r') as f:
structure = json.load(f)
target = Path(target_directory)
def create_items(items, current_path):
for item in items:
item_path = current_path / item['name']
if item['type'] == 'directory':
item_path.mkdir(parents=True, exist_ok=True)
print(f"πŸ“ {item_path}")
if 'children' in item:
create_items(item['children'], item_path)
elif item['type'] == 'file' and create_files:
item_path.touch()
print(f"πŸ“„ {item_path}")
root_path = target / structure['root']
root_path.mkdir(parents=True, exist_ok=True)
# Recreate .noclone files if they were captured
if 'noclone_patterns' in structure:
for pattern_dir, patterns in structure['noclone_patterns'].items():
noclone_path = root_path / pattern_dir / self.noclone_filename
noclone_path.parent.mkdir(parents=True, exist_ok=True)
with open(noclone_path, 'w') as f:
f.write("# .noclone file - patterns to exclude from cloning\n")
for pattern in patterns:
f.write(f"{pattern}\n")
print(f"πŸ“ Created .noclone: {noclone_path}")
if 'structure' in structure:
create_items(structure['structure'], root_path)
return root_path
def create_sample_noclone(self, directory):
"""Create a sample .noclone file with common patterns."""
noclone_path = Path(directory) / self.noclone_filename
sample_content = """# .noclone file - patterns to exclude from TreeClone
# Lines starting with # are comments
# Supports glob patterns like *.log, temp*, etc.
# Common files to exclude
*.log
*.tmp
*.cache
*.pid
.env
.env.local
# Directories to exclude
temp/
logs/
cache/
build/
dist/
coverage/
# IDE and editor files
.vscode/
.idea/
*.swp
*.swo
*~
# OS files
.DS_Store
Thumbs.db
desktop.ini
# Add your custom patterns below:
"""
with open(noclone_path, 'w') as f:
f.write(sample_content)
print(f"Created sample .noclone file at: {noclone_path}")
return noclone_path
def main():
parser = argparse.ArgumentParser(description='TreeClone - Directory structure cloning tool')
subparsers = parser.add_subparsers(dest='command', help='Available commands')
# Capture command
capture_parser = subparsers.add_parser('capture', help='Capture directory structure')
capture_parser.add_argument('directory', help='Directory to capture')
capture_parser.add_argument('-o', '--output', default='structure.json', help='Output file')
capture_parser.add_argument('--no-files', action='store_true', help='Only capture directories')
capture_parser.add_argument('--max-depth', type=int, help='Maximum depth')
# Recreate command
recreate_parser = subparsers.add_parser('recreate', help='Recreate directory structure')
recreate_parser.add_argument('structure_file', help='JSON structure file')
recreate_parser.add_argument('target_directory', help='Target directory')
recreate_parser.add_argument('--no-files', action='store_true', help='Only create directories')
# Init command for creating sample .noclone
init_parser = subparsers.add_parser('init', help='Create sample .noclone file')
init_parser.add_argument('directory', nargs='?', default='.', help='Directory to create .noclone in')
args = parser.parse_args()
tool = TreeClone()
if args.command == 'capture':
print(f"Capturing structure of {args.directory}...")
# Always use manual capture to respect .noclone files
print("Using manual capture to respect .noclone files...")
tool.capture_manual(
args.directory,
args.output,
include_files=not args.no_files,
max_depth=args.max_depth
)
print(f"Structure saved to {args.output}")
elif args.command == 'recreate':
print(f"Recreating structure from {args.structure_file}...")
created_path = tool.recreate_structure(
args.structure_file,
args.target_directory,
create_files=not args.no_files
)
print(f"Structure recreated at: {created_path}")
elif args.command == 'init':
print(f"Creating sample .noclone file in {args.directory}...")
tool.create_sample_noclone(args.directory)
print("Edit the .noclone file to customize exclusion patterns.")
else:
parser.print_help()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment