UNPKG

@openapi-generator-plus/plain-documentation-generator

Version:
303 lines (270 loc) 9.56 kB
import { CodegenAllOfStrategy, CodegenAnyOfStrategy, CodegenGeneratorConstructor, CodegenGeneratorType, CodegenOneOfStrategy, isCodegenOperation } from '@openapi-generator-plus/types' import { CodegenOptionsDocumentation } from './types' import path from 'path' import Handlebars from 'handlebars' import { loadTemplates, emit, registerStandardHelpers } from '@openapi-generator-plus/handlebars-templates' import { javaLikeGenerator, JavaLikeContext, ConstantStyle, options as javaLikeOptions, EnumMemberStyle } from '@openapi-generator-plus/java-like-generator-helper' import { commonGenerator, compareHttpMethods, configObject, configString } from '@openapi-generator-plus/generator-common' import { emit as emitLess } from './less-utils' import { copyContents } from './static-utils' import { existsSync, promises as fs } from 'fs' function computeCustomTemplatesPath(configPath: string | undefined, customTemplatesPath: string) { if (configPath) { return path.resolve(path.dirname(configPath), customTemplatesPath) } else { return customTemplatesPath } } function toSafeTypeForComposing(nativeType: string): string { if (/[^a-zA-Z0-9_.[\]]/.test(nativeType)) { return `(${nativeType})` } else { return nativeType } } export const createGenerator: CodegenGeneratorConstructor = (config, context) => { const javaLikeContext: JavaLikeContext = { ...context, defaultConstantStyle: ConstantStyle.allCapsSnake, defaultEnumMemberStyle: EnumMemberStyle.preserve, } const customTemplates = configString(config, 'customTemplates', undefined) const operationsConfig = configObject<CodegenOptionsDocumentation['operations']>(config, 'operations', {})! if (operationsConfig.navStyle === undefined) { operationsConfig.navStyle = 'name' } if (operationsConfig.exclude === undefined) { operationsConfig.exclude = [] } const generatorOptions: CodegenOptionsDocumentation = { ...javaLikeOptions(config, javaLikeContext), customTemplatesPath: customTemplates && computeCustomTemplatesPath(config.configPath, customTemplates), operations: operationsConfig, } const aCommonGenerator = commonGenerator(config, context) return { ...context.baseGenerator(config, context), ...aCommonGenerator, ...javaLikeGenerator(config, javaLikeContext), generatorType: () => CodegenGeneratorType.DOCUMENTATION, toIdentifier: (name) => name, toLiteral: (value, options) => { if (value === undefined) { const defaultValue = context.generator().defaultValue(options) if (defaultValue === null) { return null } return defaultValue.literalValue } return `${value}` }, toNativeType: (options) => { const { type, format } = options if (type === 'string') { if (format) { return new context.NativeType(format, { serializedType: 'string', }) } } else if (type === 'integer') { if (format) { return new context.NativeType(format, { serializedType: 'number', }) } else { return new context.NativeType(type, { serializedType: 'number', }) } } return new context.NativeType(type) }, toEnumMemberName: (name) => name, toNativeObjectType: function(options) { const { scopedName } = options let modelName = '' for (const name of scopedName) { modelName += `.${context.generator().toClassName(name)}` } return new context.NativeType(modelName.substring(1)) }, toNativeArrayType: (options) => { const { componentNativeType } = options return new context.NativeType(`${toSafeTypeForComposing(componentNativeType.nativeType)}[]`) }, toNativeMapType: (options) => { const { keyNativeType, componentNativeType } = options return new context.NativeType(`{ [name: ${keyNativeType}]: ${componentNativeType} }`) }, nativeTypeUsageTransformer: ({ nullable }) => ({ default: function(nativeType, nativeTypeString) { if (nullable) { return `${toSafeTypeForComposing(nativeTypeString)} | null` } return nativeTypeString }, /* We don't transform the concrete type as the concrete type is never null; we use it to make new objects */ concreteType: null, }), defaultValue: () => { return null }, initialValue: () => { return null }, toOperationGroupName: (name) => { return name }, operationGroupingStrategy: () => { return context.operationGroupingStrategies.addToGroupsByTagOrPath }, allOfStrategy: () => CodegenAllOfStrategy.NATIVE, anyOfStrategy: () => CodegenAnyOfStrategy.NATIVE, oneOfStrategy: () => CodegenOneOfStrategy.NATIVE, supportsInheritance: () => false, supportsMultipleInheritance: () => false, nativeCompositionCanBeScope: () => true, nativeComposedSchemaRequiresName: () => false, nativeComposedSchemaRequiresObjectLikeOrWrapper: () => false, interfaceCanBeNested: () => true, watchPaths: () => { const result = [path.resolve(__dirname, '..', 'templates')] result.push(path.resolve(__dirname, '..', 'less')) result.push(path.resolve(__dirname, '..', 'static')) if (generatorOptions.customTemplatesPath) { result.push(generatorOptions.customTemplatesPath) } return result }, cleanPathPatterns: () => { return [ 'index.html', 'static/**', 'main.css', 'custom.css', ] }, templateRootContext: () => { return { ...aCommonGenerator.templateRootContext(), ...generatorOptions, generatorClass: '@openapi-generator-plus/plain-documentation-generator', } }, postProcessDocument(doc) { /* Apply excludes on operations */ for (let i = 0; i < doc.groups.length; i++) { const group = doc.groups[i] for (let j = 0; j < group.operations.length; j++) { const op = group.operations[j] for (const exclude of generatorOptions.operations?.exclude || []) { if (op.fullPath.match(new RegExp(exclude))) { group.operations.splice(j, 1) j-- break } } } if (group.operations.length === 0) { doc.groups.splice(i, 1) i-- } } }, exportTemplates: async(outputPath, doc) => { const hbs = Handlebars.create() registerStandardHelpers(hbs, context) hbs.registerHelper('eachSorted', function(this: unknown, context: unknown, options: Handlebars.HelperOptions) { if (!context) { return options.inverse(this) } // eslint-disable-next-line @typescript-eslint/no-explicit-any let collection: any[] if (context instanceof Map) { collection = [...context.values()] } else if (Array.isArray(context)) { collection = context } else if (typeof context === 'object') { collection = [] for (const key in context) { // eslint-disable-next-line @typescript-eslint/no-explicit-any collection.push((context as any)[key]) } } else { collection = [context] } let result = '' // eslint-disable-next-line @typescript-eslint/no-explicit-any for (const item of collection.sort(function(a: any, b: any) { if (a === b) { return 0 } if (typeof a === 'string' && typeof b === 'string') { return a.localeCompare(b) } else if (typeof a === 'number' && typeof b === 'number') { return a < b ? -1 : a > b ? 1 : 0 } else if (typeof a === 'object' && typeof b === 'object') { if (a === null) { return 1 } else if (b === null) { return -1 } if (isCodegenOperation(a) && isCodegenOperation(b)) { /* Sort CodegenOperations first by http method */ const result = compareHttpMethods(a.httpMethod, b.httpMethod) if (result !== 0) { return result } if (generatorOptions.operations?.navStyle === 'full-path') { if (a.fullPath && b.fullPath) { return a.fullPath.localeCompare(b.fullPath) } } } if (a.name && b.name) { return a.name.localeCompare(b.name) } return 0 } else { return 0 } })) { result += options.fn(item) } return result }) hbs.registerHelper('htmlId', function(value: string) { if (value !== undefined) { return `${value}`.replace(/[^-a-zA-Z0-9_]+/g, '_').replace(/^_+/, '').replace(/_+$/, '') } else { return value } }) await loadTemplates(path.resolve(__dirname, '..', 'templates'), hbs) if (generatorOptions.customTemplatesPath) { await loadTemplates(generatorOptions.customTemplatesPath, hbs) } const rootContext = context.generator().templateRootContext() if (!outputPath.endsWith('/')) { outputPath += '/' } await emit('index', path.join(outputPath, 'index.html'), { ...rootContext, ...doc }, true, hbs) await emitLess(path.resolve(__dirname, '../less', 'style.less'), path.join(outputPath, 'static/css/main.css')) await fs.writeFile(path.join(outputPath, 'custom.css'), '', {}) if (generatorOptions.customTemplatesPath) { const customLessPath = path.resolve(generatorOptions.customTemplatesPath, 'less/custom.less') if (existsSync(customLessPath)) { await emitLess(customLessPath, path.join(outputPath, 'static/css/custom.css')) } } await copyContents(path.resolve(__dirname, '..', 'static'), path.join(outputPath, 'static')) if (generatorOptions.customTemplatesPath) { const customStaticPath = path.resolve(generatorOptions.customTemplatesPath, 'static') if (existsSync(customStaticPath)) { await copyContents(customStaticPath, path.join(outputPath, 'static')) } } }, } } export default createGenerator