component functionality rearrangement

This commit is contained in:
sqshq 2019-03-16 19:59:28 -04:00
parent fec7eefd9f
commit 78f0c0ed83
13 changed files with 138 additions and 113 deletions

View File

@ -9,48 +9,26 @@ import (
"strings"
)
type Alerter struct {
channel <-chan data.Alert
alert *data.Alert
}
func RenderAlert(alert *data.Alert, area image.Rectangle, buffer *ui.Buffer) {
func NewAlerter(channel <-chan data.Alert) *Alerter {
alerter := Alerter{channel: channel}
alerter.consume()
return &alerter
}
func (a *Alerter) consume() {
go func() {
for {
select {
case alert := <-a.channel:
a.alert = &alert
}
}
}()
}
func (a *Alerter) RenderAlert(buffer *ui.Buffer, area image.Rectangle) {
if a.alert == nil {
if alert == nil {
return
}
color := console.ColorWhite
if a.alert.Color != nil {
color = *a.alert.Color
if alert.Color != nil {
color = *alert.Color
}
width := max(len(a.alert.Title), len(a.alert.Text)) + 10
width := max(len(alert.Title), len(alert.Text)) + 10
if width > area.Dx() {
width = area.Dx()
}
cells := ui.WrapCells(ui.ParseStyles(fmt.Sprintf("%s\n%s\n",
strings.ToUpper(a.alert.Title), a.alert.Text), ui.NewStyle(console.ColorWhite)), uint(width))
strings.ToUpper(alert.Title), alert.Text), ui.NewStyle(console.ColorWhite)), uint(width))
var lines []string
line := ""

View File

@ -6,11 +6,14 @@ import (
"github.com/sqshq/sampler/asset"
"github.com/sqshq/sampler/component"
"github.com/sqshq/sampler/config"
"github.com/sqshq/sampler/data"
"image"
)
type AsciiBox struct {
*component.Component
*ui.Block
*data.Consumer
alert *data.Alert
text string
ascii string
style ui.Style
@ -34,10 +37,11 @@ func NewAsciiBox(c config.AsciiBoxConfig) *AsciiBox {
_ = render.LoadBindataFont(fontStr, options.FontName)
box := AsciiBox{
Component: component.NewComponent(c.ComponentConfig, config.TypeAsciiBox),
style: ui.NewStyle(*c.Color),
render: render,
options: options,
Block: component.NewBlock(c.Title, true),
Consumer: data.NewConsumer(),
style: ui.NewStyle(*c.Color),
render: render,
options: options,
}
go func() {
@ -46,6 +50,8 @@ func NewAsciiBox(c config.AsciiBoxConfig) *AsciiBox {
case sample := <-box.SampleChannel:
box.text = sample.Value
box.ascii, _ = box.render.RenderOpts(sample.Value, box.options)
case alert := <-box.AlertChannel:
box.alert = alert
}
}
}()
@ -69,4 +75,6 @@ func (a *AsciiBox) Draw(buffer *ui.Buffer) {
point = point.Add(image.Pt(1, 0))
}
}
component.RenderAlert(a.alert, a.Rectangle, buffer)
}

View File

