diff --git a/.github/assets/darkmode.png b/.github/assets/darkmode.png
new file mode 100644
index 0000000..c70d903
Binary files /dev/null and b/.github/assets/darkmode.png differ
diff --git a/.github/assets/help.png b/.github/assets/help.png
new file mode 100644
index 0000000..c369a40
Binary files /dev/null and b/.github/assets/help.png differ
diff --git a/.github/assets/light.png b/.github/assets/light.png
new file mode 100644
index 0000000..794f61d
Binary files /dev/null and b/.github/assets/light.png differ
diff --git a/.github/assets/margin.png b/.github/assets/margin.png
new file mode 100644
index 0000000..f1d1a16
Binary files /dev/null and b/.github/assets/margin.png differ
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000..a0ae536
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,34 @@
+name: build-and-test-gfn
+on: [push]
+jobs:
+ build:
+ strategy:
+ matrix:
+ version: [1.24.9]
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ name: Build
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Set up Go ${{ matrix.os }}
+ uses: actions/setup-go@v6
+ with:
+ go-version: '${{ matrix.version }}'
+ id: go
+
+ - name: checkout
+ uses: actions/checkout@v5
+
+ - name: build
+ run: go build
+
+
+ golangci:
+ name: lint
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/setup-go@v6
+ with:
+ go-version: 1.24
+ - uses: actions/checkout@v5
+ - name: golangci-lint
+ uses: golangci/golangci-lint-action@v8
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
new file mode 100644
index 0000000..2cd9ef5
--- /dev/null
+++ b/.github/workflows/release.yaml
@@ -0,0 +1,87 @@
+name: build-release
+on:
+ push:
+ tags:
+ - "v*.*.*"
+
+jobs:
+ release:
+ name: Build Release Assets
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v5
+
+ - name: Set up Go
+ uses: actions/setup-go@v6
+ with:
+ go-version: 1.24.9
+
+ - name: Build the executables
+ run: ./mkrel.sh gfn ${{ github.ref_name}}
+
+ - name: List the executables
+ run: ls -l ./releases
+
+ - name: Upload the binaries
+ uses: svenstaro/upload-release-action@v2
+ with:
+ repo_token: ${{ secrets.GITHUB_TOKEN }}
+ tag: ${{ github.ref_name }}
+ file: ./releases/*
+ file_glob: true
+
+ - name: Build Changelog
+ id: github_release
+ uses: mikepenz/release-changelog-builder-action@v5
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ mode: "PR"
+ configurationJson: |
+ {
+ "template": "#{{CHANGELOG}}\n\n**Full Changelog**: #{{RELEASE_DIFF}}",
+ "pr_template": "- #{{TITLE}} (##{{NUMBER}}) by #{{AUTHOR}}\n#{{BODY}}",
+ "empty_template": "- no changes",
+ "categories": [
+ {
+ "title": "## New Features",
+ "labels": ["add", "feature"]
+ },
+ {
+ "title": "## Bug Fixes",
+ "labels": ["fix", "bug", "revert"]
+ },
+ {
+ "title": "## Documentation Enhancements",
+ "labels": ["doc"]
+ },
+ {
+ "title": "## Refactoring Efforts",
+ "labels": ["refactor"]
+ },
+ {
+ "title": "## Miscellaneus Changes",
+ "labels": []
+ }
+ ],
+ "ignore_labels": [
+ "duplicate", "good first issue", "help wanted", "invalid", "question", "wontfix"
+ ],
+ "label_extractor": [
+ {
+ "pattern": "(.) (.+)",
+ "target": "$1"
+ },
+ {
+ "pattern": "(.) (.+)",
+ "target": "$1",
+ "on_property": "title"
+ }
+ ]
+ }
+
+ - name: Create Release
+ uses: softprops/action-gh-release@v2
+ with:
+ body: ${{steps.github_release.outputs.changelog}}
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..0cd5be0
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,87 @@
+# Copyright © 2025 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 = epuppy
+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:
+ gh release create v$(VERSION) --generate-notes
+
+show-versions: buildlocal
+ @echo "### epuppy 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/mkrel.sh b/mkrel.sh
new file mode 100755
index 0000000..30a6b50
--- /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 = epuppy
+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
+