diff --git a/config.go b/config.go new file mode 100644 index 0000000..d88e4d7 --- /dev/null +++ b/config.go @@ -0,0 +1,198 @@ +package main + +import ( + "fmt" + "io" + "os" + + "github.com/knadh/koanf/providers/confmap" + "github.com/knadh/koanf/providers/posflag" + "github.com/knadh/koanf/v2" + flag "github.com/spf13/pflag" +) + +const ( + MIDDLE_EARTH = "(bil|bal|ban|hil|ham|hal|hol|hob|wil|me|or|ol|od|gor|for|fos|tol|ar|fin|ere|leo|vi|bi|bren|thor)" + + "(|go|orbis|apol|adur|mos|ri|i|na|ole|n)" + + "(|tur|axia|and|bo|gil|bin|bras|las|mac|grim|wise|l|lo|fo|co|ra|via|" + + "da|ne|ta|y|wen|thiel|phin|dir|dor|tor|rod|on|rdo|dis)" + JAPANESE_NAMES_CONSTRAINED = "(aka|aki|bashi|gawa|kawa|furu|fuku|fuji|hana|hara|haru|hashi|hira|hon|hoshi|" + + "ichi|iwa|kami|kawa|ki|kita|kuchi|kuro|marui|matsu|miya|mori|moto|mura|nabe|naka|nishi|no|da|ta|o|oo|oka|" + + "saka|saki|sawa|shita|shima|i|suzu|taka|take|to|toku|toyo|ue|wa|wara|wata|yama|yoshi|kei|ko|zawa|zen|sen|" + + "ao|gin|kin|ken|shiro|zaki|yuki|asa)(||||||||||bashi|gawa|kawa|furu|fuku|fuji|hana|hara|haru|hashi|hira|" + + "hon|hoshi|chi|wa|ka|kami|kawa|ki|kita|kuchi|kuro|marui|matsu|miya|mori|moto|mura|nabe|naka|nishi|no|da|" + + "ta|o|oo|oka|saka|saki|sawa|shita|shima|suzu|taka|take|to|toku|toyo|ue|wa|wara|wata|yama|yoshi|kei|ko|" + + "zawa|zen|sen|ao|gin|kin|ken|shiro|zaki|yuki|sa)" + JAPANESE_NAMES_DIVERSE = "(a|i|u|e|o|||||)" + + "(ka|ki|ki|ku|ku|ke|ke|ko|ko|sa|sa|sa|shi|shi|shi|su|su|se|so|ta|ta|chi|chi|tsu|te|to|na|ni|ni|nu|nu|ne|" + + "no|no|ha|hi|fu|fu|he|ho|ma|ma|ma|mi|mi|mi|mu|mu|mu|mu|me|mo|mo|mo|ya|yu|yu|yu|yo|ra|ra|ra|ri|ru|ru|ru|" + + "re|ro|ro|ro|wa|wa|wa|wa|wo|wo)(ka|ki|ki|ku|ku|ke|ke|ko|ko|sa|sa|sa|shi|shi|shi|su|su|se|so|ta|ta|chi|" + + "chi|tsu|te|to|na|ni|ni|nu|nu|ne|no|no|ha|hi|fu|fu|he|ho|ma|ma|ma|mi|mi|mi|mu|mu|mu|mu|me|mo|mo|mo|ya|" + + "yu|yu|yu|yo|ra|ra|ra|ri|ru|ru|ru|re|ro|ro|ro|wa|wa|wa|wa|wo|wo)" + + "(|(ka|ki|ki|ku|ku|ke|ke|ko|ko|sa|sa|sa|shi|shi|shi|su|su|se|so|ta|ta|chi|chi|tsu|te|to|na|ni|ni|nu|nu|ne|" + + "no|no|ha|hi|fu|fu|he|ho|ma|ma|ma|mi|mi|mi|mu|mu|mu|mu|me|mo|mo|mo|ya|yu|yu|yu|yo|ra|ra|ra|ri|ru|ru|ru|re|" + + "ro|ro|ro|wa|wa|wa|wa|wo|wo)|(ka|ki|ki|ku|ku|ke|ke|ko|ko|sa|sa|sa|shi|shi|shi|su|su|se|so|ta|ta|chi|chi|" + + "tsu|te|to|na|ni|ni|nu|nu|ne|no|no|ha|hi|fu|fu|he|ho|ma|ma|ma|mi|mi|mi|mu|mu|mu|mu|me|mo|mo|mo|ya|yu|yu|" + + "yu|yo|ra|ra|ra|ri|ru|ru|ru|re|ro|ro|ro|wa|wa|wa|wa|wo|wo)(|(ka|ki|ki|ku|ku|ke|ke|ko|ko|sa|sa|sa|shi|shi|" + + "shi|su|su|se|so|ta|ta|chi|chi|tsu|te|to|na|ni|ni|nu|nu|ne|no|no|ha|hi|fu|fu|he|ho|ma|ma|ma|mi|mi|mi|mu|" + + "mu|mu|mu|me|mo|mo|mo|ya|yu|yu|yu|yo|ra|ra|ra|ri|ru|ru|ru|re|ro|ro|ro|wa|wa|wa|wa|wo|wo)))(|||n)" + CHINESE_NAMES = "(zh|x|q|sh|h)(ao|ian|uo|ou|ia)(|(l|w|c|p|b|m)(ao|ian|uo|ou|ia)" + + "(|n)|-(l|w|c|p|b|m)(ao|ian|uo|ou|ia)(|(d|j|q|l)(a|ai|iu|ao|i)))" + GREEK_NAMES = "(tia)|s(os)|Bc(ios)|Bv(ios|os)>" + HAWAIIAN_NAMES_1 = "((h|k|l|m|n|p|w)|)(a|e|i|o|u)((h|k|l|m|n|p|w)|)(a|e|i|o|u)(((h|k|l|m|n|p|w)|)" + + "(a|e|i|o|u)|)(((h|k|l|m|n|p|w)|)(a|e|i|o|u)|)(((h|k|l|m|n|p|w)|)(a|e|i|o|u)|)(((h|k|l|m|n|p|w)|)" + + "(a|e|i|o|u)|)" + HAWAIIAN_NAMES_2 = "((h|k|l|m|n|p|w|)(a|e|i|o|u|a|e|i|o|u|ae|ai|ao|au|oi|ou|eu|ei)" + + "(k|l|m|n|p|)|)(h|k|l|m|n|p|w|)(a|e|i|o|u|a|e|i|o|u|ae|ai|ao|au|oi|ou|eu|ei)(k|l|m|n|p|)" + OLD_LATIN_PLACE_NAMES = "sv(nia|lia|cia|sia)" + DRAGONS_PERN = "<|>>(th)" + DRAGON_RIDERS = "c" + POKEMON = "v(mon|chu|zard|rtle)" + FANTASY_VOWELS_R = "(|(|s|h|ty|ph|r))(i|ae|ya|ae|eu|ia|i|eo|ai|a)" + + "(lo|la|sri|da|dai|the|sty|lae|due|li|lly|ri|na|ral|sur|rith)(|(su|nu|sti|llo|ria|))" + + "(|(n|ra|p|m|lis|cal|deu|dil|suir|phos|ru|dru|rin|raap|rgue))" + FANTASY_S_A = "(cham|chan|jisk|lis|frich|isk|lass|mind|sond|sund|ass|chad|lirt|und|mar|lis|il|)" + + "(jask|ast|ista|adar|irra|im|ossa|assa|osia|ilsa|)(|(an|ya|la|sta|sda|sya|st|nya))" + FANTASY_H_L = "(ch|cht|sh|cal|val|ell|har|shar|shal|rel|laen|ral|jht|alr|ch|cht|av)" + + "(|(is|al|ow|ish|ul|el|ar|iel))" + + "(aren|aeish|aith|even|adur|ulash|alith|atar|aia|erin|aera|ael|ira|iel|ahur|ishul)" + FANTASY_N_L = "(ethr|qil|mal|er|eal|far|fil|fir|ing|ind|il|lam|quel|quar|quan|qar|pal|mal|yar|um|ard|enn|ey)" + + "(|(|on|us|un|ar|as|en|ir|ur|at|ol|al|an))" + + "(uard|wen|arn|on|il|ie|on|iel|rion|rian|an|ista|rion|rian|cil|mol|yon)" + FANTASY_K_N = "(taith|kach|chak|kank|kjar|rak|kan|kaj|tach|rskal|kjol|jok|jor|jad|kot|kon|" + + "knir|kror|kol|tul|rhaok|rhak|krol|jan|kag|ryr)(|in|or|an|ar|och|un|mar|yk|ja|arn|ir|ros|ror)" + + "(|(mund|ard|arn|karr|chim|kos|rir|arl|kni|var|an|in|ir|a|i|as))" + FANTASY_J_G_Z = "(aj|ch|etz|etzl|tz|kal|gahn|kab|aj|izl|ts|jaj|lan|kach|chaj|qaq|jol|ix|az|biq|nam)" + + "(|(|aw|al|yes|il|ay|en|tom||oj|im|ol|aj|an|as))" + + "(aj|am|al|aqa|ende|elja|ich|ak|ix|in|ak|al|il|ek|ij|os|al|im)" + FANTASY_K_J_Y = "(yi|shu|a|be|na|chi|cha|cho|ksa|yi|shu)" + + "(th|dd|jj|sh|rr|mk|n|rk|y|jj|th)" + + "(us|ash|eni|akra|nai|ral|ect|are|el|urru|aja|al|uz|ict|arja|ichi|ural|iru|aki|esh)" + FANTASY_S_E = "(syth|sith|srr|sen|yth|ssen|then|fen|ssth|kel|syn|est|bess|inth|nen|tin|cor|" + + "sv|iss|ith|sen|slar|ssil|sthen|svis|s|ss|s|ss)" + + "(|(tys|eus|yn|of|es|en|ath|elth|al|ell|ka|ith|yrrl|is|isl|yr|ast|iy))" + + "(us|yn|en|ens|ra|rg|le|en|ith|ast|zon|in|yn|ys)" +) + +type Template struct { + Name string + 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"}, +} + +const ( + VERSION string = "0.0.1" + Usage string = `This is gfn, a fantasy name generator cli. + +Usage: gfn [-vlc] + +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 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.` +) + +type Config struct { + Showversion bool `koanf:"version"` // -v + Listshortcuts bool `koanf:"list"` // -l + Code string // arg +} + +func InitConfig(output io.Writer) (*Config, error) { + var kloader = koanf.New(".") + + // Load default values using the confmap provider. + if err := kloader.Load(confmap.Provider(map[string]interface{}{ + "count": 10, + }, "."), nil); err != nil { + return nil, fmt.Errorf("failed to load default values into koanf: %w", err) + } + + // setup custom usage + flagset := flag.NewFlagSet("config", flag.ContinueOnError) + flagset.Usage = func() { + fmt.Fprintln(output, Usage) + os.Exit(0) + } + + // parse commandline flags + flagset.BoolP("list", "l", false, "show list of precompiled codes") + flagset.BoolP("version", "V", false, "show program version") + + if err := flagset.Parse(os.Args[1:]); err != nil { + return nil, fmt.Errorf("failed to parse program arguments: %w", err) + } + + // command line setup + if err := kloader.Load(posflag.Provider(flagset, ".", kloader), nil); err != nil { + return nil, fmt.Errorf("error loading flags: %w", err) + } + + // fetch values + conf := &Config{} + if err := kloader.Unmarshal("", &conf); err != nil { + return nil, fmt.Errorf("error unmarshalling: %w", err) + } + + // arg is the code + if len(flagset.Args()) > 0 { + conf.Code = flagset.Args()[0] + } + + return conf, nil +} diff --git a/generate.go b/generate.go new file mode 100644 index 0000000..d065e10 --- /dev/null +++ b/generate.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" + "math/rand" + "time" + + fn "github.com/s0rg/fantasyname" +) + +func Generate(count int, code string) ([]string, error) { + rand.Seed(time.Now().UnixNano()) + reg := map[string]int{} + + gen, err := fn.Compile(code, fn.Collapse(true)) + if err != nil { + return nil, fmt.Errorf("Could not compile FN code:", err) + } + + for i := 0; i < count; i++ { + name := gen.String() + if !exists(reg, name) { + reg[name] = 1 + } + } + + names := make([]string, len(reg)) + + i := 0 + for k, _ := range reg { + names[i] = k + i++ + } + + return names, nil +} diff --git a/gfn b/gfn new file mode 100755 index 0000000..3b0f0a1 Binary files /dev/null and b/gfn differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..176cce4 --- /dev/null +++ b/go.mod @@ -0,0 +1,23 @@ +module github.com/TLINDEN/gfn + +go 1.22 + +require github.com/s0rg/fantasyname v1.3.4 + +require ( + 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 + github.com/knadh/koanf/providers/confmap v0.1.0 // indirect + github.com/knadh/koanf/providers/posflag v0.1.0 // indirect + github.com/knadh/koanf/v2 v2.1.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/tlinden/yadu v0.1.3 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a1d4687 --- /dev/null +++ b/go.sum @@ -0,0 +1,36 @@ +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= +github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= +github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= +github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU= +github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU= +github.com/knadh/koanf/providers/posflag v0.1.0 h1:mKJlLrKPcAP7Ootf4pBZWJ6J+4wHYujwipe7Ie3qW6U= +github.com/knadh/koanf/providers/posflag v0.1.0/go.mod h1:SYg03v/t8ISBNrMBRMlojH8OsKowbkXV7giIbBVgbz0= +github.com/knadh/koanf/v2 v2.1.0 h1:eh4QmHHBuU8BybfIJ8mB8K8gsGCD/AUQTdwGq/GzId8= +github.com/knadh/koanf/v2 v2.1.0/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/s0rg/fantasyname v1.3.4 h1:zqmKri+2MGVxgRfTm0XViZPtoy0wm9BBc6oqOCuALZw= +github.com/s0rg/fantasyname v1.3.4/go.mod h1:XqTfA5mtZAxnjcSkAwijm1FGoI9FkjRyUOWEEbjsRd0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/tlinden/yadu v0.1.3 h1:5cRCUmj+l5yvlM2irtpFBIJwVV2DPEgYSaWvF19FtcY= +github.com/tlinden/yadu v0.1.3/go.mod h1:l3bRmHKL9zGAR6pnBHY2HRPxBecf7L74BoBgOOpTcUA= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..bd810fe --- /dev/null +++ b/main.go @@ -0,0 +1,65 @@ +package main + +import ( + "fmt" + "io" + "log" + "os" +) + +func main() { + os.Exit(Main(os.Stdout)) +} + +func Main(output io.Writer) int { + conf, err := InitConfig(output) + if err != nil { + return Die(err) + } + + if conf.Showversion { + fmt.Fprintf(output, "This is gfn version %s\n", VERSION) + + return 0 + } + + if conf.Listshortcuts { + for _, name := range Templates { + fmt.Println(name) + } + return 0 + } + + // FIXME: this is a slice of Template{}, turn it into a map + if Contains(Templates, conf.Code) { + conf.Code = Templates[conf.Code] + } + + names, err := Generate(conf.Count, conf.Code) + + 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 +}