@remvst/asset-catalog 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/lib/generate-image-catalog.d.ts +2 -0
- package/lib/generate-image-catalog.js +106 -0
- package/lib/generate-sound-catalog.d.ts +2 -0
- package/lib/generate-sound-catalog.js +124 -0
- package/lib/tree.d.ts +3 -0
- package/lib/tree.js +19 -0
- package/lib/utils.d.ts +8 -0
- package/lib/utils.js +67 -0
- package/package.json +27 -0
- package/src/generate-image-catalog.ts +113 -0
- package/src/generate-sound-catalog.ts +138 -0
- package/src/tree.ts +23 -0
- package/src/utils.ts +45 -0
- package/testData/directions/left.png +0 -0
- package/testData/directions/up.png +0 -0
- package/testData/fire.png +0 -0
- package/testData/sounds/click/UI_Click_Metallic_mono.mp3 +0 -0
- package/testData/sounds/click/UI_Click_Metallic_mono.ogg +0 -0
- package/testData/sounds/jump/jump2.ogg +0 -0
- package/testData/sounds/jump/xhGkA9Px.mp3 +0 -0
- package/tsconfig.json +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const image_size_1 = __importDefault(require("image-size"));
|
|
9
|
+
const utils_1 = require("./utils");
|
|
10
|
+
const path_1 = require("path");
|
|
11
|
+
const yargs_1 = __importDefault(require("yargs/yargs"));
|
|
12
|
+
const helpers_1 = require("yargs/helpers");
|
|
13
|
+
const tree_1 = require("./tree");
|
|
14
|
+
function importName(assetDir, png) {
|
|
15
|
+
let importName = png;
|
|
16
|
+
importName = (0, path_1.resolve)(png);
|
|
17
|
+
const prefix = (0, path_1.resolve)(assetDir);
|
|
18
|
+
const prefixIndex = importName.indexOf(prefix);
|
|
19
|
+
if (prefixIndex >= 0) {
|
|
20
|
+
importName = importName.slice(prefixIndex + prefix.length);
|
|
21
|
+
}
|
|
22
|
+
importName = (0, utils_1.sanitize)(importName);
|
|
23
|
+
return importName;
|
|
24
|
+
}
|
|
25
|
+
function generatedTemplateInterface(tree, name, indent = '') {
|
|
26
|
+
let generated = '{\n';
|
|
27
|
+
for (const [subname, item] of tree.entries()) {
|
|
28
|
+
if (item instanceof Map) {
|
|
29
|
+
const generatedSub = generatedTemplateInterface(item, subname, indent + ' ');
|
|
30
|
+
generated += indent + ` ${(0, utils_1.lowerCamelize)(subname)}: ${generatedSub}`;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
generated += indent + ` ${(0, utils_1.lowerCamelize)(subname)}: T,\n`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
generated += indent + '}\n';
|
|
37
|
+
return generated;
|
|
38
|
+
}
|
|
39
|
+
async function generatedCreateCatalogFunction(assetDir, tree) {
|
|
40
|
+
async function rec(tree, indent = '') {
|
|
41
|
+
let generated = '{\n';
|
|
42
|
+
for (const [subname, item] of tree.entries()) {
|
|
43
|
+
if (item instanceof Map) {
|
|
44
|
+
const generatedSub = await rec(item, indent + ' ');
|
|
45
|
+
generated += indent + ` ${(0, utils_1.lowerCamelize)(subname)}: ${generatedSub},\n`;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const dimensions = (0, image_size_1.default)(item);
|
|
49
|
+
const stats = await fs_1.promises.stat(item);
|
|
50
|
+
generated += indent + ` ${(0, utils_1.lowerCamelize)(subname)}: createItem({
|
|
51
|
+
path: ${importName(assetDir, item)},
|
|
52
|
+
width: ${dimensions.width},
|
|
53
|
+
height: ${dimensions.height},
|
|
54
|
+
size: ${stats.size},
|
|
55
|
+
}),\n`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
generated += indent + '}';
|
|
59
|
+
return generated;
|
|
60
|
+
}
|
|
61
|
+
let generated = '\n';
|
|
62
|
+
generated += 'export function createTextureCatalog<T>(createItem: (opts: {path: string, width: number, height: number, size: number}) => T): TextureCatalog<T> {\n';
|
|
63
|
+
generated += ` return ${await rec(tree, ' ')};\n`;
|
|
64
|
+
generated += '}\n';
|
|
65
|
+
return generated;
|
|
66
|
+
}
|
|
67
|
+
async function main() {
|
|
68
|
+
const argv = await (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
69
|
+
.options({
|
|
70
|
+
'outFile': {
|
|
71
|
+
type: 'string',
|
|
72
|
+
default: '.',
|
|
73
|
+
alias: 'o',
|
|
74
|
+
describe: 'Directory to generate the files into',
|
|
75
|
+
},
|
|
76
|
+
'assetDir': {
|
|
77
|
+
type: 'string',
|
|
78
|
+
default: './package.json',
|
|
79
|
+
alias: 'a',
|
|
80
|
+
describe: 'package.json file to use for the version',
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
.argv;
|
|
84
|
+
const texturesRoot = argv.assetDir;
|
|
85
|
+
const generatedTs = argv.outFile;
|
|
86
|
+
try {
|
|
87
|
+
await fs_1.promises.rm(generatedTs, { 'recursive': true });
|
|
88
|
+
}
|
|
89
|
+
catch (e) { }
|
|
90
|
+
const files = await (0, utils_1.allFiles)(texturesRoot);
|
|
91
|
+
const pngs = files.filter(file => (0, path_1.extname)(file) === '.png');
|
|
92
|
+
const imports = [];
|
|
93
|
+
const tree = await (0, tree_1.generateTree)(argv.assetDir, pngs);
|
|
94
|
+
for (const png of pngs) {
|
|
95
|
+
const importPath = (0, path_1.relative)((0, path_1.dirname)(argv.outFile), png).replace(/\\/g, '/');
|
|
96
|
+
imports.push(`import ${importName(argv.assetDir, png)} from './${importPath}';`);
|
|
97
|
+
}
|
|
98
|
+
let generatedFileContent = '';
|
|
99
|
+
generatedFileContent += imports.join('\n');
|
|
100
|
+
generatedFileContent += '\n\n';
|
|
101
|
+
generatedFileContent += 'export type TextureCatalog<T> = ' + generatedTemplateInterface(tree, 'TextureCatalog');
|
|
102
|
+
generatedFileContent += '\n\n';
|
|
103
|
+
generatedFileContent += await generatedCreateCatalogFunction(argv.assetDir, tree);
|
|
104
|
+
await fs_1.promises.writeFile(generatedTs, generatedFileContent);
|
|
105
|
+
}
|
|
106
|
+
main();
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const utils_1 = require("./utils");
|
|
9
|
+
const yargs_1 = __importDefault(require("yargs/yargs"));
|
|
10
|
+
const helpers_1 = require("yargs/helpers");
|
|
11
|
+
const path_1 = require("path");
|
|
12
|
+
async function main() {
|
|
13
|
+
const argv = await (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
14
|
+
.options({
|
|
15
|
+
'outFile': {
|
|
16
|
+
type: 'string',
|
|
17
|
+
default: '.',
|
|
18
|
+
alias: 'o',
|
|
19
|
+
describe: 'Directory to generate the files into',
|
|
20
|
+
},
|
|
21
|
+
'assetDir': {
|
|
22
|
+
type: 'string',
|
|
23
|
+
default: './package.json',
|
|
24
|
+
alias: 'a',
|
|
25
|
+
describe: 'package.json file to use for the version',
|
|
26
|
+
},
|
|
27
|
+
'wav': {
|
|
28
|
+
type: 'boolean',
|
|
29
|
+
default: false,
|
|
30
|
+
describe: 'Include .wav files',
|
|
31
|
+
},
|
|
32
|
+
'ogg': {
|
|
33
|
+
type: 'boolean',
|
|
34
|
+
default: true,
|
|
35
|
+
describe: 'Include .ogg files',
|
|
36
|
+
},
|
|
37
|
+
'mp3': {
|
|
38
|
+
type: 'boolean',
|
|
39
|
+
default: true,
|
|
40
|
+
describe: 'Include .mp3 files',
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
.argv;
|
|
44
|
+
const extensions = [];
|
|
45
|
+
if (argv.mp3)
|
|
46
|
+
extensions.push('.mp3');
|
|
47
|
+
if (argv.ogg)
|
|
48
|
+
extensions.push('.ogg');
|
|
49
|
+
if (argv.wav)
|
|
50
|
+
extensions.push('.wav');
|
|
51
|
+
const generatedTs = argv.outFile;
|
|
52
|
+
try {
|
|
53
|
+
await fs_1.promises.rm(generatedTs, { 'recursive': true });
|
|
54
|
+
}
|
|
55
|
+
catch (e) { }
|
|
56
|
+
const files = await (0, utils_1.allFiles)(argv.assetDir);
|
|
57
|
+
const sounds = files.filter(file => extensions.indexOf((0, path_1.extname)(file)) >= 0);
|
|
58
|
+
console.log(sounds);
|
|
59
|
+
// const categories = new Map<string, Set<string>>();
|
|
60
|
+
const defs = new Map();
|
|
61
|
+
for (const sound of sounds) {
|
|
62
|
+
const category = (0, utils_1.categoryPath)(argv.assetDir, sound).join('_');
|
|
63
|
+
const ext = (0, path_1.extname)(sound);
|
|
64
|
+
const filenameWithoutExt = (0, path_1.basename)(sound, ext);
|
|
65
|
+
if (!defs.has(category)) {
|
|
66
|
+
defs.set(category, new Map());
|
|
67
|
+
}
|
|
68
|
+
if (!defs.get(category).has(filenameWithoutExt)) {
|
|
69
|
+
defs.get(category).set(filenameWithoutExt, new Set());
|
|
70
|
+
}
|
|
71
|
+
defs.get(category).get(filenameWithoutExt).add(sound);
|
|
72
|
+
}
|
|
73
|
+
console.log(defs);
|
|
74
|
+
const imports = [];
|
|
75
|
+
const definitions = [];
|
|
76
|
+
const funcs = [];
|
|
77
|
+
let soundDefinition = '';
|
|
78
|
+
soundDefinition += 'export class SoundDefinition {\n';
|
|
79
|
+
soundDefinition += ' constructor(\n';
|
|
80
|
+
soundDefinition += ' readonly basename: string,\n';
|
|
81
|
+
soundDefinition += ' readonly files: string[],\n';
|
|
82
|
+
soundDefinition += ' readonly averageFileSize: number,\n';
|
|
83
|
+
soundDefinition += ' ) {}\n';
|
|
84
|
+
soundDefinition += '}\n';
|
|
85
|
+
definitions.push(soundDefinition);
|
|
86
|
+
for (const category of defs.keys()) {
|
|
87
|
+
let func = `export function sound_${category}(): SoundDefinition[] {\n`;
|
|
88
|
+
func += ` return [\n`;
|
|
89
|
+
for (const filenameWithoutExt of defs.get(category).keys()) {
|
|
90
|
+
func += ` new SoundDefinition(\n`;
|
|
91
|
+
func += ` '${filenameWithoutExt}',\n`;
|
|
92
|
+
func += ' [\n';
|
|
93
|
+
const files = Array.from(defs.get(category).get(filenameWithoutExt));
|
|
94
|
+
const idealOrder = ['.ogg', '.mp3', '.wav'];
|
|
95
|
+
files.sort((a, b) => {
|
|
96
|
+
return idealOrder.indexOf((0, path_1.extname)(a)) - idealOrder.indexOf((0, path_1.extname)(b));
|
|
97
|
+
});
|
|
98
|
+
let totalFileSize = 0;
|
|
99
|
+
for (const soundFile of files) {
|
|
100
|
+
const importName = (0, utils_1.sanitize)(soundFile);
|
|
101
|
+
imports.push(`import ${importName} from '${(0, path_1.relative)((0, path_1.dirname)(argv.outFile), soundFile)}';`);
|
|
102
|
+
func += ` ${importName},\n`;
|
|
103
|
+
const stats = await fs_1.promises.stat(soundFile);
|
|
104
|
+
totalFileSize += stats.size;
|
|
105
|
+
}
|
|
106
|
+
const averageFileSize = Math.round(totalFileSize / files.length);
|
|
107
|
+
func += ' ],\n';
|
|
108
|
+
func += ` ${averageFileSize},\n`;
|
|
109
|
+
func += ' ),\n';
|
|
110
|
+
}
|
|
111
|
+
func += ` ];\n`;
|
|
112
|
+
func += `}`;
|
|
113
|
+
funcs.push(func);
|
|
114
|
+
}
|
|
115
|
+
let generatedFileContent = '';
|
|
116
|
+
generatedFileContent += imports.join('\n');
|
|
117
|
+
generatedFileContent += '\n\n';
|
|
118
|
+
generatedFileContent += definitions.join('\n\n');
|
|
119
|
+
generatedFileContent += '\n\n';
|
|
120
|
+
generatedFileContent += funcs.join('\n\n');
|
|
121
|
+
generatedFileContent += '\n';
|
|
122
|
+
await fs_1.promises.writeFile(generatedTs, generatedFileContent);
|
|
123
|
+
}
|
|
124
|
+
main();
|
package/lib/tree.d.ts
ADDED
package/lib/tree.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateTree = void 0;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
async function generateTree(dir, files) {
|
|
6
|
+
const tree = new Map();
|
|
7
|
+
for (const file of files) {
|
|
8
|
+
let subtree = tree;
|
|
9
|
+
for (const category of (0, utils_1.categoryPath)(dir, file)) {
|
|
10
|
+
if (!subtree.has(category)) {
|
|
11
|
+
subtree.set(category, new Map());
|
|
12
|
+
}
|
|
13
|
+
subtree = subtree.get(category);
|
|
14
|
+
}
|
|
15
|
+
subtree.set((0, utils_1.basename)(file).replace('.file', ''), file);
|
|
16
|
+
}
|
|
17
|
+
return tree;
|
|
18
|
+
}
|
|
19
|
+
exports.generateTree = generateTree;
|
package/lib/utils.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function allFiles(path: string): Promise<string[]>;
|
|
2
|
+
export declare function basename(path: string): string;
|
|
3
|
+
export declare function sanitize(string: string): string;
|
|
4
|
+
export declare function execShellCommand(cmd: string): Promise<string>;
|
|
5
|
+
export declare function toUpperSnakeCase(str: string): string;
|
|
6
|
+
export declare function camelize(str: string): string;
|
|
7
|
+
export declare function lowerCamelize(str: string): string;
|
|
8
|
+
export declare function categoryPath(assetDir: string, png: string): string[];
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.categoryPath = exports.lowerCamelize = exports.camelize = exports.toUpperSnakeCase = exports.execShellCommand = exports.sanitize = exports.basename = exports.allFiles = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
async function allFiles(path) {
|
|
8
|
+
const files = await fs_1.promises.readdir(path);
|
|
9
|
+
const res = [];
|
|
10
|
+
for (const file of files) {
|
|
11
|
+
const filePath = `${path}/${file}`;
|
|
12
|
+
const stat = await fs_1.promises.lstat(filePath);
|
|
13
|
+
if (stat.isDirectory()) {
|
|
14
|
+
for (const subfile of await allFiles(filePath)) {
|
|
15
|
+
res.push(subfile);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
res.push(filePath);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return res;
|
|
23
|
+
}
|
|
24
|
+
exports.allFiles = allFiles;
|
|
25
|
+
function basename(path) {
|
|
26
|
+
const filename = path.slice(path.lastIndexOf('/') + 1);
|
|
27
|
+
return filename.slice(0, filename.lastIndexOf('.'));
|
|
28
|
+
}
|
|
29
|
+
exports.basename = basename;
|
|
30
|
+
function sanitize(string) {
|
|
31
|
+
return string.replace(/[^a-zA-Z0-9]/g, '_');
|
|
32
|
+
}
|
|
33
|
+
exports.sanitize = sanitize;
|
|
34
|
+
function execShellCommand(cmd) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
(0, child_process_1.exec)(cmd, (error, stdout, stderr) => {
|
|
37
|
+
if (error) {
|
|
38
|
+
reject(error);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
resolve(stdout ? stdout : stderr);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
exports.execShellCommand = execShellCommand;
|
|
46
|
+
function toUpperSnakeCase(str) {
|
|
47
|
+
return str.toUpperCase().replace(/[^A-Z0-9]/g, '_');
|
|
48
|
+
}
|
|
49
|
+
exports.toUpperSnakeCase = toUpperSnakeCase;
|
|
50
|
+
function camelize(str) {
|
|
51
|
+
return str.split(/[^a-z0-9]/gi).map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('');
|
|
52
|
+
}
|
|
53
|
+
exports.camelize = camelize;
|
|
54
|
+
function lowerCamelize(str) {
|
|
55
|
+
const camelized = module.exports.camelize(str);
|
|
56
|
+
return camelized.slice(0, 1).toLowerCase() + camelized.slice(1);
|
|
57
|
+
}
|
|
58
|
+
exports.lowerCamelize = lowerCamelize;
|
|
59
|
+
function categoryPath(assetDir, png) {
|
|
60
|
+
const dir = (0, path_1.dirname)(png);
|
|
61
|
+
const trimmedDir = (0, path_1.relative)(assetDir, dir);
|
|
62
|
+
return trimmedDir
|
|
63
|
+
.split('/')
|
|
64
|
+
.map(component => lowerCamelize(component))
|
|
65
|
+
.filter(component => component.length > 0);
|
|
66
|
+
}
|
|
67
|
+
exports.categoryPath = categoryPath;
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@remvst/asset-catalog",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"bin": {
|
|
6
|
+
"generate-image-catalog": "./lib/generate-image-catalog.js",
|
|
7
|
+
"generate-sound-catalog": "./lib/generate-sound-catalog.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "rm -rf lib && tsc",
|
|
11
|
+
"test:images": "ts-node src/generate-image-catalog.ts --assetDir=./testData --outFile=testOut/images.ts",
|
|
12
|
+
"test:sounds": "ts-node src/generate-sound-catalog.ts --assetDir=./testData/sounds --outFile=testOut/sounds.ts",
|
|
13
|
+
"test": "npm run test:images && npm run test:sounds"
|
|
14
|
+
},
|
|
15
|
+
"author": "Rémi Vansteelandt",
|
|
16
|
+
"license": "UNLICENSED",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"image-size": "^1.0.2",
|
|
19
|
+
"yargs": "^17.7.2"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^18.11.5",
|
|
23
|
+
"@types/yargs": "^17.0.32",
|
|
24
|
+
"ts-node": "^10.9.1",
|
|
25
|
+
"typescript": "^5.2.2"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { promises as fs } from 'fs';
|
|
4
|
+
import sizeOf from 'image-size';
|
|
5
|
+
import { sanitize, allFiles, lowerCamelize } from './utils';
|
|
6
|
+
import { resolve, relative, dirname, extname } from 'path';
|
|
7
|
+
import yargs from 'yargs/yargs';
|
|
8
|
+
import { hideBin } from 'yargs/helpers';
|
|
9
|
+
import { Tree, generateTree } from './tree';
|
|
10
|
+
|
|
11
|
+
function importName(assetDir: string, png: string): string {
|
|
12
|
+
let importName = png;
|
|
13
|
+
importName = resolve(png);
|
|
14
|
+
|
|
15
|
+
const prefix = resolve(assetDir);
|
|
16
|
+
const prefixIndex = importName.indexOf(prefix);
|
|
17
|
+
if (prefixIndex >= 0) {
|
|
18
|
+
importName = importName.slice(prefixIndex + prefix.length);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
importName = sanitize(importName);
|
|
22
|
+
return importName;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function generatedTemplateInterface(tree: Tree, name: string, indent: string = ''): string {
|
|
26
|
+
let generated = '{\n';
|
|
27
|
+
for (const [subname, item] of tree.entries()) {
|
|
28
|
+
if (item instanceof Map) {
|
|
29
|
+
const generatedSub = generatedTemplateInterface(item, subname, indent + ' ');
|
|
30
|
+
generated += indent + ` ${lowerCamelize(subname)}: ${generatedSub}`;
|
|
31
|
+
} else {
|
|
32
|
+
generated += indent + ` ${lowerCamelize(subname)}: T,\n`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
generated += indent + '}\n';
|
|
36
|
+
return generated;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function generatedCreateCatalogFunction(assetDir: string, tree: Tree): Promise<string> {
|
|
40
|
+
async function rec(tree: Tree, indent: string = '') {
|
|
41
|
+
let generated = '{\n';
|
|
42
|
+
for (const [subname, item] of tree.entries()) {
|
|
43
|
+
if (item instanceof Map) {
|
|
44
|
+
const generatedSub = await rec(item, indent + ' ');
|
|
45
|
+
generated += indent + ` ${lowerCamelize(subname)}: ${generatedSub},\n`;
|
|
46
|
+
} else {
|
|
47
|
+
const dimensions = sizeOf(item);
|
|
48
|
+
const stats = await fs.stat(item);
|
|
49
|
+
generated += indent + ` ${lowerCamelize(subname)}: createItem({
|
|
50
|
+
path: ${importName(assetDir, item)},
|
|
51
|
+
width: ${dimensions.width},
|
|
52
|
+
height: ${dimensions.height},
|
|
53
|
+
size: ${stats.size},
|
|
54
|
+
}),\n`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
generated += indent + '}';
|
|
58
|
+
return generated;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let generated = '\n';
|
|
62
|
+
generated += 'export function createTextureCatalog<T>(createItem: (opts: {path: string, width: number, height: number, size: number}) => T): TextureCatalog<T> {\n';
|
|
63
|
+
generated += ` return ${await rec(tree, ' ')};\n`;
|
|
64
|
+
generated += '}\n';
|
|
65
|
+
return generated;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function main() {
|
|
69
|
+
const argv = await yargs(hideBin(process.argv))
|
|
70
|
+
.options({
|
|
71
|
+
'outFile': {
|
|
72
|
+
type: 'string',
|
|
73
|
+
default: '.',
|
|
74
|
+
alias: 'o',
|
|
75
|
+
describe: 'Directory to generate the files into',
|
|
76
|
+
},
|
|
77
|
+
'assetDir': {
|
|
78
|
+
type: 'string',
|
|
79
|
+
default: './package.json',
|
|
80
|
+
alias: 'a',
|
|
81
|
+
describe: 'package.json file to use for the version',
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
.argv;
|
|
85
|
+
|
|
86
|
+
const texturesRoot = argv.assetDir;
|
|
87
|
+
const generatedTs = argv.outFile;
|
|
88
|
+
try {
|
|
89
|
+
await fs.rm(generatedTs, { 'recursive': true });
|
|
90
|
+
} catch (e) {}
|
|
91
|
+
|
|
92
|
+
const files = await allFiles(texturesRoot);
|
|
93
|
+
const pngs = files.filter(file => extname(file) === '.png');
|
|
94
|
+
|
|
95
|
+
const imports = [];
|
|
96
|
+
const tree = await generateTree(argv.assetDir, pngs);
|
|
97
|
+
|
|
98
|
+
for (const png of pngs) {
|
|
99
|
+
const importPath = relative(dirname(argv.outFile), png).replace(/\\/g, '/');
|
|
100
|
+
imports.push(`import ${importName(argv.assetDir, png)} from './${importPath}';`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let generatedFileContent = '';
|
|
104
|
+
generatedFileContent += imports.join('\n');
|
|
105
|
+
generatedFileContent += '\n\n';
|
|
106
|
+
generatedFileContent += 'export type TextureCatalog<T> = ' + generatedTemplateInterface(tree, 'TextureCatalog');
|
|
107
|
+
generatedFileContent += '\n\n';
|
|
108
|
+
generatedFileContent += await generatedCreateCatalogFunction(argv.assetDir, tree);
|
|
109
|
+
|
|
110
|
+
await fs.writeFile(generatedTs, generatedFileContent);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
main();
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { promises as fs } from 'fs';
|
|
4
|
+
import { sanitize, allFiles, categoryPath } from './utils';
|
|
5
|
+
import yargs from 'yargs/yargs';
|
|
6
|
+
import { hideBin } from 'yargs/helpers';
|
|
7
|
+
import { dirname, extname, basename, relative } from 'path';
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
const argv = await yargs(hideBin(process.argv))
|
|
11
|
+
.options({
|
|
12
|
+
'outFile': {
|
|
13
|
+
type: 'string',
|
|
14
|
+
default: '.',
|
|
15
|
+
alias: 'o',
|
|
16
|
+
describe: 'Directory to generate the files into',
|
|
17
|
+
},
|
|
18
|
+
'assetDir': {
|
|
19
|
+
type: 'string',
|
|
20
|
+
default: './package.json',
|
|
21
|
+
alias: 'a',
|
|
22
|
+
describe: 'package.json file to use for the version',
|
|
23
|
+
},
|
|
24
|
+
'wav': {
|
|
25
|
+
type: 'boolean',
|
|
26
|
+
default: false,
|
|
27
|
+
describe: 'Include .wav files',
|
|
28
|
+
},
|
|
29
|
+
'ogg': {
|
|
30
|
+
type: 'boolean',
|
|
31
|
+
default: true,
|
|
32
|
+
describe: 'Include .ogg files',
|
|
33
|
+
},
|
|
34
|
+
'mp3': {
|
|
35
|
+
type: 'boolean',
|
|
36
|
+
default: true,
|
|
37
|
+
describe: 'Include .mp3 files',
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
.argv;
|
|
41
|
+
|
|
42
|
+
const extensions: string[] = [];
|
|
43
|
+
if (argv.mp3) extensions.push('.mp3');
|
|
44
|
+
if (argv.ogg) extensions.push('.ogg');
|
|
45
|
+
if (argv.wav) extensions.push('.wav');
|
|
46
|
+
|
|
47
|
+
const generatedTs = argv.outFile;
|
|
48
|
+
try {
|
|
49
|
+
await fs.rm(generatedTs, { 'recursive': true });
|
|
50
|
+
} catch (e) {}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
const files = await allFiles(argv.assetDir);
|
|
54
|
+
const sounds = files.filter(file => extensions.indexOf(extname(file)) >= 0);
|
|
55
|
+
|
|
56
|
+
const defs = new Map<string, Map<string, Set<string>>>();
|
|
57
|
+
|
|
58
|
+
for (const sound of sounds) {
|
|
59
|
+
const category = categoryPath(argv.assetDir, sound).join('_');
|
|
60
|
+
const ext = extname(sound);
|
|
61
|
+
const filenameWithoutExt = basename(sound, ext);
|
|
62
|
+
|
|
63
|
+
if (!defs.has(category)) {
|
|
64
|
+
defs.set(category, new Map());
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!defs.get(category)!.has(filenameWithoutExt)) {
|
|
68
|
+
defs.get(category)!.set(filenameWithoutExt, new Set());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
defs.get(category)!.get(filenameWithoutExt)!.add(sound);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log(defs);
|
|
75
|
+
|
|
76
|
+
const imports = [];
|
|
77
|
+
const definitions = [];
|
|
78
|
+
const funcs = [];
|
|
79
|
+
|
|
80
|
+
let soundDefinition = '';
|
|
81
|
+
soundDefinition += 'export class SoundDefinition {\n';
|
|
82
|
+
soundDefinition += ' constructor(\n';
|
|
83
|
+
soundDefinition += ' readonly basename: string,\n';
|
|
84
|
+
soundDefinition += ' readonly files: string[],\n';
|
|
85
|
+
soundDefinition += ' readonly averageFileSize: number,\n';
|
|
86
|
+
soundDefinition += ' ) {}\n';
|
|
87
|
+
soundDefinition += '}\n';
|
|
88
|
+
definitions.push(soundDefinition);
|
|
89
|
+
|
|
90
|
+
for (const category of defs.keys()) {
|
|
91
|
+
let func = `export function sound_${category}(): SoundDefinition[] {\n`;
|
|
92
|
+
func += ` return [\n`;
|
|
93
|
+
|
|
94
|
+
for (const filenameWithoutExt of defs.get(category)!.keys()) {
|
|
95
|
+
func += ` new SoundDefinition(\n`;
|
|
96
|
+
func += ` '${filenameWithoutExt}',\n`;
|
|
97
|
+
func += ' [\n';
|
|
98
|
+
|
|
99
|
+
const files = Array.from(defs.get(category)!.get(filenameWithoutExt)!);
|
|
100
|
+
const idealOrder = ['.ogg', '.mp3', '.wav'];
|
|
101
|
+
files.sort((a, b) => {
|
|
102
|
+
return idealOrder.indexOf(extname(a)) - idealOrder.indexOf(extname(b));
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
let totalFileSize = 0;
|
|
106
|
+
for (const soundFile of files) {
|
|
107
|
+
const importName = sanitize(soundFile);
|
|
108
|
+
imports.push(`import ${importName} from '${relative(dirname(argv.outFile), soundFile)}';`);
|
|
109
|
+
func += ` ${importName},\n`;
|
|
110
|
+
const stats = await fs.stat(soundFile);
|
|
111
|
+
totalFileSize += stats.size;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const averageFileSize = Math.round(totalFileSize / files.length);
|
|
115
|
+
|
|
116
|
+
func += ' ],\n';
|
|
117
|
+
func += ` ${averageFileSize},\n`;
|
|
118
|
+
func += ' ),\n';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
func += ` ];\n`;
|
|
122
|
+
func += `}`;
|
|
123
|
+
|
|
124
|
+
funcs.push(func);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let generatedFileContent = '';
|
|
128
|
+
generatedFileContent += imports.join('\n');
|
|
129
|
+
generatedFileContent += '\n\n';
|
|
130
|
+
generatedFileContent += definitions.join('\n\n');
|
|
131
|
+
generatedFileContent += '\n\n';
|
|
132
|
+
generatedFileContent += funcs.join('\n\n');
|
|
133
|
+
generatedFileContent += '\n';
|
|
134
|
+
|
|
135
|
+
await fs.writeFile(generatedTs, generatedFileContent);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
main();
|
package/src/tree.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { basename } from "path";
|
|
2
|
+
import { categoryPath } from "./utils";
|
|
3
|
+
|
|
4
|
+
export type Tree = Map<string, TreeOrPath>;
|
|
5
|
+
export type TreeOrPath = Tree | string;
|
|
6
|
+
|
|
7
|
+
export async function generateTree(dir: string, files: string[]): Promise<Tree> {
|
|
8
|
+
const tree = new Map();
|
|
9
|
+
|
|
10
|
+
for (const file of files) {
|
|
11
|
+
let subtree = tree;
|
|
12
|
+
for (const category of categoryPath(dir, file)) {
|
|
13
|
+
if (!subtree.has(category)) {
|
|
14
|
+
subtree.set(category, new Map());
|
|
15
|
+
}
|
|
16
|
+
subtree = subtree.get(category);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
subtree.set(basename(file).replace('.file', ''), file);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return tree;
|
|
23
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { dirname, relative } from 'path';
|
|
3
|
+
|
|
4
|
+
export async function allFiles(path: string): Promise<string[]> {
|
|
5
|
+
const files = await fs.readdir(path);
|
|
6
|
+
|
|
7
|
+
const res: string[] = [];
|
|
8
|
+
for (const file of files) {
|
|
9
|
+
const filePath = `${path}/${file}`;
|
|
10
|
+
|
|
11
|
+
const stat = await fs.lstat(filePath);
|
|
12
|
+
if (stat.isDirectory()) {
|
|
13
|
+
for (const subfile of await allFiles(filePath)) {
|
|
14
|
+
res.push(subfile);
|
|
15
|
+
}
|
|
16
|
+
} else {
|
|
17
|
+
res.push(filePath);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return res;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function sanitize(string: string): string {
|
|
25
|
+
return string.replace(/[^a-zA-Z0-9]/g, '_');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function camelize(str: string) {
|
|
29
|
+
return str.split(/[^a-z0-9]/gi).map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function lowerCamelize(str: string): string {
|
|
33
|
+
const camelized = module.exports.camelize(str);
|
|
34
|
+
return camelized.slice(0, 1).toLowerCase() + camelized.slice(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
export function categoryPath(assetDir: string, png: string): string[] {
|
|
39
|
+
const dir = dirname(png);
|
|
40
|
+
const trimmedDir = relative(assetDir, dir);
|
|
41
|
+
return trimmedDir
|
|
42
|
+
.split('/')
|
|
43
|
+
.map(component => lowerCamelize(component))
|
|
44
|
+
.filter(component => component.length > 0);
|
|
45
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"allowJs": false,
|
|
9
|
+
"outDir": "./lib",
|
|
10
|
+
"skipLibCheck": true
|
|
11
|
+
},
|
|
12
|
+
"include": ["./src/**/*"],
|
|
13
|
+
"exclude": ["node_modules"]
|
|
14
|
+
}
|