[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Run with '...'\n2. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Desktop (please complete the following information):**\n - OS: [e.g. Linux]\n - Distribution: [e.g. Arch Linux]\n - Version [e.g. 22]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/workflows/go.yml",
    "content": "name: Continues Integration\n\non:\n  push:\n    branches:\n      - master\n      - feature/*\n      - bugfix/*\n  pull_request:\n    branches:\n      - master\n      - feature/*\n      - bugfix/*\n      - refactor/*\n      - chore/*\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repo\n        uses: actions/checkout@v2\n\n      - name: Set up Go\n        uses: actions/setup-go@v2\n        with:\n          go-version: 1.18\n\n      - uses: actions/cache@v2\n        with:\n          path: |\n            ~/.cache/go-build\n            ~/go/pkg/mod\n          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}\n          restore-keys: |\n            ${{ runner.os }}-go-\n\n      - name: Running Tests\n        run: make ci_tests\n\n      - name: Upload coverage report\n        uses: codecov/codecov-action@v2\n        with:\n          token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos\n          file: ./coverage.out\n          flags: unittests\n          name: codecov-umbrella\n\n  audit:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout repo\n        uses: actions/checkout@v2\n\n      - name: Set up Go\n        uses: actions/setup-go@v2\n        with:\n          go-version: 1.18\n\n      - name: WriteGoList\n        run: go list -json -deps > go.list\n\n      - name: Nancy\n        uses: sonatype-nexus-community/nancy-github-action@main\n\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout repo\n        uses: actions/checkout@v2\n\n      - name: golangci-lint\n        uses: golangci/golangci-lint-action@v2\n        with:\n          version: v1.45.2\n          args: --timeout 5m0s\n"
  },
  {
    "path": ".gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, build with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\nvendor/\ndeploy/\ntmp/\n"
  },
  {
    "path": ".golangci.yml",
    "content": "service:\n  golangci-lint-version: 1.45.x\n\nlinters-settings:\n  funlen:\n    lines: 100\n    statements: 50\n  cyclop:\n    skip-tests: true\n    max-complexity: 30\n    package-average: 5\n  gocyclo:\n    min-complexity: 15\n  gofumpt:\n    lang-version: \"1.18\"\n  goimports:\n    local-prefixes: github.com/golangci/golangci-lint\n  goconst:\n    min-len: 2\n    min-occurrences: 3\n  gocritic:\n    enabled-tags:\n      - diagnostic\n      - experimental\n      - opinionated\n      - performance\n      - style\n  godot:\n    capital: true\n  gosimple:\n    go: \"1.18\"\n    checks: [\"all\"]\n  govet:\n    settings:\n      printf:\n        funcs:\n          - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof\n          - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf\n          - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf\n          - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf\n    disable-all: true\n    enable:\n      - assign\n      - atomic\n      - atomicalign\n      - bools\n      - buildtag\n      - cgocall\n      - composites\n      - copylocks\n      - deepequalerrors\n      - errorsas\n      - fieldalignment\n      - findcall\n      - framepointer\n      - httpresponse\n      - ifaceassert\n      - loopclosure\n      - lostcancel\n      - nilfunc\n      - printf\n      - reflectvaluecompare\n      - shift\n      - sigchanyzer\n      - sortslice\n      - stdmethods\n      - stringintconv\n      - structtag\n      - testinggoroutine\n      - tests\n      - unmarshal\n      - unreachable\n      - unsafeptr\n      - unusedresult\n\n  maligned:\n    suggest-new: true\n  misspell:\n    locale: UK\n  staticcheck:\n    go: \"1.18\"\n    checks: [\"all\"]\n  stylecheck:\n    go: \"1.18\"\n    checks: [\"all\"]\n  unparam:\n    check-exported: true\n\nissues:\n  # Excluding configuration per-path, per-linter, per-text and per-source\n  exclude-rules:\n    - path: _test\\.go\n      linters:\n        - gosec # security check is not important in tests\n        - dupl # we usualy duplicate code in tests\n        - govet\n        - errcheck\n        - forcetypeassert\n        - godot\n  fix: true\n  exclude-use-default: false\n\nrun:\n  go: \"1.18\"\n  timeout: 5m\n  skip-dirs:\n    - tmp\n    - bin\n    - scripts\n  allow-parallel-runners: true\n  tests: true\n\nlinters:\n  enable:\n    - bidichk\n    - bodyclose\n    - cyclop\n    - deadcode\n    - depguard\n    - dogsled\n    - dupl\n    - durationcheck\n    - errcheck\n    - errname\n    - errorlint\n    - exportloopref\n    - forcetypeassert\n    - gocognit\n    - goconst\n    - gocritic\n    - gocyclo\n    - godot\n    - gofmt\n    - gofumpt\n    - goprintffuncname\n    - gosec\n    - gosimple\n    - govet\n    - ineffassign\n    - misspell\n    - nakedret\n    - nestif\n    - nilerr\n    - noctx\n    - prealloc\n    - predeclared\n    - promlinter\n    - revive\n    - rowserrcheck\n    - sqlclosecheck\n    - staticcheck\n    - structcheck\n    - stylecheck\n    - tenv\n    - tparallel\n    - typecheck\n    - unconvert\n    - unparam\n    - unused\n    - varcheck\n    - wastedassign\n    - whitespace\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment include:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at arshamshirvani+blush@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\n1. [Dependencies](#dependencies)\n2. [Testing](#testing)\n3. [Benchmarking](#benchmarking)\n4. [Pull Requests](#pull-requests)\n\n## Dependencies\n\nDependency management is done with [glide](https://github.com/Masterminds/glide).\nYou can install it by running:\n```bash\n$ go get -u github.com/Masterminds/glide\n```\n\nThen make sure you run `glide install` to install the current version of\ndependencies.\n\nIf you need to add a new dependency to the library, before you commit your\nchanges make sure you run:\n```bash\n$ glide get <DEPENDENCY>\n```\nas described in glide's documentations.\n\n## Testing\n\nBefore you make a pull request, make sure all tests are passing. Here is a handy\nsnippet using [reflex](https://github.com/cespare/reflex) to run all tests every\ntime you change your codes:\n\n```bash\n$ reflex -d none -r \"\\.go$\"  -- zsh -c \"go test ./...\"\n```\n\nIf you need a separator between each run you can run:\n\n```bash\n$ reflex -d none -r \"\\.go$\"  -- zsh -c \"go test ./... ; repeat 100 printf '#'\"\n```\n\nIt's also a good idea to run tests with `-race` flag after the final iteration\nto make sure you are not introducing any race conditions:\n\n```bash\n$ go test -race ./...\n```\n\n## Benchmarking\n\nBenchmarking is done by running:\n\n```bash\n$ go test ./... -bench=.\n```\n\n## Pull Requests\n\nMake sure each commit introduces one change at a time. This means if your\nchanges are changing a signature of a function and also adds a new feature, they\nshould be in two distinct commit. Make a new branch for your changes and make\nthe pull request based on that branch.\n\nYou can sign your commits with this command:\n```bash\n$ git commit -S\n```\n\nPlease avoid the `Signed-off by ...` clause (-s switch).\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Arsham Shirvani\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "help: ## Show help messages.\n\t@grep -E '^[0-9a-zA-Z_-]+:(.*?## .*)?$$' $(MAKEFILE_LIST) | sed 's/^Makefile://' | awk 'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n\nrun=\".\"\ndir=\"./...\"\nshort=\"-short\"\nflags=\"\"\ntimeout=40s\nbuild_tag=$(shell git describe --abbrev=0 --tags)\ncurrent_sha=$(shell git rev-parse --short HEAD)\n\nTARGET=$(shell git describe --abbrev=0 --tags)\nRELEADE_NAME=blush\nDEPLOY_FOLDER=deploy\nCHECKSUM_FILE=CHECKSUM\nMAKEFLAGS += -j1\n\n.PHONY: unit_test_watch\nunit_test_watch: ## Run unit tests in watch mode. You can set: [run, timeout, short, dir, flags]. Example: make unit_test flags=\"-race\".\n\t@echo \"running tests on $(run). waiting for changes...\"\n\t@-zsh -c \"go mod tidy; go test -trimpath --timeout=$(timeout) $(short) $(dir) -run $(run) $(flags); repeat 100 printf '#'; echo\"\n\t@reflex -d none -r \"(\\.go$$)|(go.mod)|(\\.sql$$)\" -- zsh -c \"go mod tidy; go test -trimpath --timeout=$(timeout) $(short) $(dir) -run $(run) $(flags); repeat 100 printf '#'\"\n\n.PHONY: lint\nlint: ## Run linters.\n\tgo fmt ./...\n\tgo vet ./...\n\tgolangci-lint run ./...\n\n.PHONY: ci_tests\nci_tests: ## Run tests for CI.\n\tgo test -trimpath --timeout=10m -failfast -v  -race -covermode=atomic -coverprofile=coverage.out ./...\n\n.PHONY: dependencies\ndependencies: ## Install dependencies requried for development operations.\n\t@go install github.com/cespare/reflex@latest\n\t@go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.45.2\n\t@go install github.com/psampaz/go-mod-outdated@latest\n\t@go install github.com/jondot/goweight@latest\n\t@go get -t -u golang.org/x/tools/cmd/cover\n\t@go get -t -u github.com/sonatype-nexus-community/nancy@latest\n\t@go get -u ./...\n\t@go mod tidy\n\n.PHONY: clean\nclean: ## Clean test caches and tidy up modules.\n\t@go clean -testcache\n\t@go mod tidy\n\t@rm -rf $(DEPLOY_FOLDER)\n\n.PHONY: tmpfolder\ntmpfolder: ## Create the temporary folder.\n\t@mkdir -p $(DEPLOY_FOLDER)\n\t@rm -rf $(DEPLOY_FOLDER)/$(CHECKSUM_FILE) 2> /dev/null\n\n.PHONY: linux\nlinux: tmpfolder\nlinux: ## Build for GNU/Linux.\n\t@GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags=\"-s -w -X main.version=$(build_tag) -X main.currentSha=$(current_sha)\" -o $(DEPLOY_FOLDER)/$(RELEADE_NAME) .\n\t@tar -czf $(DEPLOY_FOLDER)/blush_linux_$(TARGET).tar.gz $(DEPLOY_FOLDER)/$(RELEADE_NAME)\n\t@cd $(DEPLOY_FOLDER) ; sha256sum blush_linux_$(TARGET).tar.gz >> $(CHECKSUM_FILE)\n\t@echo \"Linux target:\" $(DEPLOY_FOLDER)/blush_linux_$(TARGET).tar.gz\n\t@rm $(DEPLOY_FOLDER)/$(RELEADE_NAME)\n\n.PHONY: darwin\ndarwin: tmpfolder\ndarwin: ## Build for Mac.\n\t@GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags=\"-s -w -X main.version=$(build_tag) -X main.currentSha=$(current_sha)\" -o $(DEPLOY_FOLDER)/$(RELEADE_NAME) .\n\t@tar -czf $(DEPLOY_FOLDER)/blush_darwin_$(TARGET).tar.gz $(DEPLOY_FOLDER)/$(RELEADE_NAME)\n\t@cd $(DEPLOY_FOLDER) ; sha256sum blush_darwin_$(TARGET).tar.gz >> $(CHECKSUM_FILE)\n\t@echo \"Darwin target:\" $(DEPLOY_FOLDER)/blush_darwin_$(TARGET).tar.gz\n\t@rm $(DEPLOY_FOLDER)/$(RELEADE_NAME)\n\n.PHONY: windows\nwindows: tmpfolder\nwindows: ## Build for windoze.\n\t@GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags=\"-s -w -X main.version=$(build_tag) -X main.currentSha=$(current_sha)\" -o $(DEPLOY_FOLDER)/$(RELEADE_NAME).exe .\n\t@zip -r $(DEPLOY_FOLDER)/blush_windows_$(TARGET).zip $(DEPLOY_FOLDER)/$(RELEADE_NAME).exe\n\t@cd $(DEPLOY_FOLDER) ; sha256sum blush_windows_$(TARGET).zip >> $(CHECKSUM_FILE)\n\t@echo \"Windows target:\" $(DEPLOY_FOLDER)/blush_windows_$(TARGET).zip\n\t@rm $(DEPLOY_FOLDER)/$(RELEADE_NAME).exe\n\n.PHONY: release\nrelease: ## Create releases for Linux, Mac, and windoze.\nrelease: linux darwin windows\n\n.PHONY: coverage\ncoverage: ## Show the test coverage on browser.\n\tgo test -covermode=count -coverprofile=coverage.out ./...\n\tgo tool cover -func=coverage.out | tail -n 1\n\tgo tool cover -html=coverage.out\n\n.PHONY: audit\naudit: ## Audit the code for updates, vulnerabilities and binary weight.\n\tgo list -u -m -json all | go-mod-outdated -update -direct\n\tgo list -json -m all | nancy sleuth\n\tgoweight | head -n 20\n"
  },
  {
    "path": "PULL_REQUEST_TEMPLATE.md",
    "content": "### Description\nPlease explain the changes you made here.\n\n### Checklist\n- [ ] Code compiles correctly\n- [ ] Created tests which fail without the change (if possible)\n- [ ] All tests passing\n- [ ] Ran tests with -race flag\n- [ ] Extended the README / documentation, if necessary\n"
  },
  {
    "path": "README.md",
    "content": "# Blush\n\n[![PkgGoDev](https://pkg.go.dev/badge/github.com/arsham/dbtools)](https://pkg.go.dev/github.com/arsham/dbtools)\n![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/arsham/dbtools)\n[![Build Status](https://github.com/arsham/dbtools/actions/workflows/go.yml/badge.svg)](https://github.com/arsham/dbtools/actions/workflows/go.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n[![Coverage Status](https://codecov.io/gh/arsham/blush/branch/master/graph/badge.svg)](https://codecov.io/gh/arsham/blush)\n[![Go Report Card](https://goreportcard.com/badge/github.com/arsham/blush)](https://goreportcard.com/report/github.com/arsham/blush)\n\nWith Blush, you can highlight matches with any colours of your choice.\n\n![1](https://user-images.githubusercontent.com/428611/164768864-e9713ac3-0097-4435-8bcb-577dbf7b9931.png)\n\n1. [Install](#install)\n2. [Usage](#usage)\n   - [Note](#note)\n   - [Normal Mode](#normal-mode)\n   - [Dropping Unmatched](#dropping-unmatched)\n   - [Piping](#piping)\n3. [Arguments](#arguments)\n   - [Notes](#notes)\n4. [Colour Groups](#colour-groups)\n5. [Colours](#colours)\n6. [Complex Grep](#complex-grep)\n7. [Suggestions](#suggestions)\n8. [License](#license)\n\n## Install\n\nYou can grab a binary from [releases](https://github.com/arsham/blush/releases)\npage. If you prefer to install it manually you can get the code and install it\nwith the following command:\n\n```bash\n$ go install github.com/arsham/blush@latest\n```\n\nMake sure you have `go>=1.18` installed.\n\n## Usage\n\nBlush can read from a file or a pipe:\n\n```bash\n$ cat FILENAME | blush -b \"print in blue\" -g \"in green\" -g \"another green\"\n$ cat FILENAME | blush \"some text\"\n$ blush -b \"print in blue\" -g \"in green\" -g \"another green\" FILENAME\n$ blush \"some text\" FILENAME\n```\n\n### Note\n\nAlthough this program has a good performance, but performance is not the main\nconcern. There are other tools you should use if you are searching in large\nfiles. Two examples:\n\n- [Ripgrep](https://github.com/BurntSushi/ripgrep)\n- [The Silver Searcher](https://github.com/ggreer/the_silver_searcher)\n\n### Normal Mode\n\nThis method shows matches with the given input:\n\n```bash\n$ blush -b \"first search\" -g \"second one\" -g \"and another one\" files/paths\n```\n\nAny occurrence of `first search` will be in blue, `second one` and `and another one`\nare in green.\n\n![2](https://user-images.githubusercontent.com/428611/164768874-bf687313-c103-449b-bb57-6fdcea51fc5d.png)\n\n### Dropping Unmatched\n\nBy default, unmatched lines are not dropped. But you can use the `-d` flag to\ndrop them:\n\n![3](https://user-images.githubusercontent.com/428611/164768875-c9aa3e47-7db0-454f-8a55-1e2bff332c69.png)\n\n## Arguments\n\n| Argument      | Shortcut | Notes                                           |\n| :------------ | :------- | :---------------------------------------------- |\n| N/A           | -i       | Case insensitive matching.                      |\n| N/A           | -R       | Recursive matching.                             |\n| --no-filename | -h       | Suppress the prefixing of file names on output. |\n| --drop        | -d       | Drop unmatched lines                            |\n\nFile names or paths are matched from the end. Any argument that doesn't match\nany files or paths are considered as regular expression. If regular expressions\nare not followed by colouring arguments are coloured based on previously\nprovided colour:\n\n```bash\n$ blush -b match1 match2 FILENAME\n```\n\n![4](https://user-images.githubusercontent.com/428611/164768879-f9b73b2c-b6bb-4cf5-a98a-e51535fa554a.png)\n\n### Notes\n\n- If no colour is provided, blush will choose blue.\n- If you only provide file/path, it will print them out without colouring.\n- If the matcher contains only alphabets and numbers, a non-regular expression is applied to search.\n\n## Colour Groups\n\nYou can provide a number for a colour argument to create a colour group:\n\n```bash\n$ blush -r1 match1 -r2 match2 -r1 match3 FILENAME\n```\n\n![5](https://user-images.githubusercontent.com/428611/164768882-5ce57477-e9d5-4170-ac10-731e9391cbee.png)\n\nAll matches will be shown as blue. But `match1` and `match3` will have a\ndifferent background colour than `match2`. This means the numbers will create\ncolour groups.\n\nYou also can provide a colour with a series of match requests:\n\n```bash\n$ blush -r match1 match3 -g match2 FILENAME\n```\n\n## Colours\n\nYou can choose a pre-defined colour, or pass it your own colour with a hash:\n\n| Argument  | Shortcut |\n| :-------- | :------- |\n| --red     | -r       |\n| --green   | -g       |\n| --blue    | -b       |\n| --white   | -w       |\n| --black   | -bl      |\n| --yellow  | -yl      |\n| --magenta | -mg      |\n| --cyan    | -cy      |\n\nYou can also pass an RGB colour. It can be in short form (--#1b2, -#1b2), or\nlong format (--#11bb22, -#11bb22).\n\n![6](https://user-images.githubusercontent.com/428611/164768883-154b4fd9-946f-43eb-b3f5-ede6027c3eda.png)\n\n## Complex Grep\n\nYou must put your complex grep into quotations:\n\n```bash\n$ blush -b \"^age: [0-9]+\" FILENAME\n```\n\n![7](https://user-images.githubusercontent.com/428611/164768886-5b94b8fa-77e2-4617-80f2-040edce18660.png)\n\n## Suggestions\n\nThis tool is made to make your experience in terminal a more pleasant. Please\nfeel free to make any suggestions or request features by creating an issue.\n\n## License\n\nUse of this source code is governed by the MIT License. License file can be\nfound in the [LICENSE](./LICENSE) file.\n"
  },
  {
    "path": "blush/benchmarks_test.go",
    "content": "package blush_test\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/arsham/blush/blush\"\n)\n\nfunc BenchmarkColourise(b *testing.B) {\n\tvar (\n\t\tshortStr = \"jNa8SZ1RPM\"\n\t\tlongStr  = strings.Repeat(\"ZL5B2kNexCcTPvf9 \", 50)\n\t)\n\n\t// nf = not found\n\t// cl = coloured\n\t// ln = long\n\tbcs := []struct {\n\t\tname   string\n\t\tinput  string\n\t\tcolour blush.Colour\n\t}{\n\t\t{\"nc\", shortStr, blush.NoColour},\n\t\t{\"fg\", shortStr, blush.Red},\n\t\t{\"bg\", shortStr, blush.Colour{Foreground: blush.NoRGB, Background: blush.FgRed}},\n\t\t{\"both cl\", shortStr, blush.Colour{Foreground: blush.FgRed, Background: blush.FgRed}},\n\t\t{\"ln nc\", longStr, blush.NoColour},\n\t\t{\"ln fg\", longStr, blush.Colour{Foreground: blush.FgRed, Background: blush.NoRGB}},\n\t\t{\"ln bg\", longStr, blush.Colour{Foreground: blush.NoRGB, Background: blush.FgRed}},\n\t\t{\"ln both cl\", longStr, blush.Colour{Foreground: blush.FgRed, Background: blush.FgRed}},\n\t}\n\tfor _, bc := range bcs {\n\t\tb.Run(bc.name, func(b *testing.B) {\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\tblush.Colourise(bc.input, bc.colour)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkNewLocator(b *testing.B) {\n\tvar l blush.Finder\n\tbcs := []struct {\n\t\tname        string\n\t\tinput       string\n\t\tinsensitive bool\n\t}{\n\t\t{\"plain\", \"aaa\", false},\n\t\t{\"asterisk\", \"*aaa\", false},\n\t\t{\"i plain\", \"aaa\", true},\n\t\t{\"i asterisk\", \"*aaa\", true},\n\t\t{\"rx empty\", \"^$\", false},\n\t\t{\"irx empty\", \"^$\", true},\n\t\t{\"rx starts with\", \"^aaa\", false},\n\t\t{\"irx starts with\", \"^aaa\", true},\n\t\t{\"rx ends with\", \"aaa$\", false},\n\t\t{\"irx ends with\", \"aaa$\", true},\n\t\t{\"rx with star\", \"blah blah.*\", false},\n\t\t{\"irx with star\", \"blah blah.*\", true},\n\t\t{\"rx with curly brackets\", \"a{3}\", false},\n\t\t{\"irx with curly brackets\", \"a{3}\", true},\n\t\t{\"rx with brackets\", \"[ab]\", false},\n\t\t{\"irx with brackets\", \"[ab]\", true},\n\t}\n\tfor _, bc := range bcs {\n\t\tb.Run(bc.name, func(b *testing.B) {\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\tl = blush.NewLocator(\"\", \"aaa\", bc.insensitive)\n\t\t\t\t_ = l\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFind(b *testing.B) {\n\tvar (\n\t\tfind         = \"FIND\"\n\t\tblob         = \"you should FIND this \\n\"\n\t\tblobLong     = strings.Repeat(blob, 50)\n\t\tnotFound     = \"YWzvnLKGyU \"\n\t\tnotFoundLong = strings.Repeat(notFound, 50)\n\t\tgot          string\n\t\tok           bool\n\t)\n\t// nf = not found\n\t// nc = no colour\n\t// cl = coloured\n\t// ln = long\n\tbcs := []struct {\n\t\tname  string\n\t\tl     blush.Finder\n\t\tinput string\n\t\tfinds bool\n\t}{\n\t\t{\"E nc\", blush.NewExact(find, blush.NoColour), blob, true},\n\t\t{\"E cl\", blush.NewExact(find, blush.Blue), blob, true},\n\t\t{\"E nf\", blush.NewExact(find, blush.NoColour), notFound, false},\n\t\t{\"E nf cl\", blush.NewExact(find, blush.Blue), notFound, false},\n\t\t{\"E ln nc\", blush.NewExact(find, blush.NoColour), blobLong, true},\n\t\t{\"E ln cl\", blush.NewExact(find, blush.Blue), blobLong, true},\n\t\t{\"E ln nf\", blush.NewExact(find, blush.NoColour), notFoundLong, false},\n\t\t{\"E ln nf cl\", blush.NewExact(find, blush.Blue), notFoundLong, false},\n\t\t{\"IE nc\", blush.NewIexact(find, blush.NoColour), blob, true},\n\t\t{\"IE cl\", blush.NewIexact(find, blush.Blue), blob, true},\n\t\t{\"IE nf\", blush.NewIexact(find, blush.NoColour), notFound, false},\n\t\t{\"IE nf cl\", blush.NewIexact(find, blush.Blue), notFound, false},\n\t\t{\"IE ln nc\", blush.NewIexact(find, blush.NoColour), blobLong, true},\n\t\t{\"IE ln cl\", blush.NewIexact(find, blush.Blue), blobLong, true},\n\t\t{\"IE ln nf\", blush.NewIexact(find, blush.NoColour), notFoundLong, false},\n\t\t{\"IE ln nf cl\", blush.NewIexact(find, blush.Blue), notFoundLong, false},\n\t\t{\"rx nc\", blush.NewRx(regexp.MustCompile(find), blush.NoColour), blob, true},\n\t\t{\"rx cl\", blush.NewRx(regexp.MustCompile(find), blush.Blue), blob, true},\n\t\t{\"rx nf\", blush.NewRx(regexp.MustCompile(find), blush.NoColour), notFound, false},\n\t\t{\"rx nf cl\", blush.NewRx(regexp.MustCompile(find), blush.Blue), notFound, false},\n\t\t{\"rx ln nc\", blush.NewRx(regexp.MustCompile(find), blush.NoColour), blobLong, true},\n\t\t{\"rx ln cl\", blush.NewRx(regexp.MustCompile(find), blush.Blue), blobLong, true},\n\t\t{\"rx ln nf\", blush.NewRx(regexp.MustCompile(find), blush.NoColour), notFoundLong, false},\n\t\t{\"rx ln nf cl\", blush.NewRx(regexp.MustCompile(find), blush.Blue), notFoundLong, false},\n\t\t{\"irx nc\", blush.NewRx(regexp.MustCompile(\"(?i)\"+find), blush.NoColour), blob, true},\n\t\t{\"irx cl\", blush.NewRx(regexp.MustCompile(\"(?i)\"+find), blush.Blue), blob, true},\n\t\t{\"irx nf\", blush.NewRx(regexp.MustCompile(\"(?i)\"+find), blush.NoColour), notFound, false},\n\t\t{\"irx nf cl\", blush.NewRx(regexp.MustCompile(\"(?i)\"+find), blush.Blue), notFound, false},\n\t\t{\"irx ln nc\", blush.NewRx(regexp.MustCompile(\"(?i)\"+find), blush.NoColour), blobLong, true},\n\t\t{\"irx ln cl\", blush.NewRx(regexp.MustCompile(\"(?i)\"+find), blush.Blue), blobLong, true},\n\t\t{\"irx ln nf\", blush.NewRx(regexp.MustCompile(\"(?i)\"+find), blush.NoColour), notFoundLong, false},\n\t\t{\"irx ln nf cl\", blush.NewRx(regexp.MustCompile(\"(?i)\"+find), blush.Blue), notFoundLong, false},\n\t}\n\tfor _, bc := range bcs {\n\t\tb.Run(bc.name, func(b *testing.B) {\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\tgot, ok = bc.l.Find(bc.input)\n\t\t\t\tif ok != bc.finds {\n\t\t\t\t\tb.Fail()\n\t\t\t\t}\n\t\t\t\t_ = got\n\t\t\t}\n\t\t})\n\t}\n}\n\n// lnr = long reader\n// ml = multi\n// rds = readers\n// mdl = middle\ntype benchCase struct {\n\tname   string\n\treader func() io.Reader\n\tmatch  []blush.Finder\n\tlength int\n}\n\nfunc BenchmarkBlush(b *testing.B) {\n\treadLarge := func() io.Reader {\n\t\tinput := bytes.Repeat([]byte(\"one two three four\\n\"), 200)\n\t\tv := make([]io.Reader, 100)\n\t\tfor i := 0; i < 100; i++ {\n\t\t\tv[i] = bytes.NewBuffer(input)\n\t\t}\n\t\treturn io.MultiReader(v...)\n\t}\n\n\treadMedium := func() io.Reader {\n\t\tinput := bytes.Repeat([]byte(\"one two three four\\n\"), 200)\n\t\treturn io.MultiReader(bytes.NewBuffer(input), bytes.NewBuffer(input))\n\t}\n\n\tmultiReader100 := func() io.Reader {\n\t\tinput := []byte(\"one two three four\\n\")\n\t\tv := make([]io.Reader, 100)\n\t\tfor i := 0; i < 100; i++ {\n\t\t\tv[i] = bytes.NewBuffer(input)\n\t\t}\n\t\treturn io.MultiReader(v...)\n\t}\n\n\treadMiddle := func() io.Reader {\n\t\tp := bytes.Repeat([]byte(\"one two three four\"), 100)\n\t\treturn bytes.NewBuffer(bytes.Join([][]byte{p, p}, []byte(\" MIDDLE \")))\n\t}\n\n\tbcs := []benchCase{\n\t\t{\n\t\t\t\"short-10\",\n\t\t\tfunc() io.Reader {\n\t\t\t\treturn bytes.NewBuffer([]byte(\"one two three four\"))\n\t\t\t},\n\t\t\t[]blush.Finder{blush.NewExact(\"three\", blush.Blue)},\n\t\t\t10,\n\t\t},\n\t\t{\n\t\t\t\"short-1000\",\n\t\t\tfunc() io.Reader {\n\t\t\t\treturn bytes.NewBuffer([]byte(\"one two three four\"))\n\t\t\t},\n\t\t\t[]blush.Finder{blush.NewExact(\"three\", blush.Blue)},\n\t\t\t1000,\n\t\t},\n\t\t{\n\t\t\t\"lnr-10\",\n\t\t\tfunc() io.Reader {\n\t\t\t\treturn bytes.NewBuffer(bytes.Repeat([]byte(\"one two three four\"), 200))\n\t\t\t},\n\t\t\t[]blush.Finder{blush.NewExact(\"three\", blush.Blue)},\n\t\t\t10,\n\t\t},\n\t\t{\n\t\t\t\"lnr-1000\",\n\t\t\tfunc() io.Reader {\n\t\t\t\treturn bytes.NewBuffer(bytes.Repeat([]byte(\"one two three four\"), 200))\n\t\t\t},\n\t\t\t[]blush.Finder{blush.NewExact(\"three\", blush.Blue)},\n\t\t\t1000,\n\t\t},\n\t\t{\n\t\t\t\"lnr ml lines-10\",\n\t\t\tfunc() io.Reader {\n\t\t\t\treturn bytes.NewBuffer(bytes.Repeat([]byte(\"one two three four\\n\"), 200))\n\t\t\t},\n\t\t\t[]blush.Finder{blush.NewExact(\"three\", blush.Blue)},\n\t\t\t10,\n\t\t},\n\t\t{\n\t\t\t\"lnr ml lines-1000\",\n\t\t\tfunc() io.Reader {\n\t\t\t\treturn bytes.NewBuffer(bytes.Repeat([]byte(\"one two three four\\n\"), 200))\n\t\t\t},\n\t\t\t[]blush.Finder{blush.NewExact(\"three\", blush.Blue)},\n\t\t\t1000,\n\t\t},\n\t\t{\n\t\t\t\"lnr in mdl-10\",\n\t\t\treadMiddle,\n\t\t\t[]blush.Finder{blush.NewExact(\"MIDDLE\", blush.Blue)},\n\t\t\t10,\n\t\t},\n\t\t{\n\t\t\t\"lnr in mdl-1000\",\n\t\t\treadMiddle,\n\t\t\t[]blush.Finder{blush.NewExact(\"MIDDLE\", blush.Blue)},\n\t\t\t1000,\n\t\t},\n\t\t{\n\t\t\t\"two rds-10\",\n\t\t\tfunc() io.Reader {\n\t\t\t\tinput := []byte(\"one two three four\\n\")\n\t\t\t\treturn io.MultiReader(bytes.NewBuffer(input), bytes.NewBuffer(input))\n\t\t\t},\n\t\t\t[]blush.Finder{blush.NewExact(\"three\", blush.Blue)},\n\t\t\t10,\n\t\t},\n\t\t{\n\t\t\t\"two rds-1000\",\n\t\t\tfunc() io.Reader {\n\t\t\t\tinput := []byte(\"one two three four\\n\")\n\t\t\t\treturn io.MultiReader(bytes.NewBuffer(input), bytes.NewBuffer(input))\n\t\t\t},\n\t\t\t[]blush.Finder{blush.NewExact(\"three\", blush.Blue)},\n\t\t\t1000,\n\t\t},\n\t\t{\n\t\t\t\"ln two rds-10\",\n\t\t\treadMedium,\n\t\t\t[]blush.Finder{blush.NewExact(\"three\", blush.Blue)},\n\t\t\t10,\n\t\t},\n\t\t{\n\t\t\t\"ln two rds-1000\",\n\t\t\treadMedium,\n\t\t\t[]blush.Finder{blush.NewExact(\"three\", blush.Blue)},\n\t\t\t1000,\n\t\t},\n\t\t{\n\t\t\t\"100 rds-10\",\n\t\t\tmultiReader100,\n\t\t\t[]blush.Finder{blush.NewExact(\"three\", blush.Blue)},\n\t\t\t10,\n\t\t},\n\t\t{\n\t\t\t\"100 rds-1000\",\n\t\t\tmultiReader100,\n\t\t\t[]blush.Finder{blush.NewExact(\"three\", blush.Blue)},\n\t\t\t1000,\n\t\t},\n\t\t{\n\t\t\t\"ln 100 rds-10\",\n\t\t\treadLarge,\n\t\t\t[]blush.Finder{blush.NewExact(\"three\", blush.Blue)},\n\t\t\t10,\n\t\t},\n\t\t{\n\t\t\t\"ln 100 rds-1000\",\n\t\t\treadLarge,\n\t\t\t[]blush.Finder{blush.NewExact(\"three\", blush.Blue)},\n\t\t\t1000,\n\t\t},\n\t}\n\tfor _, bc := range bcs {\n\t\tb.Run(\"read_\"+bc.name, func(b *testing.B) {\n\t\t\tbenchmarkRead(b, bc)\n\t\t})\n\t\tb.Run(\"writeTo_\"+bc.name, func(b *testing.B) {\n\t\t\tbenchmarkWriteTo(b, bc)\n\t\t})\n\t}\n}\n\nfunc benchmarkRead(b *testing.B, bc benchCase) {\n\tp := make([]byte, bc.length)\n\tfor i := 0; i < b.N; i++ {\n\t\tinput := io.NopCloser(bc.reader())\n\t\tbl := &blush.Blush{\n\t\t\tFinders: bc.match,\n\t\t\tReader:  input,\n\t\t}\n\t\tfor {\n\t\t\t_, err := bl.Read(p)\n\t\t\tif err != nil {\n\t\t\t\tif !errors.Is(err, io.EOF) {\n\t\t\t\t\tb.Errorf(\"err = %v\", err)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc benchmarkWriteTo(b *testing.B, bc benchCase) {\n\tfor i := 0; i < b.N; i++ {\n\t\tinput := io.NopCloser(bc.reader())\n\t\tbl := &blush.Blush{\n\t\t\tFinders: bc.match,\n\t\t\tReader:  input,\n\t\t}\n\t\tbuf := new(bytes.Buffer)\n\t\tn, err := io.Copy(buf, bl)\n\t\tif n == 0 {\n\t\t\tb.Errorf(\"b.Read(): n = 0, want some read\")\n\t\t}\n\t\tif err != nil {\n\t\t\tb.Error(err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "blush/blush.go",
    "content": "package blush\n\nimport (\n\t\"bufio\"\n\t\"io\"\n\n\t\"github.com/arsham/blush/internal/reader\"\n)\n\ntype mode int\n\nconst (\n\t// Separator string between name of the reader and the contents.\n\tSeparator = \": \"\n\n\t// DefaultLineCache is minimum lines to cache.\n\tDefaultLineCache = 50\n\n\t// DefaultCharCache is minimum characters to cache for each line. This is in\n\t// effect only if Read() function is used.\n\tDefaultCharCache = 1000\n\n\treadMode mode = iota\n\twriteToMode\n)\n\n// Blush reads from reader and matches against all finders. If NoCut is true,\n// any unmatched lines are printed as well. If WithFileName is true, blush will\n// write the filename before it writes the output. Read and WriteTo will return\n// ErrReadWriteMix if both Read and WriteTo are called on the same object. See\n// package docs for more details.\n// nolint:govet // we are expecting lots of these objects.\ntype Blush struct {\n\tFinders      []Finder\n\tReader       io.ReadCloser\n\tLineCache    uint\n\tCharCache    uint\n\tDrop         bool // do not cut out non-matched lines.\n\tWithFileName bool\n\tclosed       bool\n\treadLineCh   chan []byte\n\treadCh       chan byte\n\tmode         mode\n}\n\n// Read creates a goroutine on first invocation to read from the underlying\n// reader. It is considerably slower than WriteTo as it reads the bytes one by\n// one in order to produce the results, therefore you should use WriteTo\n// directly or use io.Copy() on blush.\nfunc (b *Blush) Read(p []byte) (n int, err error) {\n\tif b.closed {\n\t\treturn 0, ErrClosed\n\t}\n\tif b.mode == writeToMode {\n\t\treturn 0, ErrReadWriteMix\n\t}\n\tif b.mode != readMode {\n\t\terr := b.setup(readMode)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\tfor n = 0; n < cap(p); n++ {\n\t\tc, ok := <-b.readCh\n\t\tif !ok {\n\t\t\treturn n, io.EOF\n\t\t}\n\t\tp[n] = c\n\t}\n\treturn n, err\n}\n\n// WriteTo writes matches to w. It returns an error if the writer is nil or\n// there are not paths defined or there is no files found in the Reader.\nfunc (b *Blush) WriteTo(w io.Writer) (int64, error) {\n\tif b.closed {\n\t\treturn 0, ErrClosed\n\t}\n\tif b.mode == readMode {\n\t\treturn 0, ErrReadWriteMix\n\t}\n\tif b.mode != writeToMode {\n\t\tif err := b.setup(writeToMode); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\tvar total int\n\tif w == nil {\n\t\treturn 0, ErrNoWriter\n\t}\n\tfor line := range b.readLineCh {\n\t\tif n, err := w.Write(line); err != nil {\n\t\t\treturn int64(n), err\n\t\t}\n\t\ttotal += len(line)\n\t}\n\treturn int64(total), nil\n}\n\nfunc (b *Blush) setup(m mode) error {\n\tif b.Reader == nil {\n\t\treturn reader.ErrNoReader\n\t}\n\tif len(b.Finders) < 1 {\n\t\treturn ErrNoFinder\n\t}\n\n\tb.mode = m\n\tif b.LineCache == 0 {\n\t\tb.LineCache = DefaultLineCache\n\t}\n\tif b.CharCache == 0 {\n\t\tb.CharCache = DefaultCharCache\n\t}\n\tb.readLineCh = make(chan []byte, b.LineCache)\n\tb.readCh = make(chan byte, b.CharCache)\n\tgo b.readLines()\n\tif m == readMode {\n\t\tgo b.transfer()\n\t}\n\treturn nil\n}\n\nfunc (b *Blush) decorate(input string) (string, bool) {\n\tstr, ok := lookInto(b.Finders, input)\n\tif ok || !b.Drop {\n\t\tvar prefix string\n\t\tif b.WithFileName {\n\t\t\tprefix = fileName(b.Reader)\n\t\t}\n\t\treturn prefix + str, true\n\t}\n\treturn \"\", false\n}\n\nfunc (b *Blush) readLines() {\n\tvar (\n\t\tok bool\n\t\tsc = bufio.NewReader(b.Reader)\n\t)\n\tfor {\n\t\tline, err := sc.ReadString('\\n')\n\t\tif line, ok = b.decorate(line); ok {\n\t\t\tb.readLineCh <- []byte(line)\n\t\t}\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t}\n\tclose(b.readLineCh)\n}\n\nfunc (b *Blush) transfer() {\n\tfor line := range b.readLineCh {\n\t\tfor _, c := range line {\n\t\t\tb.readCh <- c\n\t\t}\n\t}\n\tclose(b.readCh)\n}\n\n// Close closes the reader and returns whatever error it returns.\nfunc (b *Blush) Close() error {\n\tb.closed = true\n\treturn b.Reader.Close()\n}\n\n// lookInto returns a new decorated line if any of the finders decorate it, or\n// the given line as it is.\nfunc lookInto(f []Finder, line string) (string, bool) {\n\tvar found bool\n\tfor _, a := range f {\n\t\tif s, ok := a.Find(line); ok {\n\t\t\tline = s\n\t\t\tfound = true\n\t\t}\n\t}\n\treturn line, found\n}\n\n// fileName returns an empty string if it could not query the fileName from r.\nfunc fileName(r io.Reader) string {\n\ttype namer interface {\n\t\tFileName() string\n\t}\n\tif o, ok := r.(namer); ok {\n\t\treturn o.FileName() + Separator\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "blush/blush_example_test.go",
    "content": "package blush_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/arsham/blush/blush\"\n)\n\nfunc ExampleBlush() {\n\tf := blush.NewExact(\"sword\", blush.Red)\n\tr := bytes.NewBufferString(\"He who lives by the sword, will surely also die\")\n\tb := &blush.Blush{\n\t\tFinders: []blush.Finder{f},\n\t\tReader:  io.NopCloser(r),\n\t}\n\tb.Close()\n}\n\nfunc ExampleBlush_Read() {\n\tvar p []byte\n\tf := blush.NewExact(\"sin\", blush.Red)\n\tr := bytes.NewBufferString(\"He who lives in sin, will surely live the lie\")\n\tb := &blush.Blush{\n\t\tFinders: []blush.Finder{f},\n\t\tReader:  io.NopCloser(r),\n\t}\n\tb.Read(p)\n}\n\nfunc ExampleBlush_Read_inDetails() {\n\tf := blush.NewExact(\"sin\", blush.Red)\n\tr := bytes.NewBufferString(\"He who lives in sin, will surely live the lie\")\n\tb := &blush.Blush{\n\t\tFinders: []blush.Finder{f},\n\t\tReader:  io.NopCloser(r),\n\t}\n\texpect := fmt.Sprintf(\"He who lives in %s, will surely live the lie\", f)\n\n\t// you should account for the additional characters for colour formatting.\n\tlength := r.Len() - len(\"sin\") + len(f.String())\n\tp := make([]byte, length)\n\tn, err := b.Read(p)\n\tfmt.Println(\"n == len(p):\", n == len(p))\n\tfmt.Println(\"err:\", err)\n\tfmt.Println(\"p == expect:\", string(p) == expect)\n\t// by the way\n\tfmt.Println(`f == \"sin\":`, f.String() == \"sin\")\n\n\t// Output:\n\t// n == len(p): true\n\t// err: <nil>\n\t// p == expect: true\n\t// f == \"sin\": false\n}\n\nfunc ExampleBlush_WriteTo() {\n\tf := blush.NewExact(\"victor\", blush.Red)\n\tr := bytes.NewBufferString(\"It is a shield of passion and strong will from this I am the victor instead of the kill\\n\")\n\tb := &blush.Blush{\n\t\tFinders: []blush.Finder{f},\n\t\tReader:  io.NopCloser(r),\n\t}\n\tbuf := new(bytes.Buffer)\n\tn, err := b.WriteTo(buf)\n\n\texpected := fmt.Sprintf(\"It is a shield of passion and strong will from this I am the %s instead of the kill\\n\", f)\n\tfmt.Println(\"err:\", err)\n\tfmt.Println(\"n == len(expected):\", int(n) == len(expected))\n\tfmt.Println(\"buf.String() == expected:\", buf.String() == expected)\n\n\t// Output:\n\t// err: <nil>\n\t// n == len(expected): true\n\t// buf.String() == expected: true\n}\n\nfunc ExampleBlush_WriteTo_copy() {\n\tf := blush.NewExact(\"you feel\", blush.Cyan)\n\tr := bytes.NewBufferString(\"Savour what you feel and what you see\\n\")\n\tb := &blush.Blush{\n\t\tFinders: []blush.Finder{f},\n\t\tReader:  io.NopCloser(r),\n\t}\n\tbuf := new(bytes.Buffer)\n\tn, err := io.Copy(buf, b)\n\n\texpected := fmt.Sprintf(\"Savour what %s and what you see\\n\", f)\n\tfmt.Println(\"err:\", err)\n\tfmt.Println(\"n == len(expected):\", int(n) == len(expected))\n\tfmt.Println(\"buf.String() == expected:\", buf.String() == expected)\n\n\t// Output:\n\t// err: <nil>\n\t// n == len(expected): true\n\t// buf.String() == expected: true\n}\n\nfunc ExampleBlush_WriteTo_multiReader() {\n\tmg := blush.NewExact(\"truth\", blush.Magenta)\n\tg := blush.NewExact(\"Life\", blush.Green)\n\tr1 := bytes.NewBufferString(\"Life is like a mystery with many clues, but with few answers\\n\")\n\tr2 := bytes.NewBufferString(\"To tell us what it is that we can do to look for messages that keep us from the truth\\n\")\n\tmr := io.MultiReader(r1, r2)\n\n\tb := &blush.Blush{\n\t\tFinders: []blush.Finder{mg, g},\n\t\tReader:  io.NopCloser(mr),\n\t}\n\tbuf := new(bytes.Buffer)\n\tb.WriteTo(buf)\n}\n\nfunc ExampleBlush_WriteTo_multiReaderInDetails() {\n\tmg := blush.NewExact(\"truth\", blush.Magenta)\n\tg := blush.NewExact(\"Life\", blush.Green)\n\tr1 := bytes.NewBufferString(\"Life is like a mystery with many clues, but with few answers\\n\")\n\tr2 := bytes.NewBufferString(\"To tell us what it is that we can do to look for messages that keep us from the truth\\n\")\n\tmr := io.MultiReader(r1, r2)\n\n\tb := &blush.Blush{\n\t\tFinders: []blush.Finder{mg, g},\n\t\tReader:  io.NopCloser(mr),\n\t}\n\tbuf := new(bytes.Buffer)\n\tn, err := b.WriteTo(buf)\n\n\tline1 := fmt.Sprintf(\"%s is like a mystery with many clues, but with few answers\\n\", g)\n\tline2 := fmt.Sprintf(\"To tell us what it is that we can do to look for messages that keep us from the %s\\n\", mg)\n\texpected := line1 + line2\n\tfmt.Println(\"err:\", err)\n\tfmt.Println(\"n == len(expected):\", int(n) == len(expected))\n\tfmt.Println(\"buf.String() == expected:\", buf.String() == expected)\n\n\t// Output:\n\t// err: <nil>\n\t// n == len(expected): true\n\t// buf.String() == expected: true\n}\n"
  },
  {
    "path": "blush/blush_test.go",
    "content": "package blush_test\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/alecthomas/assert\"\n\t\"github.com/arsham/blush/blush\"\n\t\"github.com/arsham/blush/internal/reader\"\n)\n\nfunc TestBlush(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"WriteTo\", testBlushWriteTo)\n\tt.Run(\"ClosesReader\", testBlushClosesReader)\n\tt.Run(\"Read\", testBlushRead)\n\tt.Run(\"PrintName\", testBlushPrintName)\n\tt.Run(\"StdinPrintName\", testBlushStdinPrintName)\n\tt.Run(\"PrintFilename\", testBlushPrintFilename)\n\tt.Run(\"ReadContiniously\", testBlushReadContiniously)\n\tt.Run(\"ReadMiddleOfMatch\", testBlushReadMiddleOfMatch)\n\tt.Run(\"ReadComplete\", testBlushReadComplete)\n\tt.Run(\"ReadPartComplete\", testBlushReadPartComplete)\n\tt.Run(\"PartPartOver\", testBlushReadPartPartOver)\n\tt.Run(\"ReadMultiLine\", testBlushReadMultiLine)\n\tt.Run(\"ReadWriteToMode\", testBlushReadWriteToMode)\n}\n\nfunc testBlushWriteTo(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"Errors\", testBlushWriteToErrors)\n\tt.Run(\"NoMatch\", testBlushWriteToNoMatch)\n\tt.Run(\"Match\", testBlushWriteToMatch)\n\tt.Run(\"Colour\", testBlushWriteToColour)\n\tt.Run(\"ColourNoCutMode\", testBlushWriteToColourNoCutMode)\n\tt.Run(\"MultipleMatchInOneLine\", testBlushWriteToMultipleMatchInOneLine)\n}\n\nfunc testBlushWriteToErrors(t *testing.T) {\n\tt.Parallel()\n\tw := &bytes.Buffer{}\n\te := errors.New(\"something\")\n\tnn := 10\n\tbw := &badWriter{\n\t\twriteFunc: func([]byte) (int, error) {\n\t\t\treturn nn, e\n\t\t},\n\t}\n\tgetReader := func() io.ReadCloser {\n\t\treturn io.NopCloser(bytes.NewBufferString(\"something\"))\n\t}\n\ttcs := []struct {\n\t\tname    string\n\t\tb       *blush.Blush\n\t\twriter  io.Writer\n\t\twantN   int\n\t\twantErr error\n\t}{\n\t\t{\"no input\", &blush.Blush{}, w, 0, reader.ErrNoReader},\n\t\t{\"no writer\", &blush.Blush{Reader: getReader()}, nil, 0, blush.ErrNoWriter},\n\t\t{\"bad writer\", &blush.Blush{Reader: getReader(), Drop: true}, bw, nn, e},\n\t}\n\n\tfor _, tc := range tcs {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttc.b.Finders = []blush.Finder{blush.NewExact(\"\", blush.NoColour)}\n\t\t\tn, err := tc.b.WriteTo(tc.writer)\n\t\t\tassert.Error(t, err)\n\t\t\tassert.EqualValues(t, tc.wantN, n)\n\t\t\tassert.EqualError(t, err, tc.wantErr.Error())\n\t\t})\n\t}\n}\n\nfunc testBlushWriteToNoMatch(t *testing.T) {\n\tt.Parallel()\n\tpwd, err := os.Getwd()\n\tassert.NoError(t, err)\n\tlocation := path.Join(pwd, \"testdata\")\n\tr, err := reader.NewMultiReader(reader.WithPaths([]string{location}, true))\n\tassert.NoError(t, err)\n\tb := &blush.Blush{\n\t\tReader:  r,\n\t\tFinders: []blush.Finder{blush.NewExact(\"SHOULDNOTFINDTHISONE\", blush.NoColour)},\n\t\tDrop:    true,\n\t}\n\tbuf := &bytes.Buffer{}\n\tn, err := b.WriteTo(buf)\n\tassert.NoError(t, err)\n\tassert.Zero(t, n)\n\tassert.Zero(t, buf.Len())\n}\n\nfunc testBlushWriteToMatch(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"Drop\", testBlushWriteToMatchDrop)\n\tt.Run(\"Colour\", testBlushWriteToMatchColour)\n\tt.Run(\"CountColour\", testBlushWriteToMatchCountColour)\n}\n\nfunc testBlushWriteToMatchDrop(t *testing.T) {\n\tt.Parallel()\n\tmatch := \"TOKEN\"\n\tpwd, err := os.Getwd()\n\tassert.NoError(t, err)\n\tlocation := path.Join(pwd, \"testdata\")\n\tr, err := reader.NewMultiReader(reader.WithPaths([]string{location}, true))\n\tassert.NoError(t, err)\n\n\tb := &blush.Blush{\n\t\tReader:  r,\n\t\tFinders: []blush.Finder{blush.NewExact(match, blush.NoColour)},\n\t\tDrop:    true,\n\t}\n\n\tbuf := &bytes.Buffer{}\n\tn, err := b.WriteTo(buf)\n\tassert.NoError(t, err)\n\tassert.NotZero(t, buf.Len())\n\tassert.EqualValues(t, buf.Len(), n)\n\n\tassert.Contains(t, buf.String(), match)\n\tassert.NotContains(t, buf.String(), leaveMeHere)\n}\n\nfunc testBlushWriteToMatchColour(t *testing.T) {\n\tt.Parallel()\n\tmatch := blush.Colourise(\"TOKEN\", blush.Blue)\n\tpwd, err := os.Getwd()\n\tassert.NoError(t, err)\n\tlocation := path.Join(pwd, \"testdata\")\n\tr, err := reader.NewMultiReader(reader.WithPaths([]string{location}, true))\n\tassert.NoError(t, err)\n\tb := &blush.Blush{\n\t\tReader:  r,\n\t\tFinders: []blush.Finder{blush.NewExact(\"TOKEN\", blush.Blue)},\n\t\tDrop:    true,\n\t}\n\n\tbuf := &bytes.Buffer{}\n\tn, err := b.WriteTo(buf)\n\tassert.NoError(t, err)\n\tassert.NotZero(t, buf.Len())\n\tassert.EqualValues(t, buf.Len(), n)\n\n\tassert.Contains(t, buf.String(), match)\n\tassert.NotContains(t, buf.String(), leaveMeHere)\n}\n\nfunc testBlushWriteToMatchCountColour(t *testing.T) {\n\tt.Parallel()\n\tpwd, err := os.Getwd()\n\tassert.NoError(t, err)\n\n\ttcs := []struct {\n\t\tname      string\n\t\trecursive bool\n\t\tcount     int\n\t}{\n\t\t{\"ONE\", false, 1},\n\t\t{\"ONE\", true, 3 * 1},\n\t\t{\"TWO\", false, 2},\n\t\t{\"TWO\", true, 3 * 2},\n\t\t{\"THREE\", false, 3},\n\t\t{\"THREE\", true, 3 * 3},\n\t\t{\"FOUR\", false, 4},\n\t\t{\"FOUR\", true, 3 * 4},\n\t}\n\n\tfor _, tc := range tcs {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlocation := path.Join(pwd, \"testdata\")\n\t\t\tr, err := reader.NewMultiReader(reader.WithPaths([]string{location}, tc.recursive))\n\t\t\tassert.NoError(t, err)\n\n\t\t\tmatch := blush.Colourise(tc.name, blush.Red)\n\t\t\tb := &blush.Blush{\n\t\t\t\tReader:  r,\n\t\t\t\tFinders: []blush.Finder{blush.NewExact(tc.name, blush.Red)},\n\t\t\t\tDrop:    true,\n\t\t\t}\n\n\t\t\tbuf := &bytes.Buffer{}\n\t\t\tn, err := b.WriteTo(buf)\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.EqualValues(t, buf.Len(), n)\n\t\t\tcount := strings.Count(buf.String(), match)\n\t\t\tassert.EqualValues(t, tc.count, count)\n\t\t\tassert.NotContains(t, buf.String(), leaveMeHere)\n\t\t})\n\t}\n}\n\nfunc testBlushWriteToColour(t *testing.T) {\n\tt.Parallel()\n\ttwo := blush.Colourise(\"TWO\", blush.Magenta)\n\tthree := blush.Colourise(\"THREE\", blush.Red)\n\tpwd, err := os.Getwd()\n\tassert.NoError(t, err)\n\tlocation := path.Join(pwd, \"testdata\")\n\tr, err := reader.NewMultiReader(reader.WithPaths([]string{location}, true))\n\tassert.NoError(t, err)\n\tb := &blush.Blush{\n\t\tReader: r,\n\t\tFinders: []blush.Finder{\n\t\t\tblush.NewExact(\"TWO\", blush.Magenta),\n\t\t\tblush.NewExact(\"THREE\", blush.Red),\n\t\t},\n\t\tDrop: true,\n\t}\n\n\tbuf := &bytes.Buffer{}\n\tn, err := b.WriteTo(buf)\n\tassert.NoError(t, err)\n\tassert.NotZero(t, buf.Len())\n\tassert.EqualValues(t, buf.Len(), n)\n\tcount := strings.Count(buf.String(), two)\n\tassert.EqualValues(t, 2*3, count)\n\tcount = strings.Count(buf.String(), three)\n\tassert.EqualValues(t, 3*3, count)\n\tif strings.Contains(buf.String(), leaveMeHere) {\n\t\tt.Errorf(\"didn't expect to see %s\", leaveMeHere)\n\t}\n}\n\nfunc testBlushWriteToColourNoCutMode(t *testing.T) {\n\tt.Parallel()\n\ttwo := blush.Colourise(\"TWO\", blush.Magenta)\n\tthree := blush.Colourise(\"THREE\", blush.Red)\n\tpwd, err := os.Getwd()\n\tassert.NoError(t, err)\n\tlocation := path.Join(pwd, \"testdata\")\n\tr, err := reader.NewMultiReader(reader.WithPaths([]string{location}, true))\n\tassert.NoError(t, err)\n\tb := &blush.Blush{\n\t\tReader: r,\n\t\tDrop:   false,\n\t\tFinders: []blush.Finder{\n\t\t\tblush.NewExact(\"TWO\", blush.Magenta),\n\t\t\tblush.NewExact(\"THREE\", blush.Red),\n\t\t},\n\t}\n\n\tbuf := &bytes.Buffer{}\n\tn, err := b.WriteTo(buf)\n\tassert.NoError(t, err)\n\n\tassert.NotZero(t, buf.Len())\n\tassert.EqualValues(t, buf.Len(), n)\n\tcount := strings.Count(buf.String(), two)\n\tassert.EqualValues(t, 2*3, count)\n\tcount = strings.Count(buf.String(), three)\n\tassert.EqualValues(t, 3*3, count)\n\tcount = strings.Count(buf.String(), leaveMeHere)\n\tassert.EqualValues(t, 1, count)\n}\n\nfunc testBlushWriteToMultipleMatchInOneLine(t *testing.T) {\n\tt.Parallel()\n\tline1 := \"this is an example\\n\"\n\tline2 := \"someone should find this line\\n\"\n\tinput1 := bytes.NewBuffer([]byte(line1))\n\tinput2 := bytes.NewBuffer([]byte(line2))\n\tr := io.NopCloser(io.MultiReader(input1, input2))\n\tmatch := fmt.Sprintf(\n\t\t\"someone %s find %s line\",\n\t\tblush.Colourise(\"should\", blush.Red),\n\t\tblush.Colourise(\"this\", blush.Magenta),\n\t)\n\tout := &bytes.Buffer{}\n\n\tb := &blush.Blush{\n\t\tReader: r,\n\t\tFinders: []blush.Finder{\n\t\t\tblush.NewExact(\"this\", blush.Magenta),\n\t\t\tblush.NewExact(\"should\", blush.Red),\n\t\t},\n\t}\n\n\tb.WriteTo(out)\n\tlines := strings.Split(out.String(), \"\\n\")\n\texample := lines[1]\n\tif strings.Contains(example, \"is an example\") {\n\t\texample = lines[0]\n\t}\n\tassert.EqualValues(t, match, example)\n}\n\nfunc testBlushClosesReader(t *testing.T) {\n\tt.Parallel()\n\tvar called bool\n\tinput := bytes.NewBuffer([]byte(\"DwgQnpvro5bVvrRwBB\"))\n\tw := nopCloser{\n\t\tReader: input,\n\t\tcloseFunc: func() error {\n\t\t\tcalled = true\n\t\t\treturn nil\n\t\t},\n\t}\n\tb := &blush.Blush{\n\t\tReader: w,\n\t}\n\terr := b.Close()\n\tassert.NoError(t, err)\n\tassert.True(t, called, \"didn't close the reader\")\n}\n\nfunc testBlushRead(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"OneStream\", testBlushReadOneStream)\n\tt.Run(\"TwoStreams\", testBlushReadTwoStreams)\n\tt.Run(\"HalfWay\", testBlushReadHalfWay)\n\tt.Run(\"OnClosed\", testBlushReadOnClosed)\n\tt.Run(\"LongOneLineText\", testBlushReadLongOneLineText)\n}\n\nfunc testBlushReadOneStream(t *testing.T) {\n\tt.Parallel()\n\tinput := bytes.NewBuffer([]byte(\"one two three four\"))\n\tmatch := blush.NewExact(\"three\", blush.Blue)\n\tr := io.NopCloser(input)\n\tb := &blush.Blush{\n\t\tFinders: []blush.Finder{match},\n\t\tReader:  r,\n\t}\n\tdefer b.Close()\n\temptyP := make([]byte, 10)\n\ttcs := []struct {\n\t\tname    string\n\t\tp       []byte\n\t\twantErr error\n\t\twantLen int\n\t\twantP   string\n\t}{\n\t\t{\"one\", make([]byte, len(\"one \")), nil, len(\"one \"), \"one \"},\n\t\t{\"two\", make([]byte, len(\"two \")), nil, len(\"two \"), \"two \"},\n\t\t{\"three\", make([]byte, len(match.String())), nil, len(match.String()), match.String()},\n\t\t{\"four\", make([]byte, len(\" four\")), nil, len(\" four\"), \" four\"},\n\t\t{\"empty\", emptyP, io.EOF, 0, string(emptyP)},\n\t}\n\n\tfor _, tc := range tcs {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tn, err := b.Read(tc.p)\n\t\t\tassert.True(t, errors.Is(err, tc.wantErr))\n\t\t\tassert.EqualValues(t, tc.wantLen, n)\n\t\t\tassert.EqualValues(t, tc.wantP, tc.p)\n\t\t})\n\t}\n}\n\nfunc testBlushReadTwoStreams(t *testing.T) {\n\tt.Parallel()\n\tb1 := []byte(\"one for all\\n\")\n\tb2 := []byte(\"all for one\\n\")\n\tinput1 := bytes.NewBuffer(b1)\n\tinput2 := bytes.NewBuffer(b2)\n\tmatch := blush.NewExact(\"one\", blush.Blue)\n\tr := io.NopCloser(io.MultiReader(input1, input2))\n\tb := &blush.Blush{\n\t\tFinders: []blush.Finder{match},\n\t\tReader:  r,\n\t}\n\tdefer b.Close()\n\n\tbuf := &bytes.Buffer{}\n\tn, err := buf.ReadFrom(b)\n\tassert.NoError(t, err)\n\texpectLen := len(b1) + len(b2) - len(\"one\")*2 + len(match.String())*2\n\tassert.EqualValues(t, expectLen, n)\n\texpectStr := fmt.Sprintf(\"%s%s\",\n\t\tstrings.Replace(string(b1), \"one\", match.String(), 1),\n\t\tstrings.Replace(string(b2), \"one\", match.String(), 1),\n\t)\n\tassert.EqualValues(t, expectStr, buf.String())\n}\n\nfunc testBlushReadHalfWay(t *testing.T) {\n\tt.Parallel()\n\tb1 := []byte(\"one for all\\n\")\n\tb2 := []byte(\"all for one\\n\")\n\tinput1 := bytes.NewBuffer(b1)\n\tinput2 := bytes.NewBuffer(b2)\n\tmatch := blush.NewExact(\"one\", blush.Blue)\n\tr := io.NopCloser(io.MultiReader(input1, input2))\n\tb := &blush.Blush{\n\t\tFinders: []blush.Finder{match},\n\t\tReader:  r,\n\t}\n\tp := make([]byte, len(b1))\n\t_, err := b.Read(p)\n\tassert.NoError(t, err)\n\tn, err := b.Read(p)\n\tassert.Len(t, b1, n)\n\tassert.NoError(t, err)\n}\n\nfunc testBlushReadOnClosed(t *testing.T) {\n\tt.Parallel()\n\tb1 := []byte(\"one for all\\n\")\n\tb2 := []byte(\"all for one\\n\")\n\tinput1 := bytes.NewBuffer(b1)\n\tinput2 := bytes.NewBuffer(b2)\n\tmatch := blush.NewExact(\"one\", blush.Blue)\n\tr := io.NopCloser(io.MultiReader(input1, input2))\n\tb := &blush.Blush{\n\t\tFinders: []blush.Finder{match},\n\t\tReader:  r,\n\t}\n\tp := make([]byte, len(b1))\n\t_, err := b.Read(p)\n\tassert.NoError(t, err)\n\terr = b.Close()\n\tassert.NoError(t, err)\n\n\tn, err := b.Read(p)\n\tassert.True(t, errors.Is(err, blush.ErrClosed))\n\tassert.Zero(t, n)\n}\n\nfunc testBlushReadLongOneLineText(t *testing.T) {\n\tt.Parallel()\n\thead := strings.Repeat(\"a\", 10000)\n\ttail := strings.Repeat(\"b\", 10000)\n\tinput := bytes.NewBuffer([]byte(head + \" FINDME \" + tail))\n\tmatch := blush.NewExact(\"FINDME\", blush.Blue)\n\tr := io.NopCloser(input)\n\tb := &blush.Blush{\n\t\tFinders: []blush.Finder{match},\n\t\tReader:  r,\n\t}\n\tp := make([]byte, 20)\n\t_, err := b.Read(p)\n\tassert.NoError(t, err)\n\terr = b.Close()\n\tassert.NoError(t, err)\n\tn, err := b.Read(p)\n\tassert.True(t, errors.Is(err, blush.ErrClosed))\n\tassert.Zero(t, n)\n}\n\nfunc testBlushPrintName(t *testing.T) {\n\tt.Parallel()\n\tline1 := \"line one\\n\"\n\tline2 := \"line two\\n\"\n\tr1 := io.NopCloser(bytes.NewBuffer([]byte(line1)))\n\tr2 := io.NopCloser(bytes.NewBuffer([]byte(line2)))\n\tname1 := \"reader1\"\n\tname2 := \"reader2\"\n\tr, err := reader.NewMultiReader(\n\t\treader.WithReader(name1, r1),\n\t\treader.WithReader(name2, r2),\n\t)\n\tassert.NoError(t, err)\n\tb := blush.Blush{\n\t\tReader:       r,\n\t\tFinders:      []blush.Finder{blush.NewExact(\"line\", blush.NoColour)},\n\t\tWithFileName: true,\n\t\tDrop:         true,\n\t}\n\tbuf := &bytes.Buffer{}\n\tn, err := b.WriteTo(buf)\n\tassert.NoError(t, err)\n\ttotal := len(line1+line2+name1+name2) + len(blush.Separator)*2\n\tassert.EqualValues(t, total, n)\n\n\ts := strings.Split(buf.String(), \"\\n\")\n\tassert.Contains(t, s[0], name1)\n\tassert.Contains(t, s[1], name2)\n}\n\n// testing stdin should not print the name\nfunc testBlushStdinPrintName(t *testing.T) {\n\tt.Parallel()\n\tinput := \"line one\"\n\toldStdin := os.Stdin\n\tf, err := ioutil.TempFile(t.TempDir(), \"blush_stdin\")\n\tassert.NoError(t, err)\n\tdefer func() {\n\t\tos.Stdin = oldStdin\n\t}()\n\tos.Stdin = f\n\tf.WriteString(input)\n\tf.Seek(0, 0)\n\tb := blush.Blush{\n\t\tReader:       f,\n\t\tFinders:      []blush.Finder{blush.NewExact(\"line\", blush.NoColour)},\n\t\tWithFileName: true,\n\t}\n\tbuf := &bytes.Buffer{}\n\t_, err = b.WriteTo(buf)\n\tassert.NoError(t, err)\n\n\tassert.EqualValues(t, input, buf.String())\n\tassert.NotContains(t, buf.String(), f.Name())\n}\n\nfunc testBlushPrintFilename(t *testing.T) {\n\tt.Parallel()\n\tp := t.TempDir()\n\tf1, err := ioutil.TempFile(p, \"blush_name\")\n\tassert.NoError(t, err)\n\tf2, err := ioutil.TempFile(p, \"blush_name\")\n\tassert.NoError(t, err)\n\n\tline1 := \"line one\\n\"\n\tline2 := \"line two\\n\"\n\tf1.WriteString(line1)\n\tf2.WriteString(line2)\n\ttcs := []struct {\n\t\tname          string\n\t\twithFilename  bool\n\t\twantLen       int\n\t\twantFilenames bool\n\t}{\n\t\t{\"with filename\", true, len(line1+line2+f1.Name()+f2.Name()) + len(blush.Separator)*2, true},\n\t\t{\"without filename\", false, len(line1 + line2), false},\n\t}\n\tfor _, tc := range tcs {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tr, err := reader.NewMultiReader(\n\t\t\t\treader.WithPaths([]string{p}, false),\n\t\t\t)\n\t\t\tassert.NoError(t, err)\n\t\t\tb := blush.Blush{\n\t\t\t\tReader:       r,\n\t\t\t\tFinders:      []blush.Finder{blush.NewExact(\"line\", blush.NoColour)},\n\t\t\t\tWithFileName: tc.withFilename,\n\t\t\t\tDrop:         true,\n\t\t\t}\n\t\t\tbuf := &bytes.Buffer{}\n\t\t\tn, err := b.WriteTo(buf)\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.EqualValues(t, tc.wantLen, n)\n\t\t\tnotStr := \"not\"\n\t\t\tif tc.wantFilenames {\n\t\t\t\tnotStr = \"\"\n\t\t\t}\n\t\t\tif strings.Contains(buf.String(), f1.Name()) != tc.wantFilenames {\n\t\t\t\tt.Errorf(\"want `%s` %s in `%s`\", f1.Name(), notStr, buf.String())\n\t\t\t}\n\t\t\tif strings.Contains(buf.String(), f2.Name()) != tc.wantFilenames {\n\t\t\t\tt.Errorf(\"want `%s` %s in `%s`\", f2.Name(), notStr, buf.String())\n\t\t\t}\n\t\t})\n\t}\n}\n\n// reading with a small byte slice until the read is done.\nfunc testBlushReadContiniously(t *testing.T) {\n\tt.Parallel()\n\tvar (\n\t\tret   []byte\n\t\tp     = make([]byte, 2)\n\t\tcount int\n\t\tinput = \"one two three four\\nfive six three seven\"\n\t)\n\tmatch := blush.NewExact(\"three\", blush.Blue)\n\tr := io.NopCloser(bytes.NewBufferString(input))\n\tb := &blush.Blush{\n\t\tFinders: []blush.Finder{match},\n\t\tReader:  r,\n\t}\n\tfor {\n\t\tif count > len(input) { // more that required\n\t\t\tt.Errorf(\"didn't finish after %d reads: len = %d\", count, len(input))\n\t\t\tbreak\n\t\t}\n\t\tcount++\n\t\t_, err := b.Read(p)\n\t\tif errors.Is(err, io.EOF) {\n\t\t\tret = append(ret, p...)\n\t\t\tbreak\n\t\t}\n\t\tassert.NoError(t, err)\n\t\tret = append(ret, p...)\n\t}\n\tif c := strings.Count(string(ret), \"three\"); c != 2 {\n\t\tt.Errorf(\"count %s = %d, want %d\", \"three\", c, 2)\n\t}\n\tfor _, s := range []string{\"one\", \"two\", \"three\", \"four\", \"five\", \"six\", \"seven\"} {\n\t\tassert.Contains(t, string(ret), s)\n\t}\n}\n\nfunc testBlushReadMiddleOfMatch(t *testing.T) {\n\tt.Parallel()\n\tvar (\n\t\tsearch = \"aa this aa\"\n\t\tmatch  = blush.NewExact(\"this\", blush.Blue)\n\t\tp      = make([]byte, (len(search)+len(match.String()))/2)\n\t\tret    []byte\n\t)\n\tr := io.NopCloser(bytes.NewBufferString(search))\n\tb := &blush.Blush{\n\t\tFinders: []blush.Finder{match},\n\t\tReader:  r,\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\t_, err := b.Read(p)\n\t\tif err != nil && !errors.Is(err, io.EOF) {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tret = append(ret, p...)\n\t}\n\tassert.Contains(t, string(ret), \"this\")\n}\n\nfunc testBlushReadComplete(t *testing.T) {\n\tt.Parallel()\n\tinput := \"123456789\"\n\tmatch := blush.NewExact(\"1\", blush.NoColour)\n\tr := io.NopCloser(bytes.NewBufferString(input))\n\tb := &blush.Blush{\n\t\tFinders: []blush.Finder{match},\n\t\tReader:  r,\n\t}\n\tp := make([]byte, 10)\n\tn, err := b.Read(p)\n\tassert.Len(t, input, n)\n\tassert.True(t, errors.Is(err, io.EOF))\n\tassert.EqualValues(t, input, string(bytes.Trim(p, \"\\x00\")))\n\tp = make([]byte, 4)\n\tn, err = b.Read(p)\n\tassert.Zero(t, n)\n\tassert.True(t, errors.Is(err, io.EOF))\n\tassert.Empty(t, string(bytes.Trim(p, \"\\x00\")))\n}\n\nfunc testBlushReadPartComplete(t *testing.T) {\n\tt.Parallel()\n\tinput := \"123456789\"\n\tmatch := blush.NewExact(\"1\", blush.NoColour)\n\tr := io.NopCloser(bytes.NewBufferString(input))\n\tb := &blush.Blush{\n\t\tFinders: []blush.Finder{match},\n\t\tReader:  r,\n\t}\n\tp := make([]byte, 3)\n\tn, err := b.Read(p)\n\tassert.EqualValues(t, 3, n)\n\tassert.NoError(t, err)\n\tassert.EqualValues(t, string(bytes.Trim(p, \"\\x00\")), \"123\")\n\n\tp = make([]byte, 6)\n\tn, err = b.Read(p)\n\tassert.NoError(t, err)\n\tassert.EqualValues(t, 6, n)\n\tassert.EqualValues(t, string(bytes.Trim(p, \"\\x00\")), \"456789\")\n}\n\nfunc testBlushReadPartPartOver(t *testing.T) {\n\tt.Parallel()\n\tinput := \"123456789\"\n\tmatch := blush.NewExact(\"1\", blush.NoColour)\n\tr := io.NopCloser(bytes.NewBufferString(input))\n\tb := &blush.Blush{\n\t\tFinders: []blush.Finder{match},\n\t\tReader:  r,\n\t}\n\tp := make([]byte, 3)\n\tn, err := b.Read(p)\n\tassert.NoError(t, err)\n\tassert.EqualValues(t, 3, n)\n\tassert.EqualValues(t, string(bytes.Trim(p, \"\\x00\")), \"123\")\n\n\tp = make([]byte, 3)\n\tn, err = b.Read(p)\n\tassert.EqualValues(t, 3, n)\n\tassert.NoError(t, err)\n\tassert.EqualValues(t, string(bytes.Trim(p, \"\\x00\")), \"456\")\n\n\tp = make([]byte, 10)\n\tn, err = b.Read(p)\n\tassert.EqualValues(t, 3, n)\n\tassert.True(t, errors.Is(err, io.EOF))\n\tassert.EqualValues(t, string(bytes.Trim(p, \"\\x00\")), \"789\")\n}\n\nfunc testBlushReadMultiLine(t *testing.T) {\n\tt.Parallel()\n\tinput := \"line1\\nline2\\nline3\\nline4\\n\"\n\tmatch := blush.NewExact(\"l\", blush.NoColour)\n\tr := io.NopCloser(bytes.NewBufferString(input))\n\tb := &blush.Blush{\n\t\tFinders: []blush.Finder{match},\n\t\tReader:  r,\n\t}\n\n\ttcs := []struct {\n\t\tname    string\n\t\tlength  int\n\t\twant    string\n\t\twantLen int\n\t\twantErr error\n\t}{\n\t\t{\"line1\", 5, \"line1\", 5, nil},\n\t\t{\"\\nli\", 3, \"\\nli\", 3, nil},\n\t\t{\"ne2\\nline\", 8, \"ne2\\nline\", 8, nil},\n\t\t{\"3\\nline4\", 7, \"3\\nline4\", 7, nil},\n\t\t{\"\\n\", 1, \"\\n\", 1, nil},\n\t\t{\"finish\", 10, \"\", 0, io.EOF},\n\t}\n\tfor _, tc := range tcs {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tp := make([]byte, tc.length)\n\t\t\tn, err := b.Read(p)\n\t\t\tassert.True(t, errors.Is(err, tc.wantErr))\n\t\t\tassert.EqualValues(t, tc.wantLen, n)\n\t\t\tassert.EqualValues(t, tc.want, string(bytes.Trim(p, \"\\x00\")))\n\t\t})\n\t}\n}\n\nfunc testBlushReadWriteToMode(t *testing.T) {\n\tt.Parallel()\n\tp := make([]byte, 1)\n\tr := io.NopCloser(bytes.NewBufferString(\"input\"))\n\tb := &blush.Blush{\n\t\tFinders: []blush.Finder{blush.NewExact(\"\", blush.NoColour)},\n\t\tReader:  r,\n\t}\n\t_, err := b.Read(p)\n\tassert.NoError(t, err)\n\t_, err = b.WriteTo(&bytes.Buffer{})\n\tassert.True(t, errors.Is(err, blush.ErrReadWriteMix))\n\n\tb = &blush.Blush{\n\t\tFinders: []blush.Finder{blush.NewExact(\"\", blush.NoColour)},\n\t\tReader:  r,\n\t}\n\t_, err = b.WriteTo(&bytes.Buffer{})\n\tassert.NoError(t, err)\n\n\t_, err = b.Read(p)\n\tassert.True(t, errors.Is(err, blush.ErrReadWriteMix))\n}\n"
  },
  {
    "path": "blush/colour.go",
    "content": "package blush\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// BgLevel is the colour value of R, G, or B when the colour is shown in the\n// background.\nconst BgLevel = 70\n\n// These are colour settings. NoRGB results in no colouring in the terminal.\nvar (\n\tNoRGB     = RGB{-1, -1, -1}\n\tFgRed     = RGB{255, 0, 0}\n\tFgBlue    = RGB{0, 0, 255}\n\tFgGreen   = RGB{0, 255, 0}\n\tFgBlack   = RGB{0, 0, 0}\n\tFgWhite   = RGB{255, 255, 255}\n\tFgCyan    = RGB{0, 255, 255}\n\tFgMagenta = RGB{255, 0, 255}\n\tFgYellow  = RGB{255, 255, 0}\n\tBgRed     = RGB{BgLevel, 0, 0}\n\tBgBlue    = RGB{0, 0, BgLevel}\n\tBgGreen   = RGB{0, BgLevel, 0}\n\tBgBlack   = RGB{0, 0, 0}\n\tBgWhite   = RGB{BgLevel, BgLevel, BgLevel}\n\tBgCyan    = RGB{0, BgLevel, BgLevel}\n\tBgMagenta = RGB{BgLevel, 0, BgLevel}\n\tBgYellow  = RGB{BgLevel, BgLevel, 0}\n)\n\n// Some stock colours. There will be no colouring when NoColour is used.\nvar (\n\tNoColour = Colour{NoRGB, NoRGB}\n\tRed      = Colour{FgRed, NoRGB}\n\tBlue     = Colour{FgBlue, NoRGB}\n\tGreen    = Colour{FgGreen, NoRGB}\n\tBlack    = Colour{FgBlack, NoRGB}\n\tWhite    = Colour{FgWhite, NoRGB}\n\tCyan     = Colour{FgCyan, NoRGB}\n\tMagenta  = Colour{FgMagenta, NoRGB}\n\tYellow   = Colour{FgYellow, NoRGB}\n)\n\n// DefaultColour is the default colour if no colour is set via arguments.\nvar DefaultColour = Blue\n\n// RGB represents colours that can be printed in terminals. R, G and B should be\n// between 0 and 255.\ntype RGB struct {\n\tR, G, B int\n}\n\n// Colour is a pair of RGB colours for foreground and background.\ntype Colour struct {\n\tForeground RGB\n\tBackground RGB\n}\n\n// Colourise wraps the input between colours.\nfunc Colourise(input string, c Colour) string {\n\tif c.Background == NoRGB && c.Foreground == NoRGB {\n\t\treturn input\n\t}\n\n\tvar fg, bg string\n\tif c.Foreground != NoRGB {\n\t\tfg = foreground(c.Foreground)\n\t}\n\tif c.Background != NoRGB {\n\t\tbg = background(c.Background)\n\t}\n\treturn fg + bg + input + unformat()\n}\n\nfunc foreground(c RGB) string {\n\treturn fmt.Sprintf(\"\\033[38;5;%dm\", colour(c.R, c.G, c.B))\n}\n\nfunc background(c RGB) string {\n\treturn fmt.Sprintf(\"\\033[48;5;%dm\", colour(c.R, c.G, c.B))\n}\n\nfunc unformat() string {\n\treturn \"\\033[0m\"\n}\n\nfunc colour(red, green, blue int) int {\n\treturn 16 + baseColor(red, 36) + baseColor(green, 6) + baseColor(blue, 1)\n}\n\nfunc baseColor(value, factor int) int {\n\treturn int(6*float64(value)/256) * factor\n}\n\nfunc colorFromArg(colour string) Colour {\n\tif strings.HasPrefix(colour, \"#\") {\n\t\treturn hexColour(colour)\n\t}\n\tif grouping.MatchString(colour) {\n\t\tif c := colourGroup(colour); c != NoColour {\n\t\t\treturn c\n\t\t}\n\t}\n\treturn stockColour(colour)\n}\n\nfunc colourGroup(colour string) Colour {\n\tg := grouping.FindStringSubmatch(colour)\n\tgroup, err := strconv.Atoi(g[2])\n\tif err != nil {\n\t\treturn NoColour\n\t}\n\tc := stockColour(g[1])\n\tswitch group % 8 {\n\tcase 0:\n\t\tc.Background = BgRed\n\tcase 1:\n\t\tc.Background = BgBlue\n\tcase 2:\n\t\tc.Background = BgGreen\n\tcase 3:\n\t\tc.Background = BgBlack\n\tcase 4:\n\t\tc.Background = BgWhite\n\tcase 5:\n\t\tc.Background = BgCyan\n\tcase 6:\n\t\tc.Background = BgMagenta\n\tcase 7:\n\t\tc.Background = BgYellow\n\t}\n\treturn c\n}\n\nfunc stockColour(colour string) Colour {\n\tc := DefaultColour\n\tswitch colour {\n\tcase \"r\", \"red\":\n\t\tc = Red\n\tcase \"b\", \"blue\":\n\t\tc = Blue\n\tcase \"g\", \"green\":\n\t\tc = Green\n\tcase \"bl\", \"black\":\n\t\tc = Black\n\tcase \"w\", \"white\":\n\t\tc = White\n\tcase \"cy\", \"cyan\":\n\t\tc = Cyan\n\tcase \"mg\", \"magenta\":\n\t\tc = Magenta\n\tcase \"yl\", \"yellow\":\n\t\tc = Yellow\n\tcase \"no-colour\", \"no-color\": // nolint:misspell // it's ok.\n\t\tc = NoColour\n\t}\n\treturn c\n}\n\nfunc hexColour(colour string) Colour {\n\tvar r, g, b int\n\tcolour = strings.TrimPrefix(colour, \"#\")\n\tswitch len(colour) {\n\tcase 3:\n\t\tc := strings.Split(colour, \"\")\n\t\tr = getInt(c[0] + c[0])\n\t\tg = getInt(c[1] + c[1])\n\t\tb = getInt(c[2] + c[2])\n\tcase 6:\n\t\tc := strings.Split(colour, \"\")\n\t\tr = getInt(c[0] + c[1])\n\t\tg = getInt(c[2] + c[3])\n\t\tb = getInt(c[4] + c[5])\n\tdefault:\n\t\treturn DefaultColour\n\t}\n\tfor _, n := range []int{r, g, b} {\n\t\tif n < 0 {\n\t\t\treturn DefaultColour\n\t\t}\n\t}\n\treturn Colour{RGB{R: r, G: g, B: b}, NoRGB}\n}\n\n// getInt returns a number between 0-255 from a hex code. If the hex is not\n// between 00 and ff, it returns -1.\nfunc getInt(hex string) int {\n\td, err := strconv.ParseInt(\"0x\"+hex, 0, 64)\n\tif err != nil || d > 255 || d < 0 {\n\t\treturn -99\n\t}\n\treturn int(d)\n}\n"
  },
  {
    "path": "blush/colour_test.go",
    "content": "package blush_test\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/alecthomas/assert\"\n\t\"github.com/arsham/blush/blush\"\n)\n\nfunc TestColourise(t *testing.T) {\n\tt.Parallel()\n\tinput := \"nswkgjTusmxWoiZLhZOGBG\"\n\tgot := blush.Colourise(input, blush.NoColour)\n\tassert.Contains(t, input, got)\n\tassert.NotContains(t, \"[38;\", got)\n\tassert.NotContains(t, \"[48;\", got)\n\tassert.NotContains(t, \"\\033[0m\", got)\n\n\tc := blush.Colour{\n\t\tForeground: blush.FgGreen,\n\t\tBackground: blush.FgRed,\n\t}\n\tgot = blush.Colourise(input, c)\n\tassert.Contains(t, got, input)\n\tassert.Contains(t, got, \"[38;\")\n\tassert.Contains(t, got, \"[48;\")\n\tassert.EqualValues(t, 1, strings.Count(got, \"\\033[0m\"))\n\n\tc = blush.Colour{\n\t\tForeground: blush.NoRGB,\n\t\tBackground: blush.FgRed,\n\t}\n\tgot = blush.Colourise(input, c)\n\tassert.Contains(t, got, input)\n\tassert.NotContains(t, got, \"[38;\")\n\tassert.Contains(t, got, \"[48;\")\n\tassert.EqualValues(t, 1, strings.Count(got, \"\\033[0m\"))\n}\n"
  },
  {
    "path": "blush/doc.go",
    "content": "// Package blush reads from a given io.Reader line by line and looks for\n// patterns.\n//\n// Blush struct has a Reader property which can be Stdin in case of it being\n// shell's pipe, or any type that implements io.ReadCloser. If NoCut is set to\n// true, it will show all lines despite being not matched. You cannot call\n// Read() and WriteTo() on the same object. Blush will return ErrReadWriteMix on\n// the second consequent call. The first time Read/WriteTo is called, it will\n// start a goroutine and reads up to LineCache lines from Reader. If the Read()\n// is in use, it starts a goroutine that reads up to CharCache bytes from the\n// line cache and fills up the given buffer.\n//\n// The hex number should be in 3 or 6 part format (#aaaaaa or #aaa) and each\n// part will be translated to a number value between 0 and 255 when creating the\n// Colour instance. If any of hex parts are not between 00 and ff, it creates\n// the DefaultColour value.\n//\n// Important Notes\n//\n// The Read() method could be slow in case of huge inspections. It is\n// recommended to avoid it and use WriteTo() instead; io.Copy() can take care of\n// that for you.\n//\n// When WriteTo() is called with an unavailable or un-writeable writer, there\n// will be no further checks until it tries to write into it. If the Write\n// encounters any errors regarding writes, it will return the amount if writes\n// and stops its search.\n//\n// There always will be a newline after each read.\npackage blush\n"
  },
  {
    "path": "blush/errors.go",
    "content": "package blush\n\nimport \"errors\"\n\nvar (\n\t// ErrNoWriter is returned if a nil object is passed to the WriteTo method.\n\tErrNoWriter = errors.New(\"no writer defined\")\n\n\t// ErrNoFinder is returned if there is no finder passed to Blush.\n\tErrNoFinder = errors.New(\"no finders defined\")\n\n\t// ErrClosed is returned if the reader is closed and you try to read from\n\t// it.\n\tErrClosed = errors.New(\"reader already closed\")\n\n\t// ErrReadWriteMix is returned when the Read and WriteTo are called on the\n\t// same object.\n\tErrReadWriteMix = errors.New(\"you cannot mix Read and WriteTo calls\")\n)\n"
  },
  {
    "path": "blush/find.go",
    "content": "package blush\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n)\n\nvar (\n\tisRegExp = regexp.MustCompile(`[\\^\\$.\\{\\}\\[\\]\\*\\?]`)\n\t// This is used for matching colour groups (b1, etc.).\n\tgrouping = regexp.MustCompile(`^([[:alpha:]]+)(\\d+)$`)\n)\n\n// Finder finds texts based on a plain text or regexp logic. If it doesn't find\n// any match, it will return an empty string. It might decorate the match with a\n// given instruction.\ntype Finder interface {\n\tFind(string) (string, bool)\n}\n\n// NewLocator returns a Rx object if search is a valid regexp, otherwise it\n// returns Exact or Iexact. If insensitive is true, the match will be case\n// insensitive. The colour argument can be in short form (b) or long form\n// (blue). If it cannot find the colour, it will fall-back to DefaultColour. The\n// colour also can be in hex format, which should be started with a pound sign\n// (#666).\nfunc NewLocator(colour, search string, insensitive bool) Finder {\n\tc := colorFromArg(colour)\n\tif !isRegExp.MatchString(search) {\n\t\tif insensitive {\n\t\t\treturn NewIexact(search, c)\n\t\t}\n\t\treturn NewExact(search, c)\n\t}\n\n\tdecore := fmt.Sprintf(\"(%s)\", search)\n\tif insensitive {\n\t\tdecore = fmt.Sprintf(\"(?i)%s\", decore)\n\t\tif o, err := regexp.Compile(decore); err == nil {\n\t\t\treturn NewRx(o, c)\n\t\t}\n\t\treturn NewIexact(search, c)\n\t}\n\n\tif o, err := regexp.Compile(decore); err == nil {\n\t\treturn NewRx(o, c)\n\t}\n\treturn NewExact(search, c)\n}\n\n// Exact looks for the exact word in the string.\ntype Exact struct {\n\ts      string\n\tcolour Colour\n}\n\n// NewExact returns a new instance of the Exact.\nfunc NewExact(s string, c Colour) Exact {\n\treturn Exact{\n\t\ts:      s,\n\t\tcolour: c,\n\t}\n}\n\n// Find looks for the exact string. Any strings it finds will be decorated with\n// the given Colour.\nfunc (e Exact) Find(input string) (string, bool) {\n\tif strings.Contains(input, e.s) {\n\t\treturn e.colourise(input, e.colour), true\n\t}\n\treturn \"\", false\n}\n\nfunc (e Exact) colourise(input string, c Colour) string {\n\tif c == NoColour {\n\t\treturn input\n\t}\n\treturn strings.ReplaceAll(input, e.s, Colourise(e.s, c))\n}\n\n// Colour returns the Colour property.\nfunc (e Exact) Colour() Colour {\n\treturn e.colour\n}\n\n// String will returned the colourised contents.\nfunc (e Exact) String() string {\n\treturn e.colourise(e.s, e.colour)\n}\n\n// Iexact is like Exact but case insensitive.\ntype Iexact struct {\n\ts      string\n\tcolour Colour\n}\n\n// NewIexact returns a new instance of the Iexact.\nfunc NewIexact(s string, c Colour) Iexact {\n\treturn Iexact{\n\t\ts:      s,\n\t\tcolour: c,\n\t}\n}\n\n// Find looks for the exact string. Any strings it finds will be decorated with\n// the given Colour.\nfunc (i Iexact) Find(input string) (string, bool) {\n\tif strings.Contains(strings.ToLower(input), strings.ToLower(i.s)) {\n\t\treturn i.colourise(input, i.colour), true\n\t}\n\treturn \"\", false\n}\n\nfunc (i Iexact) colourise(input string, c Colour) string {\n\tif c == NoColour {\n\t\treturn input\n\t}\n\tindex := strings.Index(strings.ToLower(input), strings.ToLower(i.s))\n\tend := len(i.s) + index\n\tmatch := input[index:end]\n\treturn strings.ReplaceAll(input, match, Colourise(match, c))\n}\n\n// Colour returns the Colour property.\nfunc (i Iexact) Colour() Colour {\n\treturn i.colour\n}\n\n// String will returned the colourised contents.\nfunc (i Iexact) String() string {\n\treturn i.colourise(i.s, i.colour)\n}\n\n// Rx is the regexp implementation of the Locator.\ntype Rx struct {\n\t*regexp.Regexp\n\tcolour Colour\n}\n\n// NewRx returns a new instance of the Rx.\nfunc NewRx(r *regexp.Regexp, c Colour) Rx {\n\treturn Rx{\n\t\tRegexp: r,\n\t\tcolour: c,\n\t}\n}\n\n// Find looks for the string matching `r` regular expression. Any strings it\n// finds will be decorated with the given Colour.\nfunc (r Rx) Find(input string) (string, bool) {\n\tif r.MatchString(input) {\n\t\treturn r.colourise(input, r.colour), true\n\t}\n\treturn \"\", false\n}\n\nfunc (r Rx) colourise(input string, c Colour) string {\n\tif c == NoColour {\n\t\treturn input\n\t}\n\treturn r.ReplaceAllString(input, Colourise(\"$1\", c))\n}\n\n// Colour returns the Colour property.\nfunc (r Rx) Colour() Colour {\n\treturn r.colour\n}\n"
  },
  {
    "path": "blush/find_test.go",
    "content": "package blush_test\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/alecthomas/assert\"\n\t\"github.com/arsham/blush/blush\"\n)\n\ntype colourer interface {\n\tColour() blush.Colour\n}\n\n// nolint:misspell // it's ok.\nfunc TestNewLocatorColours(t *testing.T) {\n\tt.Parallel()\n\ttcs := []struct {\n\t\tname   string\n\t\tcolour string\n\t\twant   blush.Colour\n\t}{\n\t\t{\"default\", \"\", blush.DefaultColour},\n\t\t{\"garbage\", \"sdsds\", blush.DefaultColour},\n\t\t{\"blue\", \"blue\", blush.Blue},\n\t\t{\"blue short\", \"b\", blush.Blue},\n\t\t{\"red\", \"red\", blush.Red},\n\t\t{\"red short\", \"r\", blush.Red},\n\t\t{\"blue\", \"blue\", blush.Blue},\n\t\t{\"blue short\", \"b\", blush.Blue},\n\t\t{\"green\", \"green\", blush.Green},\n\t\t{\"green short\", \"g\", blush.Green},\n\t\t{\"black\", \"black\", blush.Black},\n\t\t{\"black short\", \"bl\", blush.Black},\n\t\t{\"white\", \"white\", blush.White},\n\t\t{\"white short\", \"w\", blush.White},\n\t\t{\"cyan\", \"cyan\", blush.Cyan},\n\t\t{\"cyan short\", \"cy\", blush.Cyan},\n\t\t{\"magenta\", \"magenta\", blush.Magenta},\n\t\t{\"magenta short\", \"mg\", blush.Magenta},\n\t\t{\"yellow\", \"yellow\", blush.Yellow},\n\t\t{\"yellow short\", \"yl\", blush.Yellow},\n\t\t{\"no colour\", \"no-colour\", blush.NoColour},\n\t\t{\"no colour american\", \"no-color\", blush.NoColour},\n\n\t\t{\"hash 000\", \"#000\", blush.Colour{Foreground: blush.RGB{R: 0, G: 0, B: 0}, Background: blush.NoRGB}},\n\t\t{\"hash 666\", \"#666\", blush.Colour{Foreground: blush.RGB{R: 102, G: 102, B: 102}, Background: blush.NoRGB}},\n\t\t{\"hash 000000\", \"#000000\", blush.Colour{Foreground: blush.RGB{R: 0, G: 0, B: 0}, Background: blush.NoRGB}},\n\t\t{\"hash 666666\", \"#666666\", blush.Colour{Foreground: blush.RGB{R: 102, G: 102, B: 102}, Background: blush.NoRGB}},\n\t\t{\"hash FFF\", \"#FFF\", blush.Colour{Foreground: blush.RGB{R: 255, G: 255, B: 255}, Background: blush.NoRGB}},\n\t\t{\"hash fff\", \"#fff\", blush.Colour{Foreground: blush.RGB{R: 255, G: 255, B: 255}, Background: blush.NoRGB}},\n\t\t{\"hash ffffff\", \"#ffffff\", blush.Colour{Foreground: blush.RGB{R: 255, G: 255, B: 255}, Background: blush.NoRGB}},\n\t\t{\"hash ababAB\", \"#ababAB\", blush.Colour{Foreground: blush.RGB{R: 171, G: 171, B: 171}, Background: blush.NoRGB}},\n\t\t{\"hash hhhhhh\", \"#hhhhhh\", blush.DefaultColour},\n\t\t{\"hash aaaaaaa\", \"#aaaaaaa\", blush.DefaultColour},\n\t}\n\tfor _, tc := range tcs {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tl := blush.NewLocator(tc.colour, \"aaa\", false)\n\t\t\tc, ok := l.(colourer)\n\t\t\tassert.True(t, ok)\n\t\t\tassert.Equal(t, tc.want, c.Colour())\n\t\t})\n\t}\n}\n\nfunc TestNewLocatorExact(t *testing.T) {\n\tt.Parallel()\n\tl := blush.NewLocator(\"\", \"aaa\", false)\n\tassert.IsType(t, blush.Exact{}, l)\n\tl = blush.NewLocator(\"\", \"*aaa\", false)\n\tassert.IsType(t, blush.Exact{}, l)\n}\n\nfunc TestNewLocatorIexact(t *testing.T) {\n\tt.Parallel()\n\tl := blush.NewLocator(\"\", \"aaa\", true)\n\tassert.IsType(t, blush.Iexact{}, l)\n\tl = blush.NewLocator(\"\", \"*aaa\", true)\n\tassert.IsType(t, blush.Iexact{}, l)\n}\n\nfunc TestNewLocatorRx(t *testing.T) {\n\tt.Parallel()\n\ttcs := []struct {\n\t\tname    string\n\t\tinput   string\n\t\tmatches []string\n\t}{\n\t\t{\"empty\", \"^$\", []string{\"\"}},\n\t\t{\"starts with\", \"^aaa\", []string{\"aaa\", \"aaa sss\"}},\n\t\t{\"ends with\", \"aaa$\", []string{\"aaa\", \"sss aaa\"}},\n\t\t{\"with star\", \"blah blah.*\", []string{\"blah blah\", \"aa blah blah aa\"}},\n\t\t{\"with curly brackets\", \"a{3}\", []string{\"aaa\", \"aa aaa aa\"}},\n\t\t{\"with brackets\", \"[ab]\", []string{\"kjhadf\", \"kjlrbrlkj\", \"sdbsdha\"}},\n\t}\n\n\tfor _, tc := range tcs {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tl := blush.NewLocator(\"\", tc.input, false)\n\t\t\tassert.IsType(t, blush.Rx{}, l)\n\t\t\tl = blush.NewLocator(\"\", tc.input, false)\n\t\t\tassert.IsType(t, blush.Rx{}, l)\n\t\t})\n\t}\n}\n\nfunc TestNewLocatorRxColours(t *testing.T) {\n\tt.Parallel()\n\trx := blush.NewLocator(\"b\", \"a{3}\", false)\n\twant := \"this \" + blush.Colourise(\"aaa\", blush.Blue) + \"meeting\"\n\tgot, ok := rx.Find(\"this aaameeting\")\n\tassert.Equal(t, want, got)\n\tassert.True(t, ok)\n}\n\nfunc TestExactNotFound(t *testing.T) {\n\tt.Parallel()\n\tl := blush.NewExact(\"nooooo\", blush.NoColour)\n\tgot, ok := l.Find(\"yessss\")\n\tassert.Empty(t, got)\n\tassert.False(t, ok)\n}\n\nfunc TestExactFind(t *testing.T) {\n\tt.Parallel()\n\tl := blush.NewExact(\"nooooo\", blush.NoColour)\n\tgot, ok := l.Find(\"yessss\")\n\tassert.Empty(t, got)\n\tassert.False(t, ok)\n\n\ttcs := []struct {\n\t\tname   string\n\t\tsearch string\n\t\tcolour blush.Colour\n\t\tinput  string\n\t\twant   string\n\t\twantOk bool\n\t}{\n\t\t{\"exact no colour\", \"aaa\", blush.NoColour, \"aaa\", \"aaa\", true},\n\t\t{\"exact not found\", \"aaaa\", blush.NoColour, \"aaa\", \"\", false},\n\t\t{\"some parts no colour\", \"aaa\", blush.NoColour, \"bb aaa bb\", \"bb aaa bb\", true},\n\t\t{\"exact blue\", \"aaa\", blush.Blue, \"aaa\", blush.Colourise(\"aaa\", blush.Blue), true},\n\t\t{\"some parts blue\", \"aaa\", blush.Blue, \"bb aaa bb\", \"bb \" + blush.Colourise(\"aaa\", blush.Blue) + \" bb\", true},\n\t}\n\tfor _, tc := range tcs {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tl := blush.NewExact(tc.search, tc.colour)\n\t\t\tgot, ok := l.Find(tc.input)\n\t\t\tassert.Equal(t, tc.want, got)\n\t\t\tassert.Equal(t, tc.wantOk, ok)\n\t\t})\n\t}\n}\n\nfunc TestRxNotFound(t *testing.T) {\n\tt.Parallel()\n\tl := blush.NewRx(regexp.MustCompile(\"no{5}\"), blush.NoColour)\n\tgot, ok := l.Find(\"yessss\")\n\tassert.Empty(t, got)\n\tassert.False(t, ok)\n}\n\nfunc TestRxFind(t *testing.T) {\n\tt.Parallel()\n\tl := blush.NewRx(regexp.MustCompile(\"no{5}\"), blush.NoColour)\n\tgot, ok := l.Find(\"yessss\")\n\tassert.Empty(t, got)\n\tassert.False(t, ok)\n\n\ttcs := []struct {\n\t\tname   string\n\t\tsearch string\n\t\tcolour blush.Colour\n\t\tinput  string\n\t\twant   string\n\t\twantOk bool\n\t}{\n\t\t{\"exact no colour\", \"(^aaa$)\", blush.NoColour, \"aaa\", \"aaa\", true},\n\t\t{\"exact not found\", \"(^aa$)\", blush.NoColour, \"aaa\", \"\", false},\n\t\t{\"some parts no colour\", \"(aaa)\", blush.NoColour, \"bb aaa bb\", \"bb aaa bb\", true},\n\t\t{\"some parts not matched\", \"(Aaa)\", blush.NoColour, \"bb aaa bb\", \"\", false},\n\t\t{\"exact blue\", \"(aaa)\", blush.Blue, \"aaa\", blush.Colourise(\"aaa\", blush.Blue), true},\n\t\t{\"some parts blue\", \"(aaa)\", blush.Blue, \"bb aaa bb\", \"bb \" + blush.Colourise(\"aaa\", blush.Blue) + \" bb\", true},\n\t}\n\tfor _, tc := range tcs {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tl := blush.NewRx(regexp.MustCompile(tc.search), tc.colour)\n\t\t\tgot, ok := l.Find(tc.input)\n\t\t\tassert.Equal(t, tc.want, got)\n\t\t\tassert.Equal(t, tc.wantOk, ok)\n\t\t\tassert.Equal(t, tc.colour, l.Colour())\n\t\t})\n\t}\n}\n\nfunc TestIexactNotFound(t *testing.T) {\n\tt.Parallel()\n\tl := blush.NewIexact(\"nooooo\", blush.NoColour)\n\tgot, ok := l.Find(\"yessss\")\n\tassert.Empty(t, got)\n\tassert.False(t, ok)\n}\n\nfunc TestIexact(t *testing.T) {\n\tt.Parallel()\n\ttcs := []struct {\n\t\tname   string\n\t\tsearch string\n\t\tcolour blush.Colour\n\t\tinput  string\n\t\twant   string\n\t\twantOk bool\n\t}{\n\t\t{\"exact no colour\", \"aaa\", blush.NoColour, \"aaa\", \"aaa\", true},\n\t\t{\"exact not found\", \"aaaa\", blush.NoColour, \"aaa\", \"\", false},\n\t\t{\"i exact no colour\", \"AAA\", blush.NoColour, \"aaa\", \"aaa\", true},\n\t\t{\"some parts no colour\", \"aaa\", blush.NoColour, \"bb aaa bb\", \"bb aaa bb\", true},\n\t\t{\"i some parts no colour\", \"AAA\", blush.NoColour, \"bb aaa bb\", \"bb aaa bb\", true},\n\t\t{\"exact blue\", \"aaa\", blush.Blue, \"aaa\", blush.Colourise(\"aaa\", blush.Blue), true},\n\t\t{\"i exact blue\", \"AAA\", blush.Blue, \"aaa\", blush.Colourise(\"aaa\", blush.Blue), true},\n\t\t{\"some parts blue\", \"aaa\", blush.Blue, \"bb aaa bb\", \"bb \" + blush.Colourise(\"aaa\", blush.Blue) + \" bb\", true},\n\t\t{\"i some parts blue\", \"AAA\", blush.Blue, \"bb aaa bb\", \"bb \" + blush.Colourise(\"aaa\", blush.Blue) + \" bb\", true},\n\t}\n\tfor _, tc := range tcs {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tl := blush.NewIexact(tc.search, tc.colour)\n\t\t\tgot, ok := l.Find(tc.input)\n\t\t\tassert.Equal(t, tc.want, got)\n\t\t\tassert.Equal(t, tc.wantOk, ok)\n\t\t\tassert.Equal(t, tc.colour, l.Colour())\n\t\t})\n\t}\n}\n\nfunc TestRxInsensitiveFind(t *testing.T) {\n\tt.Parallel()\n\ttcs := []struct {\n\t\tname   string\n\t\tsearch string\n\t\tcolour string\n\t\tinput  string\n\t\twant   string\n\t\twantOk bool\n\t}{\n\t\t{\"exact no colour\", \"^AAA$\", \"no-colour\", \"aaa\", \"aaa\", true},\n\t\t{\"exact not found\", \"^AA$\", \"no-colour\", \"aaa\", \"\", false},\n\t\t{\"some words no colour\", `AAA*`, \"no-colour\", \"bb aaa bb\", \"bb aaa bb\", true},\n\t\t{\"exact blue\", \"^AAA$\", \"b\", \"aaa\", blush.Colourise(\"aaa\", blush.Blue), true},\n\t\t{\"default colour\", \"^AAA$\", \"\", \"aaa\", blush.Colourise(\"aaa\", blush.DefaultColour), true},\n\t\t{\"some words blue\", \"AAA?\", \"b\", \"bb aaa bb\", \"bb \" + blush.Colourise(\"aaa\", blush.Blue) + \" bb\", true},\n\t\t{\"some words blue long\", \"AAA?\", \"blue\", \"bb aaa bb\", \"bb \" + blush.Colourise(\"aaa\", blush.Blue) + \" bb\", true},\n\t}\n\tfor _, tc := range tcs {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tl := blush.NewLocator(tc.colour, tc.search, true)\n\t\t\tassert.IsType(t, blush.Rx{}, l)\n\n\t\t\tgot, ok := l.Find(tc.input)\n\t\t\tassert.Equal(t, tc.want, got)\n\t\t\tassert.Equal(t, tc.wantOk, ok)\n\t\t})\n\t}\n\n\trx := blush.NewLocator(\"b\", \"A{3}\", true)\n\twant := \"this \" + blush.Colourise(\"aaa\", blush.Blue) + \"meeting\"\n\tgot, ok := rx.Find(\"this aaameeting\")\n\tassert.Equal(t, want, got)\n\tassert.True(t, ok)\n}\n\nfunc TestColourGroup(t *testing.T) {\n\tt.Parallel()\n\tfor i := 0; i < 100; i++ {\n\t\tl := blush.NewLocator(fmt.Sprintf(\"b%d\", i), \"aaa\", false)\n\t\te, ok := l.(blush.Exact)\n\t\tassert.True(t, ok)\n\t\tassert.Equal(t, blush.FgBlue, e.Colour().Foreground)\n\t\tassert.NotEqual(t, blush.NoRGB, e.Colour().Background)\n\t\tassert.NotEqual(t, e.Colour().Background, e.Colour().Foreground)\n\t}\n}\n\nfunc TestColourNewGroup(t *testing.T) {\n\tt.Parallel()\n\tl := blush.NewLocator(\"b1\", \"aaa\", false)\n\tassert.IsType(t, blush.Exact{}, l)\n\n\te := l.(blush.Exact)\n\tc1 := e.Colour()\n\tl = blush.NewLocator(\"b1\", \"aaa\", false)\n\te = l.(blush.Exact)\n\tassert.EqualValues(t, c1, e.Colour())\n\n\tl = blush.NewLocator(\"b2\", \"aaa\", false)\n\te = l.(blush.Exact)\n\tassert.Equal(t, blush.FgBlue, e.Colour().Foreground)\n\tassert.NotEqual(t, c1, e.Colour())\n\tassert.NotEqual(t, c1.Background, e.Colour().Background)\n}\n"
  },
  {
    "path": "blush/helper_test.go",
    "content": "package blush_test\n\nimport \"io\"\n\n// this file contains helpers for all tests in this package.\n\n// In the testdata folder, there are three files. In each file there are 1 ONE,\n// 2 TWO, 3 THREE and 4 FOURs. There is a line containing `LEAVEMEHERE` which\n// does not collide with any of these numbers.\n\nvar leaveMeHere = \"LEAVEMEHERE\"\n\ntype nopCloser struct {\n\tio.Reader\n\tcloseFunc func() error\n}\n\nfunc (n nopCloser) Close() error { return n.closeFunc() }\n\ntype badWriter struct {\n\twriteFunc func([]byte) (int, error)\n}\n\nfunc (b *badWriter) Write(p []byte) (int, error) { return b.writeFunc(p) }\n"
  },
  {
    "path": "blush/testdata/d1/f1.txt",
    "content": "d1-f1 IgjZC oSlYrhXTWOHEp TOKEN-A dAThrGHHYVfYQxyCDAVm ONE\nd1-f1 jqwvUPYQhouXPCk hHcxLvqporTHREEpepOVOpy TOKEN-B IeCbNDvXef\nd1-f1 MqZyWlTHREEIFkdnrLvxC lRzgfxESRWV TOKEN-A IHI TOKEN-B AJCXcRBoTPuvYJh\nd1-f1 MqZyWIFkdnrLvxC lRzgfxTWOYESRWV TOKEN-A IHI TOKEN-B AJCXcRBoTPuvYJh\nd1-f1  JVKePHMQqe FOUR voIDKRbIrRpwIX FdOrOtFGkIEIlrQjp seWBh FLyaHCgJNLapx\nd1-f1 UfFesVYPkInOGiCCDCDQop BOroDtUFOUR IqPKvmgQh cqvrdXgJDV QHyz txkaVWY rWLxIkNjWguH\nd1-f1 hAQtogSFYXhDeWjWY AJPLeQuXjSoTxTTrUYP YKjoFOUR Tj\nd1-f1 TfCEmfp lMXZJdFmTHREEZPdS HlfI Ex excepteur laborum eu irure in amet exercitation\nd1-f1 cupidatat minim anim ut occaecat culpa non nostrud incididunt proident idFOUR.\nd1-f1 LEAVEMEHERE\n"
  },
  {
    "path": "blush/testdata/d1/f2.txt",
    "content": "d1-f2 lIgjZC oSlYrhXHEp TOKEN-A dAThrGHHYVfYQxyCDAVm\nd1-f2 ljqwvUPYQhouXPCk hHcxLvqporpepOVOpy TOKEN-B IeCbNDvXef\nd1-f2 lMqZyWlIFkdnrLvxC lRzgfxYESRWV TOKEN-A IHI TOKEN-B AJCXcRBoTPuvYJh\nd1-f2 Et enim amet dolor ONE occaecat minim ut dolore magna pariatur dolor cupidatat est\nd1-f2 ullamco eiusmod laborum do esseFOUR mollit in ex excepteur veniam amet eu excepteur\nd1-f2 mollit cupidatat voluptate tempor qui exercitation mollit sit incididunt incididunt aliquip ad\nd1-f2 labore proident magna duis aute dolor irure esse eu THREEsunt nisi anim id occaecat\nd1-f2 ad qui in ea do aliquaTWO tempor tempor ut id eu do ad aliqua dolor ut non ex in excepteur do dolore\nd1-f2 enim exercitation irure dolor ea deserunt deserunt in commodo laboris aliqua labore duis laboris cillum in nisi ut consectetur duis\nd1-f2 commodo culpa cillum ea dolore ad cillum voluptate dolore pariatur consequat non\nd1-f2 dolore cillum labore dolore sed adipisicing pariatur officia est laborum cillum dolore in exercitation\nd1-f2 ut veniam dolor ut proident eu ea id cupidatat laboris idTHREE pariatur consequat ea exercitation in dolor pariatur in\nd1-f2 elit consectetur pariatur labore nostrud commodo aliquip ullamco pariatur ea mollit quis deserunt consectetur reprehenderit adipisicing qui\nd1-f2 ex ut officia nulla adipiTWOsicing anim aute adipisicing deserunt cillum est mollFOURit incididunt FOURveniam eu ut ut non\nd1-f2 eiusmod eu reprehenderit iTHREErureFOUR mollit sit veniam dolore non dolor do ea\nd1-f2 laborum culpa laboris occaecat ut fugiat cillum officia est nostrud in tempor\nd1-f2 sed fugiat sed sunt excepteur officia labore velit laborum commodo aliqua elit\nd1-f2 nisi mollit laboris laboris voluptate commodo id excepteur.\n"
  },
  {
    "path": "blush/testdata/f1.txt",
    "content": "f1 IgjZC oSlYrhXHEp TOKEN-A dAThrGHHYVfYQxyCDAVm\nf1 jqwvUPYQhouXPCk hHcxLvqporpepOVOpy TOKEN-B IeCbNDvXef\nf1 MqZyWlIFkdnrLvxC lRzgfxYESRWV TOKEN-A IHI TOKEN-B AJCXcRBoTPuvYJh\nf1 Sed et sint est irTWOure irure commodo magna vol  uptate ex excepteur\nf1 Sed et sint est irure irure commodo magna vol THREE uptate ex excepteur\nf1 ex sit magn FOURa quis nisi amet officia veniam aliquip do adipisicing eiusmod.\nf1 Ut exercitationONE proident duis ea ut culpa ea occaecat ex cupidatat minim officia pariatur eu in\nf1 cillum consequat incididunt deserunt magna in cupidatat eiusmod ut\nf1 esse aTHREEmet esse nisi cupidatat velit id non excepteur aTHREEnim.\nf1 Esse excepteur sed in ex ad in laboris nulla culpa exercitation qui culpa quis\nf1 esse parFOURiatur ametTWO fugiat ut nullaFOUR consectetur officia id non\nf1 deserunt reprehenderit sed eu sit ullamco ea aliquip.\nf1 FOUR\n"
  },
  {
    "path": "cmd/args.go",
    "content": "package cmd\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/arsham/blush/blush\"\n)\n\n// Note that hasArgs, setFinders and setPaths methods of args are designed to\n// shrink the input as they go. Therefore the order of calls matters in some\n// cases.\ntype args struct {\n\tpaths       []string\n\tmatches     []string\n\tremaining   []string\n\tfinders     []blush.Finder\n\tcut         bool\n\tnoFilename  bool\n\trecursive   bool\n\tinsensitive bool\n\tstdin       bool\n}\n\n// nolint:misspell // it's ok.\nfunc newArgs(input ...string) (*args, error) {\n\ta := &args{\n\t\tmatches:   make([]string, 0),\n\t\tremaining: input,\n\t}\n\tif a.hasArgs(\"--help\") {\n\t\treturn nil, errShowHelp\n\t}\n\ta.recursive = a.hasArgs(\"-R\")\n\ta.cut = a.hasArgs(\"-d\", \"--drop\")\n\ta.noFilename = a.hasArgs(\"-h\", \"--no-filename\")\n\ta.insensitive = a.hasArgs(\"-i\")\n\n\tif stat, _ := os.Stdin.Stat(); (stat.Mode() & os.ModeCharDevice) == 0 {\n\t\ta.stdin = true\n\t} else if err := a.setPaths(); err != nil {\n\t\treturn nil, err\n\t}\n\ta.setFinders()\n\treturn a, nil\n}\n\n// hasArgs removes any occurring `args` argument.\nfunc (a *args) hasArgs(args ...string) (found bool) {\n\tremains := a.remaining\nLOOP:\n\tfor _, arg := range args {\n\t\tfor i, ar := range a.remaining {\n\t\t\tif ar == arg {\n\t\t\t\tremains = append(remains[:i], remains[i+1:]...)\n\t\t\t\tfound = true\n\t\t\t\tif len(remains) == 0 {\n\t\t\t\t\tbreak LOOP\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\ta.remaining = remains\n\treturn found\n}\n\n// setPaths starts from the end of the slice and removes any paths/globs/files\n// it finds and put them in the paths property.\nfunc (a *args) setPaths() error {\n\tvar (\n\t\tfoundOne bool\n\t\tcounter  int\n\t\tp, ret   []string\n\t\tinput    = a.remaining\n\t)\n\t// going backwards from the end.\n\tinput = flip(input)\n\n\t// I don't like this label, but if we replace the `switch` statement with a\n\t// regular if-then-else clause, it gets ugly and doesn't show its\n\t// intentions. Order of cases in this switch matters.\nLOOP:\n\tfor i, t := range input {\n\t\tt = strings.Trim(t, \" \")\n\t\tif t == \"\" || inStringSlice(t, p) {\n\t\t\tcontinue\n\t\t}\n\n\t\tm, err := filepath.Glob(t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tswitch {\n\t\tcase len(input) > i+1 && strings.HasPrefix(input[i+1], \"-\"):\n\t\t\t// In this case, the previous input was a flag argument, therefore\n\t\t\t// it might have been a colouring command. That is why we are\n\t\t\t// ignoring this item.\n\t\t\tret = append(ret, input[i:]...)\n\t\t\tbreak LOOP\n\t\tcase len(m) > 0:\n\t\t\tfoundOne = true\n\t\t\tp = append(p, t)\n\t\t\tcounter++\n\t\tcase foundOne:\n\t\t\t// there is already a pattern found so we stop here.\n\t\t\tret = append(ret, input[i:]...)\n\t\t\tbreak LOOP\n\t\t}\n\t}\n\tif !foundOne {\n\t\treturn ErrNoFilesFound\n\t}\n\n\t// to return back in the same order.\n\tret = flip(ret)\n\t// to keep the original user's preference.\n\tp = flip(p)\n\ta.remaining = ret\n\ta.paths = p\n\treturn nil\n}\n\nfunc (a *args) setFinders() {\n\tvar lastColour string\n\ta.finders = make([]blush.Finder, 0)\n\tfor _, token := range a.remaining {\n\t\tif strings.HasPrefix(token, \"-\") {\n\t\t\tlastColour = strings.TrimLeft(token, \"-\")\n\t\t\tcontinue\n\t\t}\n\t\tl := blush.NewLocator(lastColour, token, a.insensitive)\n\t\ta.finders = append(a.finders, l)\n\t}\n}\n\nfunc flip(s []string) []string {\n\tret := make([]string, len(s))\n\tmax := len(s) - 1\n\tfor i, v := range s {\n\t\tj := max - i\n\t\tret[j] = v\n\t}\n\treturn ret\n}\n\nfunc inStringSlice(s string, haystack []string) bool {\n\tfor _, a := range haystack {\n\t\tif a == s {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "cmd/args_test.go",
    "content": "package cmd\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"path\"\n\t\"testing\"\n\n\t\"github.com/alecthomas/assert\"\n)\n\nfunc TestArgs(t *testing.T) {\n\ttcs := []struct {\n\t\tname        string\n\t\tinput       []string\n\t\twantErr     error\n\t\tcut         bool\n\t\tnoFilename  bool\n\t\trecursive   bool\n\t\tinsensitive bool\n\t}{\n\t\t{name: \"help\", input: []string{\"--help\"}, wantErr: errShowHelp},\n\t\t{name: \"drop\", input: []string{\"--drop\"}, cut: true},\n\t\t{name: \"drop short\", input: []string{\"-d\"}, cut: true},\n\t\t{name: \"no filename\", input: []string{\"-h\"}, noFilename: true},\n\t\t{name: \"no filename long\", input: []string{\"--no-filename\"}, noFilename: true},\n\t\t{name: \"recursive\", input: []string{\"-R\"}, recursive: true},\n\t\t{name: \"ins\", input: []string{\"-i\"}, insensitive: true},\n\t\t{name: \"ins rec\", input: []string{\"-i\", \"-R\"}, insensitive: true, recursive: true},\n\t\t{name: \"rec ins\", input: []string{\"-R\", \"-i\"}, insensitive: true, recursive: true},\n\t\t{\n\t\t\tname: \"rec ins nofile\", input: []string{\"-R\", \"-i\", \"-h\"},\n\t\t\tinsensitive: true, recursive: true, noFilename: true,\n\t\t},\n\t\t{\n\t\t\tname: \"nofile rec ins\", input: []string{\"-h\", \"-R\", \"-i\"},\n\t\t\tinsensitive: true, recursive: true, noFilename: true,\n\t\t},\n\t\t{\n\t\t\tname: \"nofile rec ins drop\", input: []string{\"-h\", \"-R\", \"-i\", \"-d\"},\n\t\t\tinsensitive: true, recursive: true, noFilename: true, cut: true,\n\t\t},\n\t}\n\n\tfor _, tc := range tcs {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttc.input = append(tc.input, \"*\")\n\t\t\ta, err := newArgs(tc.input...)\n\t\t\tif tc.wantErr != nil {\n\t\t\t\tassert.True(t, errors.Is(err, tc.wantErr))\n\t\t\t\tassert.Nil(t, a)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.NotNil(t, a)\n\t\t\tassert.Equal(t, tc.cut, a.cut)\n\t\t\tassert.Equal(t, tc.noFilename, a.noFilename)\n\t\t\tassert.Equal(t, tc.recursive, a.recursive)\n\t\t\tassert.Equal(t, tc.insensitive, a.insensitive)\n\t\t})\n\t}\n}\n\nfunc TestArgsPipe(t *testing.T) {\n\tgetPipe(t)\n\ta, err := newArgs(\"something\")\n\tassert.NoError(t, err)\n\tassert.NotNil(t, a)\n\tassert.True(t, a.stdin)\n}\n\nfunc TestArgsPaths(t *testing.T) {\n\tdir := t.TempDir()\n\tf1, err := ioutil.TempFile(dir, \"main\")\n\tassert.NoError(t, err)\n\tf2, err := ioutil.TempFile(dir, \"main\")\n\tassert.NoError(t, err)\n\n\ttcs := []struct {\n\t\tname      string\n\t\tinput     []string\n\t\twantPaths []string\n\t\twantErr   bool\n\t}{\n\t\t{\"not found\", []string{\"nowhere\"}, []string{}, true},\n\t\t{\"only a file\", []string{f1.Name()}, []string{f1.Name()}, false},\n\t\t{\"two files\", []string{f1.Name(), f2.Name()}, []string{f1.Name(), f2.Name()}, false},\n\t\t{\"arg between two files\", []string{f1.Name(), \"-a\", f2.Name()}, []string{}, true},\n\t\t{\"prefix file\", []string{\"a\", f1.Name()}, []string{f1.Name()}, false},\n\t\t{\"prefix arg file\", []string{\"-r\", f1.Name()}, []string{}, true},\n\t\t{\"file matches but is an argument\", []string{\"-r\", f1.Name(), f2.Name()}, []string{f2.Name()}, false},\n\t\t{\"star dir\", []string{path.Join(dir, \"*\")}, []string{path.Join(dir, \"*\")}, false},\n\t\t{\"stared dir\", []string{dir + \"*\"}, []string{dir + \"*\"}, false},\n\t\t{\n\t\t\t\"many prefixes\",\n\t\t\t[]string{\"--#7ff\", \"main\", \"-g\", \"package\", \"-r\", \"a\", path.Join(dir, \"*\")},\n\t\t\t[]string{path.Join(dir, \"*\")},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"many prefixes star\",\n\t\t\t[]string{\"--#7ff\", \"main\", \"-g\", \"package\", \"-r\", \"a\", dir + \"*\"},\n\t\t\t[]string{dir + \"*\"},\n\t\t\tfalse,\n\t\t},\n\t}\n\tfor _, tc := range tcs {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ta, err := newArgs(tc.input...)\n\t\t\tif tc.wantErr {\n\t\t\t\tassert.Error(t, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tassert.NotNil(t, a)\n\t\t\tassert.True(t, stringSliceEq(a.paths, tc.wantPaths))\n\t\t})\n\t}\n}\n\nfunc TestArgsHasArgs(t *testing.T) {\n\ttcs := []struct {\n\t\tinput  []string\n\t\targs   []string\n\t\twant   []string\n\t\twantOk bool\n\t}{\n\t\t{[]string{}, []string{\"\"}, []string{}, false},\n\t\t{[]string{}, []string{\"-a\"}, []string{}, false},\n\t\t{[]string{}, []string{\"-a\", \"-a\"}, []string{}, false},\n\t\t{[]string{\"a\"}, []string{\"-a\"}, []string{\"a\"}, false},\n\t\t{[]string{\"a\"}, []string{\"-a\", \"-a\"}, []string{\"a\"}, false},\n\t\t{[]string{\"-a\"}, []string{\"-a\"}, []string{}, true},\n\t\t{[]string{\"-a\"}, []string{\"-a\", \"-a\"}, []string{}, true},\n\t\t{[]string{\"-a\", \"-b\"}, []string{\"-a\"}, []string{\"-b\"}, true},\n\t\t{[]string{\"-a\", \"-c\", \"-b\"}, []string{\"-c\"}, []string{\"-a\", \"-b\"}, true},\n\t\t{[]string{\"-a\", \"-c\", \"-b\"}, []string{\"-d\"}, []string{\"-a\", \"-c\", \"-b\"}, false},\n\t}\n\tfor i, tc := range tcs {\n\t\ttc := tc\n\t\tt.Run(fmt.Sprintf(\"case_%d\", i), func(t *testing.T) {\n\t\t\tgetPipe(t)\n\t\t\ta, err := newArgs(tc.input...)\n\t\t\tassert.NoError(t, err)\n\t\t\tok := a.hasArgs(tc.args...)\n\t\t\tassert.True(t, stringSliceEq(a.remaining, tc.want))\n\t\t\tassert.EqualValues(t, tc.wantOk, ok)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/doc.go",
    "content": "// Package cmd bootstraps the application.\n//\n// Main() reads the provided arguments from the command line and creates a\n// blush.Blush instance. If there is any error, it will terminate the\n// application with os.Exit(1), otherwise it then uses io.Copy() to write to\n// standard output and exits normally.\n//\n// GetBlush() returns an error if no arguments are provided or it can't find all\n// the passed files. Files should be last arguments, otherwise they are counted\n// as matching strings. If there is no file passed, the input should come in\n// from Stdin as a pipe.\n//\n// hasArgs(args ...string) function looks for args in input and if it finds it,\n// it removes it and put the rest in the remaining slice.\n//\n// Notes\n//\n// We are not using the usual flag package because it cannot handle variables in\n// the args and continues grouping of passed arguments.\npackage cmd\n"
  },
  {
    "path": "cmd/errors.go",
    "content": "package cmd\n\nimport \"errors\"\n\nvar (\n\t// ErrNoInput is returned when the application doesn't receive any files as the\n\t// last arguments or a stream of inputs from shell's pipe.\n\tErrNoInput = errors.New(\"no input provided\")\n\n\t// ErrNoFilesFound is returned when the files pattern passed to the application\n\t// doesn't match any existing files.\n\tErrNoFilesFound = errors.New(\"no files found\")\n)\n"
  },
  {
    "path": "cmd/helper_test.go",
    "content": "package cmd_test\n\nimport (\n\t\"bytes\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/alecthomas/assert\"\n\t\"github.com/arsham/blush/blush\"\n)\n\nvar leaveMeHere = \"LEAVEMEHERE\"\n\ntype stdFile struct {\n\tf *os.File\n}\n\nfunc (s *stdFile) String() string {\n\ts.f.Seek(0, 0)\n\tbuf := new(bytes.Buffer)\n\tbuf.ReadFrom(s.f)\n\treturn buf.String()\n}\n\nfunc (s *stdFile) Close() error {\n\treturn s.f.Close()\n}\n\nfunc newStdFile(t *testing.T, name string) *stdFile {\n\tt.Helper()\n\tf, err := ioutil.TempFile(t.TempDir(), name)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tsf := &stdFile{f}\n\tt.Cleanup(func() {\n\t\tf.Close()\n\t})\n\treturn sf\n}\n\nfunc setup(t *testing.T, args string) (stdout, stderr *stdFile) {\n\tt.Helper()\n\toldArgs := os.Args\n\toldStdout := os.Stdout\n\toldStderr := os.Stderr\n\n\tstdout = newStdFile(t, \"stdout\")\n\tstderr = newStdFile(t, \"stderr\")\n\tos.Stdout = stdout.f\n\tos.Stderr = stderr.f\n\n\tos.Args = []string{\"blush\"}\n\tif len(args) > 1 {\n\t\tos.Args = append(os.Args, strings.Split(args, \" \")...)\n\t}\n\n\tt.Cleanup(func() {\n\t\tos.Args = oldArgs\n\t\tos.Stdout = oldStdout\n\t\tos.Stderr = oldStderr\n\t})\n\treturn stdout, stderr\n}\n\nfunc getPipe(t *testing.T) *os.File {\n\tt.Helper()\n\tfile, err := ioutil.TempFile(t.TempDir(), \"blush_pipe\")\n\tassert.NoError(t, err)\n\tname := file.Name()\n\tfile.Close()\n\n\tfile, err = os.OpenFile(name, os.O_CREATE|os.O_RDWR, os.ModeCharDevice|os.ModeDevice)\n\tassert.NoError(t, err)\n\treturn file\n}\n\nfunc argsEqual(a, b []blush.Finder) bool {\n\tisIn := func(a blush.Finder, haystack []blush.Finder) bool {\n\t\tfor _, b := range haystack {\n\t\t\tif reflect.DeepEqual(a, b) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\n\tfor _, item := range b {\n\t\tif !isIn(item, a) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "cmd/main.go",
    "content": "package cmd\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/arsham/blush/blush\"\n\t\"github.com/arsham/blush/internal/reader\"\n)\n\n// Main reads the provided arguments from the command line and creates a\n// blush.Blush instance.\nfunc Main() {\n\tb, err := GetBlush(os.Args)\n\tif errors.Is(err, errShowHelp) {\n\t\tfmt.Println(Usage)\n\t\treturn\n\t}\n\tif err != nil {\n\t\tlog.Fatalf(\"%s\\n%s\", err, Help)\n\t\treturn // this return statement should be here to support tests.\n\t}\n\tdefer func() {\n\t\tif err := b.Close(); err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}()\n\tsig := make(chan os.Signal, 1)\n\tWaitForSignal(sig, os.Exit)\n\tif _, err := io.Copy(os.Stdout, b); err != nil {\n\t\tlog.Print(err)\n\t}\n}\n\n// GetBlush returns an error if no arguments are provided or it can't find all\n// the passed files in the input.\n//\n// Note\n//\n// The first argument will be dropped as it will be the application's name.\nfunc GetBlush(input []string) (*blush.Blush, error) {\n\tvar (\n\t\tr   io.ReadCloser = os.Stdin\n\t\ta   *args\n\t\terr error\n\t)\n\tif len(input) == 1 {\n\t\treturn nil, ErrNoInput\n\t}\n\tif a, err = newArgs(input[1:]...); err != nil {\n\t\treturn nil, err\n\t}\n\tif !a.stdin {\n\t\tr, err = reader.NewMultiReader(reader.WithPaths(a.paths, a.recursive))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn &blush.Blush{\n\t\tFinders:      a.finders,\n\t\tReader:       r,\n\t\tDrop:         a.cut,\n\t\tWithFileName: !a.noFilename,\n\t}, nil\n}\n"
  },
  {
    "path": "cmd/main_example_test.go",
    "content": "package cmd_test\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/arsham/blush/blush\"\n\t\"github.com/arsham/blush/cmd\"\n)\n\ntype colourer interface {\n\tColour() blush.Colour\n}\n\nfunc ExampleGetBlush_red() {\n\tinput := []string{\"blush\", \"--red\", \"term\", \"/\"}\n\tb, err := cmd.GetBlush(input)\n\tfmt.Println(\"err == nil:\", err == nil)\n\tfmt.Println(\"Finders count:\", len(b.Finders))\n\tc := b.Finders[0].(colourer)\n\tfmt.Println(\"Is red:\", c.Colour() == blush.Red)\n\n\t// Output:\n\t// err == nil: true\n\t// Finders count: 1\n\t// Is red: true\n}\n\nfunc ExampleGetBlush_multiColour() {\n\tinput := []string{\"-b\", \"term1\", \"-g\", \"term2\", \"/\"}\n\tb, err := cmd.GetBlush(input)\n\tc1 := b.Finders[0].(colourer)\n\tc2 := b.Finders[1].(colourer)\n\tfmt.Println(\"err == nil:\", err == nil)\n\tfmt.Println(\"Finders count:\", len(b.Finders))\n\tfmt.Println(\"Is blue:\", c1.Colour() == blush.Blue)\n\tfmt.Println(\"Is green:\", c2.Colour() == blush.Green)\n\n\t// Output:\n\t// err == nil: true\n\t// Finders count: 2\n\t// Is blue: true\n\t// Is green: true\n}\n"
  },
  {
    "path": "cmd/main_internal_test.go",
    "content": "package cmd\n\nimport (\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path\"\n\t\"testing\"\n\n\t\"github.com/alecthomas/assert\"\n)\n\nfunc getPipe(t *testing.T) *os.File {\n\tt.Helper()\n\toldStdin := os.Stdin\n\n\tfile, err := ioutil.TempFile(t.TempDir(), \"blush_pipe\")\n\tassert.NoError(t, err)\n\tname := file.Name()\n\tfile.Close()\n\tfile, err = os.OpenFile(name, os.O_CREATE|os.O_RDWR, os.ModeCharDevice|os.ModeDevice)\n\tassert.NoError(t, err)\n\tos.Stdin = file\n\tt.Cleanup(func() {\n\t\tos.Stdin = oldStdin\n\t})\n\treturn file\n}\n\nfunc stringSliceEq(a, b []string) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\tfor i := 0; i < len(a); i++ {\n\t\tif a[i] != b[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc TestFiles(t *testing.T) {\n\tdir := t.TempDir()\n\tf1, err := ioutil.TempFile(dir, \"main\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tf2, err := ioutil.TempFile(dir, \"main\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttcs := []struct {\n\t\tname          string\n\t\tinput         []string\n\t\twantRemaining []string\n\t\twantP         []string\n\t\twantErr       bool\n\t}{\n\t\t{\"not found\", []string{\"nowhere\"}, []string{}, []string{}, true},\n\t\t{\"only a file\", []string{f1.Name()}, []string{}, []string{f1.Name()}, false},\n\t\t{\"two files\", []string{f1.Name(), f2.Name()}, []string{}, []string{f1.Name(), f2.Name()}, false},\n\t\t{\"arg between two files\", []string{f1.Name(), \"-a\", f2.Name()}, []string{f1.Name(), \"-a\", f2.Name()}, []string{}, true},\n\t\t{\"prefix file\", []string{\"a\", f1.Name()}, []string{\"a\"}, []string{f1.Name()}, false},\n\t\t{\"prefix arg file\", []string{\"-r\", f1.Name()}, []string{\"-r\", f1.Name()}, []string{}, true},\n\t\t{\"file matches but is an argument\", []string{\"-r\", f1.Name(), f2.Name()}, []string{\"-r\", f1.Name()}, []string{f2.Name()}, false},\n\t\t{\n\t\t\t\"star dir\",\n\t\t\t[]string{path.Join(dir, \"*\")},\n\t\t\t[]string{},\n\t\t\t[]string{path.Join(dir, \"*\")},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"stared dir\",\n\t\t\t[]string{dir + \"*\"},\n\t\t\t[]string{},\n\t\t\t[]string{dir + \"*\"},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"many prefixes\",\n\t\t\t[]string{\"--#7ff\", \"main\", \"-g\", \"package\", \"-r\", \"a\", path.Join(dir, \"*\")},\n\t\t\t[]string{\"--#7ff\", \"main\", \"-g\", \"package\", \"-r\", \"a\"},\n\t\t\t[]string{path.Join(dir, \"*\")},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"many prefixes star\",\n\t\t\t[]string{\"--#7ff\", \"main\", \"-g\", \"package\", \"-r\", \"a\", dir + \"*\"},\n\t\t\t[]string{\"--#7ff\", \"main\", \"-g\", \"package\", \"-r\", \"a\"},\n\t\t\t[]string{dir + \"*\"},\n\t\t\tfalse,\n\t\t},\n\t}\n\tfor _, tc := range tcs {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ta, err := newArgs(tc.input...)\n\t\t\tif tc.wantErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Error(\"err = nil, want error\")\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !stringSliceEq(a.remaining, tc.wantRemaining) {\n\t\t\t\tt.Errorf(\"files(%v): a.remaining = %v, want %v\", tc.input, a.remaining, tc.wantRemaining)\n\t\t\t}\n\t\t\tif !stringSliceEq(a.paths, tc.wantP) {\n\t\t\t\tt.Errorf(\"files(%v): a.paths = %v, want %v\", tc.input, a.paths, tc.wantP)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/main_test.go",
    "content": "package cmd_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path\"\n\t\"testing\"\n\n\t\"github.com/alecthomas/assert\"\n\t\"github.com/arsham/blush/blush\"\n\t\"github.com/arsham/blush/cmd\"\n)\n\nfunc TestMainHelp(t *testing.T) {\n\tstdout, stderr := setup(t, \"--help\")\n\tcmd.Main()\n\tassert.Empty(t, stderr.String())\n\tassert.Contains(t, stdout.String(), cmd.Usage)\n}\n\nfunc TestPipeInput(t *testing.T) {\n\toldStdin := os.Stdin\n\tstdout, stderr := setup(t, \"findme\")\n\tdefer func() {\n\t\tos.Stdin = oldStdin\n\t}()\n\tfile := getPipe(t)\n\tfile.WriteString(\"you can findme here\")\n\tos.Stdin = file\n\tfile.Seek(0, 0)\n\tcmd.Main()\n\tassert.Empty(t, stderr.String())\n\tassert.Contains(t, stdout.String(), \"findme\")\n}\n\nfunc TestMainMatch(t *testing.T) {\n\tmatch := blush.Colourise(\"TOKEN\", blush.Blue)\n\tpwd, err := os.Getwd()\n\tassert.NoError(t, err)\n\tlocation := path.Join(pwd, \"../blush/testdata\")\n\n\ttcs := []struct {\n\t\tname  string\n\t\tinput string\n\t}{\n\t\t{\"exact sensitive\", \"-b TOKEN\"},\n\t\t{\"exact insensitive\", \"-i -b TOKEN\"},\n\t\t{\"regexp sensitive\", \"-b TOK[EN]{2}\"},\n\t\t{\"regexp insensitive\", \"-i -b tok[en]{2}\"},\n\t}\n\tfor _, tc := range tcs {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tstdout, stderr := setup(t, fmt.Sprintf(\"%s %s\", tc.input, location))\n\t\t\tcmd.Main()\n\n\t\t\tassert.Empty(t, stderr.String())\n\t\t\tassert.NotEmpty(t, stdout.String())\n\t\t\tassert.Contains(t, stdout.String(), match)\n\t\t})\n\t}\n}\n\nfunc TestMainMatchCut(t *testing.T) {\n\tmatches := []string{\"TOKEN\", \"ONE\", \"TWO\", \"THREE\", \"FOUR\"}\n\tpwd, err := os.Getwd()\n\tassert.NoError(t, err)\n\tlocation := path.Join(pwd, \"../blush/testdata\")\n\n\tstdout, stderr := setup(t, fmt.Sprintf(\"-b %s %s\", leaveMeHere, location))\n\tcmd.Main()\n\tassert.Empty(t, stderr.String())\n\tassert.NotEmpty(t, stdout.String())\n\tfor _, s := range matches {\n\t\tassert.Contains(t, stdout.String(), s)\n\t}\n}\n\nfunc TestNoFiles(t *testing.T) {\n\tfileName := \"test\"\n\tb, err := cmd.GetBlush([]string{fileName})\n\tassert.Error(t, err)\n\tassert.Nil(t, b)\n}\n\nfunc TestColourArgs(t *testing.T) {\n\taaa := \"aaa\"\n\tbbb := \"bbb\"\n\ttcs := []struct {\n\t\tname  string\n\t\tinput []string\n\t\twant  []blush.Finder\n\t}{\n\t\t{\"empty\", []string{\"/\"}, []blush.Finder{}},\n\t\t{\"1-default colour\", []string{\"aaa\", \"/\"}, []blush.Finder{\n\t\t\tblush.NewExact(aaa, blush.DefaultColour),\n\t\t}},\n\t\t{\"1-no colour\", []string{\"--no-colour\", \"aaa\", \"/\"}, []blush.Finder{\n\t\t\tblush.NewExact(aaa, blush.NoColour),\n\t\t}},\n\t\t{\"1-no colour american\", []string{\"--no-colour\", \"aaa\", \"/\"}, []blush.Finder{\n\t\t\tblush.NewExact(aaa, blush.NoColour),\n\t\t}},\n\t\t{\"1-colour\", []string{\"-b\", \"aaa\", \"/\"}, []blush.Finder{\n\t\t\tblush.NewExact(aaa, blush.Blue),\n\t\t}},\n\t\t{\"1-colour long\", []string{\"--blue\", \"aaa\", \"/\"}, []blush.Finder{\n\t\t\tblush.NewExact(aaa, blush.Blue),\n\t\t}},\n\t\t{\"2-default colour\", []string{\"aaa\", \"bbb\", \"/\"}, []blush.Finder{\n\t\t\tblush.NewExact(aaa, blush.DefaultColour),\n\t\t\tblush.NewExact(bbb, blush.DefaultColour),\n\t\t}},\n\t\t{\"2-no colour\", []string{\"--no-colour\", \"aaa\", \"bbb\", \"/\"}, []blush.Finder{\n\t\t\tblush.NewExact(aaa, blush.NoColour),\n\t\t\tblush.NewExact(bbb, blush.NoColour),\n\t\t}},\n\t\t{\"2-no colour american\", []string{\"--no-colour\", \"aaa\", \"bbb\", \"/\"}, []blush.Finder{\n\t\t\tblush.NewExact(aaa, blush.NoColour),\n\t\t\tblush.NewExact(bbb, blush.NoColour),\n\t\t}},\n\t\t{\"2-colour\", []string{\"-b\", \"aaa\", \"bbb\", \"/\"}, []blush.Finder{\n\t\t\tblush.NewExact(aaa, blush.Blue),\n\t\t\tblush.NewExact(bbb, blush.Blue),\n\t\t}},\n\t\t{\"2-two colours\", []string{\"-b\", \"aaa\", \"-g\", \"bbb\", \"/\"}, []blush.Finder{\n\t\t\tblush.NewExact(aaa, blush.Blue),\n\t\t\tblush.NewExact(bbb, blush.Green),\n\t\t}},\n\t\t{\"red\", []string{\"-r\", \"aaa\", \"--red\", \"bbb\", \"/\"}, []blush.Finder{\n\t\t\tblush.NewExact(aaa, blush.Red),\n\t\t\tblush.NewExact(bbb, blush.Red),\n\t\t}},\n\t\t{\"green\", []string{\"-g\", \"aaa\", \"--green\", \"bbb\", \"/\"}, []blush.Finder{\n\t\t\tblush.NewExact(aaa, blush.Green),\n\t\t\tblush.NewExact(bbb, blush.Green),\n\t\t}},\n\t\t{\"blue\", []string{\"-b\", \"aaa\", \"--blue\", \"bbb\", \"/\"}, []blush.Finder{\n\t\t\tblush.NewExact(aaa, blush.Blue),\n\t\t\tblush.NewExact(bbb, blush.Blue),\n\t\t}},\n\t\t{\"white\", []string{\"-w\", \"aaa\", \"--white\", \"bbb\", \"/\"}, []blush.Finder{\n\t\t\tblush.NewExact(aaa, blush.White),\n\t\t\tblush.NewExact(bbb, blush.White),\n\t\t}},\n\t\t{\"black\", []string{\"-bl\", \"aaa\", \"--black\", \"bbb\", \"/\"}, []blush.Finder{\n\t\t\tblush.NewExact(aaa, blush.Black),\n\t\t\tblush.NewExact(bbb, blush.Black),\n\t\t}},\n\t\t{\"cyan\", []string{\"-cy\", \"aaa\", \"--cyan\", \"bbb\", \"/\"}, []blush.Finder{\n\t\t\tblush.NewExact(aaa, blush.Cyan),\n\t\t\tblush.NewExact(bbb, blush.Cyan),\n\t\t}},\n\t\t{\"magenta\", []string{\"-mg\", \"aaa\", \"--magenta\", \"bbb\", \"/\"}, []blush.Finder{\n\t\t\tblush.NewExact(aaa, blush.Magenta),\n\t\t\tblush.NewExact(bbb, blush.Magenta),\n\t\t}},\n\t\t{\"yellow\", []string{\"-yl\", \"aaa\", \"--yellow\", \"bbb\", \"/\"}, []blush.Finder{\n\t\t\tblush.NewExact(aaa, blush.Yellow),\n\t\t\tblush.NewExact(bbb, blush.Yellow),\n\t\t}},\n\t}\n\n\tfor _, tc := range tcs {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tinput := append([]string{\"blush\"}, tc.input...)\n\t\t\tb, err := cmd.GetBlush(input)\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.NotNil(t, b)\n\t\t\tassert.True(t, argsEqual(b.Finders, tc.want))\n\t\t})\n\t}\n}\n\nfunc TestWithFilename(t *testing.T) {\n\ttcs := []struct {\n\t\tname  string\n\t\tinput []string\n\t\twant  bool\n\t}{\n\t\t{\"with filename\", []string{\"blush\", \"/\"}, true},\n\t\t{\"no filename\", []string{\"blush\", \"-h\", \"aaa\", \"/\"}, false},\n\t\t{\"no filename long\", []string{\"blush\", \"--no-filename\", \"aaa\", \"/\"}, false},\n\t\t{\"no filename both\", []string{\"blush\", \"-h\", \"--no-filename\", \"aaa\", \"/\"}, false},\n\t}\n\tfor _, tc := range tcs {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tb, err := cmd.GetBlush(tc.input)\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.NotNil(t, b)\n\t\t\tassert.Equal(t, tc.want, b.WithFileName)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/signal.go",
    "content": "package cmd\n\nimport (\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n)\n\n// WaitForSignal calls exit with code 130 if receives an SIGINT or SIGTERM, 0 if\n// SIGPIPE, and 1 otherwise.\nfunc WaitForSignal(sig chan os.Signal, exit func(int)) {\n\tsignal.Notify(sig, syscall.SIGINT, syscall.SIGTERM, syscall.SIGPIPE)\n\tgo func() {\n\t\ts := <-sig\n\t\tswitch s {\n\t\tcase syscall.SIGINT, syscall.SIGTERM:\n\t\t\texit(130) // Ctrl+c\n\t\tcase syscall.SIGPIPE:\n\t\t\texit(0)\n\t\t}\n\t\texit(1)\n\t}()\n}\n"
  },
  {
    "path": "cmd/signal_test.go",
    "content": "package cmd_test\n\nimport (\n\t\"os\"\n\t\"syscall\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/alecthomas/assert\"\n\t\"github.com/arsham/blush/cmd\"\n)\n\nfunc TestCaptureSignals(t *testing.T) {\n\ttcs := []struct {\n\t\tname   string\n\t\tsignal os.Signal\n\t\tcode   int\n\t}{\n\t\t{\"SIGINT\", syscall.SIGINT, 130},\n\t\t{\"SIGTERM\", syscall.SIGTERM, 130},\n\t\t{\"SIGPIPE\", syscall.SIGPIPE, 0},\n\t\t{\"other\", syscall.Signal(-1), 1},\n\t}\n\tfor _, tc := range tcs {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tsig := make(chan os.Signal, 1)\n\t\t\tcode := make(chan int)\n\t\t\texit := func(c int) {\n\t\t\t\tcode <- c\n\t\t\t}\n\t\t\tcmd.WaitForSignal(sig, exit)\n\t\t\tsig <- tc.signal\n\t\t\tselect {\n\t\t\tcase code := <-code:\n\t\t\t\tassert.Equal(t, tc.code, code)\n\t\t\tcase <-time.After(500 * time.Millisecond):\n\t\t\t\tt.Error(\"exit function wasn't called\")\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/usage.go",
    "content": "package cmd\n\nimport \"errors\"\n\n// These variables are used for showing help messages on command line.\nvar (\n\terrShowHelp = errors.New(\"show errors\")\n\n\tHelp = \"Usage: blush [OPTION]... PATTERN [FILE]...\\nTry 'blush --help' for more information.\"\n\t// nolint:misspell // it's ok.\n\tUsage = `Usage: blush [OPTION]... PATTERN [FILE]...\nColours:\n    -r, --red       Match decorated with red colour. See Stock Colours section.\n    -r[G], --red[G] Matches are grouped with the group number.\n                    Example: blush -b1 match filename\n    -#RGB, --#RGB   Use user defined colour schemas.\n                    Example: blush -#1eF match filename\n    -#RRGGBB, --#RRGGBB Same as -#RGB/--#RGB.\n\nPattern:\n    You can use simple pattern or regexp. If your pattern expands between\n    multiple words or has space in between, you should put them in quotations.\n\nStock Colours:\n    -r, --red\n    -g, --green\n    -b, --blue\n    -w, --white\n    -bl, --black\n    -yl, --yellow\n    -mg, --magenta\n    -cy, --cyan\n\nControl arguments:\n    -d, --drop              Drop unmatched lines.\n    -i                      Case insensitive match.\n    -h, --no-filename       Suppress the prefixing of file names on output.\n\nMulti match colouring:\n    blush -b match1 [match2]...: will colourise all matches with the same colour.\n\nUsing pipes:\n    cat FILE | blush -b match [-g match]...\n`\n)\n"
  },
  {
    "path": "configs/coverage.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\necho \"\" > coverage.txt\n\nfor d in $(go list ./... | grep -v vendor); do\n    go test -coverprofile=profile.out -covermode=atomic $d\n    if [ -f profile.out ]; then\n        cat profile.out >> coverage.txt\n        rm profile.out\n    fi\ndone\n\nsed '/mode: atomic/d' ./coverage.txt > ./codacy_coverage.txt\n"
  },
  {
    "path": "doc.go",
    "content": "// Package blush searches for matches with colours.\n//\n// Usage\n//\n//  $ blush -b \"first search\" -g \"second one\" -g \"and another one\" files/paths\n// Any occurrence of \"first search\" will be in blue, \"second one\" and \"and\n// another one\" are in green.\n//\n// Colouring Method\n//\n// With this method all texts are shown, but the matching words are coloured.\n// You can activate this mode by providing \"--colour\" or \"-C\" argument.\n//\n// Piping\n//\n// Blush can also read from a pipe:\n//  $ cat FILENAME | blush -b \"print in blue\" -g \"in green\" -g \"another green\"\n//  $ cat FILENAME | blush \"some text\"\n//\n// Arguments\n//\n//  +---------------+----------+------------------------------------------------+\n//  |    Argument   | Shortcut |                     Notes                      |\n//  +---------------+----------+------------------------------------------------+\n//  | --colour      | -C       | Colour, don't drop anything.                   |\n//  | N/A           | -i       | Case insensitive matching                      |\n//  | N/A           | -R       | Recursive                                      |\n//  | --no-colour   | N/A      | Doesn't colourize matches.                     |\n//  | --no-filename | -h       | Suppress the prefixing of file names on output |\n//  +---------------+----------+------------------------------------------------+\n//\n// File names or paths are matched from the end. Any argument that doesn't match\n// any files or paths are considered as regular expression. If regular\n// expressions are not followed by colouring arguments are coloured based on\n// previously provided colour:\n//\n//  $ blush -b match1 match3 FILENAME\n//\n// Please Note\n//\n// If no colour is provided, blush will choose blue. If you only provide\n// file/path, it will print them out without colouring. If the matcher contains\n// only alphabets and numbers, a non-regular expression is applied to search.\n//\n// Colour Groups\n//\n// You can provide a number for a colour argument to create a colour group:\n//\n//  $ blush -b1 match1 -b2 match2 -b1 match3 FILENAME\n//\n// All matches will be shown as blue. But `match1` and `match3` will have a\n// different background colour than `match2`. This means the numbers will create\n// colour groups.\n//\n// You also can provide a colour with a series of grep requests:\n//\n//  $ blush -b match1 match3 -g match2 FILENAME\n//\n// Colours\n//\n// You can choose a pre-defined colour, or pass it your own colour with a hash:\n//\n//  +-----------+----------+\n//  |  Argument | Shortcut |\n//  +-----------+----------+\n//  | --red     | -r       |\n//  | --green   | -g       |\n//  | --blue    | -b       |\n//  | --white   | -w       |\n//  | --black   | -bl      |\n//  | --yellow  | -yl      |\n//  | --magenta | -mg      |\n//  | --cyan    | -cy      |\n//  | --#11bb22 | --#1b2   |\n//  +-----------+----------+\n//\n// Complex Grep\n//\n// You must put your complex grep into quotations:\n//\n//  $ blush -b \"^age: [0-9]+\" FILENAME\npackage main\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/arsham/blush\n\ngo 1.18\n\nrequire (\n\tgithub.com/alecthomas/assert v1.0.0\n\tgithub.com/google/go-cmp v0.5.7\n\tgithub.com/pkg/errors v0.9.1\n)\n\nrequire (\n\tgithub.com/alecthomas/colour v0.1.0 // indirect\n\tgithub.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae // indirect\n\tgithub.com/mattn/go-isatty v0.0.14 // indirect\n\tgithub.com/sergi/go-diff v1.2.0 // indirect\n\tgithub.com/stretchr/testify v1.7.1 // indirect\n\tgolang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/alecthomas/assert v1.0.0 h1:3XmGh/PSuLzDbK3W2gUbRXwgW5lqPkuqvRgeQ30FI5o=\ngithub.com/alecthomas/assert v1.0.0/go.mod h1:va/d2JC+M7F6s+80kl/R3G7FUiW6JzUO+hPhLyJ36ZY=\ngithub.com/alecthomas/colour v0.1.0 h1:nOE9rJm6dsZ66RGWYSFrXw461ZIt9A6+nHgL7FRrDUk=\ngithub.com/alecthomas/colour v0.1.0/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=\ngithub.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae h1:zzGwJfFlFGD94CyyYwCJeSuD32Gj9GTaSi5y9hoVzdY=\ngithub.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=\ngithub.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=\ngithub.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=\ngithub.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=\ngolang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "internal/reader/helper_test.go",
    "content": "package reader_test\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"path\"\n\t\"testing\"\n\n\t\"github.com/alecthomas/assert\"\n)\n\n// this file contains helpers for all tests in this package.\n\ntype nopCloser struct {\n\tio.Reader\n\tcloseFunc func() error\n}\n\nfunc (n nopCloser) Close() error { return n.closeFunc() }\n\ntype testCase struct {\n\tname    string\n\tcontent string\n}\n\nfunc setup(t *testing.T, input []testCase) []string {\n\tt.Helper()\n\tdir := t.TempDir()\n\tret := make([]string, len(input))\n\tfor i, d := range input {\n\t\tname := path.Join(dir, d.name)\n\t\tbase := path.Dir(name)\n\t\terr := os.MkdirAll(base, os.ModePerm)\n\t\tassert.NoError(t, err)\n\t\tf, err := os.Create(name)\n\t\tassert.NoError(t, err)\n\t\tf.WriteString(d.content)\n\t\tf.Close()\n\t\tret[i] = base\n\t}\n\n\treturn ret\n}\n\nfunc inSlice(niddle string, haystack []string) bool {\n\tfor _, s := range haystack {\n\t\tif s == niddle {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "internal/reader/reader.go",
    "content": "package reader\n\nimport (\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/arsham/blush/internal/tools\"\n\t\"github.com/pkg/errors\"\n)\n\n// ErrNoReader is returned if there is no reader defined.\nvar ErrNoReader = errors.New(\"no input\")\n\n// MultiReader holds one or more io.ReadCloser and reads their contents when\n// Read() method is called in order. The reader is loaded lazily if it is a\n// file to prevent the system going out of file descriptors.\ntype MultiReader struct {\n\tcurrentName string\n\treaders     []*container\n}\n\n// NewMultiReader creates an instance of the MultiReader and passes it to all\n// input functions.\nfunc NewMultiReader(input ...Conf) (*MultiReader, error) {\n\tm := &MultiReader{\n\t\treaders: make([]*container, 0),\n\t}\n\tfor _, c := range input {\n\t\tif c == nil {\n\t\t\treturn nil, ErrNoReader\n\t\t}\n\t\terr := c(m)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn m, nil\n}\n\n// Conf is used to configure the MultiReader.\ntype Conf func(*MultiReader) error\n\n// WithReader adds the {name,r} reader to the MultiReader. If name is empty, the\n// key will not be written in the output. You can provide as many empty names as\n// you need.\nfunc WithReader(name string, r io.ReadCloser) Conf {\n\treturn func(m *MultiReader) error {\n\t\tif r == nil {\n\t\t\treturn errors.Wrap(ErrNoReader, \"WithReader\")\n\t\t}\n\t\tc := &container{\n\t\t\tget: func() (io.ReadCloser, error) {\n\t\t\t\tm.currentName = name\n\t\t\t\treturn r, nil\n\t\t\t},\n\t\t}\n\t\tm.readers = append(m.readers, c)\n\t\treturn nil\n\t}\n}\n\n// WithPaths searches through the path and adds any files it finds to the\n// MultiReader. Each path will become its reader's name in the process. It\n// returns an error if any of given files are not found. It ignores any files\n// that cannot be read or opened.\nfunc WithPaths(paths []string, recursive bool) Conf {\n\treturn func(m *MultiReader) error {\n\t\tif paths == nil {\n\t\t\treturn errors.Wrap(ErrNoReader, \"WithPaths: nil paths\")\n\t\t}\n\t\tif len(paths) == 0 {\n\t\t\treturn errors.Wrap(ErrNoReader, \"WithPaths: empty paths\")\n\t\t}\n\t\tfiles, err := tools.Files(recursive, paths...)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"WithPaths\")\n\t\t}\n\t\tfor _, name := range files {\n\t\t\tname := name\n\t\t\tc := &container{\n\t\t\t\tget: func() (io.ReadCloser, error) {\n\t\t\t\t\tm.currentName = name\n\t\t\t\t\tf, err := os.Open(name) // nolint:gosec // we need this.\n\t\t\t\t\treturn f, err\n\t\t\t\t},\n\t\t\t}\n\t\t\tm.readers = append(m.readers, c)\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// Read is almost the exact implementation of io.MultiReader but keeps track of\n// reader names. It closes each reader once they report they are exhausted, and\n// it will happen on the next read.\nfunc (m *MultiReader) Read(b []byte) (n int, err error) {\n\tfor len(m.readers) > 0 {\n\t\tif len(m.readers) == 1 {\n\t\t\tif r, ok := m.readers[0].r.(*MultiReader); ok {\n\t\t\t\tm.readers = r.readers\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tn, err = m.readers[0].Read(b)\n\t\tif errors.Is(err, io.EOF) {\n\t\t\terr := m.readers[0].r.Close()\n\t\t\tif err != nil {\n\t\t\t\treturn n, errors.Wrap(err, \"MultiReader.Read\")\n\t\t\t}\n\t\t\tc := &container{r: io.NopCloser(nil)}\n\t\t\tm.readers[0] = c\n\t\t\tm.readers = m.readers[1:]\n\t\t}\n\t\tif n > 0 || !errors.Is(err, io.EOF) {\n\t\t\tif errors.Is(err, io.EOF) && len(m.readers) > 0 {\n\t\t\t\terr = nil\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\tm.currentName = \"\"\n\treturn 0, io.EOF\n}\n\n// Close does nothing.\nfunc (m *MultiReader) Close() error { return nil }\n\n// FileName returns the current reader's name.\nfunc (m *MultiReader) FileName() string {\n\treturn m.currentName\n}\n\n// container takes care of opening the reader on demand. This is particularly\n// useful when searching in thousands of files, because we want to open them on\n// demand, otherwise the system gets out of file descriptors.\ntype container struct {\n\tr    io.ReadCloser\n\tget  func() (io.ReadCloser, error)\n\topen bool\n}\n\nfunc (c *container) Read(b []byte) (int, error) {\n\tif !c.open {\n\t\tvar err error\n\t\tc.r, err = c.get()\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tc.open = true\n\t}\n\treturn c.r.Read(b)\n}\n"
  },
  {
    "path": "internal/reader/reader_test.go",
    "content": "package reader_test\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"path\"\n\t\"testing\"\n\n\t\"github.com/alecthomas/assert\"\n\t\"github.com/arsham/blush/internal/reader\"\n)\n\nfunc TestWithReader(t *testing.T) {\n\tt.Parallel()\n\tm, err := reader.NewMultiReader(reader.WithReader(\"name\", nil))\n\tassert.Error(t, err)\n\tassert.Nil(t, m)\n\n\tr := io.NopCloser(&bytes.Buffer{})\n\tm, err = reader.NewMultiReader(reader.WithReader(\"name\", r))\n\tassert.NoError(t, err)\n\tassert.NotNil(t, m)\n\n\tm, err = reader.NewMultiReader(reader.WithReader(\"\", r))\n\tassert.NoError(t, err)\n\tassert.NotNil(t, m)\n}\n\nfunc TestWithReaderMultipleReadersClose(t *testing.T) {\n\tt.Parallel()\n\tvar called []string\n\tinput1 := \"afmBEswIRYosG7\"\n\tinput2 := \"UbMFeIFjvAhdA3sdT\"\n\tr1 := nopCloser{\n\t\tReader: bytes.NewBufferString(input1),\n\t\tcloseFunc: func() error {\n\t\t\tcalled = append(called, \"r1\")\n\t\t\treturn nil\n\t\t},\n\t}\n\tr2 := nopCloser{\n\t\tReader: bytes.NewBufferString(input2),\n\t\tcloseFunc: func() error {\n\t\t\tcalled = append(called, \"r2\")\n\t\t\treturn nil\n\t\t},\n\t}\n\tm, err := reader.NewMultiReader(reader.WithReader(\"r1\", r1), reader.WithReader(\"r2\", r2))\n\tassert.NoError(t, err)\n\n\tb := make([]byte, 100)\n\t_, err = m.Read(b)\n\tassert.NoError(t, err)\n\tassert.EqualValues(t, input1, bytes.Trim(b, \"\\x00\"))\n\n\t_, err = m.Read(b)\n\tassert.NoError(t, err)\n\tassert.True(t, inSlice(\"r1\", called))\n\tassert.EqualValues(t, input2, bytes.Trim(b, \"\\x00\"))\n\n\t_, err = m.Read(b)\n\tassert.EqualError(t, io.EOF, err.Error())\n\tassert.True(t, inSlice(\"r2\", called))\n}\n\nfunc TestWithReaderMultipleReadersError(t *testing.T) {\n\tt.Parallel()\n\tr := nopCloser{\n\t\tReader: &bytes.Buffer{},\n\t\tcloseFunc: func() error {\n\t\t\treturn nil\n\t\t},\n\t}\n\tm, err := reader.NewMultiReader(reader.WithReader(\"r\", r), nil)\n\tassert.Error(t, err)\n\tassert.Nil(t, m)\n}\n\nfunc TestWithPathsError(t *testing.T) {\n\tt.Parallel()\n\ttcs := []struct {\n\t\tname  string\n\t\tinput []string\n\t}{\n\t\t{\"nil\", nil},\n\t\t{\"empty\", []string{}},\n\t\t{\"empty string\", []string{\"\"}},\n\t\t{\"not found\", []string{\"nomansland2987349237\"}},\n\t}\n\n\tfor _, tc := range tcs {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tinput := reader.WithPaths(tc.input, true)\n\t\t\tm, err := reader.NewMultiReader(input)\n\t\t\tassert.Error(t, err)\n\t\t\tassert.Nil(t, m)\n\t\t})\n\t}\n}\n\nfunc TestNewMultiReaderWithPaths(t *testing.T) {\n\tt.Parallel()\n\tvar (\n\t\tc1 = \"VJSNS5IeLCtEB\"\n\t\tc2 = \"kkNL8vGNJn\"\n\t\tc3 = \"o6Ubb5Taj\"\n\t)\n\tinput := []testCase{\n\t\t{\"a/a.txt\", c1},\n\t\t{\"a/b.txt\", c2},\n\t\t{\"ab.txt\", c3},\n\t}\n\n\tdirs := setup(t, input)\n\tm, err := reader.NewMultiReader(reader.WithPaths(dirs, false))\n\tassert.NoError(t, err)\n\tassert.NotNil(t, m)\n\terr = m.Close()\n\tassert.NoError(t, err)\n}\n\nfunc TestMultiReaderReadOneReader(t *testing.T) {\n\tt.Parallel()\n\tinput := \"sdlksjdljfQYawl5OEEg\"\n\tr := io.NopCloser(bytes.NewBufferString(input))\n\tm, err := reader.NewMultiReader(reader.WithReader(\"r\", r))\n\tassert.NoError(t, err)\n\tassert.NotNil(t, m)\n\n\tb := make([]byte, len(input))\n\tn, err := m.Read(b)\n\tassert.NoError(t, err)\n\tassert.Equal(t, len(input), n)\n\tassert.EqualValues(t, input, b)\n\n\tn, err = m.Read(b)\n\tassert.EqualError(t, err, io.EOF.Error())\n\tassert.Zero(t, n)\n}\n\nfunc TestMultiReaderReadZeroBytes(t *testing.T) {\n\tt.Parallel()\n\tinput := \"3wAgvZ4bSfQYawl5OEEg\"\n\tr := io.NopCloser(bytes.NewBufferString(input))\n\tm, err := reader.NewMultiReader(reader.WithReader(\"r\", r))\n\tassert.NoError(t, err)\n\tassert.NotNil(t, m)\n\n\tb := make([]byte, 0)\n\tn, err := m.Read(b)\n\tassert.NoError(t, err)\n\tassert.Zero(t, n)\n\tassert.Empty(t, b)\n}\n\nfunc TestMultiReaderReadOneReaderMoreSpace(t *testing.T) {\n\tt.Parallel()\n\tinput := \"3wAgvZ4bSfQYawl5OEEg\"\n\tr := io.NopCloser(bytes.NewBufferString(input))\n\tm, err := reader.NewMultiReader(reader.WithReader(\"r\", r))\n\tassert.NoError(t, err)\n\tassert.NotNil(t, m)\n\tb := make([]byte, len(input)+1)\n\tn, err := m.Read(b)\n\tassert.NoError(t, err)\n\tassert.EqualValues(t, len(input), n)\n\tassert.EqualValues(t, input, bytes.Trim(b, \"\\x00\"))\n}\n\nfunc TestMultiReaderReadMultipleReaders(t *testing.T) {\n\tt.Parallel()\n\tinput := []string{\"P5tyugWXFn\", \"b8YbUO7pMX3G8j4Bi\"}\n\tr1 := io.NopCloser(bytes.NewBufferString(input[0]))\n\tr2 := io.NopCloser(bytes.NewBufferString(input[1]))\n\tm, err := reader.NewMultiReader(\n\t\treader.WithReader(\"r1\", r1),\n\t\treader.WithReader(\"r2\", r2),\n\t)\n\tassert.NoError(t, err)\n\tassert.NotNil(t, m)\n\n\ttcs := []struct {\n\t\tname    string\n\t\tb       []byte\n\t\twantErr error\n\t\twantLen int\n\t\twantOut string\n\t}{\n\t\t{\"r1\", make([]byte, len(input[0])), nil, len(input[0]), input[0]},\n\t\t{\"r2\", make([]byte, len(input[1])), nil, len(input[1]), input[1]},\n\t\t{\"nothing left\", make([]byte, 10), io.EOF, 0, \"\"},\n\t}\n\tfor _, tc := range tcs {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tn, err := m.Read(tc.b)\n\t\t\tassert.Equal(t, err, tc.wantErr, \"error\")\n\t\t\tassert.EqualValues(t, tc.wantLen, n)\n\t\t\tassert.Equal(t, tc.wantOut, string(bytes.Trim(tc.b, \"\\x00\")), \"output\")\n\t\t})\n\t}\n}\n\nfunc TestMultiReaderNames(t *testing.T) {\n\tt.Parallel()\n\tinput := []string{\"Mw0mxekLYOpXaKl8PVT\", \"1V5MjHUXYTPChW\"}\n\tr1 := io.NopCloser(bytes.NewBufferString(input[0]))\n\tr2 := io.NopCloser(bytes.NewBufferString(input[1]))\n\tm, err := reader.NewMultiReader(\n\t\treader.WithReader(\"r1\", r1),\n\t\treader.WithReader(\"r2\", r2),\n\t)\n\tassert.NoError(t, err)\n\tassert.NotNil(t, m)\n\tb := make([]byte, 100)\n\ttcs := []struct {\n\t\tname    string\n\t\twantErr error\n\t}{\n\t\t{\"r1\", nil},\n\t\t{\"r2\", nil},\n\t\t{\"\", io.EOF},\n\t}\n\tfor _, tc := range tcs {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t_, err := m.Read(b)\n\t\t\tassert.Equal(t, err, tc.wantErr, \"error\")\n\t\t\tassert.Equal(t, tc.name, m.FileName())\n\t\t})\n\t}\n}\n\nfunc TestNewMultiReaderWithPathsRead(t *testing.T) {\n\tt.Parallel()\n\tvar (\n\t\tc1 = \"VJSNS5IeLCtEB\"\n\t\tc2 = \"kkNL8vGNJn\"\n\t\tc3 = \"o6Ubb5Taj\"\n\t)\n\tinput := []testCase{\n\t\t{\"a/a.txt\", c1},\n\t\t{\"a/b.txt\", c2},\n\t\t{\"ab.txt\", c3},\n\t}\n\n\tdirs := setup(t, input)\n\tw, err := reader.NewMultiReader(reader.WithPaths(dirs, false))\n\tassert.NoError(t, err)\n\tassert.NotNil(t, w)\n\tt.Cleanup(func() {\n\t\terr = w.Close()\n\t\tassert.NoError(t, err)\n\t})\n\n\tbuf := &bytes.Buffer{}\n\t_, err = buf.ReadFrom(w)\n\tassert.NoError(t, err)\n\tfor _, s := range []string{c1, c2, c3} {\n\t\tassert.Contains(t, buf.String(), s)\n\t}\n}\n\nfunc TestNewMultiReaderRecursive(t *testing.T) {\n\tt.Parallel()\n\tvar (\n\t\tc1 = \"1JQey4agQ3w9pqg3\"\n\t\tc2 = \"7ToNRMgsOAR6A\"\n\t\tc3 = \"EtOkn9C5zoH0Dla2rF9\"\n\t)\n\tinput := []testCase{\n\t\t{\"a/a.txt\", c1},\n\t\t{\"a/b.txt\", c2},\n\t\t{\"a/b/c.txt\", c3},\n\t}\n\n\tdirs := setup(t, input)\n\tbase := path.Join(path.Dir(dirs[0]), \"a\")\n\tw, err := reader.NewMultiReader(reader.WithPaths([]string{base}, true))\n\tassert.NoError(t, err)\n\tassert.NotNil(t, w)\n\n\tt.Cleanup(func() {\n\t\terr = w.Close()\n\t\tassert.NoError(t, err)\n\t})\n\n\tbuf := &bytes.Buffer{}\n\t_, err = buf.ReadFrom(w)\n\tassert.NoError(t, err)\n\n\tfor _, s := range []string{c1, c2, c3} {\n\t\tassert.Contains(t, buf.String(), s)\n\t}\n}\n\nfunc TestNewMultiReaderNonRecursive(t *testing.T) {\n\tt.Parallel()\n\tvar (\n\t\tc1 = \"DRAjfSq2y\"\n\t\tc2 = \"ht3xCIQ\"\n\t\tc3 = \"jPqPoAbMNb\"\n\t)\n\tinput := []testCase{\n\t\t{\"a/a.txt\", c1},\n\t\t{\"a/b.txt\", c2},\n\t\t{\"a/b/c.txt\", c3},\n\t}\n\n\tdirs := setup(t, input)\n\tbase := path.Join(path.Dir(dirs[0]), \"a\")\n\tw, err := reader.NewMultiReader(reader.WithPaths([]string{base}, false))\n\tassert.NoError(t, err)\n\tassert.NotNil(t, w)\n\n\tt.Cleanup(func() {\n\t\terr = w.Close()\n\t\tassert.NoError(t, err)\n\t})\n\n\tbuf := &bytes.Buffer{}\n\t_, err = buf.ReadFrom(w)\n\tassert.NoError(t, err)\n\tfor _, s := range []string{c1, c2} {\n\t\tassert.Contains(t, buf.String(), s)\n\t}\n\tassert.NotContains(t, buf.String(), c3)\n}\n"
  },
  {
    "path": "internal/tools/dir.go",
    "content": "// Package tools contains common tools used throughout this application.\npackage tools\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n)\n\n// Files returns all files found in paths. If recursive is false, it only\n// returns the immediate files in the paths.\nfunc Files(recursive bool, paths ...string) ([]string, error) {\n\tvar (\n\t\tfileList []string\n\t\tfn       = files\n\t)\n\tif recursive {\n\t\tfn = rfiles\n\t}\n\n\tfor _, p := range paths {\n\t\tf, err := fn(p)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfileList = append(fileList, f...)\n\t}\n\tif len(fileList) == 0 {\n\t\treturn nil, errors.New(\"no files found\")\n\t}\n\tfileList = unique(fileList)\n\tfileList = nonBinary(fileList)\n\treturn fileList, nil\n}\n\nfunc unique(fileList []string) []string {\n\tvar (\n\t\tret  = make([]string, 0, len(fileList))\n\t\tseen = make(map[string]struct{}, len(fileList))\n\t)\n\tfor _, f := range fileList {\n\t\tif _, ok := seen[f]; ok {\n\t\t\tcontinue\n\t\t}\n\t\tseen[f] = struct{}{}\n\t\tret = append(ret, f)\n\t}\n\treturn ret\n}\n\nfunc nonBinary(fileList []string) []string {\n\tret := make([]string, 0, len(fileList))\n\tfor _, f := range fileList {\n\t\tif isPlainText(f) {\n\t\t\tret = append(ret, f)\n\t\t}\n\t}\n\treturn ret\n}\n\nfunc rfiles(location string) ([]string, error) {\n\tfileList := []string{}\n\terr := filepath.Walk(location, func(location string, f os.FileInfo, err error) error {\n\t\tif os.IsPermission(err) {\n\t\t\treturn nil\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !f.IsDir() {\n\t\t\tfileList = append(fileList, location)\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn fileList, nil\n}\n\nfunc files(location string) ([]string, error) {\n\tif s, err := os.Stat(location); err == nil && !s.IsDir() {\n\t\treturn []string{location}, nil\n\t}\n\tfiles, err := os.ReadDir(location)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfileList := []string{}\n\tfor _, f := range files {\n\t\tif !f.IsDir() {\n\t\t\tp := path.Join(location, f.Name())\n\t\t\tfileList = append(fileList, p)\n\t\t}\n\t}\n\treturn fileList, nil\n}\n\n// TODO: we should ignore the line in search stage instead.\nfunc isPlainText(name string) bool {\n\tf, err := os.Open(name) // nolint:gosec // this is required.\n\tif err != nil {\n\t\treturn false\n\t}\n\tdefer f.Close() // nolint:errcheck,gosec // not required.\n\theader := make([]byte, 512)\n\t_, err = f.Read(header)\n\tif err != nil && !errors.Is(err, io.EOF) {\n\t\treturn false\n\t}\n\n\treturn IsPlainText(string(header))\n}\n"
  },
  {
    "path": "internal/tools/dir_example_test.go",
    "content": "package tools_test\n\nimport (\n\t\"github.com/arsham/blush/internal/tools\"\n)\n\nfunc ExampleFiles() {\n\ttools.Files(true, \"~/Documents\", \"/tmp\")\n\t// Or\n\tdirs := []string{\"~/Documents\", \"/tmp\"}\n\ttools.Files(false, dirs...)\n}\n"
  },
  {
    "path": "internal/tools/dir_test.go",
    "content": "package tools_test\n\nimport (\n\t\"image\"\n\t\"image/png\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path\"\n\t\"sort\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/alecthomas/assert\"\n\t\"github.com/arsham/blush/internal/tools\"\n\t\"github.com/google/go-cmp/cmp\"\n)\n\nfunc stringSliceEq(t *testing.T, a, b []string) {\n\tt.Helper()\n\tsort.Strings(a)\n\tsort.Strings(b)\n\tif diff := cmp.Diff(a, b); diff != \"\" {\n\t\tt.Errorf(\"(-want +got):\\\\n%s\", diff)\n\t}\n}\n\nfunc inSlice(niddle string, haystack []string) bool {\n\tfor _, s := range haystack {\n\t\tif s == niddle {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc setup(t *testing.T, count int) (dirs, expect []string) {\n\tt.Helper()\n\tret := make(map[string]struct{})\n\ttmp := t.TempDir()\n\n\tfiles := []struct {\n\t\tdir   string\n\t\tcount int\n\t}{\n\t\t{\"a\", count},     // keep this here.\n\t\t{\"a/b/c\", count}, // this one is in the above folder, keep!\n\t\t{\"abc\", count},   // this one is outside.\n\t\t{\"f\", 0},         // this should not be matched.\n\t}\n\tfor _, f := range files {\n\t\tl := path.Join(tmp, f.dir)\n\t\terr := os.MkdirAll(l, os.ModePerm)\n\t\tassert.NoError(t, err)\n\n\t\tfor i := 0; i < f.count; i++ {\n\t\t\tf, err := ioutil.TempFile(l, \"file_\")\n\t\t\tassert.NoError(t, err)\n\t\t\tret[path.Dir(f.Name())] = struct{}{}\n\t\t\texpect = append(expect, f.Name())\n\t\t\tf.WriteString(\"test\")\n\t\t}\n\t}\n\tfor d := range ret {\n\t\tdirs = append(dirs, d)\n\t}\n\tsort.Strings(dirs)\n\treturn dirs, expect\n}\n\nfunc TestFilesError(t *testing.T) {\n\tt.Parallel()\n\tf, err := tools.Files(false)\n\tassert.Nil(t, f)\n\tassert.Error(t, err)\n\tf, err = tools.Files(false, \"/path to heaven\")\n\tassert.Error(t, err)\n\tassert.Nil(t, f)\n}\n\nfunc TestFiles(t *testing.T) {\n\tt.Parallel()\n\tdirs, expect := setup(t, 10)\n\n\tf, err := tools.Files(false, dirs...)\n\tassert.NoError(t, err)\n\tassert.NotNil(t, f)\n\tstringSliceEq(t, expect, f)\n\n\t// the a and abc should match, a/b/c should not\n\tf, err = tools.Files(false, dirs[0], dirs[2])\n\tassert.NoError(t, err)\n\tif len(f) != 20 { // all files in `a` and `abc`\n\t\tt.Errorf(\"len(f) = %d, want %d: %v\", len(f), 20, f)\n\t}\n}\n\nfunc TestFilesOnSingleFile(t *testing.T) {\n\tt.Parallel()\n\tfile, err := ioutil.TempFile(t.TempDir(), \"blush_tools\")\n\tassert.NoError(t, err)\n\tname := file.Name()\n\n\tf, err := tools.Files(true, name)\n\tassert.NoError(t, err)\n\tif len(f) != 1 {\n\t\tt.Fatalf(\"len(f) = %d, want 1\", len(f))\n\t}\n\tif f[0] != name {\n\t\tt.Errorf(\"f[0] = %s, want %s\", f[0], name)\n\t}\n\n\tf, err = tools.Files(false, name)\n\tassert.NoError(t, err)\n\tif len(f) != 1 {\n\t\tt.Fatalf(\"len(f) = %d, want 1\", len(f))\n\t}\n\tif f[0] != name {\n\t\tt.Errorf(\"f[0] = %s, want %s\", f[0], name)\n\t}\n}\n\nfunc TestFilesRecursive(t *testing.T) {\n\tt.Parallel()\n\tf, err := tools.Files(true, \"/path to heaven\")\n\tassert.Error(t, err)\n\tassert.Nil(t, f)\n\n\tdirs, expect := setup(t, 10)\n\n\tf, err = tools.Files(true, dirs...)\n\tassert.NoError(t, err)\n\tassert.NotNil(t, f)\n\tstringSliceEq(t, expect, f)\n\n\tf, err = tools.Files(true, dirs[0]) // expecting `a`\n\tassert.NoError(t, err)\n\tif len(f) != 20 { // all files in `a`\n\t\tt.Errorf(\"len(f) = %d, want %d: %v\", len(f), 20, f)\n\t}\n}\n\nfunc setupUnpermissioned(t *testing.T) (rootDir, fileB string) {\n\tt.Helper()\n\t// creates this structure:\n\t// /tmp\n\t//     /a <--- 0222 perm\n\t//       /aaa.txt\n\t//     /b <--- 0777 perm\n\t//       /bbb.txt\n\trootDir = t.TempDir()\n\tdirA := path.Join(rootDir, \"a\")\n\tdirB := path.Join(rootDir, \"b\")\n\tdirs := []struct {\n\t\tdir, file string\n\t}{\n\t\t{dirA, \"aaa.txt\"},\n\t\t{dirB, \"bbb.txt\"},\n\t}\n\tfor _, d := range dirs {\n\t\terr := os.Mkdir(d.dir, 0o777)\n\t\tassert.NoError(t, err)\n\t\tname := path.Join(d.dir, d.file)\n\t\t_, err = os.Create(name)\n\t\tassert.NoError(t, err)\n\t}\n\terr := os.Chmod(dirA, 0o222)\n\tassert.NoError(t, err)\n\tfileB = path.Join(dirB, \"bbb.txt\")\n\tt.Cleanup(func() {\n\t\terr := os.Chmod(dirA, 0o777)\n\t\tassert.NoError(t, err)\n\t})\n\treturn rootDir, fileB\n}\n\nfunc TestIgnoreNontPermissionedFolders(t *testing.T) {\n\tt.Parallel()\n\trootDir, fileB := setupUnpermissioned(t)\n\tf, err := tools.Files(true, rootDir)\n\tassert.NoError(t, err)\n\tassert.NotNil(t, f)\n\texpect := []string{\n\t\tfileB,\n\t}\n\tstringSliceEq(t, expect, f)\n}\n\n// first returned string is text format, second is binary.\nfunc setupBinaryFile(t *testing.T) (str, name string) {\n\tt.Helper()\n\tdir := t.TempDir()\n\n\ttxt, err := os.Create(path.Join(dir, \"txt\"))\n\tassert.NoError(t, err)\n\n\timg := image.NewRGBA(image.Rect(0, 0, 1, 1))\n\tbinary, err := os.Create(path.Join(dir, \"binary\"))\n\tassert.NoError(t, err)\n\n\ttxt.WriteString(\"aaaaa\")\n\tpng.Encode(binary, img)\n\n\treturn txt.Name(), binary.Name()\n}\n\nfunc TestIgnoreNonTextFiles(t *testing.T) {\n\tt.Parallel()\n\ttxt, binary := setupBinaryFile(t)\n\tpaths := path.Dir(txt)\n\tgot, err := tools.Files(false, paths)\n\tassert.NoError(t, err)\n\tassert.True(t, inSlice(txt, got))\n\tassert.False(t, inSlice(binary, got))\n}\n\nfunc TestUnPrintableButTextContents(t *testing.T) {\n\tt.Parallel()\n\ttcs := []struct {\n\t\tname    string\n\t\tinput   string\n\t\twantLen int\n\t}{\n\t\t{\"null\", string(rune(0)), 1},\n\t\t{\"space\", \" \", 1},\n\t\t{\"return\", \"\\r\", 1},\n\t\t{\"line feed\", \"\\n\", 1},\n\t\t{\"tab\", \"\\t\", 1},\n\t\t{\"mix\", \"\\na\\tbbb\\n\\n\\n\\t\\t\\n \\t \\n \\r\\nsjdk\", 1},\n\t\t{\"one\", string(rune(1)), 0},\n\t\t{\"bell\", \"\\b\", 0},\n\t\t{\"bell in middle\", \"a\\bc\", 0},\n\t}\n\tfor _, tc := range tcs {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tfile, err := ioutil.TempFile(t.TempDir(), \"blush_text\")\n\t\t\tassert.NoError(t, err)\n\t\t\tfile.WriteString(tc.input)\n\t\t\tgot, err := tools.Files(false, file.Name())\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.Len(t, got, tc.wantLen, strings.Join(got, \"\\n\"))\n\t\t})\n\t}\n}\n\nfunc TestFilesIgnoreDirs(t *testing.T) {\n\tt.Parallel()\n\tdir := t.TempDir()\n\n\tp := path.Join(dir, \"a\")\n\terr := os.MkdirAll(p, 0o777)\n\tassert.NoError(t, err)\n\n\tfile, err := ioutil.TempFile(dir, \"b\")\n\tassert.NoError(t, err)\n\n\tg, err := tools.Files(true, dir)\n\tassert.NoError(t, err)\n\tassert.NotNil(t, g)\n\n\tassert.False(t, inSlice(p, g))\n\tassert.True(t, inSlice(file.Name(), g))\n}\n"
  },
  {
    "path": "internal/tools/strings.go",
    "content": "package tools\n\nimport (\n\t\"unicode\"\n)\n\n// IsPlainText returns false if at least one of the runes in the input is not\n// represented as a plain text in a file. Null is an exception.\nfunc IsPlainText(input string) bool {\n\tfor _, r := range input {\n\t\tswitch r {\n\t\tcase 0, '\\n', '\\t', '\\r':\n\t\t\tcontinue\n\t\t}\n\t\tif r > unicode.MaxASCII || !unicode.IsPrint(r) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "internal/tools/strings_test.go",
    "content": "package tools_test\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/alecthomas/assert\"\n\t\"github.com/arsham/blush/internal/tools\"\n)\n\nfunc TestIsPlainText(t *testing.T) {\n\tt.Parallel()\n\ttcs := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  bool\n\t}{\n\t\t{\"null\", fmt.Sprintf(\"%d\", 0), true},\n\t\t{\"space\", \" \", true},\n\t\t{\"return\", \"\\r\", true},\n\t\t{\"line feed\", \"\\n\", true},\n\t\t{\"tab\", \"\\t\", true},\n\t\t{\"bell\", \"\\b\", false},\n\t\t{\"mix\", \"\\n\\n \\r\\nsjdk\", true},\n\t\t{\"1\", \"\\x01\", false},\n\t\t{\"zero in middle\", \"n\\x00b\", true},\n\t\t{\"bell in middle\", \"a\\bc\", false},\n\t}\n\tfor _, tc := range tcs {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := tools.IsPlainText(tc.input)\n\t\t\tassert.Equal(t, tc.want, got)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "main.go",
    "content": "package main\n\nimport \"github.com/arsham/blush/cmd\"\n\nfunc main() {\n\tcmd.Main()\n}\n"
  }
]