Rustelo/scripts/docs/build-docs.sh

494 lines
14 KiB
Bash
Raw Normal View History

2025-07-07 23:53:50 +01:00
#!/bin/bash
# Rustelo Documentation Build Script
# This script builds the documentation using mdBook, cargo doc, and organizes the output
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
echo -e "${BLUE}🚀 Rustelo Documentation Build Script${NC}"
echo "================================="
# Check if mdbook is installed
if ! command -v mdbook &> /dev/null; then
echo -e "${RED}❌ mdbook is not installed${NC}"
echo "Please install mdbook:"
echo " cargo install mdbook"
echo " # Optional plugins:"
echo " cargo install mdbook-linkcheck"
echo " cargo install mdbook-toc"
echo " cargo install mdbook-mermaid"
exit 1
fi
# Check mdbook version
MDBOOK_VERSION=$(mdbook --version | cut -d' ' -f2)
echo -e "${GREEN}✅ mdbook version: $MDBOOK_VERSION${NC}"
# Create necessary directories
echo -e "${BLUE}📁 Creating directories...${NC}"
mkdir -p "$PROJECT_ROOT/book-output"
mkdir -p "$PROJECT_ROOT/book/theme"
# Copy custom theme files if they don't exist
if [ ! -f "$PROJECT_ROOT/book/theme/custom.css" ]; then
echo -e "${YELLOW}📝 Creating custom CSS...${NC}"
cat > "$PROJECT_ROOT/book/theme/custom.css" << 'EOF'
/* Rustelo Documentation Custom Styles */
:root {
--rustelo-primary: #e53e3e;
--rustelo-secondary: #3182ce;
--rustelo-accent: #38a169;
--rustelo-dark: #2d3748;
--rustelo-light: #f7fafc;
}
/* Custom header styling */
.menu-title {
color: var(--rustelo-primary);
font-weight: bold;
}
/* Code block improvements */
pre {
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
/* Improved table styling */
table {
border-collapse: collapse;
width: 100%;
margin: 1rem 0;
}
table th,
table td {
border: 1px solid #e2e8f0;
padding: 0.75rem;
text-align: left;
}
table th {
background-color: var(--rustelo-light);
font-weight: 600;
}
table tr:nth-child(even) {
background-color: #f8f9fa;
}
/* Feature badge styling */
.feature-badge {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.875rem;
font-weight: 500;
margin: 0.125rem;
}
.feature-badge.enabled {
background-color: #c6f6d5;
color: #22543d;
}
.feature-badge.disabled {
background-color: #fed7d7;
color: #742a2a;
}
.feature-badge.optional {
background-color: #fef5e7;
color: #744210;
}
/* Callout boxes */
.callout {
padding: 1rem;
margin: 1rem 0;
border-left: 4px solid;
border-radius: 0 4px 4px 0;
}
.callout.note {
border-left-color: var(--rustelo-secondary);
background-color: #ebf8ff;
}
.callout.warning {
border-left-color: #ed8936;
background-color: #fffaf0;
}
.callout.tip {
border-left-color: var(--rustelo-accent);
background-color: #f0fff4;
}
.callout.danger {
border-left-color: var(--rustelo-primary);
background-color: #fff5f5;
}
/* Command line styling */
.command-line {
background-color: #1a202c;
color: #e2e8f0;
padding: 1rem;
border-radius: 8px;
font-family: 'JetBrains Mono', 'Fira Code', monospace;
margin: 1rem 0;
}
.command-line::before {
content: "$ ";
color: #48bb78;
font-weight: bold;
}
/* Navigation improvements */
.chapter li.part-title {
color: var(--rustelo-primary);
font-weight: bold;
margin-top: 1rem;
}
/* Search improvements */
#searchresults mark {
background-color: #fef5e7;
color: #744210;
}
/* Mobile improvements */
@media (max-width: 768px) {
.content {
padding: 1rem;
}
table {
font-size: 0.875rem;
}
.command-line {
font-size: 0.8rem;
padding: 0.75rem;
}
}
/* Dark theme overrides */
.navy .callout.note {
background-color: #1e3a8a;
}
.navy .callout.warning {
background-color: #92400e;
}
.navy .callout.tip {
background-color: #14532d;
}
.navy .callout.danger {
background-color: #991b1b;
}
/* Print styles */
@media print {
.nav-wrapper,
.page-wrapper > .page > .menu,
.mobile-nav-chapters,
.nav-chapters,
.sidebar-scrollbox {
display: none !important;
}
.page-wrapper > .page {
left: 0 !important;
}
.content {
margin-left: 0 !important;
max-width: none !important;
}
}
EOF
fi
if [ ! -f "$PROJECT_ROOT/book/theme/custom.js" ]; then
echo -e "${YELLOW}📝 Creating custom JavaScript...${NC}"
cat > "$PROJECT_ROOT/book/theme/custom.js" << 'EOF'
// Rustelo Documentation Custom JavaScript
// Add copy buttons to code blocks
document.addEventListener('DOMContentLoaded', function() {
// Add copy buttons to code blocks
const codeBlocks = document.querySelectorAll('pre > code');
codeBlocks.forEach(function(codeBlock) {
const pre = codeBlock.parentElement;
const button = document.createElement('button');
button.className = 'copy-button';
button.textContent = 'Copy';
button.style.cssText = `
position: absolute;
top: 8px;
right: 8px;
background: #4a5568;
color: white;
border: none;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
opacity: 0;
transition: opacity 0.2s;
`;
pre.style.position = 'relative';
pre.appendChild(button);
pre.addEventListener('mouseenter', function() {
button.style.opacity = '1';
});
pre.addEventListener('mouseleave', function() {
button.style.opacity = '0';
});
button.addEventListener('click', function() {
const text = codeBlock.textContent;
navigator.clipboard.writeText(text).then(function() {
button.textContent = 'Copied!';
button.style.background = '#48bb78';
setTimeout(function() {
button.textContent = 'Copy';
button.style.background = '#4a5568';
}, 2000);
});
});
});
// Add feature badges
const content = document.querySelector('.content');
if (content) {
let html = content.innerHTML;
// Replace feature indicators
html = html.replace(/\[FEATURE:([^\]]+)\]/g, '<span class="feature-badge enabled">$1</span>');
html = html.replace(/\[OPTIONAL:([^\]]+)\]/g, '<span class="feature-badge optional">$1</span>');
html = html.replace(/\[DISABLED:([^\]]+)\]/g, '<span class="feature-badge disabled">$1</span>');
// Add callout boxes
html = html.replace(/\[NOTE\]([\s\S]*?)\[\/NOTE\]/g, '<div class="callout note">$1</div>');
html = html.replace(/\[WARNING\]([\s\S]*?)\[\/WARNING\]/g, '<div class="callout warning">$1</div>');
html = html.replace(/\[TIP\]([\s\S]*?)\[\/TIP\]/g, '<div class="callout tip">$1</div>');
html = html.replace(/\[DANGER\]([\s\S]*?)\[\/DANGER\]/g, '<div class="callout danger">$1</div>');
content.innerHTML = html;
}
// Add smooth scrolling
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth'
});
}
});
});
});
// Add keyboard shortcuts
document.addEventListener('keydown', function(e) {
// Ctrl/Cmd + K to focus search
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
const searchInput = document.querySelector('#searchbar');
if (searchInput) {
searchInput.focus();
}
}
});
// Add version info to footer
document.addEventListener('DOMContentLoaded', function() {
const content = document.querySelector('.content');
if (content) {
const footer = document.createElement('div');
footer.style.cssText = `
margin-top: 3rem;
padding: 2rem 0;
border-top: 1px solid #e2e8f0;
text-align: center;
font-size: 0.875rem;
color: #718096;
`;
footer.innerHTML = `
<p>Built with ❤️ using <a href="https://rust-lang.github.io/mdBook/" target="_blank">mdBook</a></p>
<p>Rustelo Documentation • Last updated: ${new Date().toLocaleDateString()}</p>
`;
content.appendChild(footer);
}
});
EOF
fi
# Check if we should sync content from existing docs
if [ "$1" = "--sync" ]; then
echo -e "${BLUE}🔄 Syncing content from existing documentation...${NC}"
# Create directories for existing content
mkdir -p "$PROJECT_ROOT/book/database"
mkdir -p "$PROJECT_ROOT/book/features/auth"
mkdir -p "$PROJECT_ROOT/book/features/content"
# Copy and adapt existing documentation
if [ -f "$PROJECT_ROOT/docs/database_configuration.md" ]; then
cp "$PROJECT_ROOT/docs/database_configuration.md" "$PROJECT_ROOT/book/database/configuration.md"
echo -e "${GREEN}✅ Synced database configuration${NC}"
fi
if [ -f "$PROJECT_ROOT/docs/2fa_implementation.md" ]; then
cp "$PROJECT_ROOT/docs/2fa_implementation.md" "$PROJECT_ROOT/book/features/auth/2fa.md"
echo -e "${GREEN}✅ Synced 2FA documentation${NC}"
fi
if [ -f "$PROJECT_ROOT/docs/email.md" ]; then
cp "$PROJECT_ROOT/docs/email.md" "$PROJECT_ROOT/book/features/email.md"
echo -e "${GREEN}✅ Synced email documentation${NC}"
fi
# Copy from info directory
if [ -f "$PROJECT_ROOT/info/features.md" ]; then
cp "$PROJECT_ROOT/info/features.md" "$PROJECT_ROOT/book/features/detailed.md"
echo -e "${GREEN}✅ Synced detailed features${NC}"
fi
echo -e "${GREEN}✅ Content sync complete${NC}"
fi
# Change to project root
cd "$PROJECT_ROOT"
# Build the documentation
echo -e "${BLUE}🔨 Building documentation...${NC}"
if mdbook build; then
echo -e "${GREEN}✅ Documentation built successfully${NC}"
else
echo -e "${RED}❌ Documentation build failed${NC}"
exit 1
fi
# Check if we should serve the documentation
if [ "$1" = "--serve" ] || [ "$2" = "--serve" ] || [ "$3" = "--serve" ]; then
echo -e "${BLUE}🌐 Starting development server...${NC}"
echo "Documentation will be available at: http://localhost:3000"
echo "Press Ctrl+C to stop the server"
mdbook serve --open
elif [ "$1" = "--watch" ] || [ "$2" = "--watch" ] || [ "$3" = "--watch" ]; then
echo -e "${BLUE}👀 Starting file watcher...${NC}"
echo "Documentation will be rebuilt automatically on file changes"
echo "Press Ctrl+C to stop watching"
mdbook watch
else
# Display build information
echo ""
echo -e "${GREEN}📚 Documentation built successfully!${NC}"
echo "Output directory: $PROJECT_ROOT/book-output"
echo "HTML files: $PROJECT_ROOT/book-output/html"
echo ""
echo "To serve the documentation locally:"
echo " $0 --serve"
echo ""
echo "To watch for changes:"
echo " $0 --watch"
echo ""
echo "To sync existing documentation:"
echo " $0 --sync"
echo ""
echo "To build cargo documentation:"
echo " $0 --cargo"
echo ""
echo "To build all documentation:"
echo " $0 --all"
fi
# Generate documentation metrics
echo -e "${BLUE}📊 Documentation metrics:${NC}"
TOTAL_PAGES=$(find "$PROJECT_ROOT/book-output/html" -name "*.html" | wc -l)
TOTAL_SIZE=$(du -sh "$PROJECT_ROOT/book-output/html" | cut -f1)
echo " Total pages: $TOTAL_PAGES"
echo " Total size: $TOTAL_SIZE"
# Check for broken links if linkcheck is available
if command -v mdbook-linkcheck &> /dev/null; then
echo -e "${BLUE}🔗 Checking for broken links...${NC}"
if mdbook-linkcheck; then
echo -e "${GREEN}✅ No broken links found${NC}"
else
echo -e "${YELLOW}⚠️ Some links may be broken${NC}"
fi
fi
# Build cargo documentation if requested
if [ "$1" = "--cargo" ] || [ "$2" = "--cargo" ] || [ "$3" = "--cargo" ]; then
echo -e "${BLUE}🦀 Building cargo documentation...${NC}"
# Build cargo doc
if cargo doc --no-deps --document-private-items; then
echo -e "${GREEN}✅ Cargo documentation built successfully${NC}"
# Enhance with logos
if [ -f "$PROJECT_ROOT/scripts/docs/enhance-docs.sh" ]; then
echo -e "${BLUE}🎨 Enhancing cargo docs with logos...${NC}"
"$PROJECT_ROOT/scripts/docs/enhance-docs.sh"
fi
echo -e "${GREEN}✅ Cargo documentation enhanced with logos${NC}"
else
echo -e "${RED}❌ Cargo documentation build failed${NC}"
fi
fi
# Build all documentation if requested
if [ "$1" = "--all" ] || [ "$2" = "--all" ] || [ "$3" = "--all" ]; then
echo -e "${BLUE}📚 Building all documentation...${NC}"
# Build mdBook
if mdbook build; then
echo -e "${GREEN}✅ mdBook documentation built${NC}"
else
echo -e "${RED}❌ mdBook build failed${NC}"
fi
# Build cargo doc
if cargo doc --no-deps --document-private-items; then
echo -e "${GREEN}✅ Cargo documentation built${NC}"
# Enhance with logos
if [ -f "$PROJECT_ROOT/scripts/docs/enhance-docs.sh" ]; then
echo -e "${BLUE}🎨 Enhancing cargo docs with logos...${NC}"
"$PROJECT_ROOT/scripts/docs/enhance-docs.sh"
fi
else
echo -e "${RED}❌ Cargo documentation build failed${NC}"
fi
fi
echo ""
echo -e "${GREEN}✨ Documentation build complete!${NC}"