diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ec40192 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store + +./cowsay diff --git a/cows/MechAndCow.cow b/cows/MechAndCow.cow new file mode 100644 index 0000000..e2e0eb2 --- /dev/null +++ b/cows/MechAndCow.cow @@ -0,0 +1,17 @@ +## OFFSET 10 + $thoughts ,-----. + $thoughts | | + $thoughts ,--| |-. + __,----| | | | + ,;:: | `_____' | + `._______| i^i | + `----| |---'| . + ,-------._| |== ||// + | |_|P`. /'/ + `-------' 'Y Y/'/' + .==\ /_\ + ^__^ / /'| `i + ($eyes)\_______ /' / | | + (__)\ )\/\ /' / | `i + $tongue ||----w | ___,;`----'.___L_,-'`\__ + || || i_____;----\.____i""\____\ \ No newline at end of file diff --git a/cows/TuxStab.cow b/cows/TuxStab.cow new file mode 100644 index 0000000..3b9e5e0 --- /dev/null +++ b/cows/TuxStab.cow @@ -0,0 +1,20 @@ +## OFFSET 8 + $thoughts , , + $thoughts /( )` + $thoughts \ \___ / | + /- _ `-/ ' + (/\/ \ \ /\ + / / | ` \ + O O ) / | + `-^--'`< ' + .--. (_.) _ ) / + |o_o | `.___/` / + |:_/ | `-----' / + //<- \ \----. __ / __ \ + (| <- | )---|====O)))==) \) /==== + /'\ <- _/`\---' `--' `.__,' \ + \___)=(___/ | | + \ / + ______( (_ / \______ + ,' ,-----' | \ + `--\{__________) \/ \ No newline at end of file diff --git a/cows/beavis.zen.cow b/cows/beavis.zen.cow index 758ee80..579baf5 100644 --- a/cows/beavis.zen.cow +++ b/cows/beavis.zen.cow @@ -1,10 +1,9 @@ ## ## Beavis, with Zen philosophy removed. ## -$the_cow = <-' )._. |-' - `-|.' /_. \\_| F + `-|.' /_. \_| F /.- . _.< - /' /.' .' `\\ - /L /' |/ _.-'-\\ - /'J ___.---'\\| - |\\ .--' V | `. ` + /' /.' .' `\ + /L /' |/ _.-'-\ + /'J ___.---'\| + |\ .--' V | `. ` |/`. `-. `._) - / .-.\\ - VK \\ ( `\\ - `.\\ - -EOC + / .-.\ + VK \ ( `\ + `.\ \ No newline at end of file diff --git a/cows/bong.cow b/cows/bong.cow index 0ae3a14..45e0a26 100644 --- a/cows/bong.cow +++ b/cows/bong.cow @@ -1,16 +1,14 @@ ## ## A cow with a bong, from lars@csua.berkeley.edu ## -$the_cow = <> 5.4 ## -$the_cow = < \\ _ -~ `. ^-` ^-_ + (( // / )) '/\ / ~-. _ .-~ .-~^-. \ + (( /// )) `. { } / \ \ + (( / )) .----~-.\ \-' .~ \ `. \^-. + ///.----..> \ _ -~ `. ^-` ^-_ ///-._ _ _ _ _ _ _}^ - - - - ~ ~-- ,.-~ /.-~ -EOC diff --git a/cows/elephant-in-snake.cow b/cows/elephant-in-snake.cow index 1003cdf..462153a 100644 --- a/cows/elephant-in-snake.cow +++ b/cows/elephant-in-snake.cow @@ -1,7 +1,6 @@ ## ## Do we need to explain this? ## -$the_cow = < - ---___ XXX__/ XXXXXX \\__ / - \\- --__/ ___/\\ XXXXXX / ___--/= - \\-\\ ___/ XXXXXX '--- XXXXXX - \\-\\/XXX\\ XXXXXX /XXXXX - \\XXXXXXXXX \\ /XXXXX/ - \\XXXXXX > _/XXXXX/ - \\XXXXX--__/ __-- XXXX/ + /XXX( U ) XXXXXXX\ + /XXXXX( )--_ XXXXXXXXXXX\ + /XXXXX/ ( O ) XXXXXX \XXXXX\ + XXXXX/ / XXXXXX \__ \XXXXX + XXXXXX__/ XXXXXX \__----> + ---___ XXX__/ XXXXXX \__ / + \- --__/ ___/\ XXXXXX / ___--/= + \-\ ___/ XXXXXX '--- XXXXXX + \-\/XXX\ XXXXXX /XXXXX + \XXXXXXXXX \ /XXXXX/ + \XXXXXX > _/XXXXX/ + \XXXXX--__/ __-- XXXX/ -XXXXXXXX--------------- XXXXXX- - \\XXXXXXXXXXXXXXXXXXXXXXXXXX/ - ""VXXXXXXXXXXXXXXXXXXV"" -EOC + \XXXXXXXXXXXXXXXXXXXXXXXXXX/ + ""VXXXXXXXXXXXXXXXXXXV"" \ No newline at end of file diff --git a/cows/hellokitty.cow b/cows/hellokitty.cow index 80dce96..eca3734 100644 --- a/cows/hellokitty.cow +++ b/cows/hellokitty.cow @@ -1,11 +1,9 @@ ## ## Hello Kitty ## -$the_cow = < -EOC + <__/ \__> \ No newline at end of file diff --git a/cows/moofasa.cow b/cows/moofasa.cow index 15119d6..3bb7d96 100644 --- a/cows/moofasa.cow +++ b/cows/moofasa.cow @@ -1,14 +1,12 @@ ## ## MOOfasa. ## -$the_cow = < < > .---. - $thoughts | \\ \\ - ~ ~ - / / | + $thoughts | \ \ - ~ ~ - / / | _____ ..-~ ~-..-~ - | | \\~~~\\.' `./~~~/ - --------- \\__/ \\__/ - .' O \\ / / \\ " - (_____, `._.' | } \\/~~~/ - `----. / } | / \\__/ + | | \~~~\.' `./~~~/ + --------- \__/ \__/ + .' O \ / / \ " + (_____, `._.' | } \/~~~/ + `----. / } | / \__/ `-. | / | / `. ,~~| ~-.__| /_ - ~ ^| /- _ `..-' | / | / ~-. `-. _ _ _ - |_____| |_____| ~ - . _ _ _ _ _> -EOC + |_____| |_____| ~ - . _ _ _ _ _> \ No newline at end of file diff --git a/cows/stimpy.cow b/cows/stimpy.cow index 4d61425..7e2aab9 100644 --- a/cows/stimpy.cow +++ b/cows/stimpy.cow @@ -1,19 +1,17 @@ ## ## Stimpy! ## -$the_cow = <____) >___ ^\\_\\_\\_\\_\\_\\_\\) - ^^^//\\\\_^^//\\\\_^ ^(\\_\\_\\_\\) - ^^^ ^^ ^^^ ^ -EOC + \ \_)^)_)) ))^^^^^^^^^^))^^^^) + (_ ^\__^^^^^^^^^^^^))^^^^^^^) + ^\___ ^\__^^^^^^))^^^^^^^^)\\ + ^^^^^\uuu/^^\uuu/^^^^\^\^\^\^\^\^\^\ + ___) >____) >___ ^\_\_\_\_\_\_\) + ^^^//\\_^^//\\_^ ^(\_\_\_\) + ^^^ ^^ ^^^ ^ \ No newline at end of file diff --git a/cows/turtle.cow b/cows/turtle.cow index 4286b09..b80fb09 100644 --- a/cows/turtle.cow +++ b/cows/turtle.cow @@ -1,20 +1,18 @@ ## ## A mysterious turtle... ## -$the_cow = <'} + THINK_BORDER = []rune{'(', ')', '(', ')', '(', ')'} +) + +type options struct { + thinking bool + tongue string + eyes string + leftEye string + rightEye string + wrapWidth int +} + +// the cowfiles +// +//go:embed cows/* +var cowTemplates embed.FS + +func main() { + // basic flags + cowFile := flag.String("cow", "default.cow", "the cowfile to use") + tongue := flag.String("T", " ", "the tongue to print") + eyesFlag := flag.String("e", "oo", "the eyes") + width := flag.Int("W", 40, "approximate column width for the word-wrap") + + // also need to check for binary name here + thinkFlag := flag.Bool("think", false, "if the cow is thinking the input instead") + + // flags for special conditions + // these flags override -e and -T if set + borgFlag := flag.Bool("b", false, "borg cow") // == + deadFlag := flag.Bool("d", false, "dead cow") // xx AND tongue "U " + greedyFlag := flag.Bool("g", false, "greedy cow") // $$ + paranoidFlag := flag.Bool("p", false, "paranoid cow") // @@ + stonedFlag := flag.Bool("s", false, "stoned cow") // ** AND tongue "U " + tiredFlag := flag.Bool("t", false, "tired cow") // -- + wiredFlag := flag.Bool("w", false, "wired cow") // OO + youngFlag := flag.Bool("y", false, "young cow") // .. + flag.Parse() + + input := flag.Arg(0) + + if len(*tongue) < 2 { + *tongue = *tongue + strings.Repeat(" ", 2-len(*tongue)) + } + if len(*eyesFlag) < 2 { + *eyesFlag = *eyesFlag + strings.Repeat(" ", 2-len(*eyesFlag)) + } + + opts := options{ + thinking: *thinkFlag, + tongue: (*tongue)[0:2], + eyes: (*eyesFlag)[0:2], + wrapWidth: *width, + } + + switch { + case *borgFlag: + opts.eyes = "==" + case *deadFlag: + opts.eyes = "xx" + opts.tongue = "U " + case *greedyFlag: + opts.eyes = "$$" + case *paranoidFlag: + opts.eyes = "@@" + case *stonedFlag: + opts.eyes = "**" + opts.tongue = "U " + case *tiredFlag: + opts.eyes = "--" + case *wiredFlag: + opts.eyes = "OO" + case *youngFlag: + opts.eyes = ".." + } + + opts.leftEye = strings.Split(*eyesFlag, "")[0] + opts.rightEye = strings.Split(*eyesFlag, "")[1] + + // if there is command-line input, ignore stdin. + if !term.IsTerminal(0) && input != "" { + pipedInput, err := io.ReadAll(os.Stdin) + if err != nil { + log.Fatalf("error reading from stdin: %v", err) + } + input = strings.TrimSpace(string(pipedInput)) + } + + if input == "" { + log.Fatal("no input; cow can't say anything") + } + + b, err := cowTemplates.ReadFile("cows/" + *cowFile) + if err != nil { + log.Fatal(err) + } + + generateCow(string(b), input, opts) +} + +func generateCow(cow, say string, opt options) string { + cowSlice := strings.Split(cow, "\n") + + offset := 0 + // Remove comments + i := 0 + for _, s := range cowSlice { + if strings.HasPrefix(s, "##") && strings.Contains(s, "OFFSET") { + _, off, found := strings.Cut(s, "OFFSET ") + if found { + offset, _ = strconv.Atoi(off) + } + } + if !strings.HasPrefix(s, "##") { + cowSlice[i] = s + i++ + } + } + cow = strings.Join(cowSlice[:i], "\n") + + // do replaces + tail := `\` + if opt.thinking { + tail = `o` + } + cow = strings.ReplaceAll(cow, "$thoughts", tail) + cow = strings.ReplaceAll(cow, "$eyes", opt.eyes) + cow = strings.ReplaceAll(cow, "$el", opt.leftEye) + cow = strings.ReplaceAll(cow, "$er", opt.rightEye) + cow = strings.ReplaceAll(cow, "$tongue", opt.tongue) + + out := &bytes.Buffer{} + fmt.Fprint(out, bubble(say, opt.thinking, opt.wrapWidth, offset)) + fmt.Fprintln(out) + fmt.Fprint(out, cow) + fmt.Fprintln(out) + return out.String() +} + +func wordWrap(text []string, column int) ([]string, int) { + out := []string{} + length := 0 + var b strings.Builder + for _, line := range text { + b = strings.Builder{} + // remove control character whitespace + line = strings.ReplaceAll(line, "\t", " ") + line = strings.ReplaceAll(line, "\v", " ") + line = strings.ReplaceAll(line, "\f", " ") + words := strings.Split(line, " ") + // 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 + continue + } + if b.Len() > 0 && b.Len()+len(word)+1 > column { + // if the word we want to append is bigger than we have room for, + // save off the maximum length, save the current line to the output, + // and start a new builder. + length = max(length, b.Len()-1) + out = append(out, b.String()[0:b.Len()-1]) + b = strings.Builder{} + } + if b.Len() == 0 && len(word) >= column { + // our word is longer than our maximum column size. let's break it up. + length = max(length, column-1) + out = append(out, word[0:column-1]) + word = word[column-1:] + b = strings.Builder{} + } + // actually append the word and a space + b.WriteString(word) + b.WriteRune(' ') + } + // 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]) + } + return out, length +} + +func padText(text []string, maxLength int) []string { + for i, line := range text { + if len(line) < maxLength { + text[i] = text[i] + strings.Repeat(" ", maxLength-len(line)) + } + } + return text +} + +func bubble(text string, think bool, column int, offset int) string { + s, column := wordWrap(strings.Split(text, "\n"), column) + s = padText(s, column) + b := strings.Builder{} + padding := strings.Repeat(" ", offset) + b.WriteString(padding) + b.WriteString(" ") + b.WriteString(strings.Repeat("_", column+2)) + b.WriteString(" \n") + var borderStyle []rune + if think { + borderStyle = THINK_BORDER + } else if len(s) == 1 { + borderStyle = SINGLE_BORDER + } else { + borderStyle = SAY_BORDER + } + for i := range s { + b.WriteString(padding) + if i == 0 { + b.WriteRune(borderStyle[0]) + } else if i == len(s)-1 { + b.WriteRune(borderStyle[4]) + } else { + b.WriteRune(borderStyle[2]) + } + b.WriteRune(' ') + b.WriteString(s[i]) + b.WriteRune(' ') + if i == 0 { + b.WriteRune(borderStyle[1]) + } else if i == len(s)-1 { + b.WriteRune(borderStyle[5]) + } else { + b.WriteRune(borderStyle[3]) + } + b.WriteRune('\n') + } + b.WriteString(padding) + b.WriteString(" ") + b.WriteString(strings.Repeat("-", column+2)) + b.WriteString(" ") + return b.String() +} diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..3997df7 --- /dev/null +++ b/main_test.go @@ -0,0 +1,144 @@ +package main + +import ( + "os" + "os/exec" + "strings" + "testing" +) + +func must[T any](t T, err error) T { + if err != nil { + panic(err) + } + return t +} + +var defaultOptions options = options{ + tongue: " ", + eyes: "oo", + leftEye: "o", + rightEye: "o", + wrapWidth: 40, +} + +func TestGenerateCow(t *testing.T) { + type args struct { + cow string + say string + opt options + } + tests := []struct { + name string + args args + expected string + }{ + { + name: "basic cow", + args: args{ + cow: string(must(os.ReadFile("./cows/default.cow"))), + say: "test text", + opt: defaultOptions, + }, + expected: string(must(os.ReadFile("./testdata/basic.cow"))), + }, + { + name: "multiline test", + args: args{ + cow: string(must(os.ReadFile("./cows/default.cow"))), + say: "test\ntext", + opt: defaultOptions, + }, + expected: string(must(os.ReadFile("./testdata/multiline.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.", + opt: defaultOptions, + }, + expected: string(must(os.ReadFile("./testdata/longer_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.", + opt: defaultOptions, + }, + expected: string(must(os.ReadFile("./testdata/dragon.cow"))), + }, + { + name: "leading space cow", + args: args{ + cow: string(must(os.ReadFile("./cows/default.cow"))), + say: " text", + opt: defaultOptions, + }, + expected: string(must(os.ReadFile("./testdata/leading_space.cow"))), + }, + { + name: "extra spaces cow", + args: args{ + cow: string(must(os.ReadFile("./cows/default.cow"))), + say: "some text", + opt: defaultOptions, + }, + expected: string(must(os.ReadFile("./testdata/extra_spaces.cow"))), + }, + { + name: "vertical tab cow", + args: args{ + cow: string(must(os.ReadFile("./cows/default.cow"))), + say: "some\vtext", + opt: defaultOptions, + }, + expected: string(must(os.ReadFile("./testdata/extra_spaces.cow"))), + }, + { + name: "a word that's too long cow", + args: args{ + cow: string(must(os.ReadFile("./cows/default.cow"))), + say: "00000000000000000000000000000000000000000000000000 11", + opt: defaultOptions, + }, + expected: string(must(os.ReadFile("./testdata/too_long.cow"))), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + out := generateCow(tt.args.cow, tt.args.say, tt.args.opt) + if out != tt.expected { + t.Logf("expected: \n%v", tt.expected) + t.Logf("got: \n%v", out) + t.Fatal() + } + }) + } +} + +// fuzz testing requires perl to be installed and in the PATH +func FuzzCow(f *testing.F) { + cowString := string(must(os.ReadFile("./cows/default.cow"))) + f.Add("phrase") + f.Add("phrase with many many words in it so that we get a word-wrap situation") + f.Fuzz(func(t *testing.T, phrase string) { + if strings.TrimSpace(phrase) != phrase || phrase == "" { + // uninteresting degenerate cases where there's a bug in the perl + t.Skip() + } + res := generateCow(cowString, phrase, options{tongue: " ", eyes: "oo", wrapWidth: 40}) + cmd := exec.Command("perl", "./perl/cowsay", "-f", "./perl/cows/default.cow") + cmd.Stdin = strings.NewReader(phrase) + var out strings.Builder + cmd.Stdout = &out + err := cmd.Run() + if err != nil { + t.Error(err) + } + if res != out.String() { + t.Errorf("mismatch between perl and go.\nPerl:\n%s\ngo:\n%s", out.String(), res) + } + }) +} diff --git a/CONTRIBUTING.md b/perl/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to perl/CONTRIBUTING.md diff --git a/ChangeLog b/perl/ChangeLog similarity index 100% rename from ChangeLog rename to perl/ChangeLog diff --git a/Files.base b/perl/Files.base similarity index 100% rename from Files.base rename to perl/Files.base diff --git a/INSTALL b/perl/INSTALL similarity index 100% rename from INSTALL rename to perl/INSTALL diff --git a/LICENSE b/perl/LICENSE similarity index 100% rename from LICENSE rename to perl/LICENSE diff --git a/MANIFEST b/perl/MANIFEST similarity index 100% rename from MANIFEST rename to perl/MANIFEST diff --git a/Makefile b/perl/Makefile similarity index 100% rename from Makefile rename to perl/Makefile diff --git a/README b/perl/README similarity index 100% rename from README rename to perl/README diff --git a/README.md b/perl/README.md similarity index 100% rename from README.md rename to perl/README.md diff --git a/RELEASE b/perl/RELEASE similarity index 100% rename from RELEASE rename to perl/RELEASE diff --git a/Wrap.pm.diff b/perl/Wrap.pm.diff similarity index 100% rename from Wrap.pm.diff rename to perl/Wrap.pm.diff diff --git a/cowenv b/perl/cowenv similarity index 100% rename from cowenv rename to perl/cowenv diff --git a/cows/DragonAndCow.pm b/perl/cows/DragonAndCow.pm similarity index 100% rename from cows/DragonAndCow.pm rename to perl/cows/DragonAndCow.pm diff --git a/cows/Example.pm b/perl/cows/Example.pm similarity index 100% rename from cows/Example.pm rename to perl/cows/Example.pm diff --git a/cows/Frogs.pm b/perl/cows/Frogs.pm similarity index 100% rename from cows/Frogs.pm rename to perl/cows/Frogs.pm diff --git a/cows/MechAndCow.pm b/perl/cows/MechAndCow.pm similarity index 100% rename from cows/MechAndCow.pm rename to perl/cows/MechAndCow.pm diff --git a/cows/Stegosaurus.pm b/perl/cows/Stegosaurus.pm similarity index 100% rename from cows/Stegosaurus.pm rename to perl/cows/Stegosaurus.pm diff --git a/cows/TextBalloon.pm b/perl/cows/TextBalloon.pm similarity index 100% rename from cows/TextBalloon.pm rename to perl/cows/TextBalloon.pm diff --git a/cows/TuxStab.pm b/perl/cows/TuxStab.pm similarity index 100% rename from cows/TuxStab.pm rename to perl/cows/TuxStab.pm diff --git a/perl/cows/beavis.zen.cow b/perl/cows/beavis.zen.cow new file mode 100644 index 0000000..758ee80 --- /dev/null +++ b/perl/cows/beavis.zen.cow @@ -0,0 +1,23 @@ +## +## Beavis, with Zen philosophy removed. +## +$the_cow = <-' )._. |-' + `-|.' /_. \\_| F + /.- . _.< + /' /.' .' `\\ + /L /' |/ _.-'-\\ + /'J ___.---'\\| + |\\ .--' V | `. ` + |/`. `-. `._) + / .-.\\ + VK \\ ( `\\ + `.\\ + +EOC diff --git a/perl/cows/bong.cow b/perl/cows/bong.cow new file mode 100644 index 0000000..0ae3a14 --- /dev/null +++ b/perl/cows/bong.cow @@ -0,0 +1,16 @@ +## +## A cow with a bong, from lars@csua.berkeley.edu +## +$the_cow = <> 5.4 +## +$the_cow = < \\ _ -~ `. ^-` ^-_ + ///-._ _ _ _ _ _ _}^ - - - - ~ ~-- ,.-~ + /.-~ +EOC diff --git a/perl/cows/elephant-in-snake.cow b/perl/cows/elephant-in-snake.cow new file mode 100644 index 0000000..1003cdf --- /dev/null +++ b/perl/cows/elephant-in-snake.cow @@ -0,0 +1,14 @@ +## +## Do we need to explain this? +## +$the_cow = < + ---___ XXX__/ XXXXXX \\__ / + \\- --__/ ___/\\ XXXXXX / ___--/= + \\-\\ ___/ XXXXXX '--- XXXXXX + \\-\\/XXX\\ XXXXXX /XXXXX + \\XXXXXXXXX \\ /XXXXX/ + \\XXXXXX > _/XXXXX/ + \\XXXXX--__/ __-- XXXX/ + -XXXXXXXX--------------- XXXXXX- + \\XXXXXXXXXXXXXXXXXXXXXXXXXX/ + ""VXXXXXXXXXXXXXXXXXXV"" +EOC diff --git a/cows/head-in.cow b/perl/cows/head-in.cow similarity index 100% rename from cows/head-in.cow rename to perl/cows/head-in.cow diff --git a/perl/cows/hellokitty.cow b/perl/cows/hellokitty.cow new file mode 100644 index 0000000..80dce96 --- /dev/null +++ b/perl/cows/hellokitty.cow @@ -0,0 +1,11 @@ +## +## Hello Kitty +## +$the_cow = < +EOC diff --git a/perl/cows/moofasa.cow b/perl/cows/moofasa.cow new file mode 100644 index 0000000..15119d6 --- /dev/null +++ b/perl/cows/moofasa.cow @@ -0,0 +1,14 @@ +## +## MOOfasa. +## +$the_cow = < < > .---. + $thoughts | \\ \\ - ~ ~ - / / | + _____ ..-~ ~-..-~ + | | \\~~~\\.' `./~~~/ + --------- \\__/ \\__/ + .' O \\ / / \\ " + (_____, `._.' | } \\/~~~/ + `----. / } | / \\__/ + `-. | / | / `. ,~~| + ~-.__| /_ - ~ ^| /- _ `..-' + | / | / ~-. `-. _ _ _ + |_____| |_____| ~ - . _ _ _ _ _> +EOC diff --git a/perl/cows/stimpy.cow b/perl/cows/stimpy.cow new file mode 100644 index 0000000..4d61425 --- /dev/null +++ b/perl/cows/stimpy.cow @@ -0,0 +1,19 @@ +## +## Stimpy! +## +$the_cow = <____) >___ ^\\_\\_\\_\\_\\_\\_\\) + ^^^//\\\\_^^//\\\\_^ ^(\\_\\_\\_\\) + ^^^ ^^ ^^^ ^ +EOC diff --git a/perl/cows/turtle.cow b/perl/cows/turtle.cow new file mode 100644 index 0000000..4286b09 --- /dev/null +++ b/perl/cows/turtle.cow @@ -0,0 +1,20 @@ +## +## A mysterious turtle... +## +$the_cow = < 'oo', + 'f' => 'default.cow', + 'n' => 0, + 'T' => ' ', + 'W' => 40, +); + +getopts('bde:f:ghlLnNpstT:wW:y', \%opts); + +&display_usage if $opts{'h'}; +&list_cowfiles if $opts{'l'}; + +$borg = $opts{'b'}; +$dead = $opts{'d'}; +$greedy = $opts{'g'}; +$paranoid = $opts{'p'}; +$stoned = $opts{'s'}; +$tired = $opts{'t'}; +$wired = $opts{'w'}; +$young = $opts{'y'}; +$eyes = substr($opts{'e'}, 0, 2); +$tongue = substr($opts{'T'}, 0, 2); +$the_cow = ""; + +&slurp_input; +$Text::Wrap::columns = $opts{'W'}; +@message = ($opts{'n'} ? expand(@message) : + split("\n", fill("", "", @message))); +&construct_balloon; +&construct_face; +&get_cow; +print @balloon_lines; +print $the_cow; + +sub list_cowfiles { + my $basedir; + my @dirfiles; + chop($basedir = cwd); + for my $d (split(/$pathsep/, $cowpath)) { + print "Cow files in $d:\n"; + opendir(COWDIR, $d) || die "$0: Cannot open $d\n"; + for my $file (readdir COWDIR) { + if ($file =~ s/\.cow$//) { + push(@dirfiles, $file); + } + } + closedir(COWDIR); + print wrap("", "", sort @dirfiles), "\n"; + @dirfiles = (); + chdir($basedir); + } + exit(0); +} + +sub slurp_input { + unless ($ARGV[0]) { + chomp(@message = ); + } else { + &display_usage if $opts{'n'}; + @message = join(' ', @ARGV); + } +} + +sub maxlength { + my ($l, $m); + $m = -1; + for my $i (@_) { + $l = length $i; + $m = $l if ($l > $m); + } + return $m; +} + +sub construct_balloon { + my $max = &maxlength(@message); + my $max2 = $max + 2; ## border space fudge. + my $format = "%s %-${max}s %s\n"; + my @border; ## up-left, up-right, down-left, down-right, left, right + if ($0 =~ /think/i) { + $thoughts = 'o'; + @border = qw[ ( ) ( ) ( ) ]; + } elsif (@message < 2) { + $thoughts = '\\'; + @border = qw[ < > ]; + } else { + $thoughts = '\\'; + if ($V and $V gt v5.6.0) { # Thanks, perldelta. + @border = qw[ / \\ \\ / | | ]; + } else { + @border = qw[ / \ \ / | | ]; + } + } + push(@balloon_lines, + " " . ("_" x $max2) . " \n" , + sprintf($format, $border[0], $message[0], $border[1]), + (@message < 2 ? "" : + map { sprintf($format, $border[4], $_, $border[5]) } + @message[1 .. $#message - 1]), + (@message < 2 ? "" : + sprintf($format, $border[2], $message[$#message], $border[3])), + " " . ("-" x $max2) . " \n" + ); +} + +sub construct_face { + if ($borg) { $eyes = "=="; } + if ($dead) { $eyes = "xx"; $tongue = "U "; } + if ($greedy) { $eyes = "\$\$"; } + if ($paranoid) { $eyes = "@@"; } + if ($stoned) { $eyes = "**"; $tongue = "U "; } + if ($tired) { $eyes = "--"; } + if ($wired) { $eyes = "OO"; } + if ($young) { $eyes = ".."; } +} + +sub get_cow { +## +## Get a cow from the specified cowfile; otherwise use the default cow +## which was defined above in $the_cow. +## + my $f = $opts{'f'}; + my $full = ""; + if ($opts{'f'} =~ m,/,) { + $full = $opts{'f'}; + } else { + for my $d (split(/:/, $cowpath)) { + if (-f "$d/$f") { + $full = "$d/$f"; + last; + } elsif (-f "$d/$f.cow") { + $full = "$d/$f.cow"; + last; + } + } + if ($full eq "") { + die "$progname: Could not find $f cowfile!\n"; + } + } + do $full; + die "$progname: $@\n" if $@; +} + +sub display_usage { + die < + ----------- + \ ^__^ + \ (oo)\_______ + (__)\ )\/\ + ||----w | + || || diff --git a/testdata/dragon.cow b/testdata/dragon.cow new file mode 100644 index 0000000..e773e7c --- /dev/null +++ b/testdata/dragon.cow @@ -0,0 +1,23 @@ + _____________________________________ + / this is a long block of text. \ + | | + | | + | It goes over many lines! It'll even | + \ word-wrap for us. / + ------------------------------------- + \ ^ /^ + \ / \ // \ + \ |\___/| / \// .\ + \ /O O \__ / // | \ \ *----* + / / \/_/ // | \ \ \ | + @___@` \/_ // | \ \ \/\ \ + 0/0/| \/_ // | \ \ \ \ + 0/0/0/0/| \/// | \ \ | | + 0/0/0/0/0/_|_ / ( // | \ _\ | / + 0/0/0/0/0/0/`/,_ _ _/ ) ; -. | _ _\.-~ / / + ,-} _ *-.|.-~-. .~ ~ + \ \__/ `/\ / ~-. _ .-~ / + \____(oo) *. } { / + ( (--) .----~-.\ \-` .~ + //__\\ \__ Ack! ///.----..< \ _ -~ + // \\ ///-._ _ _ _ _ _ _{^ - - - - ~ diff --git a/testdata/extra_spaces.cow b/testdata/extra_spaces.cow new file mode 100644 index 0000000..78b8dc4 --- /dev/null +++ b/testdata/extra_spaces.cow @@ -0,0 +1,8 @@ + ___________ +< some text > + ----------- + \ ^__^ + \ (oo)\_______ + (__)\ )\/\ + ||----w | + || || diff --git a/testdata/fuzz/FuzzCow/282befd696e0675c b/testdata/fuzz/FuzzCow/282befd696e0675c new file mode 100644 index 0000000..10f906d --- /dev/null +++ b/testdata/fuzz/FuzzCow/282befd696e0675c @@ -0,0 +1,2 @@ +go test fuzz v1 +string("0\t0") diff --git a/testdata/fuzz/FuzzCow/4eade7c2e02fd614 b/testdata/fuzz/FuzzCow/4eade7c2e02fd614 new file mode 100644 index 0000000..1f6dabf --- /dev/null +++ b/testdata/fuzz/FuzzCow/4eade7c2e02fd614 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("p 000") diff --git a/testdata/fuzz/FuzzCow/5838cdfae7b16cde b/testdata/fuzz/FuzzCow/5838cdfae7b16cde new file mode 100644 index 0000000..64c3aba --- /dev/null +++ b/testdata/fuzz/FuzzCow/5838cdfae7b16cde @@ -0,0 +1,2 @@ +go test fuzz v1 +string("") diff --git a/testdata/fuzz/FuzzCow/5c09e14b55b97e29 b/testdata/fuzz/FuzzCow/5c09e14b55b97e29 new file mode 100644 index 0000000..ceb4127 --- /dev/null +++ b/testdata/fuzz/FuzzCow/5c09e14b55b97e29 @@ -0,0 +1,2 @@ +go test fuzz v1 +string(" 0") diff --git a/testdata/fuzz/FuzzCow/771e938e4458e983 b/testdata/fuzz/FuzzCow/771e938e4458e983 new file mode 100644 index 0000000..ee3f339 --- /dev/null +++ b/testdata/fuzz/FuzzCow/771e938e4458e983 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("0") diff --git a/testdata/fuzz/FuzzCow/81eda71cf903cd49 b/testdata/fuzz/FuzzCow/81eda71cf903cd49 new file mode 100644 index 0000000..3e569f3 --- /dev/null +++ b/testdata/fuzz/FuzzCow/81eda71cf903cd49 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("0000000000000000000000000000000000000000") diff --git a/testdata/fuzz/FuzzCow/8643a79bab153d1b b/testdata/fuzz/FuzzCow/8643a79bab153d1b new file mode 100644 index 0000000..037b472 --- /dev/null +++ b/testdata/fuzz/FuzzCow/8643a79bab153d1b @@ -0,0 +1,2 @@ +go test fuzz v1 +string(" ") diff --git a/testdata/fuzz/FuzzCow/89831cc049267b2c b/testdata/fuzz/FuzzCow/89831cc049267b2c new file mode 100644 index 0000000..5647d9d --- /dev/null +++ b/testdata/fuzz/FuzzCow/89831cc049267b2c @@ -0,0 +1,2 @@ +go test fuzz v1 +string("\n") diff --git a/testdata/fuzz/FuzzCow/914c8f2daf274e5b b/testdata/fuzz/FuzzCow/914c8f2daf274e5b new file mode 100644 index 0000000..9abc882 --- /dev/null +++ b/testdata/fuzz/FuzzCow/914c8f2daf274e5b @@ -0,0 +1,2 @@ +go test fuzz v1 +string("\v") diff --git a/testdata/fuzz/FuzzCow/aa5c70ea28d6d054 b/testdata/fuzz/FuzzCow/aa5c70ea28d6d054 new file mode 100644 index 0000000..944257d --- /dev/null +++ b/testdata/fuzz/FuzzCow/aa5c70ea28d6d054 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("0\v0") diff --git a/testdata/fuzz/FuzzCow/dccef4f42470437c b/testdata/fuzz/FuzzCow/dccef4f42470437c new file mode 100644 index 0000000..8c46fb6 --- /dev/null +++ b/testdata/fuzz/FuzzCow/dccef4f42470437c @@ -0,0 +1,2 @@ +go test fuzz v1 +string("0\f0") diff --git a/testdata/fuzz/FuzzCow/ff7ff6d5d10e1702 b/testdata/fuzz/FuzzCow/ff7ff6d5d10e1702 new file mode 100644 index 0000000..176e3f4 --- /dev/null +++ b/testdata/fuzz/FuzzCow/ff7ff6d5d10e1702 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("0\n0") diff --git a/testdata/leading_space.cow b/testdata/leading_space.cow new file mode 100644 index 0000000..9233f20 --- /dev/null +++ b/testdata/leading_space.cow @@ -0,0 +1,8 @@ + _______ +< text > + ------- + \ ^__^ + \ (oo)\_______ + (__)\ )\/\ + ||----w | + || || diff --git a/testdata/longer_multiline.cow b/testdata/longer_multiline.cow new file mode 100644 index 0000000..f2b3dad --- /dev/null +++ b/testdata/longer_multiline.cow @@ -0,0 +1,12 @@ + _____________________________________ +/ this is a long block of text. \ +| | +| | +| It goes over many lines! It'll even | +\ word-wrap for us. / + ------------------------------------- + \ ^__^ + \ (oo)\_______ + (__)\ )\/\ + ||----w | + || || diff --git a/testdata/multiline.cow b/testdata/multiline.cow new file mode 100644 index 0000000..baaff9c --- /dev/null +++ b/testdata/multiline.cow @@ -0,0 +1,9 @@ + ______ +/ test \ +\ text / + ------ + \ ^__^ + \ (oo)\_______ + (__)\ )\/\ + ||----w | + || || diff --git a/testdata/too_long.cow b/testdata/too_long.cow new file mode 100644 index 0000000..1937917 --- /dev/null +++ b/testdata/too_long.cow @@ -0,0 +1,9 @@ + _________________________________________ +/ 000000000000000000000000000000000000000 \ +\ 00000000000 11 / + ----------------------------------------- + \ ^__^ + \ (oo)\_______ + (__)\ )\/\ + ||----w | + || ||