From b1917a617877921b662f9dd7f710fb91a5aac12c Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 9 Jun 2023 13:38:21 +0200 Subject: [PATCH] add "alignLeft" and "alignRight" functions (#9672) Fixes https://github.com/grafana/loki/issues/9667 --- CHANGELOG.md | 1 + docs/sources/query/template_functions.md | 28 ++++++++++- pkg/logql/log/fmt.go | 28 +++++++++++ pkg/logql/log/fmt_test.go | 60 ++++++++++++++++++++++++ 4 files changed, 116 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7772822227309..166d9cb0fca6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ * [8684](https://github.com/grafana/loki/pull/8684) **oleksii-boiko-ua**: Helm: Add hpa templates for read, write and backend components. * [9535](https://github.com/grafana/loki/pull/9535) **salvacorts** Index stats cache can be configured independently of the results cache. If it's not configured, but it's enabled, it will use the results cache configuration. * [9604](https://github.com/grafana/loki/pull/9604) **dannykopping**: Querier: configurable writeback queue bytes size +* [9672](https://github.com/grafana/loki/pull/9672) **zeitlinger**: Add `alignLeft` and `alignRight` line formatting functions. ##### Fixes diff --git a/docs/sources/query/template_functions.md b/docs/sources/query/template_functions.md index 4e41703066a9e..1d3aeb1a28f12 100644 --- a/docs/sources/query/template_functions.md +++ b/docs/sources/query/template_functions.md @@ -234,6 +234,32 @@ Examples: {{ trimPrefix "-" "-hello" }} // output: hello ``` +## alignLeft + +Use this function to format a string to a fixed with, aligning the content to the left. + +Signature: `alignLeft(count int, src string) string` + +Examples: + +```template +`{{ alignLeft 5 "hello world"}}` // output: "hello" +`{{ alignLeft 5 "hi"}}` // output: "hi " +``` + +## alignRight + +Use this function to format a string to a fixed with, aligning the content to the right. + +Signature: `alignRight(count int, src string) string` + +Examples: + +```template +`{{ alignRight 5 "hello world"}}` // output: "world" +`{{ alignRight 5 "hi"}}` // output: " hi" +``` + ## indent The indent function indents every line in a given string to the specified indent width. This is useful when aligning multi-line strings. @@ -672,7 +698,7 @@ Examples: ```template {{ default "-" "" }} // output: - -{{ default "-" "foo" }} // output: foo +{{ default "" "foo" }} // output: foo ``` Example of a query to print a `-` if the `http_request_headers_x_forwarded_for` label is empty: diff --git a/pkg/logql/log/fmt.go b/pkg/logql/log/fmt.go index ef00ea0221cd8..0666964c32232 100644 --- a/pkg/logql/log/fmt.go +++ b/pkg/logql/log/fmt.go @@ -67,6 +67,8 @@ var ( "unixEpochMillis": unixEpochMillis, "unixEpochNanos": unixEpochNanos, "toDateInZone": toDateInZone, + "alignLeft": alignLeft, + "alignRight": alignRight, } // sprig template functions @@ -396,6 +398,32 @@ func trunc(c int, s string) string { return s } +func alignLeft(count int, src string) string { + runes := []rune(src) + l := len(runes) + if count < 0 || count == l { + return src + } + pad := count - l + if pad > 0 { + return src + strings.Repeat(" ", pad) + } + return string(runes[:count]) +} + +func alignRight(count int, src string) string { + runes := []rune(src) + l := len(runes) + if count < 0 || count == l { + return src + } + pad := count - l + if pad > 0 { + return strings.Repeat(" ", pad) + src + } + return string(runes[l-count:]) +} + type Decolorizer struct{} // RegExp to select ANSI characters courtesy of https://github.com/acarl005/stripansi diff --git a/pkg/logql/log/fmt_test.go b/pkg/logql/log/fmt_test.go index baebb3e713740..87f4422ce30eb 100644 --- a/pkg/logql/log/fmt_test.go +++ b/pkg/logql/log/fmt_test.go @@ -459,6 +459,24 @@ func Test_lineFormatter_Format(t *testing.T) { labels.FromStrings("foo", "aSdtIGEgc3RyaW5nLCBlbmNvZGUgbWUh"), []byte("1"), }, + { + "alignLeft", + newMustLineFormatter("{{ alignLeft 4 .foo }}"), + labels.FromStrings("foo", "hello"), + 1656353124120000000, + []byte("hell"), + labels.FromStrings("foo", "hello"), + []byte("1"), + }, + { + "alignRight", + newMustLineFormatter("{{ alignRight 4 .foo }}"), + labels.FromStrings("foo", "hello"), + 1656353124120000000, + []byte("ello"), + labels.FromStrings("foo", "hello"), + []byte("1"), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -721,6 +739,48 @@ func Test_trunc(t *testing.T) { } } +func Test_AlignLeft(t *testing.T) { + tests := []struct { + s string + c int + want string + }{ + {"Hello, 世界", -1, "Hello, 世界"}, + {"Hello, 世界", 0, ""}, + {"Hello, 世界", 1, "H"}, + {"Hello, 世界", 8, "Hello, 世"}, + {"Hello, 世界", 20, "Hello, 世界 "}, + } + for _, tt := range tests { + t.Run(fmt.Sprintf("%s%d", tt.s, tt.c), func(t *testing.T) { + if got := alignLeft(tt.c, tt.s); got != tt.want { + t.Errorf("alignLeft() = %q, want %q for %q with %v", got, tt.want, tt.s, tt.c) + } + }) + } +} + +func Test_AlignRight(t *testing.T) { + tests := []struct { + s string + c int + want string + }{ + {"Hello, 世界", -1, "Hello, 世界"}, + {"Hello, 世界", 0, ""}, + {"Hello, 世界", 1, "界"}, + {"Hello, 世界", 2, "世界"}, + {"Hello, 世界", 20, " Hello, 世界"}, + } + for _, tt := range tests { + t.Run(fmt.Sprintf("%s%d", tt.s, tt.c), func(t *testing.T) { + if got := alignRight(tt.c, tt.s); got != tt.want { + t.Errorf("alignRight() = %q, want %q for %q with %v", got, tt.want, tt.s, tt.c) + } + }) + } +} + func Test_substring(t *testing.T) { tests := []struct { start int