program works now, added a little documentation

This commit is contained in:
2024-03-19 18:12:02 +01:00
parent 50a5221f93
commit 864261dd48
9 changed files with 283 additions and 58 deletions

108
README.md
View File

@@ -1,2 +1,108 @@
# gfn
Generate fantasy names for games and stories
Generate fantasy names for games and stories. It uses the fine
[fantasyname module](https://github.com/s0rg/fantasyname) by
[s0rg](https://github.com/s0rg/), which implements the code created by
the [rinkworks fantasy name
generator](http://rinkworks.com/namegen/). The code itself is
[outlined here](http://rinkworks.com/namegen/instr.shtml), or take a
quick look at the [reference
guide](http://rinkworks.com/namegen/reference.shtml).
In case the site vanishes some day, a copy of those documents is
contained here in the repository.
# Install
Execute
```shell
% go build
% cp gfn $HOME/bin
```
# Usage
There are a bunch of pre compiled fantasy name codes builtin, you can
get a list with:
```shell
% gfn -l
```
To use one of them and limit the number of
words generated:
```shell
% gfn JapaneseNamesDiverse -c 24
yumufuchi afuchin keyu amorekin ekimuwo ashihewani rosa chireki
oterun ruwahi uwamine emiyumu temimon yuwa awayason fuki
emiwa nushiron achihora yomichi saniyutan kewaritsu saroru uhashi
```
You can also write a code yourself:
```shell
gfn '!sVm' -c 24
Quaoobunker Emeemoopsie Angeepookie Osousmoosh Umuisweetie Ustoesnookum Sulealover Imopookie
Skelaesnoogle Echiapookie Cereepoochie Gariwuddle Echaewookie Tiaieschmoopie Queaubooble Athesmoosh
Undousnuggy Urnuigooble Mosoesnuggy Eldoegooble Denoipoochie Mosoosmooch Shyucuddly Tiaeylovey
```
A short outline of the code will be printed if you add the `-h`
parameter:
```shell
This is gfn, a fantasy name generator cli.
Usage: gfn [-vlc] <name|code>
Options:
-c --count How many names to generate
-l --list List pre-compiled shortcuts
-v --version Show program version
pattern syntax The letters s, v, V, c, B, C, i, m, M, D, and d
represent different types of random replacements:
s - generic syllable
v - vowel
V - vowel or vowel combination
c - consonant
B - consonant or consonant combination suitable for beginning a word
C - consonant or consonant combination suitable anywhere in a word
i - insult
m - mushy name
M - mushy name ending
D - consonant suited for a stupid person's name
d - syllable suited for a stupid person's name (begins with a vowel)
Everything else is emitted literally.
All characters between parenthesis () are emitted literally. For
example, the pattern s(dim), emits a random generic syllable followed
by dim.
Characters between angle brackets <> emit patterns from the table
above. Imagine the entire pattern is wrapped in one of these.
In both types of groupings, a vertical bar | denotes a random
choice. Empty groups are allowed. For example, (foo|bar) emits either
foo or bar. The pattern <c|v|> emits a constant, vowel, or nothing at
all.
An exclamation point ! means to capitalize the component that follows
it. For example, !(foo) will emit Foo and v!s will emit a lowercase
vowel followed by a capitalized syllable, like eRod.
```
# Report bugs
[Please open an issue](https://github.com/TLINDEN/gfn/issues). Thanks!
# License
This work is licensed under the terms of the General Public Licens
version 3.
# Author
Copyleft (c) 2024 Thomas von Dein

View File

@@ -79,33 +79,35 @@ type Template struct {
Tmpl string
}
var Templates = []Template{
{Name: "MiddleEarth", Tmpl: MIDDLE_EARTH},
{Name: "JapaneseNamesConstrained", Tmpl: JAPANESE_NAMES_CONSTRAINED},
{Name: "JapaneseNamesDiverse", Tmpl: JAPANESE_NAMES_DIVERSE},
{Name: "ChineseNames", Tmpl: CHINESE_NAMES},
{Name: "GreekNames", Tmpl: GREEK_NAMES},
{Name: "HawaiianNames1", Tmpl: HAWAIIAN_NAMES_1},
{Name: "HawaiianNames2", Tmpl: HAWAIIAN_NAMES_2},
{Name: "OldLatinPlaceNames", Tmpl: OLD_LATIN_PLACE_NAMES},
{Name: "DragonsPern", Tmpl: DRAGONS_PERN},
{Name: "DragonRiders", Tmpl: DRAGON_RIDERS},
{Name: "Pokemon", Tmpl: POKEMON},
{Name: "FantasyR", Tmpl: FANTASY_VOWELS_R},
{Name: "FantasySA", Tmpl: FANTASY_S_A},
{Name: "FantasyHL", Tmpl: FANTASY_H_L},
{Name: "FantasyNL", Tmpl: FANTASY_N_L},
{Name: "FantasyKN", Tmpl: FANTASY_K_N},
{Name: "FantasyJGZ", Tmpl: FANTASY_J_G_Z},
{Name: "FantasyKJY", Tmpl: FANTASY_K_J_Y},
{Name: "FantasySE", Tmpl: FANTASY_S_E},
{Name: "Funny", Tmpl: "sdD"},
{Name: "Idiots", Tmpl: "ii"},
var Templates = map[string]string{
"MiddleEarth": MIDDLE_EARTH,
"JapaneseNamesConstrained": JAPANESE_NAMES_CONSTRAINED,
"JapaneseNamesDiverse": JAPANESE_NAMES_DIVERSE,
"ChineseNames": CHINESE_NAMES,
"GreekNames": GREEK_NAMES,
"HawaiianNames1": HAWAIIAN_NAMES_1,
"HawaiianNames2": HAWAIIAN_NAMES_2,
"OldLatinPlaceNames": OLD_LATIN_PLACE_NAMES,
"DragonsPern": DRAGONS_PERN,
"DragonRiders": DRAGON_RIDERS,
"Pokemon": POKEMON,
"FantasyR": FANTASY_VOWELS_R,
"FantasySA": FANTASY_S_A,
"FantasyHL": FANTASY_H_L,
"FantasyNL": FANTASY_N_L,
"FantasyKN": FANTASY_K_N,
"FantasyJGZ": FANTASY_J_G_Z,
"FantasyKJY": FANTASY_K_J_Y,
"FantasySE": FANTASY_S_E,
"Funny": "sdD",
"Idiots": "ii",
}
const (
VERSION string = "0.0.1"
Usage string = `This is gfn, a fantasy name generator cli.
VERSION string = "0.0.1"
DefaultCount int = 160 // number of words to generate if -c is omitted
Columns int = 8 // number of columns to print
Usage string = `This is gfn, a fantasy name generator cli.
Usage: gfn [-vlc] <name|code>
@@ -151,6 +153,7 @@ type Config struct {
Showversion bool `koanf:"version"` // -v
Listshortcuts bool `koanf:"list"` // -l
Code string // arg
Count int `koanf:"count"` // -c
}
func InitConfig(output io.Writer) (*Config, error) {
@@ -158,7 +161,7 @@ func InitConfig(output io.Writer) (*Config, error) {
// Load default values using the confmap provider.
if err := kloader.Load(confmap.Provider(map[string]interface{}{
"count": 10,
"count": DefaultCount,
}, "."), nil); err != nil {
return nil, fmt.Errorf("failed to load default values into koanf: %w", err)
}
@@ -173,6 +176,7 @@ func InitConfig(output io.Writer) (*Config, error) {
// parse commandline flags
flagset.BoolP("list", "l", false, "show list of precompiled codes")
flagset.BoolP("version", "V", false, "show program version")
flagset.IntP("count", "c", 1, "how many names to generate")
if err := flagset.Parse(os.Args[1:]); err != nil {
return nil, fmt.Errorf("failed to parse program arguments: %w", err)

View File

@@ -14,12 +14,12 @@ func Generate(count int, code string) ([]string, error) {
gen, err := fn.Compile(code, fn.Collapse(true))
if err != nil {
return nil, fmt.Errorf("Could not compile FN code:", err)
return nil, fmt.Errorf("could not compile FN code: %w", err)
}
for i := 0; i < count; i++ {
name := gen.String()
if !exists(reg, name) {
if !Exists(reg, name) {
reg[name] = 1
}
}

BIN
gfn

Binary file not shown.

1
go.mod
View File

@@ -5,6 +5,7 @@ go 1.22
require github.com/s0rg/fantasyname v1.3.4
require (
github.com/alecthomas/repr v0.4.0 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
github.com/knadh/koanf/maps v0.1.1 // indirect

2
go.sum
View File

@@ -1,3 +1,5 @@
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c=

43
main.go
View File

@@ -3,7 +3,6 @@ package main
import (
"fmt"
"io"
"log"
"os"
)
@@ -24,42 +23,26 @@ func Main(output io.Writer) int {
}
if conf.Listshortcuts {
for _, name := range Templates {
fmt.Println(name)
}
ListTemplates(output)
return 0
}
// FIXME: this is a slice of Template{}, turn it into a map
if Contains(Templates, conf.Code) {
if len(conf.Code) == 0 {
fmt.Fprintln(output, Usage)
}
if Exists(Templates, conf.Code) {
conf.Code = Templates[conf.Code]
}
names, err := Generate(conf.Count, conf.Code)
if err != nil {
return Die(err)
}
if err = PrintColumns(names, output); err != nil {
return Die(err)
}
return 0
}
func exists[K comparable, V any](m map[K]V, v K) bool {
if _, ok := m[v]; ok {
return true
}
return false
}
func Die(err error) int {
log.Fatal("Error", err.Error())
return 1
}
// find an item in a list, generic variant
func Contains[E comparable](s []E, v E) bool {
for _, vs := range s {
if v == vs {
return true
}
}
return false
}

81
printer.go Normal file
View File

@@ -0,0 +1,81 @@
package main
import (
"fmt"
"io"
"sort"
)
func ListTemplates(output io.Writer) {
names := []string{}
for name := range Templates {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
fmt.Fprintln(output, name)
}
}
func PrintColumns(names []string, output io.Writer) error {
count := len(names)
// no need for the hassle to calculate columns
if count <= Columns {
for _, name := range names {
fmt.Fprintln(output, name)
}
return nil
}
// get a transposed list of columns
padlist, max := Getcolumns(names, Columns)
// make sure there's enough spacing between the columns
format := fmt.Sprintf("%%-%ds", max+1)
for _, row := range padlist {
for _, word := range row {
fmt.Fprintf(output, format, word)
}
fmt.Fprintln(output)
}
return nil
}
func Getcolumns(names []string, columns int) ([][]string, int) {
words := len(names)
max := 0
// we'll have a list of $columns columns
padlist := make([][]string, columns)
// initialize'em
for col := 0; col < columns; col++ {
padlist[col] = []string{}
}
// fill from input
for idx := 0; idx < words; idx += columns {
for col := 0; col < columns; col++ {
if idx+col >= words {
padlist[col] = append(padlist[col], "")
} else {
padlist[col] = append(padlist[col], names[idx+col])
length := len(names[idx+col])
if length > max {
max = length
}
}
}
}
// turn columns to rows
return Transpose(padlist), max
}

48
util.go Normal file
View File

@@ -0,0 +1,48 @@
package main
import "log"
func Exists[K comparable, V any](m map[K]V, v K) bool {
if _, ok := m[v]; ok {
return true
}
return false
}
func Die(err error) int {
log.Fatal("Error: ", err.Error())
return 1
}
// find an item in a list, generic variant
func Contains[E comparable](s []E, v E) bool {
for _, vs := range s {
if v == vs {
return true
}
}
return false
}
// Transpose a matrix, x=>y, y=>x
// via https://gist.github.com/tanaikech/5cb41424ff8be0fdf19e78d375b6adb8
func Transpose(slice [][]string) [][]string {
xl := len(slice[0])
yl := len(slice)
result := make([][]string, xl)
for i := range result {
result[i] = make([]string, yl)
}
for i := 0; i < xl; i++ {
for j := 0; j < yl; j++ {
result[i][j] = slice[j][i]
}
}
return result
}