Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(strings)!: Update strings.Indent to error on bad input instead of silently doing nothing #2089

Merged
merged 1 commit into from
Jun 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docs-src/content/functions/strings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,19 @@ funcs:
alias: indent
description: |
Indents a string. If the input string has multiple lines, each line will be indented.
As of v4.0.0, this function will error if the `width` or `indent` arguments are invalid.
pipeline: true
arguments:
- name: width
required: false
description: 'number of times to repeat the `indent` string. Default: `1`'
description: 'Number of times to repeat the `indent` string. Must be greater than 0. Default: `1`'
- name: indent
required: false
description: 'the string to indent with. Default: `" "`'
description: 'The string to indent with. Must not contain a newline character ("\n"). Default: `" "`'
- name: input
required: true
description: the string to indent
description: The string to indent
rawExamples:
- |
This function can be especially useful when adding YAML snippets into other YAML documents, where indentation is important:
Expand Down
8 changes: 5 additions & 3 deletions docs/content/functions/strings.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ http://example.com:80

Indents a string. If the input string has multiple lines, each line will be indented.

As of v4.0.0, this function will error if the `width` or `indent` arguments are invalid.

_Added in gomplate [v1.9.0](https://github.com/hairyhenderson/gomplate/releases/tag/v1.9.0)_
### Usage

Expand All @@ -157,9 +159,9 @@ input | strings.Indent [width] [indent]

| name | description |
|------|-------------|
| `width` | _(optional)_ number of times to repeat the `indent` string. Default: `1` |
| `indent` | _(optional)_ the string to indent with. Default: `" "` |
| `input` | _(required)_ the string to indent |
| `width` | _(optional)_ Number of times to repeat the `indent` string. Must be greater than 0. Default: `1` |
| `indent` | _(optional)_ The string to indent with. Must not contain a newline character ("\n"). Default: `" "` |
| `input` | _(required)_ The string to indent |

### Examples

Expand Down
18 changes: 13 additions & 5 deletions internal/funcs/strings.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,31 +244,39 @@ func (StringFuncs) Trunc(length int, s interface{}) string {

// Indent -
func (StringFuncs) Indent(args ...interface{}) (string, error) {
input := conv.ToString(args[len(args)-1])
indent := " "
width := 1

var ok bool

switch len(args) {
case 0:
return "", fmt.Errorf("expected at least 1 argument")
case 2:
indent, ok = args[0].(string)
if !ok {
width, ok = args[0].(int)
if !ok {
return "", fmt.Errorf("indent: invalid arguments")
return "", fmt.Errorf("invalid arguments")
}

indent = " "
}
case 3:
width, ok = args[0].(int)
if !ok {
return "", fmt.Errorf("indent: invalid arguments")
return "", fmt.Errorf("invalid arguments")
}

indent, ok = args[1].(string)
if !ok {
return "", fmt.Errorf("indent: invalid arguments")
return "", fmt.Errorf("invalid arguments")
}
}
return gompstrings.Indent(width, indent, input), nil

input := conv.ToString(args[len(args)-1])

return gompstrings.Indent(width, indent, input)
}

// Slug -
Expand Down
16 changes: 9 additions & 7 deletions strings/strings.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ import (

// Indent - indent each line of the string with the given indent string.
// Any indent characters are permitted, except for '\n'.
//
// TODO: return an error if the indent string contains '\n' instead of
// succeeding
func Indent(width int, indent, s string) string {
if width <= 0 || strings.Contains(indent, "\n") {
return s
func Indent(width int, indent, s string) (string, error) {
if width <= 0 {
return "", fmt.Errorf("width must be > 0")
}

if strings.Contains(indent, "\n") {
return "", fmt.Errorf("indent must not contain '\\n'")
}

if width > 1 {
Expand All @@ -39,7 +40,8 @@ func Indent(width int, indent, s string) string {
res = append(res, c)
bol = c == '\n'
}
return string(res)

return string(res), nil
}

// ShellQuote - generate a POSIX shell literal evaluating to a string
Expand Down
15 changes: 14 additions & 1 deletion strings/strings_fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"unicode"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func FuzzIndent(f *testing.F) {
Expand All @@ -16,7 +17,19 @@ func FuzzIndent(f *testing.F) {
f.Add(15, "\n0", "\n0")

f.Fuzz(func(t *testing.T, width int, indent, s string) {
out := Indent(width, indent, s)
out, err := Indent(width, indent, s)

if width <= 0 {
require.Error(t, err)
return
}

if strings.Contains(indent, "\n") {
require.Error(t, err)
return
}

require.NoError(t, err)

// out should be equal to s when both have the indent character
// completely removed.
Expand Down
52 changes: 33 additions & 19 deletions strings/strings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,32 @@ import (
)

func TestIndent(t *testing.T) {
actual := "hello\nworld\n!"
in := "hello\nworld\n!"
expected := " hello\n world\n !"
require.Equal(t, actual, Indent(0, " ", actual))
require.Equal(t, actual, Indent(-1, " ", actual))
require.Equal(t, expected, Indent(1, " ", actual))
require.Equal(t, "\n", Indent(1, " ", "\n"))
require.Equal(t, " foo\n", Indent(1, " ", "foo\n"))
require.Equal(t, " foo", Indent(1, " ", "foo"))
require.Equal(t, " foo", Indent(3, " ", "foo"))

// indenting with newline is not permitted
require.Equal(t, "foo", Indent(3, "\n", "foo"))

actual, err := Indent(1, " ", in)
require.NoError(t, err)
require.Equal(t, expected, actual)
actual, err = Indent(1, " ", "\n")
require.NoError(t, err)
require.Equal(t, "\n", actual)
actual, err = Indent(1, " ", "foo\n")
require.NoError(t, err)
require.Equal(t, " foo\n", actual)
actual, err = Indent(1, " ", "foo")
require.NoError(t, err)
require.Equal(t, " foo", actual)
actual, err = Indent(3, " ", "foo")
require.NoError(t, err)
require.Equal(t, " foo", actual)

// error cases
_, err = Indent(3, "\n", "foo")
require.Error(t, err)
_, err = Indent(-1, " ", in)
require.Error(t, err)
_, err = Indent(0, " ", in)
require.Error(t, err)
}

func BenchmarkIndent(b *testing.B) {
Expand All @@ -29,14 +43,14 @@ func BenchmarkIndent(b *testing.B) {
outs := make([]string, b.N*8)
b.ResetTimer()
for i := 0; i < b.N; i++ {
outs[0+i*8] = Indent(20, " ", longString)
outs[1+i*8] = Indent(-1, " ", actual)
outs[2+i*8] = Indent(1, " ", actual)
outs[3+i*8] = Indent(1, " ", "\n")
outs[4+i*8] = Indent(1, " ", "foo\n")
outs[5+i*8] = Indent(1, " ", "foo")
outs[6+i*8] = Indent(3, " ", "foo")
outs[7+i*8] = Indent(3, "\n", "foo")
outs[0+i*8], _ = Indent(20, " ", longString)
outs[1+i*8], _ = Indent(-1, " ", actual)
outs[2+i*8], _ = Indent(1, " ", actual)
outs[3+i*8], _ = Indent(1, " ", "\n")
outs[4+i*8], _ = Indent(1, " ", "foo\n")
outs[5+i*8], _ = Indent(1, " ", "foo")
outs[6+i*8], _ = Indent(3, " ", "foo")
outs[7+i*8], _ = Indent(3, "\n", "foo")
}
b.StopTimer()

Expand Down