import { join } from "path"; import { readFileSync, writeFileSync } from "fs"; const { execSync } = require("child_process"); const marked = require("marked"); const hljs = require("highlight.js"); const highlight = () => { const raw = {} as any; return { raw, fn: function (code: string, language: string) { raw.code = code; raw.language = language; const validLanguage = hljs.getLanguage(language) ? language : "plaintext"; return hljs.highlight(validLanguage, code).value; } }; }; const markedOptions = { highlight: highlight().fn }; type Category = { title: string; children: number[]; }; type Group = { title: string; kind: number; children: Child[]; categories: Category[]; }; type Child = { children: Child[]; groups: Group[]; } & Method; const api = JSON.parse( readFileSync(join(__dirname, "../../docs/typedoc/api.json"), "utf8") ) as Child; const ethereum = api.children[0]; function x(unsafe: string) { return unsafe .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function e(s: string) { return encodeURIComponent(s); } type Tag = { tag: string; text: string; }; type Type = { name: string; type: string; types: Type[]; typeArguments: Type[]; elements: Type[]; elementType: Type; value?: any; declaration?: Child; }; type Comment = { shortText: string; text: string; tags: Tag[]; returns: string; }; type Method = { id: number; name: string; signatures: { name: string; type: Type; parameters: { name: string; type: Type; flags: { isOptional: boolean; }; comment?: Comment; }[]; comment: Comment; }[]; type: Type; kindString: string; flags: any; sources: any[]; }; function renderReturns(method: Method) { const comment = method.signatures[0].comment && method.signatures[0].comment.returns ? method.signatures[0].comment.returns : null; let returnType = renderReturnType(method); if (returnType.includes("Quantity")) { returnType = "QUANTITY"; } if (returnType.includes("Data")) { returnType = "DATA"; } const returnTypeHtml = marked( `\`\`\`typescript function g(): ${returnType} \`\`\``, markedOptions ) .replace( 'function g(): ', "" ) .replace(/<\/?pre>/g, ""); let returnHtml = returnTypeHtml.replace(/\n/g, "") + (comment ? marked.parse(": " + comment, markedOptions) : ""); return `
returns
${returnHtml}
`; } function renderArgs(method: Method) { const signature = method.signatures[0]; let params: string[] = []; if (signature.parameters) { params = signature.parameters.map(param => { const name = param.name + (param.flags.isOptional ? "?" : ""); let type = getTypeAsString(param.type); if (type.includes("Tag")) { type = type.replace("Tag", "TAG"); } const md = `\`\`\`typescript function (${name}: ${type}) \`\`\``; const html = marked(md, markedOptions) .replace(/function<\/span> \((.*)\)/, "$1") .replace(/<\/?pre>/g, ""); return `
  • ${html}${ param.comment && param.comment.text && param.comment.text.trim().length ? `: ${marked.parseInline(param.comment.text, markedOptions)}` : "" }
  • `; }); return `
    arguments
    `; } return ``; } function renderMethodLink(method: Method) { const title = x(method.name); return `${title}`; } function renderMethodDocs(method: Method) { return `

    ${renderSignature(method)}

    ${renderSource(method)}
    ${renderMethodComment(method)}
    ${renderArgs(method)}
    ${renderReturns(method)}
    ${renderTags(method)}
    `; } function renderMethodComment(method: Method) { const signature = method.signatures[0]; return signature.comment ? ` ${ signature.comment.shortText ? marked(signature.comment.shortText, markedOptions) : "" } ${ signature.comment.text ? marked(signature.comment.text, markedOptions) : "" } ` : ""; } function renderTag(method: Method, tag: Tag, i: number) { switch (tag.tag) { case "example": const h = highlight(); const code = marked(tag.text, { highlight: h.fn }); const lang = h.raw.language || "javascript"; const height = Math.max(100, code.split(/\n/).length * 19 + 20); return `
    ${x(tag.tag)}
    ${code}
    `; break; default: return `
    ${x(tag.tag)}
    ${marked(tag.text, markedOptions)}
    `; } } function renderSource(method: Method) { const source = method.sources[0]; let branchOrCommitHash = "master"; try { branchOrCommitHash = execSync("git rev-parse HEAD", { encoding: "utf8" }).trim() || "master"; } catch (e) {} return `source`; } function renderTags(method: Method) { const signature = method.signatures[0]; if ( signature.comment && signature.comment.tags && signature.comment.tags.length ) { return signature.comment.tags.map(renderTag.bind(null, method)).join("\n"); } return ""; } function getTypeAsString(type: Type): string { switch (type.type) { case "union": return type.types.map(getTypeAsString).join(" | "); case "array": return `${getTypeAsString(type.elementType)}[]`; case "reflection": if (type.declaration) { return `{ ${type.declaration.children .map(child => { return `${child.name}: ${getTypeAsString(child.type)}`; }) .join(", ")} }`; } else { return "object"; } case "intrinsic": case "reference": return x(type.name); case "tuple": return `[${ type.elements ? type.elements.map(getTypeAsString).join(", ") : "" }]`; case "literal": // outputs a string literal like `He said, "hello, world"!` as // the string `"He said, \"hello, world\"!"` return `"${type.value.replace(/"/g, '\\"')}"`; case "intersection": return type.types.map(getTypeAsString).join(" & "); case "conditional": return getTypeAsString((type as any).checkType); default: console.error(type); throw new Error(`Unhandled type: ${type.type}`); } } function renderReturnType(method: Method) { const signature = method.signatures[0]; let returnType = signature.type.name; if (signature.type.typeArguments.length) { let typeArgs = signature.type.typeArguments.map(getTypeAsString); typeArgs = typeArgs.map((arg: string) => { return arg.replace(/Quantity/g, "QUANTITY").replace(/Data/g, "DATA"); }); returnType = `${returnType}<${typeArgs.join(", ")}>`; } return returnType; } function renderSignature(method: Method) { const signature = method.signatures[0]; let params: string[] = []; if (signature.parameters) { params = signature.parameters.map(param => { let type = getTypeAsString(param.type); if (type.includes("Tag")) { type = type.replace("Tag", "TAG"); } return `${x(param.name)}${param.flags.isOptional ? "?" : ""}: ${type}`; }); } const sig = `function ${method.name}(${params.join( ", " )}): ${renderReturnType(method)}`; return hljs .highlight("typescript", sig) .value.replace('function', ""); } /** * Array of api method namespaces in the order they should appear on the page. */ const orderedNamespaces = [ "eth", "debug", "evm", "miner", "personal", "txpool", "web3", "db", "rpc", "net", "bzz", "shh", "other" ]; const groupedMethods: { [group: string]: Method[] } = {}; for (const child of ethereum.children) { const { name } = child; if (name === "constructor" || child.kindString !== "Method") continue; const parts = name.split("_"); let namespace = "other"; if (parts.length > 1) { if (!parts[1]) { console.warn(`method name is only namespace ${name}`); // we can't put this one on the page and have it look right, so skip continue; } if (parts[0]) { namespace = parts[0]; } } if (namespace === "other") { console.warn(`method does not have namespace prefix: ${name}`); } if (!orderedNamespaces.includes(namespace)) { console.warn( `method namespace is not included in set of namespaces for ordering: ${name}` ); orderedNamespaces.push(namespace); } const methodsInGroup = groupedMethods[namespace]; if (methodsInGroup) { methodsInGroup.push(child); } else { groupedMethods[namespace] = [child]; } } const methodListByGroup: string[] = []; const methodDocs: string[] = []; for (const namespace of orderedNamespaces) { const methodsInGroup = groupedMethods[namespace]; if (methodsInGroup) { const methodListForGroup: string[] = []; methodDocs.push( `

    ${namespace} namespace

    ` ); for (const method of methodsInGroup) { if (method) { methodListForGroup.push(renderMethodLink(method)); methodDocs.push(renderMethodDocs(method)); } } methodListByGroup.push( `
    ${namespace}
    ` ); } } const preamble = marked(`This reference describes all Ganache JSON-RPC methods and provides interactive examples for each method. The interactive examples are powered by [Ganache in the Browser](https://github.com/trufflesuite/ganache/#browser-use) and demonstrate using Ganache programmatically [as an EIP-1193 provider](https://github.com/trufflesuite/ganache/#as-an-eip-1193-provider-only). Try running these examples to see Ganache in action! Each editor can be changed to further test Ganache features. **Pro Tip**: You can define your own provider by adding \`const provider = ganache.provider({})\` to the start of any example and passing in your [startup options](https://trufflesuite.com/docs/ganache/reference/cli-options/).`); const html = ` Ganache Ethereum JSON-RPC Documentation
    ganache logo

    Ganache

    Ganache JSON-RPC Documentation

    ${preamble}

    ${methodDocs.join("")}
    `; writeFileSync(join(__dirname, "../../docs/index.html"), html);