This commit is contained in:
2025-05-23 16:10:49 +04:00
parent 3f01074e3f
commit 29d0d25a3b
133 changed files with 346 additions and 168 deletions

View File

@@ -0,0 +1,2 @@
herohandler
example

View File

@@ -0,0 +1,106 @@
# HeroHandler Example
This package demonstrates how to implement and use a handler for HeroScript in the HeroLauncher project.
## Overview
The HeroHandler example provides a simple key-value store implementation that showcases how to:
1. Create a custom handler that extends the base handler functionality
2. Implement action methods that can be called via HeroScript
3. Parse parameters from HeroScript actions
4. Process and execute HeroScript commands
## Project Structure
```
./herohandler/
├── README.md # This documentation file
├── main.go # Main executable that uses the example handler
└── internal/ # Internal package for the example handler implementation
└── example_handler.go # Example handler implementation
```
## Handler Actions
The example handler supports the following actions:
- `example.set`: Store a key-value pair
- Parameters: `key`, `value`
- `example.get`: Retrieve a value by key
- Parameters: `key`
- `example.list`: List all stored key-value pairs
- No parameters
- `example.delete`: Remove a key-value pair
- Parameters: `key`
## Usage
You can run the example handler using the provided `main.go`:
```bash
# Build the binary
cd pkg/heroscript/cmd/herohandler
go build -o herohandler
# Set a key-value pair
./herohandler "example.set key:mykey value:myvalue"
# Get a value by key
./herohandler "example.get key:mykey"
# List all stored key-value pairs
./herohandler "example.list"
# Delete a key-value pair
./herohandler "example.delete key:mykey"
```
### Important Note on State Persistence
The example handler maintains its key-value store in memory only for the duration of a single command execution. Each time you run the `herohandler` command, a new instance of the handler is created with an empty data store. This is by design to keep the example simple.
In a real-world application, you would typically implement persistence using a database, file storage, or other mechanisms to maintain state between command executions.
### Multi-Command Example
To execute multiple commands in a single script, you can create a HeroScript file and pass it to the handler. For example:
```bash
# Create a script file
cat > example.hero << EOF
!!example.set key:user value:john
!!example.set key:role value:admin
!!example.list
EOF
# Run the script
cat example.hero | ./herohandler
```
This would process all commands in a single execution, allowing the in-memory state to be shared between commands.
## Implementation Details
The example handler demonstrates several important concepts:
1. **Handler Structure**: The `ExampleHandler` extends the `handlers.BaseHandler` which provides common functionality for all handlers.
2. **Action Methods**: Each action is implemented as a method on the handler struct (e.g., `Set`, `Get`, `List`, `Delete`).
3. **Parameter Parsing**: The `ParseParams` method from `BaseHandler` is used to extract parameters from HeroScript.
4. **Action Execution**: The `Play` method from `BaseHandler` uses reflection to find and call the appropriate method based on the action name.
5. **In-Memory Storage**: The example handler maintains a simple in-memory key-value store using a map.
## Extending the Example
To create your own handler:
1. Create a new struct that embeds the `handlers.BaseHandler`
2. Implement methods for each action your handler will support
3. Create a constructor function that initializes your handler with the appropriate actor name
4. Use the `Play` method to process HeroScript commands
For more complex handlers, you might need to add additional fields to store state or configuration.

View File

@@ -0,0 +1,7 @@
!!example.set key:username value:johndoe
!!example.set key:email value:john@example.com
!!example.set key:role value:admin
!!example.list
!!example.get key:username
!!example.delete key:email
!!example.list

View File

