/ namegen.py
namegen.py
  1  #!/usr/bin/python3
  2  """Generate names (or other strings) based on specialized templates"""
  3  
  4  # SPDX-FileCopyrightText: 2021 Jeff Epler
  5  #
  6  # SPDX-License-Identifier: GPL-3.0-only
  7  
  8  import sys
  9  import random
 10  
 11  symbols = {
 12      "'": ["'"],
 13      "-": ["-"],
 14      " ": [" "],
 15      "s": [
 16          "ach",
 17          "ack",
 18          "ad",
 19          "age",
 20          "ald",
 21          "ale",
 22          "an",
 23          "ang",
 24          "ar",
 25          "ard",
 26          "as",
 27          "ash",
 28          "at",
 29          "ath",
 30          "augh",
 31          "aw",
 32          "ban",
 33          "bel",
 34          "bur",
 35          "cer",
 36          "cha",
 37          "che",
 38          "dan",
 39          "dar",
 40          "del",
 41          "den",
 42          "dra",
 43          "dyn",
 44          "ech",
 45          "eld",
 46          "elm",
 47          "em",
 48          "en",
 49          "end",
 50          "eng",
 51          "enth",
 52          "er",
 53          "ess",
 54          "est",
 55          "et",
 56          "gar",
 57          "gha",
 58          "hat",
 59          "hin",
 60          "hon",
 61          "ia",
 62          "ight",
 63          "ild",
 64          "im",
 65          "ina",
 66          "ine",
 67          "ing",
 68          "ir",
 69          "is",
 70          "iss",
 71          "it",
 72          "kal",
 73          "kel",
 74          "kim",
 75          "kin",
 76          "ler",
 77          "lor",
 78          "lye",
 79          "mor",
 80          "mos",
 81          "nal",
 82          "ny",
 83          "nys",
 84          "old",
 85          "om",
 86          "on",
 87          "or",
 88          "orm",
 89          "os",
 90          "ough",
 91          "per",
 92          "pol",
 93          "qua",
 94          "que",
 95          "rad",
 96          "rak",
 97          "ran",
 98          "ray",
 99          "ril",
100          "ris",
101          "rod",
102          "roth",
103          "ryn",
104          "sam",
105          "say",
106          "ser",
107          "shy",
108          "skel",
109          "sul",
110          "tai",
111          "tan",
112          "tas",
113          "ther",
114          "tia",
115          "tin",
116          "ton",
117          "tor",
118          "tur",
119          "um",
120          "und",
121          "unt",
122          "urn",
123          "usk",
124          "ust",
125          "ver",
126          "ves",
127          "vor",
128          "war",
129          "wor",
130          "yer",
131      ],
132      "v": ["a", "e", "i", "o", "u", "y"],
133      "V": [
134          "a",
135          "e",
136          "i",
137          "o",
138          "u",
139          "y",
140          "ae",
141          "ai",
142          "au",
143          "ay",
144          "ea",
145          "ee",
146          "ei",
147          "eu",
148          "ey",
149          "ia",
150          "ie",
151          "oe",
152          "oi",
153          "oo",
154          "ou",
155          "ui",
156      ],
157      "c": [
158          "b",
159          "c",
160          "d",
161          "f",
162          "g",
163          "h",
164          "j",
165          "k",
166          "l",
167          "m",
168          "n",
169          "p",
170          "q",
171          "r",
172          "s",
173          "t",
174          "v",
175          "w",
176          "x",
177          "y",
178          "z",
179      ],
180      "B": [
181          "b",
182          "bl",
183          "br",
184          "c",
185          "ch",
186          "chr",
187          "cl",
188          "cr",
189          "d",
190          "dr",
191          "f",
192          "g",
193          "h",
194          "j",
195          "k",
196          "l",
197          "ll",
198          "m",
199          "n",
200          "p",
201          "ph",
202          "qu",
203          "r",
204          "rh",
205          "s",
206          "sch",
207          "sh",
208          "sl",
209          "sm",
210          "sn",
211          "st",
212          "str",
213          "sw",
214          "t",
215          "th",
216          "thr",
217          "tr",
218          "v",
219          "w",
220          "wh",
221          "y",
222          "z",
223          "zh",
224      ],
225      "C": [
226          "b",
227          "c",
228          "ch",
229          "ck",
230          "d",
231          "f",
232          "g",
233          "gh",
234          "h",
235          "k",
236          "l",
237          "ld",
238          "ll",
239          "lt",
240          "m",
241          "n",
242          "nd",
243          "nn",
244          "nt",
245          "p",
246          "ph",
247          "q",
248          "r",
249          "rd",
250          "rr",
251          "rt",
252          "s",
253          "sh",
254          "ss",
255          "st",
256          "t",
257          "th",
258          "v",
259          "w",
260          "y",
261          "z",
262      ],
263      "i": [
264          "air",
265          "ankle",
266          "ball",
267          "beef",
268          "bone",
269          "bum",
270          "bumble",
271          "bump",
272          "cheese",
273          "clod",
274          "clot",
275          "clown",
276          "corn",
277          "dip",
278          "dolt",
279          "doof",
280          "dork",
281          "dumb",
282          "face",
283          "finger",
284          "foot",
285          "fumble",
286          "goof",
287          "grumble",
288          "head",
289          "knock",
290          "knocker",
291          "knuckle",
292          "loaf",
293          "lump",
294          "lunk",
295          "meat",
296          "muck",
297          "munch",
298          "nit",
299          "numb",
300          "pin",
301          "puff",
302          "skull",
303          "snark",
304          "sneeze",
305          "thimble",
306          "twerp",
307          "twit",
308          "wad",
309          "wimp",
310          "wipe",
311      ],
312      "m": [
313          "baby",
314          "booble",
315          "bunker",
316          "cuddle",
317          "cuddly",
318          "cutie",
319          "doodle",
320          "foofie",
321          "gooble",
322          "honey",
323          "kissie",
324          "lover",
325          "lovey",
326          "moofie",
327          "mooglie",
328          "moopie",
329          "moopsie",
330          "nookum",
331          "poochie",
332          "poof",
333          "poofie",
334          "pookie",
335          "schmoopie",
336          "schnoogle",
337          "schnookie",
338          "schnookum",
339          "smooch",
340          "smoochie",
341          "smoosh",
342          "snoogle",
343          "snoogy",
344          "snookie",
345          "snookum",
346          "snuggy",
347          "sweetie",
348          "woogle",
349          "woogy",
350          "wookie",
351          "wookum",
352          "wuddle",
353          "wuddly",
354          "wuggy",
355          "wunny",
356      ],
357      "M": [
358          "boo",
359          "bunch",
360          "bunny",
361          "cake",
362          "cakes",
363          "cute",
364          "darling",
365          "dumpling",
366          "dumplings",
367          "face",
368          "foof",
369          "goo",
370          "head",
371          "kin",
372          "kins",
373          "lips",
374          "love",
375          "mush",
376          "pie",
377          "poo",
378          "pooh",
379          "pook",
380          "pums",
381      ],
382      "D": [
383          "b",
384          "bl",
385          "br",
386          "cl",
387          "d",
388          "f",
389          "fl",
390          "fr",
391          "g",
392          "gh",
393          "gl",
394          "gr",
395          "h",
396          "j",
397          "k",
398          "kl",
399          "m",
400          "n",
401          "p",
402          "th",
403          "w",
404      ],
405      "d": [
406          "elch",
407          "idiot",
408          "ob",
409          "og",
410          "ok",
411          "olph",
412          "olt",
413          "omph",
414          "ong",
415          "onk",
416          "oo",
417          "oob",
418          "oof",
419          "oog",
420          "ook",
421          "ooz",
422          "org",
423          "ork",
424          "orm",
425          "oron",
426          "ub",
427          "uck",
428          "ug",
429          "ulf",
430          "ult",
431          "um",
432          "umb",
433          "ump",
434          "umph",
435          "un",
436          "unb",
437          "ung",
438          "unk",
439          "unph",
440          "unt",
441          "uzz",
442      ],
443      "z": ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
444  }
445  
446  
447  def namegen(
448      pattern, rng=random.randrange
449  ):  # pylint: disable=too-many-branches, too-many-statements
450      """Generate a name from the given pattern, with the given random number source"""
451      depth = silent = literal = capstack = capitalize = 0
452      reset = [0]
453      n = [1]
454      result = []
455  
456      for c in pattern:
457          if c == "<":
458              depth += 1
459              bit = 1 << depth
460              n.append(1)
461              reset.append(len(result))
462              literal &= ~bit
463              silent &= ~bit
464              silent |= (silent << 1) & bit
465              capstack &= ~bit
466              capstack |= capitalize << depth
467  
468          elif c == "(":
469              depth += 1
470              bit = 1 << depth
471              n.append(1)
472              reset.append(len(result))
473              literal |= bit
474              silent &= ~bit
475              silent |= (silent << 1) & bit
476              capstack &= ~bit
477              capstack |= capitalize << depth
478  
479          elif c == ">":
480              n.pop()
481              reset.pop()
482              if depth == 0:
483                  raise ValueError("Invalid pattern")
484              bit = 1 << depth
485              if literal & bit:
486                  raise ValueError("Invalid pattern")
487              depth -= 1
488  
489          elif c == ")":
490              n.pop()
491              reset.pop()
492              if depth == 0:
493                  raise ValueError("Invalid pattern")
494              bit = 1 << depth
495              if not (literal & bit):
496                  raise ValueError("Invalid pattern")
497              depth -= 1
498  
499          elif c == "|":
500              bit = 1 << depth
501              if not (silent & (bit >> 1)):
502                  nd = n[depth] = n[depth] + 1
503                  if rng(nd) == 0:
504                      del result[reset[depth] :]
505                      silent &= ~bit
506                      capitalize = bool(capstack & bit)
507                  else:
508                      silent |= bit
509  
510          elif c == "!":
511              capitalize = True
512  
513          else:
514              bit = 1 << depth
515              if not (silent & bit):
516                  if not (literal & bit):
517                      s = symbols.get(c)
518                      if s is None:
519                          raise ValueError(f"Invalid metacharacter {c}")
520                      c = s[rng(len(s))]
521                  if capitalize:
522                      c = c[0].capitalize() + c[1:]
523                      capitalize = 0
524                  result.append(c)
525  
526      if depth != 0:
527          raise ValueError("Invalid pattern")
528      return "".join(result)
529  
530  
531  if __name__ == "__main__":
532      for template in sys.argv[1:]:
533          print(end=f"{template!r}:")
534          for _ in range(8):
535              print(end=f" {namegen(template)!r}")
536          print()