Repository: Code-Hex/Neo-cowsay Branch: master Commit: f68c20f068c2 Files: 123 Total size: 112.2 KB Directory structure: gitextract_va4xlec5/ ├── .github/ │ ├── CODEOWNERS │ ├── FUNDING.yml │ ├── dependabot.yml │ └── workflows/ │ ├── lgtm.yml │ ├── main.yml │ └── release.yml ├── .gitignore ├── .goreleaser.yml ├── LICENSE ├── Makefile ├── README.md ├── balloon.go ├── bin/ │ └── .gitkeep ├── cmd/ │ ├── cmd.go │ ├── cowsay/ │ │ └── main.go │ ├── cowthink/ │ │ └── main.go │ ├── go.mod │ ├── go.sum │ ├── internal/ │ │ ├── cli/ │ │ │ ├── cli.go │ │ │ └── cli_test.go │ │ ├── screen/ │ │ │ ├── buffer.go │ │ │ └── screen.go │ │ └── super/ │ │ └── supercow.go │ └── testdata/ │ ├── cowsay/ │ │ ├── W_option.txt │ │ ├── b_option.txt │ │ ├── d_option.txt │ │ ├── eyes_option.txt │ │ ├── f_tux_option.txt │ │ ├── g_option.txt │ │ ├── n_option.txt │ │ ├── p_option.txt │ │ ├── s_option.txt │ │ ├── t_option.txt │ │ ├── tongue_option.txt │ │ ├── wired_option.txt │ │ └── y_option.txt │ └── cowthink/ │ ├── W_option.txt │ ├── b_option.txt │ ├── d_option.txt │ ├── eyes_option.txt │ ├── f_tux_option.txt │ ├── g_option.txt │ ├── n_option.txt │ ├── p_option.txt │ ├── s_option.txt │ ├── t_option.txt │ ├── tongue_option.txt │ ├── wired_option.txt │ └── y_option.txt ├── cow.go ├── cow_test.go ├── cows/ │ ├── beavis.zen.cow │ ├── bong.cow │ ├── bud-frogs.cow │ ├── bunny.cow │ ├── cheese.cow │ ├── cower.cow │ ├── daemon.cow │ ├── default.cow │ ├── deno.cow │ ├── docker.cow │ ├── dragon-and-cow.cow │ ├── dragon.cow │ ├── elephant-in-snake.cow │ ├── elephant.cow │ ├── eyes.cow │ ├── flaming-sheep.cow │ ├── ghostbusters.cow │ ├── gopher.cow │ ├── head-in.cow │ ├── hellokitty.cow │ ├── kiss.cow │ ├── kitty.cow │ ├── koala.cow │ ├── kosh.cow │ ├── luke-koala.cow │ ├── meow.cow │ ├── milk.cow │ ├── moofasa.cow │ ├── moose.cow │ ├── mutilated.cow │ ├── ren.cow │ ├── sage.cow │ ├── satanic.cow │ ├── sheep.cow │ ├── skeleton.cow │ ├── small.cow │ ├── sodomized.cow │ ├── squirrel.cow │ ├── stegosaurus.cow │ ├── stimpy.cow │ ├── supermilker.cow │ ├── surgery.cow │ ├── telebears.cow │ ├── turkey.cow │ ├── turtle.cow │ ├── tux.cow │ ├── vader-koala.cow │ ├── vader.cow │ └── www.cow ├── cowsay.go ├── cowsay_test.go ├── decoration/ │ ├── aurora.go │ ├── bold.go │ ├── decoration.go │ └── rainbow.go ├── doc/ │ ├── cowsay.1 │ └── cowsay.1.txt.tpl ├── embed.go ├── examples/ │ ├── basic/ │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ └── echo-server/ │ ├── README.md │ ├── go.mod │ ├── go.sum │ └── main.go ├── go.mod ├── go.sum ├── split.go ├── split_windows.go └── testdata/ ├── default.cow ├── nest.cow └── testdir/ └── test.cow ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/CODEOWNERS ================================================ * @Code-Hex ================================================ FILE: .github/FUNDING.yml ================================================ github: Code-Hex ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" - package-ecosystem: "gomod" directory: "/" schedule: interval: "daily" ================================================ FILE: .github/workflows/lgtm.yml ================================================ on: pull_request_review: types: - submitted jobs: lgtm: name: LGTM runs-on: ubuntu-latest if: github.event.review.state == 'approved' steps: - name: Cowsay LGTM uses: Code-Hex/neo-cowsay-action@v1.0.2 with: message: 'LGTM' cow: 'random' cowsay_on_comment: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/main.yml ================================================ on: push: branches: - "master" pull_request: jobs: test: strategy: matrix: os: [ubuntu-latest, windows-latest] name: test runs-on: ${{ matrix.os }} steps: - name: Setup Go 1.17.2 uses: actions/setup-go@v2 with: go-version: 1.17.2 - name: Check out code into the Go module directory uses: actions/checkout@v3 - name: Vet run: make vet - name: Test run: make test - name: Lint if: matrix.os == 'ubuntu-latest' run: | go get golang.org/x/lint/golint export PATH="$PATH:$(go env GOPATH)/bin" make lint env: GO111MODULE: off - name: Declare some variables if: matrix.os == 'ubuntu-latest' id: vars run: | echo "::set-output name=coverage_txt::${RUNNER_TEMP}/coverage.txt" - name: Test Coverage (pkg) if: matrix.os == 'ubuntu-latest' run: go test ./... -coverprofile=${{ steps.vars.outputs.coverage_txt }} - name: Upload coverage if: matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v3 with: files: ${{ steps.vars.outputs.coverage_txt }} ================================================ FILE: .github/workflows/release.yml ================================================ on: push: tags: - "v*.*.*" jobs: release: name: Release runs-on: ubuntu-latest steps: - name: Setup Go 1.17.2 uses: actions/setup-go@v2 with: go-version: 1.17.2 - name: Check out code into the Go module directory uses: actions/checkout@v3 - name: Run GoReleaser if: contains(github.ref, 'tags/v') uses: goreleaser/goreleaser-action@v2 with: version: latest args: release --rm-dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} winget: runs-on: ubuntu-latest needs: release steps: - uses: vedantmgoyal2009/winget-releaser@v2 with: identifier: codehex.Neo-cowsay installers-regex: '_Windows_\w+\.zip$' token: ${{ secrets.WINGET_TOKEN }} ================================================ FILE: .gitignore ================================================ # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof github-token build releases bin/* !bin/.gitkeep ================================================ FILE: .goreleaser.yml ================================================ builds: - id: cowsay dir: cmd main: ./cowsay/main.go binary: cowsay env: - CGO_ENABLED=0 ldflags: -s -w -X main.version={{.Version}} goos: - linux - darwin - windows goarch: - 386 - amd64 - arm - arm64 goarm: - 6 - 7 ignore: - goos: darwin goarch: 386 - goos: linux goarch: arm goarm: 7 - goos: windows goarch: arm goarm: 7 - id: cowthink dir: cmd main: ./cowthink/main.go binary: cowthink env: - CGO_ENABLED=0 ldflags: -s -w -X main.version={{.Version}} goos: - linux - darwin - windows goarch: - 386 - amd64 - arm - arm64 goarm: - 6 - 7 ignore: - goos: darwin goarch: 386 - goos: linux goarch: arm goarm: 7 - goos: windows goarch: arm goarm: 7 archives: - builds: - cowsay - cowthink name_template: 'cowsay_{{ .Version }}_{{ .Os }}_{{ .Arch }}' replacements: darwin: macOS linux: Linux windows: Windows 386: i386 amd64: x86_64 format_overrides: - goos: windows format: zip files: - LICENSE - doc/cowsay.1 brews: - name: neo-cowsay tap: owner: Code-Hex name: homebrew-tap token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" homepage: https://github.com/Code-Hex/Neo-cowsay description: "Fast, funny, everyone wanted? new cowsay!!" folder: Formula install: | bin.install "cowsay" bin.install "cowthink" man1.install Dir["doc/cowsay.1"] nfpms: - license: Artistic License 2.0 maintainer: Kei Kamikawa homepage: https://github.com/Code-Hex/Neo-cowsay bindir: /usr/local/bin description: "Fast, funny, everyone wanted? new cowsay!!" formats: - apk - deb - rpm contents: - src: "doc/cowsay.1" dst: "/usr/share/man/man1/cowsay.1" checksum: name_template: 'cowsay_checksums.txt' changelog: sort: asc filters: exclude: - '^docs:' - '^test:' - Merge pull request - Merge branch ================================================ FILE: LICENSE ================================================ The Artistic License 2.0 Copyright (c) 2000-2006, The Perl Foundation. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble This license establishes the terms under which a given free software Package may be copied, modified, distributed, and/or redistributed. The intent is that the Copyright Holder maintains some artistic control over the development of that Package while still keeping the Package available as open source and free software. You are always permitted to make arrangements wholly outside of this license directly with the Copyright Holder of a given Package. If the terms of this license do not permit the full use that you propose to make of the Package, you should contact the Copyright Holder and seek a different licensing arrangement. Definitions "Copyright Holder" means the individual(s) or organization(s) named in the copyright notice for the entire Package. "Contributor" means any party that has contributed code or other material to the Package, in accordance with the Copyright Holder's procedures. "You" and "your" means any person who would like to copy, distribute, or modify the Package. "Package" means the collection of files distributed by the Copyright Holder, and derivatives of that collection and/or of those files. A given Package may consist of either the Standard Version, or a Modified Version. "Distribute" means providing a copy of the Package or making it accessible to anyone else, or in the case of a company or organization, to others outside of your company or organization. "Distributor Fee" means any fee that you charge for Distributing this Package or providing support for this Package to another party. It does not mean licensing fees. "Standard Version" refers to the Package if it has not been modified, or has been modified only in ways explicitly requested by the Copyright Holder. "Modified Version" means the Package, if it has been changed, and such changes were not explicitly requested by the Copyright Holder. "Original License" means this Artistic License as Distributed with the Standard Version of the Package, in its current version or as it may be modified by The Perl Foundation in the future. "Source" form means the source code, documentation source, and configuration files for the Package. "Compiled" form means the compiled bytecode, object code, binary, or any other form resulting from mechanical transformation or translation of the Source form. Permission for Use and Modification Without Distribution (1) You are permitted to use the Standard Version and create and use Modified Versions for any purpose without restriction, provided that you do not Distribute the Modified Version. Permissions for Redistribution of the Standard Version (2) You may Distribute verbatim copies of the Source form of the Standard Version of this Package in any medium without restriction, either gratis or for a Distributor Fee, provided that you duplicate all of the original copyright notices and associated disclaimers. At your discretion, such verbatim copies may or may not include a Compiled form of the Package. (3) You may apply any bug fixes, portability changes, and other modifications made available from the Copyright Holder. The resulting Package will still be considered the Standard Version, and as such will be subject to the Original License. Distribution of Modified Versions of the Package as Source (4) You may Distribute your Modified Version as Source (either gratis or for a Distributor Fee, and with or without a Compiled form of the Modified Version) provided that you clearly document how it differs from the Standard Version, including, but not limited to, documenting any non-standard features, executables, or modules, and provided that you do at least ONE of the following: (a) make the Modified Version available to the Copyright Holder of the Standard Version, under the Original License, so that the Copyright Holder may include your modifications in the Standard Version. (b) ensure that installation of your Modified Version does not prevent the user installing or running the Standard Version. In addition, the Modified Version must bear a name that is different from the name of the Standard Version. (c) allow anyone who receives a copy of the Modified Version to make the Source form of the Modified Version available to others under (i) the Original License or (ii) a license that permits the licensee to freely copy, modify and redistribute the Modified Version using the same licensing terms that apply to the copy that the licensee received, and requires that the Source form of the Modified Version, and of any works derived from it, be made freely available in that license fees are prohibited but Distributor Fees are allowed. Distribution of Compiled Forms of the Standard Version or Modified Versions without the Source (5) You may Distribute Compiled forms of the Standard Version without the Source, provided that you include complete instructions on how to get the Source of the Standard Version. Such instructions must be valid at the time of your distribution. If these instructions, at any time while you are carrying out such distribution, become invalid, you must provide new instructions on demand or cease further distribution. If you provide valid instructions or cease distribution within thirty days after you become aware that the instructions are invalid, then you do not forfeit any of your rights under this license. (6) You may Distribute a Modified Version in Compiled form without the Source, provided that you comply with Section 4 with respect to the Source of the Modified Version. Aggregating or Linking the Package (7) You may aggregate the Package (either the Standard Version or Modified Version) with other packages and Distribute the resulting aggregation provided that you do not charge a licensing fee for the Package. Distributor Fees are permitted, and licensing fees for other components in the aggregation are permitted. The terms of this license apply to the use and Distribution of the Standard or Modified Versions as included in the aggregation. (8) You are permitted to link Modified and Standard Versions with other works, to embed the Package in a larger work of your own, or to build stand-alone binary or bytecode versions of applications that include the Package, and Distribute the result without restriction, provided the result does not expose a direct interface to the Package. Items That are Not Considered Part of a Modified Version (9) Works (including, but not limited to, modules and scripts) that merely extend or make use of the Package, do not, by themselves, cause the Package to be a Modified Version. In addition, such works are not considered parts of the Package itself, and are not subject to the terms of this license. General Provisions (10) Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license. (11) If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license. (12) This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder. (13) This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed. (14) Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: Makefile ================================================ .PHONY: build build: build/cowsay build/cowthink .PHONY: build/cowsay build/cowsay: CGO_ENABLED=0 cd cmd && go build -o ../bin/cowsay -ldflags "-w -s" ./cowsay .PHONY: build/cowthink build/cowthink: CGO_ENABLED=0 cd cmd && go build -o ../bin/cowthink -ldflags "-w -s" ./cowthink .PHONY: lint lint: golint ./... cd cmd && golint ./... .PHONY: vet vet: go vet ./... cd cmd && go vet ./... .PHONY: test test: test/pkg test/cli .PHONY: test/pkg test/pkg: go test ./... .PHONY: test/cli test/cli: cd cmd && go test ./... .PHONY: man man: asciidoctor --doctype manpage --backend manpage doc/cowsay.1.txt.tpl -o doc/cowsay.1 .PHONY: man/preview man/preview: cat doc/cowsay.1 | groff -man -Tascii | less ================================================ FILE: README.md ================================================ # Neo Cowsay Neo Cowsay is written in Go. This cowsay is extended the original cowsay. added fun more options, and you can be used as a library. for GitHub Actions users: [Code-Hex/neo-cowsay-action](https://github.com/marketplace/actions/neo-cowsay) [![Go Reference](https://pkg.go.dev/badge/github.com/Code-Hex/Neo-cowsay/v2.svg)](https://pkg.go.dev/github.com/Code-Hex/Neo-cowsay/v2) [![.github/workflows/main.yml](https://github.com/Code-Hex/Neo-cowsay/actions/workflows/main.yml/badge.svg)](https://github.com/Code-Hex/Neo-cowsay/actions/workflows/main.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/Code-Hex/Neo-cowsay)](https://goreportcard.com/report/github.com/Code-Hex/Neo-cowsay) [![codecov](https://codecov.io/gh/Code-Hex/Neo-cowsay/branch/master/graph/badge.svg?token=WwjmyHrOPv)](https://codecov.io/gh/Code-Hex/Neo-cowsay) ``` ______________ < I'm Neo cows > -------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || ``` ## About cowsay According to the [original](https://web.archive.org/web/20071026043648/http://www.nog.net/~tony/warez/cowsay.shtml) original manual. ``` cowsay is a configurable talking cow, written in Perl. It operates much as the figlet program does, and it written in the same spirit of silliness. ``` This is also supported `COWPATH` env. Please read more details in [#33](https://github.com/Code-Hex/Neo-cowsay/pull/33) if you want to use this. ## What makes it different from the original? - fast - utf8 is supported - new some cowfiles is added - cowfiles in binary - random pickup cowfile option - provides command-line fuzzy finder to search any cows with `-f -` [#39](https://github.com/Code-Hex/Neo-cowsay/pull/39) - coloring filter options - super mode
Movies for new options 🐮 ### Random [![asciicast](https://asciinema.org/a/228210.svg)](https://asciinema.org/a/228210) ### Rainbow and Aurora, Bold [![asciicast](https://asciinema.org/a/228213.svg)](https://asciinema.org/a/228213) ## And, Super Cows mode https://user-images.githubusercontent.com/6500104/140379043-53e44994-b1b0-442e-bda7-4f7ab3aedf01.mov
## Usage ### As command ``` cow{say,think} version 2.0.0, (c) 2021 codehex Usage: cowsay [-bdgpstwy] [-h] [-e eyes] [-f cowfile] [--random] [-l] [-n] [-T tongue] [-W wrapcolumn] [--bold] [--rainbow] [--aurora] [--super] [message] Original Author: (c) 1999 Tony Monroe Repository: https://github.com/Code-Hex/Neo-cowsay ``` Normal ``` $ cowsay Hello _______ < Hello > ------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || ``` Borg mode ``` $ cowsay -b Hello _______ < Hello > ------- \ ^__^ \ (==)\_______ (__)\ )\/\ ||----w | || || ``` ### As library ```go package main import ( "fmt" cowsay "github.com/Code-Hex/Neo-cowsay/v2" ) func main() { say, err := cowsay.Say( "Hello", cowsay.Type("default"), cowsay.BallonWidth(40), ) if err != nil { panic(err) } fmt.Println(say) } ``` [Examples](https://github.com/Code-Hex/Neo-cowsay/blob/master/examples) or [GoDoc](https://pkg.go.dev/github.com/Code-Hex/Neo-cowsay/v2) ## Install ### Windows users via Scoop $ scoop install neo-cowsay ### Windows users via Winget $ winget install neo-cowsay ### Mac and Linux users via Homebrew $ brew update $ brew install Code-Hex/tap/neo-cowsay ### Binary You can download from [here](https://github.com/Code-Hex/Neo-cowsay/releases) ### library $ go get github.com/Code-Hex/Neo-cowsay/v2 ### Go #### cowsay $ go install github.com/Code-Hex/Neo-cowsay/cmd/v2/cowsay@latest #### cowthink $ go install github.com/Code-Hex/Neo-cowsay/cmd/v2/cowthink@latest ## License
cowsay license ``` ============== cowsay License ============== cowsay is distributed under the same licensing terms as Perl: the Artistic License or the GNU General Public License. If you don't want to track down these licenses and read them for yourself, use the parts that I'd prefer: (0) I wrote it and you didn't. (1) Give credit where credit is due if you borrow the code for some other purpose. (2) If you have any bugfixes or suggestions, please notify me so that I may incorporate them. (3) If you try to make money off of cowsay, you suck. =============== cowsay Legalese =============== (0) Copyright (c) 1999 Tony Monroe. All rights reserved. All lefts may or may not be reversed at my discretion. (1) This software package can be freely redistributed or modified under the terms described above in the "cowsay License" section of this file. (2) cowsay is provided "as is," with no warranties whatsoever, expressed or implied. If you want some implied warranty about merchantability and/or fitness for a particular purpose, you will not find it here, because there is no such thing here. (3) I hate legalese. ```
(The Artistic License or The GNU General Public License) ## Author Neo Cowsay: [codehex](https://twitter.com/CodeHex) Original: (c) 1999 Tony Monroe ================================================ FILE: balloon.go ================================================ package cowsay import ( "fmt" "strings" wordwrap "github.com/Code-Hex/go-wordwrap" runewidth "github.com/mattn/go-runewidth" ) type border struct { first [2]rune middle [2]rune last [2]rune only [2]rune } func (cow *Cow) borderType() border { if cow.thinking { return border{ first: [2]rune{'(', ')'}, middle: [2]rune{'(', ')'}, last: [2]rune{'(', ')'}, only: [2]rune{'(', ')'}, } } return border{ first: [2]rune{'/', '\\'}, middle: [2]rune{'|', '|'}, last: [2]rune{'\\', '/'}, only: [2]rune{'<', '>'}, } } type line struct { text string runeWidth int } type lines []*line func (cow *Cow) maxLineWidth(lines []*line) int { maxWidth := 0 for _, line := range lines { if line.runeWidth > maxWidth { maxWidth = line.runeWidth } if !cow.disableWordWrap && maxWidth > cow.ballonWidth { return cow.ballonWidth } } return maxWidth } func (cow *Cow) getLines(phrase string) []*line { text := cow.canonicalizePhrase(phrase) lineTexts := strings.Split(text, "\n") lines := make([]*line, 0, len(lineTexts)) for _, lineText := range lineTexts { lines = append(lines, &line{ text: lineText, runeWidth: runewidth.StringWidth(lineText), }) } return lines } func (cow *Cow) canonicalizePhrase(phrase string) string { // Replace tab to 8 spaces phrase = strings.Replace(phrase, "\t", " ", -1) if cow.disableWordWrap { return phrase } width := cow.ballonWidth return wordwrap.WrapString(phrase, uint(width)) } // Balloon to get the balloon and the string entered in the balloon. func (cow *Cow) Balloon(phrase string) string { defer cow.buf.Reset() lines := cow.getLines(phrase) maxWidth := cow.maxLineWidth(lines) cow.writeBallon(lines, maxWidth) return cow.buf.String() } func (cow *Cow) writeBallon(lines []*line, maxWidth int) { top := make([]byte, 0, maxWidth+2) bottom := make([]byte, 0, maxWidth+2) top = append(top, ' ') bottom = append(bottom, ' ') for i := 0; i < maxWidth+2; i++ { top = append(top, '_') bottom = append(bottom, '-') } borderType := cow.borderType() cow.buf.Write(top) cow.buf.Write([]byte{' ', '\n'}) defer func() { cow.buf.Write(bottom) cow.buf.Write([]byte{' ', '\n'}) }() l := len(lines) if l == 1 { border := borderType.only cow.buf.WriteRune(border[0]) cow.buf.WriteRune(' ') cow.buf.WriteString(lines[0].text) cow.buf.WriteRune(' ') cow.buf.WriteRune(border[1]) cow.buf.WriteRune('\n') return } var border [2]rune for i := 0; i < l; i++ { switch i { case 0: border = borderType.first case l - 1: border = borderType.last default: border = borderType.middle } cow.buf.WriteRune(border[0]) cow.buf.WriteRune(' ') cow.padding(lines[i], maxWidth) cow.buf.WriteRune(' ') cow.buf.WriteRune(border[1]) cow.buf.WriteRune('\n') } } func (cow *Cow) flush(text, top, bottom fmt.Stringer) string { return fmt.Sprintf( "%s\n%s%s\n", top.String(), text.String(), bottom.String(), ) } func (cow *Cow) padding(line *line, maxWidth int) { if maxWidth <= line.runeWidth { cow.buf.WriteString(line.text) return } cow.buf.WriteString(line.text) l := maxWidth - line.runeWidth for i := 0; i < l; i++ { cow.buf.WriteRune(' ') } } ================================================ FILE: bin/.gitkeep ================================================ ================================================ FILE: cmd/cmd.go ================================================ package cmd ================================================ FILE: cmd/cowsay/main.go ================================================ package main import ( "os" "github.com/Code-Hex/Neo-cowsay/cmd/v2/internal/cli" ) var version string func main() { os.Exit((&cli.CLI{ Version: version, Thinking: false, }).Run(os.Args[1:])) } ================================================ FILE: cmd/cowthink/main.go ================================================ package main import ( "os" "github.com/Code-Hex/Neo-cowsay/cmd/v2/internal/cli" ) var version string func main() { os.Exit((&cli.CLI{ Version: version, Thinking: true, }).Run(os.Args[1:])) } ================================================ FILE: cmd/go.mod ================================================ module github.com/Code-Hex/Neo-cowsay/cmd/v2 go 1.16 require ( github.com/Code-Hex/Neo-cowsay/v2 v2.0.3 github.com/Code-Hex/go-wordwrap v1.0.0 github.com/google/go-cmp v0.5.6 github.com/jessevdk/go-flags v1.5.0 github.com/ktr0731/go-fuzzyfinder v0.5.1 github.com/mattn/go-colorable v0.1.11 github.com/mattn/go-runewidth v0.0.13 github.com/rivo/uniseg v0.2.0 golang.org/x/crypto v0.1.0 ) replace github.com/Code-Hex/Neo-cowsay/v2 => ../ ================================================ FILE: cmd/go.sum ================================================ github.com/Code-Hex/go-wordwrap v1.0.0 h1:yl5fLyZEz3+hPGbpTRlTQ8mQJ1HXWcTq1FCNR1ch6zM= github.com/Code-Hex/go-wordwrap v1.0.0/go.mod h1:/SsbgkY2Q0aPQRyvXcyQwWYTQOIwSORKe6MPjRVGIWU= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell/v2 v2.4.0 h1:W6dxJEmaxYvhICFoTY3WrLLEXsQ11SaFnKGVEXW57KM= github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/ktr0731/go-fuzzyfinder v0.5.1 h1:rDcWxmGi6ux4NURekn9iAXpbYBp8Kj4cznrz162S9og= github.com/ktr0731/go-fuzzyfinder v0.5.1/go.mod h1:gud27uRG2vF+oD58eGhYZj7Pc9enRX0qecwp09w/jno= github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/nsf/termbox-go v0.0.0-20201124104050-ed494de23a00 h1:Rl8NelBe+n7SuLbJyw13ho7CGWUt2BjGGKIoreCWQ/c= github.com/nsf/termbox-go v0.0.0-20201124104050-ed494de23a00/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= ================================================ FILE: cmd/internal/cli/cli.go ================================================ package cli import ( "bufio" cryptorand "crypto/rand" "errors" "fmt" "io" "math" "math/big" "math/rand" "os" "strconv" "strings" "time" "github.com/Code-Hex/Neo-cowsay/cmd/v2/internal/super" cowsay "github.com/Code-Hex/Neo-cowsay/v2" "github.com/Code-Hex/Neo-cowsay/v2/decoration" "github.com/Code-Hex/go-wordwrap" "github.com/jessevdk/go-flags" "github.com/ktr0731/go-fuzzyfinder" "github.com/mattn/go-colorable" ) func init() { // safely set the seed globally so we generate random ids. Tries to use a // crypto seed before falling back to time. var seed int64 cryptoseed, err := cryptorand.Int(cryptorand.Reader, big.NewInt(math.MaxInt64)) if err != nil { // This should not happen, but worst-case fallback to time-based seed. seed = time.Now().UnixNano() } else { seed = cryptoseed.Int64() } rand.Seed(seed) } // options struct for parse command line arguments type options struct { Help bool `short:"h"` Eyes string `short:"e"` Tongue string `short:"T"` Width int `short:"W"` Borg bool `short:"b"` Dead bool `short:"d"` Greedy bool `short:"g"` Paranoia bool `short:"p"` Stoned bool `short:"s"` Tired bool `short:"t"` Wired bool `short:"w"` Youthful bool `short:"y"` List bool `short:"l"` NewLine bool `short:"n"` File string `short:"f"` Bold bool `long:"bold"` Super bool `long:"super"` Random bool `long:"random"` Rainbow bool `long:"rainbow"` Aurora bool `long:"aurora"` } // CLI prepare for running command-line. type CLI struct { Version string Thinking bool stderr io.Writer stdout io.Writer stdin io.Reader } func (c *CLI) program() string { if c.Thinking { return "cowthink" } return "cowsay" } // Run runs command-line. func (c *CLI) Run(argv []string) int { if c.stderr == nil { c.stderr = os.Stderr } if c.stdout == nil { c.stdout = colorable.NewColorableStdout() } if c.stdin == nil { c.stdin = os.Stdin } if err := c.mow(argv); err != nil { fmt.Fprintf(c.stderr, "%s: %s\n", c.program(), err.Error()) return 1 } return 0 } // mow will parsing for cowsay command line arguments and invoke cowsay. func (c *CLI) mow(argv []string) error { var opts options args, err := c.parseOptions(&opts, argv) if err != nil { return err } if opts.List { cowPaths, err := cowsay.Cows() if err != nil { return err } for _, cowPath := range cowPaths { if cowPath.LocationType == cowsay.InBinary { fmt.Fprintf(c.stdout, "Cow files in binary:\n") } else { fmt.Fprintf(c.stdout, "Cow files in %s:\n", cowPath.Name) } fmt.Fprintln(c.stdout, wordwrap.WrapString(strings.Join(cowPath.CowFiles, " "), 80)) fmt.Fprintln(c.stdout) } return nil } if err := c.mowmow(&opts, args); err != nil { return err } return nil } func (c *CLI) parseOptions(opts *options, argv []string) ([]string, error) { p := flags.NewParser(opts, flags.None) args, err := p.ParseArgs(argv) if err != nil { return nil, err } if opts.Help { c.stdout.Write(c.usage()) os.Exit(0) } return args, nil } func (c *CLI) usage() []byte { year := strconv.Itoa(time.Now().Year()) return []byte(c.program() + ` version ` + c.Version + `, (c) ` + year + ` codehex Usage: ` + c.program() + ` [-bdgpstwy] [-h] [-e eyes] [-f cowfile] [--random] [-l] [-n] [-T tongue] [-W wrapcolumn] [--bold] [--rainbow] [--aurora] [--super] [message] Original Author: (c) 1999 Tony Monroe `) } func (c *CLI) generateOptions(opts *options) []cowsay.Option { o := make([]cowsay.Option, 0, 8) if opts.File == "-" { cows := cowList() idx, _ := fuzzyfinder.Find(cows, func(i int) string { return cows[i] }) opts.File = cows[idx] } o = append(o, cowsay.Type(opts.File)) if c.Thinking { o = append(o, cowsay.Thinking(), cowsay.Thoughts('o'), ) } if opts.Random { o = append(o, cowsay.Random()) } if opts.Eyes != "" { o = append(o, cowsay.Eyes(opts.Eyes)) } if opts.Tongue != "" { o = append(o, cowsay.Tongue(opts.Tongue)) } if opts.Width > 0 { o = append(o, cowsay.BallonWidth(uint(opts.Width))) } if opts.NewLine { o = append(o, cowsay.DisableWordWrap()) } return selectFace(opts, o) } func cowList() []string { cows, err := cowsay.Cows() if err != nil { return cowsay.CowsInBinary() } list := make([]string, 0) for _, cow := range cows { list = append(list, cow.CowFiles...) } return list } func (c *CLI) phrase(opts *options, args []string) string { if len(args) > 0 { return strings.Join(args, " ") } lines := make([]string, 0, 40) scanner := bufio.NewScanner(c.stdin) for scanner.Scan() { lines = append(lines, scanner.Text()) } return strings.Join(lines, "\n") } func (c *CLI) mowmow(opts *options, args []string) error { phrase := c.phrase(opts, args) o := c.generateOptions(opts) if opts.Super { return super.RunSuperCow(phrase, opts.Bold, o...) } say, err := cowsay.Say(phrase, o...) if err != nil { var notfound *cowsay.NotFound if errors.As(err, ¬found) { return fmt.Errorf("could not find %s cowfile", notfound.Cowfile) } return err } options := make([]decoration.Option, 0) if opts.Bold { options = append(options, decoration.WithBold()) } if opts.Rainbow { options = append(options, decoration.WithRainbow()) } if opts.Aurora { options = append(options, decoration.WithAurora(rand.Intn(256))) } w := decoration.NewWriter(c.stdout, options...) fmt.Fprintln(w, say) return nil } func selectFace(opts *options, o []cowsay.Option) []cowsay.Option { switch { case opts.Borg: o = append(o, cowsay.Eyes("=="), cowsay.Tongue(" "), ) case opts.Dead: o = append(o, cowsay.Eyes("xx"), cowsay.Tongue("U "), ) case opts.Greedy: o = append(o, cowsay.Eyes("$$"), cowsay.Tongue(" "), ) case opts.Paranoia: o = append(o, cowsay.Eyes("@@"), cowsay.Tongue(" "), ) case opts.Stoned: o = append(o, cowsay.Eyes("**"), cowsay.Tongue("U "), ) case opts.Tired: o = append(o, cowsay.Eyes("--"), cowsay.Tongue(" "), ) case opts.Wired: o = append(o, cowsay.Eyes("OO"), cowsay.Tongue(" "), ) case opts.Youthful: o = append(o, cowsay.Eyes(".."), cowsay.Tongue(" "), ) } return o } ================================================ FILE: cmd/internal/cli/cli_test.go ================================================ package cli import ( "bytes" "fmt" "io/ioutil" "path/filepath" "strings" "testing" "github.com/google/go-cmp/cmp" ) func TestCLI_Run(t *testing.T) { clis := []struct { name string thinking bool }{ { name: "cowsay", thinking: false, }, { name: "cowthink", thinking: true, }, } for _, cli := range clis { cli := cli t.Run(cli.name, func(t *testing.T) { t.Parallel() tests := []struct { name string phrase string argv []string testfile string }{ { name: "ignore wordwrap option", phrase: "foo\nbar\nbaz", argv: []string{"-n"}, testfile: "n_option.txt", }, { name: "tired option", phrase: "tired", argv: []string{"-t"}, testfile: "t_option.txt", }, { name: "specifies width of the ballon is 3", phrase: "foobarbaz", argv: []string{"-W", "3"}, testfile: "W_option.txt", }, { name: "borg mode", phrase: "foobarbaz", argv: []string{"-b"}, testfile: "b_option.txt", }, { name: "dead mode", phrase: "0xdeadbeef", argv: []string{"-d"}, testfile: "d_option.txt", }, { name: "greedy mode", phrase: "give me money", argv: []string{"-g"}, testfile: "g_option.txt", }, { name: "paranoid mode", phrase: "everyone hates me", argv: []string{"-p"}, testfile: "p_option.txt", }, { name: "stoned mode", phrase: "I don't know", argv: []string{"-s"}, testfile: "s_option.txt", }, { name: "wired mode", phrase: "Wanna Netflix and chill?", argv: []string{"-w"}, testfile: "wired_option.txt", }, { name: "youthful mode", phrase: "I forgot my ID at home", argv: []string{"-y"}, testfile: "y_option.txt", }, { name: "eyes option", phrase: "I'm not angry", argv: []string{"-e", "^^"}, testfile: "eyes_option.txt", }, { name: "tongue option", phrase: "hungry", argv: []string{"-T", ":"}, testfile: "tongue_option.txt", }, { name: "-f tux", phrase: "what is macOS?", argv: []string{"-f", "tux"}, testfile: "f_tux_option.txt", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { var stdout bytes.Buffer c := &CLI{ Thinking: cli.thinking, stdout: &stdout, stdin: strings.NewReader(tt.phrase), } exit := c.Run(tt.argv) if exit != 0 { t.Fatalf("unexpected exit code: %d", exit) } testpath := filepath.Join("..", "..", "testdata", cli.name, tt.testfile) content, err := ioutil.ReadFile(testpath) if err != nil { t.Fatal(err) } got := strings.Replace(stdout.String(), "\r", "", -1) // for windows want := strings.Replace(string(content), "\r", "", -1) // for windows if want != got { t.Log(cmp.Diff(want, got)) t.Errorf("want\n%s\n-----got\n%s\n", want, got) } }) } t.Run("program name", func(t *testing.T) { c := &CLI{Thinking: cli.thinking} if cli.name != c.program() { t.Fatalf("want %q, but got %q", cli.name, c.program()) } }) t.Run("not found cowfile", func(t *testing.T) { var stderr bytes.Buffer c := &CLI{ Thinking: cli.thinking, stderr: &stderr, } exit := c.Run([]string{"-f", "unknown"}) if exit == 0 { t.Errorf("unexpected exit code: %d", exit) } want := fmt.Sprintf("%s: could not find unknown cowfile\n", cli.name) if want != stderr.String() { t.Errorf("want %q, but got %q", want, stderr.String()) } }) }) } } ================================================ FILE: cmd/internal/screen/buffer.go ================================================ package screen import ( "bytes" "fmt" "io" "strings" ) // buffer is the global screen buffer // Its not recommended write to buffer dirrectly, use package Print,Printf,Println functions instead. var buffer strings.Builder // Flush buffer and ensure that it will not overflow screen func Flush() string { defer buffer.Reset() return buffer.String() } // MoveWriter is implemented io.Writer and io.StringWriter. type MoveWriter struct { idx int x, y int w io.Writer buf bytes.Buffer } var _ interface { io.Writer io.StringWriter } = (*MoveWriter)(nil) // NewMoveWriter creates a new MoveWriter. func NewMoveWriter(w io.Writer, x, y int) *MoveWriter { x, y = getXY(x, y) return &MoveWriter{ w: w, x: x, y: y, } } // SetPosx sets pos x func (m *MoveWriter) SetPosx(x int) { x, _ = getXY(x, 0) m.x = x } // Reset resets func (m *MoveWriter) Reset() { m.idx = 0 m.buf.Reset() } // Write writes bytes. which is implemented io.Writer. func (m *MoveWriter) Write(bs []byte) (nn int, _ error) { br := bytes.NewReader(bs) for { b, err := br.ReadByte() if err != nil && err != io.EOF { return 0, err } if err == io.EOF { n, _ := fmt.Fprintf(m.w, "\x1b[%d;%dH%s\x1b[0K", m.y+m.idx, m.x, m.buf.String(), ) nn += n return } if b == '\n' { n, _ := fmt.Fprintf(m.w, "\x1b[%d;%dH%s\x1b[0K", m.y+m.idx, m.x, m.buf.String(), ) m.buf.Reset() m.idx++ nn += n } else { m.buf.WriteByte(b) } } } // WriteString writes string. which is implemented io.StringWriter. func (m *MoveWriter) WriteString(s string) (nn int, _ error) { for _, char := range s { if char == '\n' { n, _ := fmt.Fprintf(m.w, "\x1b[%d;%dH%s\x1b[0K", m.y+m.idx, m.x, m.buf.String(), ) m.buf.Reset() m.idx++ nn += n } else { m.buf.WriteRune(char) } } if m.buf.Len() > 0 { n, _ := fmt.Fprintf(m.w, "\x1b[%d;%dH%s\x1b[0K", m.y+m.idx, m.x, m.buf.String(), ) nn += n } return } // getXY gets relative or absolute coorditantes // To get relative, set PCT flag to number: // // // Get 10% of total width to `x` and 20 to y // x, y = tm.GetXY(10|tm.PCT, 20) // func getXY(x int, y int) (int, int) { // Set percent flag: num | PCT // // Check percent flag: num & PCT // // Reset percent flag: num & 0xFF const shift = uint(^uint(0)>>63) << 4 const PCT = 0x8000 << shift if y == -1 { y = currentHeight() + 1 } if x&PCT != 0 { x = int((x & 0xFF) * Width() / 100) } if y&PCT != 0 { y = int((y & 0xFF) * Height() / 100) } return x, y } // currentHeight returns current height. Line count in Screen buffer. func currentHeight() int { return strings.Count(buffer.String(), "\n") } ================================================ FILE: cmd/internal/screen/screen.go ================================================ package screen import ( "os" "sync" colorable "github.com/mattn/go-colorable" "golang.org/x/crypto/ssh/terminal" ) // Stdout color supported stdout var Stdout = colorable.NewColorableStdout() // SaveState saves cursor state. func SaveState() { Stdout.Write([]byte("\0337")) } // RestoreState restores cursor state. func RestoreState() { Stdout.Write([]byte("\0338")) } // Clear clears terminal screen. func Clear() { Stdout.Write([]byte("\033[2J")) } // HideCursor hide the cursor func HideCursor() { Stdout.Write([]byte("\033[?25l")) } // UnHideCursor unhide the cursor func UnHideCursor() { Stdout.Write([]byte("\033[?25h")) } var size struct { once sync.Once width int height int } func getSize() (int, int) { size.once.Do(func() { var err error size.width, size.height, err = terminal.GetSize(int(os.Stdout.Fd())) if err != nil { size.width, size.height = -1, -1 } }) return size.width, size.height } // Width returns console width func Width() int { width, _ := getSize() return width } // Height returns console height func Height() int { _, height := getSize() return height } ================================================ FILE: cmd/internal/super/supercow.go ================================================ package super import ( "errors" "io" "os" "os/signal" "strings" "syscall" "time" "github.com/Code-Hex/Neo-cowsay/cmd/v2/internal/screen" cowsay "github.com/Code-Hex/Neo-cowsay/v2" "github.com/Code-Hex/Neo-cowsay/v2/decoration" runewidth "github.com/mattn/go-runewidth" "github.com/rivo/uniseg" ) func getNoSaidCow(cow *cowsay.Cow, opts ...cowsay.Option) (string, error) { opts = append(opts, cowsay.Thoughts(' ')) cow, err := cow.Clone(opts...) if err != nil { return "", err } return cow.GetCow() } // RunSuperCow runs super cow mode animation on the your terminal func RunSuperCow(phrase string, withBold bool, opts ...cowsay.Option) error { cow, err := cowsay.New(opts...) if err != nil { return err } balloon := cow.Balloon(phrase) blank := createBlankSpace(balloon) said, err := cow.GetCow() if err != nil { return err } notSaid, err := getNoSaidCow(cow, opts...) if err != nil { return err } saidCow := balloon + said saidCowLines := strings.Count(saidCow, "\n") + 1 // When it is higher than the height of the terminal h := screen.Height() if saidCowLines > h { return errors.New("too height messages") } notSaidCow := blank + notSaid renderer := newRenderer(saidCow, notSaidCow) screen.SaveState() screen.HideCursor() screen.Clear() go renderer.createFrames(cow, withBold) renderer.render() screen.UnHideCursor() screen.RestoreState() return nil } func createBlankSpace(balloon string) string { var buf strings.Builder l := strings.Count(balloon, "\n") for i := 0; i < l; i++ { buf.WriteRune('\n') } return buf.String() } func maxLen(cow []string) int { max := 0 for _, line := range cow { l := runewidth.StringWidth(line) if max < l { max = l } } return max } type cowLine struct { raw string clusters []rune } func (c *cowLine) Len() int { return len(c.clusters) } func (c *cowLine) Slice(i, j int) string { if c.Len() == 0 { return "" } return string(c.clusters[i:j]) } func makeCowLines(cow string) []*cowLine { sep := strings.Split(cow, "\n") cowLines := make([]*cowLine, len(sep)) for i, line := range sep { g := uniseg.NewGraphemes(line) clusters := make([]rune, 0) for g.Next() { clusters = append(clusters, g.Runes()...) } cowLines[i] = &cowLine{ raw: line, clusters: clusters, } } return cowLines } type renderer struct { max int middle int screenWidth int heightDiff int frames chan string saidCow string notSaidCowLines []*cowLine quit chan os.Signal } func newRenderer(saidCow, notSaidCow string) *renderer { notSaidCowSep := strings.Split(notSaidCow, "\n") w, cowsWidth := screen.Width(), maxLen(notSaidCowSep) max := w + cowsWidth quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) return &renderer{ max: max, middle: max / 2, screenWidth: w, heightDiff: screen.Height() - strings.Count(saidCow, "\n") - 1, frames: make(chan string, max), saidCow: saidCow, notSaidCowLines: makeCowLines(notSaidCow), quit: quit, } } const ( // Frequency the color changes magic = 2 span = 30 * time.Millisecond standup = 3 * time.Second ) func (r *renderer) createFrames(cow *cowsay.Cow, withBold bool) { const times = standup / span w := r.newWriter(withBold) for x, i := 0, 1; i <= r.max; i++ { if i == r.middle { w.SetPosx(r.posX(i)) for k := 0; k < int(times); k++ { base := x * 70 // draw colored cow w.SetColorSeq(base) w.WriteString(r.saidCow) r.frames <- w.String() if k%magic == 0 { x++ } } } else { base := x * 70 w.SetPosx(r.posX(i)) w.SetColorSeq(base) for _, line := range r.notSaidCowLines { if i > r.screenWidth { // Left side animations n := i - r.screenWidth if n < line.Len() { w.WriteString(line.Slice(n, line.Len())) } } else if i <= line.Len() { // Right side animations w.WriteString(line.Slice(0, i-1)) } else { w.WriteString(line.raw) } w.Write([]byte{'\n'}) } r.frames <- w.String() } if i%magic == 0 { x++ } } close(r.frames) } func (r *renderer) render() { initCh := make(chan struct{}, 1) initCh <- struct{}{} for view := range r.frames { select { case <-r.quit: screen.Clear() return case <-initCh: case <-time.After(span): } io.Copy(screen.Stdout, strings.NewReader(view)) } } func (r *renderer) posX(i int) int { posx := r.screenWidth - i if posx < 1 { posx = 1 } return posx } // Writer is wrapper which is both screen.MoveWriter and decoration.Writer. type Writer struct { buf *strings.Builder mw *screen.MoveWriter dw *decoration.Writer } func (r *renderer) newWriter(withBold bool) *Writer { var buf strings.Builder mw := screen.NewMoveWriter(&buf, r.posX(0), r.heightDiff) options := []decoration.Option{ decoration.WithAurora(0), } if withBold { options = append(options, decoration.WithBold()) } dw := decoration.NewWriter(mw, options...) return &Writer{ buf: &buf, mw: mw, dw: dw, } } // WriteString writes string. which is implemented io.StringWriter. func (w *Writer) WriteString(s string) (int, error) { return w.dw.WriteString(s) } // Write writes bytes. which is implemented io.Writer. func (w *Writer) Write(p []byte) (int, error) { return w.dw.Write(p) } // SetPosx sets posx func (w *Writer) SetPosx(x int) { w.mw.SetPosx(x) } // SetColorSeq sets color sequence. func (w *Writer) SetColorSeq(colorSeq int) { w.dw.SetColorSeq(colorSeq) } // Reset resets calls some Reset methods. func (w *Writer) Reset() { w.buf.Reset() w.mw.Reset() } func (w *Writer) String() string { defer w.Reset() return w.buf.String() } ================================================ FILE: cmd/testdata/cowsay/W_option.txt ================================================ _____ / foo \ | bar | \ baz / ----- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || ================================================ FILE: cmd/testdata/cowsay/b_option.txt ================================================ ___________ < foobarbaz > ----------- \ ^__^ \ (==)\_______ (__)\ )\/\ ||----w | || || ================================================ FILE: cmd/testdata/cowsay/d_option.txt ================================================ ____________ < 0xdeadbeef > ------------ \ ^__^ \ (xx)\_______ (__)\ )\/\ U ||----w | || || ================================================ FILE: cmd/testdata/cowsay/eyes_option.txt ================================================ _______________ < I'm not angry > --------------- \ ^__^ \ (^^)\_______ (__)\ )\/\ ||----w | || || ================================================ FILE: cmd/testdata/cowsay/f_tux_option.txt ================================================ ________________ < what is macOS? > ---------------- \ \ .--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/ ================================================ FILE: cmd/testdata/cowsay/g_option.txt ================================================ _______________ < give me money > --------------- \ ^__^ \ ($$)\_______ (__)\ )\/\ ||----w | || || ================================================ FILE: cmd/testdata/cowsay/n_option.txt ================================================ _____ / foo \ | bar | \ baz / ----- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || ================================================ FILE: cmd/testdata/cowsay/p_option.txt ================================================ ___________________ < everyone hates me > ------------------- \ ^__^ \ (@@)\_______ (__)\ )\/\ ||----w | || || ================================================ FILE: cmd/testdata/cowsay/s_option.txt ================================================ ______________ < I don't know > -------------- \ ^__^ \ (**)\_______ (__)\ )\/\ U ||----w | || || ================================================ FILE: cmd/testdata/cowsay/t_option.txt ================================================ _______ < tired > ------- \ ^__^ \ (--)\_______ (__)\ )\/\ ||----w | || || ================================================ FILE: cmd/testdata/cowsay/tongue_option.txt ================================================ ________ < hungry > -------- \ ^__^ \ (oo)\_______ (__)\ )\/\ : ||----w | || || ================================================ FILE: cmd/testdata/cowsay/wired_option.txt ================================================ __________________________ < Wanna Netflix and chill? > -------------------------- \ ^__^ \ (OO)\_______ (__)\ )\/\ ||----w | || || ================================================ FILE: cmd/testdata/cowsay/y_option.txt ================================================ ________________________ < I forgot my ID at home > ------------------------ \ ^__^ \ (..)\_______ (__)\ )\/\ ||----w | || || ================================================ FILE: cmd/testdata/cowthink/W_option.txt ================================================ _____ ( foo ) ( bar ) ( baz ) ----- o ^__^ o (oo)\_______ (__)\ )\/\ ||----w | || || ================================================ FILE: cmd/testdata/cowthink/b_option.txt ================================================ ___________ ( foobarbaz ) ----------- o ^__^ o (==)\_______ (__)\ )\/\ ||----w | || || ================================================ FILE: cmd/testdata/cowthink/d_option.txt ================================================ ____________ ( 0xdeadbeef ) ------------ o ^__^ o (xx)\_______ (__)\ )\/\ U ||----w | || || ================================================ FILE: cmd/testdata/cowthink/eyes_option.txt ================================================ _______________ ( I'm not angry ) --------------- o ^__^ o (^^)\_______ (__)\ )\/\ ||----w | || || ================================================ FILE: cmd/testdata/cowthink/f_tux_option.txt ================================================ ________________ ( what is macOS? ) ---------------- o o .--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/ ================================================ FILE: cmd/testdata/cowthink/g_option.txt ================================================ _______________ ( give me money ) --------------- o ^__^ o ($$)\_______ (__)\ )\/\ ||----w | || || ================================================ FILE: cmd/testdata/cowthink/n_option.txt ================================================ _____ ( foo ) ( bar ) ( baz ) ----- o ^__^ o (oo)\_______ (__)\ )\/\ ||----w | || || ================================================ FILE: cmd/testdata/cowthink/p_option.txt ================================================ ___________________ ( everyone hates me ) ------------------- o ^__^ o (@@)\_______ (__)\ )\/\ ||----w | || || ================================================ FILE: cmd/testdata/cowthink/s_option.txt ================================================ ______________ ( I don't know ) -------------- o ^__^ o (**)\_______ (__)\ )\/\ U ||----w | || || ================================================ FILE: cmd/testdata/cowthink/t_option.txt ================================================ _______ ( tired ) ------- o ^__^ o (--)\_______ (__)\ )\/\ ||----w | || || ================================================ FILE: cmd/testdata/cowthink/tongue_option.txt ================================================ ________ ( hungry ) -------- o ^__^ o (oo)\_______ (__)\ )\/\ : ||----w | || || ================================================ FILE: cmd/testdata/cowthink/wired_option.txt ================================================ __________________________ ( Wanna Netflix and chill? ) -------------------------- o ^__^ o (OO)\_______ (__)\ )\/\ ||----w | || || ================================================ FILE: cmd/testdata/cowthink/y_option.txt ================================================ ________________________ ( I forgot my ID at home ) ------------------------ o ^__^ o (..)\_______ (__)\ )\/\ ||----w | || || ================================================ FILE: cow.go ================================================ package cowsay import ( "fmt" "math/rand" "strings" ) // Cow struct!! type Cow struct { eyes string tongue string typ *CowFile thoughts rune thinking bool ballonWidth int disableWordWrap bool buf strings.Builder } // New returns pointer of Cow struct that made by options func New(options ...Option) (*Cow, error) { cow := &Cow{ eyes: "oo", tongue: " ", thoughts: '\\', typ: &CowFile{ Name: "default", BasePath: "cows", LocationType: InBinary, }, ballonWidth: 40, } for _, o := range options { if err := o(cow); err != nil { return nil, err } } return cow, nil } // Say returns string that said by cow func (cow *Cow) Say(phrase string) (string, error) { mow, err := cow.GetCow() if err != nil { return "", err } return cow.Balloon(phrase) + mow, nil } // Clone returns a copy of cow. // // If any options are specified, they will be reflected. func (cow *Cow) Clone(options ...Option) (*Cow, error) { ret := new(Cow) *ret = *cow ret.buf.Reset() for _, o := range options { if err := o(ret); err != nil { return nil, err } } return ret, nil } // Option defined for Options type Option func(*Cow) error // Eyes specifies eyes // The specified string will always be adjusted to be equal to two characters. func Eyes(s string) Option { return func(c *Cow) error { c.eyes = adjustTo2Chars(s) return nil } } // Tongue specifies tongue // The specified string will always be adjusted to be less than or equal to two characters. func Tongue(s string) Option { return func(c *Cow) error { c.tongue = adjustTo2Chars(s) return nil } } func adjustTo2Chars(s string) string { if len(s) >= 2 { return s[:2] } if len(s) == 1 { return s + " " } return " " } func containCows(target string) (*CowFile, error) { cowPaths, err := Cows() if err != nil { return nil, err } for _, cowPath := range cowPaths { cowfile, ok := cowPath.Lookup(target) if ok { return cowfile, nil } } return nil, nil } // NotFound is indicated not found the cowfile. type NotFound struct { Cowfile string } var _ error = (*NotFound)(nil) func (n *NotFound) Error() string { return fmt.Sprintf("not found %q cowfile", n.Cowfile) } // Type specify name of the cowfile func Type(s string) Option { if s == "" { s = "default" } return func(c *Cow) error { cowfile, err := containCows(s) if err != nil { return err } if cowfile != nil { c.typ = cowfile return nil } return &NotFound{Cowfile: s} } } // Thinking enables thinking mode func Thinking() Option { return func(c *Cow) error { c.thinking = true return nil } } // Thoughts Thoughts allows you to specify // the rune that will be drawn between // the speech bubbles and the cow func Thoughts(thoughts rune) Option { return func(c *Cow) error { c.thoughts = thoughts return nil } } // Random specifies something .cow from cows directory func Random() Option { pick, err := pickCow() return func(c *Cow) error { if err != nil { return err } c.typ = pick return nil } } func pickCow() (*CowFile, error) { cowPaths, err := Cows() if err != nil { return nil, err } cowPath := cowPaths[rand.Intn(len(cowPaths))] n := len(cowPath.CowFiles) cowfile := cowPath.CowFiles[rand.Intn(n)] return &CowFile{ Name: cowfile, BasePath: cowPath.Name, LocationType: cowPath.LocationType, }, nil } // BallonWidth specifies ballon size func BallonWidth(size uint) Option { return func(c *Cow) error { c.ballonWidth = int(size) return nil } } // DisableWordWrap disables word wrap. // Ignoring width of the ballon. func DisableWordWrap() Option { return func(c *Cow) error { c.disableWordWrap = true return nil } } ================================================ FILE: cow_test.go ================================================ package cowsay import ( "errors" "fmt" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" ) func TestCow_Clone(t *testing.T) { tests := []struct { name string opts []Option from *Cow want *Cow }{ { name: "without options", opts: []Option{}, from: func() *Cow { cow, _ := New() return cow }(), want: func() *Cow { cow, _ := New() return cow }(), }, { name: "with some options", opts: []Option{}, from: func() *Cow { cow, _ := New( Type("docker"), BallonWidth(60), ) return cow }(), want: func() *Cow { cow, _ := New( Type("docker"), BallonWidth(60), ) return cow }(), }, { name: "clone and some options", opts: []Option{ Thinking(), Thoughts('o'), }, from: func() *Cow { cow, _ := New( Type("docker"), BallonWidth(60), ) return cow }(), want: func() *Cow { cow, _ := New( Type("docker"), BallonWidth(60), Thinking(), Thoughts('o'), ) return cow }(), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.want.Clone(tt.opts...) if err != nil { t.Fatal(err) } if diff := cmp.Diff(tt.want, got, cmp.AllowUnexported(Cow{}), cmpopts.IgnoreFields(Cow{}, "buf")); diff != "" { t.Errorf("(-want, +got)\n%s", diff) } }) } t.Run("random", func(t *testing.T) { cow, _ := New( Type(""), Thinking(), Thoughts('o'), Eyes("xx"), Tongue("u"), Random(), ) cloned, _ := cow.Clone() if diff := cmp.Diff(cow, cloned, cmp.AllowUnexported(Cow{}), cmpopts.IgnoreFields(Cow{}, "buf")); diff != "" { t.Errorf("(-want, +got)\n%s", diff) } }) t.Run("error", func(t *testing.T) { cow, err := New() if err != nil { t.Fatal(err) } wantErr := errors.New("error") _, err = cow.Clone(func(*Cow) error { return wantErr }) if wantErr != err { t.Fatalf("want %v, but got %v", wantErr, err) } }) } func Test_adjustTo2Chars(t *testing.T) { tests := []struct { name string s string want string }{ { name: "empty", s: "", want: " ", }, { name: "1 character", s: "1", want: "1 ", }, { name: "2 characters", s: "12", want: "12", }, { name: "3 characters", s: "123", want: "12", }, } for _, tt := range tests { t.Run(tt.s, func(t *testing.T) { if got := adjustTo2Chars(tt.s); got != tt.want { t.Errorf("adjustTo2Chars() = %v, want %v", got, tt.want) } }) } } func TestNotFound_Error(t *testing.T) { file := "test" n := &NotFound{ Cowfile: file, } want := fmt.Sprintf("not found %q cowfile", file) if want != n.Error() { t.Fatalf("want %q but got %q", want, n.Error()) } } ================================================ FILE: cows/beavis.zen.cow ================================================ ## ## Beavis, with Zen philosophy removed. ## $the_cow = <> 5.4 ## $the_cow = < \\ _ -~ `. ^-` ^-_ ///-._ _ _ _ _ _ _}^ - - - - ~ ~-- ,.-~ /.-~ EOC ================================================ FILE: cows/elephant-in-snake.cow ================================================ ## ## 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 ================================================ FILE: cows/gopher.cow ================================================ $the_cow = < EOC ================================================ FILE: cows/moofasa.cow ================================================ ## ## MOOfasa. ## $the_cow = <> EOC ================================================ FILE: cows/squirrel.cow ================================================ $the_cow = < < > .---. $thoughts | \\ \\ - ~ ~ - / / | _____ ..-~ ~-..-~ | | \\~~~\\.' `./~~~/ --------- \\__/ \\__/ .' O \\ / / \\ " (_____, `._.' | } \\/~~~/ `----. / } | / \\__/ `-. | / | / `. ,~~| ~-.__| /_ - ~ ^| /- _ `..-' | / | / ~-. `-. _ _ _ |_____| |_____| ~ - . _ _ _ _ _> EOC ================================================ FILE: cows/stimpy.cow ================================================ ## ## Stimpy! ## $the_cow = <> EOC ================================================ FILE: cows/turkey.cow ================================================ ## ## Turkey! ## $the_cow = <____) >___ ^\\_\\_\\_\\_\\_\\_\\) ^^^//\\\\_^^//\\\\_^ ^(\\_\\_\\_\\) ^^^ ^^ ^^^ ^ EOC ================================================ FILE: cows/turtle.cow ================================================ ## ## A mysterious turtle... ## $the_cow = < -------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||` func TestSay(t *testing.T) { type args struct { phrase string options []Option } tests := []struct { name string args args wantFile string wantErr bool }{ { name: "default", args: args{ phrase: "hello!", }, wantFile: "default.cow", wantErr: false, }, { name: "nest", args: args{ phrase: defaultSay, options: []Option{ DisableWordWrap(), }, }, wantFile: "nest.cow", wantErr: false, }, { name: "error", args: args{ phrase: "error", options: []Option{ func(*Cow) error { return errors.New("error") }, }, }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := Say(tt.args.phrase, tt.args.options...) if (err != nil) != tt.wantErr { t.Errorf("Say() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr { return } filename := filepath.Join("testdata", tt.wantFile) content, err := ioutil.ReadFile(filename) if err != nil { t.Fatal(err) } got = strings.Replace(got, "\r", "", -1) // for windows want := strings.Replace(string(content), "\r", "", -1) // for windows if want != got { t.Log(cmp.Diff([]byte(want), []byte(got))) t.Fatalf("want\n%s\n\ngot\n%s", want, got) } }) } } ================================================ FILE: decoration/aurora.go ================================================ package decoration import ( "fmt" "io" "math" "unicode" "unicode/utf8" ) func (w *Writer) writeAsAurora(b []byte) (nn int, err error) { defer w.buf.Reset() for len(b) > 0 { char, size := utf8.DecodeRune(b) if char == '\n' { w.buf.WriteRune(char) b = b[size:] continue } if unicode.IsSpace(char) { w.buf.WriteRune(char) } else { fmt.Fprintf(&w.buf, "\033[38;5;%d%sm%c\033[0m", rgb(float64(w.options.colorSeq)), w.options.maybeBold(), char, ) } w.options.colorSeq++ b = b[size:] } return w.writer.Write(w.buf.Bytes()) } func (w *Writer) writeStringAsAurora(s string) (nn int, err error) { defer w.buf.Reset() for _, char := range s { if char == '\n' { w.buf.WriteRune(char) continue } if unicode.IsSpace(char) { w.buf.WriteRune(char) } else { fmt.Fprintf(&w.buf, "\033[38;5;%d%sm%c\033[0m", rgb(float64(w.options.colorSeq)), w.options.maybeBold(), char, ) } w.options.colorSeq++ } if sw, ok := w.writer.(io.StringWriter); ok { return sw.WriteString(w.buf.String()) } return w.writer.Write(w.buf.Bytes()) } // https://sking7.github.io/articles/139888127.html#:~:text=value%20of%20frequency.-,Using,-out-of-phase const ( freq = 0.01 m = math.Pi / 3 redPhase = 0 greenPhase = 2 * m bluePhase = 4 * m ) var rgbMemo = map[float64]int64{} func rgb(i float64) int64 { if v, ok := rgbMemo[i]; ok { return v } red := int64(6*(math.Sin(freq*i+redPhase)*127+128)/256) * 36 green := int64(6*(math.Sin(freq*i+greenPhase)*127+128)/256) * 6 blue := int64(6*(math.Sin(freq*i+bluePhase)*127+128)/256) * 1 rgbMemo[i] = 16 + red + green + blue return rgbMemo[i] } ================================================ FILE: decoration/bold.go ================================================ package decoration import ( "fmt" "io" "unicode" "unicode/utf8" ) func (w *Writer) writeAsDefaultBold(b []byte) (nn int, err error) { defer w.buf.Reset() for len(b) > 0 { char, size := utf8.DecodeRune(b) if char == '\n' { w.buf.WriteRune(char) b = b[size:] continue } if unicode.IsSpace(char) { w.buf.WriteRune(char) } else { fmt.Fprintf(&w.buf, "\x1b[1m%c\x1b[0m", char) } b = b[size:] } return w.writer.Write(w.buf.Bytes()) } func (w *Writer) writeStringAsDefaultBold(s string) (nn int, err error) { defer w.buf.Reset() for _, char := range s { if char == '\n' { w.buf.WriteRune(char) continue } if unicode.IsSpace(char) { w.buf.WriteRune(char) } else { fmt.Fprintf(&w.buf, "\x1b[1m%c\x1b[0m", char) } } if sw, ok := w.writer.(io.StringWriter); ok { return sw.WriteString(w.buf.String()) } return w.writer.Write(w.buf.Bytes()) } ================================================ FILE: decoration/decoration.go ================================================ package decoration import ( "bytes" "io" ) type options struct { withBold bool withRainbow bool withAurora bool colorSeq int } func (o *options) maybeBold() string { if o.withBold { return ";1" } return "" } // Option for any writer in this package. type Option func(o *options) // WithBold writes with bold. func WithBold() Option { return func(o *options) { o.withBold = true } } // WithRainbow writes with rainbow. func WithRainbow() Option { return func(o *options) { o.withRainbow = true } } // WithAurora writes with aurora. func WithAurora(initialSeq int) Option { return func(o *options) { o.withAurora = true o.colorSeq = initialSeq } } // Writer is a writer to decorates. type Writer struct { writer io.Writer buf bytes.Buffer options *options } var _ interface { io.Writer io.StringWriter } = (*Writer)(nil) // NewWriter creates a new writer. func NewWriter(w io.Writer, opts ...Option) *Writer { options := new(options) for _, optFunc := range opts { optFunc(options) } return &Writer{ writer: w, options: options, } } // SetColorSeq sets current color sequence. func (w *Writer) SetColorSeq(colorSeq int) { w.options.colorSeq = colorSeq } // Write writes bytes. which is implemented io.Writer. // // If Bold is enabled in the options, the text will be written as Bold. // If both Aurora and Rainbow are enabled in the options, Aurora will take precedence. func (w *Writer) Write(b []byte) (nn int, err error) { switch { case w.options.withAurora: return w.writeAsAurora(b) case w.options.withRainbow: return w.writeAsRainbow(b) case w.options.withBold: return w.writeAsDefaultBold(b) default: return w.writer.Write(b) } } // WriteString writes string. which is implemented io.StringWriter. // // See also Write. func (w *Writer) WriteString(s string) (n int, err error) { switch { case w.options.withAurora: return w.writeStringAsAurora(s) case w.options.withRainbow: return w.writeStringAsRainbow(s) case w.options.withBold: return w.writeStringAsDefaultBold(s) default: if sw, ok := w.writer.(io.StringWriter); ok { return sw.WriteString(w.buf.String()) } return w.writer.Write([]byte(s)) } } ================================================ FILE: decoration/rainbow.go ================================================ package decoration import ( "fmt" "io" "unicode" "unicode/utf8" ) const ( red = iota + 31 green yellow blue magenta cyan ) var rainbow = []int{magenta, red, yellow, green, cyan, blue} func (w *Writer) writeAsRainbow(b []byte) (nn int, err error) { defer w.buf.Reset() for len(b) > 0 { char, size := utf8.DecodeRune(b) if char == '\n' { w.options.colorSeq = 0 w.buf.WriteRune(char) b = b[size:] continue } if unicode.IsSpace(char) { w.buf.WriteRune(char) } else { fmt.Fprintf(&w.buf, "\x1b[%d%sm%c\x1b[0m", rainbow[w.options.colorSeq%len(rainbow)], w.options.maybeBold(), char, ) } w.options.colorSeq++ b = b[size:] } return w.writer.Write(w.buf.Bytes()) } func (w *Writer) writeStringAsRainbow(s string) (nn int, err error) { defer w.buf.Reset() for _, char := range s { if char == '\n' { w.options.colorSeq = 0 w.buf.WriteRune(char) continue } if unicode.IsSpace(char) { w.buf.WriteRune(char) } else { fmt.Fprintf(&w.buf, "\x1b[%d%sm%c\x1b[0m", rainbow[w.options.colorSeq%len(rainbow)], w.options.maybeBold(), char, ) } w.options.colorSeq++ } if sw, ok := w.writer.(io.StringWriter); ok { return sw.WriteString(w.buf.String()) } return w.writer.Write(w.buf.Bytes()) } ================================================ FILE: doc/cowsay.1 ================================================ '\" t .\" Title: cowsay .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.16 .\" Date: 2021-11-13 .\" Manual: \ \& .\" Source: \ \& .\" Language: English .\" .TH "NEO COWSAY/COWTHINK" "1" "2021-11-13" "\ \&" "\ \&" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 .nh .ad l .de URL \fI\\$2\fP <\\$1>\\$3 .. .als MTO URL .if \n[.g] \{\ . mso www.tmac . am URL . ad l . . . am MTO . ad l . . . LINKSTYLE blue R < > .\} .SH "NAME" Neo cowsay/cowthink \- configurable speaking/thinking cow (and a bit more) .SH "SYNOPSIS" .sp cowsay [\-e \fIeye_string\fP] [\-f \fIcowfile\fP] [\-h] [\-l] [\-n] [\-T \fItongue_string\fP] [\-W \fIcolumn\fP] [\-bdgpstwy] [\-\-random] [\-\-bold] [\-\-rainbow] [\-\-aurora] [\-\-super] [\fImessage\fP] .SH "DESCRIPTION" .sp \fINeo\-cowsay\fP (cowsay) generates an ASCII picture of a cow saying something provided by the user. If run with no arguments, it accepts standard input, word\-wraps the message given at about 40 columns, and prints the cow saying the given message on standard output. .sp To aid in the use of arbitrary messages with arbitrary whitespace, use the \fB\-n\fP option. If it is specified, the given message will not be word\-wrapped. This is possibly useful if you want to make the cow think or speak in figlet(6). If \fB\-n\fP is specified, there must not be any command\-line arguments left after all the switches have been processed. .sp The \fB\-W\fP specifies roughly (where the message should be wrapped. The default is equivalent to \fB\-W 40\fP i.e. wrap words at or before the 40th column. .sp If any command\-line arguments are left over after all switches have been processed, they become the cow\(cqs message. The program will not accept standard input for a message in this case. .sp There are several provided modes which change the appearance of the cow depending on its particular emotional/physical state. .sp The \fB\-b\fP option initiates Borg mode .sp \fB\-d\fP causes the cow to appear dead .sp \fB\-g\fP invokes greedy mode .sp \fB\-p\fP causes a state of paranoia to come over the cow .sp \fB\-s\fP makes the cow appear thoroughly stoned .sp \fB\-t\fP yields a tired cow .sp \fB\-w\fP is somewhat the opposite of \fB\-t\fP and initiates wired mode .sp \fB\-y\fP brings on the cow\(cqs youthful appearance. .sp The user may specify the \fB\-e\fP option to select the appearance of the cow\(cqs eyes, in which case the first two characters of the argument string \fIeye_string\fP will be used. The default eyes are \fIoo\fP. The tongue is similarly configurable through \fB\-T\fP and \fItongue_string\fP; it must be two characters and does not appear by default. However, it does appear in the \fIdead\fP and \fIstoned\fP modes. Any configuration done by \fB\-e\fP and \fB\-T\fP will be lost if one of the provided modes is used. .sp The \fB\-f\fP option specifies a particular cow picture file (\(lqcowfile\(rq) to use. If the cowfile spec contains \fI/\fP then it will be interpreted as a path relative to the current directory. Otherwise, cowsay will search the path specified in the \fBCOWPATH\fP environment variable. If \fB\-f \-\fP is specified, provides interactive Unix filter (command\-line fuzzy finder) to search the cowfile. .sp To list all cowfiles on the current \fBCOWPATH\fP, invoke \fBcowsay\fP with the \fB\-l\fP switch. .sp \fB\-\-random\fP pick randomly from available cowfiles .sp \fB\-\-bold\fP outputs as bold text .sp \fB\-\-rainbow\fP and \fB\-\-aurora\fP filters with colors an ASCII picture of a cow saying something .sp \fB\-\-super\fP ...enjoy! .sp If the program is invoked as \fBcowthink\fP then the cow will think its message instead of saying it. .SH "COWFILE FORMAT" .sp A cowfile is made up of a simple block of \fBperl(1)\fP code, which assigns a picture of a cow to the variable \fB$the_cow\fP. Should you wish to customize the eyes or the tongue of the cow, then the variables \fB$eyes\fP and \fB$tongue\fP may be used. The trail leading up to the cow\(cqs message balloon is composed of the character(s) in the \fB$thoughts\fP variable. Any backslashes must be reduplicated to prevent interpolation. The name of a cowfile should end with \fB.cow ,\fP otherwise it is assumed not to be a cowfile. Also, at\-signs (\(lq@\(rq) must be backslashed because that is what Perl 5 expects. .SH "ENVIRONMENT" .sp The COWPATH environment variable, if present, will be used to search for cowfiles. It contains a colon\-separated list of directories, much like \fBPATH or MANPATH\fP. It should always contain the \fB/usr/local/share/cows\fP directory, or at least a directory with a file called \fBdefault.cow\fP in it. .SH "FILES" .sp \fB%PREFIX%/share/cows\fP holds a sample set of cowfiles. If your \fBCOWPATH\fP is not explicitly set, it automatically contains this directory. .SH "BUGS" .sp .URL "https://github.com/Code\-Hex/Neo\-cowsay" "" "" .sp If there are any, please report bugs and feature requests in the issue tracker. Please do your best to provide a reproducible test case for bugs. This should include the \fBcowsay\fP command, the actual output and the expected output. .SH "AUTHORS" .sp Neo\-cowsay author is Kei Kamikawa (\c .MTO "x00.x7f.x86\(atgmail.com" "" ")." .sp The original author is Tony Monroe (\c .MTO "tony\(atnog.net" "" ")," with suggestions from Shannon Appel (\c .MTO "appel\(atCSUA.Berkeley.EDU" "" ")" and contributions from Anthony Polito (\c .MTO "aspolito\(atCSUA.Berkeley.EDU" "" ")." .SH "SEE ALSO" .sp perl(1), wall(1), nwrite(1), figlet(6) ================================================ FILE: doc/cowsay.1.txt.tpl ================================================ cowsay(1) ========= Name ---- Neo cowsay/cowthink - configurable speaking/thinking cow (and a bit more) SYNOPSIS -------- cowsay [-e _eye_string_] [-f _cowfile_] [-h] [-l] [-n] [-T _tongue_string_] [-W _column_] [-bdgpstwy] [--random] [--bold] [--rainbow] [--aurora] [--super] [_message_] DESCRIPTION ----------- _Neo-cowsay_ (cowsay) generates an ASCII picture of a cow saying something provided by the user. If run with no arguments, it accepts standard input, word-wraps the message given at about 40 columns, and prints the cow saying the given message on standard output. To aid in the use of arbitrary messages with arbitrary whitespace, use the *-n* option. If it is specified, the given message will not be word-wrapped. This is possibly useful if you want to make the cow think or speak in figlet(6). If *-n* is specified, there must not be any command-line arguments left after all the switches have been processed. The *-W* specifies roughly (where the message should be wrapped. The default is equivalent to *-W 40* i.e. wrap words at or before the 40th column. If any command-line arguments are left over after all switches have been processed, they become the cow's message. The program will not accept standard input for a message in this case. There are several provided modes which change the appearance of the cow depending on its particular emotional/physical state. The *-b* option initiates Borg mode *-d* causes the cow to appear dead *-g* invokes greedy mode *-p* causes a state of paranoia to come over the cow *-s* makes the cow appear thoroughly stoned *-t* yields a tired cow *-w* is somewhat the opposite of *-t* and initiates wired mode *-y* brings on the cow's youthful appearance. The user may specify the *-e* option to select the appearance of the cow's eyes, in which case the first two characters of the argument string _eye_string_ will be used. The default eyes are 'oo'. The tongue is similarly configurable through *-T* and _tongue_string_; it must be two characters and does not appear by default. However, it does appear in the 'dead' and 'stoned' modes. Any configuration done by *-e* and *-T* will be lost if one of the provided modes is used. The *-f* option specifies a particular cow picture file (``cowfile'') to use. If the cowfile spec contains '/' then it will be interpreted as a path relative to the current directory. Otherwise, cowsay will search the path specified in the *COWPATH* environment variable. If *-f -* is specified, provides interactive Unix filter (command-line fuzzy finder) to search the cowfile. To list all cowfiles on the current *COWPATH*, invoke *cowsay* with the *-l* switch. *--random* pick randomly from available cowfiles *--bold* outputs as bold text *--rainbow* and *--aurora* filters with colors an ASCII picture of a cow saying something *--super* ...enjoy! If the program is invoked as *cowthink* then the cow will think its message instead of saying it. COWFILE FORMAT -------------- A cowfile is made up of a simple block of *perl(1)* code, which assigns a picture of a cow to the variable *$the_cow*. Should you wish to customize the eyes or the tongue of the cow, then the variables *$eyes* and *$tongue* may be used. The trail leading up to the cow's message balloon is composed of the character(s) in the *$thoughts* variable. Any backslashes must be reduplicated to prevent interpolation. The name of a cowfile should end with *.cow ,* otherwise it is assumed not to be a cowfile. Also, at-signs (``@'') must be backslashed because that is what Perl 5 expects. ENVIRONMENT ----------- The COWPATH environment variable, if present, will be used to search for cowfiles. It contains a colon-separated list of directories, much like *PATH or MANPATH*. It should always contain the */usr/local/share/cows* directory, or at least a directory with a file called *default.cow* in it. FILES ----- *%PREFIX%/share/cows* holds a sample set of cowfiles. If your *COWPATH* is not explicitly set, it automatically contains this directory. BUGS ---- https://github.com/Code-Hex/Neo-cowsay If there are any, please report bugs and feature requests in the issue tracker. Please do your best to provide a reproducible test case for bugs. This should include the *cowsay* command, the actual output and the expected output. AUTHORS ------- Neo-cowsay author is Kei Kamikawa (x00.x7f.x86@gmail.com). The original author is Tony Monroe (tony@nog.net), with suggestions from Shannon Appel (appel@CSUA.Berkeley.EDU) and contributions from Anthony Polito (aspolito@CSUA.Berkeley.EDU). SEE ALSO -------- perl(1), wall(1), nwrite(1), figlet(6) ================================================ FILE: embed.go ================================================ package cowsay import ( "embed" "sort" "strings" ) //go:embed cows/* var cowsDir embed.FS // Asset loads and returns the asset for the given name. // It returns an error if the asset could not be found or // could not be loaded. func Asset(path string) ([]byte, error) { return cowsDir.ReadFile(path) } // AssetNames returns the list of filename of the assets. func AssetNames() []string { entries, err := cowsDir.ReadDir("cows") if err != nil { panic(err) } names := make([]string, 0, len(entries)) for _, entry := range entries { name := strings.TrimSuffix(entry.Name(), ".cow") names = append(names, name) } sort.Strings(names) return names } var cowsInBinary = AssetNames() // CowsInBinary returns the list of cowfiles which are in binary. // the list is memoized. func CowsInBinary() []string { return cowsInBinary } ================================================ FILE: examples/basic/go.mod ================================================ module github.com/Code-Hex/Neo-cowsay/v2/example go 1.16 require github.com/Code-Hex/Neo-cowsay/v2 v2.0.1 replace github.com/Code-Hex/Neo-cowsay/v2 => ../../ ================================================ FILE: examples/basic/go.sum ================================================ github.com/Code-Hex/go-wordwrap v1.0.0 h1:yl5fLyZEz3+hPGbpTRlTQ8mQJ1HXWcTq1FCNR1ch6zM= github.com/Code-Hex/go-wordwrap v1.0.0/go.mod h1:/SsbgkY2Q0aPQRyvXcyQwWYTQOIwSORKe6MPjRVGIWU= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= ================================================ FILE: examples/basic/main.go ================================================ package main import ( "fmt" cowsay "github.com/Code-Hex/Neo-cowsay/v2" ) func main() { if false { simple() } else { complex() } } func simple() { say, err := cowsay.Say( "Hello", cowsay.Type("default"), cowsay.BallonWidth(40), ) if err != nil { panic(err) } fmt.Println(say) } func complex() { cow, err := cowsay.New( cowsay.BallonWidth(40), //cowsay.Thinking(), cowsay.Random(), ) if err != nil { panic(err) } say, err := cow.Say("Hello") if err != nil { panic(err) } fmt.Println(say) } ================================================ FILE: examples/echo-server/README.md ================================================ ## echo server ``` $ go build $ ./echo-server 2021/11/14 16:16:27 server addr => 127.0.0.1:54456 ``` ## client Use [netcat](https://en.wikipedia.org/wiki/Netcat) Please enter `^D` (control + D) after you enter any characters. ``` $ nc 127.0.0.1 54456 hello _______ < hello > ------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || ``` ================================================ FILE: examples/echo-server/go.mod ================================================ module github.com/Code-Hex/Neo-cowsay/v2/examples/echo-server go 1.16 require github.com/Code-Hex/Neo-cowsay/v2 v2.0.1 replace github.com/Code-Hex/Neo-cowsay/v2 => ../../ ================================================ FILE: examples/echo-server/go.sum ================================================ github.com/Code-Hex/go-wordwrap v1.0.0 h1:yl5fLyZEz3+hPGbpTRlTQ8mQJ1HXWcTq1FCNR1ch6zM= github.com/Code-Hex/go-wordwrap v1.0.0/go.mod h1:/SsbgkY2Q0aPQRyvXcyQwWYTQOIwSORKe6MPjRVGIWU= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= ================================================ FILE: examples/echo-server/main.go ================================================ package main import ( "context" "io" "log" "net" "os" "os/signal" "strings" "time" cowsay "github.com/Code-Hex/Neo-cowsay/v2" ) const limit = 1000 func main() { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { log.Fatal(err) } log.Println("server addr =>", ln.Addr()) ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() go func() { for { conn, err := ln.Accept() if err != nil { log.Println(err) return } conn.SetDeadline(time.Now().Add(5 * time.Second)) go func() { defer conn.Close() go func() { <-ctx.Done() // cancel conn.SetDeadline(time.Now()) }() var buf strings.Builder rd := io.LimitReader(conn, limit) if _, err := io.Copy(&buf, rd); err != nil { log.Println("error:", err) return } phrase := strings.TrimSpace(buf.String()) log.Println(phrase) say, _ := cowsay.Say(phrase) conn.Write([]byte(say)) }() } }() <-ctx.Done() } ================================================ FILE: go.mod ================================================ module github.com/Code-Hex/Neo-cowsay/v2 require ( github.com/Code-Hex/go-wordwrap v1.0.0 github.com/google/go-cmp v0.5.6 github.com/mattn/go-runewidth v0.0.13 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect ) go 1.16 ================================================ FILE: go.sum ================================================ github.com/Code-Hex/go-wordwrap v1.0.0 h1:yl5fLyZEz3+hPGbpTRlTQ8mQJ1HXWcTq1FCNR1ch6zM= github.com/Code-Hex/go-wordwrap v1.0.0/go.mod h1:/SsbgkY2Q0aPQRyvXcyQwWYTQOIwSORKe6MPjRVGIWU= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= ================================================ FILE: split.go ================================================ //go:build !windows // +build !windows package cowsay import "strings" func splitPath(s string) []string { return strings.Split(s, ":") } ================================================ FILE: split_windows.go ================================================ //go:build windows // +build windows package cowsay import "strings" func splitPath(s string) []string { return strings.Split(s, ";") } ================================================ FILE: testdata/default.cow ================================================ ________ < hello! > -------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || ================================================ FILE: testdata/nest.cow ================================================ ______________________________ / ________ \ | < cowsay > | | -------- | | \ ^__^ | | \ (oo)\_______ | | (__)\ )\/\ | | ||----w | | \ || || / ------------------------------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || ================================================ FILE: testdata/testdir/test.cow ================================================ $the_cow = <