|
1 | | -"""Build the static site for Sites, without runtime dependencies.""" |
| 1 | +"""Build the static marketing site and developer reference, without dependencies.""" |
| 2 | +from html import escape |
| 3 | +import json |
2 | 4 | from pathlib import Path |
| 5 | +import re |
3 | 6 | import shutil |
| 7 | +from string import Template |
| 8 | + |
4 | 9 | root = Path(__file__).resolve().parent |
5 | 10 | output = root / 'dist' |
6 | 11 | output.mkdir(exist_ok=True) |
7 | | -for name in ('index.html', 'style.css', 'site.js', 'theme.js'): |
| 12 | +base = 'https://cloudtoid.com' |
| 13 | +pages = json.loads((root / 'docs/pages.json').read_text()) |
| 14 | +home = (root / 'index.html').read_text() |
| 15 | +favicon = re.search(r'<link rel="icon"[^>]+>', home).group() |
| 16 | + |
| 17 | + |
| 18 | +def metadata(title, description, path, kind='website'): |
| 19 | + url = base + path |
| 20 | + tags = [f'<link rel="canonical" href="{url}">'] |
| 21 | + for key, value in { |
| 22 | + 'og:type': kind, 'og:site_name': 'Cloudtoid Interprocess', |
| 23 | + 'og:title': title, 'og:description': description, 'og:url': url, |
| 24 | + 'og:image': base + '/assets/social-card.png', |
| 25 | + 'og:image:width': '1200', 'og:image:height': '630', |
| 26 | + 'og:image:alt': 'Cloudtoid Interprocess: fast shared-memory queues across six languages', |
| 27 | + }.items(): |
| 28 | + tags.append(f'<meta property="{key}" content="{escape(value, quote=True)}">') |
| 29 | + tags.append('<meta name="twitter:card" content="summary_large_image">') |
| 30 | + return '\n'.join(tags) |
| 31 | + |
| 32 | + |
| 33 | +home_title = re.search(r'<title>(.*?)</title>', home).group(1) |
| 34 | +home_description = re.search(r'<meta name="description" content="([^"]+)"', home).group(1) |
| 35 | +home = re.sub(r'<link rel="canonical"[^>]+>', metadata(home_title, home_description, '/'), home) |
| 36 | +software = { |
| 37 | + '@context': 'https://schema.org', '@type': 'SoftwareSourceCode', |
| 38 | + 'name': 'Cloudtoid Interprocess', 'url': base + '/', |
| 39 | + 'description': home_description, |
| 40 | + 'codeRepository': 'https://github.com/cloudtoid/interprocess', |
| 41 | + 'programmingLanguage': ['Rust', 'C', 'Python', 'JavaScript', 'Go', 'C#'], |
| 42 | + 'runtimePlatform': ['Linux', 'macOS', 'Windows'], |
| 43 | + 'license': 'https://github.com/cloudtoid/interprocess/blob/main/LICENSE', |
| 44 | +} |
| 45 | +home = home.replace('</head>', '<script type="application/ld+json">' + json.dumps(software) + '</script>\n</head>') |
| 46 | +(output / 'index.html').write_text(home) |
| 47 | +for name in ('style.css', 'site.js', 'theme.js', 'docs.css', 'docs.js'): |
8 | 48 | shutil.copyfile(root / name, output / name) |
9 | 49 | for name in ('assets', 'benchmarks', 'vendor'): |
10 | 50 | shutil.copytree(root / name, output / name, dirs_exist_ok=True) |
11 | | -print(f'Built {output}') |
| 51 | + |
| 52 | + |
| 53 | +def page_path(page): |
| 54 | + return '/docs/' + (page['slug'] + '/' if page['slug'] else '') |
| 55 | + |
| 56 | + |
| 57 | +template = Template((root / 'docs/template.html').read_text()) |
| 58 | +for page in pages: |
| 59 | + path = page_path(page) |
| 60 | + content = (root / 'docs' / ((page['slug'] or 'overview') + '.html')).read_text() |
| 61 | + navigation = ''.join( |
| 62 | + '<a href="{}"{}>{}</a>'.format(page_path(item), ' aria-current="page"' if item == page else '', escape(item['label'])) |
| 63 | + for item in pages |
| 64 | + ) |
| 65 | + headings = re.findall(r'<h2 id="([^"]+)">(.*?)</h2>', content) |
| 66 | + content = re.sub(r'<h([23]) id="([^"]+)">(.*?)</h\1>', |
| 67 | + lambda match: f'<h{match[1]} id="{match[2]}"><a class="heading-link" href="#{match[2]}">{match[3]}</a></h{match[1]}>', content) |
| 68 | + toc = ''.join(f'<a href="#{key}">{title}</a>' for key, title in headings) |
| 69 | + breadcrumbs = [{'@type': 'ListItem', 'position': 1, 'name': 'Documentation', 'item': base + '/docs/'}] |
| 70 | + if page['slug']: |
| 71 | + breadcrumbs.append({'@type': 'ListItem', 'position': 2, 'name': page['title'], 'item': base + path}) |
| 72 | + data = [ |
| 73 | + {'@context': 'https://schema.org', '@type': 'TechArticle', 'headline': page['title'], |
| 74 | + 'description': page['description'], 'url': base + path, 'inLanguage': 'en', |
| 75 | + 'author': {'@type': 'Organization', 'name': 'Cloudtoid', 'url': base + '/'}}, |
| 76 | + {'@context': 'https://schema.org', '@type': 'BreadcrumbList', 'itemListElement': breadcrumbs}, |
| 77 | + ] |
| 78 | + rendered = template.substitute( |
| 79 | + title=escape(page['title']), description=escape(page['description'], quote=True), |
| 80 | + metadata=metadata(page['title'] + ' | Cloudtoid Interprocess', page['description'], path, 'article') |
| 81 | + + '\n<script type="application/ld+json">' + json.dumps(data) + '</script>', |
| 82 | + favicon=favicon, navigation=navigation, toc=toc, content=content, |
| 83 | + breadcrumb=('<span>/</span><span>' + escape(page['label']) + '</span>') if page['slug'] else '', |
| 84 | + ) |
| 85 | + target = output / path.strip('/') / 'index.html' |
| 86 | + target.parent.mkdir(parents=True, exist_ok=True) |
| 87 | + target.write_text(rendered) |
| 88 | + |
| 89 | +urls = [base + '/'] + [base + page_path(page) for page in pages] |
| 90 | +(output / 'sitemap.xml').write_text('<?xml version="1.0" encoding="UTF-8"?>\n' |
| 91 | + + '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n' |
| 92 | + + '\n'.join(f'<url><loc>{url}</loc></url>' for url in urls) + '\n</urlset>\n') |
| 93 | +(output / 'robots.txt').write_text('User-agent: *\nAllow: /\n\nSitemap: ' + base + '/sitemap.xml\n') |
| 94 | +print(f'Built {output}: homepage and {len(pages)} documentation pages') |
0 commit comments