Improve startup time, fiddle with completion

This commit is contained in:
kayos@tcp.direct 2023-02-06 08:16:36 -08:00
parent edfa970e26
commit 73a0e7b85a
Signed by: kayos
GPG Key ID: 4B841471B4BEE979
9 changed files with 213 additions and 75 deletions

View File

@ -9,11 +9,10 @@ import (
"time"
cli "git.tcp.direct/Mirrors/go-prompt"
"github.com/davecgh/go-spew/spew"
tui "github.com/manifoldco/promptui"
"github.com/rs/zerolog"
"github.com/davecgh/go-spew/spew"
"github.com/google/shlex"
"git.tcp.direct/kayos/ziggs/internal/common"
@ -74,14 +73,26 @@ func executor(cmd string) {
levelsdebug := map[string]zerolog.Level{"info": zerolog.InfoLevel, "debug": zerolog.DebugLevel, "trace": zerolog.TraceLevel}
debuglevels := map[zerolog.Level]string{zerolog.InfoLevel: "info", zerolog.DebugLevel: "debug", zerolog.TraceLevel: "trace"}
if len(args) < 2 {
println("current debug level: " + debuglevels[log.GetLevel()])
log.Info().Msgf("current debug level: %s", debuglevels[log.GetLevel()])
return
}
if newlevel, ok := levelsdebug[args[1]]; ok {
zerolog.SetGlobalLevel(newlevel)
} else {
println("invalid argument: " + args[1])
return
}
if args[1] == "debugcli" || args[1] == "cli" {
if extraDebug {
extraDebug = false
log.Info().Msg("disabled cli debug")
} else {
extraDebug = true
log.Info().Msgf("dumping suggestions")
spew.Dump(suggestions)
log.Info().Msg("enabled cli debug")
}
return
}
return
case "help":
if len(args) < 2 {
getHelp("")
@ -90,13 +101,6 @@ func executor(cmd string) {
getHelp(args[len(args)-1])
case "clear":
print("\033[H\033[2J")
case "debugcli":
if extraDebug {
extraDebug = false
} else {
extraDebug = true
}
spew.Dump(suggestions)
default:
if len(args) == 0 {
return
@ -213,9 +217,12 @@ func saveHist() {
func StartCLI() {
log = config.GetLogger()
processBridges()
grpmap := ziggy.GetGroupMap()
processGroups(grpmap)
processLights()
go func() {
processGroups(ziggy.GetGroupMap())
processLights(ziggy.GetLightMap())
processScenes(ziggy.GetSceneMap())
}()
buildTime, _ := common.Version()
prompt = cli.New(
executor,
completer,
@ -235,8 +242,7 @@ func StartCLI() {
}
return fmt.Sprintf("ziggs[%s] %s ", sel.String(), bulb), true
}),
cli.OptionTitle("ziggs"),
// cli.OptionCompletionOnDown(),
cli.OptionTitle("ziggs - built "+buildTime),
)
prompt.Run()

View File

@ -2,8 +2,11 @@ package cli
import (
"strings"
"sync"
cli "git.tcp.direct/Mirrors/go-prompt"
"github.com/davecgh/go-spew/spew"
"github.com/google/shlex"
)
const (
@ -16,51 +19,71 @@ type completion struct {
cli.Suggest
inner *ziggsCommand
requires map[int]map[string]bool
callback func([]string) bool
isAlias bool
root bool
}
func (c completion) qualifies(line string) bool {
args := strings.Fields(line)
args, err := shlex.Split(line)
if err != nil {
log.Warn().Err(err).Msg("shlex.Split failed")
return false
}
if len(args) <= 1 && c.root {
verbose := func(msg string, args ...interface{}) {
if !extraDebug {
return
}
log.Trace().Caller(1).
Int("len(args)", len(args)).
Int("len(c.requires)", len(c.requires)).Msgf(msg, args...)
}
if extraDebug {
spew.Dump(args)
}
switch {
case len(args) <= 1 && c.root:
verbose("%v%s: len(args) <= 1 && c.root", grn, c.Text)
return true
}
if len(args) < len(c.requires) {
if extraDebug {
log.Trace().Int("len(args)", len(args)).Int("len(c.requires)", len(c.requires)).
Msg(red + "len(args) < len(c.requires)" + rst)
}
case len(args) < len(c.requires):
verbose(red + "len(args) < len(c.requires)" + rst)
return false
}
if len(args)-2 > len(c.requires) {
if extraDebug {
log.Trace().Int("len(args)-2", len(args)-2).Int("len(c.requires)", len(c.requires)).
Msg(red + "len(args)-2 > len(c.requires)" + rst)
}
case len(args)-2 > len(c.requires):
verbose(red + "len(args)-2 > len(c.requires)" + rst)
return false
default:
//
}
var count = 0
for i, a := range args {
i++
if _, ok := c.requires[i][a]; ok {
if extraDebug {
log.Trace().Msgf("%v%s: found %s%v", grn, c.Text, a, rst)
}
verbose("%v%s: found %s (count++) %v", grn, c.Text, a, rst)
count++
}
}
if extraDebug && !(count >= len(c.requires)) {
log.Trace().Msgf("%v%s: count(%d) < len(c.requires)(%d)", red, c.Text, count, len(c.requires))
ok := count >= len(c.requires)
if !ok {
verbose("%v%s: count(%d) < len(c.requires)(%d)", red, c.Text, count, len(c.requires))
return false
}
return count >= len(c.requires)
if c.callback == nil {
return true
}
return c.callback(args)
}
var suggestions map[int]map[string]*completion
var (
suggestions map[int]map[string]*completion
suggestionMutex = &sync.RWMutex{}
)
func init() {
Commands["ls"] = newZiggsCommand(cmdList, "list all lights, groups, scenes, rules, and schedules", 0)
@ -91,10 +114,15 @@ func init() {
}
func initCompletion() {
suggestionMutex.Lock()
defer suggestionMutex.Unlock()
suggestions = make(map[int]map[string]*completion)
suggestions[0] = make(map[string]*completion)
suggestions[1] = make(map[string]*completion)
suggestions[2] = make(map[string]*completion)
suggestions[3] = make(map[string]*completion)
suggestions[4] = make(map[string]*completion)
/* {Suggest: cli.Suggest{Text: "lights"}, inner: Commands["lights"]},
{Suggest: cli.Suggest{Text: "groups"}, inner: Commands["groups"]},
@ -111,7 +139,6 @@ func initCompletion() {
{Suggest: cli.Suggest{Text: "dump"}, inner: Commands["dump"]},
{Suggest: cli.Suggest{Text: "load"}, inner: Commands["load"]},
{Suggest: cli.Suggest{Text: "use", Description: "select bridge to perform actions on"}},
{Suggest: cli.Suggest{Text: "exit", Description: "exit ziggs"}},
*/
@ -156,16 +183,13 @@ func initCompletion() {
}
func completer(in cli.Document) []cli.Suggest {
c := in.CurrentLine()
c := in.Text
infields := strings.Fields(c)
infields, _ := shlex.Split(c)
var head = len(infields) - 1
if head < 0 {
head = 0
}
if head == 1 {
head = 1
}
if head > 0 && in.LastKeyStroke() == ' ' {
head++
}
@ -174,11 +198,15 @@ func completer(in cli.Document) []cli.Suggest {
log.Trace().Int("head", head).Msgf("completing %v", infields)
}
var sugs []cli.Suggest
suggestionMutex.RLock()
defer suggestionMutex.RUnlock()
for _, sug := range suggestions[head] {
if sug.qualifies(c) {
if in.GetWordBeforeCursor() != "" && strings.HasPrefix(sug.Text, in.GetWordBeforeCursor()) {
sugs = append(sugs, sug.Suggest)
}
if !sug.qualifies(c) {
continue
}
if in.TextBeforeCursor() != "" && strings.Contains(strings.ToLower(sug.Text),
strings.ToLower(strings.TrimSpace(in.GetWordBeforeCursorWithSpace()))) {
sugs = append(sugs, sug.Suggest)
}
}
return sugs

View File

@ -21,7 +21,7 @@ func cmdGet(bridge *ziggy.Bridge, args []string) error {
}
var (
groupMap map[string]*huego.Group
groupMap map[string]*ziggy.HueGroup
lightMap map[string]*ziggy.HueLight
currentState *huego.State
argHead = -1

View File

@ -10,6 +10,9 @@ import (
var tabber = tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', tabwriter.AlignRight)
func getHelp(target string) {
suggestionMutex.RLock()
defer suggestionMutex.RUnlock()
if target != "" && target != "meta" {
for _, su := range suggestions[0] {
if strings.Contains(strings.ToLower(su.Text), strings.ToLower(target)) {

View File

@ -2,38 +2,88 @@ package cli
import (
cli "git.tcp.direct/Mirrors/go-prompt"
"github.com/amimof/huego"
"git.tcp.direct/kayos/ziggs/internal/ziggy"
)
func processGroups(grps map[string]*huego.Group) {
func processGroups(grps map[string]*ziggy.HueGroup) {
for grp, g := range grps {
suffix := ""
if g.Type != "" {
suffix = " (" + g.Type + ")"
}
suggestionMutex.Lock()
suggestions[2][grp] = &completion{
Suggest: cli.Suggest{
Text: grp,
Description: "Group" + suffix,
},
requires: map[int]map[string]bool{
1: {"set": true, "s": true, "delete": true, "d": true},
1: {"set": true, "s": true, "delete": true, "d": true, "get": true, "dump": true},
2: {"group": true, "g": true},
},
root: false,
}
suggestionMutex.Unlock()
}
}
func processLights() {
for lt, l := range ziggy.GetLightMap() {
func processScenes(scns map[string]*ziggy.HueScene) {
for scn, s := range scns {
suffix := ""
if s.Type != "" {
suffix = " (" + s.Type + ")"
}
suggestionMutex.Lock()
suggestions[4][scn] = &completion{
Suggest: cli.Suggest{
Text: scn,
Description: "Scene" + suffix,
},
requires: map[int]map[string]bool{
1: {"set": true, "s": true, "delete": true, "d": true, "get": true, "dump": true},
2: {"group": true, "g": true, "scene": true, "s": true, "light": true, "l": true},
4: {"scene": true, "s": true},
},
callback: func(args []string) bool {
if extraDebug {
log.Trace().Msgf("Checking if scene %s belongs to group %s, their group is %s",
s.Name, args[3], s.Group)
}
if len(args) < 4 {
return false
}
delGetDumpOnly := args[1] == "scene" || args[1] == "s"
switch {
case delGetDumpOnly && args[3] == "scene" || args[3] == "s":
return false
case delGetDumpOnly && args[0] == "set":
return false
case args[1] == "group" || args[1] == "g":
if extraDebug {
log.Trace().Msgf("Checking if group %s is %s", args[3], s.Group)
}
if args[3] == s.Group {
return true
}
default:
return false
}
return false
},
root: false,
}
suggestionMutex.Unlock()
}
}
func processLights(lghts map[string]*ziggy.HueLight) {
for lt, l := range lghts {
suffix := ""
if l.Type != "" {
suffix = " (" + l.Type + ")"
}
suggestionMutex.Lock()
suggestions[2][lt] = &completion{
Suggest: cli.Suggest{
Text: lt,
@ -45,11 +95,13 @@ func processLights() {
},
root: false,
}
suggestionMutex.Unlock()
}
}
func processBridges() {
for brd, b := range ziggy.Lucifer.Bridges {
suggestionMutex.Lock()
suggestions[1]["bridge"] = &completion{
Suggest: cli.Suggest{
Text: brd,
@ -58,5 +110,6 @@ func processBridges() {
requires: map[int]map[string]bool{0: {"use": true, "u": true}},
root: false,
}
suggestionMutex.Unlock()
}
}

View File

@ -27,9 +27,11 @@ type cmdTarget interface {
Effect(string) error
}
var ErrNotEnoughArguments = errors.New("not enough arguments")
func cmdSet(bridge *ziggy.Bridge, args []string) error {
if len(args) < 3 {
return errors.New("not enough arguments")
return ErrNotEnoughArguments
}
type (
@ -37,7 +39,7 @@ func cmdSet(bridge *ziggy.Bridge, args []string) error {
)
var (
groupMap map[string]*huego.Group
groupMap map[string]*ziggy.HueGroup
lightMap map[string]*ziggy.HueLight
actions []action
currentState *huego.State
@ -141,7 +143,7 @@ func cmdSet(bridge *ziggy.Bridge, args []string) error {
})
case "hue", "h":
if len(args) == argHead-1 {
return errors.New("not enough arguments")
return ErrNotEnoughArguments
}
argHead++
newHue, numErr := strconv.Atoi(strings.TrimSpace(args[argHead]))
@ -157,7 +159,7 @@ func cmdSet(bridge *ziggy.Bridge, args []string) error {
})
case "saturation", "sat":
if len(args) == argHead-1 {
return errors.New("not enough arguments")
return ErrNotEnoughArguments
}
argHead++
newSat, numErr := strconv.Atoi(strings.TrimSpace(args[argHead]))
@ -173,7 +175,7 @@ func cmdSet(bridge *ziggy.Bridge, args []string) error {
})
case "effect", "e":
if len(args) == argHead-1 {
return errors.New("not enough arguments")
return ErrNotEnoughArguments
}
argHead++
newEffect := strings.TrimSpace(args[argHead])
@ -186,7 +188,7 @@ func cmdSet(bridge *ziggy.Bridge, args []string) error {
})
case "temperature", "temp":
if len(args) == argHead-1 {
return errors.New("not enough arguments")
return ErrNotEnoughArguments
}
argHead++
newTemp, numErr := strconv.Atoi(strings.TrimSpace(args[argHead]))
@ -222,11 +224,11 @@ func cmdSet(bridge *ziggy.Bridge, args []string) error {
return nil
case "scene", "sc":
if len(args) == argHead-1 {
return errors.New("not enough arguments")
return ErrNotEnoughArguments
}
argHead++
if argHead > len(args)-1 {
return errors.New("not enough arguments")
return ErrNotEnoughArguments
}
targetScene := strings.TrimSpace(args[argHead])
actions = append(actions, func() error {
@ -251,7 +253,7 @@ func cmdSet(bridge *ziggy.Bridge, args []string) error {
if target == nil {
return errors.New("no target specified")
}
tg, tgok := target.(*huego.Group)
tg, tgok := target.(*ziggy.HueGroup)
tl, tlok := target.(*ziggy.HueLight)
switch {
case tgok:

View File

@ -63,6 +63,18 @@ type HueLight struct {
controller *Bridge
}
type HueGroup struct {
*huego.Group
scenes map[string]*HueScene
controller *Bridge
}
type HueScene struct {
*huego.Scene
controller *Bridge
group *HueGroup
}
func (hl *HueLight) Scene(s string) error {
return hl.Scene(s)
}

View File

@ -1,6 +1,6 @@
package ziggy
import "github.com/amimof/huego"
import "strconv"
// Multiplex is all of the lights (all of the lights).
// I'll see myself out.
@ -32,8 +32,8 @@ func GetLightMap() map[string]*HueLight {
return lightMap
}
func GetGroupMap() map[string]*huego.Group {
var groupMap = make(map[string]*huego.Group)
func GetGroupMap() map[string]*HueGroup {
var groupMap = make(map[string]*HueGroup)
for _, c := range Lucifer.Bridges {
gs, err := c.GetGroups()
if err != nil {
@ -50,14 +50,16 @@ func GetGroupMap() map[string]*huego.Group {
log.Warn().Msgf("duplicate group name %s on bridge %s - please rename", g.Name, c.ID)
continue
}
groupMap[g.Name] = group
hg := &HueGroup{Group: group, controller: c}
groupMap[g.Name] = hg
groupMap[strconv.Itoa(g.ID)] = hg
}
}
return groupMap
}
func GetSceneMap() map[string]*huego.Scene {
var sceneMap = make(map[string]*huego.Scene)
func GetSceneMap() map[string]*HueScene {
var sceneMap = make(map[string]*HueScene)
for _, c := range Lucifer.Bridges {
scs, err := c.GetScenes()
if err != nil {
@ -70,11 +72,14 @@ func GetSceneMap() map[string]*huego.Scene {
log.Warn().Msgf("failed to get pointer for scene %s on bridge %s: %v", s.Name, c.ID, gerr)
continue
}
if _, ok := sceneMap[s.Name]; ok {
if _, ok := sceneMap[s.Name]; !ok {
sceneMap[s.Name] = &HueScene{Scene: group, controller: c}
continue
}
if _, ok := sceneMap[s.Name+"-2"]; ok {
log.Warn().Msgf("duplicate scene name %s on bridge %s - please rename", s.Name, c.ID)
continue
}
sceneMap[s.Name] = group
}
}
return sceneMap

View File

@ -23,7 +23,7 @@ func (c *Bridge) FindLight(input string) (light *HueLight, err error) {
return &HueLight{Light: l, controller: c}, nil
}
func (c *Bridge) FindGroup(input string) (light *huego.Group, err error) {
func (c *Bridge) FindGroup(input string) (light *HueGroup, err error) {
var groupID int
if groupID, err = strconv.Atoi(input); err != nil {
targ, ok := GetGroupMap()[input]
@ -32,5 +32,34 @@ func (c *Bridge) FindGroup(input string) (light *huego.Group, err error) {
}
return targ, nil
}
return c.GetGroup(groupID)
var hg *huego.Group
if hg, err = c.GetGroup(groupID); err != nil {
return nil, err
}
return &HueGroup{Group: hg, controller: c}, nil
}
func (hg *HueGroup) Scenes() ([]*HueScene, error) {
scenes, err := hg.controller.GetScenes()
if err != nil {
return nil, err
}
var ret []*HueScene
for _, s := range scenes {
intID, err := strconv.Atoi(s.Group)
if err != nil {
log.Warn().Msgf("unable to parse group ID from scene %s: %v", s.Name, err)
}
if intID != hg.ID {
continue
}
s, err := hg.controller.GetScene(s.ID)
if err != nil {
log.Warn().Msgf("unable to get scene pointer for scene %s: %v", s.Name, err)
return nil, err
}
ret = append(ret, &HueScene{Scene: s, controller: hg.controller})
}
return ret, nil
}