Skip to content

Commit 48ba5c3

Browse files
author
Peter Renström
committed
Behave more like the Python implementation.
1 parent a1eba44 commit 48ba5c3

File tree

2 files changed

+149
-97
lines changed

2 files changed

+149
-97
lines changed

dedent.go

+20-27
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,35 @@ import (
55
"strings"
66
)
77

8-
var whitespaceOnly = regexp.MustCompile("(?m)^[ \t]+$")
9-
var leadingWhitespace = regexp.MustCompile("(?m)(^[ \t]*)")
8+
var (
9+
whitespaceOnly = regexp.MustCompile("(?m)^[ \t]+$")
10+
leadingWhitespace = regexp.MustCompile("(?m)(^[ \t]*)(?:[^ \t\n])")
11+
)
1012

11-
// Dedent removes any common leading whitespace from every line in s.
13+
// Dedent removes any common leading whitespace from every line in text.
1214
//
1315
// This can be used to make multiline strings to line up with the left edge of
1416
// the display, while still presenting them in the source code in indented
1517
// form.
16-
func Dedent(s string) string {
17-
s = whitespaceOnly.ReplaceAllString(s, "")
18-
margin := findMargin(s)
19-
if len(margin) == 0 {
20-
return s
21-
}
22-
return regexp.MustCompile("(?m)^"+margin).ReplaceAllString(s, "")
23-
}
24-
25-
// Look for the longest leading string of spaces and tabs common to all lines.
26-
func findMargin(s string) string {
18+
func Dedent(text string) string {
2719
var margin string
2820

29-
indents := leadingWhitespace.FindAllString(s, -1)
30-
numIndents := len(indents)
31-
for i, indent := range indents {
32-
// Don't use last row if it is empty
33-
if i == numIndents-1 && indent == "" {
34-
break
35-
}
21+
text = whitespaceOnly.ReplaceAllString(text, "")
22+
indents := leadingWhitespace.FindAllStringSubmatch(text, -1)
3623

37-
if margin == "" {
38-
margin = indent
39-
} else if strings.HasPrefix(indent, margin) {
24+
// Look for the longest leading string of spaces and tabs common to all
25+
// lines.
26+
for i, indent := range indents {
27+
if i == 0 {
28+
margin = indent[1]
29+
} else if strings.HasPrefix(indent[1], margin) {
4030
// Current line more deeply indented than previous winner:
4131
// no change (previous winner is still on top).
4232
continue
43-
} else if strings.HasPrefix(margin, indent) {
33+
} else if strings.HasPrefix(margin, indent[1]) {
4434
// Current line consistent with and no deeper than previous winner:
4535
// it's the new winner.
46-
margin = indent
36+
margin = indent[1]
4737
} else {
4838
// Current line and previous winner have no common whitespace:
4939
// there is no margin.
@@ -52,5 +42,8 @@ func findMargin(s string) string {
5242
}
5343
}
5444

55-
return margin
45+
if margin != "" {
46+
text = regexp.MustCompile("(?m)^"+margin).ReplaceAllString(text, "")
47+
}
48+
return text
5649
}

dedent_test.go

+129-70
Original file line numberDiff line numberDiff line change
@@ -5,84 +5,151 @@ import (
55
"testing"
66
)
77

8-
func TestFindMargin(t *testing.T) {
9-
m := findMargin("Foo\n\t\t\tBar\n\t\tBaz")
10-
if m != "\t\t" {
11-
t.Errorf("Expected margin to be '\t\t'; got '%s'", m)
8+
const errorMsg = "\nexpected %q\ngot %q"
9+
10+
type dedentTest struct {
11+
text, expect string
12+
}
13+
14+
func TestDedentNoMargin(t *testing.T) {
15+
texts := []string{
16+
// No lines indented
17+
"Hello there.\nHow are you?\nOh good, I'm glad.",
18+
// Similar with a blank line
19+
"Hello there.\n\nBoo!",
20+
// Some lines indented, but overall margin is still zero
21+
"Hello there.\n This is indented.",
22+
// Again, add a blank line.
23+
"Hello there.\n\n Boo!\n",
24+
}
25+
26+
for _, text := range texts {
27+
if text != Dedent(text) {
28+
t.Errorf(errorMsg, text, Dedent(text))
29+
}
1230
}
1331
}
1432

15-
func TestFindMarginMultiline(t *testing.T) {
16-
m := findMargin(`Foo
17-
Bar
18-
Baz
19-
`)
20-
if m != "\t\t" {
21-
t.Errorf("Expected margin to be '\t\t'; got '%s'", m)
33+
func TestDedentEven(t *testing.T) {
34+
texts := []dedentTest{
35+
{
36+
// All lines indented by two spaces
37+
text: " Hello there.\n How are ya?\n Oh good.",
38+
expect: "Hello there.\nHow are ya?\nOh good.",
39+
},
40+
{
41+
// Same, with blank lines
42+
text: " Hello there.\n\n How are ya?\n Oh good.\n",
43+
expect: "Hello there.\n\nHow are ya?\nOh good.\n",
44+
},
45+
{
46+
// Now indent one of the blank lines
47+
text: " Hello there.\n \n How are ya?\n Oh good.\n",
48+
expect: "Hello there.\n\nHow are ya?\nOh good.\n",
49+
},
50+
}
51+
52+
for _, text := range texts {
53+
if text.expect != Dedent(text.text) {
54+
t.Errorf(errorMsg, text.expect, Dedent(text.text))
55+
}
2256
}
2357
}
2458

25-
func TestDedentMultilineString(t *testing.T) {
26-
s1 := `Lorem ipsum dolor sit amet, consectetur adipiscing elit.
27-
Curabitur justo tellus, facilisis nec efficitur dictum,
28-
fermentum vitae ligula. Sed eu convallis sapien.
29-
`
30-
s2 := `Lorem ipsum dolor sit amet, consectetur adipiscing elit.
31-
Curabitur justo tellus, facilisis nec efficitur dictum,
32-
fermentum vitae ligula. Sed eu convallis sapien.
33-
`
34-
35-
if Dedent(s1) != s2 {
36-
t.Errorf("expected string '%s'; got '%s'", s2, Dedent(s1))
59+
func TestDedentUneven(t *testing.T) {
60+
texts := []dedentTest{
61+
{
62+
// Lines indented unevenly
63+
text: `
64+
def foo():
65+
while 1:
66+
return foo
67+
`,
68+
expect: `
69+
def foo():
70+
while 1:
71+
return foo
72+
`,
73+
},
74+
{
75+
// Uneven indentation with a blank line
76+
text: " Foo\n Bar\n\n Baz\n",
77+
expect: "Foo\n Bar\n\n Baz\n",
78+
},
79+
{
80+
// Uneven indentation with a whitespace-only line
81+
text: " Foo\n Bar\n \n Baz\n",
82+
expect: "Foo\n Bar\n\n Baz\n",
83+
},
84+
}
85+
86+
for _, text := range texts {
87+
if text.expect != Dedent(text.text) {
88+
t.Errorf(errorMsg, text.expect, Dedent(text.text))
89+
}
90+
}
91+
}
92+
93+
// Dedent() should not mangle internal tabs.
94+
func TestDedentPreserveInternalTabs(t *testing.T) {
95+
text := " hello\tthere\n how are\tyou?"
96+
expect := "hello\tthere\nhow are\tyou?"
97+
if expect != Dedent(text) {
98+
t.Errorf(errorMsg, expect, Dedent(text))
99+
}
100+
101+
// Make sure that it preserves tabs when it's not making any changes at all
102+
if expect != Dedent(expect) {
103+
t.Errorf(errorMsg, expect, Dedent(expect))
37104
}
38105
}
39106

40-
func TestDedentJSONString(t *testing.T) {
41-
s1 := `
107+
// Dedent() should not mangle tabs in the margin (i.e. tabs and spaces both
108+
// count as margin, but are *not* considered equivalent).
109+
func TestDedentPreserveMarginTabs(t *testing.T) {
110+
texts := []string{
111+
" hello there\n\thow are you?",
112+
// Same effect even if we have 8 spaces
113+
" hello there\n\thow are you?",
114+
}
115+
116+
for _, text := range texts {
117+
d := Dedent(text)
118+
if text != d {
119+
t.Errorf(errorMsg, text, d)
120+
}
121+
}
122+
123+
texts2 := []dedentTest{
124+
{
125+
// Dedent() only removes whitespace that can be uniformly removed!
126+
text: "\thello there\n\thow are you?",
127+
expect: "hello there\nhow are you?",
128+
},
42129
{
43-
"key1": [
44-
"l1",
45-
"l2"
46-
],
47-
"key2": "value"
48-
}`
49-
50-
s2 := `
51-
{
52-
"key1": [
53-
"l1",
54-
"l2"
55-
],
56-
"key2": "value"
57-
}`
58-
59-
if Dedent(s1) != s2 {
60-
t.Errorf("expected string '%s'; got '%s'", s2, Dedent(s1))
130+
text: " \thello there\n \thow are you?",
131+
expect: "hello there\nhow are you?",
132+
},
133+
{
134+
text: " \t hello there\n \t how are you?",
135+
expect: "hello there\nhow are you?",
136+
},
137+
{
138+
text: " \thello there\n \t how are you?",
139+
expect: "hello there\n how are you?",
140+
},
61141
}
62142

63-
s3 := `{
64-
"key1": [
65-
"l1",
66-
"l2"
67-
],
68-
"key2": "value"
69-
}`
70-
71-
s4 := `{
72-
"key1": [
73-
"l1",
74-
"l2"
75-
],
76-
"key2": "value"
77-
}`
78-
79-
if Dedent(s3) != s4 {
80-
t.Errorf("expected string '%s'; got '%s'", s2, Dedent(s1))
143+
for _, text := range texts2 {
144+
if text.expect != Dedent(text.text) {
145+
t.Errorf(errorMsg, text.expect, Dedent(text.text))
146+
}
81147
}
82148
}
83149

84150
func ExampleDedent() {
85-
fmt.Println(Dedent(`Lorem ipsum dolor sit amet,
151+
fmt.Println(Dedent(`
152+
Lorem ipsum dolor sit amet,
86153
consectetur adipiscing elit.
87154
Curabitur justo tellus, facilisis nec efficitur dictum,
88155
fermentum vitae ligula. Sed eu convallis sapien.`))
@@ -100,11 +167,3 @@ func BenchmarkDedent(b *testing.B) {
100167
fermentum vitae ligula. Sed eu convallis sapien.`)
101168
}
102169
}
103-
104-
func BenchmarkFindMargin(b *testing.B) {
105-
for i := 0; i < b.N; i++ {
106-
findMargin(`Lorem ipsum dolor sit amet, consectetur adipiscing elit.
107-
Curabitur justo tellus, facilisis nec efficitur dictum,
108-
fermentum vitae ligula. Sed eu convallis sapien.`)
109-
}
110-
}

0 commit comments

Comments
 (0)