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

feat: added rust api documentation #1400

Closed
wants to merge 4 commits into from
Closed
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
4 changes: 4 additions & 0 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ export default defineConfig({
label: 'JavaScript API',
link: '2/reference/js',
},
{
label: 'Rust API',
link: '2/reference/rust',
},
{
label: 'Rust API (via Docs.rs)',
// TODO: Is there a way to link this to the latest pre-released version?
Expand Down
37 changes: 37 additions & 0 deletions packages/rust-api-generator/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { parseCrate, crateResolver } from './parser';
import { generatePage } from './generator';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

// IMPORTANT: Keep these up to date and correct
const baseUrl = '/2/reference/rust';
const rootDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
const docsPath = join(rootDir, 'src', 'content', 'docs', '2', 'reference', 'rust');
const documentCrates = [
'tauri',
'tauri-build',
'tauri-codegen',
'tauri-macros',
'tauri-runtime',
'tauri-runtime-wry',
'tauri-utils',
];

async function main() {
console.log('Starting');
for (const crate of documentCrates) {
console.log(`Documenting crate: ${crate}`);
const rustdoc = await crateResolver(crate);
if (!rustdoc) {
console.error(`Crate could not be resolved: ${crate}`);
continue;
}
const pages = await parseCrate(rustdoc, `${baseUrl}/${crate}/`, join(docsPath, crate));
for (const page of pages) {
console.log(`Generating page: ${page.path}`);
await generatePage(page);
}
}
}

main();
107 changes: 107 additions & 0 deletions packages/rust-api-generator/generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import type { Page, PageContent, PageType } from './types';
import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync } from 'node:fs';
import { join, dirname } from 'node:path';

/**
* Write a single page to disk
*/
export async function generatePage(page: Page) {
mkdirSync(dirname(page.path), { recursive: true });
writeFileSync(page.path, page.content);
}

/**
* Generates content for a page
*
* @param type
* @returns
*/
export async function generateContent(type: PageType, content: PageContent): Promise<string> {
switch (type) {
case 'crate':
return await generateContentModule(content, true);
case 'module':
return await generateContentModule(content);
case 'struct':
return await generateContentStruct(content);
default:
throw Error('Unknown content type');
}
}

/**
* Generates a page header
* @date 8/17/2023 - 12:23:19 AM
*
* @param {string} title
* @returns {string}
*/
function header(title: string): string {
return `---
title: '${title}'
editUrl: false
prev: false
next: false
---
`;
}

/**
* Empty string if docs is null, only use first line
* @date 8/17/2023 - 12:23:46 AM
*
* @param {(undefined | null | string)} docs
* @returns {string}
*/
function fixDocs(docs: undefined | null | string): string {
if (!docs) return '';
return docs.split('\n')[0];
}

function members(content: PageContent): string {
const output: string[] = [];
const modules = content.members.filter((val) => 'module' in val.item.inner);
if (modules.length > 0) output.push('## Modules\n\n');
for (const member of modules) {
output.push(
`- [${member.item.name}](${content.moduleUrl}${member.path.path
.slice(1)
.join('/')}): ${fixDocs(member.item.docs)}`
);
}
const structs = content.members.filter((val) => 'struct' in val.item.inner);
if (structs.length > 0) output.push('## Structs\n\n');
for (const member of structs) {
output.push(
`- [${member.item.name}](${content.moduleUrl}${member.item.name}): ${fixDocs(
member.item.docs
)}`
);
}
return output.join('\n');
}

/**
* Generates content for either a crate or a module, they are virtually identical
*
* @param isCrate
* @returns
*/
export async function generateContentModule(
content: PageContent,
isCrate: boolean = false
): Promise<string> {
return `${header((isCrate ? 'Crate' : 'Module') + ' ' + content.title)}

${content.description}
${members(content)}`;
}

/**
* Generates content for a struct
*
* @returns
*/
export async function generateContentStruct(content: PageContent): Promise<string> {
return '';
}
15 changes: 15 additions & 0 deletions packages/rust-api-generator/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "rust-api-generator",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "tsm ./build.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"tsm": "^2.3.0"
}
}
107 changes: 107 additions & 0 deletions packages/rust-api-generator/parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import type { RustDoc, Page, PageMember, ID } from './types';
import { PageMemberType, PageType } from './types/pages';
import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { generateContent } from './generator';

