import * as fs from 'fs'; import * as path from 'path'; import {parseExampleFile} from './parse-example-file'; interface ExampleMetadata { component: string; sourcePath: string; id: string; title: string; additionalComponents: string[]; additionalFiles: string[]; selectorName: string[]; } /** Build ES module import statements for the given example metadata. */ function buildImportsTemplate(data: ExampleMetadata): string { const components = data.additionalComponents.concat(data.component); const relativeSrcPath = data.sourcePath.replace(/\\/g, '/').replace('.ts', ''); return `import {${components.join(',')}} from './${relativeSrcPath}';`; } /** Inlines the example module template with the specified parsed data. */ function inlineExampleModuleTemplate(parsedData: ExampleMetadata[]): string { const exampleImports = parsedData.map(m => buildImportsTemplate(m)).join('\n'); const exampleList = parsedData.reduce((result, data) => { return result.concat(data.component).concat(data.additionalComponents); }, [] as string[]).join(','); const exampleComponents = parsedData.map((metaData) => { const fields = [ `title: '${metaData.title.trim()}'`, `component: ${metaData.component}`, ]; if (metaData.additionalFiles.length) { fields.push(`additionalFiles: ${JSON.stringify(metaData.additionalFiles)}`); } if (metaData.selectorName.length) { fields.push(`selectorName: '${metaData.selectorName.join(', ')}'`); } const data = '\n' + fields.map(field => ' ' + field).join(',\n'); return `'${metaData.id}': {${data}\n }`; }, {} as any); return fs.readFileSync(require.resolve('./example-module.template'), 'utf8') .replace('${exampleImports}', exampleImports) .replace('${exampleComponents}', `{\n ${exampleComponents.join(',\n ')}}`) .replace('${exampleList}', `[${exampleList}]`); } /** Converts a given camel-cased string to a dash-cased string. */ function convertToDashCase(name: string): string { name = name.replace(/[A-Z]/g, ' $&'); name = name.toLowerCase().trim(); return name.split(' ').join('-'); } /** Collects the metadata of the given source files by parsing the given TypeScript files. */ function collectExampleMetadata(sourceFiles: string[], baseFile: string): ExampleMetadata[] { const exampleMetadata: ExampleMetadata[] = []; for (const sourceFile of sourceFiles) { const sourceContent = fs.readFileSync(sourceFile, 'utf-8'); const {primaryComponent, secondaryComponents} = parseExampleFile(sourceFile, sourceContent); if (primaryComponent) { // Generate a unique id for the component by converting the class name to dash-case. const exampleId = convertToDashCase(primaryComponent.component.replace('Example', '')); const example: ExampleMetadata = { sourcePath: path.relative(baseFile, sourceFile), id: exampleId, component: primaryComponent.component, title: primaryComponent.title.trim(), additionalComponents: [], additionalFiles: [], selectorName: [] }; if (secondaryComponents.length) { example.selectorName.push(example.component); for (const meta of secondaryComponents) { example.additionalComponents.push(meta.component); if (meta.templateUrl) { example.additionalFiles.push(meta.templateUrl); } if (meta.styleUrls) { example.additionalFiles.push(...meta.styleUrls); } example.selectorName.push(meta.component); } } exampleMetadata.push(example); } } return exampleMetadata; } /** * Generates the example module from the given source files and writes it to a specified output * file. */ export function generateExampleModule(sourceFiles: string[], outputFile: string, baseDir: string = path.dirname(outputFile)) { const results = collectExampleMetadata(sourceFiles, baseDir); const generatedModuleFile = inlineExampleModuleTemplate(results); fs.writeFileSync(outputFile, generatedModuleFile); }