mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-10-25 15:05:25 +00:00
268 lines
11 KiB
JavaScript
268 lines
11 KiB
JavaScript
"use strict";
|
|
// Terminal output helpers for the 8bws CLI.
|
|
//
|
|
// Every command returns a CLIResult; output() renders it either as JSON
|
|
// (--json) or as colorized text. Keeping this out of 8bws.ts leaves the
|
|
// command implementations free of ANSI noise.
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.c = void 0;
|
|
exports.setJsonMode = setJsonMode;
|
|
exports.isJsonMode = isJsonMode;
|
|
exports.setServerMode = setServerMode;
|
|
exports.write = write;
|
|
exports.note = note;
|
|
exports.warn = warn;
|
|
exports.hasOutput = hasOutput;
|
|
exports.output = output;
|
|
exports.fail = fail;
|
|
exports.hexdump = hexdump;
|
|
const util_1 = require("../common/util");
|
|
exports.c = {
|
|
reset: '\x1b[0m',
|
|
bold: '\x1b[1m',
|
|
dim: '\x1b[2m',
|
|
red: '\x1b[31m',
|
|
green: '\x1b[32m',
|
|
yellow: '\x1b[33m',
|
|
blue: '\x1b[34m',
|
|
magenta: '\x1b[35m',
|
|
cyan: '\x1b[36m',
|
|
white: '\x1b[37m',
|
|
bgRed: '\x1b[41m',
|
|
bgGreen: '\x1b[42m',
|
|
};
|
|
let jsonMode = false;
|
|
let emit = console.log;
|
|
/**
|
|
* In --json mode stdout carries the CLIResult and nothing else, so the
|
|
* compiler's chatter (plain console.log from the worker) is pushed to stderr.
|
|
*/
|
|
function setJsonMode(on) {
|
|
jsonMode = on;
|
|
if (!on)
|
|
return;
|
|
emit = console.log.bind(console);
|
|
console.log = (...args) => process.stderr.write(args.join(' ') + '\n');
|
|
}
|
|
function isJsonMode() { return jsonMode; }
|
|
let serving = false;
|
|
/**
|
|
* For `8bws dap`: stdout carries the protocol, so console output goes to
|
|
* stderr, and fail() throws, so the server can answer with the error
|
|
* instead of exiting.
|
|
*/
|
|
function setServerMode() {
|
|
serving = true;
|
|
const toStderr = (...args) => { process.stderr.write(args.join(' ') + '\n'); };
|
|
console.log = console.info = console.warn = console.debug = toStderr;
|
|
}
|
|
/** Human-facing output: stdout normally, stderr in --json mode. */
|
|
function write(s) {
|
|
(jsonMode ? process.stderr : process.stdout).write(s);
|
|
}
|
|
// progress/status chatter -- suppressed in --json mode so stdout stays parseable
|
|
function note(msg) {
|
|
if (!jsonMode || serving)
|
|
process.stderr.write(`${exports.c.dim}${msg}${exports.c.reset}\n`);
|
|
}
|
|
/** A problem worth fixing that doesn't stop the command; stderr in any mode. */
|
|
function warn(msg) {
|
|
process.stderr.write(`${exports.c.yellow}Warning: ${msg}${exports.c.reset}\n`);
|
|
}
|
|
let emitted = false;
|
|
/** True once a CLIResult has been printed -- lets main() detect a silent exit. */
|
|
function hasOutput() { return emitted; }
|
|
function output(result) {
|
|
emitted = true;
|
|
if (jsonMode) {
|
|
emit(JSON.stringify(result, null, 2));
|
|
return;
|
|
}
|
|
process.stderr.write(result.success
|
|
? `${exports.c.bgGreen}${exports.c.bold}${exports.c.white} OK ${exports.c.reset} `
|
|
: `${exports.c.bgRed}${exports.c.bold}${exports.c.white} FAIL ${exports.c.reset} `);
|
|
process.stderr.write(`${exports.c.bold}${exports.c.cyan}${result.command}${exports.c.reset}\n`);
|
|
if (result.error) {
|
|
process.stderr.write(`${exports.c.red}Error: ${result.error}${exports.c.reset}\n`);
|
|
}
|
|
if (result.data) {
|
|
(FORMATTERS[result.command] || formatGeneric)(result.data);
|
|
}
|
|
}
|
|
// exit with a formatted error message
|
|
function fail(command, error, data) {
|
|
if (serving) {
|
|
const details = ((data === null || data === void 0 ? void 0 : data.errors) || []).map((e) => `\n${e.path || ''}${e.line ? ':' + e.line : ''}: ${e.msg}`).join('');
|
|
throw new Error(`${command}: ${error}${details}`);
|
|
}
|
|
output({ success: false, command, error, data });
|
|
process.exit(1);
|
|
}
|
|
const FORMATTERS = {
|
|
help: formatHelp,
|
|
build: formatBuild,
|
|
run: formatRun,
|
|
'list-tools': formatList('tools', 'Available tools'),
|
|
'list-platforms': formatPlatforms,
|
|
detect: formatDetect,
|
|
presets: formatPresets,
|
|
new: formatNew,
|
|
};
|
|
function formatHelp(data) {
|
|
console.log(`\n${exports.c.bold}Usage:${exports.c.reset} 8bws <command> [options] <file>\n`);
|
|
console.log(`${exports.c.bold}Commands:${exports.c.reset}`);
|
|
for (const [cmd, usage] of Object.entries(data.commands || {})) {
|
|
console.log(` ${exports.c.green}${cmd.padEnd(14)}${exports.c.reset}${exports.c.dim}${usage}${exports.c.reset}`);
|
|
}
|
|
if (data.options) {
|
|
for (const [section, opts] of Object.entries(data.options)) {
|
|
console.log(`\n${exports.c.bold}${section}:${exports.c.reset}`);
|
|
for (const [flag, desc] of Object.entries(opts)) {
|
|
console.log(` ${exports.c.yellow}${flag.padEnd(22)}${exports.c.reset}${exports.c.dim}${desc}${exports.c.reset}`);
|
|
}
|
|
}
|
|
}
|
|
if (data.script) {
|
|
console.log(`\n${exports.c.bold}Script commands${exports.c.reset} ${exports.c.dim}(-e / --script)${exports.c.reset}\n${data.script}`);
|
|
}
|
|
console.log();
|
|
}
|
|
function formatBuild(data) {
|
|
if (data.errors) {
|
|
for (const err of data.errors) {
|
|
let loc = '';
|
|
if (err.path)
|
|
loc += `${exports.c.cyan}${err.path}${exports.c.reset}`;
|
|
if (err.line)
|
|
loc += `${exports.c.dim}:${exports.c.reset}${exports.c.yellow}${err.line}${exports.c.reset}`;
|
|
if (loc)
|
|
loc += ` ${exports.c.dim}-${exports.c.reset} `;
|
|
console.log(` ${exports.c.red}●${exports.c.reset} ${loc}${err.msg || err.message || JSON.stringify(err)}`);
|
|
}
|
|
return;
|
|
}
|
|
field('Tool', data.tool, exports.c.green);
|
|
field('Platform', data.platform, exports.c.green);
|
|
field('Source', data.source, exports.c.cyan);
|
|
if (data.outputSize != null)
|
|
field('Size', `${data.outputSize} bytes`, exports.c.yellow);
|
|
field('Output', data.outputFile, exports.c.cyan);
|
|
if (data.symbolmap) {
|
|
console.log(`\n${exports.c.bold}Symbols${exports.c.reset} ${exports.c.dim}(${Object.keys(data.symbolmap).length})${exports.c.reset}`);
|
|
for (const [name, addr] of Object.entries(data.symbolmap).sort((a, b) => a[1] - b[1])) {
|
|
console.log(` ${exports.c.cyan}$${(0, util_1.hex)(addr, 4)}${exports.c.reset} ${name}`);
|
|
}
|
|
}
|
|
if (data.segments) {
|
|
console.log(`\n${exports.c.bold}Segments${exports.c.reset} ${exports.c.dim}(${data.segments.length})${exports.c.reset}`);
|
|
for (const seg of data.segments) {
|
|
console.log(` ${exports.c.green}${seg.name.padEnd(16)}${exports.c.reset} ${exports.c.cyan}$${(0, util_1.hex)(seg.start, 4)}${exports.c.reset} ${exports.c.dim}size${exports.c.reset} ${exports.c.yellow}${seg.size}${exports.c.reset}`);
|
|
}
|
|
}
|
|
if (data.saveDir) {
|
|
console.log(`\n${exports.c.bold}Saved to${exports.c.reset} ${exports.c.cyan}${data.saveDir}${exports.c.reset} ${exports.c.dim}(${data.savedFiles.length} files)${exports.c.reset}`);
|
|
for (const f of data.savedFiles)
|
|
console.log(` ${exports.c.dim}●${exports.c.reset} ${f}`);
|
|
}
|
|
}
|
|
function formatRun(data) {
|
|
field('Platform', data.platform, exports.c.green);
|
|
field('ROM', data.rom, exports.c.cyan);
|
|
if (data.frames != null)
|
|
field('Frames', data.frames, exports.c.yellow);
|
|
if (data.width)
|
|
field('Video', `${data.width}x${data.height}`, exports.c.yellow);
|
|
field('Screenshot', data.png, exports.c.cyan);
|
|
}
|
|
function formatList(key, title) {
|
|
return (data) => {
|
|
console.log(`\n${exports.c.bold}${title}${exports.c.reset} ${exports.c.dim}(${data[key].length})${exports.c.reset}\n`);
|
|
for (const item of data[key])
|
|
console.log(` ${exports.c.green}●${exports.c.reset} ${item}`);
|
|
console.log();
|
|
};
|
|
}
|
|
function formatPlatforms(data) {
|
|
var _a;
|
|
console.log(`\n${exports.c.bold}Available platforms${exports.c.reset} ${exports.c.dim}(${data.count})${exports.c.reset}\n`);
|
|
const byArch = {};
|
|
for (const [name, info] of Object.entries(data.platforms)) {
|
|
(byArch[_a = info.arch || 'unknown'] || (byArch[_a] = [])).push(name);
|
|
}
|
|
for (const [arch, platforms] of Object.entries(byArch).sort()) {
|
|
console.log(` ${exports.c.bold}${exports.c.magenta}${arch}${exports.c.reset}`);
|
|
for (const p of platforms.sort())
|
|
console.log(` ${exports.c.green}●${exports.c.reset} ${p}`);
|
|
}
|
|
console.log();
|
|
}
|
|
function formatDetect(data) {
|
|
if (!data.detections.length) {
|
|
console.log(` ${exports.c.dim}no platform found${exports.c.reset}`);
|
|
return;
|
|
}
|
|
for (const d of data.detections) {
|
|
const main = d.mainFile ? ` main: ${d.mainFile}` : d.mainCandidates && d.mainCandidates.length > 1 ? ` ${d.mainCandidates.length} programs` : '';
|
|
const tool = d.tool ? ` tool: ${d.tool}` : '';
|
|
console.log(` ${exports.c.bold}${exports.c.green}${d.platform.padEnd(18)}${exports.c.reset}${String(d.score).padEnd(6)}${main}${tool}`);
|
|
for (const e of d.evidence) {
|
|
console.log(` ${exports.c.dim}${e.file}${e.line ? ':' + e.line : ''}${exports.c.reset} ${e.reason}`);
|
|
}
|
|
}
|
|
if (!data.clear)
|
|
console.log(` ${exports.c.yellow}no clear winner; pass --platform${exports.c.reset}`);
|
|
}
|
|
function formatPresets(data) {
|
|
if (data.platforms) {
|
|
console.log(`\n${exports.c.bold}Platforms with examples${exports.c.reset} ${exports.c.dim}(${data.platforms.length}; 8bws presets <platform> lists them)${exports.c.reset}\n`);
|
|
for (const p of data.platforms)
|
|
console.log(` ${exports.c.green}${p.id.padEnd(22)}${exports.c.reset}${p.name.padEnd(30)}${exports.c.dim}${p.count}${exports.c.reset}`);
|
|
console.log();
|
|
return;
|
|
}
|
|
console.log(`\n${exports.c.bold}${data.name}${exports.c.reset} ${exports.c.dim}(8bws new ${data.platform}/<file> [dir])${exports.c.reset}`);
|
|
let category = '';
|
|
for (const t of data.templates) {
|
|
if (t.category !== category)
|
|
console.log(`\n ${exports.c.bold}${exports.c.magenta}${category = t.category}${exports.c.reset}`);
|
|
console.log(` ${exports.c.green}${t.id.padEnd(26)}${exports.c.reset}${exports.c.dim}${t.name}${exports.c.reset}`);
|
|
}
|
|
console.log();
|
|
}
|
|
function formatNew(data) {
|
|
var _a;
|
|
field('Preset', data.preset, exports.c.green);
|
|
field('Directory', data.dir, exports.c.cyan);
|
|
for (const f of data.files)
|
|
console.log(` ${exports.c.dim}●${exports.c.reset} ${f}`);
|
|
if ((_a = data.libraries) === null || _a === void 0 ? void 0 : _a.length)
|
|
console.log(` ${exports.c.dim}shared, not copied (add --libs): ${data.libraries.join(', ')}${exports.c.reset}`);
|
|
console.log(`\n ${exports.c.dim}next:${exports.c.reset} ${data.build}`);
|
|
}
|
|
function formatGeneric(data) {
|
|
for (const [key, value] of Object.entries(data)) {
|
|
const v = (typeof value === 'object' && value !== null) ? JSON.stringify(value) : value;
|
|
console.log(` ${exports.c.dim}${key}:${exports.c.reset} ${v}`);
|
|
}
|
|
}
|
|
function field(label, value, color) {
|
|
if (value == null || value === false)
|
|
return;
|
|
console.log(` ${exports.c.dim}${(label + ':').padEnd(11)}${exports.c.reset}${color}${value}${exports.c.reset}`);
|
|
}
|
|
// shared hexdump used by 'mem' and --memdump
|
|
function hexdump(read, start, end, out = write) {
|
|
for (let ofs = 0; start + ofs <= end; ofs += 16) {
|
|
let line = `${(0, util_1.hex)(start + ofs, 4)}:`;
|
|
let ascii = '';
|
|
for (let i = 0; i < 16 && start + ofs + i <= end; i++) {
|
|
if (i === 8)
|
|
line += ' ';
|
|
const byte = read(start + ofs + i);
|
|
line += ` ${(0, util_1.hex)(byte)}`;
|
|
ascii += (byte >= 0x20 && byte < 0x7f) ? String.fromCharCode(byte) : '.';
|
|
}
|
|
out(`${line.padEnd(60)} ${ascii}\n`);
|
|
}
|
|
}
|
|
//# sourceMappingURL=cliformat.js.map
|