1010GUIDES_DIR = os .path .join (DOCS_DIR , "guides" )
1111
1212GUIDES = [
13- {"file" : "architecture.md" , "title" : "Architecture" , "desc" : "Container layout, request flow, and tech stack." },
14- {"file" : "configuration.md" , "title" : "Configuration" , "desc" : "Environment variables and exposed ports." },
15- {"file" : "api.md" , "title" : "API Reference" , "desc" : "REST endpoints, authentication, and API key scopes." },
16- {"file" : "production.md" , "title" : "Production Deployment" , "desc" : "Hardened mode, DNS records, and TLS certificates." },
17- {"file" : "tunnel.md" , "title" : "SMTP Tunnel" , "desc" : "Bypass port-25 blocks on cloud providers with a secure, authenticated SMTP egress tunnel." },
18- {"file" : "clients.md" , "title" : "Email Clients & TLS Trust" , "desc" : "IMAP/POP3/SMTP setup and trusting the CA." },
19- {"file" : "ci.md" , "title" : "Using in CI/CD" , "desc" : "Pipeline setup and platform examples." },
20- {"file" : "mcp.md" , "title" : "MCP Server" , "desc" : "Give an AI agent its own mailbox over MCP." },
21- {"file" : "sandbox.md" , "title" : "Provider Sandbox & HTTP Bin" , "desc" : "Capture SMS, voice, and chat traffic, and inspect HTTP requests." },
22- {"file" : "networking.md" , "title" : "Sharing MailCue" , "desc" : "Run one container behind a shared Docker network." },
23- {"file" : "development.md" , "title" : "Development & Contributing" , "desc" : "Local setup, linting, tests, and the PR process." }
13+ {
14+ "file" : "architecture.md" ,
15+ "title" : "Architecture" ,
16+ "desc" : "Container layout, request flow, and tech stack." ,
17+ },
18+ {
19+ "file" : "configuration.md" ,
20+ "title" : "Configuration" ,
21+ "desc" : "Environment variables and exposed ports." ,
22+ },
23+ {
24+ "file" : "api.md" ,
25+ "title" : "API Reference" ,
26+ "desc" : "REST endpoints, authentication, and API key scopes." ,
27+ },
28+ {
29+ "file" : "production.md" ,
30+ "title" : "Production Deployment" ,
31+ "desc" : "Hardened mode, DNS records, and TLS certificates." ,
32+ },
33+ {
34+ "file" : "tunnel.md" ,
35+ "title" : "SMTP Tunnel" ,
36+ "desc" : "Bypass port-25 blocks on cloud providers with a secure, authenticated SMTP egress tunnel." ,
37+ },
38+ {
39+ "file" : "clients.md" ,
40+ "title" : "Email Clients & TLS Trust" ,
41+ "desc" : "IMAP/POP3/SMTP setup and trusting the CA." ,
42+ },
43+ {
44+ "file" : "ci.md" ,
45+ "title" : "Using in CI/CD" ,
46+ "desc" : "Pipeline setup and platform examples." ,
47+ },
48+ {
49+ "file" : "mcp.md" ,
50+ "title" : "MCP Server" ,
51+ "desc" : "Give an AI agent its own mailbox over MCP." ,
52+ },
53+ {
54+ "file" : "sandbox.md" ,
55+ "title" : "Provider Sandbox & HTTP Bin" ,
56+ "desc" : "Capture SMS, voice, and chat traffic, and inspect HTTP requests." ,
57+ },
58+ {
59+ "file" : "networking.md" ,
60+ "title" : "Sharing MailCue" ,
61+ "desc" : "Run one container behind a shared Docker network." ,
62+ },
63+ {
64+ "file" : "development.md" ,
65+ "title" : "Development & Contributing" ,
66+ "desc" : "Local setup, linting, tests, and the PR process." ,
67+ },
2468]
2569
2670# Generate Pygments syntax highlighting CSS
466510</html>
467511"""
468512
513+
469514def generate_docs ():
470515 print ("Generating HTML documentation..." )
471-
516+
472517 # Auto-copy and adapt tunnel README to docs/guides/tunnel.md
473518 tunnel_readme = os .path .join (WORKSPACE_ROOT , "tunnel" , "README.md" )
474519 tunnel_guide = os .path .join (GUIDES_DIR , "tunnel.md" )
475520 if os .path .exists (tunnel_readme ):
476521 with open (tunnel_readme , "r" , encoding = "utf-8" ) as f :
477522 content = f .read ()
478523 # Fix relative links to tunnel/docs/PROTOCOL.md and SECURITY.md
479- content = content .replace ("](docs/PROTOCOL.md)" , "](https://github.com/Olib-AI/mailcue/blob/main/tunnel/docs/PROTOCOL.md)" )
480- content = content .replace ("](docs/SECURITY.md)" , "](https://github.com/Olib-AI/mailcue/blob/main/tunnel/docs/SECURITY.md)" )
524+ content = content .replace (
525+ "](docs/PROTOCOL.md)" ,
526+ "](https://github.com/Olib-AI/mailcue/blob/main/tunnel/docs/PROTOCOL.md)" ,
527+ )
528+ content = content .replace (
529+ "](docs/SECURITY.md)" ,
530+ "](https://github.com/Olib-AI/mailcue/blob/main/tunnel/docs/SECURITY.md)" ,
531+ )
481532 with open (tunnel_guide , "w" , encoding = "utf-8" ) as f :
482533 f .write (content )
483534 print ("Copied and adapted tunnel/README.md to docs/guides/tunnel.md" )
484-
535+
485536 # Render all markdown guides
486537 for guide in GUIDES :
487538 md_file_path = os .path .join (GUIDES_DIR , guide ["file" ])
488539 html_filename = guide ["file" ].replace (".md" , ".html" )
489540 html_file_path = os .path .join (GUIDES_DIR , html_filename )
490-
541+
491542 if not os .path .exists (md_file_path ):
492543 print (f"Warning: { md_file_path } not found." )
493544 continue
494-
545+
495546 with open (md_file_path , "r" , encoding = "utf-8" ) as f :
496547 md_content = f .read ()
497-
548+
498549 # Pre-process markdown to extract mermaid blocks before markdown parses them (and codehilite ruins them)
499550 mermaid_blocks = []
551+
500552 def extract_mermaid (match ):
501553 block_content = match .group (1 ).strip ()
502554 mermaid_blocks .append (block_content )
503- return f"\n \n <!-- MERMAID_PLACEHOLDER_{ len (mermaid_blocks )- 1 } -->\n \n "
504-
505- md_content_no_mermaid = re .sub (r'```mermaid([\s\S]*?)```' , extract_mermaid , md_content )
506-
555+ return f"\n \n <!-- MERMAID_PLACEHOLDER_{ len (mermaid_blocks ) - 1 } -->\n \n "
556+
557+ md_content_no_mermaid = re .sub (
558+ r"```mermaid([\s\S]*?)```" , extract_mermaid , md_content
559+ )
560+
507561 # Parse markdown to HTML
508562 # Using extensions:
509563 # extra: includes tables, footnotes, attribute lists, etc.
510564 # codehilite: syntax highlighting
511565 # fenced_code: code blocks
512- html_body = markdown .markdown (md_content_no_mermaid , extensions = ['extra' , 'codehilite' , 'fenced_code' ])
513-
566+ html_body = markdown .markdown (
567+ md_content_no_mermaid , extensions = ["extra" , "codehilite" , "fenced_code" ]
568+ )
569+
514570 # Post-process HTML
515571 # 1. Map relative markdown links to HTML links
516572 # Match links to other guides (e.g. 'architecture.md' or '../guides/architecture.md')
@@ -525,9 +581,9 @@ def replace_link(match):
525581 if g ["file" ] == basename :
526582 return f'href="{ basename .replace (".md" , ".html" )} "'
527583 return match .group (0 )
528-
584+
529585 html_body = re .sub (r'href="([^"]+)"' , replace_link , html_body )
530-
586+
531587 # 2. Put mermaid blocks back as clean <pre class="mermaid"> tags
532588 for i , block in enumerate (mermaid_blocks ):
533589 placeholder = f"<!-- MERMAID_PLACEHOLDER_{ i } -->"
@@ -537,7 +593,7 @@ def replace_link(match):
537593 html_body = html_body .replace (p_placeholder , mermaid_html )
538594 else :
539595 html_body = html_body .replace (placeholder , mermaid_html )
540-
596+
541597 # 3. Generate Sidebar Links dynamically
542598 sidebar_links = []
543599 for g in GUIDES :
@@ -547,26 +603,28 @@ def replace_link(match):
547603 f'<li><a href="{ g_html } " class="sidebar-link { active_class } ">{ g ["title" ]} </a></li>'
548604 )
549605 sidebar_links_str = "\n " .join (sidebar_links )
550-
606+
551607 # Populate Page Template
552608 page_html = PAGE_TEMPLATE .format (
553609 title = guide ["title" ],
554610 desc = guide ["desc" ],
555611 html_filename = html_filename ,
556612 sidebar_links = sidebar_links_str ,
557613 content = html_body ,
558- pygments_css = pygments_css
614+ pygments_css = pygments_css ,
559615 )
560-
616+
561617 with open (html_file_path , "w" , encoding = "utf-8" ) as f :
562618 f .write (page_html )
563-
619+
564620 print (f"Generated: docs/guides/{ html_filename } " )
565621
566622 # Generate robots.txt
567623 robots_path = os .path .join (DOCS_DIR , "robots.txt" )
568624 with open (robots_path , "w" , encoding = "utf-8" ) as f :
569- f .write ("User-agent: *\n Allow: /\n \n Sitemap: https://olib-ai.github.io/mailcue/sitemap.xml\n " )
625+ f .write (
626+ "User-agent: *\n Allow: /\n \n Sitemap: https://olib-ai.github.io/mailcue/sitemap.xml\n "
627+ )
570628 print ("Generated: docs/robots.txt" )
571629
572630 # Generate sitemap.xml
@@ -575,20 +633,30 @@ def replace_link(match):
575633 "https://olib-ai.github.io/mailcue/" ,
576634 ]
577635 for g in GUIDES :
578- sitemap_urls .append (f"https://olib-ai.github.io/mailcue/guides/{ g ['file' ].replace ('.md' , '.html' )} " )
579-
580- sitemap_xml = ['<?xml version="1.0" encoding="UTF-8"?>' , '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' ]
636+ sitemap_urls .append (
637+ f"https://olib-ai.github.io/mailcue/guides/{ g ['file' ].replace ('.md' , '.html' )} "
638+ )
639+
640+ sitemap_xml = [
641+ '<?xml version="1.0" encoding="UTF-8"?>' ,
642+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' ,
643+ ]
581644 for url in sitemap_urls :
582645 sitemap_xml .append (" <url>" )
583646 sitemap_xml .append (f" <loc>{ url } </loc>" )
584647 sitemap_xml .append (" <changefreq>weekly</changefreq>" )
585- sitemap_xml .append (" <priority>1.0</priority>" if url .endswith ("/mailcue/" ) else " <priority>0.8</priority>" )
648+ sitemap_xml .append (
649+ " <priority>1.0</priority>"
650+ if url .endswith ("/mailcue/" )
651+ else " <priority>0.8</priority>"
652+ )
586653 sitemap_xml .append (" </url>" )
587654 sitemap_xml .append ("</urlset>" )
588-
655+
589656 with open (sitemap_path , "w" , encoding = "utf-8" ) as f :
590657 f .write ("\n " .join (sitemap_xml ))
591658 print ("Generated: docs/sitemap.xml" )
592659
660+
593661if __name__ == "__main__" :
594662 generate_docs ()
0 commit comments