Lots of additions:

- fixed issue#1: break endless loop for non-codes
- added config file support
- added logo
- added unit tests
- added ci test pipeline
- added issue templates
- added binary release builder
- enhanced documentation
- made the number of columns flexible based on word size
This commit is contained in:
2024-03-20 12:36:55 +01:00
parent f6a417b04d
commit 186c95076e
30 changed files with 874 additions and 41 deletions

31
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,31 @@
---
name: Bug report
about: Create a report to help us improve
title: "[bug-report]"
labels: bug
assignees: TLINDEN
---
**Description**
<!-- Please provide a clear and concise description of the issue: -->
**Steps To Reproduce**
<!-- Please detail the steps to reproduce the behavior, add the -d flag: -->
**Expected behavior**
<!-- What do you expected to happen instead? -->
**Version information**
<!--
Please provide as much version information as possible:
- if you have just installed a binary, provide the output of: gfn -v
- if you installed from source, provide the output of: make show-version
- provide additional details: operating system and version and shell environment
-->
**Additional informations**

View File

@@ -0,0 +1,23 @@
---
name: Feature request
about: Suggest a feature
title: "[feature-request]"
labels: feature-request
assignees: TLINDEN
---
**Describtion**
<!-- Please provide a clear and concise description of the feature you desire: -->
**Version information**
<!--
Just in case the feature is already present, please provide as
much version information as possible:
- if you have just installed a binary, provide the output of: gfn -v
- if you installed from source, provide the output of: make show-version
- provide additional details: operating system and version and shell environment
-->

BIN
.github/assets/logo.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

36
.github/workflows/ci.yaml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: build-and-test-gfn
on: [push, pull_request]
jobs:
build:
strategy:
matrix:
version: [1.22]
os: [ubuntu-latest, windows-latest, macos-latest]
name: Build
runs-on: ${{ matrix.os }}
steps:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.version }}
id: go
- name: checkout
uses: actions/checkout@v3
- name: build
run: go build
- name: test
run: make test
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v3
with:
go-version: 1.22
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
gfn
coverage.out
releases

87
Makefile Normal file
View File

