wrap
wrap copied to clipboard
Enhancement request: OutputLinePrefix - Add function to allow for >2 line only
Use case : using bbrks/wrap with email headers (https://tools.ietf.org/html/rfc2822#section-2.2.3)
i.e. ability to add a whitespace only to line numbers >=2, leaving first line untouched.
I have a patch that makes this work, but I need to think about it a bit more to make sure I'm not overfitting the solution specifically for email header folding/making the options confusing 😄
diff --git a/wrapper.go b/wrapper.go
index 2d2e421..c08beb0 100644
--- a/wrapper.go
+++ b/wrapper.go
@@ -26,6 +26,11 @@ type Wrapper struct {
// Default: ""
OutputLinePrefix string
+ // If set, OutputFirstLinePrefix overrides OutputLinePrefix for the first line of output.
+ // Can be set to an empty string to avoid prepending a space in the case of email header folding.
+ // Default: nil
+ OutputFirstLinePrefix *string
+
// OutputLineSuffix is appended to any output lines.
// Default: ""
OutputLineSuffix string
@@ -82,7 +87,7 @@ func (w Wrapper) Wrap(s string, limit int) string {
for _, str := range strings.Split(s, w.Newline) {
str = strings.TrimPrefix(str, w.TrimInputPrefix)
str = strings.TrimSuffix(str, w.TrimInputSuffix)
- ret += w.line(str, limit) + w.Newline
+ ret += w.line(str, limit, 0) + w.Newline
}
if w.StripTrailingNewline {
@@ -93,9 +98,14 @@ func (w Wrapper) Wrap(s string, limit int) string {
// line will wrap a single line of text at the given length.
// If limit is less than 1, the string remains unwrapped.
-func (w Wrapper) line(s string, limit int) string {
+func (w Wrapper) line(s string, limit int, depth int) string {
+ outputLinePrefix := w.OutputLinePrefix
+ if depth == 0 && w.OutputFirstLinePrefix != nil {
+ outputLinePrefix = *w.OutputFirstLinePrefix
+ }
+
if limit < 1 || utf8.RuneCountInString(s) < limit+1 {
- return w.OutputLinePrefix + s + w.OutputLineSuffix
+ return outputLinePrefix + s + w.OutputLineSuffix
}
// Find the index of the last breakpoint within the limit.
@@ -114,11 +124,11 @@ func (w Wrapper) line(s string, limit int) string {
i = strings.IndexAny(s, w.Breakpoints)
// Nothing left to do!
if i < 0 {
- return w.OutputLinePrefix + s + w.OutputLineSuffix
+ return outputLinePrefix + s + w.OutputLineSuffix
}
}
}
// Recurse until we have nothing left to do.
- return w.OutputLinePrefix + s[:i] + w.OutputLineSuffix + w.Newline + w.line(s[i+breakpointWidth:], limit)
+ return outputLinePrefix + s[:i] + w.OutputLineSuffix + w.Newline + w.line(s[i+breakpointWidth:], limit, depth+1)
}
diff --git a/wrapper_test.go b/wrapper_test.go
index 7d115bf..d4e6432 100644
--- a/wrapper_test.go
+++ b/wrapper_test.go
@@ -105,3 +105,38 @@ oooooord`,
})
}
}
+
+func TestWrapper_Wrap_EmailHeaderFolding(t *testing.T) {
+ const limit = 14
+
+ tests := []struct {
+ input string
+ expected string
+ }{
+ {
+ input: "Subject: This is a test",
+ expected: `Subject: This
+ is a test`,
+ },
+ }
+
+ for i, test := range tests {
+ t.Run(strconv.FormatInt(int64(i), 10), func(t *testing.T) {
+ w := wrap.NewWrapper()
+ w.CutLongWords = true
+ w.OutputLinePrefix = " "
+ w.OutputFirstLinePrefix = strPtr("")
+ w.StripTrailingNewline = true
+
+ actual := w.Wrap(test.input, limit)
+
+ if actual != test.expected {
+ t.Errorf("expected %q but got %q", test.expected, actual)
+ }
+ })
+ }
+}
+
+func strPtr(s string) *string {
+ return &s
+}
I thought about it a bit more and found a neater solution without any library changes.
You can set the newline character to be "\n " such that any time Wrap breaks a line, it is followed by a single space char.
Currently Wrap doesn't take into account the width of a custom Newline string, but I think it should so I'll fix that. For now, you can subtract 1 from limit to always be within your specified width.
https://play.golang.org/p/pMyy2u043Xr
package main
import (
"fmt"
"github.com/bbrks/wrap/v2"
)
func main() {
w := wrap.NewWrapper()
// break with a newline and then a space
w.Newline = "\n "
const width = 80
const text = "Subject: This is a test of wrapping where folded lines should be indented with whitespace. Each time Wrap breaks a line, the newline is followed by a single space character. We've managed to do this with existing options, rather than adding a special case for line prefixes."
// width-1 to account for the space after \n
fmt.Println(w.Wrap(text, width-1))
}
Thanks for your thoughts on this. I'll go play with that suggestion you made. Only comment to make for now is that newline in mail is \r\n (CRLF), so would require a custom newline string, but your limit fix should be able to take care of that for now.
Hmm....is it just me or have I inadvertently stumbled across a bug ?
https://play.golang.org/p/OB0h83c2BiN
The splitting behavior, even with CutLongWords = true set is, well, odd ? Splits on the hyphen in Content-Type.
Might (?) be the expected behavior with CutLongWords = false, but shouldn't be when true.
It's sort of intentional for breaking in English prose, but in this case it can be fixed by limiting what characters can be considered breakpoints to only spaces:
w.Breakpoints = " "
Ps: You'll still want to -1 from limit, not len(w.Newline), we're only counting the characters that come after the break (\r\n)
Thanks !