Skip to content

Commit

Permalink
New custom component for rendering the client outputs (#1818)
Browse files Browse the repository at this point in the history
New features
- Always show the typespec above
- sync the tabs
-  just defining the code blocks without the extra `TabItem`
<img width="790" alt="image"
src="https://github.com/user-attachments/assets/3780ed65-5792-46d7-a524-c69ca5eaa59e">
  • Loading branch information
timotheeguerin authored Nov 7, 2024
1 parent 8c661a2 commit b255746
Show file tree
Hide file tree
Showing 35 changed files with 744 additions and 1,366 deletions.
1 change: 1 addition & 0 deletions cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ words:
- rpaasoneboxacr
- rpass
- SERVICERP
- seti
- tcgc
- userrp
- vnet
Expand Down
371 changes: 12 additions & 359 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions website/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// @ts-check
import react from "@astrojs/react";
import starlight from "@astrojs/starlight";
import tailwind from "@astrojs/tailwind";
import { TypeSpecLang } from "@typespec/astro-utils/shiki";
import { processSidebar } from "@typespec/astro-utils/sidebar";
import astroExpressiveCode from "astro-expressive-code";
Expand Down Expand Up @@ -41,7 +40,6 @@ export default defineConfig({
},
}),
react(),
tailwind({ applyBaseStyles: false }),
],
markdown: {
// @ts-expect-error wrong type
Expand Down
13 changes: 8 additions & 5 deletions website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"@astrojs/check": "^0.9.4",
"@astrojs/react": "^3.6.2",
"@astrojs/starlight": "^0.28.5",
"@astrojs/tailwind": "^5.1.2",
"@docsearch/css": "^3.6.3",
"@docsearch/js": "^3.6.3",
"@fluentui/react-components": "~9.55.0",
Expand All @@ -30,21 +29,25 @@
"typescript": "~5.6.3"
},
"devDependencies": {
"@azure-tools/typespec-autorest-canonical": "workspace:~",
"@azure-tools/typespec-autorest": "workspace:~",
"@azure-tools/typespec-autorest-canonical": "workspace:~",
"@azure-tools/typespec-azure-core": "workspace:~",
"@azure-tools/typespec-azure-playground-website": "workspace:~",
"@azure-tools/typespec-azure-portal-core": "workspace:~",
"@azure-tools/typespec-azure-resource-manager": "workspace:~",
"@azure-tools/typespec-azure-rulesets": "workspace:~",
"@azure-tools/typespec-client-generator-core": "workspace:~",
"@typespec/internal-build-utils": "workspace:~",
"@types/react-dom": "~18.3.0",
"@types/hast": "^3.0.4",
"@types/react": "~18.3.11",
"@types/react-dom": "~18.3.0",
"@types/remark-heading-id": "^1.0.0",
"@typespec/astro-utils": "workspace:~",
"@typespec/internal-build-utils": "workspace:~",
"astro-expressive-code": "^0.37.0",
"astro-rehype-relative-markdown-links": "^0.15.0",
"remark-heading-id": "^1.0.1"
"hast-util-to-html": "^9.0.3",
"rehype": "^13.0.2",
"remark-heading-id": "^1.0.1",
"unist-util-visit": "^5.0.0"
}
}
14 changes: 14 additions & 0 deletions website/src/components/client-tabs/client-tab-item.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
import type { Language } from "./process-client";
export interface Props {
lang: Language;
}
if (!Astro.props.lang) {
throw new Error("Missing required prop: lang");
}
---

<client-tab-item data-language={Astro.props.lang}>
<slot />
</client-tab-item>
21 changes: 21 additions & 0 deletions website/src/components/client-tabs/client-tabs.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
import { TabItem, Tabs } from "@astrojs/starlight/components";
import { processContent } from "./process-client.js";
const content = await Astro.slots.render("default");
const result = processContent(content);
---

<Fragment set:html={result.typespec} />

