...
1
16
17 package explain
18
19 import (
20 "fmt"
21 "io"
22 "regexp"
23 "strings"
24 )
25
26
27 type Formatter struct {
28 IndentLevel int
29 Wrap int
30 Writer io.Writer
31 }
32
33
34 func (f Formatter) Indent(indent int) *Formatter {
35 f.IndentLevel = f.IndentLevel + indent
36 return &f
37 }
38
39
40
41 func (f *Formatter) Write(str string, a ...interface{}) error {
42
43 if str == "" {
44 _, err := io.WriteString(f.Writer, "\n")
45 return err
46 }
47
48 indent := ""
49 for i := 0; i < f.IndentLevel; i++ {
50 indent = indent + " "
51 }
52
53 if len(a) > 0 {
54 str = fmt.Sprintf(str, a...)
55 }
56 _, err := io.WriteString(f.Writer, indent+str+"\n")
57 return err
58 }
59
60
61
62 func (f *Formatter) WriteWrapped(str string, a ...interface{}) error {
63 if f.Wrap == 0 {
64 return f.Write(str, a...)
65 }
66 text := fmt.Sprintf(str, a...)
67 strs := wrapString(text, f.Wrap-f.IndentLevel)
68 for _, substr := range strs {
69 if err := f.Write(substr); err != nil {
70 return err
71 }
72 }
73 return nil
74 }
75
76 type line struct {
77 wrap int
78 words []string
79 }
80
81 func (l *line) String() string {
82 return strings.Join(l.words, " ")
83 }
84
85 func (l *line) Empty() bool {
86 return len(l.words) == 0
87 }
88
89 func (l *line) Len() int {
90 return len(l.String())
91 }
92
93
94
95 func (l *line) Add(word string) bool {
96 newLine := line{
97 wrap: l.wrap,
98 words: append(l.words, word),
99 }
100 if newLine.Len() <= l.wrap || len(l.words) == 0 {
101 l.words = newLine.words
102 return true
103 }
104 return false
105 }
106
107 var bullet = regexp.MustCompile(`^(\d+\.?|-|\*)\s`)
108
109 func shouldStartNewLine(lastWord, str string) bool {
110
111 if strings.HasSuffix(lastWord, ":") {
112 return true
113 }
114
115
116 if strings.HasPrefix(str, " ") {
117 return true
118 }
119 str = strings.TrimSpace(str)
120
121 if len(str) == 0 {
122 return true
123 }
124
125 if bullet.MatchString(str) {
126 return true
127 }
128
129 return false
130 }
131
132 func wrapString(str string, wrap int) []string {
133 wrapped := []string{}
134 l := line{wrap: wrap}
135
136 lastWord := ""
137 flush := func() {
138 if !l.Empty() {
139 lastWord = ""
140 wrapped = append(wrapped, l.String())
141 l = line{wrap: wrap}
142 }
143 }
144
145
146 for _, str := range strings.Split(str, "\n") {
147
148 if strings.HasPrefix(str, " ") {
149 flush()
150 wrapped = append(wrapped, str)
151 continue
152 }
153
154
155 if len(wrapped) > 0 && len(strings.TrimSpace(str)) == 0 {
156 flush()
157 wrapped = append(wrapped, "")
158 continue
159 }
160
161
162 if shouldStartNewLine(lastWord, str) {
163 flush()
164 }
165 words := strings.Fields(str)
166 for _, word := range words {
167 lastWord = word
168 if !l.Add(word) {
169 flush()
170 if !l.Add(word) {
171 panic("Couldn't add to empty line.")
172 }
173 }
174 }
175 }
176 flush()
177 return wrapped
178 }
179
View as plain text