Skip to content

Commit 41e53e8

Browse files
ppadmavilasomPriyesh Padmavilasom
andauthored
Changes to consume tcli as a library (#14)
* Changes to consume tcli as a library - Allows extensions as shown under examples/_pubsub to implement your own commands. * update versions * update pubsub example --------- Co-authored-by: Priyesh Padmavilasom <priyesh.padmavilasom@hp.com>
1 parent 459b284 commit 41e53e8

38 files changed

Lines changed: 237 additions & 119 deletions

‎README.md‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,42 @@ If you are still interested, please go through [docs](/docs/build_and_run.md) to
6262
combine `tcli` for meaningful tests.
6363
To find even more sophisticated uses of `tcli`, see [stress test with tcli](/docs/stress_test.md)
6464

65+
### Using tcli as a library
66+
67+
`tcli`'s reusable packages (spec parsing, config/module loading, and command
68+
execution) live under `pkg/` and can be imported directly by other Go
69+
modules — no need to copy source files into your own repository:
70+
71+
```go
72+
import (
73+
"github.com/hpinc/tcli/pkg/cmd"
74+
"github.com/hpinc/tcli/pkg/config"
75+
"github.com/hpinc/tcli/pkg/env"
76+
"github.com/hpinc/tcli/pkg/parser"
77+
)
78+
```
79+
80+
Custom extension commands (like the `sqs`/`sns` example under
81+
[`examples/_pubsub`](/examples/_pubsub)) no longer need to be copied into
82+
`internal/cmd`. Instead, implement `cmd.Command` in your own package and
83+
register it with `cmd.RegisterCommand` from an `init()`, then blank-import
84+
that package from your `main`:
85+
86+
```go
87+
package mypubsub
88+
89+
func init() {
90+
cmd.RegisterCommand("sqs", &SqsCommand{})
91+
}
92+
```
93+
94+
```go
95+
// in your main package
96+
import _ "example.com/myclient/mypubsub"
97+
```
98+
99+
See [`examples/_pubsub`](/examples/_pubsub) for a full working example.
100+
65101
#### Areas we need help with
66102

67103
- testing

‎cmd/main.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ package main
66
import (
77
"os"
88

9-
"github.com/hpinc/tcli/internal/env"
9+
"github.com/hpinc/tcli/pkg/env"
1010
)
1111

1212
// main is the entry point for the tcli application.

‎examples/_pubsub/README.md‎

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,40 @@ This example shows how to extend `tcli` to support aws `sns` and `sqs`
66
- [pubsub spec](/examples/_pubsub/pubsub.json)
77
- [pubsub cmd support](/examples/_pubsub/cmd)
88

9+
Now that tcli's reusable packages live under `pkg/` (instead of `internal/`),
10+
`sqs`/`sns` support ships as its own self-contained Go package
11+
(`examples/_pubsub/cmd`, `package pubsub`) that registers itself with
12+
`cmd.RegisterCommand` — no copying source files into tcli's tree required.
13+
914
To add pubsub as a module, do the following
1015
- add the module definition from modules.yaml to your tools/modules.yaml
1116
- copy `pubsub.json` to `tools/data`
12-
- copy files under `cmd` to `internal/cmd`
13-
- copy files under `common` to `internal/common`
17+
- blank-import the `pubsub` package as shown below
18+
19+
```go
20+
package main
21+
22+
import (
23+
"os"
24+
25+
// Blank-import registers the "file" command class with tcli's command
26+
// registry via its init().
27+
_ "github.com/hpinc/tcli/examples/_pubsub/cmd" // registers sqs/sns
28+
"github.com/hpinc/tcli/pkg/env"
29+
)
30+
31+
func main() {
32+
if err := env.Run(); err != nil {
33+
os.Exit(1)
34+
}
35+
}
36+
```
1437

1538
### Test it out
1639

1740
List supported commands
1841
```console
19-
$ TCLI_CONFIG_ROOT=tools go run cmd/main.go pubsub
42+
$ TCLI_CONFIG_ROOT=tools go run main.go pubsub
2043
Please specify a command. Supported commands are:
2144
- send_sns send sns message
2245

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

2649
Get help on commands
2750
```console
28-
$ TCLI_CONFIG_ROOT=tools go run cmd/main.go pubsub send_sns -help
51+
$ TCLI_CONFIG_ROOT=tools go run main.go pubsub send_sns -help
2952
Usage of send_sns:
3053
-arn string
3154
arn (default "arn:aws:sns:us-east-1:000000000000:dev")