@ -18,7 +18,9 @@ const (
)
type BarChart struct {
*component.Component
*ui.Block
*data.Consumer
alert *data.Alert
bars []Bar
scale int
maxValue float64
@ -35,10 +37,11 @@ type Bar struct {
func NewBarChart(c config.BarChartConfig) *BarChart {
chart := BarChart{
Component: component.NewComponent(c.ComponentConfig, config.TypeBarChart),
bars: []Bar{},
scale: *c.Scale,
maxValue: -math.MaxFloat64,
Block: component.NewBlock(c.Title, true),
Consumer: data.NewConsumer(),
bars: []Bar{},
scale: *c.Scale,
maxValue: -math.MaxFloat64,
}
for _, i := range c.Items {
@ -50,6 +53,8 @@ func NewBarChart(c config.BarChartConfig) *BarChart {
select {
case sample := <-chart.SampleChannel:
chart.consumeSample(sample)
case alert := <-chart.AlertChannel:
chart.alert = alert
}
}
}()
@ -57,13 +62,19 @@ func NewBarChart(c config.BarChartConfig) *BarChart {
return &chart
}
func (b *BarChart) consumeSample(sample data.Sample) {
func (b *BarChart) consumeSample(sample *data.Sample) {
b.count++
float, err := strconv.ParseFloat(sample.Value, 64)
if err != nil {
// TODO visual notification + check sample.Error
b.AlertChannel <- &data.Alert{
Title: "FAILED TO PARSE NUMBER",
Text: err.Error(),
Color: sample.Color,
}
return
}
index := -1
@ -102,8 +113,8 @@ func (b *BarChart) reselectMaxValue() {
b.maxValue = maxValue
}
func (b *BarChart) Draw(buf *ui.Buffer) {
b.Block.Draw(buf)
func (b *BarChart) Draw(buffer *ui.Buffer) {
b.Block.Draw(buffer)
barWidth := int(math.Ceil(float64(b.Inner.Dx()-2*barIndent-len(b.bars)*barIndent) / float64(len(b.bars))))
barXCoordinate := b.Inner.Min.X + barIndent
@ -122,7 +133,7 @@ func (b *BarChart) Draw(buf *ui.Buffer) {
for x := barXCoordinate; x < ui.MinInt(barXCoordinate+barWidth, b.Inner.Max.X-barIndent); x++ {
for y := b.Inner.Max.Y - 2; y >= maxYCoordinate; y-- {
c := ui.NewCell(console.SymbolShade, ui.NewStyle(bar.color))
buf.SetCell(c, image.Pt(x, y))
buffer.SetCell(c, image.Pt(x, y))
}
}
@ -130,7 +141,7 @@ func (b *BarChart) Draw(buf *ui.Buffer) {
labelXCoordinate := barXCoordinate +
int(float64(barWidth)/2) -
int(float64(rw.StringWidth(bar.label))/2)
buf.SetString(
buffer.SetString(
bar.label,
labelStyle,
image.Pt(labelXCoordinate, b.Inner.Max.Y-1))
@ -143,13 +154,15 @@ func (b *BarChart) Draw(buf *ui.Buffer) {
valueXCoordinate := barXCoordinate +
int(float64(barWidth)/2) -
int(float64(rw.StringWidth(value))/2)
buf.SetString(
buffer.SetString(
value,
labelStyle,
image.Pt(valueXCoordinate, maxYCoordinate-1))
barXCoordinate += barWidth + barIndent
}
component.RenderAlert(b.alert, b.Rectangle, buffer)
}
// TODO extract to utils

12
component/block.go Normal file
View File

@ -0,0 +1,12 @@
package component
import (
ui "github.com/gizak/termui/v3"
)
func NewBlock(title string, border bool) *ui.Block {
block := ui.NewBlock()
block.Title = title
block.Border = border
return block
}

View File

@ -7,9 +7,8 @@ import (
)
type Component struct {
ui.Block
data.Consumer
*Alerter
ui.Drawable
*data.Consumer
Type config.ComponentType
Title string
Position config.Position
@ -17,21 +16,15 @@ type Component struct {
RefreshRateMs int
}
func NewComponent(c config.ComponentConfig, t config.ComponentType) *Component {
consumer := data.NewConsumer()
block := *ui.NewBlock()
block.Title = c.Title
func NewComponent(dbl ui.Drawable, cmr *data.Consumer, cfg config.ComponentConfig, ct config.ComponentType) *Component {
return &Component{
Block: block,
Consumer: consumer,
Alerter: NewAlerter(consumer.AlertChannel),
Type: t,
Title: c.Title,
Position: c.Position,
Size: c.Size,
RefreshRateMs: *c.RefreshRateMs,
Drawable: dbl,
Consumer: cmr,
Type: ct,
Title: cfg.Title,
Position: cfg.Position,
Size: cfg.Size,
RefreshRateMs: *cfg.RefreshRateMs,
}
}

View File

@ -19,7 +19,9 @@ const (
)
type Gauge struct {
*component.Component
*ui.Block
*data.Consumer
alert *data.Alert
minValue float64
maxValue float64
curValue float64
@ -30,9 +32,10 @@ type Gauge struct {
func NewGauge(c config.GaugeConfig) *Gauge {
gauge := Gauge{
Component: component.NewComponent(c.ComponentConfig, config.TypeGauge),
scale: *c.Scale,
color: *c.Color,
Block: component.NewBlock(c.Title, true),
Consumer: data.NewConsumer(),
scale: *c.Scale,
color: *c.Color,
}
go func() {
@ -40,6 +43,8 @@ func NewGauge(c config.GaugeConfig) *Gauge {
select {
case sample := <-gauge.SampleChannel:
gauge.ConsumeSample(sample)
case alert := <-gauge.AlertChannel:
gauge.alert = alert
}
}
}()
@ -47,11 +52,16 @@ func NewGauge(c config.GaugeConfig) *Gauge {
return &gauge
}
func (g *Gauge) ConsumeSample(sample data.Sample) {
func (g *Gauge) ConsumeSample(sample *data.Sample) {
float, err := strconv.ParseFloat(sample.Value, 64)
if err != nil {
// TODO handle in Component
g.AlertChannel <- &data.Alert{
Title: "FAILED TO PARSE NUMBER",
Text: err.Error(),
Color: sample.Color,
}
return
}
switch sample.Label {
@ -67,9 +77,9 @@ func (g *Gauge) ConsumeSample(sample data.Sample) {
}
}
func (g *Gauge) Draw(buf *ui.Buffer) {
func (g *Gauge) Draw(buffer *ui.Buffer) {
g.Block.Draw(buf)
g.Block.Draw(buffer)
percent := 0.0
if g.curValue != 0 && g.maxValue != g.minValue {
@ -85,7 +95,7 @@ func (g *Gauge) Draw(buf *ui.Buffer) {
} else if barWidth > g.Dx()-2 {
barWidth = g.Dx() - 2
}
buf.Fill(
buffer.Fill(
ui.NewCell(console.SymbolVerticalBar, ui.NewStyle(g.color)),
image.Rect(g.Inner.Min.X+1, g.Inner.Min.Y, g.Inner.Min.X+barWidth, g.Inner.Max.Y),
)
@ -99,9 +109,11 @@ func (g *Gauge) Draw(buf *ui.Buffer) {
if labelXCoordinate+i+1 <= g.Inner.Min.X+barWidth {
style = ui.NewStyle(console.ColorWhite, ui.ColorClear)
}
buf.SetCell(ui.NewCell(char, style), image.Pt(labelXCoordinate+i, labelYCoordinate))
buffer.SetCell(ui.NewCell(char, style), image.Pt(labelXCoordinate+i, labelYCoordinate))
}
}
component.RenderAlert(g.alert, g.Rectangle, buffer)
}
// TODO extract to utils

View File

@ -57,7 +57,7 @@ func NewLayout(width, height int, statusline *component.StatusBar, menu *compone
}
}
func (l *Layout) AddComponent(cpt *component.Component, Type config.ComponentType) {
func (l *Layout) AddComponent(cpt *component.Component) {
l.Components = append(l.Components, cpt)
}
@ -76,7 +76,7 @@ func (l *Layout) HandleConsoleEvent(e string) {
l.changeMode(ModeDefault)
} else {
if selected.Type == config.TypeRunChart {
selected.CommandChannel <- data.Command{Type: runchart.CommandDisableSelection}
selected.CommandChannel <- &data.Command{Type: runchart.CommandDisableSelection}
}
l.menu.Idle()
l.changeMode(ModePause)
@ -98,7 +98,7 @@ func (l *Layout) HandleConsoleEvent(e string) {
case component.MenuOptionPinpoint:
l.changeMode(ModeChartPinpoint)
l.menu.Idle()
selected.CommandChannel <- data.Command{Type: runchart.CommandMoveSelection, Value: 0}
selected.CommandChannel <- &data.Command{Type: runchart.CommandMoveSelection, Value: 0}
case component.MenuOptionResume:
l.changeMode(ModeDefault)
l.menu.Idle()
@ -112,7 +112,7 @@ func (l *Layout) HandleConsoleEvent(e string) {
case console.KeyEsc:
switch l.mode {
case ModeChartPinpoint:
selected.CommandChannel <- data.Command{Type: runchart.CommandDisableSelection}
selected.CommandChannel <- &data.Command{Type: runchart.CommandDisableSelection}
fallthrough
case ModeComponentSelect:
fallthrough
@ -126,7 +126,7 @@ func (l *Layout) HandleConsoleEvent(e string) {
l.changeMode(ModeComponentSelect)
l.menu.Highlight(l.getComponent(l.selection))
case ModeChartPinpoint:
selected.CommandChannel <- data.Command{Type: runchart.CommandMoveSelection, Value: -1}
selected.CommandChannel <- &data.Command{Type: runchart.CommandMoveSelection, Value: -1}
case ModeComponentSelect:
l.moveSelection(e)
l.menu.Highlight(l.getComponent(l.selection))
@ -141,7 +141,7 @@ func (l *Layout) HandleConsoleEvent(e string) {
l.changeMode(ModeComponentSelect)
l.menu.Highlight(l.getComponent(l.selection))
case ModeChartPinpoint:
selected.CommandChannel <- data.Command{Type: runchart.CommandMoveSelection, Value: 1}
selected.CommandChannel <- &data.Command{Type: runchart.CommandMoveSelection, Value: 1}
case ModeComponentSelect:
l.moveSelection(e)
l.menu.Highlight(l.getComponent(l.selection))

View File

@ -219,7 +219,7 @@ func (m *Menu) renderOptions(buffer *ui.Buffer) {
}
func (m *Menu) updateDimensions() {
r := m.component.Block.GetRect()
r := m.component.GetRect()
m.SetRect(r.Min.X, r.Min.Y, r.Max.X, r.Max.Y)
}

View File

@ -40,7 +40,9 @@ const (
)
type RunChart struct {
*component.Component
*ui.Block
*data.Consumer
alert *data.Alert
lines []TimeLine
grid ChartGrid
timescale time.Duration
@ -79,7 +81,8 @@ type ValueExtrema struct {
func NewRunChart(c config.RunChartConfig) *RunChart {
chart := RunChart{
Component: component.NewComponent(c.ComponentConfig, config.TypeRunChart),
Block: component.NewBlock(c.Title, true),
Consumer: data.NewConsumer(),
lines: []TimeLine{},
timescale: calculateTimescale(*c.RefreshRateMs),
mutex: &sync.Mutex{},
@ -97,6 +100,8 @@ func NewRunChart(c config.RunChartConfig) *RunChart {
select {
case sample := <-chart.SampleChannel:
chart.consumeSample(sample)
case alert := <-chart.AlertChannel:
chart.alert = alert
case command := <-chart.CommandChannel:
switch command.Type {
case CommandDisableSelection:
@ -134,7 +139,7 @@ func (c *RunChart) Draw(buffer *ui.Buffer) {
c.renderAxes(buffer)
c.renderLines(buffer, drawArea)
c.renderLegend(buffer, drawArea)
c.RenderAlert(buffer, c.Rectangle)
component.RenderAlert(c.alert, c.Rectangle, buffer)
c.mutex.Unlock()
}
@ -148,16 +153,17 @@ func (c *RunChart) AddLine(Label string, color ui.Color) {
c.lines = append(c.lines, line)
}
func (c *RunChart) consumeSample(sample data.Sample) {
func (c *RunChart) consumeSample(sample *data.Sample) {
float, err := strconv.ParseFloat(sample.Value, 64)
if err != nil {
c.AlertChannel <- data.Alert{
Title: "SAMPLING FAILURE",
c.AlertChannel <- &data.Alert{
Title: "FAILED TO PARSE NUMBER",
Text: err.Error(),
Color: sample.Color,
}
return
}
c.mutex.Lock()

View File

@ -2,11 +2,10 @@ package data
import ui "github.com/gizak/termui/v3"
// TODO interface here, move fields declaration in the Component
type Consumer struct {
SampleChannel chan Sample
AlertChannel chan Alert
CommandChannel chan Command
SampleChannel chan *Sample
AlertChannel chan *Alert
CommandChannel chan *Command
}
type Sample struct {
@ -26,10 +25,10 @@ type Command struct {
Value interface{}
}
func NewConsumer() Consumer {
return Consumer{
SampleChannel: make(chan Sample),
AlertChannel: make(chan Alert),
CommandChannel: make(chan Command),
func NewConsumer() *Consumer {
return &Consumer{
SampleChannel: make(chan *Sample),
AlertChannel: make(chan *Alert),
CommandChannel: make(chan *Command),
}
}

View File

@ -5,13 +5,13 @@ import (
)
type Sampler struct {
consumer Consumer
consumer *Consumer
items []Item
triggers []Trigger
triggersChannel chan Sample
triggersChannel chan *Sample
}
func NewSampler(consumer Consumer, items []Item, triggers []Trigger, rateMs int) Sampler {
func NewSampler(consumer *Consumer, items []Item, triggers []Trigger, rateMs int) Sampler {
ticker := time.NewTicker(
time.Duration(rateMs * int(time.Millisecond)),
@ -21,7 +21,7 @@ func NewSampler(consumer Consumer, items []Item, triggers []Trigger, rateMs int)
consumer,
items,
triggers,
make(chan Sample),
make(chan *Sample),
}
go func() {
@ -51,11 +51,11 @@ func (s *Sampler) sample(item Item) {
val, err := item.nextValue()
if err == nil {
sample := Sample{Label: item.Label, Value: val, Color: item.Color}
sample := &Sample{Label: item.Label, Value: val, Color: item.Color}
s.consumer.SampleChannel <- sample
s.triggersChannel <- sample
} else {
s.consumer.AlertChannel <- Alert{
s.consumer.AlertChannel <- &Alert{
Title: "SAMPLING FAILURE",
Text: err.Error(),
Color: item.Color,

View File

@ -18,8 +18,8 @@ const (
type Trigger struct {
title string
condition string
actions Actions
consumer Consumer
actions *Actions
consumer *Consumer
valuesByLabel map[string]Values
player *asset.AudioPlayer
digitsRegexp *regexp.Regexp
@ -37,7 +37,7 @@ type Values struct {
previous string
}
func NewTriggers(cfgs []config.TriggerConfig, consumer Consumer, player *asset.AudioPlayer) []Trigger {
func NewTriggers(cfgs []config.TriggerConfig, consumer *Consumer, player *asset.AudioPlayer) []Trigger {
triggers := make([]Trigger, 0)
@ -48,7 +48,7 @@ func NewTriggers(cfgs []config.TriggerConfig, consumer Consumer, player *asset.A
return triggers
}
func NewTrigger(config config.TriggerConfig, consumer Consumer, player *asset.AudioPlayer) Trigger {
func NewTrigger(config config.TriggerConfig, consumer *Consumer, player *asset.AudioPlayer) Trigger {
return Trigger{
title: config.Title,
condition: config.Condition,
@ -56,7 +56,7 @@ func NewTrigger(config config.TriggerConfig, consumer Consumer, player *asset.Au
valuesByLabel: make(map[string]Values),
player: player,
digitsRegexp: regexp.MustCompile("[^0-9]+"),
actions: Actions{
actions: &Actions{
terminalBell: *config.Actions.TerminalBell,
sound: *config.Actions.Sound,
visual: *config.Actions.Visual,
@ -65,7 +65,7 @@ func NewTrigger(config config.TriggerConfig, consumer Consumer, player *asset.Au
}
}
func (t *Trigger) Execute(sample Sample) {
func (t *Trigger) Execute(sample *Sample) {
if t.evaluate(sample) {
if t.actions.terminalBell {
@ -77,7 +77,7 @@ func (t *Trigger) Execute(sample Sample) {
}
if t.actions.visual {
t.consumer.AlertChannel <- Alert{
t.consumer.AlertChannel <- &Alert{
Title: t.title,
Text: fmt.Sprintf("%s: %v", sample.Label, sample.Value),
Color: sample.Color,
@ -90,7 +90,7 @@ func (t *Trigger) Execute(sample Sample) {
}
}
func (t *Trigger) evaluate(sample Sample) bool {
func (t *Trigger) evaluate(sample *Sample) bool {
if values, ok := t.valuesByLabel[sample.Label]; ok {
values.previous = values.current
@ -103,7 +103,7 @@ func (t *Trigger) evaluate(sample Sample) bool {
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()}
t.consumer.AlertChannel <- &Alert{Title: "TRIGGER CONDITION FAILURE", Text: err.Error()}
}
return t.digitsRegexp.ReplaceAllString(string(output), "") == TrueIndicator

12
main.go
View File

@ -30,30 +30,34 @@ func main() {
for _, c := range cfg.RunCharts {
chart := runchart.NewRunChart(c)
cpt := component.NewComponent(chart, chart.Consumer, c.ComponentConfig, config.TypeRunChart)
triggers := data.NewTriggers(c.Triggers, chart.Consumer, player)
data.NewSampler(chart.Consumer, data.NewItems(c.Items), triggers, *c.RefreshRateMs)
lout.AddComponent(chart.Component, config.TypeRunChart)
lout.AddComponent(cpt)
}
for _, a := range cfg.AsciiBoxes {
box := asciibox.NewAsciiBox(a)
cpt := component.NewComponent(box, box.Consumer, a.ComponentConfig, config.TypeRunChart)
triggers := data.NewTriggers(a.Triggers, box.Consumer, player)
data.NewSampler(box.Consumer, data.NewItems([]config.Item{a.Item}), triggers, *a.RefreshRateMs)
lout.AddComponent(box.Component, config.TypeAsciiBox)
lout.AddComponent(cpt)
}
for _, b := range cfg.BarCharts {
chart := barchart.NewBarChart(b)
cpt := component.NewComponent(chart, chart.Consumer, b.ComponentConfig, config.TypeRunChart)
triggers := data.NewTriggers(b.Triggers, chart.Consumer, player)
data.NewSampler(chart.Consumer, data.NewItems(b.Items), triggers, *b.RefreshRateMs)
lout.AddComponent(chart.Component, config.TypeBarChart)
lout.AddComponent(cpt)
}
for _, gc := range cfg.Gauges {
g := gauge.NewGauge(gc)
cpt := component.NewComponent(g, g.Consumer, gc.ComponentConfig, config.TypeRunChart)
triggers := data.NewTriggers(gc.Triggers, g.Consumer, player)
data.NewSampler(g.Consumer, data.NewItems(gc.Items), triggers, *gc.RefreshRateMs)
lout.AddComponent(g.Component, config.TypeGauge)
lout.AddComponent(cpt)
}
handler := event.NewHandler(lout)