mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
136 lines
4.5 KiB
TypeScript
136 lines
4.5 KiB
TypeScript
import { ResolvedConfig } from '../types';
|
|
import {
|
|
CONFIG_OPTION_REGISTRY,
|
|
CONFIG_TEMPLATE_SECTIONS,
|
|
DEFAULT_CONFIG,
|
|
deepCloneConfig,
|
|
} from './definitions';
|
|
|
|
const OPTION_REGISTRY_BY_PATH = new Map(CONFIG_OPTION_REGISTRY.map((entry) => [entry.path, entry]));
|
|
const TOP_LEVEL_SECTION_DESCRIPTION_BY_KEY = new Map(
|
|
CONFIG_TEMPLATE_SECTIONS.map((section) => [String(section.key), section.description[0] ?? '']),
|
|
);
|
|
|
|
function normalizeCommentText(value: string): string {
|
|
return value.replace(/\s+/g, ' ').replace(/\*\//g, '*\\/').trim();
|
|
}
|
|
|
|
function humanizeKey(key: string): string {
|
|
const spaced = key
|
|
.replace(/_/g, ' ')
|
|
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
.toLowerCase();
|
|
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
}
|
|
|
|
function buildInlineOptionComment(path: string, value: unknown): string {
|
|
const registryEntry = OPTION_REGISTRY_BY_PATH.get(path);
|
|
const baseDescription = registryEntry?.description ?? TOP_LEVEL_SECTION_DESCRIPTION_BY_KEY.get(path);
|
|
const description =
|
|
baseDescription && baseDescription.trim().length > 0
|
|
? normalizeCommentText(baseDescription)
|
|
: `${humanizeKey(path.split('.').at(-1) ?? path)} setting.`;
|
|
|
|
if (registryEntry?.enumValues?.length) {
|
|
return `${description} Values: ${registryEntry.enumValues.join(' | ')}`;
|
|
}
|
|
if (typeof value === 'boolean') {
|
|
return `${description} Values: true | false`;
|
|
}
|
|
return description;
|
|
}
|
|
|
|
function renderValue(value: unknown, indent = 0, path = ''): string {
|
|
const pad = ' '.repeat(indent);
|
|
const nextPad = ' '.repeat(indent + 2);
|
|
|
|
if (value === null) return 'null';
|
|
if (typeof value === 'string') return JSON.stringify(value);
|
|
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
|
|
if (Array.isArray(value)) {
|
|
if (value.length === 0) return '[]';
|
|
const items = value.map((item) => `${nextPad}${renderValue(item, indent + 2, `${path}[]`)}`);
|
|
return `\n${items.join(',\n')}\n${pad}`.replace(/^/, '[').concat(']');
|
|
}
|
|
|
|
if (typeof value === 'object') {
|
|
const entries = Object.entries(value as Record<string, unknown>).filter(
|
|
([, child]) => child !== undefined,
|
|
);
|
|
if (entries.length === 0) return '{}';
|
|
const lines = entries.map(([key, child], index) => {
|
|
const isLast = index === entries.length - 1;
|
|
const trailingComma = isLast ? '' : ',';
|
|
const childPath = path ? `${path}.${key}` : key;
|
|
const renderedChild = renderValue(child, indent + 2, childPath);
|
|
const comment = buildInlineOptionComment(childPath, child);
|
|
if (renderedChild.startsWith('\n')) {
|
|
return `${nextPad}${JSON.stringify(key)}: /* ${comment} */ ${renderedChild}${trailingComma}`;
|
|
}
|
|
return `${nextPad}${JSON.stringify(key)}: ${renderedChild}${trailingComma} // ${comment}`;
|
|
});
|
|
return `\n${lines.join('\n')}\n${pad}`.replace(/^/, '{').concat('}');
|
|
}
|
|
|
|
return 'null';
|
|
}
|
|
|
|
function renderSection(
|
|
key: keyof ResolvedConfig,
|
|
value: unknown,
|
|
isLast: boolean,
|
|
comments: string[],
|
|
): string {
|
|
const lines: string[] = [];
|
|
lines.push(' // ==========================================');
|
|
for (const comment of comments) {
|
|
lines.push(` // ${comment}`);
|
|
}
|
|
lines.push(' // ==========================================');
|
|
const inlineComment = buildInlineOptionComment(String(key), value);
|
|
const renderedValue = renderValue(value, 2, String(key));
|
|
if (renderedValue.startsWith('\n')) {
|
|
lines.push(
|
|
` ${JSON.stringify(key)}: /* ${inlineComment} */ ${renderedValue}${isLast ? '' : ','}`,
|
|
);
|
|
} else {
|
|
lines.push(
|
|
` ${JSON.stringify(key)}: ${renderedValue}${isLast ? '' : ','} // ${inlineComment}`,
|
|
);
|
|
}
|
|
return lines.join('\n');
|
|
}
|
|
|
|
export function generateConfigTemplate(
|
|
config: ResolvedConfig = deepCloneConfig(DEFAULT_CONFIG),
|
|
): string {
|
|
const lines: string[] = [];
|
|
lines.push('/**');
|
|
lines.push(' * SubMiner Example Configuration File');
|
|
lines.push(' *');
|
|
lines.push(' * This file is auto-generated from src/config/definitions.ts.');
|
|
lines.push(
|
|
' * Copy to $XDG_CONFIG_HOME/SubMiner/config.jsonc (or ~/.config/SubMiner/config.jsonc) and edit as needed.',
|
|
);
|
|
lines.push(' */');
|
|
lines.push('{');
|
|
|
|
CONFIG_TEMPLATE_SECTIONS.forEach((section, index) => {
|
|
lines.push('');
|
|
const comments = [section.title, ...section.description, ...(section.notes ?? [])];
|
|
lines.push(
|
|
renderSection(
|
|
section.key,
|
|
config[section.key],
|
|
index === CONFIG_TEMPLATE_SECTIONS.length - 1,
|
|
comments,
|
|
),
|
|
);
|
|
});
|
|
|
|
lines.push('}');
|
|
lines.push('');
|
|
return lines.join('\n');
|
|
}
|