diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-13 09:45:09 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-13 09:45:09 +0300 |
| commit | 6c7a5d5fb3e88068799fb414e316b6bec31015e9 (patch) | |
| tree | c47a73f3fd3d3a1089f77e00934bcdb45168965c | |
| parent | 50d5220ed5a2187dbf548d70f3d795a39f7bfd00 (diff) | |
split globalfilter presentation and parsing into sub-packages
Move ParseDurationNs to globalfilter/parser and move CompareOpSymbol,
AppendStringSummary, AppendNumericSummary, FilterSummary to
globalfilter/presenter. The domain Filter type retains only matching,
equality, clone, and active-predicate logic. All callers updated;
tests for the new sub-packages added.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| -rw-r--r-- | internal/globalfilter/filter.go | 105 | ||||
| -rw-r--r-- | internal/globalfilter/filter_presenter_test.go | 75 | ||||
| -rw-r--r-- | internal/globalfilter/filter_test.go | 60 | ||||
| -rw-r--r-- | internal/globalfilter/parser/parser.go | 49 | ||||
| -rw-r--r-- | internal/globalfilter/parser/parser_test.go | 37 | ||||
| -rw-r--r-- | internal/globalfilter/presenter/presenter.go | 85 | ||||
| -rw-r--r-- | internal/globalfilter/presenter/presenter_test.go | 83 | ||||
| -rw-r--r-- | internal/tui/dashboard/model.go | 3 | ||||
| -rw-r--r-- | internal/tui/eventstream/filter.go | 9 | ||||
| -rw-r--r-- | internal/tui/eventstream/filter_test.go | 8 | ||||
| -rw-r--r-- | internal/tui/eventstream/render.go | 3 | ||||
| -rw-r--r-- | internal/tui/filterstack.go | 11 | ||||
| -rw-r--r-- | internal/tui/tracefilter/model.go | 3 |
13 files changed, 353 insertions, 178 deletions
diff --git a/internal/globalfilter/filter.go b/internal/globalfilter/filter.go index ab3b0fb..01213aa 100644 --- a/internal/globalfilter/filter.go +++ b/internal/globalfilter/filter.go @@ -1,10 +1,7 @@ package globalfilter import ( - "fmt" - "strconv" "strings" - "time" ) type CompareOp int @@ -176,77 +173,6 @@ func (f Filter) IsActive() bool { return false } -// Summary returns a compact human-readable description of the active filter -// predicates, e.g. "syscall~read pid=1234". Returns "all" when no predicates -// are set. -func (f Filter) Summary() string { - parts := make([]string, 0, 10) - if f.ErrorsOnly { - parts = append(parts, "errors") - } - parts = AppendStringSummary(parts, "syscall", f.Syscall) - parts = AppendStringSummary(parts, "comm", f.Comm) - parts = AppendStringSummary(parts, "file", f.File) - parts = AppendNumericSummary(parts, "pid", f.PID, false) - parts = AppendNumericSummary(parts, "tid", f.TID, false) - parts = AppendNumericSummary(parts, "fd", f.FD, false) - parts = AppendNumericSummary(parts, "latency", f.LatencyNs, true) - parts = AppendNumericSummary(parts, "gap", f.GapNs, true) - parts = AppendNumericSummary(parts, "bytes", f.Bytes, false) - parts = AppendNumericSummary(parts, "ret", f.RetVal, false) - if len(parts) == 0 { - return "all" - } - return strings.Join(parts, " ") -} - -func ParseDurationNs(input string) (int64, error) { - s := strings.TrimSpace(strings.ToLower(input)) - if s == "" { - return 0, fmt.Errorf("empty duration") - } - s = strings.ReplaceAll(s, "µs", "us") - s = strings.ReplaceAll(s, "μs", "us") - if onlyDigits(s) || strings.HasPrefix(s, "-") && onlyDigits(s[1:]) { - v, err := strconv.ParseInt(s, 10, 64) - if err != nil { - return 0, err - } - return v, nil - } - d, err := time.ParseDuration(s) - if err != nil { - return 0, err - } - return d.Nanoseconds(), nil -} - -// AppendStringSummary appends a "name~pattern" token to parts when the filter -// is non-nil and non-empty, then returns the updated slice. -func AppendStringSummary(parts []string, name string, sf *StringFilter) []string { - if sf == nil { - return parts - } - pattern := strings.TrimSpace(sf.Pattern) - if pattern == "" { - return parts - } - return append(parts, fmt.Sprintf("%s~%s", name, pattern)) -} - -// AppendNumericSummary appends a "nameOPvalue" token to parts when the filter -// is non-nil, formatting the value as a duration string when duration is true. -func AppendNumericSummary(parts []string, name string, nf *NumericFilter, duration bool) []string { - if nf == nil { - return parts - } - value := strconv.FormatInt(nf.Value, 10) - if duration { - value = time.Duration(nf.Value).String() - } - return append(parts, fmt.Sprintf("%s%s%s", name, CompareOpSymbol(nf.Op), value)) -} - func matchString(sf *StringFilter, value string) bool { if sf == nil { return true @@ -298,26 +224,6 @@ func matchNumeric(nf *NumericFilter, value int64) bool { } } -// CompareOpSymbol returns the summary/render symbol for a numeric comparison operator. -func CompareOpSymbol(op CompareOp) string { - switch op { - case OpEq: - return "=" - case OpNeq: - return "!=" - case OpGt: - return ">" - case OpGte: - return ">=" - case OpLt: - return "<" - case OpLte: - return "<=" - default: - return "?" - } -} - func cloneFilter[T any](in *T) *T { if in == nil { return nil @@ -337,14 +243,3 @@ func sameFilter[T comparable](left, right *T) bool { } } -func onlyDigits(s string) bool { - if s == "" { - return false - } - for _, ch := range s { - if ch < '0' || ch > '9' { - return false - } - } - return true -} diff --git a/internal/globalfilter/filter_presenter_test.go b/internal/globalfilter/filter_presenter_test.go new file mode 100644 index 0000000..7fb5c17 --- /dev/null +++ b/internal/globalfilter/filter_presenter_test.go @@ -0,0 +1,75 @@ +// External test package so that presenter and parser sub-packages can be +// imported without creating an import cycle with globalfilter itself. +package globalfilter_test + +import ( + "strings" + "testing" + + "ior/internal/globalfilter" + "ior/internal/globalfilter/parser" + "ior/internal/globalfilter/presenter" +) + +func TestFilterSummaryViaPresenter(t *testing.T) { + filter := globalfilter.Filter{ + ErrorsOnly: true, + Syscall: &globalfilter.StringFilter{Pattern: "read"}, + PID: &globalfilter.NumericFilter{Op: globalfilter.OpEq, Value: 1234}, + LatencyNs: &globalfilter.NumericFilter{Op: globalfilter.OpGt, Value: 1_000_000}, + } + got := presenter.FilterSummary(filter) + for _, want := range []string{"errors", "syscall~read", "pid=1234", "latency>1ms"} { + if !strings.Contains(got, want) { + t.Fatalf("FilterSummary() = %q, missing %q", got, want) + } + } + + // Zero-value filter should produce "all". + if got := presenter.FilterSummary(globalfilter.Filter{}); got != "all" { + t.Fatalf("FilterSummary(zero) = %q, want \"all\"", got) + } +} + +func TestParseDurationNsViaParser(t *testing.T) { + for _, tc := range []struct { + in string + want int64 + }{ + {in: "1ms", want: 1_000_000}, + {in: "10us", want: 10_000}, + {in: "500ns", want: 500}, + {in: "42", want: 42}, + } { + gotNs, err := parser.ParseDurationNs(tc.in) + if err != nil { + t.Fatalf("ParseDurationNs(%q) err = %v", tc.in, err) + } + if gotNs != tc.want { + t.Fatalf("ParseDurationNs(%q) = %d, want %d", tc.in, gotNs, tc.want) + } + } + if _, err := parser.ParseDurationNs("garbage"); err == nil { + t.Fatalf("ParseDurationNs(garbage) expected error") + } +} + +func TestCompareOpSymbolViaPresenter(t *testing.T) { + for _, tc := range []struct { + name string + op globalfilter.CompareOp + want string + }{ + {name: "eq", op: globalfilter.OpEq, want: "="}, + {name: "neq", op: globalfilter.OpNeq, want: "!="}, + {name: "gt", op: globalfilter.OpGt, want: ">"}, + {name: "gte", op: globalfilter.OpGte, want: ">="}, + {name: "lt", op: globalfilter.OpLt, want: "<"}, + {name: "lte", op: globalfilter.OpLte, want: "<="}, + {name: "unknown", op: globalfilter.CompareOp(99), want: "?"}, + } { + if got := presenter.CompareOpSymbol(tc.op); got != tc.want { + t.Fatalf("%s: CompareOpSymbol(%v) = %q, want %q", tc.name, tc.op, got, tc.want) + } + } +} diff --git a/internal/globalfilter/filter_test.go b/internal/globalfilter/filter_test.go index 7d36c88..c386673 100644 --- a/internal/globalfilter/filter_test.go +++ b/internal/globalfilter/filter_test.go @@ -1,7 +1,6 @@ package globalfilter import ( - "strings" "testing" ) @@ -56,9 +55,6 @@ func TestFilterZeroValueMatchesAll(t *testing.T) { if filter.IsActive() { t.Fatalf("zero-value filter should be inactive") } - if got := filter.Summary(); got != "all" { - t.Fatalf("Summary() = %q, want all", got) - } } func TestFilterStringAndNumericMatching(t *testing.T) { @@ -132,62 +128,6 @@ func TestFilterErrorsOnlyAndClone(t *testing.T) { } } -func TestFilterSummaryAndDurationParsing(t *testing.T) { - filter := Filter{ - ErrorsOnly: true, - Syscall: &StringFilter{Pattern: "read"}, - PID: &NumericFilter{Op: OpEq, Value: 1234}, - LatencyNs: &NumericFilter{Op: OpGt, Value: 1_000_000}, - } - got := filter.Summary() - for _, want := range []string{"errors", "syscall~read", "pid=1234", "latency>1ms"} { - if !strings.Contains(got, want) { - t.Fatalf("Summary() = %q, missing %q", got, want) - } - } - - for _, tc := range []struct { - in string - want int64 - }{ - {in: "1ms", want: 1_000_000}, - {in: "10us", want: 10_000}, - {in: "500ns", want: 500}, - {in: "42", want: 42}, - } { - gotNs, err := ParseDurationNs(tc.in) - if err != nil { - t.Fatalf("ParseDurationNs(%q) err = %v", tc.in, err) - } - if gotNs != tc.want { - t.Fatalf("ParseDurationNs(%q) = %d, want %d", tc.in, gotNs, tc.want) - } - } - if _, err := ParseDurationNs("garbage"); err == nil { - t.Fatalf("ParseDurationNs(garbage) expected error") - } -} - -func TestCompareOpSymbol(t *testing.T) { - for _, tc := range []struct { - name string - op CompareOp - want string - }{ - {name: "eq", op: OpEq, want: "="}, - {name: "neq", op: OpNeq, want: "!="}, - {name: "gt", op: OpGt, want: ">"}, - {name: "gte", op: OpGte, want: ">="}, - {name: "lt", op: OpLt, want: "<"}, - {name: "lte", op: OpLte, want: "<="}, - {name: "unknown", op: CompareOp(99), want: "?"}, - } { - if got := CompareOpSymbol(tc.op); got != tc.want { - t.Fatalf("%s: CompareOpSymbol(%v) = %q, want %q", tc.name, tc.op, got, tc.want) - } - } -} - func TestFilterEqual(t *testing.T) { base := Filter{ Syscall: &StringFilter{Pattern: "read"}, diff --git a/internal/globalfilter/parser/parser.go b/internal/globalfilter/parser/parser.go new file mode 100644 index 0000000..95234a8 --- /dev/null +++ b/internal/globalfilter/parser/parser.go @@ -0,0 +1,49 @@ +// Package parser provides input parsing helpers for globalfilter values. +// It is intentionally kept free of domain types so it can be imported by +// both globalfilter and its callers without creating import cycles. +package parser + +import ( + "fmt" + "strconv" + "strings" + "time" +) + +// ParseDurationNs parses a human-readable duration string and returns the +// equivalent number of nanoseconds. Plain integers are interpreted as +// nanoseconds directly. Standard Go duration suffixes (ns, us/µs/μs, ms, s…) +// are also accepted. Returns an error for empty or unparseable input. +func ParseDurationNs(input string) (int64, error) { + s := strings.TrimSpace(strings.ToLower(input)) + if s == "" { + return 0, fmt.Errorf("empty duration") + } + s = strings.ReplaceAll(s, "µs", "us") + s = strings.ReplaceAll(s, "μs", "us") + if onlyDigits(s) || strings.HasPrefix(s, "-") && onlyDigits(s[1:]) { + v, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return 0, err + } + return v, nil + } + d, err := time.ParseDuration(s) + if err != nil { + return 0, err + } + return d.Nanoseconds(), nil +} + +// onlyDigits reports whether s is a non-empty string of ASCII decimal digits. +func onlyDigits(s string) bool { + if s == "" { + return false + } + for _, ch := range s { + if ch < '0' || ch > '9' { + return false + } + } + return true +} diff --git a/internal/globalfilter/parser/parser_test.go b/internal/globalfilter/parser/parser_test.go new file mode 100644 index 0000000..62e5978 --- /dev/null +++ b/internal/globalfilter/parser/parser_test.go @@ -0,0 +1,37 @@ +package parser_test + +import ( + "testing" + + "ior/internal/globalfilter/parser" +) + +func TestParseDurationNsAcceptsValidInputs(t *testing.T) { + for _, tc := range []struct { + in string + want int64 + }{ + {in: "1ms", want: 1_000_000}, + {in: "10us", want: 10_000}, + {in: "500ns", want: 500}, + {in: "42", want: 42}, + {in: "-10", want: -10}, + {in: "1s", want: 1_000_000_000}, + } { + got, err := parser.ParseDurationNs(tc.in) + if err != nil { + t.Fatalf("ParseDurationNs(%q) unexpected error: %v", tc.in, err) + } + if got != tc.want { + t.Fatalf("ParseDurationNs(%q) = %d, want %d", tc.in, got, tc.want) + } + } +} + +func TestParseDurationNsRejectsInvalidInputs(t *testing.T) { + for _, bad := range []string{"garbage", "", "abc", "1xx"} { + if _, err := parser.ParseDurationNs(bad); err == nil { + t.Fatalf("ParseDurationNs(%q) expected error, got nil", bad) + } + } +} diff --git a/internal/globalfilter/presenter/presenter.go b/internal/globalfilter/presenter/presenter.go new file mode 100644 index 0000000..1bf5c79 --- /dev/null +++ b/internal/globalfilter/presenter/presenter.go @@ -0,0 +1,85 @@ +// Package presenter formats globalfilter domain values for human-readable +// display. It imports globalfilter types but adds no domain logic, keeping +// presentation concerns out of the core filter package. +package presenter + +import ( + "fmt" + "strings" + "time" + + "ior/internal/globalfilter" +) + +// CompareOpSymbol returns the display symbol for a numeric comparison operator +// (e.g. OpEq → "=", OpNeq → "!="). +func CompareOpSymbol(op globalfilter.CompareOp) string { + switch op { + case globalfilter.OpEq: + return "=" + case globalfilter.OpNeq: + return "!=" + case globalfilter.OpGt: + return ">" + case globalfilter.OpGte: + return ">=" + case globalfilter.OpLt: + return "<" + case globalfilter.OpLte: + return "<=" + default: + return "?" + } +} + +// AppendStringSummary appends a "name~pattern" token to parts when sf is +// non-nil and its pattern is non-empty, then returns the updated slice. +func AppendStringSummary(parts []string, name string, sf *globalfilter.StringFilter) []string { + if sf == nil { + return parts + } + pattern := strings.TrimSpace(sf.Pattern) + if pattern == "" { + return parts + } + return append(parts, fmt.Sprintf("%s~%s", name, pattern)) +} + +// AppendNumericSummary appends a "nameOPvalue" token to parts when nf is +// non-nil. When duration is true the value is formatted as a time.Duration +// string rather than a raw integer. +func AppendNumericSummary(parts []string, name string, nf *globalfilter.NumericFilter, duration bool) []string { + if nf == nil { + return parts + } + value := fmt.Sprintf("%d", nf.Value) + if duration { + value = time.Duration(nf.Value).String() + } + return append(parts, fmt.Sprintf("%s%s%s", name, CompareOpSymbol(nf.Op), value)) +} + +// FilterSummary returns a compact human-readable description of all active +// filter predicates, e.g. "syscall~read pid=1234". Returns "all" when no +// predicates are set. This is the canonical presentation of a Filter value +// for status bars and log messages. +func FilterSummary(f globalfilter.Filter) string { + parts := make([]string, 0, 10) + if f.ErrorsOnly { + parts = append(parts, "errors") + } + parts = AppendStringSummary(parts, "syscall", f.Syscall) + parts = AppendStringSummary(parts, "comm", f.Comm) + parts = AppendStringSummary(parts, "file", f.File) + parts = AppendNumericSummary(parts, "pid", f.PID, false) + parts = AppendNumericSummary(parts, "tid", f.TID, false) + parts = AppendNumericSummary(parts, "fd", f.FD, false) + parts = AppendNumericSummary(parts, "latency", f.LatencyNs, true) + parts = AppendNumericSummary(parts, "gap", f.GapNs, true) + parts = AppendNumericSummary(parts, "bytes", f.Bytes, false) + parts = AppendNumericSummary(parts, "ret", f.RetVal, false) + if len(parts) == 0 { + return "all" + } + return strings.Join(parts, " ") +} diff --git a/internal/globalfilter/presenter/presenter_test.go b/internal/globalfilter/presenter/presenter_test.go new file mode 100644 index 0000000..eb35aaf --- /dev/null +++ b/internal/globalfilter/presenter/presenter_test.go @@ -0,0 +1,83 @@ +package presenter_test + +import ( + "strings" + "testing" + + "ior/internal/globalfilter" + "ior/internal/globalfilter/presenter" +) + +func TestCompareOpSymbolCoversAllOps(t *testing.T) { + for _, tc := range []struct { + name string + op globalfilter.CompareOp + want string + }{ + {name: "eq", op: globalfilter.OpEq, want: "="}, + {name: "neq", op: globalfilter.OpNeq, want: "!="}, + {name: "gt", op: globalfilter.OpGt, want: ">"}, + {name: "gte", op: globalfilter.OpGte, want: ">="}, + {name: "lt", op: globalfilter.OpLt, want: "<"}, + {name: "lte", op: globalfilter.OpLte, want: "<="}, + {name: "unknown", op: globalfilter.CompareOp(99), want: "?"}, + } { + if got := presenter.CompareOpSymbol(tc.op); got != tc.want { + t.Fatalf("%s: CompareOpSymbol() = %q, want %q", tc.name, got, tc.want) + } + } +} + +func TestAppendStringSummarySkipsEmptyAndNilFilters(t *testing.T) { + parts := []string{} + parts = presenter.AppendStringSummary(parts, "syscall", nil) + if len(parts) != 0 { + t.Fatalf("nil filter should not append") + } + parts = presenter.AppendStringSummary(parts, "syscall", &globalfilter.StringFilter{Pattern: " "}) + if len(parts) != 0 { + t.Fatalf("blank pattern should not append") + } + parts = presenter.AppendStringSummary(parts, "syscall", &globalfilter.StringFilter{Pattern: "read"}) + if len(parts) != 1 || parts[0] != "syscall~read" { + t.Fatalf("expected syscall~read, got %v", parts) + } +} + +func TestAppendNumericSummaryFormatsDurationsAndIntegers(t *testing.T) { + parts := []string{} + nf := &globalfilter.NumericFilter{Op: globalfilter.OpGt, Value: 1_000_000} + parts = presenter.AppendNumericSummary(parts, "latency", nf, true) + if len(parts) != 1 || parts[0] != "latency>1ms" { + t.Fatalf("expected latency>1ms, got %v", parts) + } + + parts = []string{} + nf = &globalfilter.NumericFilter{Op: globalfilter.OpEq, Value: 42} + parts = presenter.AppendNumericSummary(parts, "pid", nf, false) + if len(parts) != 1 || parts[0] != "pid=42" { + t.Fatalf("expected pid=42, got %v", parts) + } +} + +func TestFilterSummaryReturnsAllForZeroFilter(t *testing.T) { + if got := presenter.FilterSummary(globalfilter.Filter{}); got != "all" { + t.Fatalf("FilterSummary(zero) = %q, want \"all\"", got) + } +} + +func TestFilterSummaryIncludesAllActivePredicates(t *testing.T) { + f := globalfilter.Filter{ + ErrorsOnly: true, + Syscall: &globalfilter.StringFilter{Pattern: "read"}, + Comm: &globalfilter.StringFilter{Pattern: "nginx"}, + PID: &globalfilter.NumericFilter{Op: globalfilter.OpEq, Value: 1234}, + LatencyNs: &globalfilter.NumericFilter{Op: globalfilter.OpGt, Value: 1_000_000}, + } + got := presenter.FilterSummary(f) + for _, want := range []string{"errors", "syscall~read", "comm~nginx", "pid=1234", "latency>1ms"} { + if !strings.Contains(got, want) { + t.Fatalf("FilterSummary() = %q, missing %q", got, want) + } + } +} diff --git a/internal/tui/dashboard/model.go b/internal/tui/dashboard/model.go index 123600a..8548481 100644 --- a/internal/tui/dashboard/model.go +++ b/internal/tui/dashboard/model.go @@ -7,6 +7,7 @@ import ( "time" "ior/internal/globalfilter" + "ior/internal/globalfilter/presenter" "ior/internal/statsengine" common "ior/internal/tui/common" "ior/internal/tui/eventstream" @@ -1104,7 +1105,7 @@ func (m Model) View() tea.View { } func (m Model) filterSummary() string { - summary := "filter: " + m.globalFilter.Summary() + summary := "filter: " + presenter.FilterSummary(m.globalFilter) if len(m.filterStack) > 0 { summary += " | stack: " + strings.Join(m.filterStack, " | ") } diff --git a/internal/tui/eventstream/filter.go b/internal/tui/eventstream/filter.go index 61d8c33..801b8ba 100644 --- a/internal/tui/eventstream/filter.go +++ b/internal/tui/eventstream/filter.go @@ -1,6 +1,9 @@ package eventstream -import "ior/internal/globalfilter" +import ( + "ior/internal/globalfilter" + "ior/internal/globalfilter/parser" +) type CompareOp = globalfilter.CompareOp type NumericFilter = globalfilter.NumericFilter @@ -16,6 +19,8 @@ const ( OpLte = globalfilter.OpLte ) +// ParseDurationNs delegates to parser.ParseDurationNs, re-exporting the +// duration parsing helper for eventstream callers. func ParseDurationNs(input string) (int64, error) { - return globalfilter.ParseDurationNs(input) + return parser.ParseDurationNs(input) } diff --git a/internal/tui/eventstream/filter_test.go b/internal/tui/eventstream/filter_test.go index 0413904..efedbe4 100644 --- a/internal/tui/eventstream/filter_test.go +++ b/internal/tui/eventstream/filter_test.go @@ -3,6 +3,8 @@ package eventstream import ( "strings" "testing" + + "ior/internal/globalfilter/presenter" ) func sampleEvent() StreamEvent { @@ -31,8 +33,8 @@ func TestFilterZeroValueMatchesAll(t *testing.T) { if f.IsActive() { t.Fatalf("zero-value filter should be inactive") } - if got := f.Summary(); got != "all" { - t.Fatalf("Summary() = %q, want all", got) + if got := presenter.FilterSummary(f); got != "all" { + t.Fatalf("FilterSummary() = %q, want all", got) } } @@ -120,7 +122,7 @@ func TestFilterSummaryIncludesActivePredicates(t *testing.T) { PID: &NumericFilter{Op: OpEq, Value: 1234}, LatencyNs: &NumericFilter{Op: OpGt, Value: 1000000}, } - got := f.Summary() + got := presenter.FilterSummary(f) for _, wantPart := range []string{"errors", "syscall~read", "pid=1234", "latency>1ms"} { if !strings.Contains(got, wantPart) { t.Fatalf("Summary() = %q, missing %q", got, wantPart) diff --git a/internal/tui/eventstream/render.go b/internal/tui/eventstream/render.go index 4d7970b..cd4d528 100644 --- a/internal/tui/eventstream/render.go +++ b/internal/tui/eventstream/render.go @@ -5,6 +5,7 @@ import ( "strconv" "strings" + "ior/internal/globalfilter/presenter" "ior/internal/tui/common" "charm.land/lipgloss/v2" @@ -75,7 +76,7 @@ func renderStatusLine(paused bool, totalCount, filteredCount, bufferLen, bufferC } func renderFilterLine(filter Filter) string { - summary := filter.Summary() + summary := presenter.FilterSummary(filter) if summary == "all" { summary = common.HighlightStyle.Render(summary) } diff --git a/internal/tui/filterstack.go b/internal/tui/filterstack.go index dbd26a0..016d845 100644 --- a/internal/tui/filterstack.go +++ b/internal/tui/filterstack.go @@ -4,6 +4,7 @@ import ( "strings" "ior/internal/globalfilter" + "ior/internal/globalfilter/presenter" ) // filterStack manages the trace filter chain: the active filter, the undo @@ -124,14 +125,14 @@ func globalFilterActionLabel(prev, next globalfilter.Filter, action string) stri parts = appendNumericFilterChange(parts, "bytes", prev.Bytes, next.Bytes, false) parts = appendNumericFilterChange(parts, "ret", prev.RetVal, next.RetVal, false) if len(parts) == 0 { - return next.Summary() + return presenter.FilterSummary(next) } return strings.Join(parts, " ") } // appendStringFilterChange appends a change token to parts for a string // filter field. It emits "clear name" when the filter is removed, or delegates -// to globalfilter.AppendStringSummary for the canonical "name~pattern" format. +// to presenter.AppendStringSummary for the canonical "name~pattern" format. func appendStringFilterChange(parts []string, name string, prev, next *globalfilter.StringFilter) []string { if sameStringFilter(prev, next) { return parts @@ -139,12 +140,12 @@ func appendStringFilterChange(parts []string, name string, prev, next *globalfil if next == nil || strings.TrimSpace(next.Pattern) == "" { return append(parts, "clear "+name) } - return globalfilter.AppendStringSummary(parts, name, next) + return presenter.AppendStringSummary(parts, name, next) } // appendNumericFilterChange appends a change token to parts for a numeric // filter field. It emits "clear name" when the filter is removed, or delegates -// to globalfilter.AppendNumericSummary for the canonical "nameOPvalue" format. +// to presenter.AppendNumericSummary for the canonical "nameOPvalue" format. func appendNumericFilterChange(parts []string, name string, prev, next *globalfilter.NumericFilter, duration bool) []string { if sameNumericFilter(prev, next) { return parts @@ -152,7 +153,7 @@ func appendNumericFilterChange(parts []string, name string, prev, next *globalfi if next == nil { return append(parts, "clear "+name) } - return globalfilter.AppendNumericSummary(parts, name, next, duration) + return presenter.AppendNumericSummary(parts, name, next, duration) } func sameStringFilter(a, b *globalfilter.StringFilter) bool { diff --git a/internal/tui/tracefilter/model.go b/internal/tui/tracefilter/model.go index caef948..b46d50a 100644 --- a/internal/tui/tracefilter/model.go +++ b/internal/tui/tracefilter/model.go @@ -6,6 +6,7 @@ import ( "strings" "ior/internal/globalfilter" + "ior/internal/globalfilter/parser" "charm.land/bubbles/v2/textinput" tea "charm.land/bubbletea/v2" @@ -323,7 +324,7 @@ func parseNumericFilter(value string, opIndex int, duration bool) (*globalfilter err error ) if duration { - number, err = globalfilter.ParseDurationNs(value) + number, err = parser.ParseDurationNs(value) } else { number, err = strconv.ParseInt(value, 10, 64) } |
