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 # 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 Tmpl string
} }
var Templates = []Template{ var Templates = map[string]string{
{Name: "MiddleEarth", Tmpl: MIDDLE_EARTH}, "MiddleEarth": MIDDLE_EARTH,
{Name: "JapaneseNamesConstrained", Tmpl: JAPANESE_NAMES_CONSTRAINED}, "JapaneseNamesConstrained": JAPANESE_NAMES_CONSTRAINED,
{Name: "JapaneseNamesDiverse", Tmpl: JAPANESE_NAMES_DIVERSE}, "JapaneseNamesDiverse": JAPANESE_NAMES_DIVERSE,
{Name: "ChineseNames", Tmpl: CHINESE_NAMES}, "ChineseNames": CHINESE_NAMES,
{Name: "GreekNames", Tmpl: GREEK_NAMES}, "GreekNames": GREEK_NAMES,
{Name: "HawaiianNames1", Tmpl: HAWAIIAN_NAMES_1}, "HawaiianNames1": HAWAIIAN_NAMES_1,
{Name: "HawaiianNames2", Tmpl: HAWAIIAN_NAMES_2}, "HawaiianNames2": HAWAIIAN_NAMES_2,
{Name: "OldLatinPlaceNames", Tmpl: OLD_LATIN_PLACE_NAMES}, "OldLatinPlaceNames": OLD_LATIN_PLACE_NAMES,
{Name: "DragonsPern", Tmpl: DRAGONS_PERN}, "DragonsPern": DRAGONS_PERN,
{Name: "DragonRiders", Tmpl: DRAGON_RIDERS}, "DragonRiders": DRAGON_RIDERS,
{Name: "Pokemon", Tmpl: POKEMON}, "Pokemon": POKEMON,
{Name: "FantasyR", Tmpl: FANTASY_VOWELS_R}, "FantasyR": FANTASY_VOWELS_R,
{Name: "FantasySA", Tmpl: FANTASY_S_A}, "FantasySA": FANTASY_S_A,
{Name: "FantasyHL", Tmpl: FANTASY_H_L}, "FantasyHL": FANTASY_H_L,
{Name: "FantasyNL", Tmpl: FANTASY_N_L}, "FantasyNL": FANTASY_N_L,
{Name: "FantasyKN", Tmpl: FANTASY_K_N}, "FantasyKN": FANTASY_K_N,
{Name: "FantasyJGZ", Tmpl: FANTASY_J_G_Z}, "FantasyJGZ": FANTASY_J_G_Z,
{Name: "FantasyKJY", Tmpl: FANTASY_K_J_Y}, "FantasyKJY": FANTASY_K_J_Y,
{Name: "FantasySE", Tmpl: FANTASY_S_E}, "FantasySE": FANTASY_S_E,
{Name: "Funny", Tmpl: "sdD"}, "Funny": "sdD",
{Name: "Idiots", Tmpl: "ii"}, "Idiots": "ii",
} }
const ( const (
VERSION string = "0.0.1" VERSION string = "0.0.1"
Usage string = `This is gfn, a fantasy name generator cli. 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> Usage: gfn [-vlc] <name|code>
@@ -151,6 +153,7 @@ type Config struct {
Showversion bool `koanf:"version"` // -v Showversion bool `koanf:"version"` // -v
Listshortcuts bool `koanf:"list"` // -l Listshortcuts bool `koanf:"list"` // -l
Code string // arg Code string // arg
Count int `koanf:"count"` // -c
} }
func InitConfig(output io.Writer) (*Config, error) { 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. // Load default values using the confmap provider.
if err := kloader.Load(confmap.Provider(map[string]interface{}{ if err := kloader.Load(confmap.Provider(map[string]interface{}{
"count": 10, "count": DefaultCount,
}, "."), nil); err != nil { }, "."), nil); err != nil {
return nil, fmt.Errorf("failed to load default values into koanf: %w", err) 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 // parse commandline flags
flagset.BoolP("list", "l", false, "show list of precompiled codes") flagset.BoolP("list", "l", false, "show list of precompiled codes")
flagset.BoolP("version", "V", false, "show program version") 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 { if err := flagset.Parse(os.Args[1:]); err != nil {
return nil, fmt.Errorf("failed to parse program arguments: %w", err) 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)) gen, err := fn.Compile(code, fn.Collapse(true))
if err != nil { 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++ { for i := 0; i < count; i++ {
name := gen.String() name := gen.String()
if !exists(reg, name) { if !Exists(reg, name) {
reg[name] = 1 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/s0rg/fantasyname v1.3.4
require ( require (
github.com/alecthomas/repr v0.4.0 // indirect
github.com/fatih/color v1.16.0 // indirect github.com/fatih/color v1.16.0 // indirect
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
github.com/knadh/koanf/maps v0.1.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 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= 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= 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 ( import (
"fmt" "fmt"
"io" "io"
"log"
"os" "os"
) )
@@ -24,42 +23,26 @@ func Main(output io.Writer) int {
} }
if conf.Listshortcuts { if conf.Listshortcuts {
for _, name := range Templates { ListTemplates(output)
fmt.Println(name)
}
return 0 return 0
} }
// FIXME: this is a slice of Template{}, turn it into a map if len(conf.Code) == 0 {
if Contains(Templates, conf.Code) { fmt.Fprintln(output, Usage)
}
if Exists(Templates, conf.Code) {
conf.Code = Templates[conf.Code] conf.Code = Templates[conf.Code]
} }
names, err := Generate(conf.Count, 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 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
}