mirror of
https://codeberg.org/scip/gfn.git
synced 2025-12-16 18:30:57 +01:00
program works now, added a little documentation
This commit is contained in:
108
README.md
108
README.md
@@ -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
|
||||
|
||||
54
config.go
54
config.go
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
1
go.mod
1
go.mod
@@ -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
2
go.sum
@@ -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
43
main.go
@@ -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
81
printer.go
Normal 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
48
util.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user