First commit of golang CLI.
This commit is contained in:
130
wild-cli/internal/external/base.go
vendored
Normal file
130
wild-cli/internal/external/base.go
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
package external
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Tool represents an external command-line tool
|
||||
type Tool interface {
|
||||
Name() string
|
||||
BinaryName() string
|
||||
IsInstalled() bool
|
||||
Version() (string, error)
|
||||
Execute(ctx context.Context, args ...string) ([]byte, error)
|
||||
ExecuteWithInput(ctx context.Context, input string, args ...string) ([]byte, error)
|
||||
}
|
||||
|
||||
// BaseTool provides common functionality for external tools
|
||||
type BaseTool struct {
|
||||
name string
|
||||
binaryName string
|
||||
binaryPath string
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// NewBaseTool creates a new base tool
|
||||
func NewBaseTool(name, binaryName string) *BaseTool {
|
||||
return &BaseTool{
|
||||
name: name,
|
||||
binaryName: binaryName,
|
||||
timeout: 5 * time.Minute, // Default timeout
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the tool name
|
||||
func (t *BaseTool) Name() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
// BinaryName returns the binary name
|
||||
func (t *BaseTool) BinaryName() string {
|
||||
return t.binaryName
|
||||
}
|
||||
|
||||
// IsInstalled checks if the tool is available in PATH
|
||||
func (t *BaseTool) IsInstalled() bool {
|
||||
if t.binaryPath == "" {
|
||||
path, err := exec.LookPath(t.binaryName)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
t.binaryPath = path
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Version returns the tool version
|
||||
func (t *BaseTool) Version() (string, error) {
|
||||
if !t.IsInstalled() {
|
||||
return "", fmt.Errorf("tool %s not installed", t.name)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
output, err := t.Execute(ctx, "--version")
|
||||
if err != nil {
|
||||
// Try alternative version flags
|
||||
output, err = t.Execute(ctx, "version")
|
||||
if err != nil {
|
||||
output, err = t.Execute(ctx, "-v")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("getting version: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
// Execute runs the tool with given arguments
|
||||
func (t *BaseTool) Execute(ctx context.Context, args ...string) ([]byte, error) {
|
||||
return t.ExecuteWithInput(ctx, "", args...)
|
||||
}
|
||||
|
||||
// ExecuteWithInput runs the tool with stdin input
|
||||
func (t *BaseTool) ExecuteWithInput(ctx context.Context, input string, args ...string) ([]byte, error) {
|
||||
if !t.IsInstalled() {
|
||||
return nil, fmt.Errorf("tool %s not installed", t.name)
|
||||
}
|
||||
|
||||
// Create command with timeout
|
||||
ctx, cancel := context.WithTimeout(ctx, t.timeout)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, t.binaryPath, args...)
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if input != "" {
|
||||
cmd.Stdin = bytes.NewBufferString(input)
|
||||
}
|
||||
|
||||
// Set environment
|
||||
cmd.Env = os.Environ()
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("executing %s %v: %w\nstderr: %s",
|
||||
t.name, args, err, stderr.String())
|
||||
}
|
||||
|
||||
return stdout.Bytes(), nil
|
||||
}
|
||||
|
||||
// SetTimeout sets the execution timeout
|
||||
func (t *BaseTool) SetTimeout(timeout time.Duration) {
|
||||
t.timeout = timeout
|
||||
}
|
||||
|
||||
// SetBinaryPath explicitly sets the binary path (useful for testing)
|
||||
func (t *BaseTool) SetBinaryPath(path string) {
|
||||
t.binaryPath = path
|
||||
}
|
Reference in New Issue
Block a user