diff --git a/packages/engine5/src/lib/util.ts b/packages/engine5/src/lib/util.ts index f8a99011..e0c855d5 100644 --- a/packages/engine5/src/lib/util.ts +++ b/packages/engine5/src/lib/util.ts @@ -1,8 +1,16 @@ -import {existsSync} from 'node:fs'; -import {copyFile, mkdir, readFile, rename, writeFile} from 'node:fs/promises'; +import { + existsSync, + readFileSync as readFileSync_, + writeFileSync as writeFileSync_, + mkdirSync, + copyFileSync, + renameSync, +} from 'node:fs'; +import {copyFile, mkdir, readFile as readFile_, rename, writeFile as writeFile_} from 'node:fs/promises'; import {dirname} from 'node:path'; import {logger} from './logger.js'; +import {MaybePromise} from '../type.js'; /** * Deep clones an object. @@ -31,89 +39,404 @@ export const flatStr = (s: string): string => { }; /** - * Enhanced read json file. + * Parse json string. * + * @param content - json string + * @returns json object * @example - * const fileContent = await readJsonFile('./file.json'); + * ```typescript + * const json = parseJson('{"a":1,"b":2}'); + * console.log(json.a); // 1 + * ``` */ -export async function readJsonFile(path: string): Promise { - logger.logMethodArgs?.('readJsonFile', path); - - if (!existsSync(path)) { - // existsSync is much faster than access. - throw new Error('file_not_found'); - } - - let content: string; +export function parseJson(content: string) { try { - content = flatStr(await readFile(path, {encoding: 'utf-8', flag: 'r'})); + return JSON.parse(content); } catch (err) { - logger.error('readJsonFile', 'read_file_failed', err); - throw new Error('read_file_failed', {cause: (err as Error).cause}); - } - - let data; - try { - data = JSON.parse(content); - } - catch (err) { - logger.error('readJsonFile', 'invalid_json', err); + logger.error('parseJson', 'invalid_json', err); throw new Error('invalid_json', {cause: (err as Error).cause}); } - return data; } /** - * Enhanced write json file. + * Stringify json object. * + * @param data - json object + * @returns json string * @example - * await writeJsonFile('./file.json', { a:1, b:2, c:3 }); + * ```typescript + * const json = jsonStringify({a:1, b:2}); + * console.log(json); // '{"a":1,"b":2}' + * ``` */ -export async function writeJsonFile( - path: string, - data: unknown, - existFile: 'replace' | 'copy' | 'rename', -): Promise { - logger.logMethodArgs?.('writeJsonFile', path); - - let content; +export function jsonStringify(data: unknown): string { try { - content = flatStr(JSON.stringify(data)); + return JSON.stringify(data); } catch (err) { - logger.error('writeJsonFile', 'stringify_failed', err); + logger.error('jsonStringify', 'stringify_failed', err); throw new Error('stringify_failed', {cause: (err as Error).cause}); } +} - if (existsSync(path)) { +/** + * Enhanced read file (async). + * + * @param path - file path + * @returns file content + * @example + * ```typescript + * const fileContent = await readFile('./file.txt'); + * ``` + */ +export function readFile(path: string): Promise; +/** + * Enhanced read file (sync). + * + * @param path - file path + * @param sync - sync mode + * @returns file content + * @example + * ```typescript + * const fileContent = readFile('./file.txt', true); + * ``` + */ +export function readFile(path: string, sync: true): string; +/** + * Enhanced read File. + * + * @param path - file path + * @param sync - sync mode + * @returns file content + * @example + * ```typescript + * const fileContent = await readFile('./file.txt', sync); + * ``` + */ +export function readFile(path: string, sync: boolean): MaybePromise; +/** + * Enhanced read File. + * + * @param path - file path + * @param sync - sync mode + * @returns file content + * @example + * ```typescript + * const fileContent = await readFile('./file.txt'); + * ``` + */ +export function readFile(path: string, sync = false): MaybePromise { + logger.logMethodArgs?.('readFile', {path, sync}); + // if (!existsSync(path)) throw new Error('file_not_found'); + if (sync === true) { try { - if (existFile === 'copy') { - await copyFile(path, path + '.bk'); - } - else if (existFile === 'rename') { - await rename(path, path + '.bk'); - } + return flatStr(readFileSync_(path, {encoding: 'utf-8', flag: 'r'})); } catch (err) { - logger.error('writeJsonFile', 'rename_copy_failed', err); + logger.error('readFile', 'read_file_failed', err); + throw new Error('read_file_failed', {cause: (err as Error).cause}); } } - else { + // else, async mode + return readFile_(path, {encoding: 'utf-8', flag: 'r'}) + .then((content) => flatStr(content)) + .catch((err) => { + logger.error('readFile', 'read_file_failed', err); + throw new Error('read_file_failed', {cause: (err as Error).cause}); + }); +} + +/** + * Enhanced read json file (async). + * + * @param path - file path + * @returns json object + * @example + * ```typescript + * const fileContent = await readJsonFile('./file.json'); + * ``` + */ +export function readJsonFile(path: string): Promise; +/** + * Enhanced read json file (sync). + * + * @param path - file path + * @param sync - sync mode + * @returns json object + * @example + * ```typescript + * const fileContent = readJsonFile('./file.json', true); + * ``` + */ +export function readJsonFile(path: string, sync: true): unknown; +/** + * Enhanced read json file. + * + * @param path - file path + * @param sync - sync mode + * @returns json object + * @example + * ```typescript + * const fileContent = await readJsonFile('./file.json', sync); + * ``` + */ +export function readJsonFile(path: string, sync: boolean): MaybePromise; +/** + * Enhanced read json file. + * + * @param path - file path + * @param sync - sync mode + * @returns json object + * @example + * ```typescript + * const fileContent = await readJsonFile('./file.json'); + * ``` + */ +export function readJsonFile(path: string, sync = false): MaybePromise { + logger.logMethodArgs?.('readJsonFile', {path, sync}); + if (sync === true) { + return parseJson(readFile(path, true)); + } + // else, async mode + return readFile(path).then((content) => parseJson(content)); +} + +/** + * Write file mode for exists file. + */ +export enum WriteFileMode { + Replace, + Rename, + Copy, +} + +/** + * Enhanced write file (async). + * + * @param path - file path + * @param content - file content + * @param mode - handle exists file (replace, copy, rename) + * @example + * ```typescript + * await writeFile('./file.txt', 'Hello World!', WriteFileMode.Replace); + * ``` + */ +export function writeFile(path: string, content: string, mode: WriteFileMode, sync?: false): Promise; +/** + * Enhanced write file (sync). + * + * @param path - file path + * @param content - file content + * @param mode - handle exists file (replace, copy, rename) + * @param sync - sync mode + * @example + * ```typescript + * writeFile('./file.txt', 'Hello World!', WriteFileMode.Replace, true); + * ``` + */ +export function writeFile(path: string, content: string, mode: WriteFileMode, sync: true): void; +/** + * Enhanced write file. + * + * @param path - file path + * @param content - file content + * @param mode - handle exists file (replace, copy, rename) + * @param sync - sync mode + * @example + * ```typescript + * await writeFile('./file.txt', 'Hello World!', WriteFileMode.Replace, sync); + * ``` + */ +export function writeFile(path: string, content: string, mode: WriteFileMode, sync: boolean): MaybePromise; +/** + * Enhanced write file. + * + * @param path - file path + * @param content - file content + * @param mode - handle exists file (replace, copy, rename) + * @param sync - sync mode + * @example + * ```typescript + * await writeFile('./file.txt', 'Hello World!', WriteFileMode.Replace); + * ``` + */ +export function writeFile(path: string, content: string, mode: WriteFileMode, sync = false): MaybePromise { + logger.logMethodArgs?.('writeFile', {path, existFile: mode, sync}); + if (sync === true) { + handleExistsFile(path, mode, true); try { - await mkdir(dirname(path), {recursive: true}); + writeFileSync_(path, content, {encoding: 'utf-8', flag: 'w'}); } catch (err) { - logger.error('writeJsonFile', 'make_dir_failed', err); - throw new Error('make_dir_failed', {cause: (err as Error).cause}); + logger.error('writeFile', 'write_file_failed', err); + throw new Error('write_file_failed', {cause: (err as Error).cause}); } } + // else, async mode + return handleExistsFile(path, mode) + .then(() => writeFile_(path, content, {encoding: 'utf-8', flag: 'w'})) + .catch((err) => { + logger.error('writeFile', 'write_file_failed', err); + throw new Error('write_file_failed', {cause: (err as Error).cause}); + }); +} - try { - await writeFile(path, content, {encoding: 'utf-8', flag: 'w'}); +/** + * Handle exists file (async). + * + * @param path - file path + * @param mode - handle exists file (replace, copy, rename) + * @param sync - sync mode + * @example + * ```typescript + * await handleExistsFile('./file.txt', WriteFileMode.Rename); + * ``` + */ +export function handleExistsFile(path: string, mode: WriteFileMode): Promise; +/** + * Handle exists file (sync). + * + * @param path - file path + * @param mode - handle exists file (replace, copy, rename) + * @param sync - sync mode + * @example + * ```typescript + * handleExistsFile('./file.txt', WriteFileMode.Rename, true); + * ``` + */ +export function handleExistsFile(path: string, mode: WriteFileMode, sync: true): void; +/** + * Handle exists file. + * + * @param path - file path + * @param mode - handle exists file (replace, copy, rename) + * @param sync - sync mode + * @example + * ```typescript + * await handleExistsFile('./file.txt', WriteFileMode.Rename, sync); + * ``` + */ +export function handleExistsFile(path: string, mode: WriteFileMode, sync: boolean): MaybePromise; +/** + * Handle exists file. + * + * @param path - file path + * @param mode - handle exists file (replace, copy, rename) + * @param sync - sync mode + * @example + * ```typescript + * await handleExistsFile('./file.txt', WriteFileMode.Rename); + * ``` + */ +export function handleExistsFile(path: string, mode: WriteFileMode, sync = false): MaybePromise { + logger.logMethodArgs?.('handleExistsFile', {path, mode, sync}); + if (sync === true) { + if (existsSync(path)) { + if (mode === WriteFileMode.Copy) { + try { + copyFileSync(path, path + '.bk'); + } + catch (err) { + logger.error('handleExistsFile', 'copy_failed', err); + } + } + else if (mode === WriteFileMode.Rename) { + try { + renameSync(path, path + '.bk'); + } + catch (err) { + logger.error('handleExistsFile', 'rename_failed', err); + } + } + } + else { + try { + mkdirSync(dirname(path), {recursive: true}); + } + catch (err) { + logger.error('handleExistsFile', 'make_dir_failed', err); + throw new Error('make_dir_failed', {cause: (err as Error).cause}); + } + } } - catch (err) { - logger.error('writeJsonFile', 'write_file_failed', err); - throw new Error('write_file_failed', {cause: (err as Error).cause}); + // else, async mode + if (existsSync(path)) { + // existsSync is much faster than access. + if (mode === WriteFileMode.Copy) { + return copyFile(path, path + '.bk').catch((err) => { + logger.error('handleExistsFile', 'copy_failed', err); + }); + } + else if (mode === WriteFileMode.Rename) { + return rename(path, path + '.bk').catch((err) => { + logger.error('handleExistsFile', 'rename_failed', err); + }); + } + } + else { + return mkdir(dirname(path), {recursive: true}) + .then(() => { + logger.logOther?.('handleExistsFile', 'make_dir_success'); + }) + .catch((err) => { + logger.error('handleExistsFile', 'make_dir_failed', err); + throw new Error('make_dir_failed', {cause: (err as Error).cause}); + }); } } + +/** + * Enhanced write json file (async). + * + * @param path - file path + * @param data - json object + * @param mode - handle exists file (replace, copy, rename) + * @example + * ```typescript + * await writeJsonFile('./file.json', { a:1, b:2, c:3 }, WriteFileMode.Replace); + * ``` + */ +export function writeJsonFile(path: string, data: unknown, mode: WriteFileMode, sync?: false): Promise; +/** + * Enhanced write json file (sync). + * + * @param path - file path + * @param data - json object + * @param mode - handle exists file (replace, copy, rename) + * @param sync - sync mode + * @example + * ```typescript + * writeJsonFile('./file.json', { a:1, b:2, c:3 }, WriteFileMode.Replace, true); + * ``` + */ +export function writeJsonFile(path: string, data: unknown, mode: WriteFileMode, sync: true): void; +/** + * Enhanced write json file. + * + * @param path - file path + * @param data - json object + * @param mode - handle exists file (replace, copy, rename) + * @param sync - sync mode + * @example + * ```typescript + * await writeJsonFile('./file.json', { a:1, b:2, c:3 }, WriteFileMode.Replace, sync); + * ``` + */ +export function writeJsonFile(path: string, data: unknown, mode: WriteFileMode, sync: boolean): MaybePromise; +/** + * Enhanced write json file. + * + * @param path - file path + * @param data - json object + * @param mode - handle exists file (replace, copy, rename) + * @param sync - sync mode + * @example + * ```typescript + * await writeJsonFile('./file.json', { a:1, b:2, c:3 }, WriteFileMode.Replace); + * ``` + */ +export function writeJsonFile(path: string, data: unknown, mode: WriteFileMode, sync = false): MaybePromise { + logger.logMethodArgs?.('writeJsonFile', {path, existFile: mode, sync}); + return writeFile(path, flatStr(jsonStringify(data)), mode, sync); +}