more fuzzed fixes
This commit is contained in:
parent
6aa1219f2e
commit
377c3b02b7
80
main.go
80
main.go
@ -14,6 +14,8 @@ import (
|
|||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const DEBUG = false
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// upper-left, upper-right, left side, right side, bottom-left, bottom-right
|
// upper-left, upper-right, left side, right side, bottom-left, bottom-right
|
||||||
SAY_BORDER = []rune{'/', '\\', '|', '|', '\\', '/'}
|
SAY_BORDER = []rune{'/', '\\', '|', '|', '\\', '/'}
|
||||||
@ -103,7 +105,7 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("error reading from stdin: %v", err)
|
log.Fatalf("error reading from stdin: %v", err)
|
||||||
}
|
}
|
||||||
input = strings.TrimSpace(string(pipedInput))
|
input = string(bytes.TrimSpace(pipedInput))
|
||||||
}
|
}
|
||||||
|
|
||||||
if input == "" {
|
if input == "" {
|
||||||
@ -115,7 +117,7 @@ func main() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
generateCow(string(b), input, opts)
|
fmt.Print(generateCow(string(b), input, opts))
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateCow(cow, say string, opt options) string {
|
func generateCow(cow, say string, opt options) string {
|
||||||
@ -157,21 +159,73 @@ func generateCow(cow, say string, opt options) string {
|
|||||||
return out.String()
|
return out.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this is an attempt to emulate the rather poorly-documented behavior of Text::Wrap::fill
|
||||||
|
// from CPAN: https://github.com/ap/Text-Tabs/blob/master/lib.modern/Text/Wrap.pm
|
||||||
|
// The basic idea is: take an input. Format each "paragraph" of text independently,
|
||||||
|
// then merge with empty lines between.
|
||||||
|
//
|
||||||
|
// What is under-defined in the documentation is what fill() considers a "paragraph".
|
||||||
|
// From testing and reviewing the rather obtuse code, a paragraph is any number of lines
|
||||||
|
// that start with a line that has optional whitespace at its start, then all subsequent lines
|
||||||
|
// that have no whitespace at the start, until either reaching another line that has leading
|
||||||
|
// whitespace, or a blank line. fill() also "destroy[s] any whitespace in the original text",
|
||||||
|
// by which it appearently means all tabs and multiple-spaces are replaced with single-spaces.
|
||||||
|
//
|
||||||
|
// So, for example, `text\ntext` is one paragraph, `text\n text` is two paragraphs. Since for
|
||||||
|
// our purposes we don't want any indentation of paragraphs, the output of these two examples
|
||||||
|
// should be `text text` and `text\n\ntext`, respectively.
|
||||||
|
//
|
||||||
|
// This is a pretty gnarly! Might be easier in a multi-pass model: collect together each paragraph,
|
||||||
|
// collapse them into single strings and format in a sub-function, then merge them all together.
|
||||||
|
// That's closer to how the perl module does it.
|
||||||
func wordWrap(text []string, column int) ([]string, int) {
|
func wordWrap(text []string, column int) ([]string, int) {
|
||||||
out := []string{}
|
out := []string{}
|
||||||
length := 0
|
length := 0
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
for _, line := range text {
|
var inParagraph bool
|
||||||
b = strings.Builder{}
|
for beginning, line := range text {
|
||||||
// remove control character whitespace
|
// remove control character whitespace
|
||||||
line = strings.ReplaceAll(line, "\t", " ")
|
line = strings.ReplaceAll(line, "\t", " ")
|
||||||
line = strings.ReplaceAll(line, "\v", " ")
|
line = strings.ReplaceAll(line, "\v", " ")
|
||||||
line = strings.ReplaceAll(line, "\f", " ")
|
line = strings.ReplaceAll(line, "\f", " ")
|
||||||
|
line = strings.ReplaceAll(line, "\r", " ")
|
||||||
words := strings.Split(line, " ")
|
words := strings.Split(line, " ")
|
||||||
|
// skip empty newlines if not in a paragraph, but start a new paragraph if we are.
|
||||||
|
if strings.TrimSpace(line) == "" {
|
||||||
|
if inParagraph {
|
||||||
|
length = max(length, b.Len()-1)
|
||||||
|
out = append(out, b.String()[0:b.Len()-1])
|
||||||
|
out = append(out, "")
|
||||||
|
b = strings.Builder{}
|
||||||
|
inParagraph = false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
// we -1 all over the place to clean up the loose space on the end of the builder
|
// we -1 all over the place to clean up the loose space on the end of the builder
|
||||||
for _, word := range words { // split into words
|
for i, word := range words { // split into words
|
||||||
if b.Len() > 0 && word == "" {
|
if DEBUG {
|
||||||
// skip multiple spaces in a row
|
log.Printf("%d, `%s`", i, b.String())
|
||||||
|
}
|
||||||
|
if inParagraph && i == 0 && word == "" && len(words) != 0 {
|
||||||
|
// we've found a new paragraph while we were still processing the old one.
|
||||||
|
// (that is, the new line we're parsing had leading whitespace)
|
||||||
|
length = max(length, b.Len()-1)
|
||||||
|
out = append(out, b.String()[0:b.Len()-1])
|
||||||
|
out = append(out, "")
|
||||||
|
b = strings.Builder{}
|
||||||
|
log.Println("here")
|
||||||
|
}
|
||||||
|
// bizarrely, cowsay allows for indentation to survive in the /first/ paragraph,
|
||||||
|
// but only up to two spaces worth.
|
||||||
|
if beginning == 0 && (b.Len() == 0 || b.String() == " ") && word == "" {
|
||||||
|
b.WriteRune(' ')
|
||||||
|
}
|
||||||
|
if b.Len() == 0 && word != "" {
|
||||||
|
inParagraph = true
|
||||||
|
}
|
||||||
|
if b.Len() >= 0 && word == "" && i+1 != len(words) {
|
||||||
|
// skip multiple spaces in a row...
|
||||||
|
// ...but a single trailing space on a line is OK??
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if b.Len() > 0 && b.Len()+len(word)+1 > column {
|
if b.Len() > 0 && b.Len()+len(word)+1 > column {
|
||||||
@ -182,8 +236,9 @@ func wordWrap(text []string, column int) ([]string, int) {
|
|||||||
out = append(out, b.String()[0:b.Len()-1])
|
out = append(out, b.String()[0:b.Len()-1])
|
||||||
b = strings.Builder{}
|
b = strings.Builder{}
|
||||||
}
|
}
|
||||||
if b.Len() == 0 && len(word) >= column {
|
for b.Len() == 0 && len(word) >= column {
|
||||||
// our word is longer than our maximum column size. let's break it up.
|
// our word is longer than our maximum column size. let's break it up.
|
||||||
|
// we loop until we've consumed the full overly-long word.
|
||||||
length = max(length, column-1)
|
length = max(length, column-1)
|
||||||
out = append(out, word[0:column-1])
|
out = append(out, word[0:column-1])
|
||||||
word = word[column-1:]
|
word = word[column-1:]
|
||||||
@ -192,11 +247,14 @@ func wordWrap(text []string, column int) ([]string, int) {
|
|||||||
// actually append the word and a space
|
// actually append the word and a space
|
||||||
b.WriteString(word)
|
b.WriteString(word)
|
||||||
b.WriteRune(' ')
|
b.WriteRune(' ')
|
||||||
|
if DEBUG {
|
||||||
|
log.Printf("%d, `%s`", i, b.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// out of words in the line, so save it off and start on the next one
|
|
||||||
length = max(length, b.Len()-1)
|
|
||||||
out = append(out, b.String()[0:b.Len()-1])
|
|
||||||
}
|
}
|
||||||
|
// out of words! save off our last line.
|
||||||
|
length = max(length, b.Len()-1)
|
||||||
|
out = append(out, b.String()[0:b.Len()-1])
|
||||||
return out, length
|
return out, length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
36
main_test.go
36
main_test.go
@ -43,28 +43,37 @@ func TestGenerateCow(t *testing.T) {
|
|||||||
expected: string(must(os.ReadFile("./testdata/basic.cow"))),
|
expected: string(must(os.ReadFile("./testdata/basic.cow"))),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiline test",
|
name: "single-paragraph test",
|
||||||
args: args{
|
args: args{
|
||||||
cow: string(must(os.ReadFile("./cows/default.cow"))),
|
cow: string(must(os.ReadFile("./cows/default.cow"))),
|
||||||
say: "test\ntext",
|
say: "test\ntext",
|
||||||
opt: defaultOptions,
|
opt: defaultOptions,
|
||||||
},
|
},
|
||||||
expected: string(must(os.ReadFile("./testdata/multiline.cow"))),
|
expected: string(must(os.ReadFile("./testdata/one_paragraph.cow"))),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiline with wordwrap test",
|
name: "multiline with wordwrap test",
|
||||||
args: args{
|
args: args{
|
||||||
cow: string(must(os.ReadFile("./cows/default.cow"))),
|
cow: string(must(os.ReadFile("./cows/default.cow"))),
|
||||||
say: "this is a long block of text.\n\n\nIt goes over many lines! It'll even word-wrap for us.",
|
say: "this is a long block of text.\nIt goes over many lines! It'll even word-wrap for us.",
|
||||||
opt: defaultOptions,
|
opt: defaultOptions,
|
||||||
},
|
},
|
||||||
expected: string(must(os.ReadFile("./testdata/longer_multiline.cow"))),
|
expected: string(must(os.ReadFile("./testdata/longer_multiline.cow"))),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "two-paragraph multiline with wordwrap test",
|
||||||
|
args: args{
|
||||||
|
cow: string(must(os.ReadFile("./cows/default.cow"))),
|
||||||
|
say: "this is a long block of text.\n\nIt goes over many lines! It'll even word-wrap for us.",
|
||||||
|
opt: defaultOptions,
|
||||||
|
},
|
||||||
|
expected: string(must(os.ReadFile("./testdata/two_paragraph_multiline.cow"))),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "offset bubble",
|
name: "offset bubble",
|
||||||
args: args{
|
args: args{
|
||||||
cow: string(must(os.ReadFile("./cows/dragon-and-cow.cow"))),
|
cow: string(must(os.ReadFile("./cows/dragon-and-cow.cow"))),
|
||||||
say: "this is a long block of text.\n\n\nIt goes over many lines! It'll even word-wrap for us.",
|
say: "this is a long block of text.\nIt goes over many lines! It'll even word-wrap for us.",
|
||||||
opt: defaultOptions,
|
opt: defaultOptions,
|
||||||
},
|
},
|
||||||
expected: string(must(os.ReadFile("./testdata/dragon.cow"))),
|
expected: string(must(os.ReadFile("./testdata/dragon.cow"))),
|
||||||
@ -105,6 +114,25 @@ func TestGenerateCow(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expected: string(must(os.ReadFile("./testdata/too_long.cow"))),
|
expected: string(must(os.ReadFile("./testdata/too_long.cow"))),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "fortune cow",
|
||||||
|
args: args{
|
||||||
|
cow: string(must(os.ReadFile("./cows/default.cow"))),
|
||||||
|
say: `Most people eat as though they were fattening themselves for market.
|
||||||
|
-- E.W. Howe`,
|
||||||
|
opt: defaultOptions,
|
||||||
|
},
|
||||||
|
expected: string(must(os.ReadFile("./testdata/fortune.cow"))),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "trailing space cow",
|
||||||
|
args: args{
|
||||||
|
cow: string(must(os.ReadFile("./cows/default.cow"))),
|
||||||
|
say: `test `,
|
||||||
|
opt: defaultOptions,
|
||||||
|
},
|
||||||
|
expected: string(must(os.ReadFile("./testdata/trailing_space.cow"))),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
12
testdata/dragon.cow
vendored
12
testdata/dragon.cow
vendored
@ -1,10 +1,8 @@
|
|||||||
_____________________________________
|
_______________________________________
|
||||||
/ this is a long block of text. \
|
/ this is a long block of text. It goes \
|
||||||
| |
|
| over many lines! It'll even word-wrap |
|
||||||
| |
|
\ for us. /
|
||||||
| It goes over many lines! It'll even |
|
---------------------------------------
|
||||||
\ word-wrap for us. /
|
|
||||||
-------------------------------------
|
|
||||||
\ ^ /^
|
\ ^ /^
|
||||||
\ / \ // \
|
\ / \ // \
|
||||||
\ |\___/| / \// .\
|
\ |\___/| / \// .\
|
||||||
|
11
testdata/fortune.cow
vendored
Normal file
11
testdata/fortune.cow
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
_____________________________________
|
||||||
|
/ Most people eat as though they were \
|
||||||
|
| fattening themselves for market. |
|
||||||
|
| |
|
||||||
|
\ -- E.W. Howe /
|
||||||
|
-------------------------------------
|
||||||
|
\ ^__^
|
||||||
|
\ (oo)\_______
|
||||||
|
(__)\ )\/\
|
||||||
|
||----w |
|
||||||
|
|| ||
|
2
testdata/fuzz/FuzzCow/7b9a589d5aa859e5
vendored
Normal file
2
testdata/fuzz/FuzzCow/7b9a589d5aa859e5
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
string("0 \n0")
|
2
testdata/fuzz/FuzzCow/93d7daf11243d295
vendored
Normal file
2
testdata/fuzz/FuzzCow/93d7daf11243d295
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
string("0000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
2
testdata/fuzz/FuzzCow/ceaa3f997e266d98
vendored
Normal file
2
testdata/fuzz/FuzzCow/ceaa3f997e266d98
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
string("0 \n 0")
|
2
testdata/fuzz/FuzzCow/dde7a120ccdf11af
vendored
Normal file
2
testdata/fuzz/FuzzCow/dde7a120ccdf11af
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
string("0\r0")
|
12
testdata/longer_multiline.cow
vendored
12
testdata/longer_multiline.cow
vendored
@ -1,10 +1,8 @@
|
|||||||
_____________________________________
|
_______________________________________
|
||||||
/ this is a long block of text. \
|
/ this is a long block of text. It goes \
|
||||||
| |
|
| over many lines! It'll even word-wrap |
|
||||||
| |
|
\ for us. /
|
||||||
| It goes over many lines! It'll even |
|
---------------------------------------
|
||||||
\ word-wrap for us. /
|
|
||||||
-------------------------------------
|
|
||||||
\ ^__^
|
\ ^__^
|
||||||
\ (oo)\_______
|
\ (oo)\_______
|
||||||
(__)\ )\/\
|
(__)\ )\/\
|
||||||
|
8
testdata/one_paragraph.cow
vendored
Normal file
8
testdata/one_paragraph.cow
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
___________
|
||||||
|
< test text >
|
||||||
|
-----------
|
||||||
|
\ ^__^
|
||||||
|
\ (oo)\_______
|
||||||
|
(__)\ )\/\
|
||||||
|
||----w |
|
||||||
|
|| ||
|
@ -1,7 +1,6 @@
|
|||||||
______
|
_______
|
||||||
/ test \
|
< test >
|
||||||
\ text /
|
-------
|
||||||
------
|
|
||||||
\ ^__^
|
\ ^__^
|
||||||
\ (oo)\_______
|
\ (oo)\_______
|
||||||
(__)\ )\/\
|
(__)\ )\/\
|
11
testdata/two_paragraph_multiline.cow
vendored
Normal file
11
testdata/two_paragraph_multiline.cow
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
_____________________________________
|
||||||
|
/ this is a long block of text. \
|
||||||
|
| |
|
||||||
|
| It goes over many lines! It'll even |
|
||||||
|
\ word-wrap for us. /
|
||||||
|
-------------------------------------
|
||||||
|
\ ^__^
|
||||||
|
\ (oo)\_______
|
||||||
|
(__)\ )\/\
|
||||||
|
||----w |
|
||||||
|
|| ||
|
Loading…
Reference in New Issue
Block a user