252 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { ResultFile, ResultDir } from '@alilc/lowcode-types';
import nm from 'nanomatch';
import { CodeGeneratorError } from '../types/error';
import { FlattenFile } from '../types/file';
export function createResultFile(name: string, ext = 'jsx', content = ''): ResultFile {
return {
name,
ext,
content,
};
}
export function createResultDir(name: string): ResultDir {
return {
name,
dirs: [],
files: [],
};
}
export function addDirectory(target: ResultDir, dir: ResultDir): void {
if (target.dirs.findIndex((d) => d.name === dir.name) < 0) {
target.dirs.push(dir);
} else {
throw new CodeGeneratorError(
`Adding same directory to one directory: ${dir.name} -> ${target.name}`,
);
}
}
export function addFile(target: ResultDir, file: ResultFile): void {
if (target.files.findIndex((f) => f.name === file.name && f.ext === file.ext) < 0) {
target.files.push(file);
} else {
throw new CodeGeneratorError(
`Adding same file to one directory: ${file.name} -> ${target.name}`,
);
}
}
export function flattenResult(dir: ResultDir, cwd = ''): FlattenFile[] {
if (!dir.files.length && !dir.dirs.length) {
return [];
}
return [
...dir.files.map(
(file): FlattenFile => ({
pathName: joinPath(cwd, `${file.name}${file.ext ? `.${file.ext}` : ''}`),
content: file.content,
}),
),
].concat(...dir.dirs.map((subDir) => flattenResult(subDir, joinPath(cwd, subDir.name))));
}
export type GlobOptions = {
/** 是否查找 ".xxx" 文件, 默认: 否 */
dot?: boolean;
};
/**
* 查找文件
* @param result 出码结果
* @param fileGlobExpr 文件名匹配表达式
* @param resultDirPath 出码结果的路径(默认是 '.'
* @returns 匹配的第一个文件或 null (找不到)
*/
export function findFile(
result: ResultDir,
fileGlobExpr: string,
options: GlobOptions = {},
resultDirPath = getResultNameOrDefault(result, ''),
): ResultFile | null {
const maxDepth = !/\/|\*\*/.test(fileGlobExpr) ? 1 : undefined; // 如果 glob 表达式里面压根不会匹配子目录,则深度限制为 1
const files = scanFiles(result, resultDirPath, maxDepth);
for (let [filePath, file] of files) {
if (nm.isMatch(filePath, fileGlobExpr, options)) {
return file;
}
}
return null;
}
/**
* 使用 glob 语法查找多个文件
* @param result 出码结果
* @param fileGlobExpr 文件名匹配表达式
* @param resultDirPath 出码结果的路径(默认是 '.'
* @returns 找到的文件列表的迭代器 [ [文件路径, 文件信息], ... ]
*/
export function* globFiles(
result: ResultDir,
fileGlobExpr: string,
options: GlobOptions = {},
resultDirPath = getResultNameOrDefault(result, ''),
): IterableIterator<[string, ResultFile]> {
const files = scanFiles(result, resultDirPath);
for (let [filePath, file] of files) {
if (nm.isMatch(filePath, fileGlobExpr, options)) {
yield [filePath, file];
}
}
}
/**
* 遍历所有的文件
*/
export function* scanFiles(
result: ResultDir,
resultDirPath = getResultNameOrDefault(result, ''),
maxDepth = 10000,
): IterableIterator<[string, ResultFile]> {
for (let file of result.files) {
const fileName = getFileNameWithExt(file);
yield [joinPath(resultDirPath, fileName), file];
}
for (let subDir of result.dirs) {
yield* scanFiles(subDir, joinPath(resultDirPath, subDir.name), maxDepth - 1);
}
}
export function getFileNameWithExt(file: ResultFile) {
return `${file.name}${file.ext ? `.${file.ext}` : ''}`;
}
function getResultNameOrDefault(result: ResultDir, defaultDir = '/') {
return result.name && result.name !== '.' ? result.name : defaultDir;
}
function joinPath(...pathParts: string[]): string {
return pathParts
.filter((x) => x !== '' && x !== '.')
.join('/')
.replace(/\\+/g, '/')
.replace(/\/+/g, '/');
}
export function* scanDirs(
result: ResultDir,
resultDirPath = getResultNameOrDefault(result, ''),
maxDepth = 10000,
): IterableIterator<[string, ResultDir]> {
yield [resultDirPath, result];
for (let subDir of result.dirs) {
yield* scanDirs(subDir, joinPath(resultDirPath, subDir.name), maxDepth - 1);
}
}
export function* globDirs(
result: ResultDir,
dirGlobExpr: string,
options: GlobOptions = {},
resultDirPath = getResultNameOrDefault(result, ''),
): IterableIterator<[string, ResultDir]> {
const dirs = scanDirs(result, resultDirPath);
for (let [dirPath, dir] of dirs) {
if (nm.isMatch(dirPath, dirGlobExpr, options)) {
yield [dirPath, dir];
}
}
}
export function findDir(
result: ResultDir,
dirGlobExpr: string,
options: GlobOptions = {},
resultDirPath = getResultNameOrDefault(result, ''),
): ResultDir | null {
const dirs = scanDirs(result, resultDirPath);
for (let [dirPath, dir] of dirs) {
if (nm.isMatch(dirPath, dirGlobExpr, options)) {
return dir;
}
}
return null;
}
/**
* 从结果中移除一些文件
* @param result 出码结果目录
* @param filePathGlobExpr 要移除的文件路径glob 表达式)
* @param globOptions glob 参数
* @returns 移除了多少文件
*/
export function removeFilesFromResult(
result: ResultDir,
filePathGlobExpr: string,
globOptions: GlobOptions = {},
): number {
let removedCount = 0;
const [dirPath, fileName] = splitPath(filePathGlobExpr);
const dirs = dirPath ? globDirs(result, dirPath) : [['', result] as const];
for (let [, dir] of dirs) {
const files = globFiles(dir, fileName, globOptions, '.');
for (let [, file] of files) {
dir.files.splice(dir.files.indexOf(file), 1);
removedCount += 1;
}
}
return removedCount;
}
/**
* 从结果中移除一些目录
* @param result 出码结果目录
* @param dirPathGlobExpr 要移除的目录路径glob 表达式)
* @param globOptions glob 参数
* @returns 移除了多少文件
*/
export function removeDirsFromResult(
result: ResultDir,
dirPathGlobExpr: string,
globOptions: GlobOptions = {},
): number {
let removedCount = 0;
const [dirPath, fileName] = splitPath(dirPathGlobExpr);
const dirs = dirPath ? globDirs(result, dirPath) : [['', result] as const];
for (let [, dir] of dirs) {
const foundDirs = globDirs(dir, fileName, globOptions, '.');
for (let [, foundDir] of foundDirs) {
dir.dirs.splice(dir.dirs.indexOf(foundDir), 1);
removedCount += 1;
}
}
return removedCount;
}
/**
* 将文件路径拆分为目录路径和文件名
* @param filePath
* @returns [fileDirPath, fileName]
*/
function splitPath(filePath: string) {
const parts = filePath.split('/');
const fileName = parts.pop() || '';
return [joinPath(...parts), fileName];
}