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
1 change: 1 addition & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ linters:
# See the comment on top of this file.
enable:
- errcheck
- gosec
146 changes: 4 additions & 142 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,151 +4,13 @@
package main

import (
"encoding/json"
"errors"
"io"
"os"
"strings"

"tcli/internal/cmd"
"tcli/internal/common"
"tcli/internal/config"
"tcli/internal/parser"
)

const (
cmdModules = 1
cmdCommands = 2
cmdCommand = 3

//
posModule = 1
posCommand = 2
posSubCommand = 3
)

type cmdState struct {
state int
module string
cmd string
subCmd string
args []string
}

type module struct {
Tag parser.Tag
}

const (
defaultInput = "data/example.json"
envOpenApiFile = "OPENAPI_FILE"
)

var (
modules []module
argc = len(os.Args)
state = getCmdState()
"tcli/internal/env"
)

// main is the entry point for the tcli application.
func main() {
var err error
if !config.Load() {
panic("config load failed")
}
// handle command line args
switch state.state {
case cmdModules:
config.ShowModules()
case cmdCommands:
err = config.ShowCommands(state.module)
case cmdCommand:
err = call(config.ShowCommand(state.module, state.cmd, state.subCmd))
}
handleError(err)
}

func call(m *parser.Method, err error) error {
if err != nil {
return err
}

env := cmd.GetExecutionEnv(state.args)

// read from stdin and feed to commands
if hasStdin() {
input := json.NewDecoder(os.Stdin)
var any common.Input
for err == nil {
err = input.Decode(&any)
if err == io.EOF {
break
}
err = env.Exec(m, &any)
}
} else {
err = env.Exec(m, nil)
}
if err != nil {
return err
}
return env.Wait()
}

func getCmdState() cmdState {
if argc < 2 {
return cmdState{state: cmdModules}
} else if argc < 3 {
return cmdState{state: cmdCommands, module: os.Args[posModule]}
} else {
subCmd := getSubCmd()
args := os.Args[3:]
if subCmd != "" {
args = os.Args[4:]
}
return cmdState{
state: cmdCommand,
module: os.Args[posModule],
cmd: os.Args[posCommand],
subCmd: subCmd,
args: args}
}
}

// subcommands are present when an api has a tagged set of commands
// this will be the final level of supported drill in for commands
func getSubCmd() string {
if argc > posSubCommand {
temp := os.Args[posSubCommand]
if !strings.HasPrefix(temp, "-") {
return temp
}
}
return ""
}

// Attempt to avoid specifying a flag for stdin
// check if there is something readable at stdin
// this is not tested beyond minimal redirection
// it is tested in windows as well but there could be
// corner cases when it doesnt work. If that is the case
// it's better to specify a flag to indicate input
func hasStdin() bool {
fi, err := os.Stdin.Stat()
if err != nil {
return false
}
return fi.Mode()&os.ModeCharDevice == 0
}

