Files
wehub-resource-sync bb5c75ce05
Component Security Validation / Security Audit (push) Has been cancelled
Deploy to Cloudflare Pages / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:58 +08:00

296 lines
11 KiB
Python
Executable File

#!/usr/bin/env python3
"""
MOC (Map of Content) Generator for Obsidian Vault
Automatically generates MOCs for directories and topics.
"""
import os
import re
from pathlib import Path
from datetime import datetime
from collections import defaultdict
import argparse
class MOCGenerator:
def __init__(self, vault_path):
self.vault_path = Path(vault_path)
self.directory_stats = {}
self.topic_clusters = defaultdict(list)
def analyze_directory(self, directory_path):
"""Analyze a directory and its contents."""
stats = {
'total_files': 0,
'md_files': 0,
'subdirectories': [],
'file_types': defaultdict(int),
'common_topics': defaultdict(int)
}
try:
for item in directory_path.iterdir():
if item.is_file():
stats['total_files'] += 1
if item.suffix == '.md':
stats['md_files'] += 1
# Extract topics from filename
topics = self.extract_topics_from_filename(item.name)
for topic in topics:
stats['common_topics'][topic] += 1
stats['file_types'][item.suffix] += 1
elif item.is_dir() and not item.name.startswith('.'):
stats['subdirectories'].append(item.name)
except PermissionError:
print(f"Permission denied: {directory_path}")
return stats
def extract_topics_from_filename(self, filename):
"""Extract potential topics from filename."""
# Remove extension and common prefixes
name = filename.replace('.md', '')
name = re.sub(r'^\d{4}-\d{2}-\d{2}[_-]', '', name) # Remove date prefix
name = re.sub(r'^MOC[_-]', '', name) # Remove MOC prefix
# Split on common separators and filter
words = re.split(r'[_\-\s]+', name)
topics = []
# Filter out common words and keep meaningful terms
stop_words = {'and', 'the', 'for', 'with', 'to', 'of', 'in', 'on', 'at', 'by'}
for word in words:
if len(word) > 2 and word.lower() not in stop_words:
topics.append(word.lower())
return topics
def generate_moc_content(self, directory_path, title, description=""):
"""Generate MOC content for a directory."""
stats = self.analyze_directory(directory_path)
# Create frontmatter
frontmatter = f"""---
tags: [MOC, {directory_path.name.lower().replace(' ', '-')}]
type: map-of-content
created: {datetime.now().strftime('%Y-%m-%d')}
modified: {datetime.now().strftime('%Y-%m-%d')}
status: active
cssclass: moc
aliases: [{title} Hub, {title} Overview]
hub_for: [{directory_path.name}]
related_mocs: []
---
"""
# Create content
content = f"""# {title} Map of Content
## Overview
{description if description else f"This MOC organizes all content related to {title.lower()}."}
**Directory**: `{directory_path.name}/`
**Total Files**: {stats['md_files']} markdown files
**Last Updated**: {datetime.now().strftime('%Y-%m-%d')}
"""
# Add subdirectories if any
if stats['subdirectories']:
content += "## Subdirectories\n\n"
for subdir in sorted(stats['subdirectories']):
content += f"### {subdir}\n"
content += f"- [[MOC - {subdir}|{subdir} Overview]]\n\n"
# Organize files by topic
content += self.organize_files_by_topic(directory_path, stats)
# Add common topics section
if stats['common_topics']:
content += "## Key Topics\n\n"
sorted_topics = sorted(stats['common_topics'].items(), key=lambda x: x[1], reverse=True)
for topic, count in sorted_topics[:10]: # Top 10 topics
content += f"- **{topic.title()}** ({count} files)\n"
content += "\n"
# Add templates and resources
content += """## Related MOCs
- [[MOC - AI Development|AI Development]]
- [[MOC - Learning Resources|Learning Resources]]
- [[Master Index|🗂️ Master Index]]
## Status & Progress
- [ ] Organize files by topic
- [ ] Add cross-references
- [ ] Review and update links
- [ ] Add examples and tutorials
## Next Steps
- Review all files in this directory
- Create sub-MOCs for large topic clusters
- Add relevant tags to all files
- Connect to related MOCs
---
*This MOC was auto-generated. Please review and customize as needed.*
"""
return frontmatter + content
def organize_files_by_topic(self, directory_path, stats):
"""Organize files by topic clusters."""
content = "## Content Organization\n\n"
# Group files by topic
topic_files = defaultdict(list)
ungrouped_files = []
try:
for file_path in directory_path.glob('*.md'):
if file_path.name.startswith('MOC'):
continue # Skip existing MOCs
topics = self.extract_topics_from_filename(file_path.name)
if topics:
# Use first topic as primary grouping
primary_topic = topics[0]
topic_files[primary_topic].append(file_path)
else:
ungrouped_files.append(file_path)
except Exception as e:
print(f"Error organizing files: {e}")
return content
# Sort topics by number of files
sorted_topics = sorted(topic_files.items(), key=lambda x: len(x[1]), reverse=True)
for topic, files in sorted_topics:
if len(files) > 1: # Only create sections for topics with multiple files
content += f"### {topic.title()}\n"
for file_path in sorted(files):
title = file_path.stem.replace('_', ' ').replace('-', ' ')
content += f"- [[{title}]]\n"
content += "\n"
# Add ungrouped files
if ungrouped_files:
content += "### Other Files\n"
for file_path in sorted(ungrouped_files):
title = file_path.stem.replace('_', ' ').replace('-', ' ')
content += f"- [[{title}]]\n"
content += "\n"
return content
def create_moc_file(self, directory_path, title, description="", output_path=None):
"""Create a MOC file for a directory."""
content = self.generate_moc_content(directory_path, title, description)
if output_path is None:
# Create MOCs in the centralized map-of-content directory
moc_dir = self.vault_path / "map-of-content"
moc_dir.mkdir(exist_ok=True)
output_path = moc_dir / f"MOC - {title}.md"
# Check if file already exists
if output_path.exists():
print(f"MOC already exists: {output_path}")
return False
try:
with open(output_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f"Created MOC: {output_path}")
return True
except Exception as e:
print(f"Error creating MOC: {e}")
return False
def suggest_mocs(self):
"""Suggest MOCs for directories that don't have them."""
suggestions = []
# Skip these directories
skip_dirs = {'.obsidian', '.trash', 'System_Files', '.git'}
for directory in self.vault_path.iterdir():
if not directory.is_dir() or directory.name in skip_dirs:
continue
stats = self.analyze_directory(directory)
# Check if directory has enough content to warrant a MOC
if stats['md_files'] >= 3:
# Check if MOC already exists
existing_mocs = list(directory.glob('MOC*.md'))
if not existing_mocs:
suggestions.append({
'directory': directory,
'title': directory.name,
'file_count': stats['md_files'],
'subdirs': len(stats['subdirectories']),
'top_topics': sorted(stats['common_topics'].items(),
key=lambda x: x[1], reverse=True)[:5]
})
return suggestions
def main():
parser = argparse.ArgumentParser(description='Generate MOCs for Obsidian vault')
parser.add_argument('--vault', default='/Users/cam/VAULT01',
help='Path to Obsidian vault')
parser.add_argument('--directory',
help='Specific directory to create MOC for')
parser.add_argument('--title',
help='Title for the MOC')
parser.add_argument('--description',
help='Description for the MOC')
parser.add_argument('--suggest', action='store_true',
help='Suggest directories that need MOCs')
parser.add_argument('--create-all', action='store_true',
help='Create MOCs for all suggested directories')
args = parser.parse_args()
generator = MOCGenerator(args.vault)
if args.suggest or args.create_all:
suggestions = generator.suggest_mocs()
if args.suggest:
print("MOC Suggestions:")
print("="*50)
for suggestion in suggestions:
print(f"Directory: {suggestion['directory'].name}")
print(f" Files: {suggestion['file_count']}")
print(f" Subdirs: {suggestion['subdirs']}")
if suggestion['top_topics']:
topics = [f"{topic} ({count})" for topic, count in suggestion['top_topics']]
print(f" Topics: {', '.join(topics)}")
print()
if args.create_all:
for suggestion in suggestions:
generator.create_moc_file(
suggestion['directory'],
suggestion['title']
)
elif args.directory:
directory_path = Path(args.vault) / args.directory
if not directory_path.exists():
print(f"Directory not found: {directory_path}")
return
title = args.title or directory_path.name
description = args.description or ""
generator.create_moc_file(directory_path, title, description)
else:
parser.print_help()
if __name__ == '__main__':
main()