Skip to content
Merged
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
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Generates examples in nodes.tsx from a local copy of the openapi spec. Make sure you pull the latest changes
# in the substrate repo and run `make generate` in that repo before running this script.

generate:
poetry run python codegen/guides_generate.py
npx prettier --write ./components/nodes.tsx
122 changes: 122 additions & 0 deletions codegen/guides_generate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""
Generates examples in nodes.tsx from a local copy of the openapi spec. Make sure you pull the latest change
in the substrate repo and run `make generate` before running this script.

Run this from docs root. The main substrate repo should be colocated with the docs repo.
Or you can set the SUBSTRATE_HOME environment variable.
"""

import json
import os
from jinja2 import Template

SUBSTRATE_HOME = os.environ.get("SUBSTRATE_HOME", "../substrate")

spec_path = f"{SUBSTRATE_HOME}/site/public/openapi.json"
inout_path = f"{SUBSTRATE_HOME}/site/app/nodes/examples/inout"


openapi = {}
with open(spec_path) as file:
openapi = json.load(file)

paths = openapi["paths"]
schemas = openapi["components"]["schemas"]
keys = list(paths.keys())
nodes = list(map(lambda x: x.replace("/", ""), keys))
nodes_dict = {}
for key in keys:
if "RunPython" in key:
continue
if "If" in key:
continue
if "Box" in key:
continue
# get operation data
node_data = paths[key]
tags = node_data["post"]["tags"]
info = {}
info["description"] = node_data["post"]["description"]
for tag in tags:
k, v = tag.split(":")
info[k] = v
node = key.replace("/", "")
# get input properties
inputs = schemas[f"{node}In"]["properties"]
input_list = []
required = schemas[f"{node}In"].get("required", [])
for key, value in inputs.items():
prop = {"name": key, "required": key in required, **value}
# resolve refs
if prop.get("$ref"):
prop = {"name": key, **value, **schemas[prop["$ref"].split("/")[-1]]}
elif prop["type"] == "array" and prop["items"].get("$ref"):
prop["items"] = schemas[prop["items"]["$ref"].split("/")[-1]]
input_list.append(prop)
info["inputs"] = input_list
# get output properties
outputs = schemas[f"{node}Out"]["properties"]
output_list = []
for key, value in outputs.items():
prop = {"name": key, **value}

# resolve refs
if prop.get("$ref"):
prop = {"name": key, **value, **schemas[prop["$ref"].split("/")[-1]]}
elif prop["type"] == "array" and prop["items"].get("$ref"):
prop["items"] = schemas[prop["items"]["$ref"].split("/")[-1]]
output_list.append(prop)
info["outputs"] = output_list
# set data
nodes_dict[node] = info


def py_path(node: str):
return f"{inout_path}/{node}In.py"


def ts_path(node: str):
return f"{inout_path}/{node}In.ts"


def json_path(node: str):
return f"{inout_path}/{node}Out.json"


node_to_ts = {}
node_to_py = {}
node_to_json = {}
for node in nodes:
py_content = ""
ts_content = ""
js_content = ""
with open(py_path(node)) as f:
py_content = f.read()
with open(ts_path(node)) as f:
ts_content = f.read()
with open(json_path(node)) as f:
json_content = f.read()
node_to_ts[node] = ts_content
node_to_py[node] = py_content
node_to_json[node] = json_content


nodes_list = []
for key, value in nodes_dict.items():
node_data = {k: v for k, v in value.items()}
node_data["name"] = key
node_data["example"] = node_to_py[key]
node_data["output"] = node_to_json[key]
nodes_list.append(node_data)
all_node_names = [n["name"] for n in nodes_list]


with open("codegen/nodes.j2") as template_f:
interface_template = Template(template_f.read())
data = {
"nodes": nodes_list,
}
rendered_template = interface_template.render(**data)
output = "./components/nodes.tsx"
with open(output, "w") as file:
file.write(rendered_template)
82 changes: 82 additions & 0 deletions codegen/nodes.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
Notes:
- Suppressing the hydration warning using details with useEffect
- https://nextjs.org/docs/messages/react-hydration-error#solution-1-using-useeffect-to-run-on-the-client-only
*/
"use client";
import Link from "next/link";
import { useState, useEffect, useRef } from "react";
import ReactMarkdown from "react-markdown"

// https://www.robinwieruch.de/react-hook-detect-click-outside-component/
const useOutsideClick = (callback: any) => {
const ref = useRef();
useEffect(() => {
const handleClick = (event: any) => {
// @ts-ignore
if (ref.current && !ref.current.contains(event.target)) {
callback();
}
};
document.addEventListener('click', handleClick);
return () => {
document.removeEventListener('click', handleClick);
};
}, [ref]);
return ref;
};


{% for d in nodes -%}
export function {{d["name"]}}() {
const [isClient, setIsClient] = useState(false);
const [open, setOpen] = useState(false);

useEffect(() => {
setIsClient(true);
}, []);

const ref = useOutsideClick(() => {
setOpen(false);
});

return isClient ? (
<details className="inline" ref={ref} open={open}>
<summary
onClick={() => {
setTimeout(() => {
setOpen(true);
}, 100);
}}
className="font-mono text-sm text-[#6E42BD] cursor-pointer">
{"{{d["name"]}}"}
</summary>
<div className="absolute max-w-sm z-50 text-xs text-black bg-white rounded drop-shadow p-1">
<ReactMarkdown className="text-xs p-2">
{"{{d["description"]}}"}
</ReactMarkdown>
<div className="flex w-full text-[14px] text-[#ABA7A6] justify-between">
<div className="">{"Example"}</div>
<Link
href={`https://substrate.run/nodes#{{d["name"]}}`}
className="text-[14px] text-[#ABA7A6] underline"
target="_blank"
>
{"API Reference"}
</Link>
</div>
<div className="whitespace-pre px-2 pb-2 pt-1 font-mono text-[13px] overflow-x-auto">
{% raw %}{{% endraw %}`{{d["example"]}}`{% raw %}}{% endraw %}
</div>
<div className="text-[14px] text-[#ABA7A6]">{"Output"}</div>
<div className="whitespace-pre px-2 pb-2 pt-1 font-mono text-[13px] overflow-x-auto">
{% raw %}{{% endraw %}`{{d["output"]}}`{% raw %}}{% endraw %}
</div>
</div>
</details>
) : (
<>{"{{d["name"]}}"}</>
);
}

{% endfor %}
92 changes: 92 additions & 0 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[tool.poetry]
name = "docs"
version = "0.1.0"
description = ""
authors = ["rishabh <rishabh@substrate.run>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.10"
jinja2 = ">=3.1.3"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"