feat: add shell scripting with variables
Implemented roadmap #9: Shell scripts execution with variables. Features: - set <name> <value> - Set shell variable - unset <name> - Remove variable - vars - List all variables - Variable substitution in all commands Variable syntax: - $VAR or ${VAR} - Shell variables - $ENV:VARNAME - Environment variables Variables expand before command execution, enabling: - Dynamic key paths: put $BASE/$ID '{"data":"value"}' - Reusable values: set TOKEN xyz && auth $TOKEN - Script parameterization in batch files Example batch script: set USER alice set KEY_PREFIX users put $KEY_PREFIX/$USER '{"name":"$USER"}' get $KEY_PREFIX/$USER Variables are session-scoped and work in both interactive and batch modes. Environment variables can be injected using $ENV:HOME syntax. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ type KVSClient struct {
|
|||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
profiles map[string]Profile
|
profiles map[string]Profile
|
||||||
activeProfile string
|
activeProfile string
|
||||||
|
variables map[string]string // Shell variables for scripting
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewKVSClient creates a new KVS client instance
|
// NewKVSClient creates a new KVS client instance
|
||||||
@@ -25,7 +26,8 @@ func NewKVSClient(baseURL string) *KVSClient {
|
|||||||
httpClient: &http.Client{
|
httpClient: &http.Client{
|
||||||
Timeout: 30 * time.Second,
|
Timeout: 30 * time.Second,
|
||||||
},
|
},
|
||||||
profiles: make(map[string]Profile),
|
profiles: make(map[string]Profile),
|
||||||
|
variables: make(map[string]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -67,6 +67,13 @@ Export/Import:
|
|||||||
--skip-existing - Skip existing keys (default)
|
--skip-existing - Skip existing keys (default)
|
||||||
--overwrite - Overwrite existing keys
|
--overwrite - Overwrite existing keys
|
||||||
|
|
||||||
|
Shell Scripting:
|
||||||
|
set <name> <value> - Set shell variable
|
||||||
|
unset <name> - Remove shell variable
|
||||||
|
vars - List all variables
|
||||||
|
Variables: $VAR or ${VAR}
|
||||||
|
Environment: $ENV:VARNAME
|
||||||
|
|
||||||
Batch Operations:
|
Batch Operations:
|
||||||
batch <file> - Execute commands from file
|
batch <file> - Execute commands from file
|
||||||
source <file> - Alias for batch
|
source <file> - Alias for batch
|
||||||
|
84
cmd_variables.go
Normal file
84
cmd_variables.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// handleSet sets a shell variable
|
||||||
|
func (c *KVSClient) handleSet(args []string) {
|
||||||
|
if len(args) < 2 {
|
||||||
|
fmt.Println(red("Usage: set <name> <value>"))
|
||||||
|
fmt.Println("Example: set USER_ID abc-123")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := args[0]
|
||||||
|
value := strings.Join(args[1:], " ")
|
||||||
|
|
||||||
|
c.variables[name] = value
|
||||||
|
fmt.Printf(green("Set:")+" %s = %s\n", name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleUnset removes a shell variable
|
||||||
|
func (c *KVSClient) handleUnset(args []string) {
|
||||||
|
if len(args) < 1 {
|
||||||
|
fmt.Println(red("Usage: unset <name>"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := args[0]
|
||||||
|
if _, exists := c.variables[name]; exists {
|
||||||
|
delete(c.variables, name)
|
||||||
|
fmt.Println(green("Unset:"), name)
|
||||||
|
} else {
|
||||||
|
fmt.Println(yellow("Variable not set:"), name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleVars lists all shell variables
|
||||||
|
func (c *KVSClient) handleVars(args []string) {
|
||||||
|
if len(c.variables) == 0 {
|
||||||
|
fmt.Println(yellow("No variables set"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(cyan("Shell Variables:"))
|
||||||
|
for name, value := range c.variables {
|
||||||
|
fmt.Printf(" %s = %s\n", name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// expandVariables performs variable substitution on a string
|
||||||
|
// Supports:
|
||||||
|
// - $VAR or ${VAR} for shell variables
|
||||||
|
// - $ENV:VAR for environment variables
|
||||||
|
func (c *KVSClient) expandVariables(input string) string {
|
||||||
|
// Pattern for $VAR or ${VAR}
|
||||||
|
varPattern := regexp.MustCompile(`\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?`)
|
||||||
|
|
||||||
|
// Pattern for $ENV:VAR
|
||||||
|
envPattern := regexp.MustCompile(`\$ENV:([A-Za-z_][A-Za-z0-9_]*)`)
|
||||||
|
|
||||||
|
// First, replace environment variables
|
||||||
|
result := envPattern.ReplaceAllStringFunc(input, func(match string) string {
|
||||||
|
varName := envPattern.FindStringSubmatch(match)[1]
|
||||||
|
if value, exists := os.LookupEnv(varName); exists {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return match // Leave unchanged if not found
|
||||||
|
})
|
||||||
|
|
||||||
|
// Then, replace shell variables
|
||||||
|
result = varPattern.ReplaceAllStringFunc(result, func(match string) string {
|
||||||
|
varName := varPattern.FindStringSubmatch(match)[1]
|
||||||
|
if value, exists := c.variables[varName]; exists {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return match // Leave unchanged if not found
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
9
main.go
9
main.go
@@ -10,6 +10,9 @@ import (
|
|||||||
|
|
||||||
// executeCommand executes a single command line
|
// executeCommand executes a single command line
|
||||||
func (c *KVSClient) executeCommand(line string) bool {
|
func (c *KVSClient) executeCommand(line string) bool {
|
||||||
|
// Expand variables in the line
|
||||||
|
line = c.expandVariables(line)
|
||||||
|
|
||||||
parts := parseCommand(line)
|
parts := parseCommand(line)
|
||||||
if len(parts) == 0 {
|
if len(parts) == 0 {
|
||||||
return true
|
return true
|
||||||
@@ -66,6 +69,12 @@ func (c *KVSClient) executeCommand(line string) bool {
|
|||||||
c.handleImport(args)
|
c.handleImport(args)
|
||||||
case "export-list":
|
case "export-list":
|
||||||
c.handleExportList(args)
|
c.handleExportList(args)
|
||||||
|
case "set":
|
||||||
|
c.handleSet(args)
|
||||||
|
case "unset":
|
||||||
|
c.handleUnset(args)
|
||||||
|
case "vars":
|
||||||
|
c.handleVars(args)
|
||||||
case "batch", "source":
|
case "batch", "source":
|
||||||
c.handleBatch(args, c.executeCommand)
|
c.handleBatch(args, c.executeCommand)
|
||||||
default:
|
default:
|
||||||
|
3
utils.go
3
utils.go
@@ -130,6 +130,9 @@ var completer = readline.NewPrefixCompleter(
|
|||||||
readline.PcItem("export"),
|
readline.PcItem("export"),
|
||||||
readline.PcItem("import"),
|
readline.PcItem("import"),
|
||||||
readline.PcItem("export-list"),
|
readline.PcItem("export-list"),
|
||||||
|
readline.PcItem("set"),
|
||||||
|
readline.PcItem("unset"),
|
||||||
|
readline.PcItem("vars"),
|
||||||
readline.PcItem("batch"),
|
readline.PcItem("batch"),
|
||||||
readline.PcItem("source"),
|
readline.PcItem("source"),
|
||||||
readline.PcItem("help"),
|
readline.PcItem("help"),
|
||||||
|
Reference in New Issue
Block a user