├── .github └── workflows │ └── go.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── Makefile ├── README.md ├── docs └── rainbow.png ├── go.mod ├── go.sum ├── main.go ├── main_test.go └── rainbow ├── rainbow.go ├── rainbow_test.go └── testdata ├── painted.txt └── plain.txt /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Continues Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - feature/* 8 | - bugfix/* 9 | pull_request: 10 | branches: 11 | - master 12 | - feature/* 13 | - bugfix/* 14 | - refactor/* 15 | 16 | jobs: 17 | test: 18 | runs-on: ubuntu-latest 19 | strategy: 20 | matrix: 21 | go: ["1.17", "1.18"] 22 | 23 | steps: 24 | - name: Checkout repo 25 | uses: actions/checkout@v2 26 | 27 | - name: Set up Go ${{ matrix.go }} 28 | uses: actions/setup-go@v2 29 | with: 30 | go-version: ${{ matrix.go }} 31 | 32 | - uses: actions/cache@v2 33 | with: 34 | path: | 35 | ~/.cache/go-build 36 | ~/go/pkg/mod 37 | key: ${{ runner.os }}-go-${{ matrix.go }}-${{ hashFiles('**/go.sum') }} 38 | restore-keys: | 39 | ${{ runner.os }}-go-${{ matrix.go }} 40 | 41 | - name: Running Tests 42 | run: make ci_tests 43 | 44 | - name: Upload coverage report 45 | uses: codecov/codecov-action@v2 46 | with: 47 | token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos 48 | file: ./coverage.out 49 | flags: unittests 50 | name: codecov-umbrella 51 | 52 | audit: 53 | runs-on: ubuntu-latest 54 | steps: 55 | - name: Checkout repo 56 | uses: actions/checkout@v2 57 | 58 | - name: Set up Go ${{ matrix.go }} 59 | uses: actions/setup-go@v2 60 | with: 61 | go-version: ${{ matrix.go }} 62 | 63 | - name: WriteGoList 64 | run: go list -json -m all > go.list 65 | 66 | - name: Nancy 67 | uses: sonatype-nexus-community/nancy-github-action@main 68 | 69 | lint: 70 | runs-on: ubuntu-latest 71 | steps: 72 | - name: Checkout repo 73 | uses: actions/checkout@v2 74 | 75 | - name: golangci-lint 76 | uses: golangci/golangci-lint-action@v2 77 | with: 78 | version: v1.45.0 79 | args: --timeout 5m0s 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 10 | *.o 11 | *.a 12 | *.so 13 | 14 | # Folders 15 | _obj 16 | _test 17 | 18 | # Architecture specific extensions/prefixes 19 | *.[568vq] 20 | [568vq].out 21 | 22 | *.cgo1.go 23 | *.cgo2.c 24 | _cgo_defun.c 25 | _cgo_gotypes.go 26 | _cgo_export.* 27 | 28 | # Output of the go coverage tool, specifically when used with LiteIDE 29 | *.out 30 | _testmain.go 31 | 32 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 33 | vendor/ 34 | deploy/ 35 | tmp/ 36 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | govet: 3 | check-shadowing: true 4 | settings: 5 | printf: 6 | funcs: 7 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof 8 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf 9 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf 10 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf 11 | enable: 12 | - fieldalignment 13 | golint: 14 | min-confidence: 0 15 | gocyclo: 16 | min-complexity: 15 17 | maligned: 18 | suggest-new: true 19 | goconst: 20 | min-len: 2 21 | min-occurrences: 2 22 | misspell: 23 | locale: UK 24 | lll: 25 | line-length: 140 26 | goimports: 27 | local-prefixes: github.com/golangci/golangci-lint 28 | gocritic: 29 | enabled-tags: 30 | - diagnostic 31 | - experimental 32 | - opinionated 33 | - performance 34 | - style 35 | funlen: 36 | lines: 100 37 | statements: 50 38 | godot: 39 | capital: true 40 | unparam: 41 | check-exported: true 42 | 43 | issues: 44 | # Excluding configuration per-path, per-linter, per-text and per-source 45 | exclude-rules: 46 | - path: _test\.go 47 | linters: 48 | - gosec # security check is not important in tests 49 | - dupl # we usualy duplicate code in tests 50 | - errcheck 51 | - govet 52 | fix: true 53 | exclude-use-default: false 54 | 55 | run: 56 | skip-dirs: 57 | - model 58 | - tmp 59 | - bin 60 | - scripts 61 | 62 | tests: true 63 | build-tags: 64 | - integration 65 | 66 | linters: 67 | enable: 68 | - bodyclose 69 | - deadcode 70 | - depguard 71 | - dogsled 72 | - dupl 73 | - durationcheck 74 | - errcheck 75 | - errorlint 76 | - exportloopref 77 | - forcetypeassert 78 | - gocognit 79 | - goconst 80 | - gocritic 81 | - gocyclo 82 | - godot 83 | - godox 84 | - goprintffuncname 85 | - gosimple 86 | - govet 87 | - ineffassign 88 | - misspell 89 | - nakedret 90 | - nestif 91 | - nilerr 92 | - prealloc 93 | - revive 94 | - rowserrcheck 95 | - sqlclosecheck 96 | - staticcheck 97 | - structcheck 98 | - stylecheck 99 | - tparallel 100 | - typecheck 101 | - unconvert 102 | - unparam 103 | - unused 104 | - varcheck 105 | - wastedassign 106 | - whitespace 107 | 108 | # golangci.com configuration 109 | # https://github.com/golangci/golangci/wiki/Configuration 110 | service: 111 | golangci-lint-version: 1.45.x 112 | prepare: 113 | - echo "here I can run custom commands, but no preparation needed for this repo" 114 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2018 Arsham Shirvani 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | help: ## Show help messages. 2 | @grep -E '^[0-9a-zA-Z_-]+:(.*?## .*)?$$' $(MAKEFILE_LIST) | sed 's/^Makefile://' | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 3 | 4 | run="." 5 | dir="./..." 6 | short="-short" 7 | flags="" 8 | timeout=40s 9 | 10 | TARGET=$(shell git describe --abbrev=0 --tags) 11 | RELEADE_NAME=rainbow 12 | DEPLOY_FOLDER=deploy 13 | CHECKSUM_FILE=CHECKSUM 14 | MAKEFLAGS += -j1 15 | 16 | .PHONY: install 17 | install: ## Install the binary. 18 | @go install -trimpath -ldflags="-s -w" 19 | 20 | .PHONY: unittest 21 | unittest: ## Run unit tests in watch mode. You can set: [run, timeout, short, dir, flags]. Example: make unittest flags="-race". 22 | @echo "running tests on $(run). waiting for changes..." 23 | @-zsh -c "go test -trimpath --timeout=$(timeout) $(short) $(dir) -run $(run) $(flags); repeat 100 printf '#'; echo" 24 | @reflex -d none -r "(\.go$$)|(go.mod)" -- zsh -c "go test -trimpath --timeout=$(timeout) $(short) $(dir) -run $(run) $(flags); repeat 100 printf '#'" 25 | 26 | .PHONY: lint 27 | lint: ## Run linters. 28 | go fmt ./... 29 | go vet ./... 30 | golangci-lint run ./... 31 | 32 | .PHONY: ci_tests 33 | ci_tests: ## Run tests for CI. 34 | go test -trimpath --timeout=5m -failfast -v -race -covermode=atomic -coverprofile=coverage.out ./... 35 | 36 | .PHONY: dependencies 37 | dependencies: ## Install dependencies requried for development operations. 38 | @go install github.com/cespare/reflex@latest 39 | @go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.45.0 40 | @go install github.com/psampaz/go-mod-outdated@latest 41 | @go install github.com/jondot/goweight@latest 42 | @go get -t -u golang.org/x/tools/cmd/cover 43 | @go get -t -u github.com/sonatype-nexus-community/nancy@latest 44 | @go get -u ./... 45 | @go mod tidy 46 | 47 | .PHONY: clean 48 | clean: ## Clean test caches and tidy up modules. 49 | @go clean -testcache 50 | @go mod tidy 51 | @rm -rf $(DEPLOY_FOLDER) 52 | 53 | .PHONY: tmpfolder 54 | tmpfolder: ## Create the temporary folder. 55 | @mkdir -p $(DEPLOY_FOLDER) 56 | @rm -rf $(DEPLOY_FOLDER)/$(CHECKSUM_FILE) 2> /dev/null 57 | 58 | .PHONY: linux 59 | linux: tmpfolder 60 | linux: ## Build for GNU/Linux. 61 | @GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o $(DEPLOY_FOLDER)/$(RELEADE_NAME) main.go 62 | @tar -czf $(DEPLOY_FOLDER)/figurine_linux_$(TARGET).tar.gz $(DEPLOY_FOLDER)/$(RELEADE_NAME) 63 | @cd $(DEPLOY_FOLDER) ; sha256sum figurine_linux_$(TARGET).tar.gz >> $(CHECKSUM_FILE) 64 | @echo "Linux target:" $(DEPLOY_FOLDER)/figurine_linux_$(TARGET).tar.gz 65 | @rm $(DEPLOY_FOLDER)/$(RELEADE_NAME) 66 | 67 | .PHONY: darwin 68 | darwin: tmpfolder 69 | darwin: ## Build for Mac. 70 | @GOOS=darwin GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o $(DEPLOY_FOLDER)/$(RELEADE_NAME) main.go 71 | @tar -czf $(DEPLOY_FOLDER)/figurine_darwin_$(TARGET).tar.gz $(DEPLOY_FOLDER)/$(RELEADE_NAME) 72 | @cd $(DEPLOY_FOLDER) ; sha256sum figurine_darwin_$(TARGET).tar.gz >> $(CHECKSUM_FILE) 73 | @echo "Darwin target:" $(DEPLOY_FOLDER)/figurine_darwin_$(TARGET).tar.gz 74 | @rm $(DEPLOY_FOLDER)/$(RELEADE_NAME) 75 | 76 | .PHONY: windows 77 | windows: tmpfolder 78 | windows: ## Build for windoze. 79 | @GOOS=windows GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o $(DEPLOY_FOLDER)/$(RELEADE_NAME).exe main.go 80 | @zip -r $(DEPLOY_FOLDER)/figurine_windows_$(TARGET).zip $(DEPLOY_FOLDER)/$(RELEADE_NAME).exe 81 | @cd $(DEPLOY_FOLDER) ; sha256sum figurine_windows_$(TARGET).zip >> $(CHECKSUM_FILE) 82 | @echo "Windows target:" $(DEPLOY_FOLDER)/figurine_windows_$(TARGET).zip 83 | @rm $(DEPLOY_FOLDER)/$(RELEADE_NAME).exe 84 | 85 | .PHONY: release 86 | release: ## Create releases for Linux, Mac, and windoze. 87 | release: tmpfolder linux darwin windows 88 | 89 | 90 | .PHONY: coverage 91 | coverage: ## Show the test coverage on browser. 92 | go test -covermode=count -coverprofile=coverage.out ./... 93 | go tool cover -func=coverage.out | tail -n 1 94 | go tool cover -html=coverage.out 95 | 96 | .PHONY: audit 97 | audit: 98 | go list -u -m -json all | go-mod-outdated -update -direct 99 | go list -json -m all | nancy sleuth 100 | goweight | head -n 20 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rainbow 2 | 3 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/arsham/rainbow)](https://pkg.go.dev/github.com/arsham/rainbow) 4 | ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/arsham/rainbow) 5 | [![Build Status](https://github.com/arsham/rainbow/actions/workflows/go.yml/badge.svg)](https://github.com/arsham/rainbow/actions/workflows/go.yml) 6 | [![Coverage Status](https://codecov.io/gh/arsham/rainbow/branch/master/graph/badge.svg)](https://codecov.io/gh/arsham/rainbow) 7 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/9d181f84a8ab4ab3a07201e37d9a218b)](https://www.codacy.com/app/arsham/rainbow?utm_source=github.com&utm_medium=referral&utm_content=arsham/rainbow&utm_campaign=Badge_Grade) 8 | [![Go Report Card](https://goreportcard.com/badge/github.com/arsham/rainbow)](https://goreportcard.com/report/github.com/arsham/rainbow) 9 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 10 | 11 | Tasty rainbows for your terminal like these: 12 | 13 | ![Screenshot](/docs/rainbow.png?raw=true "Rainbow") 14 | 15 | This app was inspired by lolcats, but written in Go. 16 | 17 | ### Table of Contents 18 | 19 | 1. [Installation](#installation) 20 | 2. [Usage](#usage) 21 | 3. [As library](#as-library) 22 | 4. [See Also](#see-also) 23 | 5. [License](#license) 24 | 25 | ## Installation 26 | 27 | You can download the latest binary from 28 | [here](https://github.com/arsham/rainbow/releases), or you can compile from 29 | source: 30 | 31 | ```bash 32 | go install github.com/arsham/rainbow@latest 33 | ``` 34 | 35 | ## Usage 36 | 37 | You can pipe the text into the app in many ways. Choose one that is suitable for 38 | you: 39 | 40 | ```bash 41 | # File contents: 42 | rainbow < filename.txt 43 | 44 | # Echo a string: 45 | echo "Any quotes" | rainbow 46 | 47 | # Here string: 48 | rainbow <. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 license 3 | // License that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "io" 11 | "math/rand" 12 | "os" 13 | "strings" 14 | "time" 15 | 16 | "github.com/arsham/rainbow/rainbow" 17 | ) 18 | 19 | func main() { 20 | rand.Seed(time.Now().UTC().UnixNano()) 21 | var r io.Reader 22 | switch len(os.Args) { 23 | case 1: 24 | r = os.Stdin 25 | default: 26 | r = bytes.NewBufferString(strings.Join(os.Args[1:], " ") + "\n") 27 | } 28 | l := &rainbow.Light{ 29 | Writer: os.Stdout, 30 | Seed: rand.Int63n(256), 31 | } 32 | 33 | if _, err := io.Copy(l, r); err != nil { 34 | fmt.Println("Error painting your input:", err) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Arsham Shirvani . All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 license 3 | // License that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "bytes" 9 | "io/ioutil" 10 | "os" 11 | "regexp" 12 | "testing" 13 | 14 | "github.com/brianvoe/gofakeit/v5" 15 | "github.com/stretchr/testify/assert" 16 | "github.com/stretchr/testify/require" 17 | ) 18 | 19 | var re = regexp.MustCompile(`\x1B\[[0-9;]*[JKmsu]`) 20 | 21 | func setup(t *testing.T) func() { 22 | t.Helper() 23 | oldStdin := os.Stdin 24 | oldStdout := os.Stdout 25 | oldArgs := os.Args 26 | 27 | fin, err := ioutil.TempFile("", "testMain") 28 | require.NoError(t, err) 29 | fout, err := ioutil.TempFile("", "testMain") 30 | if err != nil { 31 | assert.NoError(t, fin.Close()) 32 | t.Fatal(err) 33 | } 34 | os.Stdin = fin 35 | os.Stdout = fout 36 | return func() { 37 | os.Stdin = oldStdin 38 | os.Stdout = oldStdout 39 | os.Args = oldArgs 40 | assert.NoError(t, fin.Close()) 41 | assert.NoError(t, fout.Close()) 42 | assert.NoError(t, os.Remove(fin.Name())) 43 | assert.NoError(t, os.Remove(fout.Name())) 44 | } 45 | } 46 | 47 | func TestMain(t *testing.T) { 48 | t.Run("WithArgs", testMainWithArgs) 49 | t.Run("WithPipe", testMainWithPipe) 50 | t.Run("CopyError", testMainCopyError) 51 | } 52 | 53 | func testMainWithArgs(t *testing.T) { 54 | cleanup := setup(t) 55 | defer cleanup() 56 | input := gofakeit.Sentence(20) 57 | os.Args = []string{"rainbow", input} 58 | main() 59 | os.Stdout.Seek(0, 0) 60 | buf := &bytes.Buffer{} 61 | buf.ReadFrom(os.Stdout) 62 | 63 | out := buf.Bytes() 64 | got := re.ReplaceAll(out, []byte("")) 65 | assert.Equal(t, []byte(input+"\n"), got) 66 | } 67 | 68 | func testMainWithPipe(t *testing.T) { 69 | cleanup := setup(t) 70 | defer cleanup() 71 | input := gofakeit.Sentence(20) 72 | os.Args = []string{"rainbow"} 73 | os.Stdin.WriteString(input) 74 | os.Stdin.Seek(0, 0) 75 | main() 76 | os.Stdout.Seek(0, 0) 77 | buf := &bytes.Buffer{} 78 | buf.ReadFrom(os.Stdout) 79 | 80 | out := buf.Bytes() 81 | got := re.ReplaceAll(out, []byte("")) 82 | assert.Equal(t, []byte(input), got) 83 | } 84 | 85 | func testMainCopyError(t *testing.T) { 86 | cleanup := setup(t) 87 | defer cleanup() 88 | fin, err := ioutil.TempFile("", "testMain") 89 | require.NoError(t, err) 90 | require.NoError(t, fin.Close()) 91 | os.Stdin = fin 92 | 93 | os.Args = []string{"rainbow"} 94 | main() 95 | os.Stdout.Seek(0, 0) 96 | buf := &bytes.Buffer{} 97 | buf.ReadFrom(os.Stdout) 98 | got := buf.String() 99 | assert.Contains(t, got, "already closed") 100 | } 101 | -------------------------------------------------------------------------------- /rainbow/rainbow.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Arsham Shirvani . All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 license 3 | // License that can be found in the LICENSE file. 4 | 5 | // Package rainbow prints texts in beautiful rainbows in terminal. Usage is very 6 | // simple: 7 | // 8 | // import "github.com/arsham/rainbow/rainbow" 9 | // // ... 10 | // l := rainbow.Light{ 11 | // Reader: someReader, // to read from 12 | // Writer: os.Stdout, // to write to 13 | // } 14 | // l.Paint() // will rainbow everything it reads from reader to writer. 15 | // 16 | // If you want the rainbow to be random, you can seed it this way: 17 | // l := rainbow.Light{ 18 | // Reader: buf, 19 | // Writer: os.Stdout, 20 | // Seed: rand.Int63n(256), 21 | // } 22 | // 23 | // You can also use the Light as a Writer: 24 | // l := rainbow.Light{ 25 | // Writer: os.Stdout, // to write to 26 | // Seed: rand.Int63n(256), 27 | // } 28 | // io.Copy(l, someReader) 29 | package rainbow 30 | 31 | import ( 32 | "bytes" 33 | "errors" 34 | "io" 35 | "math" 36 | "math/rand" 37 | "regexp" 38 | "strconv" 39 | ) 40 | 41 | var ( 42 | // We remove all previous paintings to create a new rainbow. 43 | colorMatch = regexp.MustCompile("^\033" + `\[\d+(;\d+)?(;\d+)?[mK]`) 44 | 45 | // ErrNilWriter is returned when Light.Writer is nil. 46 | ErrNilWriter = errors.New("nil writer") 47 | ) 48 | 49 | const ( 50 | freq = 0.1 51 | spread = 3 52 | ) 53 | 54 | // Light reads data from the Writer and pains the contents to the Reader. You 55 | // should seed it everytime otherwise you get the same results. 56 | type Light struct { 57 | Reader io.Reader 58 | Writer io.Writer 59 | Seed int64 60 | } 61 | 62 | // Paint returns an error if it could not copy the data. 63 | func (l *Light) Paint() error { 64 | if l.Seed == 0 { 65 | l.Seed = rand.Int63n(256) 66 | } 67 | _, err := io.Copy(l, l.Reader) 68 | return err 69 | } 70 | 71 | // Write paints the data and writes it into l.Writer. 72 | func (l *Light) Write(data []byte) (int, error) { 73 | if l.Writer == nil { 74 | return 0, ErrNilWriter 75 | } 76 | var ( 77 | offset float64 78 | dataLen = len(data) 79 | // 16 times seems to be the sweet spot. 80 | buf = bytes.NewBuffer(make([]byte, 0, dataLen*16)) 81 | seed = l.Seed 82 | ) 83 | 84 | data = colorMatch.ReplaceAll(data, []byte("")) 85 | for _, c := range string(data) { 86 | switch c { 87 | case '\n': 88 | offset = 0 89 | seed++ 90 | buf.WriteByte('\n') 91 | case '\t': 92 | offset++ 93 | buf.WriteByte('\t') 94 | default: 95 | r, g, b := plotPos(float64(seed) + (offset / spread)) 96 | colouriseWriter(buf, c, r, g, b) 97 | offset++ 98 | } 99 | } 100 | _, err := l.Writer.Write(buf.Bytes()) 101 | return dataLen, err 102 | } 103 | 104 | func plotPos(x float64) (red, green, blue float64) { 105 | red = math.Sin(freq*x)*127 + 128 106 | green = math.Sin(freq*x+2*math.Pi/3)*127 + 128 107 | blue = math.Sin(freq*x+4*math.Pi/3)*127 + 128 108 | return red, green, blue 109 | } 110 | 111 | const max = 16 + (6 * (127 + 128) / 256 * 36) + (6 * (127 + 128) / 256 * 6) + (6 * (127 + 128) / 256) 112 | 113 | // nums is used to cache the values of strconv.Itoa(n) for better performance 114 | // gains. 115 | var nums = make([]string, 0, max) 116 | 117 | func init() { 118 | for i := int64(0); i < max; i++ { 119 | nums = append(nums, strconv.FormatInt(i, 10)) 120 | } 121 | } 122 | 123 | func colouriseWriter(s *bytes.Buffer, c rune, r, g, b float64) { 124 | s.WriteString("\033[38;5;") 125 | s.WriteString(nums[colour(r, g, b)]) 126 | s.WriteByte('m') 127 | s.WriteRune(c) 128 | s.WriteString("\033[0m") 129 | } 130 | 131 | func colour(red, green, blue float64) uint8 { 132 | return 16 + baseColor(red, 36) + baseColor(green, 6) + baseColor(blue, 1) 133 | } 134 | 135 | func baseColor(value float64, factor uint8) uint8 { 136 | return uint8(6*value/256) * factor 137 | } 138 | -------------------------------------------------------------------------------- /rainbow/rainbow_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Arsham Shirvani . All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 license 3 | // License that can be found in the LICENSE file. 4 | 5 | package rainbow 6 | 7 | import ( 8 | "bytes" 9 | "errors" 10 | "fmt" 11 | "io" 12 | "math/rand" 13 | "os" 14 | "regexp" 15 | "strings" 16 | "sync" 17 | "testing" 18 | "time" 19 | 20 | "github.com/stretchr/testify/assert" 21 | "github.com/stretchr/testify/require" 22 | ) 23 | 24 | func init() { 25 | rand.Seed(time.Now().UTC().UnixNano()) 26 | } 27 | 28 | func readFile(t *testing.T, name string) []byte { 29 | f, err := os.Open("testdata/" + name) 30 | require.NoError(t, err) 31 | b, err := io.ReadAll(f) 32 | require.NoError(t, err) 33 | return b 34 | } 35 | 36 | func TestLightPaint(t *testing.T) { 37 | t.Parallel() 38 | plain := string(readFile(t, "plain.txt")) 39 | painted := string(readFile(t, "painted.txt")) 40 | //nolint:stylecheck // this is on purpose. 41 | tcs := []struct { 42 | sample string 43 | painted string 44 | }{ 45 | { 46 | "2d7gMRSgGLj9F0c tPjSmsdRsTej4x7BJiOp R9HUHEiyH0G1Ld XeL5fjQ1KkxI3", 47 | `2d7gMRSgGLj9F0c tPjSmsdRsTej4x7BJiOp R9HUHEiyH0G1Ld XeL5fjQ1KkxI3`, 48 | }, 49 | { 50 | "11✂1", 51 | `11✂1`, 52 | }, 53 | { 54 | "🏧-✂1", 55 | `🏧-✂1`, 56 | }, 57 | { 58 | plain, 59 | painted, 60 | }, 61 | } 62 | for _, tc := range tcs { 63 | r := strings.NewReader(tc.sample) 64 | w := &bytes.Buffer{} 65 | l := &Light{ 66 | Reader: r, 67 | Writer: w, 68 | Seed: 1, 69 | } 70 | err := l.Paint() 71 | assert.NoError(t, err) 72 | assert.EqualValues(t, tc.painted, w.String()) 73 | } 74 | } 75 | 76 | func BenchmarkLightPaint(b *testing.B) { 77 | bcs := []struct { 78 | lines int 79 | letters int 80 | }{ 81 | {1, 10}, 82 | {1, 240}, 83 | {10, 10}, 84 | {10, 240}, 85 | {100, 10}, 86 | {100, 240}, 87 | {500, 500}, 88 | } 89 | for _, bc := range bcs { 90 | var ( 91 | totalLen int 92 | name = fmt.Sprintf("lines%d_let%d", bc.lines, bc.letters) 93 | line = make([]byte, bc.letters) 94 | r = &bytes.Buffer{} 95 | w = &bytes.Buffer{} 96 | ) 97 | rand.Read(line) 98 | for i := 0; i < bc.lines; i++ { 99 | r.Write(line) 100 | r.WriteString("\n") 101 | totalLen += len(line) + 1 102 | } 103 | b.ResetTimer() 104 | b.Run(name, func(b *testing.B) { 105 | b.Run("Serial", func(b *testing.B) { 106 | b.ResetTimer() 107 | l := &Light{ 108 | Writer: w, 109 | Reader: r, 110 | Seed: 1, 111 | } 112 | for i := 0; i < b.N; i++ { 113 | l.Paint() 114 | } 115 | }) 116 | b.Run("Parallel", func(b *testing.B) { 117 | b.ResetTimer() 118 | b.RunParallel(func(bp *testing.PB) { 119 | l := &Light{ 120 | Writer: w, 121 | Reader: r, 122 | Seed: 1, 123 | } 124 | for bp.Next() { 125 | l.Paint() 126 | } 127 | }) 128 | }) 129 | }) 130 | } 131 | } 132 | 133 | func TestPlotPos(t *testing.T) { 134 | t.Parallel() 135 | tcs := []struct { 136 | name string 137 | x float64 138 | red int 139 | green int 140 | blue int 141 | }{ 142 | {"0", 0, 128, 237, 18}, 143 | {"1", 1, 140, 231, 12}, 144 | {"5", 5, 188, 194, 1}, 145 | {"10", 10, 234, 133, 15}, 146 | {"50", 50, 6, 220, 157}, 147 | {"100", 100, 58, 70, 254}, 148 | {"360", 360, 2, 176, 205}, 149 | } 150 | for _, tc := range tcs { 151 | tc := tc 152 | t.Run(tc.name, func(t *testing.T) { 153 | t.Parallel() 154 | got, got1, got2 := plotPos(tc.x) 155 | assert.Equal(t, tc.red, int(got), "red value") 156 | assert.Equal(t, tc.green, int(got1), "green value") 157 | assert.Equal(t, tc.blue, int(got2), " blue value") 158 | }) 159 | } 160 | } 161 | 162 | var got, got1, got2 float64 163 | 164 | func BenchmarkPlotPos(b *testing.B) { 165 | b.Run("Serial", func(b *testing.B) { 166 | for i := 0; i < b.N; i++ { 167 | got, got1, got2 = plotPos(100) 168 | } 169 | }) 170 | b.Run("Parallel", func(b *testing.B) { 171 | b.RunParallel(func(b *testing.PB) { 172 | for b.Next() { 173 | got, got1, got2 = plotPos(100) 174 | } 175 | }) 176 | }) 177 | } 178 | 179 | // this test is here for keeping the logic in sync when we refactor the codes. 180 | func TestColour(t *testing.T) { 181 | t.Parallel() 182 | randColour := func() int32 { 183 | return rand.Int31n(256) 184 | } 185 | bc := func(value float64, factor uint8) uint8 { 186 | return uint8(6*value/256) * factor 187 | } 188 | check := func(red, green, blue float64) uint8 { 189 | return 16 + bc(red, 36) + bc(green, 6) + bc(blue, 1) 190 | } 191 | for i := 0; i < 1000; i++ { 192 | red, green, blue := float64(randColour()), float64(randColour()), float64(randColour()) 193 | got := colour(red, green, blue) 194 | want := check(red, green, blue) 195 | assert.Equalf(t, want, got, "colour(%f, %f, %f)", red, green, blue) 196 | } 197 | } 198 | 199 | var intval uint8 200 | 201 | func BenchmarkColour(b *testing.B) { 202 | b.Run("Serial", func(b *testing.B) { 203 | for i := 0; i < b.N; i++ { 204 | intval = colour(6, 100, 1000) 205 | } 206 | }) 207 | b.Run("Parallel", func(b *testing.B) { 208 | b.RunParallel(func(b *testing.PB) { 209 | for b.Next() { 210 | intval = colour(6, 100, 1000) 211 | } 212 | }) 213 | }) 214 | } 215 | 216 | type writeError func([]byte) (int, error) 217 | 218 | func (w *writeError) Write(p []byte) (int, error) { return (*w)(p) } 219 | 220 | func TestLightWrite(t *testing.T) { 221 | errExam := errors.New("this error") 222 | wrErr := writeError(func([]byte) (int, error) { return 0, errExam }) 223 | //nolint:stylecheck // this is on purpose. 224 | tcs := map[string]struct { 225 | data []byte 226 | want []byte 227 | checkErr bool 228 | }{ 229 | "new line": {[]byte("\n"), []byte("\n"), true}, 230 | "tab": {[]byte("\t"), []byte("\t"), true}, 231 | "NL tab": {[]byte("\n\t"), []byte("\n\t"), true}, 232 | "tab NL": {[]byte("\t\n"), []byte("\t\n"), true}, 233 | `033[38;5;2m`: {[]byte("\033[38;5;2m"), []byte(""), false}, 234 | `033[38;5;2K`: {[]byte("\033[38;5;2K"), []byte(""), false}, 235 | `033[32K`: {[]byte("\033[32K"), []byte(""), false}, 236 | `033[3K`: {[]byte("\033[3K"), []byte(""), false}, 237 | `033[3KARSHAM bytes`: {[]byte("\033[3KARSHAM"), []byte{27, 91, 51, 56, 59, 53, 59, 49, 53, 52, 109, 65, 27, 91, 48, 109, 27, 91, 51, 56, 59, 53, 59, 49, 53, 52, 109, 82, 27, 91, 48, 109, 27, 91, 51, 56, 59, 53, 59, 49, 53, 52, 109, 83, 27, 91, 48, 109, 27, 91, 51, 56, 59, 53, 59, 49, 53, 52, 109, 72, 27, 91, 48, 109, 27, 91, 51, 56, 59, 53, 59, 49, 53, 52, 109, 65, 27, 91, 48, 109, 27, 91, 51, 56, 59, 53, 59, 49, 53, 52, 109, 77, 27, 91, 48, 109}, true}, 238 | `033[3KARSHAM string`: {[]byte("\033[3KARSHAM"), []byte("ARSHAM"), true}, 239 | } 240 | for name, tc := range tcs { 241 | tc := tc 242 | t.Run(name, func(t *testing.T) { 243 | buf := &bytes.Buffer{} 244 | l := &Light{ 245 | Writer: buf, 246 | Seed: 1, 247 | } 248 | _, err := l.Write(tc.data) 249 | assert.NoError(t, err) 250 | got := buf.Bytes() 251 | if !bytes.Equal(got, tc.want) { 252 | t.Errorf("got (%v), want (%v)", got, tc.want) 253 | } 254 | if !tc.checkErr { 255 | return 256 | } 257 | l.Writer = &wrErr 258 | _, err = l.Write(tc.data) 259 | assert.Error(t, err) 260 | }) 261 | } 262 | r := &bytes.Buffer{} 263 | l := &Light{ 264 | Reader: r, 265 | } 266 | n, err := l.Write([]byte("blah")) 267 | assert.Error(t, err) 268 | assert.Zero(t, n) 269 | } 270 | 271 | func TestLightWriteRace(t *testing.T) { 272 | var ( 273 | wg sync.WaitGroup 274 | count = 1000 275 | data = bytes.Repeat([]byte("abc def\n"), 10) 276 | l = &Light{ 277 | Writer: io.Discard, 278 | Seed: 1, 279 | } 280 | ) 281 | wg.Add(3) 282 | go func() { 283 | for i := 0; i < count; i++ { 284 | _, err := l.Write(data) 285 | require.NoError(t, err) 286 | } 287 | wg.Done() 288 | }() 289 | go func() { 290 | for i := 0; i < count; i++ { 291 | _, err := l.Write(data) 292 | require.NoError(t, err) 293 | } 294 | wg.Done() 295 | }() 296 | go func() { 297 | for i := 0; i < count; i++ { 298 | _, err := l.Write(data) 299 | require.NoError(t, err) 300 | } 301 | wg.Done() 302 | }() 303 | wg.Wait() 304 | } 305 | 306 | func BenchmarkLightWrite(b *testing.B) { 307 | bcs := []struct { 308 | line int 309 | length int 310 | data string 311 | }{ 312 | {1, 1, "\n"}, 313 | {5, 10, strings.Repeat("aaaaa\n", 10)}, 314 | {15, 50, strings.Repeat("aaaaabbbbbccccc\n", 15)}, 315 | {15, 100, strings.Repeat(strings.Repeat("abcde", 20)+"\n", 15)}, 316 | {50, 50, strings.Repeat(strings.Repeat("abcde", 10)+"\n", 50)}, 317 | {100, 120, strings.Repeat(strings.Repeat("a", 120)+"\n", 100)}, 318 | } 319 | b.ResetTimer() 320 | b.Run("Serial", func(b *testing.B) { 321 | for _, bc := range bcs { 322 | bc := bc 323 | l := &Light{ 324 | Writer: io.Discard, 325 | Seed: 1, 326 | } 327 | name := fmt.Sprintf("line%d_len%d", bc.line, bc.length) 328 | b.Run(name, func(b *testing.B) { 329 | b.ResetTimer() 330 | for i := 0; i < b.N; i++ { 331 | l.Write([]byte(bc.data)) 332 | } 333 | }) 334 | } 335 | }) 336 | b.Run("Parallel", func(b *testing.B) { 337 | for _, bc := range bcs { 338 | bc := bc 339 | l := &Light{ 340 | Writer: io.Discard, 341 | Seed: 1, 342 | } 343 | name := fmt.Sprintf("line%d_len%d", bc.line, bc.length) 344 | b.Run(name, func(b *testing.B) { 345 | b.ResetTimer() 346 | b.RunParallel(func(b *testing.PB) { 347 | for b.Next() { 348 | l.Write([]byte(bc.data)) 349 | } 350 | }) 351 | }) 352 | } 353 | }) 354 | } 355 | 356 | // making sure we are not altering any texts. 357 | func TestLightWriteRevert(t *testing.T) { 358 | re := regexp.MustCompile(`\x1B\[[0-9;]*[JKmsu]`) 359 | tcs := []string{ 360 | "u9VGCQ1E4KCr8bO8 3ULdtlHL3WsjulJU kqUneSFT6 tvyAfih1Qew 5wBKffL4Yc", 361 | "Y5LFQNulLC0GTKB W4buVQmQTMu6C7aFs uGL6 x2OgVRlUZHCq46kgk sjr 4HKIb", 362 | "MamsTagRix6bEYwBGR b9FK7b1L 5x1YtTo8nFLrz0dZ rIdZdY0 b0bC05T42bHfV", 363 | "Ubmba7pnOCoUw9 xGgDjSIPU 7vOwUoiPHeCoxT XtywrjciBYZR cBQySayKWb rx", 364 | "EPz0kb3pQUuVv LL0t4t8mNaRklyuZTPi wHI2H35IReZbbdb9akXw gMCuJ PRrVK", 365 | "9qR4HkZ86 enPzoAyIWfz3bFg 8LSokcvV47J0 XfHM ngASr MfkM53zAkwLZmY ", 366 | "G7R9mjoYz9FU306 ATfgJ2C6AokIgtf BmL5uLWSJFTDs VO85P JXUjV 5n4OEuvl", 367 | "32Tj lwI9YaHDjsbqZUBIi 5XiJ7tOh 4eCqaaHT1i WpsJh3JA7s HKHk5R9yPKjL", 368 | "188qrmdf0GLU9 E2g vp5iX4g2CJ ueKSPvwY369daXZU 0bhg7IGhzeegWeGk5Fj ", 369 | "YXXUN751 qkTsoR95Udu EtFAN NgBAEe97uzp wpu2VKcX0W20P084d V1LLH28Rk", 370 | "nSZ6 Qww4GOxAsduJkEPIuNln GGbS rNq YEfhc4jaTAzoHC eT1ugr0mMYANX5JX", 371 | "wFZkZVWqV3ag7pYtlyN mKtPoVvvZMgU3p6E1 u6zSuUFHuk8nZsvQQ5 qeq 14YPo", 372 | "uXDLM l2J sznWecxn Ayjv9Ii3akvRD ArTwAkrA THyyrqT6LSAGnfJSSMx4Mes ", 373 | "m3Lo ufeV1XmoFxqs 4LE21GCiI5UT51 pj9YV B2a43pJxjCg b5CqaspplX5N2dq", 374 | "9BwGLsCTNGXDfLXNl ZcPNImZhzmDp8S 3ZG177TSj tjOSRMxBZ rhjP0zwJU1K o", 375 | "4MySJAF rybjdvOUuZhf VqPKvVuw5jaNsldI 8oYI8ZTL2s mZ7X 4awf4PPeLIHh", 376 | "LTLj3ayz83gM N5So9T32GVipPB2B Ccy 8UutGx N7u6DeZ bUN1hsBDoSC4Z0 sd", 377 | "gybKEl1 73H5qkvjR UbTUZl jAeMVFeDCtAGVbGhCq Fi0ZQctnmhtk0edD5 gkU3", 378 | "YX4dsZWyblp6PJIqTR vTgLmrYZd3 MlnwDVOtS1wKZDpoqlxY vu0ivZaYrqWhzAV", 379 | "Vb1Nks0QpBl1CTkJF QiO7nMLehiI0u S2XwbQXs9Znz Mbe0BQ13JTAOmSjmh WFH", 380 | "GNIzszrfT5 8WBHpE00a5j7Srfnx e8Qrrhomy8tw7XIa kQFG7ZazYio x5z PZIQ", 381 | } 382 | for _, tc := range tcs { 383 | w := &bytes.Buffer{} 384 | r := bytes.NewReader([]byte(tc)) 385 | l := Light{ 386 | Reader: r, 387 | Writer: w, 388 | } 389 | err := l.Paint() 390 | require.NoError(t, err) 391 | 392 | got := re.ReplaceAll(w.Bytes(), []byte("")) 393 | assert.EqualValues(t, tc, got) 394 | } 395 | } 396 | -------------------------------------------------------------------------------- /rainbow/testdata/painted.txt: -------------------------------------------------------------------------------- 1 | Magna excepteur incididunt laboris voluptate consectetur nostrud culpa velit 2 | anim anim nostrud. Lorem ipsum anim elit enim ut nulla cillum laborum nisi 3 | voluptate elit dolore deserunt elit exercitation magna pariatur cillum qui velit 4 | laboris laboris velit veniam ut adipisicing excepteur fugiat consectetur eiusmod 5 | officia eiusmod magna officia aute. Velit labore mollit ullamco labore sunt 6 | consectetur id ex ex nisi consectetur amet et eu commodo incididunt elit tempor 7 | sunt ea ea fugiat sit laborum sint enim sit incididunt adipisicing adipisicing 8 | sit commodo labore dolor amet amet dolore ea ad officia eiusmod sit excepteur do 9 | culpa ut dolore minim nostrud id fugiat ut esse ut tempor qui ullamco ea nisi ut 10 | enim ea veniam officia laborum eu sed minim enim exercitation labore culpa elit 11 | qui quis proident dolor et mollit consequat cillum ea in dolor occaecat sint 12 | culpa non ut exercitation in irure quis ea eu cupidatat sed consequat est 13 | reprehenderit sed consectetur incididunt duis culpa consequat magna laborum in 14 | sit id duis est minim magna nostrud culpa sunt deserunt excepteur ut tempor id 15 | sed laboris deserunt fugiat veniam sint sed velit est commodo dolor elit cillum 16 | et duis veniam exercitation enim ullamco tempor id irure ut labore occaecat 17 | voluptate do tempor incididunt occaecat duis esse labore velit est sunt veniam 18 | cupidatat elit ut ut elit quis ea dolore incididunt ut dolor enim mollit irure 19 | officia excepteur in enim aute sint laborum sunt est dolor labore nostrud in. 20 | -------------------------------------------------------------------------------- /rainbow/testdata/plain.txt: -------------------------------------------------------------------------------- 1 | Magna excepteur incididunt laboris voluptate consectetur nostrud culpa velit 2 | anim anim nostrud. Lorem ipsum anim elit enim ut nulla cillum laborum nisi 3 | voluptate elit dolore deserunt elit exercitation magna pariatur cillum qui velit 4 | laboris laboris velit veniam ut adipisicing excepteur fugiat consectetur eiusmod 5 | officia eiusmod magna officia aute. Velit labore mollit ullamco labore sunt 6 | consectetur id ex ex nisi consectetur amet et eu commodo incididunt elit tempor 7 | sunt ea ea fugiat sit laborum sint enim sit incididunt adipisicing adipisicing 8 | sit commodo labore dolor amet amet dolore ea ad officia eiusmod sit excepteur do 9 | culpa ut dolore minim nostrud id fugiat ut esse ut tempor qui ullamco ea nisi ut 10 | enim ea veniam officia laborum eu sed minim enim exercitation labore culpa elit 11 | qui quis proident dolor et mollit consequat cillum ea in dolor occaecat sint 12 | culpa non ut exercitation in irure quis ea eu cupidatat sed consequat est 13 | reprehenderit sed consectetur incididunt duis culpa consequat magna laborum in 14 | sit id duis est minim magna nostrud culpa sunt deserunt excepteur ut tempor id 15 | sed laboris deserunt fugiat veniam sint sed velit est commodo dolor elit cillum 16 | et duis veniam exercitation enim ullamco tempor id irure ut labore occaecat 17 | voluptate do tempor incididunt occaecat duis esse labore velit est sunt veniam 18 | cupidatat elit ut ut elit quis ea dolore incididunt ut dolor enim mollit irure 19 | officia excepteur in enim aute sint laborum sunt est dolor labore nostrud in. 20 | --------------------------------------------------------------------------------