From c47b48cd83634b6400076d1d073b28f0f45e13a8 Mon Sep 17 00:00:00 2001 From: Pierre-Henri Symoneaux Date: Wed, 17 Jun 2026 17:17:11 +0200 Subject: [PATCH] refactor: expose explorer as an embeddable library package Move the TUI from internal/ to the module-root explorer package and relocate the CLI entrypoint to cmd/kmip-explorer. --- .github/.goreleaser.yaml | 2 +- .github/workflows/test.yaml | 4 +-- README.md | 42 +++++++++++++++++++++++++++- main.go => cmd/kmip-explorer/main.go | 4 +-- internal/explorer.go => explorer.go | 36 ++++++++++++++++++++++-- 5 files changed, 80 insertions(+), 8 deletions(-) rename main.go => cmd/kmip-explorer/main.go (97%) rename internal/explorer.go => explorer.go (88%) diff --git a/.github/.goreleaser.yaml b/.github/.goreleaser.yaml index 69688e0..6f76492 100644 --- a/.github/.goreleaser.yaml +++ b/.github/.goreleaser.yaml @@ -15,7 +15,7 @@ builds: # main: . id: "kmip-explorer" binary: kmip-explorer - dir: ./ + dir: ./cmd/kmip-explorer archives: - format: tar.gz diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 85f8784..b613660 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -5,7 +5,7 @@ name: test on: push: - branches: [ "main" ] + branches: ["main"] workflow_call: {} permissions: {} @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-build-env - name: Build - run: go build -v -ldflags="-s -w" -trimpath -o build/ . + run: go build -v -ldflags="-s -w" -trimpath -o build/ ./cmd/kmip-explorer lint: runs-on: ubuntu-latest diff --git a/README.md b/README.md index 21de02b..7f874e6 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,47 @@ It supports KMIP from v1.0 up to v1.4. Download the latest release from the [release page](https://github.com/phsym/kmip-explorer/releases/latest) #### Install with go -Run `go install github.com/phsym/kmip-explorer@latest` +Run `go install github.com/phsym/kmip-explorer/cmd/kmip-explorer@latest` + +## Use as a library + +The terminal UI can be embedded into your own application. The root package +exposes a minimal API by design: you build and own a `kmipclient.Client` +(controlling TLS, middlewares and the server address yourself), hand it to +`explorer.New`, then call `Run` to start the UI. + +```go +package main + +import ( + "log" + + "github.com/ovh/kmip-go/kmipclient" + explorer "github.com/phsym/kmip-explorer" +) + +func main() { + client, err := kmipclient.Dial( + "eu-west-rbx.okms.ovh.net:5696", + kmipclient.WithClientCertFiles("cert.pem", "key.pem"), + ) + if err != nil { + log.Fatal(err) + } + defer client.Close() + + // version is shown in the banner; pass "" as latestVersion to disable the + // update hint. + exp := explorer.New(client, "dev", "") + if err := exp.Run(); err != nil { + log.Fatal(err) + } +} +``` + +`Run` takes over the terminal and blocks until the user quits. See the +[package documentation](https://pkg.go.dev/github.com/phsym/kmip-explorer) for +details. ### Run it Display help with `kmip-explorer -h` diff --git a/main.go b/cmd/kmip-explorer/main.go similarity index 97% rename from main.go rename to cmd/kmip-explorer/main.go index 0378ccf..6d5fede 100644 --- a/main.go +++ b/cmd/kmip-explorer/main.go @@ -24,7 +24,7 @@ import ( "strings" "github.com/ovh/kmip-go/kmipclient" - "github.com/phsym/kmip-explorer/internal" + explorer "github.com/phsym/kmip-explorer" "golang.org/x/mod/semver" "flag" @@ -81,7 +81,7 @@ func main() { // tview.Styles.PrimitiveBackgroundColor = tcell.ColorNone client := newClient() - exp := internal.NewExplorer(client, version, latestVersion) + exp := explorer.New(client, version, latestVersion) if err := exp.Run(); err != nil { fmt.Fprintln(os.Stderr, "ERROR:", err) os.Exit(1) diff --git a/internal/explorer.go b/explorer.go similarity index 88% rename from internal/explorer.go rename to explorer.go index ae3599b..3c029d2 100644 --- a/internal/explorer.go +++ b/explorer.go @@ -12,7 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -package internal +// Package explorer provides a terminal UI for browsing and managing KMIP +// objects over a [kmipclient.Client] connection. +// +// It is primarily consumed by the kmip-explorer command, but the TUI can also +// be embedded into another application: construct an [Explorer] with [New] and +// drive it with [Explorer.Run]. The public surface is intentionally minimal — +// the caller owns the KMIP client (TLS, middlewares, server address) and hands +// it to the explorer ready to use. +// +// client, err := kmipclient.Dial(addr, opts...) +// if err != nil { +// log.Fatal(err) +// } +// defer client.Close() +// if err := explorer.New(client, version, "").Run(); err != nil { +// log.Fatal(err) +// } +package explorer import ( "fmt" @@ -44,6 +61,9 @@ const ( attrPanelExpanded = 6 // focused: the user dived into the attributes ) +// Explorer is the KMIP browser terminal application. Create one with [New] and +// start it with [Explorer.Run]. An Explorer is single-use and not safe for +// concurrent use by multiple goroutines. type Explorer struct { app *tview.Application search *tview.InputField @@ -68,7 +88,15 @@ type Explorer struct { client *kmipclient.Client } -func NewExplorer(client *kmipclient.Client, version, latestVersion string) *Explorer { +// New builds an Explorer that operates against the given KMIP client. The +// client must be connected and ready to use; the Explorer does not close it. +// +// version is the application version displayed in the banner; pass an empty +// string to hide the version line entirely. latestVersion is the newest +// version available upstream, used to show an update hint next to the version; +// pass an empty string to disable the hint (it is also suppressed when version +// is empty). The returned Explorer is started with [Explorer.Run]. +func New(client *kmipclient.Client, version, latestVersion string) *Explorer { ex := &Explorer{ client: client, } @@ -341,6 +369,10 @@ func (ex *Explorer) init() { go ex.refresh(true) } +// Run takes over the terminal, draws the UI and blocks until the user quits +// (by pressing 'q') or a fatal error occurs. It returns any error reported by +// the underlying terminal application. Run must be called at most once per +// Explorer. func (ex *Explorer) Run() error { ex.init() defer ex.app.Stop()