@twardoch/namzy 1.0.15 → 1.0.21

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/dist/cli.js CHANGED
@@ -2,22 +2,20 @@
2
2
  // this_file: src/cli.ts
3
3
  import { generate } from "./index.js";
4
4
  const HELP = `
5
- namzy — generate fun fused project names
5
+ namzy — generate compact, memorable, unique names
6
6
 
7
7
  Usage:
8
8
  namzy [options]
9
9
 
10
10
  Options:
11
11
  --count <N> Number of names to generate (default: 1)
12
+ --seed <N> Integer seed (default: current timestamp)
12
13
  --help Show this help
13
-
14
- Examples:
15
- namzy
16
- namzy --count 5
17
14
  `.trimStart();
18
15
  function parseArgs(argv) {
19
16
  const args = argv.slice(2);
20
17
  let count = 1;
18
+ let seed;
21
19
  for (let i = 0; i < args.length; i++) {
22
20
  const arg = args[i];
23
21
  switch (arg) {
@@ -29,28 +27,33 @@ function parseArgs(argv) {
29
27
  case "--count": {
30
28
  const n = parseInt(args[++i], 10);
31
29
  if (Number.isNaN(n) || n < 1) {
32
- process.stderr.write(`--count must be a positive integer\n`);
30
+ process.stderr.write("--count must be a positive integer\n");
33
31
  process.exit(1);
34
32
  }
35
33
  count = n;
36
34
  break;
37
35
  }
36
+ case "--seed": {
37
+ const n = parseInt(args[++i], 10);
38
+ if (Number.isNaN(n)) {
39
+ process.stderr.write("--seed must be an integer\n");
40
+ process.exit(1);
41
+ }
42
+ seed = n;
43
+ break;
44
+ }
38
45
  default:
39
46
  process.stderr.write(`Unknown flag: ${arg}\nRun namzy --help for usage.\n`);
40
47
  process.exit(1);
41
48
  }
42
49
  }
43
- return { count };
50
+ return { count, seed };
44
51
  }
45
- async function main() {
46
- const { count } = parseArgs(process.argv);
47
- const base = Date.now();
52
+ function main() {
53
+ const { count, seed } = parseArgs(process.argv);
54
+ const base = seed ?? Date.now();
48
55
  for (let i = 0; i < count; i++) {
49
- const name = await generate({ seed: base + i * 1337 });
50
- process.stdout.write(`${name}\n`);
56
+ process.stdout.write(`${generate({ seed: base + i * 2654435761 })}\n`);
51
57
  }
52
58
  }
53
- main().catch((err) => {
54
- process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
55
- process.exit(1);
56
- });
59
+ main();
package/dist/index.d.ts CHANGED
@@ -1,11 +1,10 @@
1
- import { activeRotationMask, joinClean, mangle, mulberry32 } from "./mangle.js";
1
+ import { applyRotations, buildName, mulberry32 } from "./mangle.js";
2
2
  export interface NamzyOptions {
3
3
  seed?: number;
4
4
  }
5
- /**
6
- * Generate a single fused, mangled namzy name.
7
- * Two raw words junction-cleaned fusion consonant rotation → capitalize.
8
- */
9
- export declare function generate(opts?: NamzyOptions): Promise<string>;
10
- export { COMMON, GEO } from "./wordlist.js";
11
- export { activeRotationMask, joinClean, mangle, mulberry32 };
5
+ /** Generate one namzy name. Default seed is the current timestamp. */
6
+ export declare function generate(opts?: NamzyOptions): string;
7
+ /** Generate `count` names. Distinct seeds derived from the base seed. */
8
+ export declare function generateMany(count: number, opts?: NamzyOptions): string[];
9
+ export { STEMS, ROTATIONS } from "./wordlist.js";
10
+ export { applyRotations, buildName, mulberry32 };
package/dist/index.js CHANGED
@@ -1,24 +1,18 @@
1
1
  // this_file: src/index.ts
2
- import { activeRotationMask, joinClean, mangle, mulberry32 } from "./mangle.js";
3
- import { COMMON, GEO } from "./wordlist.js";
4
- function capitalize(s) {
5
- return s.charAt(0).toUpperCase() + s.slice(1);
6
- }
7
- function pick(arr, rng) {
8
- return arr[Math.floor(rng() * arr.length)];
9
- }
10
- /**
11
- * Generate a single fused, mangled namzy name.
12
- * Two raw words → junction-cleaned fusion → consonant rotation → capitalize.
13
- */
14
- export async function generate(opts) {
2
+ import { applyRotations, buildName, mulberry32 } from "./mangle.js";
3
+ /** Generate one namzy name. Default seed is the current timestamp. */
4
+ export function generate(opts) {
15
5
  const seed = opts?.seed ?? Date.now();
16
- const rng = mulberry32(seed);
17
- const w1 = pick(GEO, rng).toLowerCase();
18
- const w2 = pick(COMMON, rng).toLowerCase();
19
- const [first, second] = rng() < 0.5 ? [w1, w2] : [w2, w1];
20
- const fused = joinClean(first, second);
21
- return capitalize(mangle(fused, activeRotationMask(rng)));
6
+ return buildName(mulberry32(seed));
7
+ }
8
+ /** Generate `count` names. Distinct seeds derived from the base seed. */
9
+ export function generateMany(count, opts) {
10
+ const base = opts?.seed ?? Date.now();
11
+ const out = [];
12
+ for (let i = 0; i < count; i++) {
13
+ out.push(generate({ seed: base + i * 2654435761 }));
14
+ }
15
+ return out;
22
16
  }
23
- export { COMMON, GEO } from "./wordlist.js";
24
- export { activeRotationMask, joinClean, mangle, mulberry32 };
17
+ export { STEMS, ROTATIONS } from "./wordlist.js";
18
+ export { applyRotations, buildName, mulberry32 };
package/dist/mangle.d.ts CHANGED
@@ -1,15 +1,6 @@
1
- /** Simple seeded mulberry32 RNG — returns a function that yields [0,1) floats */
1
+ /** Seeded mulberry32 RNG — yields [0,1) floats. */
2
2
  export declare function mulberry32(seed: number): () => number;
3
- type RotationRule = readonly [from: string, to: string];
4
- export declare const ROTATION_RULES: readonly RotationRule[];
5
- /** Choose 1..10 active consonant-rotation rules from the seeded RNG. */
6
- export declare function activeRotationMask(rng: () => number): number;
7
- /** Consonant rotation. Case-preserving. Defaults to all rules for direct helper use. */
8
- export declare function mangle(s: string, activeMask?: number): string;
9
- /**
10
- * Fuse two lowercase words at a clean junction.
11
- * Drops the first char of `b` while it duplicates the tail of `a`,
12
- * or while the seam is vowel-vowel. Max two trims.
13
- */
14
- export declare function joinClean(a: string, b: string): string;
15
- export {};
3
+ /** Apply 1 or 2 rotations on randomly selected positions of the compound. */
4
+ export declare function applyRotations(compound: string, rng: () => number): string;
5
+ /** Build one name: any two stems, concatenated, lightly rotated, capitalized. */
6
+ export declare function buildName(rng: () => number): string;
package/dist/mangle.js CHANGED
@@ -1,85 +1,45 @@
1
1
  // this_file: src/mangle.ts
2
- /** Simple seeded mulberry32 RNG returns a function that yields [0,1) floats */
2
+ import { ROTATIONS, STEMS } from "./wordlist.js";
3
+ /** Seeded mulberry32 RNG — yields [0,1) floats. */
3
4
  export function mulberry32(seed) {
4
5
  let s = seed >>> 0;
5
6
  return () => {
6
- s += 0x6d2b79f5;
7
+ s = (s + 0x6d2b79f5) >>> 0;
7
8
  let t = Math.imul(s ^ (s >>> 15), 1 | s);
8
9
  t ^= t + Math.imul(t ^ (t >>> 7), 61 | t);
9
10
  return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
10
11
  };
11
12
  }
12
- export const ROTATION_RULES = [
13
- ["c", "q"],
14
- ["f", "v"],
15
- ["k", "c"],
16
- ["q", "k"],
17
- ["s", "z"],
18
- ["z", "s"],
19
- ["v", "f"],
20
- ["w", "u"],
21
- ["b", "p"],
22
- ["p", "b"],
23
- ];
24
- const ALL_ROTATIONS = (1 << ROTATION_RULES.length) - 1;
25
- function rotationFor(lower, activeMask) {
26
- for (let i = 0; i < ROTATION_RULES.length; i++) {
27
- if ((activeMask & (1 << i)) === 0) {
28
- continue;
29
- }
30
- const [from, to] = ROTATION_RULES[i];
31
- if (lower === from) {
32
- return to;
33
- }
34
- }
35
- return undefined;
13
+ function pick(arr, rng) {
14
+ return arr[Math.floor(rng() * arr.length)];
36
15
  }
37
- /** Choose 1..10 active consonant-rotation rules from the seeded RNG. */
38
- export function activeRotationMask(rng) {
39
- const order = ROTATION_RULES.map((_, i) => i);
40
- const activeCount = 1 + Math.floor(rng() * ROTATION_RULES.length);
41
- for (let i = 0; i < activeCount; i++) {
42
- const swap = i + Math.floor(rng() * (order.length - i));
43
- [order[i], order[swap]] = [order[swap], order[i]];
44
- }
45
- return order.slice(0, activeCount).reduce((mask, i) => mask | (1 << i), 0);
16
+ const ROT_MAP = new Map(ROTATIONS);
17
+ function rotateAt(s, pos) {
18
+ const ch = s[pos];
19
+ const lower = ch.toLowerCase();
20
+ const repl = ROT_MAP.get(lower);
21
+ if (repl === undefined)
22
+ return s;
23
+ const out = ch === lower ? repl : repl.toUpperCase();
24
+ return s.slice(0, pos) + out + s.slice(pos + 1);
46
25
  }
47
- /** Consonant rotation. Case-preserving. Defaults to all rules for direct helper use. */
48
- export function mangle(s, activeMask = ALL_ROTATIONS) {
49
- let out = "";
50
- for (const ch of s) {
51
- const lower = ch.toLowerCase();
52
- const repl = rotationFor(lower, activeMask);
53
- if (repl === undefined) {
54
- out += ch;
55
- }
56
- else {
57
- out += ch === lower ? repl : repl.toUpperCase();
58
- }
26
+ /** Apply 1 or 2 rotations on randomly selected positions of the compound. */
27
+ export function applyRotations(compound, rng) {
28
+ if (compound.length === 0)
29
+ return compound;
30
+ const passes = rng() < 0.5 ? 1 : 2;
31
+ let out = compound;
32
+ for (let i = 0; i < passes; i++) {
33
+ out = rotateAt(out, Math.floor(rng() * out.length));
59
34
  }
60
35
  return out;
61
36
  }
62
- const VOWELS = new Set(["a", "e", "i", "o", "u", "y"]);
63
- /**
64
- * Fuse two lowercase words at a clean junction.
65
- * Drops the first char of `b` while it duplicates the tail of `a`,
66
- * or while the seam is vowel-vowel. Max two trims.
67
- */
68
- export function joinClean(a, b) {
69
- const head = a;
70
- let tail = b;
71
- for (let i = 0; i < 2 && head.length > 0 && tail.length > 0; i++) {
72
- const last = head[head.length - 1].toLowerCase();
73
- const first = tail[0].toLowerCase();
74
- if (last === first) {
75
- tail = tail.slice(1);
76
- }
77
- else if (VOWELS.has(last) && VOWELS.has(first)) {
78
- tail = tail.slice(1);
79
- }
80
- else {
81
- break;
82
- }
83
- }
84
- return head + tail;
37
+ function capitalize(s) {
38
+ return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
39
+ }
40
+ /** Build one name: any two stems, concatenated, lightly rotated, capitalized. */
41
+ export function buildName(rng) {
42
+ const a = pick(STEMS, rng);
43
+ const b = pick(STEMS, rng);
44
+ return capitalize(applyRotations(a + b, rng));
85
45
  }
package/dist/web.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { generate, mangle, joinClean, mulberry32, GEO, COMMON } from "./index.js";
1
+ export { generate, generateMany, applyRotations, buildName, mulberry32, STEMS, ROTATIONS } from "./index.js";
2
2
  export type { NamzyOptions } from "./index.js";
package/dist/web.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // this_file: src/web.ts
2
2
  // Browser entry point — exposes namzy.generate() on window.namzy.
3
- export { generate, mangle, joinClean, mulberry32, GEO, COMMON } from "./index.js";
3
+ export { generate, generateMany, applyRotations, buildName, mulberry32, STEMS, ROTATIONS } from "./index.js";
@@ -1,2 +1,2 @@
1
- export declare const GEO: string[];
2
- export declare const COMMON: string[];
1
+ export declare const STEMS: readonly string[];
2
+ export declare const ROTATIONS: readonly (readonly [string, string])[];