mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-02 18:39:05 +01:00
quickplot
This commit is contained in:
parent
a1693ade6c
commit
0f03bd2e49
190
pkg/flatjson/flatjson.go
Normal file
190
pkg/flatjson/flatjson.go
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
// Copyright 2024, Command Line Inc.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package flatjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/wavetermdev/waveterm/pkg/ijson"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FlatJsonVal struct {
|
||||||
|
Key string
|
||||||
|
Val any
|
||||||
|
}
|
||||||
|
|
||||||
|
type FlatJson []FlatJsonVal
|
||||||
|
|
||||||
|
func (fj FlatJson) Pack() ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for _, fjv := range fj {
|
||||||
|
buf.WriteString(fjv.Key)
|
||||||
|
buf.WriteString("=")
|
||||||
|
barr, err := json.Marshal(fjv.Val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error marshalling key %s: %w", fjv.Key, err)
|
||||||
|
}
|
||||||
|
buf.Write(barr)
|
||||||
|
buf.WriteString("\n")
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unpack(barr []byte) (FlatJson, error) {
|
||||||
|
rtn := make(FlatJson, 0)
|
||||||
|
// dont use split, keep a pos and scan for \n
|
||||||
|
pos := 0
|
||||||
|
lineNum := 1
|
||||||
|
for {
|
||||||
|
// find newline
|
||||||
|
newlinePos := bytes.IndexByte(barr[pos:], '\n')
|
||||||
|
if newlinePos == -1 {
|
||||||
|
return nil, fmt.Errorf("no newline found, possibly truncated")
|
||||||
|
}
|
||||||
|
line := barr[pos : pos+newlinePos]
|
||||||
|
eqPos := bytes.IndexByte(line, '=')
|
||||||
|
if eqPos == -1 {
|
||||||
|
return nil, fmt.Errorf("no = found on line %d", lineNum)
|
||||||
|
}
|
||||||
|
key := string(line[:eqPos])
|
||||||
|
valStr := string(line[eqPos+1:])
|
||||||
|
var val any
|
||||||
|
err := json.Unmarshal([]byte(valStr), &val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error unmarshalling value on line %d: %w", lineNum, err)
|
||||||
|
}
|
||||||
|
rtn = append(rtn, FlatJsonVal{Key: key, Val: val})
|
||||||
|
lineNum++
|
||||||
|
pos += newlinePos
|
||||||
|
if pos == len(barr) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rtn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyToPath(key string) []any {
|
||||||
|
keyParts := strings.Split(key, ":")
|
||||||
|
path := make([]any, len(keyParts))
|
||||||
|
for idx, keyPart := range keyParts {
|
||||||
|
ival, isInt := asInt(keyPart)
|
||||||
|
if isInt {
|
||||||
|
path[idx] = ival
|
||||||
|
} else {
|
||||||
|
path[idx] = keyPart
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func asInt(s string) (int, bool) {
|
||||||
|
ival, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return ival, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func reverseAndRemoteDups(fj FlatJson) FlatJson {
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
rtn := make(FlatJson, 0)
|
||||||
|
// iterate backwards
|
||||||
|
for i := len(fj) - 1; i >= 0; i-- {
|
||||||
|
if !seen[fj[i].Key] {
|
||||||
|
seen[fj[i].Key] = true
|
||||||
|
rtn = append(rtn, fj[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rtn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fj FlatJson) ToJson(budget int) (map[string]any, map[string]error) {
|
||||||
|
rtn := make(map[string]any)
|
||||||
|
errs := make(map[string]error)
|
||||||
|
fj = reverseAndRemoteDups(fj)
|
||||||
|
opts := ijson.SetPathOpts{Budget: budget}
|
||||||
|
for _, fjv := range fj {
|
||||||
|
if fjv.Key == "" {
|
||||||
|
// must be a map
|
||||||
|
valMap, ok := fjv.Val.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
errs[""] = fmt.Errorf("bad key, does not produce a map")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rtn = valMap
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
path := keyToPath(fjv.Key)
|
||||||
|
newRtn, err := ijson.SetPath(rtn, path, fjv.Val, &opts)
|
||||||
|
if err != nil {
|
||||||
|
errs[fjv.Key] = err
|
||||||
|
} else {
|
||||||
|
newRtnMap, ok := newRtn.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
errs[fjv.Key] = fmt.Errorf("bad key, does not produce a map")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rtn = newRtnMap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rtn, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromJsonArray(v []any, prefix string, fj *FlatJson) error {
|
||||||
|
for idx, val := range v {
|
||||||
|
newKey := fmt.Sprintf("%s:%d", prefix, idx)
|
||||||
|
switch val := val.(type) {
|
||||||
|
case map[string]any:
|
||||||
|
err := fromJsonMap(val, newKey, fj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case []any:
|
||||||
|
err := fromJsonArray(val, newKey, fj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
*fj = append(*fj, FlatJsonVal{newKey, fmt.Sprintf("%v", val)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var validKeyRe = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_-]*$`)
|
||||||
|
|
||||||
|
func fromJsonMap(v map[string]any, prefix string, fj *FlatJson) error {
|
||||||
|
for key, val := range v {
|
||||||
|
if !validKeyRe.MatchString(key) {
|
||||||
|
return fmt.Errorf("in map at prefix %q, invalid key %q", prefix, key)
|
||||||
|
}
|
||||||
|
newKey := key
|
||||||
|
if prefix != "" {
|
||||||
|
newKey = prefix + ":" + key
|
||||||
|
}
|
||||||
|
switch val := val.(type) {
|
||||||
|
case map[string]any:
|
||||||
|
fromJsonMap(val, newKey, fj)
|
||||||
|
case []any:
|
||||||
|
fromJsonArray(val, newKey, fj)
|
||||||
|
default:
|
||||||
|
*fj = append(*fj, FlatJsonVal{newKey, fmt.Sprintf("%v", val)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromJson(v map[string]any) (FlatJson, error) {
|
||||||
|
rtn := make(FlatJson, 0)
|
||||||
|
err := fromJsonMap(v, "", &rtn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rtn
|
||||||
|
}
|
@ -385,3 +385,34 @@ type WaveNotificationOptions struct {
|
|||||||
Body string `json:"body,omitempty"`
|
Body string `json:"body,omitempty"`
|
||||||
Silent bool `json:"silent,omitempty"`
|
Silent bool `json:"silent,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PlotSeries struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
Key string `json:"key,omitempty"`
|
||||||
|
Color string `json:"color,omitempty"`
|
||||||
|
Fill string `json:"fill,omitempty"`
|
||||||
|
Width float64 `json:"width,omitempty"`
|
||||||
|
Marker string `json:"marker,omitempty"`
|
||||||
|
MarkerSize float64 `json:"markersize,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlotAxis struct {
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
Color string `json:"color,omitempty"`
|
||||||
|
MinVal float64 `json:"minval,omitempty"`
|
||||||
|
MaxVal float64 `json:"maxval,omitempty"`
|
||||||
|
AxisType string `json:"axistype,omitempty"`
|
||||||
|
Formatter string `json:"formatter,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlotOptions struct {
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
Bg string `json:"bg,omitempty"`
|
||||||
|
BgOpacity float64 `json:"bg:opacity,omitempty"`
|
||||||
|
BgBlendMode string `json:"bg:blendmode,omitempty"`
|
||||||
|
Series []PlotSeries `json:"series"`
|
||||||
|
XAxis *PlotAxis `json:"xaxis,omitempty"`
|
||||||
|
YAxis *PlotAxis `json:"yaxis,omitempty"`
|
||||||
|
MaxData int `json:"maxdata,omitempty"`
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user