Build a website. (#4189)

Demo site: https://jonmeow.carbon-lang.dev/

I'm trying to keep work under the `/website` subdirectory so that the
misc files don't interfere with unrelated views of the repository. The
`prebuild.py` script does some work to move things around and add
frontmatter, helping the jekyll generation.

I'm using the "just-the-docs" theme because I think it's a decent match
for what we want, and getting jekyll up and running with it wasn't too
difficult. Note #1526 proposed using Docusaurus; I started out there,
but was having trouble getting it working with newer versions. The
plugins in particular I got stuck trying to make work, which sent me
looking for options that we could have working with less customization.
I do lean towards jekyll though, because it's what GH uses so hopefully
we can get a more consistent experience.

Having a website has been approved for a while under #1492, but hasn't
been a priority. I'm mainly doing this because I want to just be able to
point people to carbon-lang.dev and have easy links that way.

---------

Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
This commit is contained in:
Jon Ross-Perkins
2024-08-20 17:53:06 +00:00
committed by GitHub
co-authored by Chandler Carruth
parent 61e87c3a88
commit b6396e97f8
24 changed files with 688 additions and 4 deletions
+1
View File
@@ -16,4 +16,5 @@ inout
parameteras
pullrequest
rightt
rouge
statics
+41
View File
@@ -0,0 +1,41 @@
# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
# Exceptions. See /LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
name: GitHub Pages CI
on:
pull_request:
# Cancel previous workflows on the PR when there are multiple fast commits.
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
# Build job
build:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Prebuild actions
run: ./website/prebuild.py
- name: Setup Ruby
uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # v1.190.0
with:
# Runs 'bundle install' and caches installed gems automatically.
bundler-cache: true
# Increment this number if you need to re-download cached gems.
cache-version: 0
- name: Build with Jekyll
run: bundle exec jekyll build
+76
View File
@@ -0,0 +1,76 @@
# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
# Exceptions. See /LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
name: GitHub Pages deploy
on:
# Runs on pushes targeting the default branch.
push:
branches: ['trunk']
# Allows you to run this workflow manually from the Actions tab.
workflow_dispatch:
# Cancel previous workflows on the PR when there are multiple fast commits.
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages.
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Prebuild actions
run: ./website/prebuild.py
- name: Setup Pages
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0
- name: Setup Ruby
uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # v1.190.0
with:
# Runs 'bundle install' and caches installed gems automatically.
bundler-cache: true
# Increment this number if you need to re-download cached gems.
cache-version: 0
- name: Build with Jekyll
env:
JEKYLL_ENV: production
run: |
bundle exec jekyll build --verbose \
--source ./ \
--destination ./_site \
--baseurl "${{ steps.pages.outputs.base_path }}"
- name: Upload artifact
# Automatically uploads an artifact from the './_site' directory by
# default.
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Harden Runner
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
with:
egress-policy: audit
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
+3 -1
View File
@@ -180,7 +180,7 @@ repos:
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- --custom_format
- '\.(carbon|c|json|proto|ypp)(\.tmpl)?$'
- '\.(carbon|c|json|proto|scss|ypp)(\.tmpl)?$'
- ''
- '// '
- ''
@@ -216,6 +216,8 @@ repos:
compile_flags.txt|
github_tools/requirements.txt|
third_party/.*|
website/.ruby-version|
website/Gemfile.lock|
.*\.def|
.*\.svg|
.*/fuzzer_corpus/.*|
+3 -3
View File
@@ -1,11 +1,11 @@
# Security policy
<!--
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->
# Security policy
It's important to us that the Carbon Language provides a secure implementation.
Thank you for taking the time to report vulnerabilities.
@@ -13,7 +13,7 @@ The Carbon Language is still an
[experimental project](/README.md#project-status), so please be careful if using
it in security-sensitive environments.
# Reporting a vulnerability
## Reporting a vulnerability
Please use
<https://github.com/carbon-language/carbon-lang/security/advisories/new> to
+9
View File
@@ -6,6 +6,11 @@ Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->
<!--
{% raw %}
Hides `{{` from jekyll's liquid parsing. Note endraw at the bottom.
-->
## Images
Images are managed in
@@ -138,3 +143,7 @@ auto main() -> int {
return 0;
}
```
<!--
{% endraw %}
-->
+9
View File
@@ -6,6 +6,11 @@ Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->
<!--
{% raw %}
Hides `{{` from jekyll's liquid parsing. Note endraw at the bottom.
-->
`explorer` is an implementation of Carbon whose primary purpose is to act as a
clear specification of the language. As an extension of that goal, it can also
be used as a platform for prototyping and validating changes to the language.
@@ -334,3 +339,7 @@ information and provide visual separation for different sections.
- - - - - Sub Heading - - - - -
--------------------------------
```
<!--
{% endraw %}
-->
+2
View File
@@ -1,3 +1,5 @@
# Explorer AST
<!--
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
+2
View File
@@ -1,3 +1,5 @@
# Explorer Syntax
<!--
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
+9
View File
@@ -0,0 +1,9 @@
# Testing
<!--
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->
Testing-specific libraries.
+9
View File
@@ -6,6 +6,11 @@ Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->
<!--
{% raw %}
Hides `{{` from jekyll's liquid parsing. Note endraw at the bottom.
-->
## BUILD
A typical BUILD target will look like:
@@ -156,3 +161,7 @@ Supported comment markers are:
Tips like this are added by autoupdate, for example providing commands to
run the test directly. Tips have no impact on validation; the marker informs
autoupdate that it can update or remove them as needed.
<!--
{% endraw %}
-->
+1
View File
@@ -0,0 +1 @@
3.3
+12
View File
@@ -0,0 +1,12 @@
---
title: 404
nav_exclude: true
---
# 404: File not found
<!--
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->
+14
View File
@@ -0,0 +1,14 @@
# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
# Exceptions. See /LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
source 'https://rubygems.org'
gem "jekyll", "4.3.3"
gem "just-the-docs", "0.8.2"
group :jekyll_plugins do
gem "jekyll-default-layout", "0.1.5"
gem "jekyll-readme-index", "0.3.0"
gem "jekyll-relative-links", "0.7.0"
end
+100
View File
@@ -0,0 +1,100 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
bigdecimal (3.1.8)
colorator (1.1.0)
concurrent-ruby (1.3.4)
em-websocket (0.5.3)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0)
eventmachine (1.2.7)
ffi (1.17.0)
forwardable-extended (2.6.0)
google-protobuf (4.27.3-arm64-darwin)
bigdecimal
rake (>= 13)
google-protobuf (4.27.3-x86_64-linux)
bigdecimal
rake (>= 13)
http_parser.rb (0.8.0)
i18n (1.14.5)
concurrent-ruby (~> 1.0)
jekyll (4.3.3)
addressable (~> 2.4)
colorator (~> 1.0)
em-websocket (~> 0.5)
i18n (~> 1.0)
jekyll-sass-converter (>= 2.0, < 4.0)
jekyll-watch (~> 2.0)
kramdown (~> 2.3, >= 2.3.1)
kramdown-parser-gfm (~> 1.0)
liquid (~> 4.0)
mercenary (>= 0.3.6, < 0.5)
pathutil (~> 0.9)
rouge (>= 3.0, < 5.0)
safe_yaml (~> 1.0)
terminal-table (>= 1.8, < 4.0)
webrick (~> 1.7)
jekyll-default-layout (0.1.5)
jekyll (>= 3.0, < 5.0)
jekyll-include-cache (0.2.1)
jekyll (>= 3.7, < 5.0)
jekyll-readme-index (0.3.0)
jekyll (>= 3.0, < 5.0)
jekyll-relative-links (0.7.0)
jekyll (>= 3.3, < 5.0)
jekyll-sass-converter (3.0.0)
sass-embedded (~> 1.54)
jekyll-seo-tag (2.8.0)
jekyll (>= 3.8, < 5.0)
jekyll-watch (2.2.1)
listen (~> 3.0)
just-the-docs (0.8.2)
jekyll (>= 3.8.5)
jekyll-include-cache
jekyll-seo-tag (>= 2.0)
rake (>= 12.3.1)
kramdown (2.4.0)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
liquid (4.0.4)
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
mercenary (0.4.0)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
public_suffix (6.0.1)
rake (13.2.1)
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0)
rexml (3.3.5)
strscan
rouge (4.3.0)
safe_yaml (1.0.5)
sass-embedded (1.77.8)
google-protobuf (~> 4.26)
rake (>= 13)
strscan (3.1.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
unicode-display_width (2.5.0)
webrick (1.8.1)
PLATFORMS
arm64-darwin
x86_64-linux
DEPENDENCIES
jekyll (= 4.3.3)
jekyll-default-layout (= 0.1.5)
jekyll-readme-index (= 0.3.0)
jekyll-relative-links (= 0.7.0)
just-the-docs (= 0.8.2)
BUNDLED WITH
2.5.17
+59
View File
@@ -0,0 +1,59 @@
# Documentation website
<!--
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->
<!-- toc -->
## Table of contents
- [Overview](#overview)
- [Build process](#build-process)
- [Development tips](#development-tips)
- [Domain configuration](#domain-configuration)
<!-- tocstop -->
## Overview
Carbon's main website is the GitHub project page. Carbon remains too early and
experimental to have a full-fledged website.
This directories contains infrastructure for building the convenience
documentation website, which should be at <https://docs.carbon-lang.dev>.
## Build process
Website generation is done by
[gh_pages_deploy.yaml](/.github/workflows/gh_pages_deploy.yaml). It runs
`prebuild.py`, which prepares files for website generation, then builds the
website using Jekyll, configured through `_config.yml`.
## Development tips
[rbenv](https://github.com/rbenv/rbenv) can be used to set up Ruby and `bundle`.
To run a server, run `bundle exec jekyll serve`. See
[Jekyll docs](https://jekyllrb.com/docs/usage/) for more commands.
To update the `Gemfile.lock` after `Gemfile` changes, run `bundle update`.
## Domain configuration
The
[custom domain](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site)
is configured in three places:
- GitHub organization
[verified domains](https://github.com/organizations/carbon-language/settings/pages)
- GitHub repository
[custom domain](https://github.com/carbon-language/carbon-lang/settings/pages)
- Google Cloud DNS
- This is visible with `dig docs.carbon-lang.dev`
Note all of these require admin permissions to modify. For sharing test pages, a
GitHub user and repository can be used, pushing to `<username>.github.io` (or
getting a custom DNS setup).
+25
View File
@@ -0,0 +1,25 @@
# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
# Exceptions. See /LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
title: Carbon Language documentation
description: An experimental successor to C++
# https://just-the-docs.github.io/just-the-docs/
theme: just-the-docs
favicon_ico: '/favicon.png'
aux_links:
Discord: https://discord.gg/ZjVdShJDAs
GitHub: https://github.com/carbon-language/carbon-lang
mermaid:
# Version of mermaid library
# Pick an available version from https://cdn.jsdelivr.net/npm/mermaid/
version: '10.9.1'
# We add frontmatter to files, but still want README.md -> index.md.
readme_index:
remove_originals: true
with_frontmatter: true
+33
View File
@@ -0,0 +1,33 @@
<!--
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->
<!--
Used from:
https://github.com/just-the-docs/just-the-docs/blob/main/_includes/components/footer.html
-->
<footer>
<p><a href="#top" id="back-to-top">Back to top</a></p>
<p>
<a
href="https://github.com/carbon-language/carbon-lang/blob/trunk/{{ page.path }}"
id="edit-this-page"
>View on GitHub</a
>
</p>
<p class="text-small">
Part of the Carbon Language project, licensed under the
<a href="https://github.com/carbon-language/carbon-lang/blob/trunk/LICENSE"
>Apache License v2.0 with LLVM Exceptions</a
>.
</p>
<p class="text-small">
This site is hosted on GitHub Pages and uses
<a href="https://github.com/just-the-docs/just-the-docs">Just the Docs</a>.
</p>
</footer>
+12
View File
@@ -0,0 +1,12 @@
<!--
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->
<!--
Used from:
https://github.com/just-the-docs/just-the-docs/blob/main/_includes/components/sidebar.html
This just removes the nav footer; we use `footer_custom.html` instead.
-->
+47
View File
@@ -0,0 +1,47 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// This is modifying the builtin scss, particularly:
// https://github.com/just-the-docs/just-the-docs/blob/main/_sass/layout.scss
.site-nav {
// The navbar will typically overflow, so keep a scrollbar so that the zippies
// don't move on open/close.
overflow-y: scroll;
}
.side-bar {
@include mq(lg) {
// Allow left alignment instead of centering.
width: #{$nav-width};
}
& + .main {
@include mq(lg) {
// Allow left alignment instead of centering.
margin-left: #{$nav-width};
}
}
}
.site-footer {
color: #57606a;
}
// Comments in code.
.highlight .c {
color: #57606a;
}
// Misc syntax in code.
.highlight .nb,
.highlight .nt,
.highlight .nv {
color: #a36000;
}
// Strings in code.
.highlight .s2 {
color: #3f7f3f;
}
+14
View File
@@ -0,0 +1,14 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// This is modifying the builtin scss, particularly:
// https://github.com/just-the-docs/just-the-docs/blob/main/_sass/color_schemes/light.scss
// https://github.com/just-the-docs/just-the-docs/blob/main/_sass/support/_variables.scss
// Prefer wider content.
$nav-width: 20rem;
$content-width: 60rem;
$code-background-color: #f6f8fa;
$body-text-color: #1f2328;
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+9
View File
@@ -0,0 +1,9 @@
# Implementation
<!--
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->
These documents cover components of Carbon's reference implementation.
+198
View File
@@ -0,0 +1,198 @@
#!/usr/bin/env python3
"""Updates files in preparation for a jekyll build.
Used from .github/workflows/gh_pages.yaml. This updates the file and directory
structure prior to the jekyll build.
"""
import dataclasses
import os
from pathlib import Path
import re
from typing import Optional
__copyright__ = """
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
@dataclasses.dataclass
class ChildDir:
"""Tracks whether a child directory has grandchildren."""
title: str
has_grandchildren: bool = False
def get_title(f: Path, content: str) -> str:
"""Returns a file's title according to markdown.
Replacements are for YAML compatibility in `add_frontmatter`.
"""
m = re.search("^# (.*)$", content, re.MULTILINE)
assert m, f
title = m[1]
title = title.replace("\\", "\\\\")
title = title.replace('"', '\\"')
title = re.sub("`([^`]+)`", r"<code>\1</code>", title)
return title
def add_frontmatter(
f: Path,
orig_content: str,
titles: list[str],
nav_order: Optional[int],
has_children: bool,
) -> None:
"""Adds frontmatter to a file."""
content = "---\n"
assert len(titles) <= 3
content += f'title: "{titles[-1]}"\n'
if len(titles) >= 2:
content += f'parent: "{titles[-2]}"\n'
if len(titles) == 3:
content += f'grand_parent: "{titles[-3]}"\n'
if nav_order is not None:
content += f"nav_order: {nav_order}\n"
if has_children:
# has_toc is disabling the list of child files in a separate table of
# contents.
content += "has_children: true\nhas_toc: false\n"
content += "---\n\n" + orig_content
f.write_text(content)
def label_subdir(
subdir_str: str,
top_nav_order: int,
parent_title: Optional[str] = None,
grandchild_dirs: bool = False,
) -> None:
"""Automatically adds child information to a subdirectory's markdown files.
This is in support of navigation.
"""
assert not (parent_title and grandchild_dirs)
subdir = Path(subdir_str)
readme = subdir / "README.md"
readme_content = readme.read_text()
readme_title = get_title(readme, readme_content)
readme_titles = [readme_title]
if parent_title:
readme_titles.insert(0, parent_title)
children = [x for x in subdir.glob("**/*.md") if x != readme]
add_frontmatter(
readme, readme_content, readme_titles, top_nav_order, bool(children)
)
if grandchild_dirs:
# When adding grandchildren, we cluster files by child directory.
child_dirs = {}
for child in children:
if child.name == "README.md":
title = get_title(child, child.read_text())
child_dirs[child.parent] = ChildDir(title)
for child in children:
if child.name != "README.md" and child.parent in child_dirs:
child_dirs[child.parent].has_grandchildren = True
for child in children:
child_content = child.read_text()
child_title = get_title(child, child_content)
child_nav_order = None
if subdir_str == "proposals":
# Use proposal numbers as part of the title and ordering.
m = re.match(r"p(\d+).md", child.name)
# Skip files that aren't proposals.
if not m:
continue
child_title = f"#{m[1]}: {child_title}"
child_nav_order = int(m[1])
titles = [readme_title, child_title]
has_children = False
if parent_title:
titles.insert(0, parent_title)
elif grandchild_dirs and child.parent in child_dirs:
if child.name == "README.md":
has_children = child_dirs[child.parent].has_grandchildren
else:
dir_title = child_dirs[child.parent].title
titles.insert(1, dir_title)
add_frontmatter(
child, child_content, titles, child_nav_order, has_children
)
def label_root_file(
name: str, title: str, top_nav_order: int, has_children: bool = False
) -> None:
"""Adds frontmatter to a root file, like CONTRIBUTING.md."""
f = Path(name)
add_frontmatter(f, f.read_text(), [title], top_nav_order, has_children)
def main() -> None:
# Ensure this runs from the repo root.
os.chdir(Path(__file__).parent.parent)
# bazel-execroot interferes with jekyll because it's a broken symlink.
Path("bazel-execroot").unlink()
# The external symlink is created by scripts/create_compdb.py, and can
# interfere with local execution.
external = Path("external")
if external.exists():
external.unlink()
# Move files to the repo root.
for f in Path("website").iterdir():
if f.name == "README.md":
continue
f.rename(f.name)
# Use an object for a reference.
nav_order = [0]
# Returns an incrementing value for ordering.
def next(nav_order: list[int]) -> int:
nav_order[0] += 1
return nav_order[0]
label_root_file("README.md", "Home", next(nav_order))
label_root_file("CONTRIBUTING.md", "Contributing", next(nav_order))
label_subdir("docs/design", next(nav_order), grandchild_dirs=True)
label_subdir("docs/guides", next(nav_order))
label_subdir("docs/project", next(nav_order), grandchild_dirs=True)
label_subdir("docs/spec", next(nav_order))
# Provide a small file to cluster implementation-related directories.
label_root_file(
"implementation.md",
"Implementation",
next(nav_order),
has_children=True,
)
label_subdir("utils", next(nav_order))
label_subdir("proposals", next(nav_order))
label_root_file("CODE_OF_CONDUCT.md", "Code of conduct", next(nav_order))
label_root_file("SECURITY.md", "Security policy", next(nav_order))
# Reset the order for the implementation children.
nav_order[0] = 0
label_subdir("toolchain", next(nav_order), parent_title="Implementation")
label_subdir("explorer", next(nav_order), parent_title="Implementation")
label_subdir("testing", next(nav_order), parent_title="Implementation")
if __name__ == "__main__":
main()