more fuzzed fixes
This commit is contained in:
		
							
								
								
									
										80
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										80
									
								
								main.go
									
									
									
									
									
								
							| @@ -14,6 +14,8 @@ import ( | ||||
| 	"golang.org/x/term" | ||||
| ) | ||||
|  | ||||
| const DEBUG = false | ||||
|  | ||||
| var ( | ||||
| 	// upper-left, upper-right, left side, right side, bottom-left, bottom-right | ||||
| 	SAY_BORDER    = []rune{'/', '\\', '|', '|', '\\', '/'} | ||||
| @@ -103,7 +105,7 @@ func main() { | ||||
| 		if err != nil { | ||||
| 			log.Fatalf("error reading from stdin: %v", err) | ||||
| 		} | ||||
| 		input = strings.TrimSpace(string(pipedInput)) | ||||
| 		input = string(bytes.TrimSpace(pipedInput)) | ||||
| 	} | ||||
|  | ||||
| 	if input == "" { | ||||
| @@ -115,7 +117,7 @@ func main() { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	generateCow(string(b), input, opts) | ||||
| 	fmt.Print(generateCow(string(b), input, opts)) | ||||
| } | ||||
|  | ||||
| func generateCow(cow, say string, opt options) string { | ||||
| @@ -157,21 +159,73 @@ func generateCow(cow, say string, opt options) 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) { | ||||
| 	out := []string{} | ||||
| 	length := 0 | ||||
| 	var b strings.Builder | ||||
| 	for _, line := range text { | ||||
| 		b = strings.Builder{} | ||||
| 	var inParagraph bool | ||||
| 	for beginning, line := range text { | ||||
| 		// remove control character whitespace | ||||
| 		line = strings.ReplaceAll(line, "\t", " ") | ||||
| 		line = strings.ReplaceAll(line, "\v", " ") | ||||
| 		line = strings.ReplaceAll(line, "\f", " ") | ||||
| 		line = strings.ReplaceAll(line, "\r", " ") | ||||
| 		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 | ||||
| 		for _, word := range words { // split into words | ||||
| 			if b.Len() > 0 && word == "" { | ||||
| 				// skip multiple spaces in a row | ||||
| 		for i, word := range words { // split into words | ||||
| 			if DEBUG { | ||||
| 				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 | ||||
| 			} | ||||
| 			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]) | ||||
| 				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. | ||||
| 				// we loop until we've consumed the full overly-long word. | ||||
| 				length = max(length, column-1) | ||||
| 				out = append(out, word[0: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 | ||||
| 			b.WriteString(word) | ||||
| 			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 | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										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"))), | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "multiline test", | ||||
| 			name: "single-paragraph test", | ||||
| 			args: args{ | ||||
| 				cow: string(must(os.ReadFile("./cows/default.cow"))), | ||||
| 				say: "test\ntext", | ||||
| 				opt: defaultOptions, | ||||
| 			}, | ||||
| 			expected: string(must(os.ReadFile("./testdata/multiline.cow"))), | ||||
| 			expected: string(must(os.ReadFile("./testdata/one_paragraph.cow"))), | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "multiline with wordwrap test", | ||||
| 			args: args{ | ||||
| 				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, | ||||
| 			}, | ||||
| 			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", | ||||
| 			args: args{ | ||||
| 				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, | ||||
| 			}, | ||||
| 			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"))), | ||||
| 		}, | ||||
| 		{ | ||||
| 			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 { | ||||
| 		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.       \ | ||||
|                 |                                     | | ||||
|                 |                                     | | ||||
|                 | It goes over many lines! It'll even | | ||||
|                 \ word-wrap for us.                   / | ||||
|                  -------------------------------------  | ||||
|                  _______________________________________  | ||||
|                 / this is a long block of text. 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.       \ | ||||
| |                                     | | ||||
| |                                     | | ||||
| | It goes over many lines! It'll even | | ||||
| \ word-wrap for us.                   / | ||||
|  -------------------------------------  | ||||
|  _______________________________________  | ||||
| / this is a long block of text. It goes \ | ||||
| | over many lines! It'll even word-wrap | | ||||
| \ for us.                               / | ||||
|  ---------------------------------------  | ||||
|         \   ^__^ | ||||
|          \  (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 \ | ||||
| \ text / | ||||
|  ------  | ||||
|  _______  | ||||
| < test  > | ||||
|  -------  | ||||
|         \   ^__^ | ||||
|          \  (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 | | ||||
|                 ||     || | ||||
		Reference in New Issue
	
	Block a user