From 7316ebc7ddb384cfd579964c3fb7c9786980b818 Mon Sep 17 00:00:00 2001 From: Axel Meyer Date: Sat, 23 May 2026 13:46:03 +0200 Subject: [PATCH] Add encode subcommand Wraps gid.encodeGid, prints the packed-u32 as decimal on stdout, emits usage on stderr on bad arity, and a labelled error on out-of-range or non-integer input. --- src/cli.js | 2 +- src/commands/encode.js | 32 +++++++++++++++++++++++++ tests/encode.test.js | 54 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 src/commands/encode.js create mode 100644 tests/encode.test.js diff --git a/src/cli.js b/src/cli.js index edcda16..d8fe9fd 100644 --- a/src/cli.js +++ b/src/cli.js @@ -30,7 +30,7 @@ function run(argv, { out, err }) { return 0; } const handlers = { - // populated in subsequent tasks + encode: require('./commands/encode'), }; const handler = handlers[head]; if (!handler) { diff --git a/src/commands/encode.js b/src/commands/encode.js new file mode 100644 index 0000000..16db4a1 --- /dev/null +++ b/src/commands/encode.js @@ -0,0 +1,32 @@ +'use strict'; + +const { encodeGid } = require('../gid'); + +const USAGE = 'usage: sporel-mapper encode [rotation]\n'; + +function parseIntStrict(s, field) { + if (!/^-?\d+$/.test(s)) { + throw new RangeError(`${field} '${s}' is not an integer`); + } + return parseInt(s, 10); +} + +function run(args, { out, err }) { + if (args.length < 2 || args.length > 3) { + err.write(USAGE); + return 1; + } + try { + const atlas = parseIntStrict(args[0], 'atlas'); + const tile = parseIntStrict(args[1], 'tile'); + const rotation = args.length === 3 ? parseIntStrict(args[2], 'rotation') : 0; + const gid = encodeGid(atlas, tile, rotation); + out.write(`${gid}\n`); + return 0; + } catch (e) { + err.write(`error: ${e.message}\n`); + return 1; + } +} + +module.exports = { run }; diff --git a/tests/encode.test.js b/tests/encode.test.js new file mode 100644 index 0000000..65a85ad --- /dev/null +++ b/tests/encode.test.js @@ -0,0 +1,54 @@ +'use strict'; + +const test = require('node:test'); +const assert = require('node:assert/strict'); +const { run } = require('../src/cli'); + +class Sink { + constructor() { this.chunks = []; } + write(s) { this.chunks.push(s); } + get text() { return this.chunks.join(''); } +} + +test('encode: (0,12,1) prints 196 with trailing newline, exit 0', () => { + const out = new Sink(); const err = new Sink(); + const code = run(['encode', '0', '12', '1'], { out, err }); + assert.equal(code, 0); + assert.equal(out.text, '196\n'); + assert.equal(err.text, ''); +}); + +test('encode: rotation defaults to 0', () => { + const out = new Sink(); const err = new Sink(); + const code = run(['encode', '0', '12'], { out, err }); + assert.equal(code, 0); + assert.equal(out.text, '192\n'); +}); + +test('encode: no args prints usage to stderr, exit 1', () => { + const out = new Sink(); const err = new Sink(); + const code = run(['encode'], { out, err }); + assert.equal(code, 1); + assert.match(err.text, /usage: sporel-mapper encode/); +}); + +test('encode: too many args prints usage, exit 1', () => { + const out = new Sink(); const err = new Sink(); + const code = run(['encode', '0', '0', '0', '0'], { out, err }); + assert.equal(code, 1); + assert.match(err.text, /usage/); +}); + +test('encode: out-of-range tile prints error, exit 1', () => { + const out = new Sink(); const err = new Sink(); + const code = run(['encode', '0', '99999999', '0'], { out, err }); + assert.equal(code, 1); + assert.match(err.text, /error: tile/); +}); + +test('encode: non-integer arg prints error, exit 1', () => { + const out = new Sink(); const err = new Sink(); + const code = run(['encode', '0', 'abc', '0'], { out, err }); + assert.equal(code, 1); + assert.match(err.text, /error/i); +});