UNPKG

europa-build

Version:

Tool for generating and maintaining Europa plugins and presets

141 lines 6.86 kB
/* * Copyright (C) 2022 neocotic * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ import { readFile, readdir, stat, writeFile } from 'fs/promises'; import * as mkdirp from 'mkdirp'; import * as Mustache from 'mustache'; import { EOL } from 'os'; import { basename, dirname, extname, join, resolve } from 'path'; import { PackageInfo } from "../PackageInfo"; const _logger = Symbol(); const _partials = Symbol(); const _provider = Symbol(); /** * A {@link TemplateGenerator} implementation that uses Mustache to render templates. */ export class MustacheTemplateGenerator { /** * Creates an instance of {@link MustacheTemplateGenerator} using the `options` provided. * * @param options - The options to be used. */ constructor(options) { this[_logger] = options.parentLogger.child({ name: 'MustacheTemplateGenerator' }); this[_provider] = options.provider; } async generate(context, directory) { const targetDirectory = join(directory, context.name); this[_logger].info(`Generating ${this[_provider].getType()} package '${context.name}' in directory: ${targetDirectory}`); await MustacheTemplateGenerator.verifyDirectoryEmptyOrNotExists(targetDirectory); await mkdirp(targetDirectory); for (const templateDirectory of this[_provider].getDirectories()) { await this.generateDirectory(context, targetDirectory, templateDirectory); } this[_logger].info(`Generated ${this[_provider].getType()} package: ${targetDirectory}@${context.version}`); } static formatOutput(output, fileExtension) { if (fileExtension === '.json') { return JSON.stringify(JSON.parse(output), undefined, ' ') + EOL; } return output; } async generateDirectory(context, targetDirectory, directory) { const templatePath = await this.getTemplatePath(directory); const entries = await readdir(templatePath, { withFileTypes: true }); for (const entry of entries) { await this.generateEntry(context, targetDirectory, directory, '', entry); } } async generateEntry(context, targetDirectory, directory, subDirectoryPath, entry) { const templatePath = await this.getTemplatePath(directory, subDirectoryPath, entry.name); if (entry.isDirectory()) { const targetPath = MustacheTemplateGenerator.getTargetPath(targetDirectory, subDirectoryPath, entry.name); this[_logger].info(`Generating ${this[_provider].getType()} package sub-directory: ${targetPath}`); await mkdirp(targetPath); const subEntries = await readdir(templatePath, { withFileTypes: true }); for (const subEntry of subEntries) { await this.generateEntry(context, targetDirectory, directory, join(subDirectoryPath, entry.name), subEntry); } } else if (entry.isFile()) { const targetPath = MustacheTemplateGenerator.getTargetPathExcludingExtension(targetDirectory, subDirectoryPath, entry.name); this[_logger].info(`Generating ${this[_provider].getType()} package template file: ${targetPath}`); const template = await readFile(templatePath, 'utf8'); // eslint-disable-next-line import/namespace const templateOutput = Mustache.render(template, context, await this.getPartials()); const output = MustacheTemplateGenerator.formatOutput(templateOutput, extname(targetPath)); await writeFile(targetPath, output, 'utf8'); } else { throw new Error(`Unexpected template entry: ${templatePath}`); } } async getPartials() { const cachedPartials = this[_partials]; if (cachedPartials) { return cachedPartials; } const partialDirectoryPath = await this.getTemplatePath('.partial'); this[_logger].debug(`Loading ${this[_provider].getType()} package template partials from directory: ${partialDirectoryPath}`); const partials = {}; const entries = await readdir(partialDirectoryPath, { withFileTypes: true }); for (const entry of entries) { const partialName = basename(entry.name, extname(entry.name)); const partialPath = join(partialDirectoryPath, entry.name); if (!entry.isFile()) { throw new Error(`Unexpected partial entry: ${partialPath}`); } partials[partialName] = await readFile(partialPath, 'utf8'); } this[_partials] = partials; return partials; } async getTemplatePath(directory, ...paths) { const packageInfo = await PackageInfo.getSingleton(); const providerType = this[_provider].getType(); return resolve(packageInfo.directoryPath, 'templates', providerType, directory, ...paths); } static getTargetPath(targetDirectory, ...paths) { return resolve(targetDirectory, ...paths); } static getTargetPathExcludingExtension(targetDirectory, ...paths) { const targetPath = MustacheTemplateGenerator.getTargetPath(targetDirectory, ...paths); const targetDirectoryPath = dirname(targetPath); const targetFileName = basename(targetPath, extname(targetPath)); return join(targetDirectoryPath, targetFileName); } static async verifyDirectoryEmptyOrNotExists(directoryPath) { let stats; try { stats = await stat(directoryPath); } catch (e) { if (e.code === 'ENOENT') { return; } throw e; } if (!stats.isDirectory()) { throw new Error(`Invalid directory: ${directoryPath}`); } } } //# sourceMappingURL=MustacheTemplateGenerator.js.map