Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,42 @@ If you are still interested, please go through [docs](/docs/build_and_run.md) to
combine `tcli` for meaningful tests.
To find even more sophisticated uses of `tcli`, see [stress test with tcli](/docs/stress_test.md)

### Using tcli as a library

`tcli`'s reusable packages (spec parsing, config/module loading, and command
execution) live under `pkg/` and can be imported directly by other Go
modules — no need to copy source files into your own repository:

```go
import (
"github.com/hpinc/tcli/pkg/cmd"
"github.com/hpinc/tcli/pkg/config"
"github.com/hpinc/tcli/pkg/env"
"github.com/hpinc/tcli/pkg/parser"
)
```

Custom extension commands (like the `sqs`/`sns` example under
[`examples/_pubsub`](/examples/_pubsub)) no longer need to be copied into
`internal/cmd`. Instead, implement `cmd.Command` in your own package and
register it with `cmd.RegisterCommand` from an `init()`, then blank-import
that package from your `main`:

```go
package mypubsub

func init() {
cmd.RegisterCommand("sqs", &SqsCommand{})
}
```

```go
// in your main package
import _ "example.com/myclient/mypubsub"
```

See [`examples/_pubsub`](/examples/_pubsub) for a full working example.

#### Areas we need help with

- testing
Expand Down
2 changes: 1 addition & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package main
import (
"os"

"github.com/hpinc/tcli/internal/env"
"github.com/hpinc/tcli/pkg/env"
)

// main is the entry point for the tcli application.
Expand Down
31 changes: 27 additions & 4 deletions examples/_pubsub/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,40 @@ This example shows how to extend `tcli` to support aws `sns` and `sqs`
- [pubsub spec](/examples/_pubsub/pubsub.json)
- [pubsub cmd support](/examples/_pubsub/cmd)

Now that tcli's reusable packages live under `pkg/` (instead of `internal/`),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not able to understand the example of sns-sqs.

Please update example of petstore as well

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point.
I have updated the example from a new project perspective. A full working example will require the current changes to go in first as the dependencies will not properly resolve without these changes in main. I will provide steps to test locally here (this will not apply as is when this branch is merged so not making it part of README)
If you want to check out how this works locally, you will have to redirect the ref locally. Assuming you create a new go project and create main.go as shown in example.
Do the following to get that project in a testable state for pubsub extensions

go mod init tcli/pubsubtest

edit go.mod to add the following (assumes you create example in the same parent folder as this project. otherwise, change redirect as applies)

go 1.26.5

require github.com/hpinc/tcli v0.0.0
replace github.com/hpinc/tcli => ../tcli

run go mod tidy and you should be able to run go run main.go now with proper config override.

petstore does not need changes. It's usage example will continue to work without changes.
Note that the usage as a lib is primarily aimed at extending tcli functionality via the x-extension mechanism to support custom commands.

`sqs`/`sns` support ships as its own self-contained Go package
(`examples/_pubsub/cmd`, `package pubsub`) that registers itself with
`cmd.RegisterCommand` — no copying source files into tcli's tree required.

To add pubsub as a module, do the following
- add the module definition from modules.yaml to your tools/modules.yaml
- copy `pubsub.json` to `tools/data`
- copy files under `cmd` to `internal/cmd`
- copy files under `common` to `internal/common`
- blank-import the `pubsub` package as shown below

```go
package main

import (
"os"

// Blank-import registers the "file" command class with tcli's command
// registry via its init().
_ "github.com/hpinc/tcli/examples/_pubsub/cmd" // registers sqs/sns
"github.com/hpinc/tcli/pkg/env"
)

func main() {
if err := env.Run(); err != nil {
os.Exit(1)
}
}
```

### Test it out

List supported commands
```console
$ TCLI_CONFIG_ROOT=tools go run cmd/main.go pubsub
$ TCLI_CONFIG_ROOT=tools go run main.go pubsub
Please specify a command. Supported commands are:
- send_sns send sns message

Expand All @@ -25,7 +48,7 @@ Please specify a command. Supported commands are:

Get help on commands
```console
$ TCLI_CONFIG_ROOT=tools go run cmd/main.go pubsub send_sns -help
$ TCLI_CONFIG_ROOT=tools go run main.go pubsub send_sns -help
Usage of send_sns:
-arn string
arn (default "arn:aws:sns:us-east-1:000000000000:dev")
Expand Down
24 changes: 12 additions & 12 deletions examples/_pubsub/cmd/sns.go
Original file line number Diff line number Diff line change
@@ -1,45 +1,45 @@
// Copyright 2025 HP Development Company, L.P.
// SPDX-License-Identifier: MIT

package cmd
package pubsub

import (
"log"
"fmt"

"github.com/hpinc/tcli/internal/common"
pubsubcommon "github.com/hpinc/tcli/examples/_pubsub/common"
"github.com/hpinc/tcli/pkg/cmd"
)

func init() {
cmds["sns"] = &SnsCommand{}
cmd.RegisterCommand("sns", &SnsCommand{})
}

type SnsCommand struct {
CmdBase
cmd.CmdBase
Name string
Data string
Arn string
Endpoint string
}