func handleError(err error) {
if err == nil {
return
}
if errors.Is(err, common.ErrNoSuchModule) {
config.ShowModules()
} else if errors.Is(err, common.ErrNoSuchCommand) {
err = config.ShowCommands(state.module)
} else if errors.Is(err, common.ErrNoSuchSubCommand) {
config.ShowTaggedCommands(state.cmd)
if err := env.Run(); err != nil {
os.Exit(1)
}
}
2 changes: 1 addition & 1 deletion internal/cmd/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (c *HttpCommand) http() error {
utils.AddAuthorizationHeader(req, *p.Values[JwtParam])
}

client := utils.RetriableClient(p.Global.RetryCount)
client := utils.RetriableClient(int64(p.Global.RetryCount))
resp, err := client.Do(req)
if err != nil {
log.Fatalf("Response error: %v\n", err)
Expand Down
13 changes: 11 additions & 2 deletions internal/cmd/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const GlobalFlags = "global"

type GlobalResult struct {
Count uint
RetryCount uint
RetryCount int64
BasePath string
Scheme string
Server string
Expand All @@ -47,6 +47,8 @@ type paramsResult struct {
headers http.Header
}

// New creates a new ParseResult and initializes the flag set
// with the parameters from the given method and global flags.
func New(o *parser.Method, in *common.Input, r *parser.Root) *ParseResult {
fs := flag.NewFlagSet(o.OperationId, flag.ExitOnError)

Expand All @@ -71,6 +73,8 @@ func New(o *parser.Method, in *common.Input, r *parser.Root) *ParseResult {
return &p
}

// getParamVal returns the value of the parameter from the input map
// if it exists, otherwise it returns the default value of the parameter.
func getParamVal(p *parser.Parameter, i *common.Input) string {
if i != nil {
if input, ok := (*i)[p.Name]; ok {
Expand All @@ -83,12 +87,13 @@ func getParamVal(p *parser.Parameter, i *common.Input) string {
return p.DefaultStr()
}

// getGlobal initializes the global flags and returns a GlobalResult.
func getGlobal(fs *flag.FlagSet, r *parser.Root) *GlobalResult {
g := GlobalResult{}
fs.StringVar(&g.BasePath, "base_path", getBasePath(r), "http base path")
fs.StringVar(&g.Doc, "doc", "none", "Generate docs (none, shell)")
fs.StringVar(&g.Format, "format", "", "json format")
fs.UintVar(&g.RetryCount, "retry_count", 10, "Number of retries on failure")
fs.Int64Var(&g.RetryCount, "retry_count", 10, "Number of retries on failure")
fs.StringVar(&g.Scheme, "scheme", getScheme(r), "Scheme")
fs.StringVar(&g.Server, "server", getHost(r), "Server")
fs.UintVar(&g.Count, "count", 1, "Number of times to repeat command")
Expand Down Expand Up @@ -124,6 +129,8 @@ func getScheme(r *parser.Root) string {
}
}

// ValidateParams checks that all required parameters have values
// and sets global configuration options based on the parsed values.
func (p *ParseResult) ValidateParams() error {
for _, v := range p.Method.Parameters {
val, ok := p.Values[v.Name]
Expand All @@ -143,6 +150,8 @@ func (p *ParseResult) ValidateParams() error {
return nil
}

// getParamValues processes the parameters and returns a paramsResult
// containing the URL values, path, body, and headers for the HTTP request.
func (p *ParseResult) getParamValues() (*paramsResult, error) {
result := paramsResult{
urlValues: &url.Values{},
Expand Down
153 changes: 153 additions & 0 deletions internal/env/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Copyright 2025 HP Development Company, L.P.
// SPDX-License-Identifier: MIT

package env

import (
"encoding/json"
"errors"
"io"
"os"
"strings"

"tcli/internal/cmd"
"tcli/internal/common"
"tcli/internal/config"
"tcli/internal/parser"
)

const (
cmdModules = 1
cmdCommands = 2
cmdCommand = 3

//
posModule = 1
posCommand = 2
posSubCommand = 3
)

type cmdState struct {
state int
module string
cmd string
subCmd string
args []string
}

type module struct {
Tag parser.Tag
}

var (
modules []module
argc = len(os.Args)
state = getCmdState()
)

// Run is the main entry point for executing commands based on command line arguments
func Run() error {
var err error
if !config.Load() {
return errors.New("config load failed")
}
// handle command line args
switch state.state {
case cmdModules:
config.ShowModules()
case cmdCommands:
err = config.ShowCommands(state.module)
case cmdCommand:
err = call(config.ShowCommand(state.module, state.cmd, state.subCmd))
}
return handleError(err)
}

// call executes the given method with the provided error handling
func call(m *parser.Method, err error) error {
if err != nil {
return err
}

env := cmd.GetExecutionEnv(state.args)

// read from stdin and feed to commands
if hasStdin() {
input := json.NewDecoder(os.Stdin)
var any common.Input
for err == nil {
err = input.Decode(&any)
if err == io.EOF {
break
}
err = env.Exec(m, &any)
}
} else {
err = env.Exec(m, nil)
}
return env.Wait()
}

// getCmdState determines the current command state based on command line arguments
func getCmdState() cmdState {
if argc < 2 {
return cmdState{state: cmdModules}
} else if argc < 3 {
return cmdState{state: cmdCommands, module: os.Args[posModule]}
} else {
subCmd := getSubCmd()
args := os.Args[3:]
if subCmd != "" {
args = os.Args[4:]
}
return cmdState{
state: cmdCommand,
module: os.Args[posModule],
cmd: os.Args[posCommand],
subCmd: subCmd,
args: args}
}
}

// subcommands are present when an api has a tagged set of commands
// this will be the final level of supported drill in for commands
func getSubCmd() string {
if argc > posSubCommand {
temp := os.Args[posSubCommand]
if !strings.HasPrefix(temp, "-") {
return temp
}
}
return ""
}

// Attempt to avoid specifying a flag for stdin
// check if there is something readable at stdin
// this is not tested beyond minimal redirection
// it is tested in windows as well but there could be
// corner cases when it doesnt work. If that is the case
// it's better to specify a flag to indicate input
func hasStdin() bool {
fi, err := os.Stdin.Stat()
if err != nil {
return false
}
return fi.Mode()&os.ModeCharDevice == 0
}

// handleError processes errors and displays appropriate information
func handleError(err error) error {
if err == nil {
return nil
}
if errors.Is(err, common.ErrNoSuchModule) {
config.ShowModules()
} else if errors.Is(err, common.ErrNoSuchCommand) {
err = config.ShowCommands(state.module)
} else if errors.Is(err, common.ErrNoSuchSubCommand) {
config.ShowTaggedCommands(state.cmd)
} else {
return err
}
return nil
}
Loading