<Tabs syncKey="client-outputs">
{
result.outputs.map((value) => {
return (
<TabItem label={value.label} icon={value.icon as any}>
<Fragment set:html={value.html} />
</TabItem>
);
})
}
</Tabs>
3 changes: 3 additions & 0 deletions website/src/components/client-tabs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default as ClientTabItem } from "./client-tab-item.astro";
export { default as ClientTabs } from "./client-tabs.astro";
export type { Language } from "./process-client.js";
129 changes: 129 additions & 0 deletions website/src/components/client-tabs/process-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import type { Element, ElementContent } from "hast";
import { toHtml } from "hast-util-to-html";
import { rehype } from "rehype";
import { CONTINUE, EXIT, visit } from "unist-util-visit";

/**
* Update this list to register a new language
* For icons see https://starlight.astro.build/reference/icons/#all-icons
*/
const KnownLanguages = {
python: {
label: "Python",
icon: "seti:python",
},
csharp: {
label: "CSharp",
icon: "seti:c-sharp",
},
typescript: {
label: "TypeScript",
icon: "seti:typescript",
},
java: {
label: "Java",
icon: "seti:java",
},
go: {
label: "Go",
icon: "seti:go",
},
tcgc: {
label: "tcgc",
icon: "seti:json",
},
} as const;

export type Language = keyof typeof KnownLanguages;

function getLanguage(node: ElementContent): Language | "typespec" {
const language = (node as any).properties.dataLanguage as string;
if (!language) {
throw new Error(`Missing language data code block: ${toHtml(node)}`);
}
if (language !== "typespec" && !KnownLanguages[language as Language]) {
throw new Error(`Unknown language '${language}' used in code block in <ClientTabs>`);
}

return language as any;
}

function getLanguageForCodeBlock(el: ElementContent): Language | "typespec" {
let resolved;
visit(el, "element", (node) => {
if (node.tagName !== "pre" || !node.properties) {
return CONTINUE;
}
const language = node.properties.dataLanguage as string;
if (!language) {
throw new Error("Missing language code code block");
}
if (language !== "typespec" && !KnownLanguages[language as Language]) {
throw new Error(`Unknown language '${language}' used in code block in <ClientTabs>`);
}

resolved = language;
return EXIT;
});
if (!resolved) {
throw new Error("Couldn't find language");
}
return resolved;
}

const tabsProcessor = rehype()
.data("settings", { fragment: true })
.use(() => {
return (tree: Element, file) => {
const results: any[] = (file.data.results = []);
for (const item of tree.children) {
if (item.type === "element" && item.tagName === "client-tab-item") {
const language = getLanguage(item);
results.push({ language, html: toHtml(item.children) });
} else if (
item.type === "element" &&
(item.properties.className as any)?.includes("expressive-code")
) {
const language = getLanguageForCodeBlock(item);
results.push({ language, html: toHtml(item) });
} else {
throw new Error(
`Unexpected item should only have code blocks or ClientTabItem but got:\n${toHtml(item)}`,
);
}
}
};
});

export interface Result {
typespec: string;
outputs: Array<{
language: Language;
label: string;
icon?: string;
html: string;
}>;
}
export function processContent(html: string): Result {
const file = tabsProcessor.processSync({ value: html });
const codeBlocks = file.data.results as any[];
let typespec;
const outputs: Result["outputs"] = [];
for (const item of codeBlocks) {
if (item.language === "typespec") {
typespec = item.html;
} else {
outputs.push({
language: item.language as any,
label: KnownLanguages[item.language as Language].label,
icon: KnownLanguages[item.language as Language].icon,
html: item.html,
});
}
}
if (typespec === undefined) {
throw new Error("Missing typespec code block");
}

return { typespec, outputs };
}
4 changes: 0 additions & 4 deletions website/src/components/docusaurus/theme/TabItem.ts

This file was deleted.

4 changes: 0 additions & 4 deletions website/src/components/docusaurus/theme/Tabs.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: How to generate client libraries
---

import { Tabs, TabItem } from "@astrojs/starlight/components";
import { ClientTabs, ClientTabItem } from "@components/client-tabs";

This page outlines the steps to build a client library based on your TypeSpec specification. It provides a guide for the whole process from initial API design to the release of SDKs. please visit https://aka.ms/azsdk/dpcodegen.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Setup for SDK customization
---

import { Tabs, TabItem } from "@astrojs/starlight/components";
import { ClientTabs, ClientTabItem } from "@components/client-tabs";

This page explains how to setup customization of a generator if necessary.

Expand Down
Loading

0 comments on commit b255746

Please sign in to comment.