func (c *SnsCommand) Init(p *ParseResult) Command {
func (c *SnsCommand) Init(p *cmd.ParseResult) cmd.Command {
k := SnsCommand{}
k.Endpoint = *p.Values["endpoint"]
k.Data = *p.Values["data"]
k.Arn = *p.Values["arn"]
k.Name = *p.Values["name"]
k.baseInit(p, k.send)
k.InitBase(p, k.send)
return &k
}

// Function to publish the event to SNS
func (c *SnsCommand) send() error {
cli, err := common.NewSNSClient(c.Endpoint)
cli, err := pubsubcommon.NewSNSClient(c.Endpoint)
if err != nil {
log.Fatalf("Publish to sns failed: %v\n", err)
return fmt.Errorf("publish to sns failed: %w", err)
}
err = cli.Publish(c.Arn, c.Data, c.Name)
if err != nil {
log.Fatalf("Publish to sns failed: %v\n", err)
if err := cli.Publish(c.Arn, c.Data, c.Name); err != nil {
return fmt.Errorf("publish to sns failed: %w", err)
}
return nil
}
35 changes: 21 additions & 14 deletions examples/_pubsub/cmd/sqs.go
Original file line number Diff line number Diff line change
@@ -1,44 +1,51 @@
// Copyright 2025 HP Development Company, L.P.
// SPDX-License-Identifier: MIT

package cmd
// Package pubsub demonstrates adding a custom extension command to tcli
// without vendoring or copying files into the tcli module itself. It
// imports tcli's public pkg/cmd package and self-registers via
// cmd.RegisterCommand. Consumers only need to blank-import this package
// (e.g. `import _ "example.com/myclient/pubsub"`) from their main so its
// init() runs.
package pubsub

import (
"log"
"fmt"

"github.com/hpinc/tcli/internal/common"
pubsubcommon "github.com/hpinc/tcli/examples/_pubsub/common"
"github.com/hpinc/tcli/pkg/cmd"
tcommon "github.com/hpinc/tcli/pkg/common"
)

func init() {
cmds["sqs"] = &SqsCommand{}
cmd.RegisterCommand("sqs", &SqsCommand{})
}

type SqsCommand struct {
CmdBase
cmd.CmdBase
Data string
Url string
Endpoint string
}

func (c *SqsCommand) Init(p *ParseResult) Command {
func (c *SqsCommand) Init(p *cmd.ParseResult) cmd.Command {
k := SqsCommand{}
k.Endpoint = *p.Values["endpoint"]
k.Data = *p.Values["data"]
k.Url = *p.Values["queue_url"]
k.baseInit(p, k.send)
k.InitBase(p, k.send)
return &k
}

// Function to publish the event to SNS
// Function to publish the event to SQS
func (c *SqsCommand) send() error {
data := common.GetJsonString(c.Data)
cli, err := common.NewSQSClient(c.Endpoint)
data := tcommon.GetJsonString(c.Data)
cli, err := pubsubcommon.NewSQSClient(c.Endpoint)
if err != nil {
log.Fatalf("Publish to sqs failed: %v\n", err)
return fmt.Errorf("publish to sqs failed: %w", err)
}
err = cli.Send(c.Url, data)
if err != nil {
log.Fatalf("Publish to sqs failed: %v\n", err)
if err := cli.Send(c.Url, data); err != nil {
return fmt.Errorf("publish to sqs failed: %w", err)
}
return nil
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/hpinc/tcli

go 1.26.4
go 1.26.5

require (
github.com/itchyny/gojq v0.12.19
Expand Down
24 changes: 0 additions & 24 deletions internal/cmd/base.go

This file was deleted.

31 changes: 0 additions & 31 deletions internal/cmd/command.go

This file was deleted.

49 changes: 49 additions & 0 deletions pkg/cmd/base.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2025 HP Development Company, L.P.
// SPDX-License-Identifier: MIT

package cmd

type CmdBase struct {
p *ParseResult
global *GlobalResult
runFunc fnRun
}

func (c *CmdBase) baseInit(p *ParseResult, f fnRun) {
c.p = p
c.global = p.Global
c.runFunc = f
}

// InitBase initializes a CmdBase from outside this package. Custom Command
// implementations registered via RegisterCommand should call this from
// their Init method instead of reimplementing CmdBase's plumbing:
//
// func (c *MyCommand) Init(p *cmd.ParseResult) cmd.Command {
// k := MyCommand{}
// k.InitBase(p, k.run)
// return &k
// }
func (c *CmdBase) InitBase(p *ParseResult, f func() error) {
c.baseInit(p, f)
}

// Params returns the parsed parameter values for this command, so external
// Command implementations can read parameters without needing direct
// access to CmdBase's unexported fields.
func (c *CmdBase) Params() *ParseResult {
return c.p
}

// Global returns the parsed global flag values for this command.
func (c *CmdBase) Global() *GlobalResult {
return c.global
}

func (c *CmdBase) GetBase() *CmdBase {
return c
}

func (c *CmdBase) Execute() error {
return nil
}
54 changes: 54 additions & 0 deletions pkg/cmd/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2025 HP Development Company, L.P.
// SPDX-License-Identifier: MIT

package cmd

import (
"github.com/hpinc/tcli/pkg/common"
"github.com/hpinc/tcli/pkg/config"
)

// hold commands in a module
type commands map[string]Command
type fnRun func() error

type Command interface {
Init(*ParseResult) Command
Execute() error
GetBase() *CmdBase
}

var (
cmds = make(commands)
log = config.GetLogger()
)

func GetCommand(name string) (Command, error) {
if c, ok := cmds[name]; ok {
return c, nil
}
return nil, common.ErrNoSuchCommand
}

// RegisterCommand registers a Command implementation under the given
// extension class name (the value returned by Method.GetExtensionClass(),
// which maps to the "class" field of an operation's extension in the
// OpenAPI spec).
//
// This lets a program that imports tcli as a library add its own command
// types (e.g. for publishing to a queue or topic) without needing to copy
// source files into this package. Call it from an init() in your own
// package, then blank-import that package from your main so the init()
// runs:
//
// package mypubsub
//
// func init() {
// cmd.RegisterCommand("sqs", &SqsCommand{})
// }
//
// // in your main package
// import _ "example.com/myclient/mypubsub"
func RegisterCommand(name string, c Command) {
cmds[name] = c
}
Loading
Loading