@@ -0,0 +1,87 @@
# Copyright © 2024 Thomas von Dein
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# no need to modify anything below
tool = gfn
VERSION = $(shell grep VERSION config.go | head -1 | cut -d '"' -f2)
archs = darwin freebsd linux windows
PREFIX = /usr/local
UID = root
GID = 0
HAVE_POD := $(shell pod2text -h 2>/dev/null)
all: buildlocal
buildlocal:
CGO_LDFLAGS='-static' go build -tags osusergo,netgo -ldflags "-extldflags=-static" -o $(tool)
install: buildlocal
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1
install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/
install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/
clean:
rm -rf $(tool) coverage.out testdata t/out
test: clean
mkdir -p t/out
go test ./... $(ARGS)
testlint: test lint
lint:
golangci-lint run
lint-full:
golangci-lint run --enable-all --exclude-use-default --disable exhaustivestruct,exhaustruct,depguard,interfacer,deadcode,golint,structcheck,scopelint,varcheck,ifshort,maligned,nosnakecase,godot,funlen,gofumpt,cyclop,noctx,gochecknoglobals,paralleltest
testfuzzy: clean
go test -fuzz ./... $(ARGS)
singletest:
@echo "Call like this: make singletest TEST=TestPrepareColumns ARGS=-v"
go test -run $(TEST) $(ARGS)
cover-report:
go test ./... -cover -coverprofile=coverage.out
go tool cover -html=coverage.out
goupdate:
go get -t -u=patch ./...
buildall:
./mkrel.sh $(tool) $(VERSION)
release: buildall
gh release create v$(VERSION) --generate-notes releases/*
show-versions: buildlocal
@echo "### gfn version:"
@./gfn -V
@echo
@echo "### go module versions:"
@go list -m all
@echo
@echo "### go version used for building:"
@grep -m 1 go go.mod
# lint:
# golangci-lint run -p bugs -p unused

View File

@@ -1,4 +1,10 @@
# gfn
# gfn - generate fantasy names for games and stories on the commandline
![Gfn Logo](https://github.com/TLINDEN/gfn/blob/main/.github/assets/logo.png)
[![Actions](https://github.com/tlinden/gfnc/actions/workflows/ci.yaml/badge.svg)](https://github.com/tlinden/gfnc/actions)
[![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://github.com/tlinden/gfnc/blob/master/LICENSE)
[![Go Report Card](https://goreportcard.com/badge/github.com/tlinden/gfnc)](https://goreportcard.com/report/github.com/tlinden/gfnc)
Generate fantasy names for games and stories. It uses the fine
[fantasyname module](https://github.com/s0rg/fantasyname) by
@@ -12,14 +18,51 @@ 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
## Installation
Execute
The tool does not have any dependencies. Just download the binary for
your platform from the releases page and you're good to go.
### Installation using a pre-compiled binary
Go to the [latest release page](https://github.com/TLINDEN/gfn/releases/latest)
and look for your OS and platform. There are two options to install the binary:
Directly download the binary for your platform,
e.g. `gfn-linux-amd64-0.0.2`, rename it to `gfn` (or whatever
you like more!) and put it into your bin dir (e.g. `$HOME/bin` or as
root to `/usr/local/bin`).
Be sure to verify the signature of the binary file. For this also
download the matching `gfn-linux-amd64-0.0.2.sha256` file and:
```shell
% go build
% cp gfn $HOME/bin
cat gfn-linux-amd64-0.0.2.sha25 && sha256sum gfn-linux-amd64-0.0.2
```
You should see the same SHA256 hash.
You may also download a binary tarball for your platform, e.g.
`gfn-linux-amd64-0.0.2.tar.gz`, unpack and install it. GNU Make is
required for this:
```shell
tar xvfz gfn-linux-amd64-0.0.2.tar.gz
cd gfn-linux-amd64-0.0.2
sudo make install
```
### Installation from source
You will need the Golang toolchain in order to build from source. GNU
Make will also help but is not strictly neccessary.
If you want to compile the tool yourself, use `git clone` to clone the
repository. Then execute `go mod tidy` to install all
dependencies. Then just enter `go build` or - if you have GNU Make
installed - `make`.
To install after building either copy the binary or execute `sudo make
install`.
# Usage
@@ -34,19 +77,21 @@ 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
% gfn JapaneseNamesDiverse -n 24
iyonen isuyaro iwamo remi kikune chikeyu iwamun
ruri orasenin wamo oramamo ironisuru hokoku kumun
ewoyani imanoma enenoya sawo enunumiken wayumu itamachi
```
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
gfn '!sVm' -n 25
Angeeschnookum Arysmooch Oraepookie Rothaudoodle Hinaypoochie
Keloosnookum Esteeschnookum Ageausmoosh Erucuddle Tonuilover
Ghaeaschnoogle Seraylover Dynysnoogle Chaibunker Poloebaby
Leroepoochie Tinowuggy Baniaschmoopie Banoesnoogy Taseamoopie
Entheysmooch Ustamooglie Taneidoodle Hatiesnoogy Belacuddle
```
A short outline of the code will be printed if you add the `-h`
@@ -54,11 +99,13 @@ parameter:
```shell
This is gfn, a fantasy name generator cli.
Usage: gfn [-vlc] <name|code>
Usage: gfn [-vld] [-c <config file>] [-n <number of names>] <name|code>
Options:
-c --count How many names to generate
-c --config Config file to use (optional)
-n --number Number of names to generate
-l --list List pre-compiled shortcuts
-d --debug Show debugging output
-v --version Show program version
pattern syntax The letters s, v, V, c, B, C, i, m, M, D, and d
@@ -94,6 +141,28 @@ it. For example, !(foo) will emit Foo and v!s will emit a lowercase
vowel followed by a capitalized syllable, like eRod.
```
You can use a config file to store your own codes, once you found one
you like. A configfile is searched in these locations in this order:
* `/etc/gfn.conf`
* `/usr/local/etc/gfn.conf`
* `$HOME/.config/gfn/config`
* `$HOME/.gfn`
You may also specify a config file on the commandline using the `-c`
flag.
You can add multiple codes, here's an example:
```toml
# example config file
[[Templates]]
morph = "!s(na|ha|ma|va)v"
morphium = "!s(na|ha|ma|va)v(ius|ium|aum|oum|eum)"
```
Config files are expected to be in the [TOML format](https://toml.io/en/).
# Report bugs
[Please open an issue](https://github.com/TLINDEN/gfn/issues). Thanks!

BIN
README.pdf Normal file

Binary file not shown.

View File

@@ -1,11 +1,31 @@
/*
Copyright © 2024 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"fmt"
"io"
"os"
"path/filepath"
"github.com/knadh/koanf/parsers/toml"
"github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/providers/posflag"
"github.com/knadh/koanf/v2"
flag "github.com/spf13/pflag"
@@ -104,16 +124,19 @@ var Templates = map[string]string{
}
const (
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.
VERSION string = "0.0.2"
DefaultCount int = 160 // number of words to generate if -c is omitted
DefaultColumns int = 10 // number of columns to print
MaxWidth int = 72 // max width of output, adjusts columns
Usage string = `This is gfn, a fantasy name generator cli.
Usage: gfn [-vlc] <name|code>
Usage: gfn [-vld] [-c <config file>] [-n <number of names>] <name|code>
Options:
-c --count How many names to generate
-c --config Config file to use (optional)
-n --number Number of names to generate
-l --list List pre-compiled shortcuts
-d --debug Show debugging output
-v --version Show program version
pattern syntax The letters s, v, V, c, B, C, i, m, M, D, and d
@@ -150,10 +173,15 @@ vowel followed by a capitalized syllable, like eRod.`
)
type Config struct {
Showversion bool `koanf:"version"` // -v
Listshortcuts bool `koanf:"list"` // -l
Code string // arg
Count int `koanf:"count"` // -c
Showversion bool `koanf:"version"` // -v
Debug bool `koanf:"debug"` // -d
Listshortcuts bool `koanf:"list"` // -l
Number int `koanf:"number"` // -c
Templates map[string]string `koanf:"templates"`
Config string `koanf:"config"`
Columns int `koanf:"columns"` // number of columns to use
Code string // arg
WordWidth int // max width of generated words
}
func InitConfig(output io.Writer) (*Config, error) {
@@ -161,7 +189,8 @@ func InitConfig(output io.Writer) (*Config, error) {
// Load default values using the confmap provider.
if err := kloader.Load(confmap.Provider(map[string]interface{}{
"count": DefaultCount,
"number": DefaultCount,
"columns": DefaultColumns,
}, "."), nil); err != nil {
return nil, fmt.Errorf("failed to load default values into koanf: %w", err)
}
@@ -175,13 +204,44 @@ 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")
flagset.BoolP("version", "v", false, "show program version")
flagset.BoolP("debug", "d", false, "enable debug output")
flagset.IntP("number", "n", 1, "number of names to generate")
flagset.StringP("config", "c", "", "config file")
if err := flagset.Parse(os.Args[1:]); err != nil {
return nil, fmt.Errorf("failed to parse program arguments: %w", err)
}
// generate a list of config files to try to load, including the
// one provided via -c, if any
var configfiles []string
configfile, _ := flagset.GetString("config")
home, _ := os.UserHomeDir()
if configfile != "" {
configfiles = []string{configfile}
} else {
configfiles = []string{
"/etc/gfn.conf", "/usr/local/etc/gfn.conf", // unix variants
filepath.Join(home, ".config", "gfn", "config"),
filepath.Join(home, ".gfn"),
"gfn.conf",
}
}
// Load the config file[s]
for _, cfgfile := range configfiles {
if path, err := os.Stat(cfgfile); !os.IsNotExist(err) {
if !path.IsDir() {
if err := kloader.Load(file.Provider(cfgfile), toml.Parser()); err != nil {
return nil, fmt.Errorf("error loading config file: %w", err)
}
}
} // else: we ignore the file if it doesn't exists
}
// command line setup
if err := kloader.Load(posflag.Provider(flagset, ".", kloader), nil); err != nil {
return nil, fmt.Errorf("error loading flags: %w", err)
@@ -198,5 +258,14 @@ func InitConfig(output io.Writer) (*Config, error) {
conf.Code = flagset.Args()[0]
}
// merge configured and hardcoded templates
if conf.Templates == nil {
conf.Templates = Templates
} else {
for name, code := range Templates {
conf.Templates[name] = code
}
}
return conf, nil
}

View File

@@ -1,29 +1,75 @@
/*
Copyright © 2024 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"fmt"
"log/slog"
"math/rand"
"time"
fn "github.com/s0rg/fantasyname"
)
func Generate(count int, code string) ([]string, error) {
// Actual fantasy name generation
func Generate(conf *Config) ([]string, error) {
rand.Seed(time.Now().UnixNano())
// we register each generated word to avoid duplicates, which
// naturally happens every while
reg := map[string]int{}
gen, err := fn.Compile(code, fn.Collapse(true))
// library call
gen, err := fn.Compile(conf.Code, fn.Collapse(true))
if err != nil {
return nil, fmt.Errorf("could not compile FN code: %w", err)
}
for i := 0; i < count; i++ {
// fetch requested number of names
for i := 0; len(reg) < conf.Number; i++ {
name := gen.String()
if !Exists(reg, name) {
reg[name] = 1
if conf.WordWidth < len(name) {
conf.WordWidth = len(name)
}
}
// static codes (like 'akx', which is no FN code, just a
// literal) generates just 1 item
if i > conf.Number*2 {
break
}
}
fmt.Println("fetched names")
slog.Debug("Generated fantasy names from code",
"code", conf.Code, "count-names", len(reg))
// adjust columns, if needed
if conf.WordWidth*conf.Columns > MaxWidth {
conf.Columns = MaxWidth / conf.WordWidth
}
// we just return a slice of names
names := make([]string, len(reg))
i := 0

77
generate_test.go Normal file
View File

@@ -0,0 +1,77 @@
/*
Copyright © 2024 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"fmt"
"testing"
)
var tests = []struct {
name string
want bool
code string
}{
{
name: "code-ok",
want: true,
code: "!s(na|ha|ma|va)v",
},
{
name: "code-fail",
want: false,
code: "!s(na|ha|ma|vav",
},
}
var conf = &Config{
Number: 3,
}
func TestGenerate(t *testing.T) {
for _, tt := range tests {
testname := fmt.Sprintf("generate-%s", tt.name)
t.Run(testname, func(t *testing.T) {
conf.Code = tt.code
names, err := Generate(conf)
if err != nil {
if tt.want {
t.Errorf("Generate() returned an unexpected error: %s", err)
}
return
}
if len(names) != 3 {
t.Errorf("Generate() returned wrong number of results\nExp: %+v\nGot: %+v\n",
conf.Number, len(names))
return
}
for idx, name := range names {
if len(name) == 0 {
t.Errorf("Generate() returned empty results\nIndex: %d\nGot: <%s>\n",
idx, name)
return
}
}
})
}
}

BIN
gfn

Binary file not shown.

5
gfn.conf Normal file
View File

@@ -0,0 +1,5 @@
# example config file
[[Templates]]
morph = "!s(na|ha|ma|va)v"
morphium = "!s(na|ha|ma|va)v(ius|ium|aum|oum|eum)"

6
go.mod
View File

@@ -7,18 +7,24 @@ 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/fsnotify/fsnotify v1.6.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/parsers/toml v0.1.0 // indirect
github.com/knadh/koanf/providers/confmap v0.1.0 // indirect
github.com/knadh/koanf/providers/file 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/pelletier/go-toml v1.9.5 // indirect
github.com/rogpeppe/go-internal v1.12.0 // 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
golang.org/x/tools v0.6.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

13
go.sum
View File

@@ -2,12 +2,18 @@ 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/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
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/parsers/toml v0.1.0 h1:S2hLqS4TgWZYj4/7mI5m1CQQcWurxUz6ODgOub/6LCI=
github.com/knadh/koanf/parsers/toml v0.1.0/go.mod h1:yUprhq6eo3GbyVXFFMdbfZSo928ksS+uo0FFqNMnO18=
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/file v0.1.0 h1:fs6U7nrV58d3CFAFh8VTde8TM262ObYf3ODrc//Lp+c=
github.com/knadh/koanf/providers/file v0.1.0/go.mod h1:rjJ/nHQl64iYCtAW2QQnF0eSmDEX/YZ/eNFj5yR6BvA=
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=
@@ -21,6 +27,10 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1
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/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
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=
@@ -28,11 +38,14 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
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.0.0-20220908164124-27713097b956/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=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
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=

69
main.go
View File

@@ -1,16 +1,43 @@
/*
Copyright © 2024 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"fmt"
"io"
"os"
"runtime/debug"
"log/slog"
"github.com/tlinden/yadu"
)
func main() {
os.Exit(Main(os.Stdout))
}
func TMain() int {
return Main(os.Stdout)
}
func Main(output io.Writer) int {
// parse config file and command line parameters, if any
conf, err := InitConfig(output)
if err != nil {
return Die(err)
@@ -22,25 +49,57 @@ func Main(output io.Writer) int {
return 0
}
// enable debugging, if needed. We only use log/slog for
// debugging, so there's no need to configure it outside debugging
if conf.Debug {
logLevel := &slog.LevelVar{}
// we're using a more verbose logger in debug mode
buildInfo, _ := debug.ReadBuildInfo()
opts := &yadu.Options{
Level: logLevel,
AddSource: true,
}
logLevel.Set(slog.LevelDebug)
handler := yadu.NewHandler(output, opts)
debuglogger := slog.New(handler).With(
slog.Group("program_info",
slog.Int("pid", os.Getpid()),
slog.String("go_version", buildInfo.GoVersion),
),
)
slog.SetDefault(debuglogger)
}
// just show what we have
if conf.Listshortcuts {
ListTemplates(output)
ListTemplates(conf, output)
return 0
}
// code argument is mandatory
if len(conf.Code) == 0 {
fmt.Fprintln(output, Usage)
return 1
}
if Exists(Templates, conf.Code) {
conf.Code = Templates[conf.Code]
// check if we can use a template, otherwise consider the argument
// to be FN code
if Exists(conf.Templates, conf.Code) {
slog.Debug("Argument resolves to template code",
"name", conf.Code, "code", conf.Templates[conf.Code])
conf.Code = conf.Templates[conf.Code]
}
names, err := Generate(conf.Count, conf.Code)
// all prepared, run baby run
names, err := Generate(conf)
if err != nil {
return Die(err)
}
if err = PrintColumns(names, output); err != nil {
if err = PrintColumns(conf, names, output); err != nil {
return Die(err)
}

39
main_test.go Normal file
View File

@@ -0,0 +1,39 @@
/*
Copyright © 2024 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"os"
"testing"
"github.com/rogpeppe/go-internal/testscript"
)
// see https://bitfieldconsulting.com/golang/test-scripts
func TestMain(m *testing.M) {
os.Exit(testscript.RunMain(m, map[string]func() int{
"testgfn": TMain,
}))
}
func TestFfn(t *testing.T) {
testscript.Run(t, testscript.Params{
Dir: "t",
})
}

75
mkrel.sh Executable file
View File

@@ -0,0 +1,75 @@
#!/bin/bash
# Copyright © 2024 Thomas von Dein
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# get list with: go tool dist list
DIST="darwin/amd64
freebsd/amd64
linux/amd64
netbsd/amd64
openbsd/amd64
windows/amd64
freebsd/arm64
linux/arm64
netbsd/arm64
openbsd/arm64
windows/arm64"
tool="$1"
version="$2"
if test -z "$version"; then
echo "Usage: $0 <tool name> <release version>"
exit 1
fi
rm -rf releases
mkdir -p releases
for D in $DIST; do
os=${D/\/*/}
arch=${D/*\//}
binfile="releases/${tool}-${os}-${arch}-${version}"
if test "$os" = "windows"; then
binfile="${binfile}.exe"
fi
tardir="${tool}-${os}-${arch}-${version}"
tarfile="releases/${tool}-${os}-${arch}-${version}.tar.gz"
set -x
GOOS=${os} GOARCH=${arch} go build -tags osusergo,netgo -ldflags "-extldflags=-static" -o ${binfile}
mkdir -p ${tardir}
cp ${binfile} README.md LICENSE ${tardir}/
echo 'tool = gfn
PREFIX = /usr/local
UID = root
GID = 0
install:
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1
install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/
install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/' > ${tardir}/Makefile
tar cpzf ${tarfile} ${tardir}
sha256sum ${binfile} | cut -d' ' -f1 > ${binfile}.sha256
sha256sum ${tarfile} | cut -d' ' -f1 > ${tarfile}.sha256
rm -rf ${tardir}
set +x
done

View File

@@ -1,15 +1,36 @@
/*
Copyright © 2024 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"fmt"
"io"
"log/slog"
"sort"
)
func ListTemplates(output io.Writer) {
// Output a list of hardcoded FN code templates
func ListTemplates(conf *Config, output io.Writer) {
slog.Debug("Listing configured templates", "templates", conf.Templates)
names := []string{}
for name := range Templates {
for name := range conf.Templates {
names = append(names, name)
}
@@ -20,11 +41,12 @@ func ListTemplates(output io.Writer) {
}
}
func PrintColumns(names []string, output io.Writer) error {
// Columnar output
func PrintColumns(conf *Config, names []string, output io.Writer) error {
count := len(names)
// no need for the hassle to calculate columns
if count <= Columns {
if count <= conf.Columns {
for _, name := range names {
fmt.Fprintln(output, name)
}
@@ -33,7 +55,7 @@ func PrintColumns(names []string, output io.Writer) error {
}
// get a transposed list of columns
padlist, max := Getcolumns(names, Columns)
padlist, max := Getcolumns(names, conf.Columns)
// make sure there's enough spacing between the columns
format := fmt.Sprintf("%%-%ds", max+1)

2
t/debug.txtar Normal file
View File

@@ -0,0 +1,2 @@
exec testgfn -d Vs
stdout 'gfn/generate.go:'

2
t/help.txtar Normal file
View File

@@ -0,0 +1,2 @@
exec testgfn -h
stdout 'This is gfn'

2
t/list.txtar Normal file
View File

@@ -0,0 +1,2 @@
exec testgfn -l
stdout 'JapaneseNamesDiverse'

1
t/noarg.txtar Normal file
View File

@@ -0,0 +1 @@
! exec testgfn

2
t/nocode.txtar Normal file
View File

@@ -0,0 +1,2 @@
exec testgfn akx
stdout 'akx'

2
t/nocodeloop.txtar Normal file
View File

@@ -0,0 +1,2 @@
exec testgfn akx
! stdout 'akx akx'

2
t/template.txtar Normal file
View File

@@ -0,0 +1,2 @@
exec testgfn Idiots
stdout 'face'

2
t/valid.txtar Normal file
View File

@@ -0,0 +1,2 @@
exec testgfn sVdmi(usus|hehe)
stdout 'usus'

2
t/version.txtar Normal file
View File

@@ -0,0 +1,2 @@
exec testgfn -v
stdout 'This is gfn version'

17
util.go
View File

@@ -1,3 +1,20 @@
/*
Copyright © 2024 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import "log"

139
util_test.go Normal file
View File

@@ -0,0 +1,139 @@
/*
Copyright © 2024 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"fmt"
"reflect"
"testing"
)
var testexists = []struct {
name string
want bool
key int
hash map[int]int
}{
{
name: "have-key",
want: true,
key: 1,
hash: map[int]int{1: 4, 2: 5},
},
{
name: "miss-key",
want: false,
key: 3,
hash: map[int]int{1: 4, 2: 5},
},
}
func TestExists(t *testing.T) {
for _, tt := range testexists {
testname := fmt.Sprintf("exists-%s", tt.name)
t.Run(testname, func(t *testing.T) {
got := Exists(tt.hash, tt.key)
if got != tt.want {
t.Errorf("Exists() returned wrong result\nExp: %+v\nGot: %+v\n",
tt.want, got)
}
})
}
}
var testcontains = []struct {
name string
want bool
key int
list []int
}{
{
name: "have-item",
want: true,
key: 1,
list: []int{1, 2},
},
{
name: "miss-item",
want: false,
key: 3,
list: []int{1, 2},
},
}
func TestContains(t *testing.T) {
for _, tt := range testcontains {
testname := fmt.Sprintf("contains-%s", tt.name)
t.Run(testname, func(t *testing.T) {
got := Contains(tt.list, tt.key)
if got != tt.want {
t.Errorf("Contains() returned wrong result\nExp: %+v\nGot: %+v\n",
tt.want, got)
}
})
}
}
var testtranspose = []struct {
name string
slice [][]string
expect [][]string
}{
{
name: "2x2-matrix",
slice: [][]string{
{"a1", "a2"},
{"b1", "b2"},
},
expect: [][]string{
{"a1", "b1"},
{"a2", "b2"},
},
},
{
name: "2x3-matrix",
slice: [][]string{
{"a1", "a2", "a3"},
{"b1", "b2", "b3"},
},
expect: [][]string{
{"a1", "b1"},
{"a2", "b2"},
{"a3", "b3"},
},
},
}
func TestTranspose(t *testing.T) {
for _, tt := range testtranspose {
testname := fmt.Sprintf("transpose-%s", tt.name)
t.Run(testname, func(t *testing.T) {
got := Transpose(tt.slice)
if !reflect.DeepEqual(tt.expect, got) {
t.Errorf("Transpose() returned wrong result\nExp: %+v\nGot: %+v\n",
tt.expect, got)
}
})
}
}