-
Notifications
You must be signed in to change notification settings - Fork 0
/
para.go
144 lines (132 loc) · 3.7 KB
/
para.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// Wrap text to a given column number. Acts as a filter from stdin to stdout.
package main
import (
"bufio"
"log"
"os"
"strconv"
"strings"
"unicode"
)
const defaultWrapCol = 80
func main() {
var wrap int
if len(os.Args) < 2 {
wrap = defaultWrapCol
} else {
num, err := strconv.ParseInt(os.Args[1], 10, 0)
if err != nil {
log.Fatalf("could not read column width: %v", err)
} else {
wrap = int(num)
}
}
scanner := bufio.NewScanner(os.Stdin)
writer := bufio.NewWriter(os.Stdout)
r := Wrapper{maxCols: wrap}
err := r.Wraptext(scanner, writer)
if err != nil {
log.Fatalf("could not wrap text: %v", err)
}
}
// Wrapper contains methods for compressive text wrapping.
//
// It operates on line-oriented streams, as that is how it will be used from
// the command line.
type Wrapper struct {
maxCols int
}
// isMarkdownStart determines whether the text opens with
// a Markdown section or list indicator
func isMarkdownStart(text string) bool {
return strings.HasPrefix(text, "#") ||
strings.HasPrefix(text, "-") ||
strings.HasPrefix(text, "*")
}
// closesParagraph checks if the text ends in a full stop
func closesParagraph(text string) bool {
return strings.HasSuffix(text, ".")
}
// Wraptext wraps text to column length, compacting paragraphs along the way.
// It respects lines that end in a period, as well as Markdown lists and sections
func (wr Wrapper) Wraptext(scanner *bufio.Scanner, writer *bufio.Writer) error {
var carry int
// flushRunningText puts a line break on the open end of the text
flushRunningText := func() {
if carry > 0 {
writer.WriteString("\n")
carry = 0
}
}
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if len(line) == 0 {
// Respect paragraphs
flushRunningText()
writer.WriteString("\n")
continue
}
if isMarkdownStart(line) {
flushRunningText()
}
var wrapped string
wrapped, carry = wr.wrapLine(line, carry)
writer.WriteString(wrapped)
if closesParagraph(line) || isMarkdownStart(line) {
// Respect full stops, markdown
flushRunningText()
}
}
if scanner.Err() != nil {
return scanner.Err()
}
return writer.Flush()
}
// wrapLine wraps a single line to a max number of columns, possibly breaking it
// into more lines by adding newlines in-place between words.
// There may be carry from a previous line wrapping.
//
// returns the wrapped text, and the number of carry-over characters
//
// NOTE: the input lines come from a line-scanner and don't have the terminating `\n`
func (wr Wrapper) wrapLine(line string, carry int) (string, int) {
lastWhite := -1
lastNewline := -carry - 1
out := make([]byte, len(line))
copy(out, line)
var startWithBreak bool
for j := 0; j < len(line); j++ {
if unicode.IsSpace(rune(line[j])) {
lastWhite = j
}
if j-lastWhite > wr.maxCols {
log.Fatal("Word exceeds maxCols, line cannot be wrapped: " + line)
}
if j-lastNewline > wr.maxCols && lastWhite > -1 {
// we exceeded maxcols and can put a linebreak between previous words
out[lastWhite] = '\n'
lastNewline = lastWhite
} else if j-lastNewline > wr.maxCols && lastNewline < -1 {
// counting the carry, we would exceed maxCols.
// We should break from the previous fragment
startWithBreak = true
lastNewline = -1
} else if j-lastNewline > wr.maxCols {
panic("Should never get here")
}
}
var wrapped string
switch {
case startWithBreak:
// we were unable to compress by appending to the previous carry
wrapped = "\n" + string(out)
case carry > 0:
// we appended text to previous carry - we add a word break
wrapped = " " + string(out)
default:
wrapped = string(out)
}
lastBreak := strings.LastIndex(wrapped, "\n")
newCarry := len(wrapped) - lastBreak
return wrapped, newCarry
}