...
This commit is contained in:
301
pkg2/heroscript/playbook/playbook.go
Normal file
301
pkg2/heroscript/playbook/playbook.go
Normal file
@@ -0,0 +1,301 @@
|
||||
package playbook
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/paramsparser"
|
||||
)
|
||||
|
||||
// ActionType represents the type of action
|
||||
type ActionType int
|
||||
|
||||
const (
|
||||
ActionTypeUnknown ActionType = iota
|
||||
ActionTypeDAL
|
||||
ActionTypeSAL
|
||||
ActionTypeWAL
|
||||
ActionTypeMacro
|
||||
)
|
||||
|
||||
// Action represents a single action in a heroscript
|
||||
type Action struct {
|
||||
ID int
|
||||
CID string
|
||||
Name string
|
||||
Actor string
|
||||
Priority int
|
||||
Params *paramsparser.ParamsParser
|
||||
Result *paramsparser.ParamsParser
|
||||
ActionType ActionType
|
||||
Comments string
|
||||
Done bool
|
||||
}
|
||||
|
||||
// PlayBook represents a collection of actions
|
||||
type PlayBook struct {
|
||||
Actions []*Action
|
||||
Priorities map[int][]int // key is priority, value is list of action indices
|
||||
OtherText string // text outside of actions
|
||||
Result string
|
||||
NrActions int
|
||||
Done []int
|
||||
}
|
||||
|
||||
// NewAction creates a new action and adds it to the playbook
|
||||
func (p *PlayBook) NewAction(cid, name, actor string, priority int, actionType ActionType) *Action {
|
||||
p.NrActions++
|
||||
action := &Action{
|
||||
ID: p.NrActions,
|
||||
CID: cid,
|
||||
Name: name,
|
||||
Actor: actor,
|
||||
Priority: priority,
|
||||
ActionType: actionType,
|
||||
Params: paramsparser.New(),
|
||||
Result: paramsparser.New(),
|
||||
}
|
||||
p.Actions = append(p.Actions, action)
|
||||
return action
|
||||
}
|
||||
|
||||
// New creates a new PlayBook
|
||||
func New() *PlayBook {
|
||||
return &PlayBook{
|
||||
Actions: make([]*Action, 0),
|
||||
Priorities: make(map[int][]int),
|
||||
NrActions: 0,
|
||||
Done: make([]int, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// NewFromText creates a new PlayBook from heroscript text
|
||||
func NewFromText(text string) (*PlayBook, error) {
|
||||
pb := New()
|
||||
err := pb.AddText(text, 10) // Default priority 10
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pb, nil
|
||||
}
|
||||
|
||||
// String returns the heroscript representation of the action
|
||||
func (a *Action) String() string {
|
||||
out := a.HeroScript()
|
||||
if a.Result != nil && len(a.Result.GetAll()) > 0 {
|
||||
out += "\n\nResult:\n"
|
||||
// Indent the result
|
||||
resultParams := a.Result.GetAll()
|
||||
for k, v := range resultParams {
|
||||
out += " " + k + ": '" + v + "'\n"
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// HeroScript returns the heroscript representation of the action
|
||||
func (a *Action) HeroScript() string {
|
||||
var out strings.Builder
|
||||
|
||||
// Add comments if any
|
||||
if a.Comments != "" {
|
||||
lines := strings.Split(a.Comments, "\n")
|
||||
for _, line := range lines {
|
||||
out.WriteString("// " + line + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Add action type prefix
|
||||
switch a.ActionType {
|
||||
case ActionTypeDAL:
|
||||
out.WriteString("!")
|
||||
case ActionTypeSAL:
|
||||
out.WriteString("!!")
|
||||
case ActionTypeMacro:
|
||||
out.WriteString("!!!")
|
||||
default:
|
||||
out.WriteString("!!") // Default to SAL
|
||||
}
|
||||
|
||||
// Add actor and name
|
||||
if a.Actor != "" {
|
||||
out.WriteString(a.Actor + ".")
|
||||
}
|
||||
out.WriteString(a.Name + " ")
|
||||
|
||||
// Add ID if present
|
||||
if a.ID > 0 {
|
||||
out.WriteString(fmt.Sprintf("id:%d ", a.ID))
|
||||
}
|
||||
|
||||
// Add parameters
|
||||
if a.Params != nil && len(a.Params.GetAll()) > 0 {
|
||||
params := a.Params.GetAll()
|
||||
firstLine := true
|
||||
for k, v := range params {
|
||||
if firstLine {
|
||||
out.WriteString(k + ":'" + v + "'\n")
|
||||
firstLine = false
|
||||
} else {
|
||||
out.WriteString(" " + k + ":'" + v + "'\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// HashKey returns a unique hash for the action
|
||||
func (a *Action) HashKey() string {
|
||||
h := sha1.New()
|
||||
h.Write([]byte(a.HeroScript()))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// HashKey returns a unique hash for the playbook
|
||||
func (p *PlayBook) HashKey() string {
|
||||
h := sha1.New()
|
||||
for _, action := range p.Actions {
|
||||
h.Write([]byte(action.HashKey()))
|
||||
}
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// HeroScript returns the heroscript representation of the playbook
|
||||
func (p *PlayBook) HeroScript(showDone bool) string {
|
||||
var out strings.Builder
|
||||
|
||||
actions, _ := p.ActionsSorted(false)
|
||||
for _, action := range actions {
|
||||
if !showDone && action.Done {
|
||||
continue
|
||||
}
|
||||
out.WriteString(action.HeroScript() + "\n")
|
||||
}
|
||||
|
||||
if p.OtherText != "" {
|
||||
out.WriteString(p.OtherText)
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// ActionsSorted returns the actions sorted by priority
|
||||
func (p *PlayBook) ActionsSorted(prioOnly bool) ([]*Action, error) {
|
||||
var result []*Action
|
||||
|
||||
// If no priorities are set, return all actions
|
||||
if len(p.Priorities) == 0 {
|
||||
return p.Actions, nil
|
||||
}
|
||||
|
||||
// Get all priority numbers and sort them
|
||||
var priorities []int
|
||||
for prio := range p.Priorities {
|
||||
priorities = append(priorities, prio)
|
||||
}
|
||||
sort.Ints(priorities)
|
||||
|
||||
// Add actions in priority order
|
||||
for _, prio := range priorities {
|
||||
if prioOnly && prio > 49 {
|
||||
continue
|
||||
}
|
||||
|
||||
actionIDs := p.Priorities[prio]
|
||||
for _, id := range actionIDs {
|
||||
action, err := p.GetAction(id, "", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, action)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetAction finds an action by ID, actor, or name
|
||||
func (p *PlayBook) GetAction(id int, actor, name string) (*Action, error) {
|
||||
actions, err := p.FindActions(id, actor, name, ActionTypeUnknown)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(actions) == 1 {
|
||||
return actions[0], nil
|
||||
} else if len(actions) == 0 {
|
||||
return nil, fmt.Errorf("couldn't find action with id: %d, actor: %s, name: %s", id, actor, name)
|
||||
} else {
|
||||
return nil, fmt.Errorf("multiple actions found with id: %d, actor: %s, name: %s", id, actor, name)
|
||||
}
|
||||
}
|
||||
|
||||
// FindActions finds actions based on criteria
|
||||
func (p *PlayBook) FindActions(id int, actor, name string, actionType ActionType) ([]*Action, error) {
|
||||
var result []*Action
|
||||
|
||||
for _, a := range p.Actions {
|
||||
// If ID is specified, return only the action with that ID
|
||||
if id != 0 {
|
||||
if a.ID == id {
|
||||
return []*Action{a}, nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Filter by actor if specified
|
||||
if actor != "" && a.Actor != actor {
|
||||
continue
|
||||
}
|
||||
|
||||
// Filter by name if specified
|
||||
if name != "" && a.Name != name {
|
||||
continue
|
||||
}
|
||||
|
||||
// Filter by actionType if specified
|
||||
if actionType != ActionTypeUnknown && a.ActionType != actionType {
|
||||
continue
|
||||
}
|
||||
|
||||
// If the action passes all filters, add it to the result
|
||||
result = append(result, a)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ActionExists checks if an action exists
|
||||
func (p *PlayBook) ActionExists(id int, actor, name string) bool {
|
||||
actions, err := p.FindActions(id, actor, name, ActionTypeUnknown)
|
||||
if err != nil || len(actions) == 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// String returns a string representation of the playbook
|
||||
func (p *PlayBook) String() string {
|
||||
return p.HeroScript(true)
|
||||
}
|
||||
|
||||
// EmptyCheck checks if there are any actions left to execute
|
||||
func (p *PlayBook) EmptyCheck() error {
|
||||
var undoneActions []*Action
|
||||
|
||||
for _, a := range p.Actions {
|
||||
if !a.Done {
|
||||
undoneActions = append(undoneActions, a)
|
||||
}
|
||||
}
|
||||
|
||||
if len(undoneActions) > 0 {
|
||||
return fmt.Errorf("there are actions left to execute: %d", len(undoneActions))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user