refactoring, added sequential triggers execution per component
This commit is contained in:
parent
22fbf3e796
commit
7db33312b6
|
@ -7,28 +7,41 @@ import (
|
||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Beep() error {
|
type AudioPlayer struct {
|
||||||
|
player *oto.Player
|
||||||
|
beep []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAudioPlayer() *AudioPlayer {
|
||||||
|
|
||||||
bytes, err := Asset("quindar-tone.mp3")
|
bytes, err := Asset("quindar-tone.mp3")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Can't find asset file")
|
log.Fatal("Can't find audio file")
|
||||||
}
|
}
|
||||||
|
|
||||||
d, err := mp3.NewDecoder(NewAssetFile(bytes))
|
player, err := oto.NewPlayer(44100, 2, 2, 8192)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
panic(err)
|
||||||
}
|
}
|
||||||
defer d.Close()
|
|
||||||
|
|
||||||
p, err := oto.NewPlayer(d.SampleRate(), 2, 2, 8192)
|
return &AudioPlayer{
|
||||||
|
player: player,
|
||||||
|
beep: bytes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AudioPlayer) Beep() {
|
||||||
|
|
||||||
|
decoder, err := mp3.NewDecoder(NewAssetFile(a.beep))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
panic(err)
|
||||||
}
|
|
||||||
defer p.Close()
|
|
||||||
|
|
||||||
if _, err := io.Copy(p, d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
if _, err := io.Copy(a.player, decoder); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AudioPlayer) Close() {
|
||||||
|
_ = a.player.Close()
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"github.com/sqshq/sampler/config"
|
"github.com/sqshq/sampler/config"
|
||||||
"github.com/sqshq/sampler/console"
|
"github.com/sqshq/sampler/console"
|
||||||
"github.com/sqshq/sampler/data"
|
"github.com/sqshq/sampler/data"
|
||||||
"github.com/sqshq/sampler/trigger"
|
|
||||||
"image"
|
"image"
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -39,7 +38,6 @@ const (
|
||||||
type RunChart struct {
|
type RunChart struct {
|
||||||
ui.Block
|
ui.Block
|
||||||
data.Consumer
|
data.Consumer
|
||||||
triggers []trigger.Trigger
|
|
||||||
lines []TimeLine
|
lines []TimeLine
|
||||||
grid ChartGrid
|
grid ChartGrid
|
||||||
timescale time.Duration
|
timescale time.Duration
|
||||||
|
|
14
config.yml
14
config.yml
|
@ -7,13 +7,13 @@ runcharts:
|
||||||
w: 53
|
w: 53
|
||||||
h: 16
|
h: 16
|
||||||
triggers:
|
triggers:
|
||||||
- title: PROCESSING STARTED # ${prev} ${cur} ${lavel} echo $(( 3 < 4 && 1 > 2 ))
|
- title: LATENCY
|
||||||
condition: ((${prev} == 0 && ${cur} > 0))
|
condition: echo "$prev < 0.4 && $cur > 0.4" |bc -l
|
||||||
actions:
|
actions:
|
||||||
terminal-bell: true
|
terminal-bell: true
|
||||||
sound: false
|
sound: true
|
||||||
visual: true
|
visual: true
|
||||||
script: say ${lavel} initiated with value ${cur}
|
script: 'say alert: ${label} latency exceeded ${cur} second'
|
||||||
scale: 3
|
scale: 3
|
||||||
items:
|
items:
|
||||||
- label: GOOGLE
|
- label: GOOGLE
|
||||||
|
@ -100,6 +100,12 @@ gauges:
|
||||||
size:
|
size:
|
||||||
w: 27
|
w: 27
|
||||||
h: 2
|
h: 2
|
||||||
|
triggers:
|
||||||
|
- title: CLOCK BELL EVERY MINUTE
|
||||||
|
condition: '[ $label == "cur" ] && [ $cur -eq 0 ] && echo 1'
|
||||||
|
actions:
|
||||||
|
sound: true
|
||||||
|
script: say -v samantha `date +%I:%M%p`
|
||||||
values:
|
values:
|
||||||
cur: date +%S
|
cur: date +%S
|
||||||
max: echo 60
|
max: echo 60
|
||||||
|
|
|
@ -104,9 +104,9 @@ func (c *Config) setDefaultValues() {
|
||||||
|
|
||||||
func setDefaultTriggersValues(triggers []TriggerConfig) {
|
func setDefaultTriggersValues(triggers []TriggerConfig) {
|
||||||
|
|
||||||
defaultTerminalBell := true
|
defaultTerminalBell := false
|
||||||
defaultSound := false
|
defaultSound := false
|
||||||
defaultVisual := true
|
defaultVisual := false
|
||||||
|
|
||||||
for i, trigger := range triggers {
|
for i, trigger := range triggers {
|
||||||
|
|
||||||
|
@ -137,20 +137,20 @@ func (c *Config) setDefaultColors() {
|
||||||
palette := console.GetPalette(*c.Theme)
|
palette := console.GetPalette(*c.Theme)
|
||||||
colorsCount := len(palette.Colors)
|
colorsCount := len(palette.Colors)
|
||||||
|
|
||||||
for _, chart := range c.RunCharts {
|
for _, ch := range c.RunCharts {
|
||||||
for j, item := range chart.Items {
|
for j, item := range ch.Items {
|
||||||
if item.Color == nil {
|
if item.Color == nil {
|
||||||
item.Color = &palette.Colors[j%colorsCount]
|
item.Color = &palette.Colors[j%colorsCount]
|
||||||
chart.Items[j] = item
|
ch.Items[j] = item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, chart := range c.BarCharts {
|
for _, b := range c.BarCharts {
|
||||||
for j, item := range chart.Items {
|
for j, item := range b.Items {
|
||||||
if item.Color == nil {
|
if item.Color == nil {
|
||||||
item.Color = &palette.Colors[j%colorsCount]
|
item.Color = &palette.Colors[j%colorsCount]
|
||||||
chart.Items[j] = item
|
b.Items[j] = item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,10 @@ const (
|
||||||
|
|
||||||
type Console struct{}
|
type Console struct{}
|
||||||
|
|
||||||
|
const (
|
||||||
|
BellCharacter = "\a"
|
||||||
|
)
|
||||||
|
|
||||||
func (self *Console) Init() {
|
func (self *Console) Init() {
|
||||||
|
|
||||||
fmt.Printf("\033]0;%s\007", AppTitle)
|
fmt.Printf("\033]0;%s\007", AppTitle)
|
||||||
|
|
|
@ -11,6 +11,7 @@ type Sample struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Alert struct {
|
type Alert struct {
|
||||||
|
Title string
|
||||||
Text string
|
Text string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
15
data/item.go
15
data/item.go
|
@ -1,6 +1,7 @@
|
||||||
package data
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/sqshq/sampler/config"
|
||||||
ui "github.com/sqshq/termui"
|
ui "github.com/sqshq/termui"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -9,7 +10,19 @@ import (
|
||||||
type Item struct {
|
type Item struct {
|
||||||
Label string
|
Label string
|
||||||
Script string
|
Script string
|
||||||
Color ui.Color
|
Color *ui.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewItems(cfgs []config.Item) []Item {
|
||||||
|
|
||||||
|
items := make([]Item, 0)
|
||||||
|
|
||||||
|
for _, i := range cfgs {
|
||||||
|
item := Item{Label: *i.Label, Script: i.Script, Color: i.Color}
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Item) nextValue() (value string, err error) {
|
func (i *Item) nextValue() (value string, err error) {
|
||||||
|
|
|
@ -1,43 +1,63 @@
|
||||||
package data
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/sqshq/sampler/trigger"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Sampler struct {
|
type Sampler struct {
|
||||||
consumer Consumer
|
consumer Consumer
|
||||||
item Item
|
items []Item
|
||||||
triggers []trigger.Trigger
|
triggers []Trigger
|
||||||
|
triggersChannel chan Sample
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSampler(consumer Consumer, item Item, triggers []trigger.Trigger, rateMs int) Sampler {
|
func NewSampler(consumer Consumer, items []Item, triggers []Trigger, rateMs int) Sampler {
|
||||||
|
|
||||||
ticker := time.NewTicker(time.Duration(rateMs * int(time.Millisecond)))
|
ticker := time.NewTicker(
|
||||||
sampler := Sampler{consumer, item, triggers}
|
time.Duration(rateMs * int(time.Millisecond)),
|
||||||
|
)
|
||||||
|
|
||||||
|
sampler := Sampler{
|
||||||
|
consumer,
|
||||||
|
items,
|
||||||
|
triggers,
|
||||||
|
make(chan Sample),
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
sampler.sample()
|
|
||||||
for ; true; <-ticker.C {
|
for ; true; <-ticker.C {
|
||||||
sampler.sample()
|
for _, item := range sampler.items {
|
||||||
|
go sampler.sample(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case sample := <-sampler.triggersChannel:
|
||||||
|
for _, t := range sampler.triggers {
|
||||||
|
t.Execute(sample)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return sampler
|
return sampler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sampler) sample() {
|
func (s *Sampler) sample(item Item) {
|
||||||
value, err := s.item.nextValue()
|
|
||||||
|
val, err := item.nextValue()
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
sample := Sample{Label: item.Label, Value: val}
|
||||||
sample := Sample{Value: value, Label: s.item.Label}
|
|
||||||
s.consumer.SampleChannel <- sample
|
s.consumer.SampleChannel <- sample
|
||||||
|
s.triggersChannel <- sample
|
||||||
for _, t := range s.triggers {
|
|
||||||
t.Execute(s.item.Label, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
s.consumer.AlertChannel <- Alert{Text: err.Error()}
|
s.consumer.AlertChannel <- Alert{
|
||||||
|
Title: "SAMPLING FAILURE",
|
||||||
|
Text: err.Error(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/sqshq/sampler/asset"
|
||||||
|
"github.com/sqshq/sampler/config"
|
||||||
|
"github.com/sqshq/sampler/console"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TrueIndicator = "1"
|
||||||
|
InitialValue = "0"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Trigger struct {
|
||||||
|
title string
|
||||||
|
condition string
|
||||||
|
actions Actions
|
||||||
|
consumer Consumer
|
||||||
|
valuesByLabel map[string]Values
|
||||||
|
player *asset.AudioPlayer
|
||||||
|
digitsRegexp *regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
type Actions struct {
|
||||||
|
terminalBell bool
|
||||||
|
sound bool
|
||||||
|
visual bool
|
||||||
|
script *string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Values struct {
|
||||||
|
current string
|
||||||
|
previous string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTriggers(cfgs []config.TriggerConfig, consumer Consumer, player *asset.AudioPlayer) []Trigger {
|
||||||
|
|
||||||
|
triggers := make([]Trigger, 0)
|
||||||
|
|
||||||
|
for _, cfg := range cfgs {
|
||||||
|
triggers = append(triggers, NewTrigger(cfg, consumer, player))
|
||||||
|
}
|
||||||
|
|
||||||
|
return triggers
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTrigger(config config.TriggerConfig, consumer Consumer, player *asset.AudioPlayer) Trigger {
|
||||||
|
return Trigger{
|
||||||
|
title: config.Title,
|
||||||
|
condition: config.Condition,
|
||||||
|
consumer: consumer,
|
||||||
|
valuesByLabel: make(map[string]Values),
|
||||||
|
player: player,
|
||||||
|
digitsRegexp: regexp.MustCompile("[^0-9]+"),
|
||||||
|
actions: Actions{
|
||||||
|
terminalBell: *config.Actions.TerminalBell,
|
||||||
|
sound: *config.Actions.Sound,
|
||||||
|
visual: *config.Actions.Visual,
|
||||||
|
script: config.Actions.Script,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Trigger) Execute(sample Sample) {
|
||||||
|
if t.evaluate(sample) {
|
||||||
|
|
||||||
|
if t.actions.terminalBell {
|
||||||
|
fmt.Print(console.BellCharacter)
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.actions.sound {
|
||||||
|
t.player.Beep()
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.actions.visual {
|
||||||
|
//t.consumer.AlertChannel <- Alert{
|
||||||
|
// Title: "TRIGGER ALERT", Text: sample.Label,
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.actions.script != nil {
|
||||||
|
_, _ = runScript(*t.actions.script, sample.Label, t.valuesByLabel[sample.Label])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Trigger) evaluate(sample Sample) bool {
|
||||||
|
|
||||||
|
if values, ok := t.valuesByLabel[sample.Label]; ok {
|
||||||
|
values.previous = values.current
|
||||||
|
values.current = sample.Value
|
||||||
|
t.valuesByLabel[sample.Label] = values
|
||||||
|
} else {
|
||||||
|
t.valuesByLabel[sample.Label] = Values{previous: InitialValue, current: sample.Value}
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := runScript(t.condition, sample.Label, t.valuesByLabel[sample.Label])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
//t.consumer.AlertChannel <- Alert{Title: "TRIGGER CONDITION FAILURE", Text: err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.digitsRegexp.ReplaceAllString(string(output), "") == TrueIndicator
|
||||||
|
}
|
||||||
|
|
||||||
|
func runScript(script, label string, data Values) ([]byte, error) {
|
||||||
|
|
||||||
|
cmd := exec.Command("sh", "-c", script)
|
||||||
|
cmd.Env = os.Environ()
|
||||||
|
cmd.Env = append(cmd.Env,
|
||||||
|
fmt.Sprintf("prev=%v", data.previous),
|
||||||
|
fmt.Sprintf("cur=%v", data.current),
|
||||||
|
fmt.Sprintf("label=%v", label))
|
||||||
|
|
||||||
|
return cmd.Output()
|
||||||
|
}
|
35
main.go
35
main.go
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/sqshq/sampler/asset"
|
||||||
"github.com/sqshq/sampler/component"
|
"github.com/sqshq/sampler/component"
|
||||||
"github.com/sqshq/sampler/component/asciibox"
|
"github.com/sqshq/sampler/component/asciibox"
|
||||||
"github.com/sqshq/sampler/component/barchart"
|
"github.com/sqshq/sampler/component/barchart"
|
||||||
|
@ -11,7 +12,6 @@ import (
|
||||||
"github.com/sqshq/sampler/console"
|
"github.com/sqshq/sampler/console"
|
||||||
"github.com/sqshq/sampler/data"
|
"github.com/sqshq/sampler/data"
|
||||||
"github.com/sqshq/sampler/event"
|
"github.com/sqshq/sampler/event"
|
||||||
"github.com/sqshq/sampler/trigger"
|
|
||||||
ui "github.com/sqshq/termui"
|
ui "github.com/sqshq/termui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,51 +25,50 @@ func main() {
|
||||||
width, height := ui.TerminalDimensions()
|
width, height := ui.TerminalDimensions()
|
||||||
lout := layout.NewLayout(width, height, component.NewStatusLine(flg.ConfigFileName), component.NewMenu())
|
lout := layout.NewLayout(width, height, component.NewStatusLine(flg.ConfigFileName), component.NewMenu())
|
||||||
|
|
||||||
|
player := asset.NewAudioPlayer()
|
||||||
|
defer player.Close()
|
||||||
|
|
||||||
for _, c := range cfg.RunCharts {
|
for _, c := range cfg.RunCharts {
|
||||||
|
|
||||||
legend := runchart.Legend{Enabled: c.Legend.Enabled, Details: c.Legend.Details}
|
legend := runchart.Legend{Enabled: c.Legend.Enabled, Details: c.Legend.Details}
|
||||||
chart := runchart.NewRunChart(c, legend)
|
chart := runchart.NewRunChart(c, legend)
|
||||||
lout.AddComponent(config.TypeRunChart, chart, c.Title, c.Position, c.Size, *c.RefreshRateMs)
|
lout.AddComponent(config.TypeRunChart, chart, c.Title, c.Position, c.Size, *c.RefreshRateMs)
|
||||||
triggers := trigger.NewTriggers(c.Triggers)
|
triggers := data.NewTriggers(c.Triggers, chart.Consumer, player)
|
||||||
|
items := data.NewItems(c.Items)
|
||||||
|
data.NewSampler(chart.Consumer, items, triggers, *c.RefreshRateMs)
|
||||||
|
|
||||||
for _, i := range c.Items {
|
for _, i := range c.Items {
|
||||||
item := data.Item{Label: *i.Label, Script: i.Script, Color: *i.Color}
|
chart.AddLine(*i.Label, *i.Color)
|
||||||
chart.AddLine(item.Label, item.Color)
|
|
||||||
data.NewSampler(chart.Consumer, item, triggers, *c.RefreshRateMs)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, a := range cfg.AsciiBoxes {
|
for _, a := range cfg.AsciiBoxes {
|
||||||
box := asciibox.NewAsciiBox(a)
|
box := asciibox.NewAsciiBox(a)
|
||||||
item := data.Item{Label: *a.Label, Script: a.Script, Color: *a.Color}
|
item := data.Item{Label: *a.Label, Script: a.Script, Color: a.Color}
|
||||||
triggers := trigger.NewTriggers(a.Triggers)
|
|
||||||
lout.AddComponent(config.TypeAsciiBox, box, a.Title, a.Position, a.Size, *a.RefreshRateMs)
|
lout.AddComponent(config.TypeAsciiBox, box, a.Title, a.Position, a.Size, *a.RefreshRateMs)
|
||||||
data.NewSampler(box.Consumer, item, triggers, *a.RefreshRateMs)
|
triggers := data.NewTriggers(a.Triggers, box.Consumer, player)
|
||||||
|
data.NewSampler(box.Consumer, []data.Item{item}, triggers, *a.RefreshRateMs)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, b := range cfg.BarCharts {
|
for _, b := range cfg.BarCharts {
|
||||||
|
|
||||||
chart := barchart.NewBarChart(b.Title, *b.Scale)
|
chart := barchart.NewBarChart(b.Title, *b.Scale)
|
||||||
triggers := trigger.NewTriggers(b.Triggers)
|
triggers := data.NewTriggers(b.Triggers, chart.Consumer, player)
|
||||||
lout.AddComponent(config.TypeBarChart, chart, b.Title, b.Position, b.Size, *b.RefreshRateMs)
|
lout.AddComponent(config.TypeBarChart, chart, b.Title, b.Position, b.Size, *b.RefreshRateMs)
|
||||||
|
items := data.NewItems(b.Items)
|
||||||
|
data.NewSampler(chart.Consumer, items, triggers, *b.RefreshRateMs)
|
||||||
|
|
||||||
for _, i := range b.Items {
|
for _, i := range b.Items {
|
||||||
item := data.Item{Label: *i.Label, Script: i.Script, Color: *i.Color}
|
|
||||||
chart.AddBar(*i.Label, *i.Color)
|
chart.AddBar(*i.Label, *i.Color)
|
||||||
data.NewSampler(chart.Consumer, item, triggers, *b.RefreshRateMs)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, gc := range cfg.Gauges {
|
for _, gc := range cfg.Gauges {
|
||||||
|
|
||||||
g := gauge.NewGauge(gc.Title, *gc.Scale, *gc.Color)
|
g := gauge.NewGauge(gc.Title, *gc.Scale, *gc.Color)
|
||||||
triggers := trigger.NewTriggers(gc.Triggers)
|
triggers := data.NewTriggers(gc.Triggers, g.Consumer, player)
|
||||||
lout.AddComponent(config.TypeGauge, g, gc.Title, gc.Position, gc.Size, *gc.RefreshRateMs)
|
lout.AddComponent(config.TypeGauge, g, gc.Title, gc.Position, gc.Size, *gc.RefreshRateMs)
|
||||||
|
items := data.NewItems(gc.Items)
|
||||||
for _, i := range gc.Items {
|
data.NewSampler(g.Consumer, items, triggers, *gc.RefreshRateMs)
|
||||||
item := data.Item{Label: *i.Label, Script: i.Script}
|
|
||||||
data.NewSampler(g.Consumer, item, triggers, *gc.RefreshRateMs)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handler := event.NewHandler(lout)
|
handler := event.NewHandler(lout)
|
||||||
|
|
|
@ -1,111 +0,0 @@
|
||||||
package trigger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/sqshq/sampler/asset"
|
|
||||||
"github.com/sqshq/sampler/config"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
TrueIndicator = "1"
|
|
||||||
BellCharacter = "\a"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Trigger struct {
|
|
||||||
title string
|
|
||||||
condition string
|
|
||||||
actions Actions
|
|
||||||
data map[string]Data
|
|
||||||
}
|
|
||||||
|
|
||||||
type Actions struct {
|
|
||||||
terminalBell bool
|
|
||||||
sound bool
|
|
||||||
visual bool
|
|
||||||
script *string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Data struct {
|
|
||||||
previousValue interface{}
|
|
||||||
currentValue interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTriggers(configs []config.TriggerConfig) []Trigger {
|
|
||||||
|
|
||||||
triggers := make([]Trigger, len(configs))
|
|
||||||
|
|
||||||
for _, c := range configs {
|
|
||||||
triggers = append(triggers, NewTrigger(c))
|
|
||||||
}
|
|
||||||
|
|
||||||
return triggers
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTrigger(config config.TriggerConfig) Trigger {
|
|
||||||
return Trigger{
|
|
||||||
title: config.Title,
|
|
||||||
condition: config.Condition,
|
|
||||||
data: make(map[string]Data),
|
|
||||||
actions: Actions{
|
|
||||||
terminalBell: *config.Actions.TerminalBell,
|
|
||||||
sound: *config.Actions.Sound,
|
|
||||||
visual: *config.Actions.Visual,
|
|
||||||
script: config.Actions.Script,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t Trigger) Execute(label string, value interface{}) {
|
|
||||||
|
|
||||||
if data, ok := t.data[label]; ok {
|
|
||||||
data.previousValue = data.currentValue
|
|
||||||
data.currentValue = value
|
|
||||||
} else {
|
|
||||||
t.data[label] = Data{previousValue: nil, currentValue: value}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.evaluate(label, t.data[label])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t Trigger) evaluate(label string, data Data) {
|
|
||||||
|
|
||||||
output, err := runScript(t.condition, label, data)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
//println(err) // TODO visual notification
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(output) != TrueIndicator {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.actions.terminalBell {
|
|
||||||
fmt.Print(BellCharacter)
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.actions.sound {
|
|
||||||
_ = asset.Beep()
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.actions.visual {
|
|
||||||
// TODO visual notification
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.actions.script != nil {
|
|
||||||
_, _ = runScript(*t.actions.script, label, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runScript(script, label string, data Data) ([]byte, error) {
|
|
||||||
cmd := exec.Command("sh", "-c", script)
|
|
||||||
|
|
||||||
cmd.Env = os.Environ()
|
|
||||||
cmd.Env = append(cmd.Env,
|
|
||||||
fmt.Sprintf("prev=%v", data.previousValue),
|
|
||||||
fmt.Sprintf("cur=%v", data.currentValue),
|
|
||||||
fmt.Sprintf("label=%v", label))
|
|
||||||
|
|
||||||
return cmd.Output()
|
|
||||||
}
|
|
Loading…
Reference in New Issue