launcher/commandset.go

151 lines
3.6 KiB
Go
Raw Permalink Normal View History

2023-08-19 09:30:59 +02:00
package main
import (
// External
"gopkg.in/yaml.v2"
// Internal
"launcher/log"
// Standard
"fmt"
"io/ioutil"
"os/exec"
"regexp"
)
// Create reads the file and parses its YAML content.
func CreateCommandSet(fName string) (*CommandSet, error) {
var commandSet CommandSet
fileContent, err := ioutil.ReadFile(fName)
if err != nil {
return nil, err
}
err = yaml.UnmarshalStrict(fileContent, &commandSet)
if err != nil {
return nil, err
}
return &commandSet, nil
}
// Dump marshals the struct and outputs it.
func (cs *CommandSet) Dump() {
if str, err := yaml.Marshal(cs); err != nil {
fmt.Println(err)
} else {
fmt.Printf("%s\n", str)
}
}
// GetCommand returns the commands with names matching the given regex.
func (cs *CommandSet) GetCommands(nameRegex string) ([]*CommandSet, error) {
var results []*CommandSet
re, err := regexp.Compile(nameRegex)
if err != nil {
return []*CommandSet{}, err
}
for _, cmd := range cs.Commands {
if re.MatchString(cmd.Name) {
// A commandset can be dynamic, which needs to run a script that generates YAML items.
if cmd.Dynamic != "" && !cmd.completed {
configData, err := exec.Command("/bin/bash", "-c", cmd.Dynamic).Output()
if err != nil {
if exErr, ok := err.(*exec.ExitError); ok {
return nil, fmt.Errorf("Script:\n%s", exErr.Stderr)
} else {
return nil, err
}
} else {
// Ignore the topmost name and add its commands instead,
// to prevent a forced extra level as we need one toplevel commandset.
var commandSet CommandSet
err = yaml.UnmarshalStrict(configData, &commandSet)
if err != nil {
return nil, err
}
for _, commands := range commandSet.Commands {
cmd.Commands = append(cmd.Commands, commands)
}
log.Print("dynamic data: %s", configData)
cmd.completed = true
}
}
results = append(results, cmd)
}
}
return results, nil
}
// Search takes a search string, splits it and iteratively walks along the
// provided commandset.
func (cs *CommandSet) Search(search string) ([]SearchResult, error) {
// Regex is much more flexible to search by than just string including
// searched text. It also makes it possible to use ^ and $ when search
// terms isn't conclusive.
re := regexp.MustCompile("\\s+")
words := re.Split(search, -1)
if words[len(words)-1] == "" {
words = words[:len(words)-1]
}
var results []SearchResult
currCommandset := cs
// We need to return everything when no search term is provided,
// as the search term is more akin to a filter.
if len(words) == 0 {
words = []string{""}
}
WordLoop:
for _, w := range words {
if cmds, err := currCommandset.GetCommands(w); err == nil {
switch len(cmds) {
// No more results, search is over.
case 0:
results = append(results, SearchResult{
Search: w,
Commands: []*CommandSet{},
})
break WordLoop
// Only one result
case 1:
results = append(results, SearchResult{
Search: w,
Commands: cmds,
})
currCommandset = cmds[0]
// Many results - return them to let user know what can be searched,
// but the search is over.
default:
results = append(results, SearchResult{
Search: w,
Commands: cmds,
})
break WordLoop
}
} else {
// error in regex compilation or dynamic command execution
results = append(results, SearchResult{
Commands: []*CommandSet{},
Error: err.Error(),
})
break
}
}
return results, nil
}
// refresh runs the dynamic script, parses the input and puts it into place.
func (cs *CommandSet) refresh() error {
return nil
}