Skip to content

Commit 91d8789

Browse files
authored
Merge pull request #19 from rescript-lang/api-docs
API docs
2 parents e5386f8 + cbe8118 commit 91d8789

File tree

14 files changed

+591
-14
lines changed

14 files changed

+591
-14
lines changed

.prettierrc.mjs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/** @type {import("prettier").Config} */
2+
export default {
3+
plugins: ["prettier-plugin-astro"],
4+
overrides: [
5+
{
6+
files: "*.astro",
7+
options: {
8+
parser: "astro",
9+
},
10+
},
11+
],
12+
endOfLine: "lf",
13+
};

astro.config.mjs

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,31 @@
11
import { readFileSync } from "node:fs";
22
import { defineConfig } from "astro/config";
33
import starlight from "@astrojs/starlight";
4+
import { apiModules } from "./docs/utils";
45

56
const rescriptTM = JSON.parse(
67
readFileSync("./docs/assets/rescript.tmLanguage.json", "utf-8"),
78
);
89

10+
const apiSidebarItems = apiModules.map(({ moduleName, link, items }) => {
11+
const nestedItems = Object.values(items).map(({ moduleName, link }) => ({
12+
label: moduleName,
13+
link
14+
}));
15+
16+
return ({
17+
label: moduleName,
18+
collapsed: true,
19+
items: [
20+
{
21+
label: `Overview`,
22+
link
23+
},
24+
...nestedItems
25+
]
26+
});
27+
});
28+
929
export default defineConfig({
1030
srcDir: "docs",
1131
publicDir: "docs/public",
@@ -18,24 +38,36 @@ export default defineConfig({
1838
src: "./docs/assets/rescript-logo.svg",
1939
},
2040
social: {
21-
github: 'https://github.com/rescript-lang/experimental-rescript-webapi',
41+
github: "https://github.com/rescript-lang/experimental-rescript-webapi",
2242
},
2343
editLink: {
24-
baseUrl: 'https://github.com/rescript-lang/experimental-rescript-webapi/edit/main/',
44+
baseUrl:
45+
"https://github.com/rescript-lang/experimental-rescript-webapi/edit/main/",
2546
},
2647
sidebar: [
2748
{
28-
slug: '',
49+
slug: "",
50+
},
51+
{
52+
slug: "design-philosophy",
2953
},
3054
{
31-
slug: 'design-philosophy',
55+
slug: "project-status",
3256
},
3357
{
34-
slug: 'project-status',
58+
label: "Contributing",
59+
autogenerate: { directory: "contributing" },
3560
},
3661
{
37-
label: 'Contributing',
38-
autogenerate: { directory: 'contributing' },
62+
label: "API Documentation",
63+
collapsed: true,
64+
items: [
65+
{
66+
label: "Overview",
67+
link: "apidocs",
68+
},
69+
...apiSidebarItems,
70+
],
3971
},
4072
],
4173
customCss: ["./docs/styles/fonts.css", "./docs/styles/theme.css"],

docs/components/record.astro

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
import { micromark } from "micromark";
3+
import Signature from "./signature.astro";
4+
const { name, items, typesInOwnModule } = Astro.props;
5+
---
6+
7+
<h4>Record fields</h4>
8+
{
9+
items.map((item) => {
10+
const documentation =
11+
item.docstrings && micromark(item.docstrings.join("\n"));
12+
13+
return (
14+
<div class="record_field">
15+
<h5 id={`${name}_${item.name}`}>{item.name}</h5>
16+
<div class="type">
17+
<Signature
18+
signature={item.signature}
19+
typesInOwnModule={typesInOwnModule}
20+
/>
21+
</div>
22+
{documentation && <div class="doc" set:html={documentation} />}
23+
</div>
24+
);
25+
})
26+
}
27+
28+
<style>
29+
.record_field {
30+
display: grid;
31+
grid-template-rows: repeat(2, auto);
32+
grid-template-columns: repeat(2, 1fr);
33+
gap: 0.5rem;
34+
border-bottom: 1px solid var(--sl-color-gray-7);
35+
36+
& .type {
37+
justify-self: end;
38+
color: var(--sl-color-accent-low);
39+
margin-top: 0;
40+
}
41+
42+
& .doc {
43+
padding-bottom: 0.5rem;
44+
grid-column: 1 / -1;
45+
grid-row: 2;
46+
}
47+
}
48+
</style>

docs/components/signature.astro

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
import SignatureItem from "./signatureItem.astro";
3+
4+
5+
function tokenize(input) {
6+
// Split by special characters while keeping them
7+
const regex = /([<>,])/g;
8+
return input
9+
.split(regex)
10+
.map((token) => token.trim())
11+
.filter(Boolean);
12+
}
13+
14+
function parseTokens(tokens) {
15+
let index = 0;
16+
17+
const parseNode = () => {
18+
const path = tokens[index++]; // Read the current token and increment index
19+
let genericTypeParameters = [];
20+
21+
if (tokens[index] === "<") {
22+
// Check for generics
23+
index++; // Consume "<"
24+
while (tokens[index] !== ">") {
25+
genericTypeParameters.push(parseNode());
26+
if (tokens[index] === ",") index++; // Consume ","
27+
}
28+
index++; // Consume ">"
29+
}
30+
31+
return { path, genericTypeParameters };
32+
};
33+
34+
return parseNode();
35+
}
36+
37+
function parse(input) {
38+
const tokens = tokenize(input);
39+
return parseTokens(tokens);
40+
}
41+
42+
43+
const { signature, typesInOwnModule } = Astro.props;
44+
const tree = parse(signature);
45+
---
46+
47+
<div class="signature">
48+
{(<SignatureItem typesInOwnModule={typesInOwnModule} item={tree} />)}
49+
</div>

docs/components/signatureItem.astro

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
import { createAPIModuleLink } from "../utils.js";
3+
const { item, typesInOwnModule } = Astro.props;
4+
5+
let link;
6+
if (typesInOwnModule && typesInOwnModule.has(item.path)) {
7+
link = `#${item.path}`;
8+
}
9+
10+
if (item.path.startsWith("WebAPI.")) {
11+
const paths = item.path.split(".");
12+
if (paths.length === 3) {
13+
link = `${import.meta.env.BASE_URL}/${createAPIModuleLink(paths[1])}#${paths[2]}`;
14+
}
15+
}
16+
17+
const genericTypeParameters = item.genericTypeParameters || [];
18+
---
19+
20+
<span>
21+
{link ? <a href={link}>{item.path}</a> : item.path}{
22+
genericTypeParameters.length > 0 && (
23+
<>
24+
{"<"}
25+
{genericTypeParameters.map((subItem) => (
26+
<Astro.self typesInOwnModule={typesInOwnModule} item={subItem} />
27+
))}
28+
{">"}
29+
</>
30+
)
31+
}
32+
</span>
33+
<style>
34+
span {
35+
display: inline-flex;
36+
color: var(--sl-color-accent-low);
37+
}
38+
</style>

docs/components/value.astro

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
import SignatureItem from "./signatureItem.astro";
3+
const { parameters, returnType } = Astro.props;
4+
---
5+
6+
<div class="value">
7+
<h4>Parameters</h4>
8+
{
9+
parameters.map((p) => (
10+
<SignatureItem item={p} />
11+
))
12+
}
13+
<h4>Return type</h4>
14+
<SignatureItem item={returnType} />
15+
</div>
16+
<style>
17+
.value {
18+
display: flex;
19+
flex-direction: column;
20+
padding-bottom: 1rem;
21+
border-bottom: 1px solid var(--sl-color-gray-7);
22+
23+
.signature {
24+
margin-top: 0rem;
25+
}
26+
}
27+
</style>

docs/pages/apidocs/[API].astro

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
---
2+
import * as path from "node:path";
3+
import { existsSync } from "fs";
4+
import { apiModules, getDoc, createTypeModuleLink } from "../../utils";
5+
import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro";
6+
import { Code } from "@astrojs/starlight/components";
7+
import { micromark } from "micromark";
8+
import Record from "../../components/record.astro";
9+
10+
export async function getStaticPaths() {
11+
return apiModules.map((apiModule) => {
12+
return {
13+
params: {
14+
API: apiModule.apiRouteParameter,
15+
},
16+
props: apiModule,
17+
};
18+
});
19+
}
20+
21+
function showRecord(details) {
22+
return details && details.kind === "record" && details.items.length > 0;
23+
}
24+
25+
function getModuleFileName(typeName) {
26+
return `${typeName[0].toUpperCase()}${typeName.slice(1)}`;
27+
}
28+
29+
function showModule(typeName, filePath) {
30+
const moduleFileName = `${getModuleFileName(typeName)}.res`;
31+
const potentialPath = path.join(filePath.replace(".res", ""), moduleFileName);
32+
return existsSync(potentialPath);
33+
}
34+
35+
const { moduleName, filePath, link } = Astro.props;
36+
37+
const docInfo = await getDoc(filePath);
38+
39+
const types = docInfo.items
40+
.filter((item) => item.kind === "type")
41+
.sort((a, b) => a.name.localeCompare(b.name))
42+
.map((type) => {
43+
const documentation =
44+
type.docstrings && micromark(type.docstrings.join("\n"));
45+
return {
46+
name: type.name,
47+
documentation,
48+
signature: type.signature,
49+
detail: type.detail,
50+
};
51+
});
52+
53+
const typesInOwnModule = new Set(types.map((t) => t.name));
54+
55+
const typeHeadings = types.map((type) => ({
56+
depth: 3,
57+
slug: type.name,
58+
text: type.name,
59+
}));
60+
61+
const frontmatter = {
62+
title: moduleName,
63+
};
64+
65+
const headings = [
66+
{
67+
depth: 2,
68+
slug: "types",
69+
text: "Types",
70+
},
71+
...typeHeadings,
72+
];
73+
---
74+
75+
<StarlightPage frontmatter={frontmatter} headings={headings}>
76+
<div id="apidocs">
77+
<h2 id="types">Types</h2>
78+
{
79+
types.map((type) => (
80+
<div class="rescript_type">
81+
<h3 id={type.name}>{type.name}</h3>
82+
<div set:html={type.documentation} />
83+
<Code lang="ReScript" code={type.signature} />
84+
{showRecord(type.detail) ? (
85+
<Record
86+
name={type.name}
87+
typesInOwnModule={typesInOwnModule}
88+
{...type.detail}
89+
/>
90+
) : null}
91+
{showModule(type.name, filePath) && (
92+
<>
93+
<h4>Module</h4>
94+
<p>
95+
There are methods and helpers defined in{" "}
96+
<a
97+
href={`${import.meta.env.BASE_URL}/${createTypeModuleLink(link, type.name)}`}
98+
>
99+
{getModuleFileName(type.name)}
100+
</a>
101+
.
102+
</p>
103+
</>
104+
)}
105+
</div>
106+
))
107+
}
108+
</div>
109+
</StarlightPage>
110+
<style>
111+
#apidocs .rescript_type {
112+
margin-block: 2rem;
113+
}
114+
</style>

0 commit comments

Comments
 (0)