VOL-2496 Add "event listen" command to voltctl
Change-Id: I8f1fb34b55f56c8125142ac289e2f19fc170d804
diff --git a/pkg/filter/filter.go b/pkg/filter/filter.go
index d78a21a..0c18394 100644
--- a/pkg/filter/filter.go
+++ b/pkg/filter/filter.go
@@ -113,6 +113,31 @@
return result, nil
}
+// returns False if the filter does not match
+// returns true if the filter does match or the operation is unsupported
+func testField(v FilterTerm, field reflect.Value) bool {
+ switch v.Op {
+ case RE:
+ if !v.re.MatchString(fmt.Sprintf("%v", field)) {
+ return false
+ }
+ case EQ:
+ // This seems to work for most comparisons
+ if fmt.Sprintf("%v", field) != v.Value {
+ return false
+ }
+ case NE:
+ // This seems to work for most comparisons
+ if fmt.Sprintf("%v", field) == v.Value {
+ return false
+ }
+ default:
+ // For unsupported operations, always pass
+ }
+
+ return true
+}
+
func (f Filter) Evaluate(item interface{}) bool {
val := reflect.ValueOf(item)
@@ -122,23 +147,22 @@
return false
}
- switch v.Op {
- case RE:
- if !v.re.MatchString(fmt.Sprintf("%v", field)) {
+ if (field.Kind() == reflect.Slice) || (field.Kind() == reflect.Array) {
+ // For an array, check to see if any item matches
+ someMatch := false
+ for i := 0; i < field.Len(); i++ {
+ arrayElem := field.Index(i)
+ if testField(v, arrayElem) {
+ someMatch = true
+ }
+ }
+ if !someMatch {
return false
}
- case EQ:
- // This seems to work for most comparisons
- if fmt.Sprintf("%v", field) != v.Value {
+ } else {
+ if !testField(v, field) {
return false
}
- case NE:
- // This seems to work for most comparisons
- if fmt.Sprintf("%v", field) == v.Value {
- return false
- }
- default:
- // For unsupported operations, always pass
}
}
return true
diff --git a/pkg/format/formatter.go b/pkg/format/formatter.go
index eaa42eb..c42b8ea 100644
--- a/pkg/format/formatter.go
+++ b/pkg/format/formatter.go
@@ -16,9 +16,13 @@
package format
import (
+ "bytes"
+ "errors"
+ "fmt"
"io"
"reflect"
"regexp"
+ "strconv"
"strings"
"text/tabwriter"
"text/template"
@@ -29,6 +33,52 @@
type Format string
+/* TrimAndPad
+ *
+ * Modify `s` so that it is exactly `l` characters long, removing
+ * characters from the end, or adding spaces as necessary.
+ */
+
+func TrimAndPad(s string, l int) string {
+ // TODO: support right justification if a negative number is passed
+ if len(s) > l {
+ s = s[:l]
+ }
+ return s + strings.Repeat(" ", l-len(s))
+}
+
+/* GetHeaderString
+ *
+ * From a template, extract the set of column names.
+ */
+
+func GetHeaderString(tmpl *template.Template, nameLimit int) string {
+ var header string
+ for _, n := range tmpl.Tree.Root.Nodes {
+ switch n.Type() {
+ case parse.NodeText:
+ header += n.String()
+ case parse.NodeString:
+ header += n.String()
+ case parse.NodeAction:
+ found := nameFinder.FindStringSubmatch(n.String())
+ if len(found) == 2 {
+ if nameLimit > 0 {
+ parts := strings.Split(found[1], ".")
+ start := len(parts) - nameLimit
+ if start < 0 {
+ start = 0
+ }
+ header += strings.ToUpper(strings.Join(parts[start:], "."))
+ } else {
+ header += strings.ToUpper(found[1])
+ }
+ }
+ }
+ }
+ return header
+}
+
func (f Format) IsTable() bool {
return strings.HasPrefix(string(f), "table")
}
@@ -48,29 +98,8 @@
}
if f.IsTable() && withHeaders {
- var header string
- for _, n := range tmpl.Tree.Root.Nodes {
- switch n.Type() {
- case parse.NodeText:
- header += n.String()
- case parse.NodeString:
- header += n.String()
- case parse.NodeAction:
- found := nameFinder.FindStringSubmatch(n.String())
- if len(found) == 2 {
- if nameLimit > 0 {
- parts := strings.Split(found[1], ".")
- start := len(parts) - nameLimit
- if start < 0 {
- start = 0
- }
- header += strings.ToUpper(strings.Join(parts[start:], "."))
- } else {
- header += strings.ToUpper(found[1])
- }
- }
- }
- }
+ header := GetHeaderString(tmpl, nameLimit)
+
if _, err = tabWriter.Write([]byte(header)); err != nil {
return err
}
@@ -121,3 +150,73 @@
return nil
}
+
+/*
+ * ExecuteFixedWidth
+ *
+ * Formats a table row using a set of fixed column widths. Used for streaming
+ * output where column widths cannot be automatically determined because only
+ * one line of the output is available at a time.
+ *
+ * Assumes the format uses tab as a field delimiter.
+ *
+ * columnWidths: struct that contains column widths
+ * header: If true return the header. If false then evaluate data and return data.
+ * data: Data to evaluate
+ */
+
+func (f Format) ExecuteFixedWidth(columnWidths interface{}, header bool, data interface{}) (string, error) {
+ if !f.IsTable() {
+ return "", errors.New("Fixed width is only available on table format")
+ }
+
+ outputAs := strings.TrimPrefix(string(f), "table")
+ tmpl, err := template.New("output").Parse(string(outputAs))
+ if err != nil {
+ return "", fmt.Errorf("Failed to parse template: %v", err)
+ }
+
+ var buf bytes.Buffer
+ var tabSepOutput string
+
+ if header {
+ // Caller wants the table header.
+ tabSepOutput = GetHeaderString(tmpl, 1)
+ } else {
+ // Caller wants the data.
+ err = tmpl.Execute(&buf, data)
+ if err != nil {
+ return "", fmt.Errorf("Failed to execute template: %v", err)
+ }
+ tabSepOutput = buf.String()
+ }
+
+ // Extract the column width constants by running the template on the
+ // columnWidth structure. This will cause text.template to split the
+ // column widths exactly like it did the output (i.e. separated by
+ // tab characters)
+ buf.Reset()
+ err = tmpl.Execute(&buf, columnWidths)
+ if err != nil {
+ return "", fmt.Errorf("Failed to execute template on widths: %v", err)
+ }
+ tabSepWidth := buf.String()
+
+ // Loop through the fields and widths, printing each field to the
+ // preset width.
+ output := ""
+ outParts := strings.Split(tabSepOutput, "\t")
+ widthParts := strings.Split(tabSepWidth, "\t")
+ for i, outPart := range outParts {
+ width, err := strconv.Atoi(widthParts[i])
+ if err != nil {
+ return "", fmt.Errorf("Failed to parse width %s: %v", widthParts[i], err)
+ }
+ output = output + TrimAndPad(outPart, width) + " "
+ }
+
+ // remove any trailing spaces
+ output = strings.TrimRight(output, " ")
+
+ return output, nil
+}
diff --git a/pkg/model/utils.go b/pkg/model/utils.go
index d30324f..15ef51f 100644
--- a/pkg/model/utils.go
+++ b/pkg/model/utils.go
@@ -28,3 +28,11 @@
eValue := msg.FindFieldDescriptorByName(name).GetEnumType().FindValueByName(value)
msg.SetFieldByName(name, eValue.GetNumber())
}
+
+func GetEnumString(msg *dynamic.Message, name string, value int32) string {
+ eValue := msg.FindFieldDescriptorByName(name).GetEnumType().FindValueByNumber(value)
+ if eValue == nil {
+ panic("eValue is nil")
+ }
+ return eValue.GetName()
+}