Skip to content

Commit

Permalink
dedent
Browse files Browse the repository at this point in the history
  • Loading branch information
emarx committed Sep 29, 2024
1 parent 5a21cb3 commit 0c01483
Show file tree
Hide file tree
Showing 3 changed files with 240 additions and 0 deletions.
34 changes: 34 additions & 0 deletions assert.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,40 @@ func Equal(t testRunner, expected any, actual any, msg ...any) {
}
}

func EqualDedent(t testRunner, expected string, actual string, msg ...any) {
if test, ok := t.(helper); ok {
test.Helper()
}

expected, actual = Dedent(expected), Dedent(actual)

if !assert.Equal(expected, actual) {
internal.Fail(
t,
"Two strings that !!should be equal!!, are not equal.",
internal.NewObjectsExpectedActualWithDiff(expected, actual),
msg...,
)
}
}

func EqualDedentStrip(t testRunner, expected string, actual string, msg ...any) {
if test, ok := t.(helper); ok {
test.Helper()
}

expected, actual = strings.TrimSpace(Dedent(expected)), strings.TrimSpace(Dedent(actual))

if !assert.Equal(expected, actual) {
internal.Fail(
t,
"Two strings that !!should be equal!!, are not equal.",
internal.NewObjectsExpectedActualWithDiff(expected, actual),
msg...,
)
}
}

// NotEqual asserts that two objects are not equal.
//
// When using a custom message, the same formatting as with fmt.Sprintf() is used.
Expand Down
49 changes: 49 additions & 0 deletions dedent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package assert

import (
"regexp"
"strings"
)

var (
whitespaceOnly = regexp.MustCompile("(?m)^[ \t]+$")
leadingWhitespace = regexp.MustCompile("(?m)(^[ \t]*)(?:[^ \t\n])")
)

// Dedent removes any common leading whitespace from every line in text.
//
// This can be used to make multiline strings to line up with the left edge of
// the display, while still presenting them in the source code in indented
// form.
func Dedent(text string) string {
var margin string

text = whitespaceOnly.ReplaceAllString(text, "")
indents := leadingWhitespace.FindAllStringSubmatch(text, -1)

// Look for the longest leading string of spaces and tabs common to all
// lines.
for i, indent := range indents {
if i == 0 {
margin = indent[1]
} else if strings.HasPrefix(indent[1], margin) {
// Current line more deeply indented than previous winner:
// no change (previous winner is still on top).
continue
} else if strings.HasPrefix(margin, indent[1]) {
// Current line consistent with and no deeper than previous winner:
// it's the new winner.
margin = indent[1]
} else {
// Current line and previous winner have no common whitespace:
// there is no margin.
margin = ""
break
}
}

if margin != "" {
text = regexp.MustCompile("(?m)^"+margin).ReplaceAllString(text, "")
}
return text
}
157 changes: 157 additions & 0 deletions dedent_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package assert

import (
"fmt"
"testing"
)

type dedentTest struct {
text, expect string
}

func TestDedentNoMargin(t *testing.T) {
t.Parallel()
for _, text := range []string{
// No lines indented
"Hello there.\nHow are you?\nOh good, I'm glad.",
// Similar with a blank line
"Hello there.\n\nBoo!",
// Some lines indented, but overall margin is still zero
"Hello there.\n This is indented.",
// Again, add a blank line.
"Hello there.\n\n Boo!\n",
} {
t.Run(text, func(t *testing.T) {
t.Parallel()
Equal(t, text, Dedent(text))
})
}
}

func TestDedentEven(t *testing.T) {
t.Parallel()
texts := []dedentTest{
{
// All lines indented by two spaces
text: " Hello there.\n How are ya?\n Oh good.",
expect: "Hello there.\nHow are ya?\nOh good.",
},
{
// Same, with blank lines
text: " Hello there.\n\n How are ya?\n Oh good.\n",
expect: "Hello there.\n\nHow are ya?\nOh good.\n",
},
{
// Now indent one of the blank lines
text: " Hello there.\n \n How are ya?\n Oh good.\n",
expect: "Hello there.\n\nHow are ya?\nOh good.\n",
},
}

for _, text := range texts {
Equal(t, text.expect, Dedent(text.text))
}
}

func TestDedentUneven(t *testing.T) {
t.Parallel()
texts := []dedentTest{
{
// Lines indented unevenly
text: `
def foo():
while 1:
return foo
`,
expect: `
def foo():
while 1:
return foo
`,
},
{
// Uneven indentation with a blank line
text: " Foo\n Bar\n\n Baz\n",
expect: "Foo\n Bar\n\n Baz\n",
},
{
// Uneven indentation with a whitespace-only line
text: " Foo\n Bar\n \n Baz\n",
expect: "Foo\n Bar\n\n Baz\n",
},
}

for _, text := range texts {
Equal(t, text.expect, Dedent(text.text))
}
}

// Dedent() should not mangle internal tabs.
func TestDedentPreserveInternalTabs(t *testing.T) {
t.Parallel()
text := " hello\tthere\n how are\tyou?"
expect := "hello\tthere\nhow are\tyou?"
Equal(t, expect, Dedent(text))

// Make sure that it preserves tabs when it's not making any changes at all
Equal(t, expect, Dedent(expect))
}

// Dedent() should not mangle tabs in the margin (i.e. tabs and spaces both
// count as margin, but are *not* considered equivalent).
func TestDedentPreserveMarginTabs(t *testing.T) {
t.Parallel()
for _, text := range []string{
" hello there\n\thow are you?",
// Same effect even if we have 8 spaces
" hello there\n\thow are you?",
} {
Equal(t, text, Dedent(text))
}

texts2 := []dedentTest{
{
// Dedent() only removes whitespace that can be uniformly removed!
text: "\thello there\n\thow are you?",
expect: "hello there\nhow are you?",
},
{
text: " \thello there\n \thow are you?",
expect: "hello there\nhow are you?",
},
{
text: " \t hello there\n \t how are you?",
expect: "hello there\nhow are you?",
},
{
text: " \thello there\n \t how are you?",
expect: "hello there\n how are you?",
},
}

for _, text := range texts2 {
Equal(t, text.expect, Dedent(text.text))
}
}

func ExampleDedent() {
s := `
Lorem ipsum dolor sit amet,
consectetur adipiscing elit.
Curabitur justo tellus, facilisis nec efficitur dictum,
fermentum vitae ligula. Sed eu convallis sapien.`
fmt.Println(Dedent(s))
fmt.Println("-------------")
fmt.Println(s)
// Output:
// Lorem ipsum dolor sit amet,
// consectetur adipiscing elit.
// Curabitur justo tellus, facilisis nec efficitur dictum,
// fermentum vitae ligula. Sed eu convallis sapien.
// -------------
//
// Lorem ipsum dolor sit amet,
// consectetur adipiscing elit.
// Curabitur justo tellus, facilisis nec efficitur dictum,
// fermentum vitae ligula. Sed eu convallis sapien.
}

0 comments on commit 0c01483

Please sign in to comment.