‎examples/_pubsub/cmd/sns.go‎

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,45 @@
11
// Copyright 2025 HP Development Company, L.P.
22
// SPDX-License-Identifier: MIT
33

4-
package cmd
4+
package pubsub
55

66
import (
7-
"log"
7+
"fmt"
88

9-
"github.com/hpinc/tcli/internal/common"
9+
pubsubcommon "github.com/hpinc/tcli/examples/_pubsub/common"
10+
"github.com/hpinc/tcli/pkg/cmd"
1011
)
1112

1213
func init() {
13-
cmds["sns"] = &SnsCommand{}
14+
cmd.RegisterCommand("sns", &SnsCommand{})
1415
}
1516

1617
type SnsCommand struct {
17-
CmdBase
18+
cmd.CmdBase
1819
Name string
1920
Data string
2021
Arn string
2122
Endpoint string
2223
}
2324

24-
func (c *SnsCommand) Init(p *ParseResult) Command {
25+
func (c *SnsCommand) Init(p *cmd.ParseResult) cmd.Command {
2526
k := SnsCommand{}
2627
k.Endpoint = *p.Values["endpoint"]
2728
k.Data = *p.Values["data"]
2829
k.Arn = *p.Values["arn"]
2930
k.Name = *p.Values["name"]
30-
k.baseInit(p, k.send)
31+
k.InitBase(p, k.send)
3132
return &k
3233
}
3334

3435
// Function to publish the event to SNS
3536
func (c *SnsCommand) send() error {
36-
cli, err := common.NewSNSClient(c.Endpoint)
37+
cli, err := pubsubcommon.NewSNSClient(c.Endpoint)
3738
if err != nil {
38-
log.Fatalf("Publish to sns failed: %v\n", err)
39+
return fmt.Errorf("publish to sns failed: %w", err)
3940
}
40-
err = cli.Publish(c.Arn, c.Data, c.Name)
41-
if err != nil {
42-
log.Fatalf("Publish to sns failed: %v\n", err)
41+
if err := cli.Publish(c.Arn, c.Data, c.Name); err != nil {
42+
return fmt.Errorf("publish to sns failed: %w", err)
4343
}
4444
return nil
4545
}

‎examples/_pubsub/cmd/sqs.go‎

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,51 @@
11
// Copyright 2025 HP Development Company, L.P.
22
// SPDX-License-Identifier: MIT
33

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

612
import (
7-
"log"
13+
"fmt"
814

9-
"github.com/hpinc/tcli/internal/common"
15+
pubsubcommon "github.com/hpinc/tcli/examples/_pubsub/common"
16+
"github.com/hpinc/tcli/pkg/cmd"
17+
tcommon "github.com/hpinc/tcli/pkg/common"
1018
)
1119

1220
func init() {
13-
cmds["sqs"] = &SqsCommand{}
21+
cmd.RegisterCommand("sqs", &SqsCommand{})
1422
}
1523

1624
type SqsCommand struct {
17-
CmdBase
25+
cmd.CmdBase
1826
Data string
1927
Url string
2028
Endpoint string
2129
}
2230

23-
func (c *SqsCommand) Init(p *ParseResult) Command {
31+
func (c *SqsCommand) Init(p *cmd.ParseResult) cmd.Command {
2432
k := SqsCommand{}
2533
k.Endpoint = *p.Values["endpoint"]
2634
k.Data = *p.Values["data"]
2735
k.Url = *p.Values["queue_url"]
28-
k.baseInit(p, k.send)
36+
k.InitBase(p, k.send)
2937
return &k
3038
}
3139

32-
// Function to publish the event to SNS
40+
// Function to publish the event to SQS
3341
func (c *SqsCommand) send() error {
34-
data := common.GetJsonString(c.Data)
35-
cli, err := common.NewSQSClient(c.Endpoint)
42+
data := tcommon.GetJsonString(c.Data)
43+
cli, err := pubsubcommon.NewSQSClient(c.Endpoint)
3644
if err != nil {
37-
log.Fatalf("Publish to sqs failed: %v\n", err)
45+
return fmt.Errorf("publish to sqs failed: %w", err)
3846
}
39-
err = cli.Send(c.Url, data)
40-
if err != nil {
41-
log.Fatalf("Publish to sqs failed: %v\n", err)
47+
if err := cli.Send(c.Url, data); err != nil {
48+
return fmt.Errorf("publish to sqs failed: %w", err)
4249
}
4350
return nil
4451
}

‎internal/cmd/base.go‎

Lines changed: 0 additions & 24 deletions
This file was deleted.

‎internal/cmd/command.go‎

Lines changed: 0 additions & 31 deletions
This file was deleted.

‎pkg/cmd/base.go‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2025 HP Development Company, L.P.
2+
// SPDX-License-Identifier: MIT
3+
4+
package cmd
5+
6+
type CmdBase struct {
7+
p *ParseResult
8+
global *GlobalResult
9+
runFunc fnRun
10+
}
11+
12+
func (c *CmdBase) baseInit(p *ParseResult, f fnRun) {
13+
c.p = p
14+
c.global = p.Global
15+
c.runFunc = f
16+
}
17+
18+
// InitBase initializes a CmdBase from outside this package. Custom Command
19+
// implementations registered via RegisterCommand should call this from
20+
// their Init method instead of reimplementing CmdBase's plumbing:
21+
//
22+
// func (c *MyCommand) Init(p *cmd.ParseResult) cmd.Command {
23+
// k := MyCommand{}
24+
// k.InitBase(p, k.run)
25+
// return &k
26+
// }
27+
func (c *CmdBase) InitBase(p *ParseResult, f func() error) {
28+
c.baseInit(p, f)
29+
}
30+
31+
// Params returns the parsed parameter values for this command, so external
32+
// Command implementations can read parameters without needing direct
33+
// access to CmdBase's unexported fields.
34+
func (c *CmdBase) Params() *ParseResult {
35+
return c.p
36+
}
37+
38+
// Global returns the parsed global flag values for this command.
39+
func (c *CmdBase) Global() *GlobalResult {
40+
return c.global
41+
}
42+
43+
func (c *CmdBase) GetBase() *CmdBase {
44+
return c
45+
}
46+
47+
func (c *CmdBase) Execute() error {
48+
return nil
49+
}

‎pkg/cmd/command.go‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2025 HP Development Company, L.P.
2+
// SPDX-License-Identifier: MIT
3+
4+
package cmd
5+
6+
import (
7+
"github.com/hpinc/tcli/pkg/common"
8+
"github.com/hpinc/tcli/pkg/config"
9+
)
10+
11+
// hold commands in a module
12+
type commands map[string]Command
13+
type fnRun func() error
14+
15+
type Command interface {
16+
Init(*ParseResult) Command
17+
Execute() error
18+
GetBase() *CmdBase
19+
}
20+
21+
var (
22+
cmds = make(commands)
23+
log = config.GetLogger()
24+
)
25+
26+
func GetCommand(name string) (Command, error) {
27+
if c, ok := cmds[name]; ok {
28+
return c, nil
29+
}
30+
return nil, common.ErrNoSuchCommand
31+
}
32+
33+
// RegisterCommand registers a Command implementation under the given
34+
// extension class name (the value returned by Method.GetExtensionClass(),
35+
// which maps to the "class" field of an operation's extension in the
36+
// OpenAPI spec).
37+
//
38+
// This lets a program that imports tcli as a library add its own command
39+
// types (e.g. for publishing to a queue or topic) without needing to copy
40+
// source files into this package. Call it from an init() in your own
41+
// package, then blank-import that package from your main so the init()
42+
// runs:
43+
//
44+
// package mypubsub
45+
//
46+
// func init() {
47+
// cmd.RegisterCommand("sqs", &SqsCommand{})
48+
// }
49+
//
50+
// // in your main package
51+
// import _ "example.com/myclient/mypubsub"
52+
func RegisterCommand(name string, c Command) {
53+
cmds[name] = c
54+
}

0 commit comments

Comments
 (0)