@bcts/lifehash-cli 1.0.0-alpha.17
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/LICENSE +48 -0
- package/README.md +109 -0
- package/dist/index.cjs +317 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +191 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +191 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +308 -0
- package/dist/index.mjs.map +1 -0
- package/dist/main.mjs +209 -0
- package/dist/main.mjs.map +1 -0
- package/package.json +92 -0
- package/src/index.ts +104 -0
- package/src/main.ts +150 -0
- package/src/png-writer.ts +101 -0
- package/src/utils.ts +75 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["Version","makeFromUtf8"],"sources":["../src/png-writer.ts","../src/utils.ts","../src/main.ts","../src/index.ts"],"sourcesContent":["/**\n * PNG writer for LifeHash images.\n *\n * Ported from bc-lifehash-cli C++ implementation (png-writer.hpp).\n * Uses pngjs instead of libpng.\n *\n * @module\n */\n\nimport { writeFileSync } from \"fs\";\nimport { PNG } from \"pngjs\";\nimport type { Image } from \"@bcts/lifehash\";\n\n/**\n * Writes a LifeHash image to a PNG file.\n *\n * Port of `write_image()` from lifehash.cpp lines 174-186 and\n * PNGWriter class from png-writer.hpp.\n *\n * @param image - The LifeHash image to write\n * @param filename - The output filename\n * @category PNG Encoding\n *\n * @example\n * ```typescript\n * import { makeFromUtf8 } from \"@bcts/lifehash\";\n *\n * const image = makeFromUtf8(\"Hello\");\n * writeImage(image, \"Hello.png\");\n * ```\n */\nexport function writeImage(image: Image, filename: string): void {\n const png = new PNG({\n width: image.width,\n height: image.height,\n colorType: 2, // RGB\n bitDepth: 8,\n inputColorType: 2,\n inputHasAlpha: false,\n });\n\n // Copy RGB data from image to PNG\n // PNG expects RGBA, so we need to add alpha channel\n for (let y = 0; y < image.height; y++) {\n for (let x = 0; x < image.width; x++) {\n const srcOffset = (y * image.width + x) * 3;\n const dstOffset = (y * image.width + x) * 4;\n\n // Copy RGB from source\n png.data[dstOffset] = image.colors[srcOffset]; // R\n png.data[dstOffset + 1] = image.colors[srcOffset + 1]; // G\n png.data[dstOffset + 2] = image.colors[srcOffset + 2]; // B\n png.data[dstOffset + 3] = 255; // A (fully opaque)\n }\n }\n\n // Write to file\n const buffer = PNG.sync.write(png);\n writeFileSync(filename, buffer);\n}\n\n/**\n * Generates a PNG buffer from a LifeHash image without writing to disk.\n *\n * @param image - The LifeHash image to encode\n * @returns A Buffer containing the PNG data\n * @category PNG Encoding\n *\n * @example\n * ```typescript\n * import { makeFromUtf8 } from \"@bcts/lifehash\";\n *\n * const image = makeFromUtf8(\"Hello\");\n * const pngBuffer = generatePNG(image);\n * ```\n */\nexport function generatePNG(image: Image): Buffer {\n const png = new PNG({\n width: image.width,\n height: image.height,\n colorType: 2,\n bitDepth: 8,\n inputColorType: 2,\n inputHasAlpha: false,\n });\n\n // Copy RGB data from image to PNG\n for (let y = 0; y < image.height; y++) {\n for (let x = 0; x < image.width; x++) {\n const srcOffset = (y * image.width + x) * 3;\n const dstOffset = (y * image.width + x) * 4;\n\n png.data[dstOffset] = image.colors[srcOffset];\n png.data[dstOffset + 1] = image.colors[srcOffset + 1];\n png.data[dstOffset + 2] = image.colors[srcOffset + 2];\n png.data[dstOffset + 3] = 255;\n }\n }\n\n return PNG.sync.write(png);\n}\n","/**\n * Utility functions for the LifeHash CLI.\n *\n * Ported from bc-lifehash-cli C++ implementation.\n *\n * @module\n */\n\n/**\n * Appends a path component to a path, handling trailing slashes correctly.\n *\n * Port of `appending_path_component()` from lifehash.cpp lines 18-24.\n *\n * @param path - The base path\n * @param component - The component to append\n * @returns The combined path\n * @category Utilities\n *\n * @example\n * ```typescript\n * appendingPathComponent(\"\", \"file.png\") // => \"file.png\"\n * appendingPathComponent(\"/tmp/\", \"file.png\") // => \"/tmp/file.png\"\n * appendingPathComponent(\"/tmp\", \"file.png\") // => \"/tmp/file.png\"\n * ```\n */\nexport function appendingPathComponent(path: string, component: string): string {\n if (path === \"\") {\n return component;\n }\n if (path.endsWith(\"/\")) {\n return `${path}${component}`;\n }\n return `${path}/${component}`;\n}\n\n/**\n * Selects a random element from an array.\n *\n * Port of `random_element()` template from lifehash.cpp lines 38-50.\n *\n * @param array - The array to select from\n * @returns A random element from the array\n * @category Utilities\n *\n * @example\n * ```typescript\n * randomElement([\"A\", \"B\", \"C\"]) // => \"A\" or \"B\" or \"C\"\n * ```\n */\nexport function randomElement<T>(array: T[]): T {\n const index = Math.floor(Math.random() * array.length);\n return array[index];\n}\n\n/**\n * Generates a random input string in \"XXX-XXX\" format where X is a random uppercase letter.\n *\n * Port of `make_random_input()` from lifehash.cpp lines 52-57.\n *\n * @returns A random string in \"XXX-XXX\" format\n * @category Utilities\n *\n * @example\n * ```typescript\n * makeRandomInput() // => \"ABC-DEF\" (random letters)\n * ```\n */\nexport function makeRandomInput(): string {\n const letters = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\".split(\"\");\n\n const letter = (): string => randomElement(letters);\n const cluster = (): string => `${letter()}${letter()}${letter()}`;\n\n return `${cluster()}-${cluster()}`;\n}\n","/**\n * LifeHash CLI - Command line tool for generating LifeHash PNG images.\n *\n * Ported from bc-lifehash-cli C++ implementation (lifehash.cpp).\n *\n * @module\n */\n\nimport { Command } from \"commander\";\nimport { makeFromUtf8, Version } from \"@bcts/lifehash\";\nimport { writeImage } from \"./png-writer\";\nimport { appendingPathComponent, makeRandomInput } from \"./utils\";\n\n/**\n * CLI options interface.\n *\n * Port of Parameters struct from lifehash.cpp lines 86-91.\n *\n * @category CLI\n */\nexport interface CliOptions {\n /** LifeHash version to generate */\n version: string;\n /** Size of each module (\"pixel\") */\n module: string;\n /** Output directory path */\n path: string;\n}\n\n/**\n * Parses a version string to the Version enum.\n *\n * Port of version parsing logic from lifehash.cpp lines 130-145.\n *\n * @param versionString - The version string from CLI\n * @returns The corresponding Version enum value\n * @throws Error if the version string is invalid\n * @category CLI\n *\n * @example\n * ```typescript\n * parseVersion(\"version2\") // => Version.version2\n * parseVersion(\"detailed\") // => Version.detailed\n * parseVersion(\"invalid\") // throws Error\n * ```\n */\nexport function parseVersion(versionString: string): Version {\n switch (versionString) {\n case \"version1\":\n return Version.version1;\n case \"version2\":\n return Version.version2;\n case \"detailed\":\n return Version.detailed;\n case \"fiducial\":\n return Version.fiducial;\n case \"grayscaleFiducial\":\n return Version.grayscale_fiducial;\n default:\n throw new Error(\"Invalid version.\");\n }\n}\n\n/**\n * Main execution function that generates a LifeHash image.\n *\n * Port of `run()` from lifehash.cpp lines 188-192.\n *\n * @param input - Input string to hash (or empty for random)\n * @param options - CLI options\n * @category CLI\n *\n * @example\n * ```typescript\n * run(\"Hello\", { version: \"version2\", module: \"1\", path: \".\" });\n * // Generates Hello.png in current directory\n * ```\n */\nexport function run(input: string, options: CliOptions): void {\n // Parse parameters (matching Parameters constructor from C++)\n const version = parseVersion(options.version);\n const moduleSize = parseInt(options.module, 10);\n\n if (moduleSize < 1 || isNaN(moduleSize)) {\n throw new Error(\"Illegal value.\");\n }\n\n // Use random input if none provided\n const actualInput = input !== \"\" ? input : makeRandomInput();\n\n // Generate output filename\n const outputFilename = `${actualInput}.png`;\n const outputFile = appendingPathComponent(options.path, outputFilename);\n\n // Generate LifeHash image\n const image = makeFromUtf8(actualInput, version, moduleSize);\n\n // Write to PNG file\n writeImage(image, outputFile);\n}\n\n/**\n * CLI entry point.\n *\n * Port of `main()` from lifehash.cpp lines 194-206.\n *\n * @category CLI\n */\nexport function main(): void {\n const program = new Command();\n\n program\n .name(\"lifehash\")\n .description(\"Generate LifeHash PNG images from input strings\")\n .argument(\"[input]\", \"Input string to hash (default: random XXX-XXX)\")\n .option(\n \"-v, --version <version>\",\n \"LifeHash version: version1, version2, detailed, fiducial, grayscaleFiducial\",\n \"version2\",\n )\n .option(\"-m, --module <size>\", 'Size of each module (\"pixel\")', \"1\")\n .option(\"-p, --path <path>\", \"Output directory path\", \"\")\n .action((input: string | undefined, options: CliOptions) => {\n try {\n run(input ?? \"\", options);\n } catch (error) {\n if (error instanceof Error) {\n console.log(`\\u{1F928} ${error.message}`);\n console.log();\n program.help();\n }\n process.exit(1);\n }\n });\n\n program.parse();\n}\n\n// Run CLI when executed directly (not when imported as a module)\n// Check if this file is the main entry point\nconst isMainModule =\n typeof process !== \"undefined\" &&\n typeof process.argv[1] === \"string\" &&\n (process.argv[1].endsWith(\"/main.mjs\") ||\n process.argv[1].endsWith(\"/main.js\") ||\n process.argv[1].includes(\"lifehash-cli\"));\n\nif (isMainModule) {\n main();\n}\n","/**\n * @bcts/lifehash-cli - Command line tool for generating LifeHash PNG images.\n *\n * This package provides both a CLI tool and a programmatic API for generating\n * LifeHash visual hash images as PNG files.\n *\n * @packageDocumentation\n * @module @bcts/lifehash-cli\n *\n * @example CLI Usage\n * ```bash\n * # Generate a LifeHash from a string\n * lifehash Hello\n *\n * # Generate with specific version and module size\n * lifehash -v detailed -m 8 Hello\n *\n * # Generate with random input\n * lifehash\n * ```\n *\n * @example Programmatic Usage\n * ```typescript\n * import { generateLifeHash } from \"@bcts/lifehash-cli\";\n * import { writeFileSync } from \"fs\";\n *\n * // Generate a PNG buffer\n * const pngBuffer = generateLifeHash(\"Hello\", {\n * version: \"version2\",\n * moduleSize: 1,\n * });\n *\n * // Write to file\n * writeFileSync(\"Hello.png\", pngBuffer);\n * ```\n */\n\nimport { generatePNG, writeImage } from \"./png-writer\";\nimport { parseVersion, run, type CliOptions } from \"./main\";\nimport { makeFromUtf8, Version, type Image } from \"@bcts/lifehash\";\n\n// PNG encoding exports\nexport { writeImage, generatePNG };\n\n// Utility exports\nexport { appendingPathComponent, randomElement, makeRandomInput } from \"./utils\";\n\n// CLI exports\nexport { parseVersion, run, type CliOptions };\n\n// Re-export @bcts/lifehash types for convenience\nexport { Version, type Image };\n\n// Re-export makeFromUtf8 for programmatic usage\nexport { makeFromUtf8 };\n\n/**\n * Options for generating a LifeHash image.\n *\n * @category Image Generation\n */\nexport interface GenerateOptions {\n /**\n * LifeHash version to generate.\n * @default \"version2\"\n */\n version?: \"version1\" | \"version2\" | \"detailed\" | \"fiducial\" | \"grayscaleFiducial\";\n /**\n * Size of each module (\"pixel\").\n * @default 1\n */\n moduleSize?: number;\n}\n\n/**\n * Generates a LifeHash PNG buffer from an input string.\n *\n * This is the main programmatic API for generating LifeHash images.\n *\n * @param input - The input string to hash\n * @param options - Generation options\n * @returns A Buffer containing the PNG data\n * @category Image Generation\n *\n * @example\n * ```typescript\n * import { generateLifeHash } from \"@bcts/lifehash-cli\";\n * import { writeFileSync } from \"fs\";\n *\n * const pngBuffer = generateLifeHash(\"Hello\", {\n * version: \"version2\",\n * moduleSize: 1,\n * });\n *\n * writeFileSync(\"Hello.png\", pngBuffer);\n * ```\n */\nexport function generateLifeHash(input: string, options: GenerateOptions = {}): Buffer {\n const { version = \"version2\", moduleSize = 1 } = options;\n\n const versionEnum = parseVersion(version);\n const image = makeFromUtf8(input, versionEnum, moduleSize);\n return generatePNG(image);\n}\n"],"mappings":"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"}
|
package/dist/main.mjs
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { Version, makeFromUtf8 } from "@bcts/lifehash";
|
|
4
|
+
import { writeFileSync } from "fs";
|
|
5
|
+
import { PNG } from "pngjs";
|
|
6
|
+
|
|
7
|
+
//#region src/png-writer.ts
|
|
8
|
+
/**
|
|
9
|
+
* PNG writer for LifeHash images.
|
|
10
|
+
*
|
|
11
|
+
* Ported from bc-lifehash-cli C++ implementation (png-writer.hpp).
|
|
12
|
+
* Uses pngjs instead of libpng.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Writes a LifeHash image to a PNG file.
|
|
18
|
+
*
|
|
19
|
+
* Port of `write_image()` from lifehash.cpp lines 174-186 and
|
|
20
|
+
* PNGWriter class from png-writer.hpp.
|
|
21
|
+
*
|
|
22
|
+
* @param image - The LifeHash image to write
|
|
23
|
+
* @param filename - The output filename
|
|
24
|
+
* @category PNG Encoding
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import { makeFromUtf8 } from "@bcts/lifehash";
|
|
29
|
+
*
|
|
30
|
+
* const image = makeFromUtf8("Hello");
|
|
31
|
+
* writeImage(image, "Hello.png");
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
function writeImage(image, filename) {
|
|
35
|
+
const png = new PNG({
|
|
36
|
+
width: image.width,
|
|
37
|
+
height: image.height,
|
|
38
|
+
colorType: 2,
|
|
39
|
+
bitDepth: 8,
|
|
40
|
+
inputColorType: 2,
|
|
41
|
+
inputHasAlpha: false
|
|
42
|
+
});
|
|
43
|
+
for (let y = 0; y < image.height; y++) for (let x = 0; x < image.width; x++) {
|
|
44
|
+
const srcOffset = (y * image.width + x) * 3;
|
|
45
|
+
const dstOffset = (y * image.width + x) * 4;
|
|
46
|
+
png.data[dstOffset] = image.colors[srcOffset];
|
|
47
|
+
png.data[dstOffset + 1] = image.colors[srcOffset + 1];
|
|
48
|
+
png.data[dstOffset + 2] = image.colors[srcOffset + 2];
|
|
49
|
+
png.data[dstOffset + 3] = 255;
|
|
50
|
+
}
|
|
51
|
+
writeFileSync(filename, PNG.sync.write(png));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/utils.ts
|
|
56
|
+
/**
|
|
57
|
+
* Utility functions for the LifeHash CLI.
|
|
58
|
+
*
|
|
59
|
+
* Ported from bc-lifehash-cli C++ implementation.
|
|
60
|
+
*
|
|
61
|
+
* @module
|
|
62
|
+
*/
|
|
63
|
+
/**
|
|
64
|
+
* Appends a path component to a path, handling trailing slashes correctly.
|
|
65
|
+
*
|
|
66
|
+
* Port of `appending_path_component()` from lifehash.cpp lines 18-24.
|
|
67
|
+
*
|
|
68
|
+
* @param path - The base path
|
|
69
|
+
* @param component - The component to append
|
|
70
|
+
* @returns The combined path
|
|
71
|
+
* @category Utilities
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* appendingPathComponent("", "file.png") // => "file.png"
|
|
76
|
+
* appendingPathComponent("/tmp/", "file.png") // => "/tmp/file.png"
|
|
77
|
+
* appendingPathComponent("/tmp", "file.png") // => "/tmp/file.png"
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
function appendingPathComponent(path, component) {
|
|
81
|
+
if (path === "") return component;
|
|
82
|
+
if (path.endsWith("/")) return `${path}${component}`;
|
|
83
|
+
return `${path}/${component}`;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Selects a random element from an array.
|
|
87
|
+
*
|
|
88
|
+
* Port of `random_element()` template from lifehash.cpp lines 38-50.
|
|
89
|
+
*
|
|
90
|
+
* @param array - The array to select from
|
|
91
|
+
* @returns A random element from the array
|
|
92
|
+
* @category Utilities
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* randomElement(["A", "B", "C"]) // => "A" or "B" or "C"
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
function randomElement(array) {
|
|
100
|
+
return array[Math.floor(Math.random() * array.length)];
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Generates a random input string in "XXX-XXX" format where X is a random uppercase letter.
|
|
104
|
+
*
|
|
105
|
+
* Port of `make_random_input()` from lifehash.cpp lines 52-57.
|
|
106
|
+
*
|
|
107
|
+
* @returns A random string in "XXX-XXX" format
|
|
108
|
+
* @category Utilities
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* makeRandomInput() // => "ABC-DEF" (random letters)
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
function makeRandomInput() {
|
|
116
|
+
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
|
|
117
|
+
const letter = () => randomElement(letters);
|
|
118
|
+
const cluster = () => `${letter()}${letter()}${letter()}`;
|
|
119
|
+
return `${cluster()}-${cluster()}`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/main.ts
|
|
124
|
+
/**
|
|
125
|
+
* LifeHash CLI - Command line tool for generating LifeHash PNG images.
|
|
126
|
+
*
|
|
127
|
+
* Ported from bc-lifehash-cli C++ implementation (lifehash.cpp).
|
|
128
|
+
*
|
|
129
|
+
* @module
|
|
130
|
+
*/
|
|
131
|
+
/**
|
|
132
|
+
* Parses a version string to the Version enum.
|
|
133
|
+
*
|
|
134
|
+
* Port of version parsing logic from lifehash.cpp lines 130-145.
|
|
135
|
+
*
|
|
136
|
+
* @param versionString - The version string from CLI
|
|
137
|
+
* @returns The corresponding Version enum value
|
|
138
|
+
* @throws Error if the version string is invalid
|
|
139
|
+
* @category CLI
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```typescript
|
|
143
|
+
* parseVersion("version2") // => Version.version2
|
|
144
|
+
* parseVersion("detailed") // => Version.detailed
|
|
145
|
+
* parseVersion("invalid") // throws Error
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
function parseVersion(versionString) {
|
|
149
|
+
switch (versionString) {
|
|
150
|
+
case "version1": return Version.version1;
|
|
151
|
+
case "version2": return Version.version2;
|
|
152
|
+
case "detailed": return Version.detailed;
|
|
153
|
+
case "fiducial": return Version.fiducial;
|
|
154
|
+
case "grayscaleFiducial": return Version.grayscale_fiducial;
|
|
155
|
+
default: throw new Error("Invalid version.");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Main execution function that generates a LifeHash image.
|
|
160
|
+
*
|
|
161
|
+
* Port of `run()` from lifehash.cpp lines 188-192.
|
|
162
|
+
*
|
|
163
|
+
* @param input - Input string to hash (or empty for random)
|
|
164
|
+
* @param options - CLI options
|
|
165
|
+
* @category CLI
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```typescript
|
|
169
|
+
* run("Hello", { version: "version2", module: "1", path: "." });
|
|
170
|
+
* // Generates Hello.png in current directory
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
function run(input, options) {
|
|
174
|
+
const version = parseVersion(options.version);
|
|
175
|
+
const moduleSize = parseInt(options.module, 10);
|
|
176
|
+
if (moduleSize < 1 || isNaN(moduleSize)) throw new Error("Illegal value.");
|
|
177
|
+
const actualInput = input !== "" ? input : makeRandomInput();
|
|
178
|
+
const outputFilename = `${actualInput}.png`;
|
|
179
|
+
const outputFile = appendingPathComponent(options.path, outputFilename);
|
|
180
|
+
writeImage(makeFromUtf8(actualInput, version, moduleSize), outputFile);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* CLI entry point.
|
|
184
|
+
*
|
|
185
|
+
* Port of `main()` from lifehash.cpp lines 194-206.
|
|
186
|
+
*
|
|
187
|
+
* @category CLI
|
|
188
|
+
*/
|
|
189
|
+
function main() {
|
|
190
|
+
const program = new Command();
|
|
191
|
+
program.name("lifehash").description("Generate LifeHash PNG images from input strings").argument("[input]", "Input string to hash (default: random XXX-XXX)").option("-v, --version <version>", "LifeHash version: version1, version2, detailed, fiducial, grayscaleFiducial", "version2").option("-m, --module <size>", "Size of each module (\"pixel\")", "1").option("-p, --path <path>", "Output directory path", "").action((input, options) => {
|
|
192
|
+
try {
|
|
193
|
+
run(input ?? "", options);
|
|
194
|
+
} catch (error) {
|
|
195
|
+
if (error instanceof Error) {
|
|
196
|
+
console.log(`\u{1F928} ${error.message}`);
|
|
197
|
+
console.log();
|
|
198
|
+
program.help();
|
|
199
|
+
}
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
program.parse();
|
|
204
|
+
}
|
|
205
|
+
if (typeof process !== "undefined" && typeof process.argv[1] === "string" && (process.argv[1].endsWith("/main.mjs") || process.argv[1].endsWith("/main.js") || process.argv[1].includes("lifehash-cli"))) main();
|
|
206
|
+
|
|
207
|
+
//#endregion
|
|
208
|
+
export { main, parseVersion, run };
|
|
209
|
+
//# sourceMappingURL=main.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.mjs","names":[],"sources":["../src/png-writer.ts","../src/utils.ts","../src/main.ts"],"sourcesContent":["/**\n * PNG writer for LifeHash images.\n *\n * Ported from bc-lifehash-cli C++ implementation (png-writer.hpp).\n * Uses pngjs instead of libpng.\n *\n * @module\n */\n\nimport { writeFileSync } from \"fs\";\nimport { PNG } from \"pngjs\";\nimport type { Image } from \"@bcts/lifehash\";\n\n/**\n * Writes a LifeHash image to a PNG file.\n *\n * Port of `write_image()` from lifehash.cpp lines 174-186 and\n * PNGWriter class from png-writer.hpp.\n *\n * @param image - The LifeHash image to write\n * @param filename - The output filename\n * @category PNG Encoding\n *\n * @example\n * ```typescript\n * import { makeFromUtf8 } from \"@bcts/lifehash\";\n *\n * const image = makeFromUtf8(\"Hello\");\n * writeImage(image, \"Hello.png\");\n * ```\n */\nexport function writeImage(image: Image, filename: string): void {\n const png = new PNG({\n width: image.width,\n height: image.height,\n colorType: 2, // RGB\n bitDepth: 8,\n inputColorType: 2,\n inputHasAlpha: false,\n });\n\n // Copy RGB data from image to PNG\n // PNG expects RGBA, so we need to add alpha channel\n for (let y = 0; y < image.height; y++) {\n for (let x = 0; x < image.width; x++) {\n const srcOffset = (y * image.width + x) * 3;\n const dstOffset = (y * image.width + x) * 4;\n\n // Copy RGB from source\n png.data[dstOffset] = image.colors[srcOffset]; // R\n png.data[dstOffset + 1] = image.colors[srcOffset + 1]; // G\n png.data[dstOffset + 2] = image.colors[srcOffset + 2]; // B\n png.data[dstOffset + 3] = 255; // A (fully opaque)\n }\n }\n\n // Write to file\n const buffer = PNG.sync.write(png);\n writeFileSync(filename, buffer);\n}\n\n/**\n * Generates a PNG buffer from a LifeHash image without writing to disk.\n *\n * @param image - The LifeHash image to encode\n * @returns A Buffer containing the PNG data\n * @category PNG Encoding\n *\n * @example\n * ```typescript\n * import { makeFromUtf8 } from \"@bcts/lifehash\";\n *\n * const image = makeFromUtf8(\"Hello\");\n * const pngBuffer = generatePNG(image);\n * ```\n */\nexport function generatePNG(image: Image): Buffer {\n const png = new PNG({\n width: image.width,\n height: image.height,\n colorType: 2,\n bitDepth: 8,\n inputColorType: 2,\n inputHasAlpha: false,\n });\n\n // Copy RGB data from image to PNG\n for (let y = 0; y < image.height; y++) {\n for (let x = 0; x < image.width; x++) {\n const srcOffset = (y * image.width + x) * 3;\n const dstOffset = (y * image.width + x) * 4;\n\n png.data[dstOffset] = image.colors[srcOffset];\n png.data[dstOffset + 1] = image.colors[srcOffset + 1];\n png.data[dstOffset + 2] = image.colors[srcOffset + 2];\n png.data[dstOffset + 3] = 255;\n }\n }\n\n return PNG.sync.write(png);\n}\n","/**\n * Utility functions for the LifeHash CLI.\n *\n * Ported from bc-lifehash-cli C++ implementation.\n *\n * @module\n */\n\n/**\n * Appends a path component to a path, handling trailing slashes correctly.\n *\n * Port of `appending_path_component()` from lifehash.cpp lines 18-24.\n *\n * @param path - The base path\n * @param component - The component to append\n * @returns The combined path\n * @category Utilities\n *\n * @example\n * ```typescript\n * appendingPathComponent(\"\", \"file.png\") // => \"file.png\"\n * appendingPathComponent(\"/tmp/\", \"file.png\") // => \"/tmp/file.png\"\n * appendingPathComponent(\"/tmp\", \"file.png\") // => \"/tmp/file.png\"\n * ```\n */\nexport function appendingPathComponent(path: string, component: string): string {\n if (path === \"\") {\n return component;\n }\n if (path.endsWith(\"/\")) {\n return `${path}${component}`;\n }\n return `${path}/${component}`;\n}\n\n/**\n * Selects a random element from an array.\n *\n * Port of `random_element()` template from lifehash.cpp lines 38-50.\n *\n * @param array - The array to select from\n * @returns A random element from the array\n * @category Utilities\n *\n * @example\n * ```typescript\n * randomElement([\"A\", \"B\", \"C\"]) // => \"A\" or \"B\" or \"C\"\n * ```\n */\nexport function randomElement<T>(array: T[]): T {\n const index = Math.floor(Math.random() * array.length);\n return array[index];\n}\n\n/**\n * Generates a random input string in \"XXX-XXX\" format where X is a random uppercase letter.\n *\n * Port of `make_random_input()` from lifehash.cpp lines 52-57.\n *\n * @returns A random string in \"XXX-XXX\" format\n * @category Utilities\n *\n * @example\n * ```typescript\n * makeRandomInput() // => \"ABC-DEF\" (random letters)\n * ```\n */\nexport function makeRandomInput(): string {\n const letters = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\".split(\"\");\n\n const letter = (): string => randomElement(letters);\n const cluster = (): string => `${letter()}${letter()}${letter()}`;\n\n return `${cluster()}-${cluster()}`;\n}\n","/**\n * LifeHash CLI - Command line tool for generating LifeHash PNG images.\n *\n * Ported from bc-lifehash-cli C++ implementation (lifehash.cpp).\n *\n * @module\n */\n\nimport { Command } from \"commander\";\nimport { makeFromUtf8, Version } from \"@bcts/lifehash\";\nimport { writeImage } from \"./png-writer\";\nimport { appendingPathComponent, makeRandomInput } from \"./utils\";\n\n/**\n * CLI options interface.\n *\n * Port of Parameters struct from lifehash.cpp lines 86-91.\n *\n * @category CLI\n */\nexport interface CliOptions {\n /** LifeHash version to generate */\n version: string;\n /** Size of each module (\"pixel\") */\n module: string;\n /** Output directory path */\n path: string;\n}\n\n/**\n * Parses a version string to the Version enum.\n *\n * Port of version parsing logic from lifehash.cpp lines 130-145.\n *\n * @param versionString - The version string from CLI\n * @returns The corresponding Version enum value\n * @throws Error if the version string is invalid\n * @category CLI\n *\n * @example\n * ```typescript\n * parseVersion(\"version2\") // => Version.version2\n * parseVersion(\"detailed\") // => Version.detailed\n * parseVersion(\"invalid\") // throws Error\n * ```\n */\nexport function parseVersion(versionString: string): Version {\n switch (versionString) {\n case \"version1\":\n return Version.version1;\n case \"version2\":\n return Version.version2;\n case \"detailed\":\n return Version.detailed;\n case \"fiducial\":\n return Version.fiducial;\n case \"grayscaleFiducial\":\n return Version.grayscale_fiducial;\n default:\n throw new Error(\"Invalid version.\");\n }\n}\n\n/**\n * Main execution function that generates a LifeHash image.\n *\n * Port of `run()` from lifehash.cpp lines 188-192.\n *\n * @param input - Input string to hash (or empty for random)\n * @param options - CLI options\n * @category CLI\n *\n * @example\n * ```typescript\n * run(\"Hello\", { version: \"version2\", module: \"1\", path: \".\" });\n * // Generates Hello.png in current directory\n * ```\n */\nexport function run(input: string, options: CliOptions): void {\n // Parse parameters (matching Parameters constructor from C++)\n const version = parseVersion(options.version);\n const moduleSize = parseInt(options.module, 10);\n\n if (moduleSize < 1 || isNaN(moduleSize)) {\n throw new Error(\"Illegal value.\");\n }\n\n // Use random input if none provided\n const actualInput = input !== \"\" ? input : makeRandomInput();\n\n // Generate output filename\n const outputFilename = `${actualInput}.png`;\n const outputFile = appendingPathComponent(options.path, outputFilename);\n\n // Generate LifeHash image\n const image = makeFromUtf8(actualInput, version, moduleSize);\n\n // Write to PNG file\n writeImage(image, outputFile);\n}\n\n/**\n * CLI entry point.\n *\n * Port of `main()` from lifehash.cpp lines 194-206.\n *\n * @category CLI\n */\nexport function main(): void {\n const program = new Command();\n\n program\n .name(\"lifehash\")\n .description(\"Generate LifeHash PNG images from input strings\")\n .argument(\"[input]\", \"Input string to hash (default: random XXX-XXX)\")\n .option(\n \"-v, --version <version>\",\n \"LifeHash version: version1, version2, detailed, fiducial, grayscaleFiducial\",\n \"version2\",\n )\n .option(\"-m, --module <size>\", 'Size of each module (\"pixel\")', \"1\")\n .option(\"-p, --path <path>\", \"Output directory path\", \"\")\n .action((input: string | undefined, options: CliOptions) => {\n try {\n run(input ?? \"\", options);\n } catch (error) {\n if (error instanceof Error) {\n console.log(`\\u{1F928} ${error.message}`);\n console.log();\n program.help();\n }\n process.exit(1);\n }\n });\n\n program.parse();\n}\n\n// Run CLI when executed directly (not when imported as a module)\n// Check if this file is the main entry point\nconst isMainModule =\n typeof process !== \"undefined\" &&\n typeof process.argv[1] === \"string\" &&\n (process.argv[1].endsWith(\"/main.mjs\") ||\n process.argv[1].endsWith(\"/main.js\") ||\n process.argv[1].includes(\"lifehash-cli\"));\n\nif (isMainModule) {\n main();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,WAAW,OAAc,UAAwB;CAC/D,MAAM,MAAM,IAAI,IAAI;EAClB,OAAO,MAAM;EACb,QAAQ,MAAM;EACd,WAAW;EACX,UAAU;EACV,gBAAgB;EAChB,eAAe;EAChB,CAAC;AAIF,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,IAChC,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,OAAO,KAAK;EACpC,MAAM,aAAa,IAAI,MAAM,QAAQ,KAAK;EAC1C,MAAM,aAAa,IAAI,MAAM,QAAQ,KAAK;AAG1C,MAAI,KAAK,aAAa,MAAM,OAAO;AACnC,MAAI,KAAK,YAAY,KAAK,MAAM,OAAO,YAAY;AACnD,MAAI,KAAK,YAAY,KAAK,MAAM,OAAO,YAAY;AACnD,MAAI,KAAK,YAAY,KAAK;;AAM9B,eAAc,UADC,IAAI,KAAK,MAAM,IAAI,CACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACjCjC,SAAgB,uBAAuB,MAAc,WAA2B;AAC9E,KAAI,SAAS,GACX,QAAO;AAET,KAAI,KAAK,SAAS,IAAI,CACpB,QAAO,GAAG,OAAO;AAEnB,QAAO,GAAG,KAAK,GAAG;;;;;;;;;;;;;;;;AAiBpB,SAAgB,cAAiB,OAAe;AAE9C,QAAO,MADO,KAAK,MAAM,KAAK,QAAQ,GAAG,MAAM,OAAO;;;;;;;;;;;;;;;AAiBxD,SAAgB,kBAA0B;CACxC,MAAM,UAAU,6BAA6B,MAAM,GAAG;CAEtD,MAAM,eAAuB,cAAc,QAAQ;CACnD,MAAM,gBAAwB,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ;AAE/D,QAAO,GAAG,SAAS,CAAC,GAAG,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC3BlC,SAAgB,aAAa,eAAgC;AAC3D,SAAQ,eAAR;EACE,KAAK,WACH,QAAO,QAAQ;EACjB,KAAK,WACH,QAAO,QAAQ;EACjB,KAAK,WACH,QAAO,QAAQ;EACjB,KAAK,WACH,QAAO,QAAQ;EACjB,KAAK,oBACH,QAAO,QAAQ;EACjB,QACE,OAAM,IAAI,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;AAmBzC,SAAgB,IAAI,OAAe,SAA2B;CAE5D,MAAM,UAAU,aAAa,QAAQ,QAAQ;CAC7C,MAAM,aAAa,SAAS,QAAQ,QAAQ,GAAG;AAE/C,KAAI,aAAa,KAAK,MAAM,WAAW,CACrC,OAAM,IAAI,MAAM,iBAAiB;CAInC,MAAM,cAAc,UAAU,KAAK,QAAQ,iBAAiB;CAG5D,MAAM,iBAAiB,GAAG,YAAY;CACtC,MAAM,aAAa,uBAAuB,QAAQ,MAAM,eAAe;AAMvE,YAHc,aAAa,aAAa,SAAS,WAAW,EAG1C,WAAW;;;;;;;;;AAU/B,SAAgB,OAAa;CAC3B,MAAM,UAAU,IAAI,SAAS;AAE7B,SACG,KAAK,WAAW,CAChB,YAAY,kDAAkD,CAC9D,SAAS,WAAW,iDAAiD,CACrE,OACC,2BACA,+EACA,WACD,CACA,OAAO,uBAAuB,mCAAiC,IAAI,CACnE,OAAO,qBAAqB,yBAAyB,GAAG,CACxD,QAAQ,OAA2B,YAAwB;AAC1D,MAAI;AACF,OAAI,SAAS,IAAI,QAAQ;WAClB,OAAO;AACd,OAAI,iBAAiB,OAAO;AAC1B,YAAQ,IAAI,aAAa,MAAM,UAAU;AACzC,YAAQ,KAAK;AACb,YAAQ,MAAM;;AAEhB,WAAQ,KAAK,EAAE;;GAEjB;AAEJ,SAAQ,OAAO;;AAYjB,IANE,OAAO,YAAY,eACnB,OAAO,QAAQ,KAAK,OAAO,aAC1B,QAAQ,KAAK,GAAG,SAAS,YAAY,IACpC,QAAQ,KAAK,GAAG,SAAS,WAAW,IACpC,QAAQ,KAAK,GAAG,SAAS,eAAe,EAG1C,OAAM"}
|
package/package.json
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bcts/lifehash-cli",
|
|
3
|
+
"version": "1.0.0-alpha.17",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A command-line tool for generating LifeHash visual hash images as PNG files.",
|
|
6
|
+
"license": "BSD-2-Clause-Patent",
|
|
7
|
+
"contributors": [
|
|
8
|
+
{
|
|
9
|
+
"name": "Leonardo Custodio",
|
|
10
|
+
"email": "leonardo.custodio@parity.io",
|
|
11
|
+
"url": "https://github.com/leonardocustodio"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "Karim Jedda",
|
|
15
|
+
"email": "karim@parity.io",
|
|
16
|
+
"url": "https://github.com/KarimJedda"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"homepage": "https://bcts.dev",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/leonardocustodio/bcts.git",
|
|
23
|
+
"directory": "tools/lifehash-cli"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/leonardocustodio/bcts/issues"
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"lifehash": "./dist/main.mjs"
|
|
30
|
+
},
|
|
31
|
+
"main": "dist/index.cjs",
|
|
32
|
+
"module": "dist/index.mjs",
|
|
33
|
+
"types": "dist/index.d.mts",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./dist/index.d.mts",
|
|
37
|
+
"import": "./dist/index.mjs",
|
|
38
|
+
"require": "./dist/index.cjs",
|
|
39
|
+
"default": "./dist/index.mjs"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist",
|
|
44
|
+
"src",
|
|
45
|
+
"README.md"
|
|
46
|
+
],
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsdown",
|
|
49
|
+
"dev": "tsdown --watch",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"test:watch": "vitest",
|
|
52
|
+
"lint": "eslint 'src/**/*.ts' 'tests/**/*.ts'",
|
|
53
|
+
"lint:fix": "eslint 'src/**/*.ts' 'tests/**/*.ts' --fix",
|
|
54
|
+
"typecheck": "tsc --noEmit",
|
|
55
|
+
"clean": "rm -rf dist",
|
|
56
|
+
"docs": "typedoc",
|
|
57
|
+
"prepublishOnly": "npm run clean && npm run build && npm test"
|
|
58
|
+
},
|
|
59
|
+
"keywords": [
|
|
60
|
+
"lifehash",
|
|
61
|
+
"visual-hash",
|
|
62
|
+
"hash-visualization",
|
|
63
|
+
"png",
|
|
64
|
+
"image",
|
|
65
|
+
"cli",
|
|
66
|
+
"blockchain-commons",
|
|
67
|
+
"game-of-life",
|
|
68
|
+
"cellular-automata",
|
|
69
|
+
"fiducial"
|
|
70
|
+
],
|
|
71
|
+
"engines": {
|
|
72
|
+
"node": ">=18.0.0"
|
|
73
|
+
},
|
|
74
|
+
"dependencies": {
|
|
75
|
+
"@bcts/lifehash": "workspace:*",
|
|
76
|
+
"commander": "^14.0.0",
|
|
77
|
+
"pngjs": "^7.0.0"
|
|
78
|
+
},
|
|
79
|
+
"devDependencies": {
|
|
80
|
+
"@bcts/eslint": "workspace:*",
|
|
81
|
+
"@bcts/tsconfig": "workspace:*",
|
|
82
|
+
"@eslint/js": "^9.39.2",
|
|
83
|
+
"@types/node": "^25.0.10",
|
|
84
|
+
"@types/pngjs": "^6.0.5",
|
|
85
|
+
"eslint": "^9.39.2",
|
|
86
|
+
"ts-node": "^10.9.2",
|
|
87
|
+
"tsdown": "^0.20.1",
|
|
88
|
+
"typedoc": "^0.28.16",
|
|
89
|
+
"typescript": "^5.9.3",
|
|
90
|
+
"vitest": "^4.0.18"
|
|
91
|
+
}
|
|
92
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @bcts/lifehash-cli - Command line tool for generating LifeHash PNG images.
|
|
3
|
+
*
|
|
4
|
+
* This package provides both a CLI tool and a programmatic API for generating
|
|
5
|
+
* LifeHash visual hash images as PNG files.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
* @module @bcts/lifehash-cli
|
|
9
|
+
*
|
|
10
|
+
* @example CLI Usage
|
|
11
|
+
* ```bash
|
|
12
|
+
* # Generate a LifeHash from a string
|
|
13
|
+
* lifehash Hello
|
|
14
|
+
*
|
|
15
|
+
* # Generate with specific version and module size
|
|
16
|
+
* lifehash -v detailed -m 8 Hello
|
|
17
|
+
*
|
|
18
|
+
* # Generate with random input
|
|
19
|
+
* lifehash
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @example Programmatic Usage
|
|
23
|
+
* ```typescript
|
|
24
|
+
* import { generateLifeHash } from "@bcts/lifehash-cli";
|
|
25
|
+
* import { writeFileSync } from "fs";
|
|
26
|
+
*
|
|
27
|
+
* // Generate a PNG buffer
|
|
28
|
+
* const pngBuffer = generateLifeHash("Hello", {
|
|
29
|
+
* version: "version2",
|
|
30
|
+
* moduleSize: 1,
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* // Write to file
|
|
34
|
+
* writeFileSync("Hello.png", pngBuffer);
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import { generatePNG, writeImage } from "./png-writer";
|
|
39
|
+
import { parseVersion, run, type CliOptions } from "./main";
|
|
40
|
+
import { makeFromUtf8, Version, type Image } from "@bcts/lifehash";
|
|
41
|
+
|
|
42
|
+
// PNG encoding exports
|
|
43
|
+
export { writeImage, generatePNG };
|
|
44
|
+
|
|
45
|
+
// Utility exports
|
|
46
|
+
export { appendingPathComponent, randomElement, makeRandomInput } from "./utils";
|
|
47
|
+
|
|
48
|
+
// CLI exports
|
|
49
|
+
export { parseVersion, run, type CliOptions };
|
|
50
|
+
|
|
51
|
+
// Re-export @bcts/lifehash types for convenience
|
|
52
|
+
export { Version, type Image };
|
|
53
|
+
|
|
54
|
+
// Re-export makeFromUtf8 for programmatic usage
|
|
55
|
+
export { makeFromUtf8 };
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Options for generating a LifeHash image.
|
|
59
|
+
*
|
|
60
|
+
* @category Image Generation
|
|
61
|
+
*/
|
|
62
|
+
export interface GenerateOptions {
|
|
63
|
+
/**
|
|
64
|
+
* LifeHash version to generate.
|
|
65
|
+
* @default "version2"
|
|
66
|
+
*/
|
|
67
|
+
version?: "version1" | "version2" | "detailed" | "fiducial" | "grayscaleFiducial";
|
|
68
|
+
/**
|
|
69
|
+
* Size of each module ("pixel").
|
|
70
|
+
* @default 1
|
|
71
|
+
*/
|
|
72
|
+
moduleSize?: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Generates a LifeHash PNG buffer from an input string.
|
|
77
|
+
*
|
|
78
|
+
* This is the main programmatic API for generating LifeHash images.
|
|
79
|
+
*
|
|
80
|
+
* @param input - The input string to hash
|
|
81
|
+
* @param options - Generation options
|
|
82
|
+
* @returns A Buffer containing the PNG data
|
|
83
|
+
* @category Image Generation
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* import { generateLifeHash } from "@bcts/lifehash-cli";
|
|
88
|
+
* import { writeFileSync } from "fs";
|
|
89
|
+
*
|
|
90
|
+
* const pngBuffer = generateLifeHash("Hello", {
|
|
91
|
+
* version: "version2",
|
|
92
|
+
* moduleSize: 1,
|
|
93
|
+
* });
|
|
94
|
+
*
|
|
95
|
+
* writeFileSync("Hello.png", pngBuffer);
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export function generateLifeHash(input: string, options: GenerateOptions = {}): Buffer {
|
|
99
|
+
const { version = "version2", moduleSize = 1 } = options;
|
|
100
|
+
|
|
101
|
+
const versionEnum = parseVersion(version);
|
|
102
|
+
const image = makeFromUtf8(input, versionEnum, moduleSize);
|
|
103
|
+
return generatePNG(image);
|
|
104
|
+
}
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LifeHash CLI - Command line tool for generating LifeHash PNG images.
|
|
3
|
+
*
|
|
4
|
+
* Ported from bc-lifehash-cli C++ implementation (lifehash.cpp).
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Command } from "commander";
|
|
10
|
+
import { makeFromUtf8, Version } from "@bcts/lifehash";
|
|
11
|
+
import { writeImage } from "./png-writer";
|
|
12
|
+
import { appendingPathComponent, makeRandomInput } from "./utils";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* CLI options interface.
|
|
16
|
+
*
|
|
17
|
+
* Port of Parameters struct from lifehash.cpp lines 86-91.
|
|
18
|
+
*
|
|
19
|
+
* @category CLI
|
|
20
|
+
*/
|
|
21
|
+
export interface CliOptions {
|
|
22
|
+
/** LifeHash version to generate */
|
|
23
|
+
version: string;
|
|
24
|
+
/** Size of each module ("pixel") */
|
|
25
|
+
module: string;
|
|
26
|
+
/** Output directory path */
|
|
27
|
+
path: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Parses a version string to the Version enum.
|
|
32
|
+
*
|
|
33
|
+
* Port of version parsing logic from lifehash.cpp lines 130-145.
|
|
34
|
+
*
|
|
35
|
+
* @param versionString - The version string from CLI
|
|
36
|
+
* @returns The corresponding Version enum value
|
|
37
|
+
* @throws Error if the version string is invalid
|
|
38
|
+
* @category CLI
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* parseVersion("version2") // => Version.version2
|
|
43
|
+
* parseVersion("detailed") // => Version.detailed
|
|
44
|
+
* parseVersion("invalid") // throws Error
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function parseVersion(versionString: string): Version {
|
|
48
|
+
switch (versionString) {
|
|
49
|
+
case "version1":
|
|
50
|
+
return Version.version1;
|
|
51
|
+
case "version2":
|
|
52
|
+
return Version.version2;
|
|
53
|
+
case "detailed":
|
|
54
|
+
return Version.detailed;
|
|
55
|
+
case "fiducial":
|
|
56
|
+
return Version.fiducial;
|
|
57
|
+
case "grayscaleFiducial":
|
|
58
|
+
return Version.grayscale_fiducial;
|
|
59
|
+
default:
|
|
60
|
+
throw new Error("Invalid version.");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Main execution function that generates a LifeHash image.
|
|
66
|
+
*
|
|
67
|
+
* Port of `run()` from lifehash.cpp lines 188-192.
|
|
68
|
+
*
|
|
69
|
+
* @param input - Input string to hash (or empty for random)
|
|
70
|
+
* @param options - CLI options
|
|
71
|
+
* @category CLI
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* run("Hello", { version: "version2", module: "1", path: "." });
|
|
76
|
+
* // Generates Hello.png in current directory
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export function run(input: string, options: CliOptions): void {
|
|
80
|
+
// Parse parameters (matching Parameters constructor from C++)
|
|
81
|
+
const version = parseVersion(options.version);
|
|
82
|
+
const moduleSize = parseInt(options.module, 10);
|
|
83
|
+
|
|
84
|
+
if (moduleSize < 1 || isNaN(moduleSize)) {
|
|
85
|
+
throw new Error("Illegal value.");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Use random input if none provided
|
|
89
|
+
const actualInput = input !== "" ? input : makeRandomInput();
|
|
90
|
+
|
|
91
|
+
// Generate output filename
|
|
92
|
+
const outputFilename = `${actualInput}.png`;
|
|
93
|
+
const outputFile = appendingPathComponent(options.path, outputFilename);
|
|
94
|
+
|
|
95
|
+
// Generate LifeHash image
|
|
96
|
+
const image = makeFromUtf8(actualInput, version, moduleSize);
|
|
97
|
+
|
|
98
|
+
// Write to PNG file
|
|
99
|
+
writeImage(image, outputFile);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* CLI entry point.
|
|
104
|
+
*
|
|
105
|
+
* Port of `main()` from lifehash.cpp lines 194-206.
|
|
106
|
+
*
|
|
107
|
+
* @category CLI
|
|
108
|
+
*/
|
|
109
|
+
export function main(): void {
|
|
110
|
+
const program = new Command();
|
|
111
|
+
|
|
112
|
+
program
|
|
113
|
+
.name("lifehash")
|
|
114
|
+
.description("Generate LifeHash PNG images from input strings")
|
|
115
|
+
.argument("[input]", "Input string to hash (default: random XXX-XXX)")
|
|
116
|
+
.option(
|
|
117
|
+
"-v, --version <version>",
|
|
118
|
+
"LifeHash version: version1, version2, detailed, fiducial, grayscaleFiducial",
|
|
119
|
+
"version2",
|
|
120
|
+
)
|
|
121
|
+
.option("-m, --module <size>", 'Size of each module ("pixel")', "1")
|
|
122
|
+
.option("-p, --path <path>", "Output directory path", "")
|
|
123
|
+
.action((input: string | undefined, options: CliOptions) => {
|
|
124
|
+
try {
|
|
125
|
+
run(input ?? "", options);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (error instanceof Error) {
|
|
128
|
+
console.log(`\u{1F928} ${error.message}`);
|
|
129
|
+
console.log();
|
|
130
|
+
program.help();
|
|
131
|
+
}
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
program.parse();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Run CLI when executed directly (not when imported as a module)
|
|
140
|
+
// Check if this file is the main entry point
|
|
141
|
+
const isMainModule =
|
|
142
|
+
typeof process !== "undefined" &&
|
|
143
|
+
typeof process.argv[1] === "string" &&
|
|
144
|
+
(process.argv[1].endsWith("/main.mjs") ||
|
|
145
|
+
process.argv[1].endsWith("/main.js") ||
|
|
146
|
+
process.argv[1].includes("lifehash-cli"));
|
|
147
|
+
|
|
148
|
+
if (isMainModule) {
|
|
149
|
+
main();
|
|
150
|
+
}
|