UNPKG

@solarity/hardhat-markup

Version:

Customizable markdown documentation for smart contracts

119 lines (86 loc) 4 kB
import fs from "fs"; import fsp from "fs/promises"; import path from "path"; import { ArtifactManager } from "hardhat/types/artifacts"; import { HardhatRuntimeEnvironment } from "hardhat/types/hre"; import { SolidityBuildInfo, SolidityBuildInfoOutput } from "hardhat/types/solidity"; import { Parser } from "../parser/Parser.js"; import { ContractInfo } from "../parser/types.js"; import { MDGenerator } from "./md-generator/MDGenerator.js"; export class Generator { private artifacts: ArtifactManager; private outDir: string; private onlyFiles: string[]; private skipFiles: string[]; private verbose: boolean; private mdGenerator: MDGenerator; constructor(hre: HardhatRuntimeEnvironment) { this.artifacts = hre.artifacts; this.outDir = path.resolve(hre.config.markup.outdir); this.mdGenerator = new MDGenerator(); this.onlyFiles = hre.config.markup.onlyFiles.map((p) => this.toUnixPath(path.normalize(p))); this.skipFiles = hre.config.markup.skipFiles.map((p) => this.toUnixPath(path.normalize(p))); this.verbose = hre.config.markup.verbose; } async generate(): Promise<string[]> { console.log("\nGenerating markups..."); const _names = (await this.artifacts.getAllFullyQualifiedNames()).keys().toArray(); const filterer = async (n: any) => { const src = (await this.artifacts.readArtifact(n)).sourceName; return (this.onlyFiles.length == 0 || this.contains(this.onlyFiles, src)) && !this.contains(this.skipFiles, src); }; const filtered: string[] = _names.filter(filterer); this.verboseLog(`\n${_names.length} compiled contracts found, skipping ${_names.length - filtered.length} of them`); await this.generateMDs(filtered); return filtered; } async generateMDs(artifactNames: string[]): Promise<void> { for (const contractName of artifactNames) { const [source, name] = contractName.split(":"); this.verboseLog(`\nStarted generating markup for ${name} contract`); const buildInfoId = await this.artifacts.getBuildInfoId(contractName); if (buildInfoId === undefined) { continue; } const buildInfoPath = await this.artifacts.getBuildInfoPath(buildInfoId); const buildInfoOutputPath = await this.artifacts.getBuildInfoOutputPath(buildInfoId); if (!buildInfoPath || !buildInfoOutputPath) { continue; } const buildInfo: SolidityBuildInfo = JSON.parse(fs.readFileSync(buildInfoPath, "utf-8")); const buildInfoOutput: SolidityBuildInfoOutput = JSON.parse(fs.readFileSync(buildInfoOutputPath, "utf-8")); const contractInfo: ContractInfo = await new Parser(buildInfo, buildInfoOutput).parseContractInfo(source, name); const genDir = `${this.outDir}/${path.dirname(source)}`; const genPath = `${genDir}/${name}.md`; await fsp.mkdir(genDir, { recursive: true }); await fsp.writeFile(genPath, this.mdGenerator.generateContractMDStr(contractInfo)); this.verboseLog(`Markup for ${name} is successfully generated`); } } async clean(): Promise<void> { if (!fs.existsSync(this.outDir)) { return; } const dirStats = await fsp.stat(this.outDir); if (!dirStats.isDirectory()) { throw new Error(`outdir is not a directory: ${this.outDir}`); } await fsp.rm(this.outDir, { recursive: true }); } private contains(pathList: any, source: any) { const isSubPath = (parent: string, child: string) => { const parentTokens = parent.split(path.posix.sep).filter((i) => i.length); const childTokens = child.split(path.posix.sep).filter((i) => i.length); return parentTokens.every((t, i) => childTokens[i] === t); }; return pathList === undefined ? false : pathList.some((p: any) => isSubPath(p, source)); } private toUnixPath(userPath: string) { return userPath.split(path.sep).join(path.posix.sep); } private verboseLog(msg: string) { if (this.verbose) { console.log(msg); } } }