Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[docs] Render proto files #21090

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/content/references/fullnode-protocol.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
title: Full Node Protocol
description: The Full node protocol API is available on all Sui Full nodes.
---

import Protocol from "../../site/src/components/Protocol";

The Full node protocol ...

<Protocol />
1 change: 1 addition & 0 deletions docs/content/sidebars/references.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const references = [
'references/cli/validator',
],
},
'references/fullnode-protocol',
{
type: 'category',
label: 'Sui IDE Support',
Expand Down
1 change: 1 addition & 0 deletions docs/site/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ const config = {
path.resolve(__dirname, `./src/plugins/descriptions`),
path.resolve(__dirname, `./src/plugins/framework`),
path.resolve(__dirname, `./src/plugins/askcookbook`),
path.resolve(__dirname, `./src/plugins/protocol`),
],
presets: [
[
Expand Down
90 changes: 90 additions & 0 deletions docs/site/src/components/Protocol/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import React, { useState, useEffect } from "react";

export default function Protocol(props) {
const { toc } = props;
const [proto, setProto] = useState(toc[0]);
const [messages, setMessages] = useState(toc[0].messages);
const [belowFold, setBelowFold] = useState(false);
const triggerY = 140;

useEffect(() => {
const handleScroll = () => {
if (window.scrollY >= triggerY) {
setBelowFold(true);
} else {
setBelowFold(false);
}
};

window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);

if (!toc) {
return;
}

const handleProtoChange = (e) => {
const selected = e.target.value;
const selectedProto = toc[selected]; // Get the selected protocol
setProto(selectedProto);
setMessages(selectedProto.messages);
window.location.hash = `#${selectedProto.link}`;
};
const handleMessageChange = (e) => {
const selected = e.target.value;
const message = proto.messages.filter((item) => {
return item.name === selected;
});
const hash = message[0].link;
window.location.hash = `#${hash}`;
};

return (
<div
className={`max-xl:hidden sticky top-16 py-4 -mx-4 z-10 backdrop-blur-sm border-sui-ghost-white dark:border-sui-ghost-dark ${belowFold ? "border-solid border-x-0 border-t-0 border-b" : ""}`}
>
<style>
{`
h2, h3 {
scroll-margin:126px !important;
}
`}
</style>
<label
className="m-2 text-xs bg-sui-white rounded-lg backdrop-blur-none"
htmlFor="proto"
>
Proto files
</label>
<select id="proto" className="p-2 w-[200px]" onChange={handleProtoChange}>
{toc.map((item, i) => {
return (
<option key={i} value={i}>
{item.name}
</option>
);
})}
</select>
<label className="mx-2 text-xs" htmlFor="messages">
Messages
</label>
<select
id="messages"
className="p-2 w-[200px]"
onChange={handleMessageChange}
>
{messages.map((message) => {
return (
<option key={message.name} value={message.name}>
{message.name}
</option>
);
})}
</select>
</div>
);
}
43 changes: 43 additions & 0 deletions docs/site/src/plugins/protocol/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

// Plugin copies file from crates and creates fullnode doc

import path from "path";
import fs from "fs";
//import Protocol from "@site/src/components/Protocol";

const PROTOCOL_PATH = path.join(
__dirname,
"../../../../../crates/sui-rpc-api/proto/documentation.json",
);
const MDX_FILENAME = "fullnode-protocol";
const MDX_TEST = new RegExp(`${MDX_FILENAME}\\.mdx$`);
const SPEC_MD = fs.readFileSync(PROTOCOL_PATH, "utf-8");

const fullnodeProtocolPlugin = (context, options) => {
return {
name: "sui-fullnode-protocol-plugin",
configureWebpack() {
return {
module: {
rules: [
{
test: MDX_TEST,
use: [
{
loader: path.resolve(__dirname, "./protocolLoader-json.js"),
options: {
protocolSpec: SPEC_MD,
},
},
],
},
],
},
};
},
};
};

module.exports = fullnodeProtocolPlugin;
212 changes: 212 additions & 0 deletions docs/site/src/plugins/protocol/protocolLoader-json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

const protocolInject = async function (source) {
this.cacheable && this.cacheable();

const callback = this.async();
const options = this.getOptions();
const spec = JSON.parse(options.protocolSpec);
const toc = [];
const createId = (name) => {
return name.replace(/[\._]/g, "-").replace(/\//g, "_");
};
for (const proto of spec.files) {
let messages = [];
let protoLink = createId(proto.name);
if (proto.messages) {
for (const message of proto.messages) {
messages.push({
name: message.name,
link: createId(message.fullName),
});
}
}
let item = { name: proto.name, link: protoLink, messages: messages };
toc.push(item);
}
const types = [];
for (const prototype of spec.scalarValueTypes) {
types.push({ name: prototype.protoType, link: prototype.protoType });
}
toc.push({
name: "Scalar Value Types",
link: "scalar-value-types",
messages: types,
});
// Unescaped curly braces mess up docusaurus render
const handleCurlies = (text) => {
let isCodeblock = false;

let final = text.split("\n");
for (const [idx, line] of final.entries()) {
if (line.includes("```")) {
isCodeblock = !isCodeblock;
}
if (!isCodeblock) {
let curlyIndices = [];
let insideBackticks = false;
for (let i = 0; i < line.length; i++) {
if (line[i] === "`") {
insideBackticks = !insideBackticks;
}
if (line[i] === "{" && !insideBackticks) {
curlyIndices.unshift(i);
}
}
for (const j of curlyIndices) {
final[idx] = [
line.substring(0, j),
"&#123;",
line.substring(j + 1),
].join("");
}
}
}
return final.join("\n");
};

let content = [`<Protocol toc={${JSON.stringify(toc)}}/>`];

let messageSort = (array) => {
return array.sort((a, b) => a.name.localeCompare(b.name));
};

for (const file of spec.files) {
content.push(`\n## ${file.name} {#${createId(file.name)}}`);
content.push(
`<div class="text-lg">\n${handleCurlies(file.description).replace(
/\n##? (.*)\n/,
"### $1",
)}\n</div>`,
);
for (const message of file.messages) {
let fields = [];
let oneofFields = [];
if (message.hasOneofs) {
for (const field of message.fields) {
if (field.isoneof === true) {
oneofFields.push(field);
} else {
fields.push(field);
}
}
} else {
fields = Object.values(message.fields);
}
const allFields = [...messageSort(fields), ...messageSort(oneofFields)];
content.push(`\n### ${message.name} {#${createId(message.fullName)}}`);
content.push(
`<div class="text-lg">\n${handleCurlies(message.description)
.replace(/</g, "&#lt;")
.replace(/^(#{1,2})\s(?!#)/gm, "### ")}\n
</div>`,
);

if (allFields.length > 0) {
const attrStyle =
"text-lg before:pr-2 before:mr-2 before:text-sm before:border before:border-solid before:border-transparent before:border-r-sui-gray-65";
const fieldStyle =
"p-2 font-medium text-lg rounded-lg bg-sui-ghost-white dark:bg-sui-ghost-dark";
const borderStyle =
"border border-solid border-y-transparent border-r-transparent border-l-sui-gray-65";
const leftArrowStyle =
"relative inline-flex items-center before:content-[''] before:border-t-transparent before:border-b-transparent before:border-solid before:border-y-5 before:border-r-0 before:border-l-8 before:border-l-sui-gray-65";
content.push(`<p class="ml-4 text-2xl">Fields</p>`);
content.push(`<div class="ml-4">`);
content.push(`<div class="grid grid-cols-12">`);
let foundoneof = false;
for (const [idx, field] of allFields.entries()) {
const hasType = field.type && field.type !== "";
const hasLabel = field.label && field.label !== "";
const hasDesc = field.description && field.description !== "";
if (field.isoneof) {
if (!foundoneof) {
content.push(
`<div class="col-span-3 ${leftArrowStyle} ${borderStyle}"><div class="${fieldStyle} py-2">One of</div></div>`,
);
foundoneof = !foundoneof;
}
}
content.push(
`<div class="${field.isoneof ? "col-start-2 col-end-12" : "col-span-12"} ${borderStyle} py-2">`,
);
content.push(`<div class="${leftArrowStyle}">`);
content.push(
`<div class="${fieldStyle} col-span-12">${field.name}</div>\n</div>`,
);
content.push(`<div class="flex flex-row ml-4 pt-2 items-center">`);
if (hasType) {
content.push(
`<div class="${attrStyle} before:content-['Type']">[${field.type}](#${createId(field.fullType)})</div>`,
);
}
if (hasLabel) {
content.push(
`<div class="${attrStyle} before:content-['Label'] ml-4">${field.label}</div>`,
);
}
content.push("</div>");
if (hasDesc) {
content.push(`<div class="ml-4 pt-2">`);
content.push(
`<div class="${attrStyle} before:content-['Description'] indent-[-88px] pl-[88px]">${handleCurlies(
field.description,
)
.replace(/\n\/?/g, " ")
.replace(/<(http.*)>/g, "$1")}</div>`,
);
content.push(`</div>`);
}
content.push("</div>");
}

content.push("</div>\n</div>");
}
}
}

content.push("\n## Scalar Value Types");
const cellStyle =
"m-2 min-w-24 max-w-[13rem] rounded-lg border border-solid align-center text-center relative border-sui-gray-65";
const titleStyle =
"p-4 pb-2 font-bold text-sui-ghost-dark dark:text-sui-ghost-white bg-sui-ghost-white dark:bg-sui-ghost-dark border border-solid border-transparent rounded-t-lg";
const valStyle =
"p-4 pt-2 border border-solid border-transparent border-t-sui-gray-65 whitespace-break-spaces";
for (const scalar of spec.scalarValueTypes) {
content.push(`\n### ${scalar.protoType}`);
content.push(
`<div class="text-lg">\n${handleCurlies(scalar.notes)}\n</div>`,
);
content.push(`<div class="flex flex-wrap">`);
content.push(
`<div class="${cellStyle}"><div class="${titleStyle}">C++</div><div class="${valStyle}">${scalar.cppType}</div></div>`,
);
content.push(
`<div class="${cellStyle}"><div class="${titleStyle}">C#</div><div class="${valStyle}">${scalar.csType}</div></div>`,
);
content.push(
`<div class="${cellStyle}"><div class="${titleStyle}">Go</div><div class="${valStyle}">${scalar.goType}</div></div>`,
);
content.push(
`<div class="${cellStyle}"><div class="${titleStyle}">Java</div><div class="${valStyle}">${scalar.javaType}</div></div>`,
);
content.push(
`<div class="${cellStyle}"><div class="${titleStyle}">PHP</div><div class="${valStyle}">${scalar.phpType}</div></div>`,
);
content.push(
`<div class="${cellStyle}"><div class="${titleStyle}">Python</div><div class="${valStyle}">${scalar.pythonType}</div></div>`,
);
content.push(
`<div class="${cellStyle}"><div class="${titleStyle}">Ruby</div><div class="${valStyle}">${scalar.rubyType}</div></div>`,
);
content.push(`</div>`);
}

return (
callback &&
callback(null, source.replace(/<Protocol ?\/>/, content.join(`\n`)))
);
};

module.exports = protocolInject;
Loading