494 lines
14 KiB
Bash
494 lines
14 KiB
Bash
![]() |
#!/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}"
|