Add inspect subcommand: read-only stats for v2 map files
Reads the map JSON, optionally loads --atlas-dir paths for name resolution, and prints the inspector report to stdout. Missing file, bad JSON, and missing --atlas-dir paths all produce labelled stderr errors with exit 1.
This commit is contained in:
@@ -33,6 +33,7 @@ function run(argv, { out, err }) {
|
||||
encode: require('./commands/encode'),
|
||||
decode: require('./commands/decode'),
|
||||
build: require('./commands/build'),
|
||||
inspect: require('./commands/inspect'),
|
||||
};
|
||||
const handler = handlers[head];
|
||||
if (!handler) {
|
||||
|
||||
77
src/commands/inspect.js
Normal file
77
src/commands/inspect.js
Normal file
@@ -0,0 +1,77 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('node:fs');
|
||||
const { loadAtlasDirs } = require('../atlas-loader');
|
||||
const { inspectMap } = require('../inspector');
|
||||
|
||||
const USAGE = 'usage: sporel-mapper inspect <map.json> [--atlas-dir <path> ...]\n';
|
||||
|
||||
function parseArgs(args) {
|
||||
const positional = [];
|
||||
const atlasDirs = [];
|
||||
let i = 0;
|
||||
while (i < args.length) {
|
||||
const a = args[i];
|
||||
if (a === '--atlas-dir') {
|
||||
if (i + 1 >= args.length) {
|
||||
throw new Error(`--atlas-dir requires a path`);
|
||||
}
|
||||
atlasDirs.push(args[i + 1]);
|
||||
i += 2;
|
||||
} else {
|
||||
positional.push(a);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
return { positional, atlasDirs };
|
||||
}
|
||||
|
||||
function run(args, { out, err }) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = parseArgs(args);
|
||||
} catch (e) {
|
||||
err.write(`error: ${e.message}\n${USAGE}`);
|
||||
return 1;
|
||||
}
|
||||
if (parsed.positional.length !== 1) {
|
||||
err.write(USAGE);
|
||||
return 1;
|
||||
}
|
||||
const [mapPath] = parsed.positional;
|
||||
|
||||
let raw;
|
||||
try {
|
||||
raw = fs.readFileSync(mapPath, 'utf8');
|
||||
} catch (e) {
|
||||
err.write(`error: cannot read ${mapPath}: ${e.message}\n`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
let map;
|
||||
try {
|
||||
map = JSON.parse(raw);
|
||||
} catch (e) {
|
||||
err.write(`error: ${mapPath} is not valid JSON: ${e.message}\n`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
let registry = {};
|
||||
if (parsed.atlasDirs.length > 0) {
|
||||
try {
|
||||
const result = loadAtlasDirs(parsed.atlasDirs);
|
||||
registry = result.registry;
|
||||
for (const w of result.warnings) {
|
||||
err.write(`warning: ${w}\n`);
|
||||
}
|
||||
} catch (e) {
|
||||
err.write(`error: ${e.message}\n`);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
out.write(inspectMap(map, registry));
|
||||
return 0;
|
||||
}
|
||||
|
||||
module.exports = { run };
|
||||
53
tests/inspect.test.js
Normal file
53
tests/inspect.test.js
Normal file
@@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
const { run } = require('../src/cli');
|
||||
|
||||
class Sink {
|
||||
constructor() { this.chunks = []; }
|
||||
write(s) { this.chunks.push(s); }
|
||||
get text() { return this.chunks.join(''); }
|
||||
}
|
||||
|
||||
const FIX_MAP = path.join(__dirname, 'fixtures', 'maps', 'inspect_sample.map.json');
|
||||
const FIX_ATLAS = path.join(__dirname, 'fixtures', 'atlases');
|
||||
|
||||
test('inspect: prints report to stdout, exit 0', () => {
|
||||
const out = new Sink(); const err = new Sink();
|
||||
const code = run(['inspect', FIX_MAP], { out, err });
|
||||
assert.equal(code, 0, err.text);
|
||||
assert.match(out.text, /Map: inspect_sample/);
|
||||
assert.match(out.text, /Roof: 4 cells flagged/);
|
||||
});
|
||||
|
||||
test('inspect: --atlas-dir enables name resolution', () => {
|
||||
const out = new Sink(); const err = new Sink();
|
||||
const code = run(['inspect', FIX_MAP, '--atlas-dir', FIX_ATLAS], { out, err });
|
||||
assert.equal(code, 0);
|
||||
assert.match(out.text, /atlas_a:grass/);
|
||||
});
|
||||
|
||||
test('inspect: missing positional arg prints usage, exit 1', () => {
|
||||
const out = new Sink(); const err = new Sink();
|
||||
const code = run(['inspect'], { out, err });
|
||||
assert.equal(code, 1);
|
||||
assert.match(err.text, /usage: sporel-mapper inspect/);
|
||||
});
|
||||
|
||||
test('inspect: missing map file prints error, exit 1', () => {
|
||||
const out = new Sink(); const err = new Sink();
|
||||
const code = run(['inspect', '/no/such/map.json'], { out, err });
|
||||
assert.equal(code, 1);
|
||||
assert.match(err.text, /error: cannot read/);
|
||||
});
|
||||
|
||||
test('inspect: invalid JSON prints error, exit 1', () => {
|
||||
const out = new Sink(); const err = new Sink();
|
||||
// Use a non-JSON file as bogus input
|
||||
const reallyNotJson = path.join(__dirname, '..', 'src', 'cli.js');
|
||||
const code = run(['inspect', reallyNotJson], { out, err });
|
||||
assert.equal(code, 1);
|
||||
assert.match(err.text, /error/i);
|
||||
});
|
||||
Reference in New Issue
Block a user