150 lines
3.6 KiB
Go
150 lines
3.6 KiB
Go
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
|
|
}
|