refactor: reorganize directory structure into src/
Move all source code directories into src/ for cleaner project structure: - pages/, components/, utils/, config/, providers/, types/, lib/, assets/, entrypoints/ → src/ - Use WXT srcDir config to resolve @/ alias to src/ - Update tsconfig, vitest, eslint, tailwind configs - Remove scattered README.md files from subdirectories - Update documentation (AGENTS.md, CODING_STANDARDS.md, README.md) - Fix pre-existing lint error in RouterProvider.tsx Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* JSON 转 YAML 转换结果
|
||||
*/
|
||||
export interface JsonToYamlResult {
|
||||
/** 转换后的 YAML 字符串 */
|
||||
output: string;
|
||||
/** 原始输入的字节大小 */
|
||||
originalBytes: number;
|
||||
/** 转换后的字节大小 */
|
||||
outputBytes: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 JSON 值转换为 YAML 字符串表示
|
||||
*
|
||||
* @param value - 已解析的 JSON 值
|
||||
* @param indent - 当前缩进级别
|
||||
* @returns YAML 字符串
|
||||
*/
|
||||
function toYamlString(value: unknown, indent: number): string {
|
||||
if (value === null) {
|
||||
return 'null';
|
||||
}
|
||||
if (value === true) {
|
||||
return 'true';
|
||||
}
|
||||
if (value === false) {
|
||||
return 'false';
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
return String(value);
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return stringifyYamlString(value);
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return arrayToYaml(value, indent);
|
||||
}
|
||||
if (typeof value === 'object') {
|
||||
return objectToYaml(value as Record<string, unknown>, indent);
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串转为 YAML 安全的表示
|
||||
* 对于包含特殊字符的字符串使用双引号包裹
|
||||
*/
|
||||
function stringifyYamlString(str: string): string {
|
||||
// 需要引号包裹的情况:空字符串、以特殊字符开头、包含特殊字符
|
||||
const needsQuoting =
|
||||
str === '' ||
|
||||
str === 'null' ||
|
||||
str === 'true' ||
|
||||
str === 'false' ||
|
||||
/[:#{}[\],&*?|>\-!%@`]/.test(str) ||
|
||||
str.includes(' ') ||
|
||||
str.includes('\n') ||
|
||||
/^\d/.test(str);
|
||||
|
||||
if (!needsQuoting) {
|
||||
return str;
|
||||
}
|
||||
|
||||
// 转义双引号和反斜杠,然后用双引号包裹
|
||||
const escaped = str.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
||||
return `"${escaped}"`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 JSON 对象转换为 YAML 映射
|
||||
*/
|
||||
function objectToYaml(obj: Record<string, unknown>, indent: number): string {
|
||||
const prefix = ' '.repeat(indent);
|
||||
const keys = Object.keys(obj);
|
||||
|
||||
if (keys.length === 0) {
|
||||
return '{}';
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
for (const key of keys) {
|
||||
const value = obj[key];
|
||||
const safeKey = /^[a-zA-Z0-9_-]+$/.test(key) ? key : stringifyYamlString(key);
|
||||
|
||||
if (
|
||||
value !== null &&
|
||||
typeof value === 'object' &&
|
||||
!Array.isArray(value) &&
|
||||
Object.keys(value as Record<string, unknown>).length > 0
|
||||
) {
|
||||
// 非空嵌套对象
|
||||
lines.push(`${prefix}${safeKey}:`);
|
||||
lines.push(toYamlString(value, indent + 1));
|
||||
} else if (Array.isArray(value) && value.length > 0) {
|
||||
// 非空数组
|
||||
lines.push(`${prefix}${safeKey}:`);
|
||||
lines.push(arrayToYaml(value, indent + 1));
|
||||
} else {
|
||||
// 基本值、空对象、空数组
|
||||
lines.push(`${prefix}${safeKey}: ${toYamlString(value, indent + 1)}`);
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 JSON 数组转换为 YAML 序列
|
||||
*/
|
||||
function arrayToYaml(arr: unknown[], indent: number): string {
|
||||
const prefix = ' '.repeat(indent);
|
||||
|
||||
if (arr.length === 0) {
|
||||
return `${prefix}[]`;
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
for (const item of arr) {
|
||||
if (
|
||||
item !== null &&
|
||||
typeof item === 'object' &&
|
||||
!Array.isArray(item) &&
|
||||
Object.keys(item as Record<string, unknown>).length > 0
|
||||
) {
|
||||
// 数组中的非空对象
|
||||
const objLines = objectToYaml(item as Record<string, unknown>, indent + 1);
|
||||
lines.push(`${prefix}- ${objLines.trimStart()}`);
|
||||
} else if (Array.isArray(item) && item.length > 0) {
|
||||
// 数组中的非空数组
|
||||
lines.push(`${prefix}-`);
|
||||
lines.push(arrayToYaml(item, indent + 1));
|
||||
} else {
|
||||
// 基本值、空对象、空数组
|
||||
lines.push(`${prefix}- ${toYamlString(item, indent + 1)}`);
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 JSON 字符串转换为 YAML 格式
|
||||
*
|
||||
* @param text - 输入的 JSON 字符串
|
||||
* @returns 转换结果
|
||||
* @throws {SyntaxError} 当输入不是有效的 JSON 时抛出语法错误
|
||||
*/
|
||||
export function jsonToYaml(text: string): JsonToYamlResult {
|
||||
const trimmed = text.trim();
|
||||
if (!trimmed) {
|
||||
return { output: '', originalBytes: 0, outputBytes: 0 };
|
||||
}
|
||||
|
||||
const parsed: unknown = JSON.parse(trimmed);
|
||||
const output = toYamlString(parsed, 0);
|
||||
const originalBytes = new TextEncoder().encode(trimmed).length;
|
||||
const outputBytes = new TextEncoder().encode(output).length;
|
||||
|
||||
return { output, originalBytes, outputBytes };
|
||||
}
|
||||
Reference in New Issue
Block a user