diff --git a/src/alias.js b/src/alias.js new file mode 100644 index 0000000..63f44ea --- /dev/null +++ b/src/alias.js @@ -0,0 +1,30 @@ +// src/alias.js +// Pure functions for deriving snake_case aliases from sprite filenames. +// No I/O. + +const path = require('node:path'); +const SUPPORTED_EXT = ['.png', '.webp', '.jpg', '.jpeg']; + +function stripPrefix(name, prefix) { + if (!prefix) return name; + if (name.startsWith(prefix)) return name.slice(prefix.length); + return name; +} + +// CamelCase -> snake_case. Boundary: lowercase/digit -> uppercase. +function camelToSnakeCase(s) { + return s + .replace(/([a-z0-9])([A-Z])/g, '$1_$2') + .toLowerCase(); +} + +function aliasFromFilename(filename, opts = {}) { + const ext = path.extname(filename).toLowerCase(); + let base = SUPPORTED_EXT.includes(ext) + ? filename.slice(0, -ext.length) + : filename; + if (opts.stripPrefix) base = stripPrefix(base, opts.stripPrefix); + return camelToSnakeCase(base); +} + +module.exports = { stripPrefix, camelToSnakeCase, aliasFromFilename }; diff --git a/tests/alias.test.js b/tests/alias.test.js new file mode 100644 index 0000000..7d7bd2c --- /dev/null +++ b/tests/alias.test.js @@ -0,0 +1,50 @@ +const { test } = require('node:test'); +const assert = require('node:assert'); +const { stripPrefix, camelToSnakeCase, aliasFromFilename } = require('../src/alias'); + +test('stripPrefix: strips matching prefix', () => { + assert.strictEqual(stripPrefix('TC_Basics Asset Pack_Bed1', 'TC_Basics Asset Pack_'), 'Bed1'); +}); + +test('stripPrefix: returns unchanged when no match', () => { + assert.strictEqual(stripPrefix('Bed1', 'TC_Basics Asset Pack_'), 'Bed1'); +}); + +test('stripPrefix: empty prefix returns unchanged', () => { + assert.strictEqual(stripPrefix('Bed1', ''), 'Bed1'); +}); + +test('camelToSnakeCase: BenchTable1 -> bench_table1', () => { + assert.strictEqual(camelToSnakeCase('BenchTable1'), 'bench_table1'); +}); + +test('camelToSnakeCase: ArmourStand -> armour_stand', () => { + assert.strictEqual(camelToSnakeCase('ArmourStand'), 'armour_stand'); +}); + +test('camelToSnakeCase: Bed1 -> bed1', () => { + assert.strictEqual(camelToSnakeCase('Bed1'), 'bed1'); +}); + +test('camelToSnakeCase: BarrelLarge -> barrel_large', () => { + assert.strictEqual(camelToSnakeCase('BarrelLarge'), 'barrel_large'); +}); + +test('camelToSnakeCase: already snake_case unchanged', () => { + assert.strictEqual(camelToSnakeCase('bench_table1'), 'bench_table1'); +}); + +test('aliasFromFilename: full pipeline', () => { + const alias = aliasFromFilename('TC_Basics Asset Pack_BenchTable1.png', { + stripPrefix: 'TC_Basics Asset Pack_', + }); + assert.strictEqual(alias, 'bench_table1'); +}); + +test('aliasFromFilename: no opts -> bare-name snake_case', () => { + assert.strictEqual(aliasFromFilename('Bed1.png', {}), 'bed1'); +}); + +test('aliasFromFilename: handles .webp extension', () => { + assert.strictEqual(aliasFromFilename('Bed1.webp', {}), 'bed1'); +});