/**
* @description get code using standard dataSource format
* @NOTE getd files structure is as below:
* - library (contains class library code)
* - interfaces (contains interfaces code)
* - api.d.ts (contains interfaces and library definitions)
* - api.lock (contains local code state)
*/
import * as _ from 'lodash';
import { StandardDataSource, Interface, Mod, BaseClass } from '../standard';
import * as fs from 'fs-extra';
import * as path from 'path';
import {
format,
reviseModName,
Surrounding,
getFileName,
getTemplatesDirFile,
judgeTemplatesDirFileExists
} from '../utils';
import { info } from '../debugLog';
import { templateRegistion } from '../templates';
type bcBelongs = {
[key: string]: {
mods: string[],
inters: string[],
};
}
// bc中为避免不同mod同样inter名称而使用mod+inter的连接符
const connector = '+++';
export class FileStructures {
constructor(
private generators: CodeGenerator[],
private usingMultipleOrigins: boolean,
private surrounding = Surrounding.typeScript,
private baseDir = 'src/service',
private templateType = ''
) { }
getMultipleOriginsFileStructures() {
const files = { };
this.generators
.filter(generator => generator.outDir === this.baseDir)
.forEach(generator => {
const dsName = generator.dataSource.name;
const dsFiles = this.getOriginFileStructures(generator, true);
files[dsName] = dsFiles;
});
return {
...files,
[getFileName('index', this.surrounding)]: this.getDataSourcesTs.bind(this),
'api.d.ts': this.getDataSourcesDeclarationTs.bind(this),
};
}
getBaseClassesInDeclaration(originCode: string, usingMultipleOrigins: boolean) {
if (usingMultipleOrigins) {
return `
declare namespace defs {
export ${originCode}
};
`;
}
return `
declare ${originCode}
`;
}
getModsDeclaration(originCode: string, usingMultipleOrigins: boolean) {
if (usingMultipleOrigins) {
return `
declare namespace API {
export ${originCode}
};
`;
}
return `
declare ${originCode}
`;
}
getOriginFileStructures(generator: CodeGenerator, usingMultipleOrigins = false) {
let mods = { };
const dataSource = generator.dataSource;
const indexFileName = getFileName('index', this.surrounding);
dataSource.mods.forEach(mod => {
const currMod = { };
mod.interfaces.forEach(inter => {
currMod[inter.name] = {
'index.ts': generator.getInterfaceContent.bind(generator, inter),
// 生成inter级别的lock
'api-lock.json': this.getLockContent.bind(this, 'inter', dataSource.name, mod.name, inter.name)
}
currMod[indexFileName] = generator.getModIndex.bind(generator, mod);
// 生成mod级别的lock
currMod['api-lock.json'] = this.getLockContent.bind(this, 'mod', dataSource.name, mod.name);
});
const modName = reviseModName(mod.name);
mods[modName] = currMod;
mods[indexFileName] = generator.getModsIndex.bind(generator);
// 生成mod级别的lock
// mods['api-lock.json'] = this.getLockContent.bind(this, 'mod', dataSource.name, mod.name);
});
if (!generator.hasContextBund) {
generator.getBaseClassesInDeclaration = this.getBaseClassesInDeclaration.bind(
this,
generator.getBaseClassesInDeclaration(),
usingMultipleOrigins
);
generator.getModsDeclaration = this.getModsDeclaration.bind(
this,
generator.getModsDeclaration(),
usingMultipleOrigins
);
generator.hasContextBund = true;
}
const result = {
[getFileName('baseClass', this.surrounding)]: generator.getBaseClassesIndex.bind(generator),
mods: mods,
[indexFileName]: generator.getIndex.bind(generator),
'api.d.ts': generator.getDeclaration.bind(generator),
// origin级别的lock文件
'api-lock.json': this.getLockContent.bind(this, 'origin', dataSource.name)
};
return result;
}
getFileStructures() {
const result =
this.usingMultipleOrigins || this.generators.length > 1
? this.getMultipleOriginsFileStructures()
: this.getOriginFileStructures(this.generators[0]);
// js环境时,默认为新用户,生成pontCore文件
if (this.surrounding === Surrounding.javaScript) {
if (!fs.existsSync(this.baseDir + '/pontCore.js')) {
result['pontCore.js'] = getTemplatesDirFile('pontCore.js', 'pontCore/');
result['pontCore.d.ts'] = getTemplatesDirFile('pontCore.d.ts', 'pontCore/');
}
if (this.templateType && this.checkHasTemplateFetch()) {
result[`${this.templateType}.js`] = getTemplatesDirFile(`${this.templateType}.js`, 'pontCore/');
result[`${this.templateType}.d.ts`] = getTemplatesDirFile(`${this.templateType}.d.ts`, 'pontCore/');
}
}
return result;
}
private checkHasTemplateFetch() {
const templateTypesWithOutFetch = templateRegistion.map(item => item.templateType).filter(item => item !== 'fetch');
if (
templateTypesWithOutFetch.includes(this.templateType) &&
judgeTemplatesDirFileExists(`${this.templateType}.js`, 'pontCore/')
) {
return true;
}
return false;
}
getMultipleOriginsDataSourceName() {
const dsNames = this.generators.map(ge => ge.dataSource.name);
if (this.judgeHasMultipleFilesName()) {
const generate = this.generators.find(ge => ge.outDir === this.baseDir);
if (generate) {
return [generate.dataSource.name];
}
}
return dsNames;
}
judgeHasMultipleFilesName(): boolean {
return this.generators.some(generate => {
return generate.outDir !== this.baseDir;
});
}
getDataSourcesTs() {
const dsNames = this.getMultipleOriginsDataSourceName();
const generatedCode = this.surrounding === Surrounding.typeScript ? '(window as any)' : 'window';
return `
${dsNames
.map(name => {
return `import { defs as ${name}Defs, ${name} } from './${name}';
`;
})
.join('\n')}
${generatedCode}.defs = {
${dsNames.map(name => `${name}: ${name}Defs,`).join('\n')}
};
${generatedCode}.API = {
${dsNames.join(',\n')}
};
`;
}
getDataSourcesDeclarationTs() {
const dsNames = this.getMultipleOriginsDataSourceName();
return `
${dsNames
.map(name => {
return `/// `;
})
.join('\n')}
`;
}
getLockContent(level: string, origin?: string, mod?: string, inter?: string) {
if (this.generators) {
const ge = this.generators.find(item => item.dataSource.name === origin);
const originData = ge.dataSource;
// 暂不支持多outDir
let jsonObj: any;
if (level === 'origin') {
// level为origin时
jsonObj = {
name: originData.name,
mods: [],
baseClasses: ge.originBaseClassObj[originData.name] || []
}
} else if (level === 'mod') {
// level为mod时
const modData = originData.mods.find(item => item.name === mod);
delete modData['context'];
jsonObj = {
...modData,
interfaces: [],
baseClasses: ge.modBaseClassObj[modData.name] || []
}
} else {
// level为interface时
const modData = originData.mods.find(item => item.name === mod);
const interData = modData.interfaces.find(item => item.name === inter);
delete interData['context'];
jsonObj = {
...interData,
baseClasses: ge.interBaseClassObj[`${modData.name}${connector}${interData.name}`] || []
}
}
return JSON.stringify(jsonObj, null, 2);
}
}
}
export class CodeGenerator {
usingMultipleOrigins = false;
dataSource: StandardDataSource;
originBaseClassObj: {
[key: string]: BaseClass[];
} = {};
modBaseClassObj: {
[key: string]: BaseClass[];
} = {};
interBaseClassObj: {
[key: string]: BaseClass[];
} = {};
hasContextBund = false;
constructor(public surrounding = Surrounding.typeScript, public outDir = '') { }
setDataSource(dataSource: StandardDataSource) {
this.dataSource = dataSource;
// 将basic-resource这种命名转化成合法命名
this.dataSource.name = _.camelCase(this.dataSource.name);
}
// 遍历baseClasses将其分为三个级别,origin、mod、inter,并记录对应集合
setBaseClasses (dataSource: StandardDataSource) {
const bcNames: bcBelongs = {};
const rank = (prop, mod, inter) => {
// 建立typename所属关系,针对基础类型也有记录,但不影响后面操作
if (!bcNames[prop.typeName]) {
// 不存在的情况下,保存对应的mod和inter
bcNames[prop.typeName] = {
mods: [mod],
inters: [inter],
}
} else {
// 存在的情况下,记录多mod、inter,
// 多mod表示跨mod通用,为origin级别,多inter表示跨inter通用,为mod级别,单inter为inter级别
!bcNames[prop.typeName].mods.includes(mod) && bcNames[prop.typeName].mods.push(mod);
!bcNames[prop.typeName].inters.includes(mod) && bcNames[prop.typeName].inters.push(mod);
}
prop.typeArgs?.forEach(subProp => {
rank(subProp, mod, inter)
})
}
// 扫描inter的response和parameters
dataSource.mods.forEach(mod => {
mod.interfaces.forEach(inter => {
rank(inter.response, mod.name, inter.name);
inter.parameters.forEach(item => {
rank(item.dataType, mod.name, inter.name);
})
})
})
const findTop = (prop, top) => {
if (!bcNames[prop.typeName]) {
bcNames[prop.typeName] = bcNames[top];
}
prop.typeArgs.forEach(subProp => {
findTop(subProp, top);
})
}
// 遍历bc,找到不和inter直接关联的bc
dataSource.baseClasses.forEach(bc => {
bc.properties.forEach(item => {
findTop(item.dataType, bc.name);
})
})
dataSource.baseClasses.forEach(bc => {
if (!bcNames[bc.name]) {
info(`${bc.name}独立存在,请检查相应数据`);
return
}
if (bcNames[bc.name].mods.length > 1) {
// 如果为origin级别
if (!this.originBaseClassObj[dataSource.name]) {
this.originBaseClassObj[dataSource.name] = [bc];
} else {
this.originBaseClassObj[dataSource.name].push(bc);
}
} else if (bcNames[bc.name].inters.length > 1) {
// 如果为mod级别
if (!this.modBaseClassObj[bcNames[bc.name].mods[0]]) {
this.modBaseClassObj[bcNames[bc.name].mods[0]] = [bc]
} else {
this.modBaseClassObj[bcNames[bc.name].mods[0]].push(bc);
}
} else {
// 如果为inter级别
if (!this.interBaseClassObj[`${bcNames[bc.name].mods[0]}${connector}${bcNames[bc.name].inters[0]}`]) {
this.interBaseClassObj[`${bcNames[bc.name].mods[0]}${connector}${bcNames[bc.name].inters[0]}`] = [bc]
} else {
this.interBaseClassObj[`${bcNames[bc.name].mods[0]}${connector}${bcNames[bc.name].inters[0]}`].push(bc);
}
}
})
}
/** 获取某个基类的类型定义代码 */
getBaseClassInDeclaration(base: BaseClass) {
if (base.templateArgs && base.templateArgs.length) {
return `class ${base.name}<${base.templateArgs.map((_, index) => `T${index} = any`).join(', ')}> {
${base.properties.map(prop => prop.toPropertyCode(Surrounding.typeScript, true)).join('\n')}
}
`;
}
return `class ${base.name} {
${base.properties.map(prop => prop.toPropertyCode(Surrounding.typeScript, true)).join('\n')}
}
`;
}
/** 获取所有基类的类型定义代码,一个 namespace
* surrounding, 优先级高于this.surrounding,用于生成api.d.ts时强制保留类型
*/
getBaseClassesInDeclaration() {
const content = `namespace ${this.dataSource.name || 'defs'} {
${this.dataSource.baseClasses
.map(
base => `
export ${this.getBaseClassInDeclaration(base)}
`
)
.join('\n')}
}
`;
return content;
}
getBaseClassesInDeclarationWithMultipleOrigins() {
return `
declare namespace defs {
export ${this.getBaseClassesInDeclaration()}
}
`;
}
getBaseClassesInDeclarationWithSingleOrigin() {
return `
declare ${this.getBaseClassesInDeclaration()}
`;
}
/** 获取接口内容的类型定义代码 */
getInterfaceContentInDeclaration(inter: Interface) {
const bodyParams = inter.getBodyParamsCode();
const requestParams = bodyParams ? `params: Params, bodyParams: ${bodyParams}` : `params: Params`;
return `
export ${inter.getParamsCode('Params', this.surrounding)}
export type Response = ${inter.responseType};
export const init: Response;
export function request(${requestParams}): Promise<${inter.responseType}>;
`;
}
private getInterfaceInDeclaration(inter: Interface) {
return `
/**
* ${inter.description}
* ${inter.path}
*/
export namespace ${inter.name} {
${this.getInterfaceContentInDeclaration(inter)}
}
`;
}
/** 获取模块的类型定义代码,一个 namespace ,一般不需要覆盖 */
getModsDeclaration() {
const mods = this.dataSource.mods;
const content = `namespace ${this.dataSource.name || 'API'} {
${mods
.map(
mod => `
/**
* ${mod.description}
*/
export namespace ${reviseModName(mod.name)} {
${mod.interfaces.map(this.getInterfaceInDeclaration.bind(this)).join('\n')}
}
`
)
.join('\n\n')}
}
`;
return content;
}
getModsDeclarationWithMultipleOrigins() { }
getModsDeclarationWithSingleOrigin() { }
/** 获取公共的类型定义代码 */
getCommonDeclaration() {
return '';
}
/** 获取总的类型定义代码 */
getDeclaration() {
return `
type ObjectMap = {
[key in Key]: Value;
}
${this.getCommonDeclaration()}
${this.getBaseClassesInDeclaration()}
${this.getModsDeclaration()}
`;
}
/** 获取接口类和基类的总的 index 入口文件代码 */
getIndex() {
let conclusion = `
import * as defs from './baseClass';
import './mods/';
${this.surrounding === Surrounding.typeScript ? '(window as any)' : 'window'}.defs = defs;
`;
// dataSource name means multiple dataSource
if (this.dataSource.name) {
conclusion = `
import { ${this.dataSource.name} as defs } from './baseClass';
export { ${this.dataSource.name} } from './mods/';
export { defs };
`;
}
return conclusion;
}
/** 获取所有基类文件代码 */
getBaseClassesIndex() {
const clsCodes = this.dataSource.baseClasses.map(
base => `
class ${base.name} {
${base.properties
.map(prop => {
return prop.toPropertyCodeWithInitValue(base.name);
})
.filter(id => id)
.join('\n')}
}
`
);
if (this.dataSource.name) {
return `
${clsCodes.join('\n')}
export const ${this.dataSource.name} = {
${this.dataSource.baseClasses.map(bs => bs.name).join(',\n')}
}
`;
}
return clsCodes.map(cls => `export ${cls}`).join('\n');
}
/** 获取接口实现内容的代码 */
getInterfaceContent(inter: Interface) {
const method = inter.method.toUpperCase();
const bodyParams = inter.getBodyParamsCode();
return `
/**
* @desc ${inter.description}
*/
import * as defs from '../../baseClass';
import { pontCore } from '../../pontCore';
export ${inter.getParamsCode('Params', this.surrounding)}
export const init = ${inter.response.getInitialValue()};
export function request(${bodyParams ? `params = {}, bodyParams = null` : 'params = {}'}) {
return pontCore.fetch(pontCore.getUrl("${inter.path}", params, "${method}"), {
method: "${method}",
body: ${bodyParams ? 'bodyParams' : 'null'},
});
}
`;
}
/** 获取单个模块的 index 入口文件 */
getModIndex(mod: Mod) {
return `
/**
* @description ${mod.description}
*/
${mod.interfaces
.map(inter => {
return `import * as ${inter.name} from './${inter.name}/index';`;
})
.join('\n')}
export {
${mod.interfaces.map(inter => inter.name).join(', \n')}
}
`;
}
/** 获取所有模块的 index 入口文件 */
getModsIndex() {
let conclusion = `
${this.surrounding === Surrounding.typeScript ? '(window as any)' : 'window'}.API = {
${this.dataSource.mods.map(mod => reviseModName(mod.name)).join(', \n')}
};
`;
// dataSource name means multiple dataSource
if (this.dataSource.name) {
conclusion = `
export const ${this.dataSource.name} = {
${this.dataSource.mods.map(mod => reviseModName(mod.name)).join(', \n')}
};
`;
}
return `
${this.dataSource.mods
.map(mod => {
const modName = reviseModName(mod.name);
return `import * as ${modName} from './${modName}';`;
})
.join('\n')}
${conclusion}
`;
}
/**
* 获取中间态数据结构
* @param dataSource
*/
getDataSourceCallback(dataSource?: StandardDataSource): void {
// 空实现, 用于对外暴露文件数据结构
if (dataSource) {
return;
}
}
}
export class FilesManager {
// todo: report 可以更改为单例,防止每个地方都注入。
report = info;
prettierConfig: {};
constructor(public fileStructures: FileStructures, private baseDir: string) { }
/** 初始化清空路径 */
private initPath(path: string) {
if (!fs.existsSync(path)) {
fs.mkdirpSync(path);
}
}
async regenerate(files: {}, oldFiles?: {}) {
// if (report) {
// this.report = report;
// }
this.initPath(this.baseDir);
this.created = true;
if (oldFiles && Object.keys(oldFiles || { }).length) {
const updateTask = this.diffFiles(files, oldFiles);
if (updateTask.deletes && updateTask.deletes.length) {
this.report(`删除${updateTask.deletes.length}个文件及文件夹`);
await Promise.all(
updateTask.deletes.map(filePath => {
fs.unlink(filePath);
})
);
}
if (updateTask.updateCnt) {
this.report(`更新${updateTask.updateCnt}个文件`);
console.time(`更新${updateTask.updateCnt}个文件`);
await this.updateFiles(updateTask.files);
console.timeEnd(`更新${updateTask.updateCnt}个文件`);
}
} else {
await this.generateFiles(files);
}
}
/** 区分lock文件是创建的还是人为更改的 */
created = false;
async saveLock() {
const lockFilePath = path.join(this.baseDir, 'api-lock.json');
const oldLockFilePath = path.join(this.baseDir, 'api.lock');
const isExists = fs.existsSync(lockFilePath);
const readFilePath = isExists ? lockFilePath : oldLockFilePath;
const lockContent = await fs.readFile(readFilePath, 'utf8');
// const newLockContent = this.fileStructures.getLockContent();
const newLockContent = this.fileStructures.getLockContent('origin');
if (lockContent !== newLockContent) {
this.created = true;
await fs.writeFile(lockFilePath, newLockContent);
}
}
diffFiles(newFiles: {}, lastFiles: {}, dir = this.baseDir) {
const task = {
deletes: [] as string[],
files: { },
updateCnt: 0
};
// 待删除、待更新
_.map(lastFiles, (lastValue: string | {}, name) => {
const currPath = `${dir}/${name}`;
const newValue = newFiles[name];
// 待删除
if (!newValue) {
task.deletes.push(currPath);
return;
}
// 文件转文件夹
if (typeof newValue === 'object' && typeof lastValue === 'string') {
task.deletes.push(currPath);
const fileTask = this.diffFiles(newValue, { }, currPath);
if (fileTask.updateCnt) {
task.files = { ...task.files, [currPath]: undefined, ...fileTask.files };
task.updateCnt += fileTask.updateCnt + 1;
}
return;
}
// 文件夹转文件
if (typeof newValue === 'string' && typeof lastValue === 'object') {
task.deletes.push(currPath);
return;
}
// 待更新
if (typeof lastValue === 'string') {
// 文件更新
if (newValue !== lastValue) {
task.files[currPath] = newValue;
task.updateCnt++;
}
} else {
// 文件夹更新
const fileTask = this.diffFiles(newValue, lastValue, currPath);
task.deletes.push(...fileTask.deletes);
if (fileTask.updateCnt) {
task.updateCnt += fileTask.updateCnt;
task.files = { ...task.files, ...fileTask.files };
}
}
});
// 待增加
_.map(newFiles, (newValue: string | {}, name) => {
const currPath = `${dir}/${name}`;
const lastValue = lastFiles[name];
if (!lastValue) {
if (typeof newValue === 'string') {
task.files[currPath] = newValue;
task.updateCnt += 1;
} else {
const fileTask = this.diffFiles(newValue, { }, currPath);
if (fileTask.updateCnt) {
task.updateCnt += fileTask.updateCnt + 1;
task.files = { ...task.files, [currPath]: undefined, ...fileTask.files };
}
}
}
});
return task;
}
public formatFile(code: string, name = '') {
if (name && name.endsWith('.json')) {
return code;
}
return format(code, this.prettierConfig);
}
async updateFiles(files: {}) {
await Promise.all(
_.map(files, async (value: string, filePath) => {
if (value === undefined) {
return fs.mkdir(filePath);
}
if (filePath.endsWith('.json')) {
return fs.writeFile(filePath, value);
}
return fs.writeFile(filePath, this.formatFile(value));
})
);
}
/** 根据 Codegenerator 配置生成目录和文件 */
async generateFiles(files: {}, dir = this.baseDir) {
const currFiles = await fs.readdir(dir);
const promises = _.map(files, async (value: string | {}, name) => {
const currPath = `${dir}/${name}`;
if (typeof value === 'string') {
if (currFiles.includes(name)) {
const state = await fs.lstat(currPath);
if (state.isDirectory()) {
await fs.unlink(currPath);
return fs.writeFile(currPath, this.formatFile(value, name));
} else {
const newValue = this.formatFile(value);
const currValue = await fs.readFile(currPath, 'utf8');
if (newValue !== currValue) {
return fs.writeFile(currPath, this.formatFile(value, name));
}
return;
}
} else {
return fs.writeFile(currPath, this.formatFile(value, name));
}
}
// 新路径为文件夹
if (currFiles.includes(name)) {
const state = await fs.lstat(currPath);
if (state.isDirectory()) {
return this.generateFiles(files[name], currPath);
} else {
await fs.unlink(currPath);
await fs.mkdir(currPath);
return this.generateFiles(files[name], currPath);
}
} else {
await fs.mkdir(currPath);
return this.generateFiles(files[name], currPath);
}
});
await Promise.all(promises);
}
}