Refresh Configs with browser editing and source inspection
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import { composeServerCfg, parseCfg } from './config-compose.js';
|
||||
import { selectedZombieMap, mapRotation } from './map-aliases.js';
|
||||
import { configWarnings, type ConfigAssignment, type ConfigInspection, type ConfigSource } from '../../config-workbench.js';
|
||||
|
||||
export interface ConfigLayer { source: ConfigSource; text: string; }
|
||||
|
||||
/** Trace file composition, including assignments left in place by the composer.
|
||||
* This is an intended-file preview, not a query of live runtime dvars. */
|
||||
export function inspectConfig(baseline: ConfigLayer, overlays: ConfigLayer[], game: string | null, map: string | null, mapSlug?: string | null): ConfigInspection {
|
||||
const sorted = [...overlays].sort((a, b) => a.source.id! - b.source.id!);
|
||||
const parsed = parseCfg(baseline.text);
|
||||
type TracedLine = { line: string; history: ConfigAssignment[] };
|
||||
function assignment(line: string, source: ConfigSource, index: number): ConfigAssignment {
|
||||
const match = /^\s*(?:(?:set|seta)\s+)(\S+)\s+(.*)$/i.exec(line)
|
||||
?? /^\s*(sv_maprotation)\s+(.*)$/i.exec(line);
|
||||
return { value: match?.[2] ?? '', source, line: index + 1 };
|
||||
}
|
||||
const lines: TracedLine[] = parsed.order.map((entry, index) => ({
|
||||
line: entry.line, history: [assignment(entry.line, baseline.source, index)]
|
||||
}));
|
||||
const replacements = new Map<string, TracedLine>();
|
||||
const warnings = configWarnings(baseline.text, false).map(w => `${baseline.source.name}: ${w}`);
|
||||
for (const overlay of sorted) {
|
||||
warnings.push(...configWarnings(overlay.text, true).map(w => `${overlay.source.name}: ${w}`));
|
||||
overlay.text.split(/\r?\n/).forEach((line, index) => {
|
||||
const match = /^\s*set\s+(\S+)\s+/.exec(line);
|
||||
if (!match) return;
|
||||
const previous = replacements.get(match[1]);
|
||||
replacements.set(match[1], { line, history: [...(previous?.history ?? []), assignment(line, overlay.source, index)] });
|
||||
});
|
||||
}
|
||||
for (const [key, replacement] of replacements) {
|
||||
const index = parsed.entries.get(key);
|
||||
if (index === undefined) lines.push(replacement);
|
||||
else lines[index] = { line: replacement.line, history: [...lines[index].history, ...replacement.history] };
|
||||
}
|
||||
const sources = [baseline.source, ...sorted.map(o => o.source)];
|
||||
const selected = selectedZombieMap(game, map, mapSlug);
|
||||
if (selected) {
|
||||
const source: ConfigSource = { id: null, name: 'Server map selection', version: null, kind: 'map' };
|
||||
sources.push(source);
|
||||
const rotation = /^\s*(?:(?:set|seta)\s+)?sv_maprotation\s+/i;
|
||||
const history = lines.filter(entry => rotation.test(entry.line)).flatMap(entry => entry.history);
|
||||
for (let i = lines.length - 1; i >= 0; i--) if (rotation.test(lines[i].line)) lines.splice(i, 1);
|
||||
const line = `set sv_maprotation "${mapRotation(selected)}"`;
|
||||
lines.push({ line, history: [...history, assignment(line, source, 0)] });
|
||||
}
|
||||
const byKey = new Map<string, { key: string; history: ConfigAssignment[] }>();
|
||||
for (const entry of lines) {
|
||||
const match = /^\s*(?:set|seta)\s+(\S+)\s+/i.exec(entry.line) ?? /^\s*(sv_maprotation)\s+/i.exec(entry.line);
|
||||
if (!match) continue;
|
||||
const normalized = match[1].toLowerCase();
|
||||
const existing = byKey.get(normalized);
|
||||
byKey.set(normalized, { key: match[1], history: [...(existing?.history ?? []), ...entry.history] });
|
||||
}
|
||||
return {
|
||||
text: composeServerCfg(baseline.text, sorted.map(o => ({ id: o.source.id!, text: o.text })), game, map, mapSlug),
|
||||
sources, warnings,
|
||||
settings: [...byKey.values()].map(({ key, history }) => ({ key, history, value: history.at(-1)!.value, source: history.at(-1)!.source }))
|
||||
.sort((a, b) => a.key.localeCompare(b.key))
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user