diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..f1f36e5
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,31 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: "[bug-report]"
+labels: bug
+assignees: TLINDEN
+
+---
+
+**Description**
+
+
+
+**Steps To Reproduce**
+
+
+
+**Expected behavior**
+
+
+
+**Version information**
+
+
+
+**Additional informations**
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..723c9a3
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,23 @@
+---
+name: Feature request
+about: Suggest a feature
+title: "[feature-request]"
+labels: feature-request
+assignees: TLINDEN
+
+---
+
+**Describtion**
+
+
+
+
+**Version information**
+
+
diff --git a/.github/assets/logo.png b/.github/assets/logo.png
new file mode 100644
index 0000000..d9e46fe
Binary files /dev/null and b/.github/assets/logo.png differ
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000..8b676ca
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -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
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fde216d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+gfn
+coverage.out
+releases
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d4c9e15
--- /dev/null
+++ b/Makefile
@@ -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 .
+
+
+#
+# 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
diff --git a/README.md b/README.md
index 63f7d02..04edc6b 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,10 @@
-# gfn
+# gfn - generate fantasy names for games and stories on the commandline
+
+
+
+[](https://github.com/tlinden/gfnc/actions)
+[](https://github.com/tlinden/gfnc/blob/master/LICENSE)
+[](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]
+Usage: gfn [-vld] [-c ] [-n ]
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!
diff --git a/README.pdf b/README.pdf
new file mode 100644
index 0000000..aa632af
Binary files /dev/null and b/README.pdf differ
diff --git a/config.go b/config.go
index f6b3700..ea3e0ae 100644
--- a/config.go
+++ b/config.go
@@ -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 .
+*/
+
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]
+Usage: gfn [-vld] [-c ] [-n ]
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
}
diff --git a/generate.go b/generate.go
index 38c34a5..65f6fb1 100644
--- a/generate.go
+++ b/generate.go
@@ -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 .
+*/
+
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
diff --git a/generate_test.go b/generate_test.go
new file mode 100644
index 0000000..0cee322
--- /dev/null
+++ b/generate_test.go
@@ -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 .
+*/
+
+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
+ }
+ }
+ })
+ }
+
+}
diff --git a/gfn b/gfn
deleted file mode 100755
index da5f034..0000000
Binary files a/gfn and /dev/null differ
diff --git a/gfn.conf b/gfn.conf
new file mode 100644
index 0000000..0332bee
--- /dev/null
+++ b/gfn.conf
@@ -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)"
+
diff --git a/go.mod b/go.mod
index d04aa73..74a04a6 100644
--- a/go.mod
+++ b/go.mod
@@ -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
)
diff --git a/go.sum b/go.sum
index a065457..03d166e 100644
--- a/go.sum
+++ b/go.sum
@@ -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=
diff --git a/main.go b/main.go
index 2f34eb3..79df29a 100644
--- a/main.go
+++ b/main.go
@@ -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 .
+*/
+
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)
}
diff --git a/main_test.go b/main_test.go
new file mode 100644
index 0000000..bdba295
--- /dev/null
+++ b/main_test.go
@@ -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 .
+*/
+
+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",
+ })
+}
diff --git a/mkrel.sh b/mkrel.sh
new file mode 100755
index 0000000..6a7d03e
--- /dev/null
+++ b/mkrel.sh
@@ -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 .
+
+
+# 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 "
+ 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
+
diff --git a/printer.go b/printer.go
index e8dea56..0191af1 100644
--- a/printer.go
+++ b/printer.go
@@ -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 .
+*/
+
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)
diff --git a/t/debug.txtar b/t/debug.txtar
new file mode 100644
index 0000000..74d2829
--- /dev/null
+++ b/t/debug.txtar
@@ -0,0 +1,2 @@
+exec testgfn -d Vs
+stdout 'gfn/generate.go:'
diff --git a/t/help.txtar b/t/help.txtar
new file mode 100644
index 0000000..6008e83
--- /dev/null
+++ b/t/help.txtar
@@ -0,0 +1,2 @@
+exec testgfn -h
+stdout 'This is gfn'
diff --git a/t/list.txtar b/t/list.txtar
new file mode 100644
index 0000000..fabc8b5
--- /dev/null
+++ b/t/list.txtar
@@ -0,0 +1,2 @@
+exec testgfn -l
+stdout 'JapaneseNamesDiverse'
diff --git a/t/noarg.txtar b/t/noarg.txtar
new file mode 100644
index 0000000..90db264
--- /dev/null
+++ b/t/noarg.txtar
@@ -0,0 +1 @@
+! exec testgfn
diff --git a/t/nocode.txtar b/t/nocode.txtar
new file mode 100644
index 0000000..7f5e724
--- /dev/null
+++ b/t/nocode.txtar
@@ -0,0 +1,2 @@
+exec testgfn akx
+stdout 'akx'
diff --git a/t/nocodeloop.txtar b/t/nocodeloop.txtar
new file mode 100644
index 0000000..7ea9b91
--- /dev/null
+++ b/t/nocodeloop.txtar
@@ -0,0 +1,2 @@
+exec testgfn akx
+! stdout 'akx akx'
diff --git a/t/template.txtar b/t/template.txtar
new file mode 100644
index 0000000..cfeca63
--- /dev/null
+++ b/t/template.txtar
@@ -0,0 +1,2 @@
+exec testgfn Idiots
+stdout 'face'
diff --git a/t/valid.txtar b/t/valid.txtar
new file mode 100644
index 0000000..d4f6c0b
--- /dev/null
+++ b/t/valid.txtar
@@ -0,0 +1,2 @@
+exec testgfn sVdmi(usus|hehe)
+stdout 'usus'
diff --git a/t/version.txtar b/t/version.txtar
new file mode 100644
index 0000000..47a5ff0
--- /dev/null
+++ b/t/version.txtar
@@ -0,0 +1,2 @@
+exec testgfn -v
+stdout 'This is gfn version'
diff --git a/util.go b/util.go
index 4397550..3d7552e 100644
--- a/util.go
+++ b/util.go
@@ -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 .
+*/
+
package main
import "log"
diff --git a/util_test.go b/util_test.go
new file mode 100644
index 0000000..74dded3
--- /dev/null
+++ b/util_test.go
@@ -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 .
+*/
+
+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)
+ }
+ })
+ }
+
+}