@@ -0,0 +1,102 @@
package internal
import (
"fmt"
"git.ourworld.tf/herocode/heroagent/pkg/handlerfactory"
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/handlers"
)
// ExampleHandler handles example actions
type ExampleHandler struct {
handlers.BaseHandler
data map[string]string
}
// NewExampleHandler creates a new example handler
func NewExampleHandler() *ExampleHandler {
return &ExampleHandler{
BaseHandler: handlers.BaseHandler{
BaseHandler: handlerfactory.BaseHandler{
ActorName: "example",
},
},
data: make(map[string]string),
}
}
// Set handles the example.set action
func (h *ExampleHandler) Set(script string) string {
params, err := h.BaseHandler.ParseParams(script)
if err != nil {
return fmt.Sprintf("Error parsing parameters: %v", err)
}
key := params.Get("key")
if key == "" {
return "Error: key is required"
}
value := params.Get("value")
if value == "" {
return "Error: value is required"
}
h.data[key] = value
return fmt.Sprintf("Set %s = %s", key, value)
}
// Get handles the example.get action
func (h *ExampleHandler) Get(script string) string {
params, err := h.BaseHandler.ParseParams(script)
if err != nil {
return fmt.Sprintf("Error parsing parameters: %v", err)
}
key := params.Get("key")
if key == "" {
return "Error: key is required"
}
value, exists := h.data[key]
if !exists {
return fmt.Sprintf("Key '%s' not found", key)
}
return fmt.Sprintf("%s = %s", key, value)
}
// List handles the example.list action
func (h *ExampleHandler) List(script string) string {
if len(h.data) == 0 {
return "No data stored"
}
var result string
for key, value := range h.data {
result += fmt.Sprintf("%s = %s\n", key, value)
}
return result
}
// Delete handles the example.delete action
func (h *ExampleHandler) Delete(script string) string {
params, err := h.BaseHandler.ParseParams(script)
if err != nil {
return fmt.Sprintf("Error parsing parameters: %v", err)
}
key := params.Get("key")
if key == "" {
return "Error: key is required"
}
_, exists := h.data[key]
if !exists {
return fmt.Sprintf("Key '%s' not found", key)
}
delete(h.data, key)
return fmt.Sprintf("Deleted key '%s'", key)
}

View File

@@ -0,0 +1,101 @@
package main
import (
"bufio"
"fmt"
"io"
"os"
"strings"
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/cmd/herohandler/internal"
)
func main() {
// Create a new example handler
handler := internal.NewExampleHandler()
// Check if input is coming from stdin (piped input)
stat, _ := os.Stdin.Stat()
if (stat.Mode() & os.ModeCharDevice) == 0 {
// Reading from stdin (pipe or redirect)
processStdin(handler)
return
}
// Check if there are command-line arguments
if len(os.Args) < 2 {
printUsage()
return
}
// Get the command from arguments
command := strings.Join(os.Args[1:], " ")
// Format as proper HeroScript with !! prefix if not already prefixed
script := command
if !strings.HasPrefix(script, "!!") {
script = "!!" + script
}
// Process the script
result, err := handler.Play(script, handler)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
// Print the result
fmt.Println(result)
}
func printUsage() {
fmt.Println("Usage: herohandler <action>")
fmt.Println(" cat script.hero | herohandler")
fmt.Println("\nExample commands:")
fmt.Println(" example.set key:mykey value:myvalue")
fmt.Println(" example.get key:mykey")
fmt.Println(" example.list")
fmt.Println(" example.delete key:mykey")
fmt.Println("\nNote: The command will be automatically formatted as HeroScript with !! prefix.")
fmt.Println(" You can also pipe a multi-line HeroScript file to process multiple commands.")
}
// processStdin reads and processes HeroScript from stdin
func processStdin(handler *internal.ExampleHandler) {
reader := bufio.NewReader(os.Stdin)
var scriptBuilder strings.Builder
// Read all lines from stdin
for {
line, err := reader.ReadString('\n')
if err != nil && err != io.EOF {
fmt.Printf("Error reading from stdin: %v\n", err)
return
}
// Add the line to our script
scriptBuilder.WriteString(line)
// If we've reached EOF, break
if err == io.EOF {
break
}
}
// Process the complete script
script := scriptBuilder.String()
if script == "" {
fmt.Println("Error: Empty script")
return
}
// Process the script
result, err := handler.Play(script, handler)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
// Print the result
fmt.Println(result)
}