Skip to content

Commit

Permalink
feat(strings): Update strings.Indent to error on bad input instead of…
Browse files Browse the repository at this point in the history
… silently doing nothing

Signed-off-by: Dave Henderson <[email protected]>
  • Loading branch information
hairyhenderson committed Jun 1, 2024
1 parent 33868d1 commit 5f62936
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 38 deletions.
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

0 comments on commit 5f62936

Please sign in to comment.