async function parseModules(
rootPath: string,
baseUrl: string,
rustdoc: RustDoc,
isCrate: boolean = false
): Promise<Page[]> {
const pages: Page[] = [];
if (isCrate) {
const item = rustdoc.index[rustdoc.root];
const path = rustdoc.paths[rustdoc.root];
const members: PageMember[] = item.inner.module.items.map((id: ID) => {
const member: PageMember = {
type: PageMemberType.struct,
item: rustdoc.index[id],
path: rustdoc.paths[id],
};
return member;
});
pages.push({
type: PageType.crate,
path: join(rootPath, path.path.slice(1).join('/'), 'index.md'),
content: await generateContent(PageType.crate, {
title: path.path.join('::'),
description: item.docs ?? '',
moduleUrl: baseUrl,
members: members,
}),
});
} else {
for (const id in rustdoc.paths) {
const path = rustdoc.paths[id];
if (path.kind !== 'module' || path.crate_id !== 0 || id === rustdoc.root) continue;
const item = rustdoc.index[id];
const members: PageMember[] = item.inner.module.items.map((id: ID) => {
const member: PageMember = {
type: PageMemberType.struct,
item: rustdoc.index[id],
path: rustdoc.paths[id],
};
return member;
});
pages.push({
type: PageType.module,
path: join(rootPath, path.path.slice(1).join('/'), 'index.md'),
content: await generateContent(PageType.module, {
title: path.path.join('::'),
description: item.docs ?? '',
moduleUrl: baseUrl,
members: members,
}),
});
}
}
return pages;
}

async function parseStructs(rootPath: string, baseUrl: string, rustdoc: RustDoc): Promise<Page[]> {
const pages: Page[] = [];
return pages;
}

/**
* Parses a single JSON file
*/
export async function parseCrate(
rustdoc: RustDoc,
baseUrl: string,
rootPath: string
): Promise<Page[]> {
let pages: Page[] = [];
const crateItem = rustdoc.index[rustdoc.root];

for (const type in PageType) {
switch (type) {
case 'crate':
pages = pages.concat(await parseModules(rootPath, baseUrl, rustdoc, true));
case 'module':
pages = pages.concat(await parseModules(rootPath, baseUrl, rustdoc));
case 'struct':
pages = pages.concat(await parseStructs(rootPath, baseUrl, rustdoc));
}
}
return pages;
}

/**
* Resolves the path to a json file for external crates
* @param crate
*/
export async function crateResolver(crate: string): Promise<RustDoc | null> {
const targetFolder = '../tauri/target';
for (const file of readdirSync(targetFolder)) {
if (file !== crate + '.json') continue;

const rustdoc: RustDoc = JSON.parse(readFileSync(join(targetFolder, file), 'utf-8'));
rustdoc.name = crate;
return rustdoc;
}
return null;
}
2 changes: 2 additions & 0 deletions packages/rust-api-generator/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './rustdoc';
export * from './pages';
64 changes: 64 additions & 0 deletions packages/rust-api-generator/types/pages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { Item, ItemSummary } from './rustdoc';

/**
* A `Page` to be rendered
*/
export interface Page {
/**
* The type of the page
*/
type: PageType;
/**
* Path to the page .md file
*/
path: string;
/**
* Contents of the page
*/
content: string;
}

/**
* Page content used for templating
*/
export interface PageContent {
/**
* Page title
*/
title: string;
/**
* URL to the page
*/
moduleUrl: string;
/**
* Page description
*/
description: string;
/**
* Member items of this page
*/
members: PageMember[];
}

export interface PageMember {
type: PageMemberType;
item: Item;
path: ItemSummary;
}

/**
* The type of the page
*/
export enum PageMemberType {
'module' = 'module',
'struct' = 'struct',
}

/**
* The type of the page
*/
export enum PageType {
'crate' = 'crate',
'module' = 'module',
'struct' = 'struct',
}
Loading