Skip to content

Commit

Permalink
Merge pull request #3 from bbrks/customisable-breakpoints
Browse files Browse the repository at this point in the history
Improve API. Bumping to v2.0. Resolves #1
  • Loading branch information
bbrks authored Feb 16, 2017
2 parents 418c82e + 0f63161 commit 1a14f77
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 80 deletions.
41 changes: 26 additions & 15 deletions wrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,53 @@ package wrap
import "strings"

const (
// breakpoints defines which characters should be able to break a line.
breakpoints = " "
defaultBreakpoints = " -"
)

// Line will wrap a single line of text at the given length.
// Wrapper contains settings for customisable word-wrapping.
type Wrapper struct {
// Breakpoints defines which characters should be able to break a line.
// By default, this follows the usual English rules of spaces, and hyphens.
// Default: " -"
Breakpoints string
}

// NewWrapper returns a new instance of a Wrapper initialised with defaults.
func NewWrapper() Wrapper {
return Wrapper{
Breakpoints: defaultBreakpoints,
}
}

// line will wrap a single line of text at the given length.
// If limit is less than 1, the string remains unchanged.
//
// If a word is longer than the given limit, it will not be broken to fit.
// See the examples for this scenario.
func Line(s string, limit int) string {
func (w Wrapper) line(s string, limit int) string {
if limit < 1 || len(s) < limit {
return s
}

// Find the index of the last breakpoint within the limit.
i := strings.LastIndexAny(s[:limit], breakpoints)
i := strings.LastIndexAny(s[:limit], w.Breakpoints)

// Can't wrap within the limit, wrap at the next breakpoint instead.
if i < 0 {
i = strings.IndexAny(s, breakpoints)
i = strings.IndexAny(s, w.Breakpoints)
// Nothing left to do!
if i < 0 {
return s
}
}

// Recurse until we have nothing left to do.
return s[:i] + "\n" + Line(s[i+1:], limit)
return s[:i] + "\n" + w.line(s[i+1:], limit)
}

// LineWithPrefix will wrap a single line of text and prepend the given prefix,
// whilst staying within given limits.
func LineWithPrefix(s, prefix string, limit int) string {
// Wrap will wrap one or more lines of text at the given length.
// If limit is less than 1, the string remains unchanged.
func (w Wrapper) Wrap(s string, limit int) string {
var ret string
for _, str := range strings.Split(Line(s, limit-len(prefix)), "\n") {
ret += prefix + str + "\n"
for _, str := range strings.Split(s, "\n") {
ret += w.line(str, limit) + "\n"
}
return ret
}
160 changes: 95 additions & 65 deletions wrap_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package wrap
package wrap_test

import (
"fmt"
"strings"
"testing"

"github.com/bbrks/wrap"
"github.com/stretchr/testify/assert"
)

var w = wrap.NewWrapper()

// tests contains various line lengths to test our wrap functions.
var tests = []int{-5, 0, 5, 10, 25, 80, 120, 500}

Expand All @@ -20,13 +23,13 @@ var loremIpsums = []string{
"Duis ac ornare erat. Nulla in odio eget ante tristique dignissim a non erat. Sed non nisi vitae arcu dapibus porta vitae dignissim ante. Cras et fringilla turpis. Maecenas arcu nibh, tempus euismod pretium eget, hendrerit vitae arcu. Sed vel dolor quam. Etiam consequat sed dolor ut elementum. Quisque dictum tempor pretium. Sed eu sollicitudin mi, in commodo ante.",
}

func TestLine(t *testing.T) {
func TestWrapper_Wrap(t *testing.T) {
// Test multiple line lengths.
for _, l := range tests {

// Test each input line individually.
for _, s := range loremIpsums {
wrapped := Line(s, l)
wrapped := w.Wrap(s, l)

// Assert that each output line is no longer than the limit.
for _, v := range strings.Split(wrapped, "\n") {
Expand All @@ -51,75 +54,26 @@ func TestLine(t *testing.T) {
}
}

func TestLineWithPrefix(t *testing.T) {
var prefix = "// "
// Test multiple line lengths.
for _, l := range tests {

// Test each input line individually.
for _, s := range loremIpsums {
wrapped := LineWithPrefix(s, prefix, l)

// Assert that each output line is no longer than the limit.
for _, v := range strings.Split(wrapped, "\n") {
if !strings.HasPrefix(s, prefix) {
continue
}

// Only check lines which contain more than one word.
if !strings.Contains(v, " ") {
continue
}

// If length < 1, the string remains unchaged.
if l < 1 {
assert.Equal(t, prefix+s, v)
continue
}

assert.True(t, len(v) <= l,
fmt.Sprintf("Line length greater than %d: %s", l, v))
}

}

}
}

func benchmarkLine(b *testing.B, limit int) {
func benchmarkWrap(b *testing.B, limit int) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
Line(loremIpsums[0], limit)
w.Wrap(loremIpsums[0], limit)
}
}

func BenchmarkLine0(b *testing.B) { benchmarkLine(b, 0) }
func BenchmarkLine5(b *testing.B) { benchmarkLine(b, 5) }
func BenchmarkLine10(b *testing.B) { benchmarkLine(b, 10) }
func BenchmarkLine25(b *testing.B) { benchmarkLine(b, 25) }
func BenchmarkLine80(b *testing.B) { benchmarkLine(b, 80) }
func BenchmarkLine120(b *testing.B) { benchmarkLine(b, 120) }
func BenchmarkLine500(b *testing.B) { benchmarkLine(b, 500) }
func BenchmarkWrap0(b *testing.B) { benchmarkWrap(b, 0) }
func BenchmarkWrap5(b *testing.B) { benchmarkWrap(b, 5) }
func BenchmarkWrap10(b *testing.B) { benchmarkWrap(b, 10) }
func BenchmarkWrap25(b *testing.B) { benchmarkWrap(b, 25) }
func BenchmarkWrap80(b *testing.B) { benchmarkWrap(b, 80) }
func BenchmarkWrap120(b *testing.B) { benchmarkWrap(b, 120) }
func BenchmarkWrap500(b *testing.B) { benchmarkWrap(b, 500) }

func ExampleLineWithPrefix() {
var loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vulputate quam nibh, et faucibus enim gravida vel. Integer bibendum lectus et erat semper fermentum quis a risus. Fusce dignissim tempus metus non pretium. Nunc sagittis magna nec purus porttitor mollis. Pellentesque feugiat quam eget laoreet aliquet. Donec gravida congue massa, et sollicitudin turpis lacinia a. Fusce non tortor magna. Cras vel finibus tellus."

// Wrap when lines exceed 80 chars and prepend a comment prefix.
fmt.Println(LineWithPrefix(loremIpsum, "// ", 80))
// Output:
// // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vulputate quam
// // nibh, et faucibus enim gravida vel. Integer bibendum lectus et erat semper
// // fermentum quis a risus. Fusce dignissim tempus metus non pretium. Nunc
// // sagittis magna nec purus porttitor mollis. Pellentesque feugiat quam eget
// // laoreet aliquet. Donec gravida congue massa, et sollicitudin turpis lacinia
// // a. Fusce non tortor magna. Cras vel finibus tellus.
}

func ExampleLine() {
func ExampleWrapper_Wrap() {
var loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vulputate quam nibh, et faucibus enim gravida vel. Integer bibendum lectus et erat semper fermentum quis a risus. Fusce dignissim tempus metus non pretium. Nunc sagittis magna nec purus porttitor mollis. Pellentesque feugiat quam eget laoreet aliquet. Donec gravida congue massa, et sollicitudin turpis lacinia a. Fusce non tortor magna. Cras vel finibus tellus."

// Wrap when lines exceed 80 chars.
fmt.Println(Line(loremIpsum, 80))
fmt.Println(w.Wrap(loremIpsum, 80))
// Output:
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vulputate quam
// nibh, et faucibus enim gravida vel. Integer bibendum lectus et erat semper
Expand All @@ -129,11 +83,71 @@ func ExampleLine() {
// non tortor magna. Cras vel finibus tellus.
}

func ExampleLine_short() {
func ExampleWrapper_Wrap_paragraphs() {
var loremIpsum = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. In pulvinar augue vel dui gravida, sed convallis ante aliquam. Morbi euismod felis in justo lobortis, eu egestas quam cursus. Ut ut tellus mattis, porttitor leo ut, porttitor ex. Nulla suscipit molestie ligula, quis porta nulla pellentesque ac. Cras ut vestibulum orci. Phasellus ante nisl, dignissim non nunc eget, dapibus convallis orci. Integer vel euismod mauris. Integer tortor elit, vestibulum eget augue vitae, vehicula commodo sapien. Integer iaculis maximus dui, vitae rutrum magna congue at. Praesent varius quam vitae rhoncus fringilla. Quisque ac ex sit amet enim aliquam rutrum in in tortor. Sed sit amet est finibus, congue purus et, ultrices quam. Aenean felis velit, ullamcorper at sagittis ut, aliquam eu mauris.
Phasellus vel lorem venenatis, condimentum risus quis, ultricies risus. Vivamus porttitor lorem sit amet bibendum congue. Mauris sem enim, rutrum in ipsum eget, porttitor placerat diam. Pellentesque ut pharetra augue. Maecenas in ante eget ex efficitur tincidunt. Cras ut ultrices nisl. Donec tristique tincidunt eros condimentum tempus. Morbi libero urna, pretium id turpis vel, cursus efficitur orci. Mauris ut elit felis. Duis ultrices nisl eget accumsan consectetur. Nullam blandit elit vel vulputate scelerisque. Nulla facilisi. Cras quis maximus odio. Nam orci est, tempor ac arcu eget, tincidunt consectetur risus. Donec quis faucibus velit.
Maecenas rhoncus semper nisi non luctus. Nam accumsan malesuada urna vel vehicula. Nullam quis dui in augue tristique sollicitudin. Praesent vulputate condimentum vestibulum. Morbi tincidunt consectetur velit non accumsan. Praesent sit amet vestibulum purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla rhoncus urna ut aliquet congue. Sed ornare dignissim orci non imperdiet. Maecenas nec magna bibendum, cursus nisi ac, commodo arcu.
Sed auctor id leo at molestie. Donec sed cursus massa. Morbi semper lobortis dui. Sed mattis sem a molestie sodales. Cras consequat sapien semper, pretium nulla a, dignissim massa. Aliquam non ornare lacus. Cras gravida lorem tellus, et consectetur ante sodales ut.
Nunc mi enim, aliquam quis bibendum sed, commodo quis nulla. Aliquam vulputate arcu a volutpat semper. Donec nec mauris eros. Suspendisse velit ante, fermentum a odio non, porta dignissim nunc. Vestibulum condimentum at massa at malesuada. Etiam augue purus, interdum a est pretium, cursus interdum eros. Vestibulum et ligula dignissim, suscipit arcu et, congue sem. Integer posuere mauris id scelerisque sagittis. Proin cursus congue sem, nec pulvinar neque auctor eget. Suspendisse vitae mi ipsum. Nullam sed mauris posuere, accumsan ligula vitae, viverra tellus. Morbi quam turpis, sagittis vitae arcu vel, tempus sagittis neque. Vivamus dolor purus, blandit ac condimentum a, interdum in ipsum.`

fmt.Println(w.Wrap(loremIpsum, 80))
// Output:
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. In pulvinar augue vel
// dui gravida, sed convallis ante aliquam. Morbi euismod felis in justo lobortis,
// eu egestas quam cursus. Ut ut tellus mattis, porttitor leo ut, porttitor ex.
// Nulla suscipit molestie ligula, quis porta nulla pellentesque ac. Cras ut
// vestibulum orci. Phasellus ante nisl, dignissim non nunc eget, dapibus
// convallis orci. Integer vel euismod mauris. Integer tortor elit, vestibulum
// eget augue vitae, vehicula commodo sapien. Integer iaculis maximus dui, vitae
// rutrum magna congue at. Praesent varius quam vitae rhoncus fringilla. Quisque
// ac ex sit amet enim aliquam rutrum in in tortor. Sed sit amet est finibus,
// congue purus et, ultrices quam. Aenean felis velit, ullamcorper at sagittis ut,
// aliquam eu mauris.
//
// Phasellus vel lorem venenatis, condimentum risus quis, ultricies risus. Vivamus
// porttitor lorem sit amet bibendum congue. Mauris sem enim, rutrum in ipsum
// eget, porttitor placerat diam. Pellentesque ut pharetra augue. Maecenas in ante
// eget ex efficitur tincidunt. Cras ut ultrices nisl. Donec tristique tincidunt
// eros condimentum tempus. Morbi libero urna, pretium id turpis vel, cursus
// efficitur orci. Mauris ut elit felis. Duis ultrices nisl eget accumsan
// consectetur. Nullam blandit elit vel vulputate scelerisque. Nulla facilisi.
// Cras quis maximus odio. Nam orci est, tempor ac arcu eget, tincidunt
// consectetur risus. Donec quis faucibus velit.
//
// Maecenas rhoncus semper nisi non luctus. Nam accumsan malesuada urna vel
// vehicula. Nullam quis dui in augue tristique sollicitudin. Praesent vulputate
// condimentum vestibulum. Morbi tincidunt consectetur velit non accumsan.
// Praesent sit amet vestibulum purus. Orci varius natoque penatibus et magnis dis
// parturient montes, nascetur ridiculus mus. Nulla rhoncus urna ut aliquet
// congue. Sed ornare dignissim orci non imperdiet. Maecenas nec magna bibendum,
// cursus nisi ac, commodo arcu.
//
// Sed auctor id leo at molestie. Donec sed cursus massa. Morbi semper lobortis
// dui. Sed mattis sem a molestie sodales. Cras consequat sapien semper, pretium
// nulla a, dignissim massa. Aliquam non ornare lacus. Cras gravida lorem tellus,
// et consectetur ante sodales ut.
//
// Nunc mi enim, aliquam quis bibendum sed, commodo quis nulla. Aliquam vulputate
// arcu a volutpat semper. Donec nec mauris eros. Suspendisse velit ante,
// fermentum a odio non, porta dignissim nunc. Vestibulum condimentum at massa at
// malesuada. Etiam augue purus, interdum a est pretium, cursus interdum eros.
// Vestibulum et ligula dignissim, suscipit arcu et, congue sem. Integer posuere
// mauris id scelerisque sagittis. Proin cursus congue sem, nec pulvinar neque
// auctor eget. Suspendisse vitae mi ipsum. Nullam sed mauris posuere, accumsan
// ligula vitae, viverra tellus. Morbi quam turpis, sagittis vitae arcu vel,
// tempus sagittis neque. Vivamus dolor purus, blandit ac condimentum a, interdum
// in ipsum.
}

func ExampleWrapper_Wrap_short() {
var loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vulputate quam nibh, et faucibus enim gravida vel. Integer bibendum lectus et erat semper fermentum quis a risus. Fusce dignissim tempus metus non pretium. Nunc sagittis magna nec purus porttitor mollis. Pellentesque feugiat quam eget laoreet aliquet. Donec gravida congue massa, et sollicitudin turpis lacinia a. Fusce non tortor magna. Cras vel finibus tellus."

// Wrap on every word.
fmt.Println(Line(loremIpsum, 1))
fmt.Println(w.Wrap(loremIpsum, 1))
// Output:
// Lorem
// ipsum
Expand Down Expand Up @@ -199,3 +213,19 @@ func ExampleLine_short() {
// finibus
// tellus.
}

func ExampleWrapper_Wrap_hyphens() {
var loremIpsum = `
In this particular example, I will spam a lot of hyphenated words, which should wrap at some point, and test the multi-breakpoint feature of this package.
The girl was accident-prone, good-looking, quick-thinking, carbon-neutral, bad-tempered, sport-mad, fair-haired, camera-ready, and finally open-mouthed.
`

fmt.Println(w.Wrap(loremIpsum, 80))
// Output:
// In this particular example, I will spam a lot of hyphenated words, which should
// wrap at some point, and test the multi-breakpoint feature of this package.
//
// The girl was accident-prone, good-looking, quick-thinking, carbon-neutral, bad
// tempered, sport-mad, fair-haired, camera-ready, and finally open-mouthed.
}

0 comments on commit 1a14f77

Please sign in to comment.