Compare commits

...

268 Commits

Author SHA1 Message Date
Chris Allen Lane 7908a678df
Merge pull request #742 from cheat/dependabot/go_modules/github.com/cloudflare/circl-1.3.7
chore(deps): bump github.com/cloudflare/circl from 1.3.6 to 1.3.7
2024-01-08 15:34:48 -05:00
dependabot[bot] 7c0eacb53d
chore(deps): bump github.com/cloudflare/circl from 1.3.6 to 1.3.7
Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.3.6 to 1.3.7.
- [Release notes](https://github.com/cloudflare/circl/releases)
- [Commits](https://github.com/cloudflare/circl/compare/v1.3.6...v1.3.7)

---
updated-dependencies:
- dependency-name: github.com/cloudflare/circl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-08 16:53:49 +00:00
Chris Allen Lane 4bf804ac60
Merge pull request #741 from cheat/dependabot/go_modules/golang.org/x/crypto-0.17.0
chore(deps): bump golang.org/x/crypto from 0.16.0 to 0.17.0
2023-12-19 14:51:50 -05:00
dependabot[bot] 33c5918087
chore(deps): bump golang.org/x/crypto from 0.16.0 to 0.17.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.16.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.16.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-19 00:07:38 +00:00
Christopher Allen Lane d34177729d chore: bump version to 4.4.2 2023-12-15 12:48:39 -05:00
Chris Allen Lane 7fa50328d7
Merge pull request #740 from chrisallenlane/chroma
chore(deps): bump chroma to v2 #735
2023-12-13 12:59:52 -05:00
Christopher Allen Lane 1790aec85d chore(deps): bump chroma to v2 #735
Bump `alecthomas/chroma` to `v2`:
https://github.com/cheat/cheat/issues/735
2023-12-13 12:54:32 -05:00
Mikel Olasagasti Uranga 6bf51e758f Use yaml.v3 rather than .v2 and .v1 2023-12-13 09:57:58 -05:00
Christopher Allen Lane 242da8c89a chore(build): remove `plan9` support
Remove `plan9` support from the `Makefile` because the executable now
fails to build.
2023-12-13 09:45:21 -05:00
Chris Allen Lane 2294f40ee0
Merge pull request #739 from chrisallenlane/v4.4.1
V4.4.1
2023-12-13 09:17:16 -05:00
Christopher Allen Lane fe25019b14 chore: bump version to `4.4.1` 2023-12-13 09:12:14 -05:00
Christopher Allen Lane bfb071c0b2 chore(lint): various changes to appease revive
- Add `package` comments
- Rename `opts` to `_` where unused
2023-12-13 09:10:20 -05:00
Christopher Allen Lane 95a4e31b6c chore(deps): upgrade dependencies
Upgrade all dependencies to newest versions.
2023-12-13 08:29:02 -05:00
Chris Allen Lane 0d9c92c8c0
Merge pull request #707 from chrisallenlane/v4.4.0
V4.4.0
2022-11-05 13:11:22 -04:00
Christopher Allen Lane 16c50bb659 chore: bump version to `4.4.0` 2022-11-05 12:56:43 -04:00
Christopher Allen Lane 1a85c9e9c8 feat: platform compatibility
Add experimental support for the following platforms:

- aix
- dragonfly
- illumos
- ios
- netbsd
- openbsd
- plan9
- solaris
2022-11-05 12:00:43 -04:00
Christopher Allen Lane c9ccefa607 chore(deps): remove `yaml.v1`
Remove errant `yaml.v1` dependency, and use `yaml.v2` everywhere.
2022-11-05 11:39:48 -04:00
Christopher Allen Lane 3a6b6e58f0 chore(deps): update dependencies
`make vendor-update`
2022-11-05 10:15:15 -04:00
Christopher Allen Lane 2edc0ee299 chore: add a comment
Add a small comment regarding a tricky edge-case in `gitdir.go`.
2022-08-28 06:54:29 -04:00
Chris Allen Lane bd9fa1ba70
Merge pull request #700 from chrisallenlane/4.3.3
4.3.3
2022-08-27 22:07:19 -04:00
Christopher Allen Lane bb85e611f4 chore: bump version to `4.3.3` 2022-08-27 21:29:06 -04:00
Christopher Allen Lane a2f538f114 refactor(repo): create `repo` package
- Refactor `installer.clone` into new `repo.Clone` package and method.

- Refactor `sheets.isGitDir` into `repo.GitDir`.

Both of these changes read better, and will facilitate cleaner
architecture when `--update` is implemented.
2022-08-27 21:02:48 -04:00
Christopher Allen Lane 80c91cbdee feat(installer): use `go-git` to clone
Integrate `go-git` into the application, and use it to `git clone`
cheatsheets when the installer runs.

Previously, the installer required that `git` be installed on the system
`PATH`, so this change has to big advantages:

1. It removes that system dependency on `git`
2. It paves the way for implementing the `--update` command

Additionally, `cheat` now performs a `--depth=1` clone when installing
cheatsheets, which should at least somewhat improve installation times
(especially on slow network connections).
2022-08-27 21:00:46 -04:00
Christopher Allen Lane ede2d2dbaa fix(Sheets): `.gitignore` in cheatpath (#699)
Fix an issue whereby `cheat` would crash if a cheatpath contained a file
that began with `.git`, like `.gitignore`.
2022-08-27 20:57:07 -04:00
Chris Allen Lane db3d7e53a4
Merge pull request #698 from chrisallenlane/4.3.2
4.3.2
2022-08-26 14:07:14 -04:00
Christopher Allen Lane 06c4ff52fc chore: bump version to `4.3.2` 2022-08-26 13:56:35 -04:00
Christopher Allen Lane cbc2638d96 fix(docopt): whitespace typo in `--help` output
Fix a whitespace (alignment) typo in the `--help` output.
2022-08-26 13:56:35 -04:00
Christopher Allen Lane fd93da799d fix(sheets): cheatsheets in submodules (#694)
Resolve an issue whereby cheatsheets contained within `git` submodules
were ignored due to a regression introduced in `4.3.1`.
2022-08-26 13:55:09 -04:00
Christopher Allen Lane 5c5ed7344f chore(docs): improve configuration docs (#656)
Improve the configuration documentation in `configs/conf.yml` (#656).
2022-08-26 13:55:09 -04:00
Christopher Allen Lane d773383f70 fix(build): `Makefile` corrections
- Fix an issue whereby `make clean` filed to remove assets created by
  `make generate`.

- Fix a subsequent issue whereby `make generate` was being run too late
  in the `make build` target, which resulted in a build failure.
2022-08-26 13:55:09 -04:00
Christopher Allen Lane 2717044b62 chore(docs): update `INSTALLING.md` (#677)
Update `INSTALLING.md` with more package information. See: #677
2022-08-26 13:55:09 -04:00
Christopher Allen Lane 2d635293c5 refactor(Sheet): create `parse` method
Move `Frontmatter.Parse` to `Sheet.parse`, and delete the `frontmatter`
package. `Sheet.parse` more accurately describes the parser's behavior.
2022-08-26 13:55:09 -04:00
Christopher Allen Lane f0bfeda47a fix(frontmatter): do not trim whitespace (#663)
Do not strip leading or trailing newlines. Doing so had interferred with
users' intended cheatsheet layouts.
2022-08-26 13:55:09 -04:00
Christopher Allen Lane f1540290a7 chore(deps): update dependencies 2022-08-26 11:06:21 -04:00
Chris Allen Lane 0b80a608c3
Merge pull request #692 from chrisallenlane/4.3.1
4.3.1
2022-08-08 21:01:56 -04:00
Christopher Allen Lane 3c1e24a0e8 chore: bump version to `4.3.1` 2022-08-08 20:38:23 -04:00
Christopher Allen Lane 2a6586b41b fix(installer): always use `more` pager on Windows 2022-08-08 20:38:23 -04:00
Christopher Allen Lane 6421953183 feat(installer): set default `editor`
Attempt to set and locate a default editor when running the installer.
2022-08-08 20:14:27 -04:00
Christopher Allen Lane 0c47f44ff9 fix: no colorization on default install (#687)
Fix an issue whereby a default installation (as created by the
installer) would (seemingly) fail to output colorized text, even when
the `-c` flag was passed.

The root cause of the problem was that the installer did not set a
default `style` for `chroma`, which in turn defaulted to using the `bw`
(black-and-white) style.

Thus, colorization actually *was* being applied with `-c` - it was
simply black and white!
2022-08-08 19:45:32 -04:00
Christopher Allen Lane 77f9c3fdd0 fix(Sheets): cheatsheets in hidden directories (#690)
Fix an issue whereby cheatsheets that were contained within hidden
directories were prevented from being loaded.
2022-08-08 19:17:59 -04:00
Chris Allen Lane b53a14b1a7
Merge pull request #691 from chrisallenlane/4.3.0
4.3.0
2022-08-07 14:45:27 -04:00
Christopher Allen Lane f1e8602369 chore: bump version to `4.3.0` 2022-08-07 14:11:47 -04:00
Christopher Allen Lane ddbe710881 feat: add the `--conf` command
Add the `--conf` command, which dipslay's the current `cheat`
configuration file path.
2022-08-07 14:08:25 -04:00
Christopher Allen Lane d598d96fce fix(Config): colorization without pager (#687)
Fix an issue whereby colorization would output ANSI codes if a pager was
not configured.

The solution here is to stop guessing about the state of the user's
system at runtime, as well as the user's intention. The installer now
chooses an appropriate installer when generating configs, and no longer
bothers searching for pagers at runtime.
2022-08-07 10:19:56 -04:00
Christopher Allen Lane 4fdec50487 chore(deps): upgrade dependencies
- Upgrade to Go 1.19 in `go.mod`
- Upgrade dependencies
2022-08-07 10:19:56 -04:00
Chris Allen Lane 9de866dfb6
Merge pull request #689 from chrisallenlane/ci
chore(ci): YAML lints
2022-08-05 07:53:53 -04:00
Christopher Allen Lane eb99a070ce chore: CI template nits
- Fix YAML nits per `yamllint`
- Upgrade Go version to `1.19`
2022-08-05 07:49:20 -04:00
Chris Allen Lane 73f80bde48
Merge pull request #688 from chrisallenlane/4.2.7
4.2.7
2022-08-05 07:01:51 -04:00
Chris Allen Lane 8130b2f3bd chore: bump version to `4.2.7` 2022-08-05 06:41:16 -04:00
Christopher Allen Lane f4e6c76e58 fix: escape sequences in search output (#687)
Fix an issue whereby ANSI escape characters could appear in search
output when a pager was not configured.

The root cause of the problem was code that was overzealously applying
an underlying effect to search terms.

This commit simply rips out underlying entirely, both as means of
resolving this problem, and also simply for removing needless visual
noise from search output.
2022-08-05 06:41:16 -04:00
Chris Allen Lane 85f5ae8ec7 chore: various lint corrections
Make various lint corrections in order to appease `staticcheck`.
2022-08-04 20:43:50 -04:00
Chris Allen Lane 484b447391 perf(Sheets): do not walk hidden directories
Modify `Sheets.Load` to not walk hidden directories like `.git`. This
optimization can potentially prevent thousands of system calls from
being made, because `.git` directories can contain many files.
2022-08-04 20:43:42 -04:00
Chris Allen Lane cfd1702bc6
Merge pull request #685 from chrisallenlane/page-bug
Fix #681
2022-08-02 20:37:48 -04:00
Christopher Allen Lane 7406ebfb5e chore(deps): update dependencies 2022-08-02 20:30:31 -04:00
Christopher Allen Lane 0737af2fec fix: pagination error on Linux
Bury the `more` pager default on Linux in an effort to work around the
following problem:

https://github.com/cheat/cheat/issues/681#issuecomment-1201842334

We're satisficing for this kludge because it does not appear to be
possible to actually make `more` perform as expected in all
environments.
2022-08-02 20:27:56 -04:00
Christopher Allen Lane a23d372d1f docs(INSTALLING): `nix` link
Update the `nix` package information.
2022-07-06 08:11:42 -04:00
Chris Allen Lane fe66ff3768
Merge pull request #679 from chrisallenlane/hacking-md
docs: create `HACKING.md`
2022-07-05 15:11:09 -04:00
Christopher Allen Lane 7fed1f63a6 docs: create `HACKING.md`
Create a `HACKING.md` file for onboarding new developers to `cheat`.
2022-07-05 15:07:34 -04:00
Christopher Allen Lane a297d1619c chore(build): remove `make docker-run`
Remove the `docker-run` `make` target, which was added in haste. It was
entirely redundant with `docker-sh`.
2022-07-05 14:40:15 -04:00
Chris Allen Lane ef1da90a77
Merge pull request #678 from chrisallenlane/go-install
Build/CI corrections
2022-07-05 12:24:48 -04:00
Christopher Allen Lane d8f405c112 chore(ci): use Go 1.18 in CI
Use Go 1.18 in the CI pipeline.
2022-07-05 11:59:08 -04:00
Chris Allen Lane f8403ff241
Merge pull request #676 from chrisallenlane/install-md
docs: create `INSTALLING.md`
2022-07-05 11:24:02 -04:00
Christopher Allen Lane 65f6be3fd8 docs: create `INSTALLING.md`
- Create `INSTALLING.md`
- Update the `README.md`
2022-07-05 11:19:40 -04:00
Chris Allen Lane 1cb53697d2
Merge pull request #674 from cheat/4.2.5
4.2.5
2022-07-04 22:37:19 -04:00
Christopher Allen Lane 14f321b0e6 chore: bump version to `4.2.5` 2022-07-04 22:00:35 -04:00
Christopher Allen Lane d3250fda79 chore(deps): upgrade vendored dependencies 2022-07-04 22:00:35 -04:00
Zhizhen He c482488c41 fix: replace Parse with ParseArgs
Parse() is deprecated
2022-07-04 22:00:35 -04:00
Chris Allen Lane fe8f39013e
Merge pull request #673 from chrisallenlane/win-compat
fix: Windows compatibility
2022-07-04 17:03:11 -04:00
Christopher Allen Lane 1016b20ef2 chore: bump version to `4.2.4`
Bump version to `4.2.4`. This version contains numerous Windows fixes
and improvements.
2022-07-04 16:58:58 -04:00
Christopher Allen Lane def8985dcd fix: Windows support
Fix an issue whereby the installer installed cheatsheets into the wrong
directory on Windows. This occurred because previously `path.Join` was
used where `path/filepath.Join` should have been used.

This matters, because the former always uses `/` as the path separator,
whereas the latter will use `/` or `\` as is appropriate for the
runtime environment.

This should resolve bullet point 4 in #665.
2022-07-04 16:55:57 -04:00
Christopher Allen Lane e6f12147df fix: config fixes for Windows
- Update the default config file to use `more` instead of `less` as the
  default pager, in order to support Windows out-of-the-box. (#655, #665).

- Use `terminal` Chroma formatter (rather than `terminal16m`) in order
  to accommodate less capable terminal emulators like `cmd.exe` by
  default. Similarly, default to `colorize: false` in configs (changed
  from `true`) (#665).

- Comment out default `style` in order to avoid printing ANSI color
  codes into terminals without color support (#665)

- Attempt to intelligently choose a default editor, rather than rely on
  a hard-coded `vim` in the configs. This should make it easier to use
  `cheat` immediately without needing to specify configs. It should also
  improve `cheat`'s Windows compatibility. (#665)
2022-07-04 16:06:37 -04:00
Christopher Allen Lane a8c2c396ed feat(build): crate `docker-run` target
Create a `docker-run` `make` target for opening a shell in an Alpine
container for development.
2022-07-04 13:13:27 -04:00
Christopher Allen Lane 35262df4f2 fix(build): Windows executable packaging
Fix an issue whereby the Windows zip release contained an extraneous
(and annoying) `dist` parent directory.
2022-07-04 12:34:06 -04:00
Chris Allen Lane 12ffa4cb5c
Merge pull request #644 from cheat/develop
Windows fixes, Android support
2021-10-09 12:13:01 -04:00
Chris Allen Lane d9c602f9e1
Merge pull request #643 from chrisallenlane/android
fix(Paths): Android support
2021-10-09 11:30:18 -04:00
Christopher Allen Lane b67ff8b6a8 fix(Paths): Android support
Add `"android"` to the explicit whitelist of supported operating
systems.  This may resolve incompatibilities with certain Android
environments.
2021-10-09 11:27:38 -04:00
Christopher Allen Lane a500a621a1 chore: bump version
Bump version to 4.2.3.
2021-10-09 10:59:02 -04:00
Chris Allen Lane 23b6928874
Merge pull request #639 from mattn/fix-windows
Fix Windows
2021-10-09 10:10:39 -04:00
Chris Allen Lane 9de39fb12b
Merge pull request #634 from cheat/dependabot/go_modules/github.com/mattn/go-isatty-0.0.14
chore(deps): bump github.com/mattn/go-isatty from 0.0.13 to 0.0.14
2021-10-09 09:51:49 -04:00
Chris Allen Lane ad501c4cbe
Merge pull request #641 from OmgImAlexis/patch-1
chore: fix typo in comment
2021-10-09 09:39:41 -04:00
Christopher Allen Lane f17de401e5 docs(CONTRIBUTING): pr against `develop`
Add a note to `CONTRIBUTING.md` requesting that contributors open
pull-requests against the `develop` branch.
2021-10-09 09:34:23 -04:00
Alexis Tyler 2c097adeda
chore: fix typo in comment 2021-09-30 07:30:20 +09:30
Yasuhiro Matsumoto b825e0f535
Fix Windows 2021-09-29 01:33:59 +09:00
dependabot[bot] 8385277b28
chore(deps): bump github.com/mattn/go-isatty from 0.0.13 to 0.0.14
Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.13 to 0.0.14.
- [Release notes](https://github.com/mattn/go-isatty/releases)
- [Commits](https://github.com/mattn/go-isatty/compare/v0.0.13...v0.0.14)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-isatty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-07 22:10:46 +00:00
Chris Lane 768d55e5d4 chore: bump version
Bump version to `4.2.2`.
2021-06-08 21:02:03 -04:00
Chris Lane 6aedc5c116 chore: whitespace edit on `Makefile` 2021-06-08 20:59:57 -04:00
Chris Lane e881bb1f97 chore: update `go.sum` 2021-06-08 20:59:57 -04:00
Chris Lane 501f9c66ad deps: upgrade dependencies 2021-06-08 20:59:57 -04:00
Pablo Lecolinet a2aa82d9f3 Add ARM64/ARMv8 build 2021-06-08 20:59:57 -04:00
PabloLec 018bce7ad5 Fix ZSH autocompletion 2021-06-07 11:42:47 +02:00
Chris Allen Lane 17acefdd9b
Merge pull request #617 from bernermic/master
Adds some git helper scripts
2021-05-14 12:21:26 -04:00
Michael Berner 37918e09a4
Adds some git helper scripts 2021-05-07 20:53:54 +02:00
Chris Allen Lane 86967873a8 Merge pull request #623 from cheat/github-actions
chore: migrate into Github Actions
2021-05-03 17:02:27 -04:00
Chris Lane d237d98c15 chore: migrate into Github Actions
Replace Travis CI integration in favor of Github Actions.
2021-05-03 16:43:27 -04:00
Chris Allen Lane eb9b3e7798
Merge pull request #624 from cheat/dependabot/add-v2-config-file
chore: upgrade to GitHub-native Dependabot
2021-05-03 14:32:08 -04:00
dependabot-preview[bot] b0a351033d
Upgrade to GitHub-native Dependabot 2021-04-29 20:40:56 +00:00
Chris Allen Lane 1eb44e8809
Merge pull request #621 from chrisallenlane/v4.2.1
Squashed commit of the following:
2021-04-28 12:55:17 -04:00
Chris Lane 55b18b4897 Squashed commit of the following:
commit 95479c8ad744db48386a5c78e54ef8da80e9120b
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Wed Apr 28 12:26:32 2021 -0400

    chore(version): bump version to 4.2.1

commit 6956f51cae
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Wed Apr 28 12:24:21 2021 -0400

    fix(Makefile): `vendor-update`

    Update the `vendor-update` build target to run `go mod vendor` after
    updating dependencies.

commit 0aca411279
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Wed Apr 28 12:23:24 2021 -0400

    chore(deps): update dependencies

commit e847956b02
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Wed Apr 28 08:26:51 2021 -0400

    chore(deps): build updates

    - Upgrade `go` to `1.16.3`

    - Attempt to fix build errors regarding dependencies
2021-04-28 12:35:32 -04:00
Chris Allen Lane 883a17092f
Merge pull request #606 from chrisallenlane/4.2.0
4.2.0
2020-11-28 11:27:09 -05:00
Chris Lane 4f2a57fce8 fix(view): whitespace corrections
- Fix bug whereby `--all` flag would conflict with pager

- Fix whitespace inconsistencies among view and search outputs
2020-11-28 11:18:16 -05:00
Chris Lane ecc96c64f9 refactor(installer): externalize installer
Move installation-related code out of `main.go` and into a new
`installer.Run` method.
2020-11-28 10:32:37 -05:00
Chris Lane a81dd96ff4 fix: rename display.go
Rename `display.go` to `write.go`. (I forgot to do this previously.)
2020-11-27 23:05:02 -05:00
Chris Lane fb538baba5 chore(version): bump to `4.2.0` 2020-11-27 22:57:25 -05:00
Chris Lane 1a7b5c6127 feat(display): make `Faint` respect `Colorize`
Make `display.Faint` respect the `Colorize` config value.
2020-11-27 22:50:55 -05:00
Chris Lane cdddfbb516 chore: rename `display.Display`
Rename `display.Display` to `display.Write` for clarity and to reduce
"stutter".
2020-11-27 22:35:24 -05:00
Chris Lane 4ef4c35d8c feat(search): search all cheatpaths
Update the search function. It now searches all cheatpaths all the time,
as if `--all` were implicitly passed.
2020-11-27 22:31:16 -05:00
Chris Lane a58294859e chore: spelling
`s/pathSheets/pathsheets/g` in `cmd_list` for consistency elsewhere.
2020-11-27 22:26:14 -05:00
Chris Lane 606092e288 feat(search): improve search output formatting
Improve the search output formatting.
2020-11-27 17:06:02 -05:00
Chris Lane 233a9de1aa feat: implement `--all` flag
Implement an `--all` flag that can be used to view cheatsheets on all
chaetpaths. (Resolves #548)
2020-11-27 16:39:34 -05:00
Chris Lane aa16f68620 feat(display): add methods to `display`
- Add `indent`, `faint`, and `underline` methods to `display`
- Add tests for the above
2020-11-27 16:14:33 -05:00
Chris Lane 367673d5d9 chore(dependencies): update dependencies
Run `make vendor-update`.
2020-11-27 09:51:39 -05:00
Chris Lane 08fb9e11a9 feat(Makefile): add `vendor-update`
Add `vendor-update` target to `Makefile`, which updates all dependencies
to their newest versions.
2020-11-27 09:50:11 -05:00
Chris Lane 3f4d4bddb2 feat(tests): add unit-tests
Add unit-tests for `sheets.Load`.
2020-11-11 19:33:31 -05:00
Chris Allen Lane 6c6753b35c
Merge pull request #599 from chrisallenlane/issue-597
fix: update installation instructions in README
2020-11-07 18:56:37 -05:00
Chris Lane 0718b606e1 fix(README): clarify installation verbiage
Update the installation verbiage in the `README` for clarity
(issue #597).
2020-11-07 18:48:24 -05:00
Chris Lane 857119b443 feat(Docker): create development Docker image
- Create Docker image to be used for experimentation during development
- Create targets in `Makefile` pertaining to the above
2020-11-07 18:47:24 -05:00
Chris Allen Lane f421483eea
Merge pull request #596 from chrisallenlane/v4.1.1
v4.1.1
2020-11-03 18:32:25 -05:00
Chris Lane 4adddbf504 chore: bump version to v4.1.1 2020-11-03 18:05:46 -05:00
Chris Lane b9c86b6975 chore(dependencies): update dependencies 2020-11-03 17:59:56 -05:00
Chris Lane 0b21ccf6f8 feat(tests): improve test coverage 2020-11-03 17:29:49 -05:00
Chris Allen Lane a3ad8c5101
Merge pull request #595 from chrisallenlane/codeql
feat: integrate CodeQL build action
2020-11-01 10:51:38 -05:00
Chris Lane bacb74929a feat: integrate CodeQL build action 2020-11-01 10:47:25 -05:00
Chris Allen Lane 82e1c27494
Merge pull request #588 from chrisallenlane/bare-tag
feat: implement `cheat -t` shorthand
2020-09-05 09:05:09 -04:00
Chris Lane 45beeb2edb chore: bump version to 4.1.0 2020-09-05 08:56:51 -04:00
Chris Lane c2c479b36c feat: support `-t` shorthand
Make `cheat -t <tag>` function as a shorthand for `cheat -l -t <tag>`.
2020-09-02 17:17:44 -04:00
Chris Allen Lane cb0243e7fc
Merge pull request #580 from ryaanwells/patch-1
Fixing "cheetsheet" typo in README.md
2020-08-23 15:25:55 -04:00
Ryan Wells e5d04d41ea
Fixing "cheetsheet" typo in tags_test.go 2020-08-21 15:45:49 +01:00
Ryan Wells 2474ea4fb1
Fixing "cheetsheet" typo in README.md 2020-08-18 14:53:15 +01:00
Chris Allen Lane 7467c9fbc0
Merge pull request #578 from chrisallenlane/v4.0.3
chore: update dependencies
2020-08-08 13:45:10 -04:00
Chris Lane dfba3da003 chore: update dependencies 2020-08-08 10:29:29 -04:00
Chris Allen Lane ad7ad64a75
Merge pull request #573 from chrisallenlane/linux-conf
fix(config): add /etc/cheat config path
2020-07-11 08:12:04 -04:00
Chris Lane c4dcfd5da0 fix(config): add /etc/cheat config path
Add `/etc/cheat/conf.yml` to default config paths. See #568 for context.
2020-07-09 18:28:10 -04:00
Chris Allen Lane 278a5d9154
Merge pull request #570 from cheat/v4.0.1
fix(search): fix pagination error
2020-06-30 07:26:30 -04:00
Chris Lane 9fa0c466fd fix(search): fix pagination error
Fix the paginator when used in combination with the `-s` (search)
subcommand. Previously, it would not behave as intended, because `cheat`
was writing to `stdout` at inappropriate times.
2020-06-30 07:21:21 -04:00
Chris Allen Lane 4e9b2928b3
Merge pull request #569 from chrisallenlane/dev
v4.0.0
2020-06-25 19:05:37 -04:00
Chris Lane fa5eb44be8 chore: bump version to 4.0.0 2020-06-25 18:53:27 -04:00
Chris Lane 49afd7c16b feat: modify return codes
Modify exit codes. `cheat` now returns an exit code value `2` on errors
pertaining to a cheatsheet not being found.

BREAKING CHANGE
2020-06-25 18:38:03 -04:00
Chris Lane 59d5c96c24 feat(pagination): implement paginated output
Implement a `pager` config option. If configured, `cheat` will
automatically pipe output through the configured pager (where
appropriate).
2020-06-25 18:21:51 -04:00
Chris Allen Lane 8e602b0e93
Merge pull request #563 from chrisallenlane/3.10.1
feat(makefile): support 32-bit systems
2020-05-14 20:11:14 -04:00
Chris Lane fb04cb1fcd feat(makefile): support 32-bit systems
- Update the `Makefile` to additionally output a 386 binary (#562)
- Update the dependencies (#561)
- Bump version
2020-05-14 20:02:35 -04:00
Chris Allen Lane d42726101e
Merge pull request #560 from cheat/3.10.0
3.10.0
2020-05-11 20:20:09 -04:00
Chris Lane 93b3a711f5 chore: bump version to 3.10.0 2020-05-11 20:15:26 -04:00
Chris Lane 9c3d41c8bd Merge branch 'syohex-syohex/improve-completion' into 3.10.0 2020-05-11 20:13:58 -04:00
Chris Lane 4eeec6c868 chore(deps): resolve dependency conflict 2020-05-11 20:12:28 -04:00
dependabot-preview[bot] 1b17ab1914
chore(deps): bump github.com/alecthomas/chroma from 0.7.1 to 0.7.3
Bumps [github.com/alecthomas/chroma](https://github.com/alecthomas/chroma) from 0.7.1 to 0.7.3.
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v0.7.1...v0.7.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-11 07:20:59 +00:00
Shohei YOSHIDA 477650ee44 Improve zsh completion 2020-04-12 11:03:08 +09:00
Chris Allen Lane c4dd3b52fd
Merge pull request #554 from chrisallenlane/master
v3.9.0
2020-04-07 18:59:18 -04:00
Chris Lane e8a0ea0dc3 chore(version): bump version to 3.9.0 2020-04-07 18:50:00 -04:00
Chris Lane 992ee66a56 Merge branch 'add-homebrew-release-flow' of https://github.com/chenrui333/cheat into v3.9.0 2020-04-07 18:48:57 -04:00
Chris Lane c9840c2d6f docs(autocompletion): update autocompletion docs
Update the autocompletion documentation (in `README` and `man` page) to
include information about configuring `zsh` autocompletion script.
2020-04-07 18:47:35 -04:00
Chris Lane bd53768f67 chore(zsh): set execute perm on cheat.zsh
Set execute permission on the `zsh` autocompletion script.
2020-04-07 18:41:00 -04:00
raochsinai 8092687956 change taglist and pathlist to local variable. 2020-04-07 14:56:34 +08:00
raochsinai 16ade50672 add complete script for zsh. 2020-04-03 15:34:01 +08:00
Rui Chen 62c80d76eb
Automate homebrew release flow 2020-03-24 21:21:54 -04:00
Chris Allen Lane 3e67eaa3b7
Merge pull request #546 from chrisallenlane/issue-272
docs(man): implement manpage
2020-03-24 20:55:57 -04:00
Chris Lane 38b13655fe chore(version): bump version to 3.8.0 2020-03-24 20:54:40 -04:00
Chris Lane 749d5c1182 docs(man): implement manpage
- Implement `make man` to generate a manpage

- Change verb tense in `make` help text
2020-03-24 20:46:48 -04:00
Chris Allen Lane 521f83377c
Merge pull request #545 from chrisallenlane/issue-544
fix(frontmatter): resolve #544
2020-03-11 19:03:06 -04:00
Chris Lane b15ff10537 chore(version): bump version to 3.7.1 2020-03-11 18:55:59 -04:00
Chris Lane 5288bd0c1c fix(frontmatter): resolve issue #544
- Fix error in `frontmatter.Parse` which would cause a `panic` when
  encountering malformed frontmatter

- Add a unit-test to cover the above
2020-03-11 18:54:46 -04:00
Chris Lane bddbee4158 fix(logging): improve sheets load logging
Improve logging in `sheets.Load`:

- Make error verbage consistant with verbiage elsewhere

- Add more information to logging statements
2020-03-11 18:51:06 -04:00
Chris Lane ce27cf2cc0 chore(version): bump version to 3.7.0 2020-03-06 20:19:31 -05:00
Chris Lane 5733b1d6d4 chore(vendor): update vendor dependencies 2020-03-06 20:18:41 -05:00
Chris Lane 2d221050d8 Merge branch '1-14' into 3.7.0 2020-03-06 20:17:54 -05:00
Chris Lane ce37b670c7 feat(installer): implement "installer"
Squashed commit of the following:

commit 5c322e79b7
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Fri Mar 6 19:56:56 2020 -0500

    docs(README): update the `README`

    Update the `README` to document the improved config-generation
    mechanism.

commit 803e1f014c
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Fri Mar 6 19:19:49 2020 -0500

    feat(config-init): platform-specific pathing

    Update `--init` subcommand to rely upon the same platform-detection
    intelligence that was previously implemented by the "installer".

    The installer and `--init` should now produce identical config files.

commit 99c48097e2
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Fri Mar 6 18:26:33 2020 -0500

    feat(installer): platform-correct config templating

    Modify the "installer" to populate cheatpaths with sensible defaults
    based on the detection of the user's operating system and environment.

commit 8e1580ff5a
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Thu Mar 5 20:19:58 2020 -0500

    fix(tests): fix `config.Paths` tests

    Refactor `config.Paths` (by externalizing a call to `homedir.Dir`) to
    decouple it from filesystem paths, thus facilitating cleaner unit-tests.

commit a08dca70d9
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Thu Mar 5 18:14:27 2020 -0500

    feat(installer): default path selection

    Modify the installer to improve default config and cheatsheet path
    selection.

commit e15bc6c966
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Thu Mar 5 17:49:50 2020 -0500

    fix(typo): correct comment typo in `main.go`

commit efd09575df
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Thu Mar 5 17:46:49 2020 -0500

    feat(config): refactor config path detection

    Previously, failing other checks, on Unix and BSD systems,
    `config.Paths` would attempt to compute the user's home directory by
    reading the `HOME` environment variable.

    This change deprecates that approach with a call to `homedir.Dir`, which
    is used elsewhere throughout the application.

commit ec10244ebe
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Thu Mar 5 17:15:28 2020 -0500

    chore(installer): delete unused file

    Delete `installer/installer.go`, which (in hindsight) was unnecessary.

commit ebd9ec6287
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Wed Mar 4 19:31:13 2020 -0500

    wip(installer): stub experimental "installer"

    Stubs out an experimental "installer" that will help new users to
    quickly configure `cheat`.

commit ecac5a0971
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Wed Mar 4 19:30:12 2020 -0500

    chore(dependencies): updates vendored dependencies
2020-03-06 20:17:26 -05:00
Rui Chen 47a9eeb4fd go mod vendor 2020-03-03 18:28:13 -05:00
Rui Chen be56c9cf0c Bump golang to v1.14 2020-03-03 18:27:31 -05:00
Chris Allen Lane 7be57cb01c
Merge pull request #540 from chrisallenlane/master
fix: README and Makefil
2020-02-26 19:29:04 -05:00
Chris Lane 8453af8601 fix(Makefile): build targets
Adds missing dependencies to the `build` and `install` targets.
2020-02-26 19:21:07 -05:00
Chris Lane 6e388c3693 fix(README): typo fix
Fixes a bad link to the completion scripts in the `README`.
2020-02-26 19:20:24 -05:00
Chris Allen Lane b13246978a
Merge pull request #538 from chrisallenlane/improved-search
feat(search): issue #260
2020-02-15 18:07:40 -05:00
Chris Lane a39d36cd34 chore: bumps version to 3.6.0 2020-02-15 16:13:33 -05:00
Chris Lane 87cba04ff2 chore: DRY out colorization code
Creates a `sheet.Colorize` method that DRYs out code that was duplicated
among `cmd_search` and `cmd_view`.
2020-02-15 16:11:15 -05:00
Chris Lane bc623da74b feat: issue #260
Dramatically improves the usefulness of `--search` by outputting
"chunked" results. This removes the need (usually) to search and then
manually open a cheatsheet.
2020-02-15 15:56:25 -05:00
Chris Lane a6c25d4b9c feat: --search improvements
- Deprecates the `Match` struct

- Applies syntax highlighting to search results output in a manner
  consistent with the 'View' output

- Refactors search to move colorization functionality outside of its
  concern
2020-02-15 14:42:06 -05:00
Chris Lane e24ac2b385 chore: deprecates line numbers in search
Removes line numbers from search result output as part of the
refactoring effort.
2020-02-15 10:11:25 -05:00
Chris Lane e0c35a74d4 chore: removes colorization from search
Deprecates `mgutz/ansi` and removes colorization from search as a first
step toward improving the functionality.
2020-02-15 10:01:44 -05:00
Chris Lane 3e4c1818a9 feat: single-sheet searching
`--search` will now honor the `<cheatsheet>` argument, making it
possible to constrain a search to a single cheatsheet.
2020-02-15 06:38:57 -05:00
Chris Lane 7b4a268ebd fix: resolves #301
Fixes an issue whereby `cheat` would crash when `$EDITOR` (or
equivalent) contained arguments.
2020-02-02 15:56:18 -05:00
Chris Allen Lane f7183aa17a
Merge pull request #536 from chrisallenlane/issue-301
fix: issue #301
2020-02-02 15:54:47 -05:00
Chris Lane 1ce6c29e6a fix: issue #301
Attempts to resolve issue #301, whereby `cheat` fails if `$EDITOR` (or
equivalent) contains flags.
2020-02-02 15:48:45 -05:00
Chris Allen Lane 219db679e1
Merge pull request #535 from chrisallenlane/master
chore: bumps version to 3.5.0
2020-02-02 14:54:46 -05:00
Chris Lane 53177cb09d chore: bumps version to 3.5.0
Squashed commit of the following:

commit 8b74d50f1f
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Sun Feb 2 14:40:23 2020 -0500

    chore: updates README

    Edits the `README` to provid updated information regarding the shell
    autocompletion scripts and `fzf` integration.

commit 9868ba2d68
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Sun Feb 2 14:39:04 2020 -0500

    chore: modifies envvar check

    Modifies the `CHEAT_USE_FZF` envvar check within the bash autocompletion
    script for clarity.

commit ac1012f743
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Sun Feb 2 14:25:34 2020 -0500

    chore: renames autocompletion scripts

    Renames autocompletion scripts to conform with the conventions
    established in `scop/bash-completion`.

commit c8747bd91d
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Sun Feb 2 14:23:03 2020 -0500

    feat: improved bash autocompletions

    - Dramatically improves quality of bash autocompletions

    - Provides optional integration with `fzf`

commit 825bd0139d
Author: Chris Lane <chris@chris-allen-lane.com>
Date:   Sun Feb 2 09:19:46 2020 -0500

    chore: deletes `fzf.bash`

    Deletes `fzf.bash`, which was always intended to be a temporary
    placeholder anticipating future improvements.
2020-02-02 14:50:16 -05:00
Chris Allen Lane ef7a41f9a9
Merge pull request #533 from chrisallenlane/master
chore: creates `make coverage` target
2020-01-31 14:07:20 -05:00
Chris Lane 008316d030 chore: creates `make coverage` target
Creates a `make coverage` target that generates a unit-testing coverage
report.
2020-01-31 14:01:57 -05:00
Chris Lane a59c019642 chore: removes `bin` directory
Removes the `bin` directory. It has been obsoleted by the `Makefile`,
and no downstream packages appeared to depend on it.
2020-01-31 13:30:21 -05:00
Chris Allen Lane 57225442be
Merge pull request #532 from chrisallenlane/auto-config-bugfix
fix(config generation): issue #501
2020-01-30 20:12:24 -05:00
Chris Lane 2c7ce48859 chore: multi-os builds on travis
Modifies `.travis.yml` to specify that builds be performed on both Linux
and MacOSX. Experimental.
2020-01-30 20:08:11 -05:00
Chris Lane a3fe4f40bb chore: bumps version to 3.4.1 2020-01-30 20:06:06 -05:00
Chris Lane 506fb8be15 fix: XDG_CONFIG_HOME mishandling
Attempts to resolve an issue regarding automatic config file generation,
as referenced in #501. This issue is believed occur when
`XDG_CONFIG_HOME` is unset.
2020-01-30 19:59:35 -05:00
Chris Lane 408e944eea chore: refactors `config.path` (small)
Performs a minor refactoring on `config.Paths` to consistently use
`path.Join` when computing config directory paths. Previously, both
`path.Join` and `fmt.Sprintf` were being used, strictly due to an
oversight.
2020-01-30 19:45:02 -05:00
Chris Lane 8a313b92ca chore: implements unit-tests for `config.Paths` 2020-01-30 19:25:53 -05:00
Chris Lane 6912771c39 chore: refactors `config.Paths`
Refactors the reading of multiple envvars out of `config.Paths` in order
to facilitate cleaner unit-testing.
2020-01-30 18:48:36 -05:00
Chris Allen Lane d4c6200702
Merge pull request #531 from chrisallenlane/auto-config
feat(configs): config auto-generation
2020-01-29 14:22:12 -05:00
Chris Lane 9251849d23 chore: bumps version to 3.4.0 2020-01-29 14:17:06 -05:00
Chris Lane 313b5ebd27 feat: config auto-generation
`cheat` now attempts to auto-generate a config file if one cannot be
found on the filesystem.
2020-01-29 14:08:03 -05:00
Chris Lane ca91b25b02 fix: logging on failed configs
Fixes an issue whereby the error message generated when a config file
could not be loaded (due to a symlink resolution failure) would fail to
print the config file path to `stderr`.
2020-01-29 14:05:27 -05:00
Chris Lane bbf6af50b1 chore: modifies example config
- Generally reduces the complexity demonstrated in the example configs.
  The prior complexity appears to have confused some new users.

- Removes the `dotfiles` references in the example configs. This idiom
  likewise appears to have confused some new users.

- Adds some instruction regarding how to download and configure the
  "community" cheatsheets (`cheat/cheatsheets`).
2020-01-29 14:01:19 -05:00
Chris Lane 9f05442bce fix: Makefile
Resolves an error whereby `make build` would fail to call `go generate`
prior to calling `go build`.
2020-01-29 13:59:31 -05:00
Chris Lane 3fc4c2f89e chore: Makefile
Makes some adjustments to the behaviors of `make tags` and `make
distclean`.
2020-01-29 09:36:59 -05:00
Chris Lane 9e88ff2642 chore: Makefile adjustment
Previously, `build-release` would produce `.gz` files which had the
execute permission set. This modifies the `Makefile` to `chmod -x` the
`.gz` files after compression.
2020-01-29 09:11:06 -05:00
Chris Allen Lane e3764b81e7
3.3.2 (#530)
* chore: trivial Makefile changes

Trivial `Makefile` changes regarding file pathing.

* chore: build flags

- Passes `-trimpath` flag to build tasks

- Creates `make ci` task and modifies `travis.yml` to use it

- Bumps version to `3.3.2`
2020-01-29 08:50:47 -05:00
Chris Allen Lane 3786ac96a5
3.3.1 (#529)
* makefile wip

* feat: adds Makefile

Adds a `Makefile` for managing build-related tasks.

* chore: updates dependencies

* chore: updates dependencies

* chore: updates bin scripts

- Removes `build_release.sh`
- Places deprecation notice in `build_devel.sh`, as its purpose has been
  superceded by the `Makefile`.

* chore: updates bin scripts

- Removes `build_release.sh`
- Places deprecation notice in `build_devel.sh`, as its purpose has been
  superceded by the `Makefile`.

* fix: Makefile

Makes several corrections and improvements to the `Makefile`:

- Previously, the `ifeq` rules were not behaving as intended, due to
  false assumptions regarding how `make` fundamentally behaves.
  Malfunctioning imperative-style programming has been replaced with
  declarative rules to repair this issue.

- Previously, all release executables were zipped after compilation. In
  order to spare non-Windows users from (possibly) needing to install a
  package to unzip the executables, all non-Windows binaries are now
  compressed with `gzip`. (Windows executables are still compressed with
  `zip`.)

- Removes a bit of needlessly verbosity in several rules and paths.

* chore: updates dependencies

* chore: bumps version to 3.3.1
2020-01-25 14:44:51 -05:00
aperum 4cb7a3b42c add FreeBSD to the supported systems in config.Path (#528) 2020-01-25 14:33:15 -05:00
dependabot-preview[bot] ff6a866abe chore(deps): bump gopkg.in/yaml.v2 from 2.2.7 to 2.2.8 (#527)
Bumps [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml) from 2.2.7 to 2.2.8.
- [Release notes](https://github.com/go-yaml/yaml/releases)
- [Commits](https://github.com/go-yaml/yaml/compare/v2.2.7...v2.2.8)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-25 14:20:06 -05:00
dependabot-preview[bot] 2e7ccb2a68 chore(deps): bump github.com/mattn/go-isatty from 0.0.11 to 0.0.12 (#526)
Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.11 to 0.0.12.
- [Release notes](https://github.com/mattn/go-isatty/releases)
- [Commits](https://github.com/mattn/go-isatty/compare/v0.0.11...v0.0.12)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-25 14:19:57 -05:00
Chris Lane 126231db1f feat: makefile release compression
The `Makefile` `build-release` target now zips all executables after
compilation.
2020-01-20 13:54:53 -05:00
Chris Allen Lane 91f0d02de2
v3.3.0 (#525)
* feat: directory-scoped cheatpaths

`cheat` now searches for a `.cheat` directory in the current working
directory. If found, that directory is (temporarily) appended to the slice
of cheatpaths.

* makefile wip

* fix: appeases linter

Appeases linter (`go vet`) by adding quotation marks to YAML struct
tags.

* chore: modifies .gitignore

Adds `tag` to `.gitignore`

* feat: adds Makefile

Adds a `Makefile` for managing build-related tasks.

* chore: documents directory-local paths

Adds documentation regarding the new directory-local cheatpath
functionality.

* chore: updates dependencies

* chore: bumps version to 3.3.0

* chore: updates bin scripts

- Removes `build_release.sh`
- Places deprecation notice in `build_devel.sh`, as its purpose has been
  superceded by the `Makefile`.
2020-01-20 12:34:48 -05:00
Chris Allen Lane 815e714fb4
Merge pull request #523 from cheat/dependabot/go_modules/github.com/alecthomas/chroma-0.7.1
chore(deps): bump github.com/alecthomas/chroma from 0.7.0 to 0.7.1
2020-01-19 17:05:16 -05:00
dependabot-preview[bot] bd3986a051
chore(deps): bump github.com/alecthomas/chroma from 0.7.0 to 0.7.1
Bumps [github.com/alecthomas/chroma](https://github.com/alecthomas/chroma) from 0.7.0 to 0.7.1.
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v0.7.0...v0.7.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-09 07:03:22 +00:00
Chris Allen Lane f47b75edc0
Merge pull request #521 from chrisallenlane/master
chore: dependencies and build script
2019-12-28 10:08:32 -05:00
Chris Lane 180ee20f77 chore: bumps version to 3.2.2 2019-12-28 09:58:48 -05:00
Chris Lane 9b86c583f8 chore: updates `chroma` dependency 2019-12-28 09:56:19 -05:00
Chris Lane e1f7828869 Merge branch 'master' of github.com:cheat/cheat into issue-520 2019-12-28 09:54:39 -05:00
Chris Lane 7f3ae2ab30 feat: ARM support
Modifies `bin/build_release.sh` to compile releases for ARM versions 5,
6, and 7.
2019-12-28 09:53:55 -05:00
Chris Allen Lane bbd03a1bb8
Merge pull request #515 from cheat/dependabot/go_modules/github.com/alecthomas/chroma-0.7.0
chore(deps): bump github.com/alecthomas/chroma from 0.6.9 to 0.7.0
2019-12-28 09:53:22 -05:00
Chris Lane 326c54147b chore: updates dependencies 2019-12-28 09:07:37 -05:00
dependabot-preview[bot] efcedaedec
chore(deps): bump github.com/alecthomas/chroma from 0.6.9 to 0.7.0
Bumps [github.com/alecthomas/chroma](https://github.com/alecthomas/chroma) from 0.6.9 to 0.7.0.
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v0.6.9...v0.7.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-28 14:06:13 +00:00
Chris Allen Lane 301cbefb0c
Merge pull request #519 from cheat/dependabot/go_modules/github.com/mattn/go-isatty-0.0.11
chore(deps): bump github.com/mattn/go-isatty from 0.0.10 to 0.0.11
2019-12-28 09:04:43 -05:00
dependabot-preview[bot] 9a481f7e75
chore(deps): bump github.com/mattn/go-isatty from 0.0.10 to 0.0.11
Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.10 to 0.0.11.
- [Release notes](https://github.com/mattn/go-isatty/releases)
- [Commits](https://github.com/mattn/go-isatty/compare/v0.0.10...v0.0.11)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-11 07:07:50 +00:00
Chris Allen Lane e2920bd922
Merge pull request #514 from chrisallenlane/master
fix: colorization errors
2019-11-24 12:17:19 -05:00
Chris Lane 973a1f59ea chore: version bump
Bumps version to `3.2.1`.
2019-11-24 12:03:26 -05:00
Chris Lane 3afea0972c fix: colorization errors
- Corrects an error with `--search`. Previously, `--search` was not
  aware of whether it was outputted to a TTY, and would apply colorization
  at all times. This resulted in unwanted behavior when, for example,
  piping search results into a paginator.

- Corrects an error with `--color`. Previously, `--color` would be
  ignored if output was being written to a non-TTY. This made it
  impossible, for example, to `cheat tar --color | less -R`, as
  colorization would always be stripped. The behavior of `--color` has
  been modified such that it now behaves similarly to `--color=always` in
  other applications.
2019-11-23 13:47:08 -05:00
Chris Allen Lane f86633ca1c
Merge pull request #511 from chrisallenlane/master
Resolves #504
2019-11-20 19:02:50 -05:00
Chris Lane a01a3491a4 chore: version bump
Bumps version to 3.2.0.
2019-11-20 18:56:50 -05:00
Chris Lane daa43d3867 feat: implements filter on `-l`
Implements filtering by pattern with `-l`. Resolves #504.
2019-11-20 18:55:43 -05:00
Chris Lane e94a1e22df chore: updates vendored dependencies 2019-11-20 18:55:24 -05:00
Chris Allen Lane 5046975a0f
Merge pull request #510 from cheat/dependabot/go_modules/gopkg.in/yaml.v2-2.2.7
chore(deps): bump gopkg.in/yaml.v2 from 2.2.5 to 2.2.7
2019-11-20 18:53:54 -05:00
dependabot-preview[bot] 198156a299
chore(deps): bump gopkg.in/yaml.v2 from 2.2.5 to 2.2.7
Bumps [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml) from 2.2.5 to 2.2.7.
- [Release notes](https://github.com/go-yaml/yaml/releases)
- [Commits](https://github.com/go-yaml/yaml/compare/v2.2.5...v2.2.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-20 07:07:43 +00:00
Chris Lane a7067279df v3.1.1
Bumps version to `3.1.1`.
2019-11-19 19:59:45 -05:00
Stefan Cosma a8e6fdb18a Properly trim path separators on Windows 2019-11-20 01:10:19 +01:00
Chris Allen Lane bbfa4efdb7
Merge pull request #508 from exploide/fish-autocompletion
fish autocompletion: updated to cheat 3.1.0
2019-11-17 18:12:41 -05:00
exploide 741ad91389 fish autocompletion: updated to cheat 3.1.0 2019-11-17 19:33:33 +01:00
Chris Allen Lane 573d43a7e6
Merge pull request #506 from chrisallenlane/3.1.0
3.1.0
2019-11-16 09:27:18 -05:00
Chris Lane 879e8f2be4 chore: trivial copy change in docopt.txt 2019-11-16 09:23:45 -05:00
Chris Lane eab3c14f1f fix: broken logline in --search
Resolves a malformed logging statement in `cmd_search` which would never
actually write to stderr.
2019-11-16 09:18:45 -05:00
Chris Lane 9a6130b6b7 feat: --rm and --tags
- Implements the `--rm` command (#483)
- Implements the `--tags` command (#484)
- Bumps version to `3.1.0`
2019-11-16 09:18:32 -05:00
Chris Lane aeaf01e1de feat: implements --tags
Implements `--tags`, which lists all tags in use.
2019-11-14 21:56:11 -05:00
Chris Lane 09c29a322f chore: bumps version to 3.0.7 2019-11-12 19:16:21 -05:00
Chris Lane 0525b2331b chore: updates vendored dependencies
Updates `alecthomas/chroma` and `gopkg.in/yaml.v2` to newer versions.
2019-11-12 19:03:59 -05:00
Chris Lane 27a4991a3a feat: updates build scripts
Build scripts now invoke `go mod vendor` and `go mod tidy` early in the
build process, to ensure that the `vendor` directory stays consistent
with the `go.mod` et. al.
2019-11-12 19:02:42 -05:00
Chris Lane 4dda412dcb Merge branch 'dependabot/go_modules/github.com/alecthomas/chroma-0.6.9' 2019-11-12 18:55:16 -05:00
Chris Lane bfb60764ad Merge branch 'master' into dependabot/go_modules/github.com/alecthomas/chroma-0.6.9 2019-11-12 18:54:02 -05:00
Chris Lane 3a97c680bb Merge branch 'dependabot/go_modules/gopkg.in/yaml.v2-2.2.5' 2019-11-12 18:53:40 -05:00
dependabot-preview[bot] edc0fe41ef
Bump github.com/alecthomas/chroma from 0.6.8 to 0.6.9
Bumps [github.com/alecthomas/chroma](https://github.com/alecthomas/chroma) from 0.6.8 to 0.6.9.
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v0.6.8...v0.6.9)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-12 23:42:53 +00:00
dependabot-preview[bot] 9e49bf8e9c
Bump gopkg.in/yaml.v2 from 2.2.4 to 2.2.5
Bumps [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml) from 2.2.4 to 2.2.5.
- [Release notes](https://github.com/go-yaml/yaml/releases)
- [Commits](https://github.com/go-yaml/yaml/compare/v2.2.4...v2.2.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-12 23:42:30 +00:00
Chris Lane 50dc3c8b29 fix: resolves #498
Resolves an issue whereby background colors were written to the terminal
output when colorization was applied.
2019-11-07 20:24:08 -05:00
Chris Lane e08a4f3cec chore: bumps version to 3.0.5 2019-11-07 20:00:57 -05:00
Chris Lane d6ebe0799d fix: repairs broken config unit-tests
Repairs the `config` unit-tests which were broken with `3.0.4`. It does
so by providing a simple switch that allows us to disable the resolution
of filesystem symlinks when running tests.
2019-11-07 19:59:18 -05:00
Chris Lane 51aaaf3423 chore: removes the `tj/front` dependency
Removes the `tj/front` dependency due to licensing concerns. See #490.
2019-11-07 06:46:34 -05:00
Chris Lane 197ff58796 fix: comments out more broken tests
Comments out another test broken by `3.0.4`.
2019-11-05 20:02:16 -05:00
Chris Lane b8f512aae8 fix: comments out broken test
Comments out a test that was broken with changes introduced in `3.0.4`.
2019-11-05 19:58:08 -05:00
Chris Lane e7a1a296e3 chore: version bump
Version bump to `3.0.4`.
2019-11-05 19:19:17 -05:00
Chris Lane f7c093bec0 fix: resolves #474
Resolves #474, whereby `cheat` failed to resolve symlinks. The root
cause was that `path/filepath#Walk` simply does not resolve symlinks:

  https://golang.org/pkg/path/filepath/#Walk
2019-11-05 19:08:40 -05:00
Chris Lane c47b7f81aa fix: file permissions
Set executable permission on `scripts/cheat-autocompletion.fish`.
2019-11-05 18:49:05 -05:00
Chris Lane 52081b97ac Merge branch 'fish-completions' of github.com:exploide/cheat into exploide-fish-completions 2019-11-05 18:46:58 -05:00
Chris Lane 1dda796e7c fix: resolves #492
Fixes an issue whereby hidden files (like `.DS_Store`) may be wrongly
loaded as cheatsheets.
2019-11-05 18:44:47 -05:00
exploide 67469b0afa added cheat 3.x autocompletion file for fish shell, see #476 2019-11-05 17:29:32 +01:00
exploide 4f8431a600 fixed some cli option typos 2019-11-05 15:53:21 +01:00
Chris Lane 5301442f7c chore: issue template labels 2019-11-04 18:43:52 -05:00
Chris Allen Lane cd45efcdec Update issue templates 2019-11-04 18:42:21 -05:00
Chris Lane c31786fc5b chore: updated the bug report template 2019-11-04 18:40:46 -05:00
Chris Allen Lane 934c36ad77 Update issue templates 2019-11-04 18:37:26 -05:00
Chris Allen Lane 2c0099c28a
Merge pull request #479 from zhujian0805/master
adding the cheat autocomplete script back :)
2019-11-04 18:12:12 -05:00
Chris Lane 33ac3d34d1 fix: removed colorization on non-tty
Fixes a bug whereby `cheat` would apply ANSI colorization even when
outputting into a non-TTY.
2019-10-29 19:47:56 -04:00
James Zhu 749173f1f6 remove the title as cheat -l lists the title as well. 2019-10-22 11:58:51 +08:00
James Zhu 33e33dc7b7 adding the autocompletion back 2019-10-21 18:07:26 +08:00
James Zhu 201cd1d629 integrate with bash complete for autocompletion 2019-10-21 15:18:47 +08:00
1873 changed files with 318130 additions and 141580 deletions

23
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,23 @@
---
name: Bug report
about: Submit a bug report
title: ''
labels: 'bug'
assignees: ''
---
Thanks for submitting a bug report. Please provide the following information:
**A description of the problem**
Describe the problem here.
**cheat version info**
Please paste the output of `cheat -v` here.
**cheat configuration info**
If your bug pertains to how cheatsheets are loaded and/or displayed, please
paste here the following information:
1. The output of `cheat -d`
2. The contents of your `conf.yml` file

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: 'enhancement'
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

11
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: gomod
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
ignore:
- dependency-name: github.com/alecthomas/chroma
versions:
- 0.9.1

46
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,46 @@
---
name: Go
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
# TODO: is it possible to DRY out these jobs? Aside from `runs-on`, they are
# identical.
# See: https://github.com/actions/runner/issues/1182
build-linux:
runs-on: [ubuntu-latest]
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.19
- name: Set up Revive (linter)
run: go get -u github.com/boyter/scc github.com/mgechev/revive
env:
GO111MODULE: "off"
- name: Build
run: make build
- name: Test
run: make test
build-osx:
runs-on: [macos-latest]
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.19
- name: Set up Revive (linter)
run: go get -u github.com/boyter/scc github.com/mgechev/revive
env:
GO111MODULE: "off"
- name: Build
run: make build
- name: Test
run: make test

30
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -0,0 +1,30 @@
---
name: CodeQL
on:
push:
branches: [master]
pull_request:
branches: [master]
schedule:
- cron: '45 23 * * 0'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [go]
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

19
.github/workflows/homebrew.yml vendored Normal file
View File

@ -0,0 +1,19 @@
---
name: homebrew
on:
push:
tags: '*'
jobs:
homebrew:
name: Bump Homebrew formula
runs-on: ubuntu-latest
steps:
- uses: mislav/bump-homebrew-formula-action@v1
with:
# A PR will be sent to github.com/Homebrew/homebrew-core to update
# this formula:
formula-name: cheat
env:
COMMITTER_TOKEN: ${{ secrets.COMMITTER_TOKEN }}

1
.gitignore vendored
View File

@ -1 +1,2 @@
dist
tags

View File

@ -1,4 +0,0 @@
language: go
go:
- 1.13.x

View File

@ -19,7 +19,8 @@ tracker][issues] to discuss with the maintainer whether it would be considered
for merging.
`cheat` is mostly mature and feature-complete, but may still have some room for
new features.
new features. See [HACKING.md][hacking] for a quick-start guide to `cheat`
development.
#### Add documentation ####
Did you encounter features, bugs, edge-cases, use-cases, or environment
@ -35,9 +36,13 @@ Are you unable to do the above, but still want to contribute? You can help
`cheat` simply by telling others about it. Share it with friends and coworkers
that might benefit from using it.
#### Pull Requests ####
Please open all pull-requests against the `develop` branch.
[cheat]: https://github.com/cheat/cheat
[cheatsheets]: https://github.com/cheat/cheatsheets
[hacking]: HACKING.md
[issues]: https://github.com/cheat/cheat/issues
[pr]: https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork
[wiki]: https://github.com/cheat/cheat/wiki

8
Dockerfile Normal file
View File

@ -0,0 +1,8 @@
# NB: this image isn't used anywhere in the build pipeline. It exists to
# conveniently facilitate ad-hoc experimentation in a sandboxed environment
# during development.
FROM golang:1.15-alpine
RUN apk add git less make
WORKDIR /app

57
HACKING.md Normal file
View File

@ -0,0 +1,57 @@
Hacking
=======
The following is a quickstart guide for developing `cheat`.
## 1. Install system dependencies
Before you begin, you must install a handful of system dependencies. The
following are required, and must be available on your `PATH`:
- `git`
- `go` (>= 1.17 is recommended)
- `make`
The following dependencies are optional:
- `docker`
- `pandoc` (necessary to generate a `man` page)
## 2. Install utility applications
Run `make setup` to install `scc` and `revive`, which are used by various
`make` targets.
## 3. Development workflow
After your environment has been configured, your development workflow will
resemble the following:
1. Make changes to the `cheat` source code.
2. Run `make test` to run unit-tests.
3. Fix compiler errors and failing tests as necessary.
4. Run `make`. A `cheat` executable will be written to the `dist` directory.
5. Use the new executable by running `dist/cheat <command>`.
6. Run `make install` to install `cheat` to your `PATH`.
7. Run `make build-release` to build cross-platform binaries in `dist`.
8. Run `make clean` to clean the `dist` directory when desired.
You may run `make help` to see a list of available `make` commands.
### Developing with docker
It may be useful to test your changes within a pristine environment. An
Alpine-based docker container has been provided for that purpose.
If you would like to build the docker container, run:
```sh
make docker-setup
```
To shell into the container, run:
```sh
make docker-sh
```
The `cheat` source code will be mounted at `/app` within the container.
If you would like to destroy this container, you may run:
```sh
make distclean
```
[go]: https://go.dev/

79
INSTALLING.md Normal file
View File

@ -0,0 +1,79 @@
Installing
==========
`cheat` has no runtime dependencies. As such, installing it is generally
straightforward. There are a few methods available:
### Install manually
#### Unix-like
On Unix-like systems, you may simply paste the following snippet into your terminal:
```sh
cd /tmp \
&& wget https://github.com/cheat/cheat/releases/download/4.4.2/cheat-linux-amd64.gz \
&& gunzip cheat-linux-amd64.gz \
&& chmod +x cheat-linux-amd64 \
&& sudo mv cheat-linux-amd64 /usr/local/bin/cheat
```
You may need to need to change the version number (`4.4.2`) and the archive
(`cheat-linux-amd64.gz`) depending on your platform.
See the [releases page][releases] for a list of supported platforms.
#### Windows
TODO: community support is requested here. Please open a PR if you'd like to
contribute installation instructions for Windows.
### Install via `go install`
If you have `go` version `>=1.17` available on your `PATH`, you can install
`cheat` via `go install`:
```sh
go install github.com/cheat/cheat/cmd/cheat@latest
```
### Install via package manager
Several community-maintained packages are also available:
Package manager | Package(s)
---------------- | -----------
aur | [cheat][pkg-aur-cheat], [cheat-bin][pkg-aur-cheat-bin]
brew | [cheat][pkg-brew]
docker | [docker-cheat][pkg-docker]
nix | [nixos.cheat][pkg-nix]
snap | [cheat][pkg-snap]
<!--[pacman][] |-->
## Configuring
Three things must be done before you can use `cheat`:
1. A config file must be generated
2. [`cheatpaths`][cheatpaths] must be configured
3. [Community cheatsheets][community] must be downloaded
On first run, `cheat` will run an installer that will do all of the above
automatically. After the installer is complete, it is strongly advised that you
view the configuration file that was generated, as you may want to change some
of its default values (to enable colorization, change the paginator, etc).
### conf.yml ###
`cheat` is configured by a YAML file that will be auto-generated on first run.
By default, the config file is assumed to exist on an XDG-compliant
configuration path like `~/.config/cheat/conf.yml`. If you would like to store
it elsewhere, you may export a `CHEAT_CONFIG_PATH` environment variable that
specifies its path:
```sh
export CHEAT_CONFIG_PATH="~/.dotfiles/cheat/conf.yml"
```
[cheatpaths]: README.md#cheatpaths
[community]: https://github.com/cheat/cheatsheets/
[pkg-aur-cheat-bin]: https://aur.archlinux.org/packages/cheat-bin
[pkg-aur-cheat]: https://aur.archlinux.org/packages/cheat
[pkg-brew]: https://formulae.brew.sh/formula/cheat
[pkg-docker]: https://github.com/bannmann/docker-cheat
[pkg-nix]: https://search.nixos.org/packages?channel=unstable&show=cheat&from=0&size=50&sort=relevance&type=packages&query=cheat
[pkg-snap]: https://snapcraft.io/cheat
[releases]: https://github.com/cheat/cheat/releases

218
Makefile Normal file
View File

@ -0,0 +1,218 @@
# paths
makefile := $(realpath $(lastword $(MAKEFILE_LIST)))
cmd_dir := ./cmd/cheat
dist_dir := ./dist
# executables
CAT := cat
COLUMN := column
CTAGS := ctags
DOCKER := docker
GO := go
GREP := grep
GZIP := gzip --best
LINT := revive
MAN := man
MKDIR := mkdir -p
PANDOC := pandoc
RM := rm
SCC := scc
SED := sed
SORT := sort
ZIP := zip -m
docker_image := cheat-devel:latest
# build flags
BUILD_FLAGS := -ldflags="-s -w" -mod vendor -trimpath
GOBIN :=
TMPDIR := /tmp
# release binaries
releases := \
$(dist_dir)/cheat-darwin-amd64 \
$(dist_dir)/cheat-linux-386 \
$(dist_dir)/cheat-linux-amd64 \
$(dist_dir)/cheat-linux-arm5 \
$(dist_dir)/cheat-linux-arm6 \
$(dist_dir)/cheat-linux-arm64 \
$(dist_dir)/cheat-linux-arm7 \
$(dist_dir)/cheat-netbsd-amd64 \
$(dist_dir)/cheat-openbsd-amd64 \
$(dist_dir)/cheat-solaris-amd64 \
$(dist_dir)/cheat-windows-amd64.exe
## build: build an executable for your architecture
.PHONY: build
build: | clean $(dist_dir) generate fmt lint vet vendor man
$(GO) build $(BUILD_FLAGS) -o $(dist_dir)/cheat $(cmd_dir)
## build-release: build release executables
.PHONY: build-release
build-release: $(releases)
# cheat-darwin-amd64
$(dist_dir)/cheat-darwin-amd64: prepare
GOARCH=amd64 GOOS=darwin \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-linux-386
$(dist_dir)/cheat-linux-386: prepare
GOARCH=386 GOOS=linux \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-linux-amd64
$(dist_dir)/cheat-linux-amd64: prepare
GOARCH=amd64 GOOS=linux \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-linux-arm5
$(dist_dir)/cheat-linux-arm5: prepare
GOARCH=arm GOOS=linux GOARM=5 \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-linux-arm6
$(dist_dir)/cheat-linux-arm6: prepare
GOARCH=arm GOOS=linux GOARM=6 \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-linux-arm7
$(dist_dir)/cheat-linux-arm7: prepare
GOARCH=arm GOOS=linux GOARM=7 \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-linux-arm64
$(dist_dir)/cheat-linux-arm64: prepare
GOARCH=arm64 GOOS=linux \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-netbsd-amd64
$(dist_dir)/cheat-netbsd-amd64: prepare
GOARCH=amd64 GOOS=netbsd \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-openbsd-amd64
$(dist_dir)/cheat-openbsd-amd64: prepare
GOARCH=amd64 GOOS=openbsd \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-plan9-amd64
$(dist_dir)/cheat-plan9-amd64: prepare
GOARCH=amd64 GOOS=plan9 \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-solaris-amd64
$(dist_dir)/cheat-solaris-amd64: prepare
GOARCH=amd64 GOOS=solaris \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-windows-amd64
$(dist_dir)/cheat-windows-amd64.exe: prepare
GOARCH=amd64 GOOS=windows \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(ZIP) $@.zip $@ -j
# ./dist
$(dist_dir):
$(MKDIR) $(dist_dir)
.PHONY: generate
generate:
$(GO) generate $(cmd_dir)
## install: build and install cheat on your PATH
.PHONY: install
install: build
$(GO) install $(BUILD_FLAGS) $(GOBIN) $(cmd_dir)
## clean: remove compiled executables
.PHONY: clean
clean:
$(RM) -f $(dist_dir)/* $(cmd_dir)/str_config.go $(cmd_dir)/str_usage.go
## distclean: remove the tags file
.PHONY: distclean
distclean:
$(RM) -f tags
@$(DOCKER) image rm -f $(docker_image)
## setup: install revive (linter) and scc (sloc tool)
.PHONY: setup
setup:
GO111MODULE=off $(GO) get -u github.com/boyter/scc github.com/mgechev/revive
## sloc: count "semantic lines of code"
.PHONY: sloc
sloc:
$(SCC) --exclude-dir=vendor
## tags: build a tags file
.PHONY: tags
tags:
$(CTAGS) -R --exclude=vendor --languages=go
## man: build a man page
# NB: pandoc may not be installed, so we're ignoring this error on failure
.PHONY: man
man:
-$(PANDOC) -s -t man doc/cheat.1.md -o doc/cheat.1
## vendor: download, tidy, and verify dependencies
.PHONY: vendor
vendor:
$(GO) mod vendor && $(GO) mod tidy && $(GO) mod verify
## vendor-update: update vendored dependencies
vendor-update:
$(GO) get -t -u ./... && $(GO) mod vendor && $(GO) mod tidy && $(GO) mod verify
## fmt: run go fmt
.PHONY: fmt
fmt:
$(GO) fmt ./...
## lint: lint go source files
.PHONY: lint
lint: vendor
$(LINT) -exclude vendor/... ./...
## vet: vet go source files
.PHONY: vet
vet:
$(GO) vet ./...
## test: run unit-tests
.PHONY: test
test:
$(GO) test ./...
## coverage: generate a test coverage report
.PHONY: coverage
coverage:
$(GO) test ./... -coverprofile=$(TMPDIR)/cheat-coverage.out && \
$(GO) tool cover -html=$(TMPDIR)/cheat-coverage.out
## check: format, lint, vet, vendor, and run unit-tests
.PHONY: check
check: | vendor fmt lint vet test
.PHONY: prepare
prepare: | clean $(dist_dir) generate vendor fmt lint vet test
## docker-setup: create a docker image for use during development
.PHONY: docker-setup
docker-setup:
$(DOCKER) build -t $(docker_image) -f Dockerfile .
## docker-sh: shell into the docker development container
.PHONY: docker-sh
docker-sh:
$(DOCKER) run -v $(shell pwd):/app -ti $(docker_image) /bin/ash
## help: display this help text
.PHONY: help
help:
@$(CAT) $(makefile) | \
$(SORT) | \
$(GREP) "^##" | \
$(SED) 's/## //g' | \
$(COLUMN) -t -s ':'

187
README.md
View File

@ -1,8 +1,9 @@
![Workflow status](https://github.com/cheat/cheat/actions/workflows/build.yml/badge.svg)
cheat
=====
[![Build Status](https://travis-ci.com/cheat/cheat.svg?branch=master)](https://travis-ci.com/cheat/cheat)
`cheat` allows you to create and view interactive cheatsheets on the
command-line. It was designed to help remind \*nix system administrators of
options for commands that they use frequently, but not frequently enough to
@ -41,91 +42,6 @@ tar -xjvf '/path/to/foo.tgz'
tar -cjvf '/path/to/foo.tgz' '/path/to/foo/'
```
Installing
----------
`cheat` has no dependencies. To install it, download the executable from the
[releases][] page and place it on your `PATH`.
Configuring
-----------
### conf.yml ###
`cheat` is configured by a YAML file that can be generated with `cheat --init`:
```sh
mkdir -p ~/.config/cheat && cheat --init > ~/.config/cheat/conf.yml
```
By default, the config file is assumed to exist on an XDG-compliant
configuration path like `~/.config/cheat/conf.yml`. If you would like to store
it elsewhere, you may export a `CHEAT_CONFIG_PATH` environment variable that
specifies its path:
```sh
export CHEAT_CONFIG_PATH="~/.dotfiles/cheat/conf.yml"
```
Cheatsheets
-----------
Cheatsheets are plain-text files with no file extension, and are named
according to the command used to view them:
```sh
cheat tar # file is named "tar"
cheat foo/bar # file is named "bar", in a "foo" subdirectory
```
Cheatsheet text may optionally be preceeded by a YAML frontmatter header that
assigns tags and specifies syntax:
```
---
syntax: javascript
tags: [ array, map ]
---
// To map over an array:
const squares = [1, 2, 3, 4].map(x => x * x);
```
The `cheat` executable includes no cheatsheets, but [community-sourced
cheatsheets are available][cheatsheets].
Cheatpaths
----------
Cheatsheets are stored on "cheatpaths", which are directories that contain
cheetsheets. Cheatpaths are specified in the `conf.yml` file.
It can be useful to configure `cheat` against multiple cheatpaths. A common
pattern is to store cheatsheets from multiple repositories on individual
cheatpaths:
```yaml
# conf.yml:
# ...
cheatpaths:
- name: community # a name for the cheatpath
path: ~/documents/cheat/community # the path's location on the filesystem
tags: [ community ] # these tags will be applied to all sheets on the path
readonly: true # if true, `cheat` will not create new cheatsheets here
- name: personal
path: ~/documents/cheat/personal # this is a separate directory and repository than above
tags: [ personal ]
readonly: false # new sheets may be written here
# ...
```
The `readonly` option instructs `cheat` not to edit (or create) any cheatsheets
on the path. This is useful to prevent merge-conflicts from arising on upstream
cheatsheet repositories.
If a user attempts to edit a cheatsheet on a read-only cheatpath, `cheat` will
transparently copy that sheet to a writeable directory before opening it for
editing.
Usage
-----
To view a cheatsheet:
@ -178,21 +94,100 @@ To search (by regex) for cheatsheets that contain an IP address:
cheat -r -s '(?:[0-9]{1,3}\.){3}[0-9]{1,3}'
```
Flags may be combined in inuitive ways. Example: to search sheets on the
Flags may be combined in intuitive ways. Example: to search sheets on the
"personal" cheatpath that are tagged with "networking" and match a regex:
```sh
cheat -p personal -t networking -s --regex '(?:[0-9]{1,3}\.){3}[0-9]{1,3}'
cheat -p personal -t networking --regex -s '(?:[0-9]{1,3}\.){3}[0-9]{1,3}'
```
Advanced Usage
Installing
----------
For installation and configuration instructions, see [INSTALLING.md][].
Cheatsheets
-----------
Cheatsheets are plain-text files with no file extension, and are named
according to the command used to view them:
```sh
cheat tar # file is named "tar"
cheat foo/bar # file is named "bar", in a "foo" subdirectory
```
Cheatsheet text may optionally be preceeded by a YAML frontmatter header that
assigns tags and specifies syntax:
```
---
syntax: javascript
tags: [ array, map ]
---
// To map over an array:
const squares = [1, 2, 3, 4].map(x => x * x);
```
The `cheat` executable includes no cheatsheets, but [community-sourced
cheatsheets are available][cheatsheets]. You will be asked if you would like to
install the community-sourced cheatsheets the first time you run `cheat`.
Cheatpaths
----------
Cheatsheets are stored on "cheatpaths", which are directories that contain
cheatsheets. Cheatpaths are specified in the `conf.yml` file.
It can be useful to configure `cheat` against multiple cheatpaths. A common
pattern is to store cheatsheets from multiple repositories on individual
cheatpaths:
```yaml
# conf.yml:
# ...
cheatpaths:
- name: community # a name for the cheatpath
path: ~/documents/cheat/community # the path's location on the filesystem
tags: [ community ] # these tags will be applied to all sheets on the path
readonly: true # if true, `cheat` will not create new cheatsheets here
- name: personal
path: ~/documents/cheat/personal # this is a separate directory and repository than above
tags: [ personal ]
readonly: false # new sheets may be written here
# ...
```
The `readonly` option instructs `cheat` not to edit (or create) any cheatsheets
on the path. This is useful to prevent merge-conflicts from arising on upstream
cheatsheet repositories.
If a user attempts to edit a cheatsheet on a read-only cheatpath, `cheat` will
transparently copy that sheet to a writeable directory before opening it for
editing.
### Directory-scoped Cheatpaths ###
At times, it can be useful to closely associate cheatsheets with a directory on
your filesystem. `cheat` facilitates this by searching for a `.cheat` folder in
the current working directory. If found, the `.cheat` directory will
(temporarily) be added to the cheatpaths.
Autocompletion
--------------
`cheat` may be integrated with [fzf][]. See [fzf.bash][bash] for instructions.
(Support for other shells will be added in future releases.)
Shell autocompletion is currently available for `bash`, `fish`, and `zsh`. Copy
the relevant [completion script][completions] into the appropriate directory on
your filesystem to enable autocompletion. (This directory will vary depending
on operating system and shell specifics.)
Additionally, `cheat` supports enhanced autocompletion via integration with
[fzf][]. To enable `fzf` integration:
[Releases]: https://github.com/cheat/cheat/releases
[bash]: https://github.com/cheat/cheat/blob/master/scripts/fzf.bash
[cheatsheets]: https://github.com/cheat/cheatsheets
[fzf]: https://github.com/junegunn/fzf
1. Ensure that `fzf` is available on your `$PATH`
2. Set an envvar: `export CHEAT_USE_FZF=true`
[INSTALLING.md]: INSTALLING.md
[Releases]: https://github.com/cheat/cheat/releases
[cheatsheets]: https://github.com/cheat/cheatsheets
[completions]: https://github.com/cheat/cheat/tree/master/scripts
[fzf]: https://github.com/junegunn/fzf
[go]: https://golang.org

View File

@ -1,13 +0,0 @@
#!/bin/bash
# locate the cheat project root
BINDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
APPDIR=$(readlink -f "$BINDIR/..")
# compile the executable
cd "$APPDIR/cmd/cheat"
go clean && go generate && go build -mod vendor
mv "$APPDIR/cmd/cheat/cheat" "$APPDIR/dist/cheat"
# display a build checksum
md5sum "$APPDIR/dist/cheat"

View File

@ -1,19 +0,0 @@
#!/bin/bash
# locate the cheat project root
BINDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
APPDIR=$(readlink -f "$BINDIR/..")
# build embeds
cd "$APPDIR/cmd/cheat"
go clean && go generate
# compile AMD64 for Linux, OSX, and Windows
env GOOS=darwin GOARCH=amd64 go build -mod vendor -o \
"$APPDIR/dist/cheat-darwin-amd64" "$APPDIR/cmd/cheat"
env GOOS=linux GOARCH=amd64 go build -mod vendor -o \
"$APPDIR/dist/cheat-linux-amd64" "$APPDIR/cmd/cheat"
env GOOS=windows GOARCH=amd64 go build -mod vendor -o \
"$APPDIR/dist/cheat-win-amd64.exe" "$APPDIR/cmd/cheat"

View File

@ -1,3 +1,4 @@
//go:build ignore
// +build ignore
// This script embeds `docopt.txt and `conf.yml` into the binary during at
@ -5,13 +6,11 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
)
@ -52,10 +51,10 @@ func main() {
for _, file := range files {
// delete the outfile
os.Remove(path.Join(root, file.Out))
os.Remove(filepath.Join(root, file.Out))
// read the static template
bytes, err := ioutil.ReadFile(path.Join(root, file.In))
bytes, err := ioutil.ReadFile(filepath.Join(root, file.In))
if err != nil {
log.Fatal(err)
}
@ -64,7 +63,7 @@ func main() {
data := template(file.Method, string(bytes))
// write the file to the specified outpath
spath := path.Join(root, file.Out)
spath := filepath.Join(root, file.Out)
err = ioutil.WriteFile(spath, []byte(data), 0644)
if err != nil {
log.Fatal(err)

11
cmd/cheat/cmd_conf.go Normal file
View File

@ -0,0 +1,11 @@
package main
import (
"fmt"
"github.com/cheat/cheat/internal/config"
)
func cmdConf(_ map[string]interface{}, conf config.Config) {
fmt.Println(conf.Path)
}

View File

@ -1,28 +1,27 @@
package main
import (
"bytes"
"fmt"
"os"
"text/tabwriter"
"github.com/cheat/cheat/internal/config"
"github.com/cheat/cheat/internal/display"
)
// cmdDirectories lists the configured cheatpaths.
func cmdDirectories(opts map[string]interface{}, conf config.Config) {
func cmdDirectories(_ map[string]interface{}, conf config.Config) {
// initialize a tabwriter to produce cleanly columnized output
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
var out bytes.Buffer
w := tabwriter.NewWriter(&out, 0, 0, 1, ' ', 0)
// generate sorted, columnized output
for _, path := range conf.Cheatpaths {
fmt.Fprintln(w, fmt.Sprintf(
"%s:\t%s",
path.Name,
path.Path,
))
fmt.Fprintf(w, "%s:\t%s\n", path.Name, path.Path)
}
// write columnized output to stdout
w.Flush()
display.Write(out.String(), conf)
}

View File

@ -4,7 +4,7 @@ import (
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"github.com/cheat/cheat/internal/cheatpath"
@ -20,7 +20,7 @@ func cmdEdit(opts map[string]interface{}, conf config.Config) {
// load the cheatsheets
cheatsheets, err := sheets.Load(conf.Cheatpaths)
if err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to list cheatsheets: %v", err))
fmt.Fprintf(os.Stderr, "failed to list cheatsheets: %v\n", err)
os.Exit(1)
}
@ -58,10 +58,10 @@ func cmdEdit(opts map[string]interface{}, conf config.Config) {
}
// compute the new edit path
editpath = path.Join(writepath.Path, sheet.Title)
editpath = filepath.Join(writepath.Path, sheet.Title)
// create any necessary subdirectories
dirs := path.Dir(editpath)
dirs := filepath.Dir(editpath)
if dirs != "." {
if err := os.MkdirAll(dirs, 0755); err != nil {
fmt.Fprintf(os.Stderr, "failed to create directory: %s, %v\n", dirs, err)
@ -87,10 +87,10 @@ func cmdEdit(opts map[string]interface{}, conf config.Config) {
}
// compute the new edit path
editpath = path.Join(writepath.Path, cheatsheet)
editpath = filepath.Join(writepath.Path, cheatsheet)
// create any necessary subdirectories
dirs := path.Dir(editpath)
dirs := filepath.Dir(editpath)
if dirs != "." {
if err := os.MkdirAll(dirs, 0755); err != nil {
fmt.Fprintf(os.Stderr, "failed to create directory: %s, %v\n", dirs, err)
@ -99,8 +99,15 @@ func cmdEdit(opts map[string]interface{}, conf config.Config) {
}
}
// split `conf.Editor` into parts to separate the editor's executable from
// any arguments it may have been passed. If this is not done, the nearby
// call to `exec.Command` will fail.
parts := strings.Fields(conf.Editor)
editor := parts[0]
args := append(parts[1:], editpath)
// edit the cheatsheet
cmd := exec.Command(conf.Editor, editpath)
cmd := exec.Command(editor, args...)
cmd.Stdout = os.Stdout
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr

View File

@ -2,9 +2,56 @@ package main
import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/mitchellh/go-homedir"
"github.com/cheat/cheat/internal/config"
)
// cmdInit displays an example config file.
func cmdInit() {
fmt.Println(configs())
// get the user's home directory
home, err := homedir.Dir()
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get user home directory: %v\n", err)
os.Exit(1)
}
// read the envvars into a map of strings
envvars := map[string]string{}
for _, e := range os.Environ() {
pair := strings.SplitN(e, "=", 2)
envvars[pair[0]] = pair[1]
}
// load the config template
configs := configs()
// identify the os-specifc paths at which configs may be located
confpaths, err := config.Paths(runtime.GOOS, home, envvars)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to read config paths: %v\n", err)
os.Exit(1)
}
// determine the appropriate paths for config data and (optional) community
// cheatsheets based on the user's platform
confpath := confpaths[0]
confdir := filepath.Dir(confpath)
// create paths for community and personal cheatsheets
community := filepath.Join(confdir, "cheatsheets", "community")
personal := filepath.Join(confdir, "cheatsheets", "personal")
// template the above paths into the default configs
configs = strings.Replace(configs, "COMMUNITY_PATH", community, -1)
configs = strings.Replace(configs, "PERSONAL_PATH", personal, -1)
// output the templated configs
fmt.Println(configs)
}

View File

@ -1,13 +1,16 @@
package main
import (
"bytes"
"fmt"
"os"
"regexp"
"sort"
"strings"
"text/tabwriter"
"github.com/cheat/cheat/internal/config"
"github.com/cheat/cheat/internal/display"
"github.com/cheat/cheat/internal/sheet"
"github.com/cheat/cheat/internal/sheets"
)
@ -18,11 +21,11 @@ func cmdList(opts map[string]interface{}, conf config.Config) {
// load the cheatsheets
cheatsheets, err := sheets.Load(conf.Cheatpaths)
if err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to list cheatsheets: %v", err))
fmt.Fprintf(os.Stderr, "failed to list cheatsheets: %v\n", err)
os.Exit(1)
}
// filter cheatcheats by tag if --tag was provided
// filter cheatsheets by tag if --tag was provided
if opts["--tag"] != nil {
cheatsheets = sheets.Filter(
cheatsheets,
@ -34,8 +37,8 @@ func cmdList(opts map[string]interface{}, conf config.Config) {
// sheets with local sheets), here we simply want to create a slice
// containing all sheets.
flattened := []sheet.Sheet{}
for _, pathSheets := range cheatsheets {
for _, s := range pathSheets {
for _, pathsheets := range cheatsheets {
for _, s := range pathsheets {
flattened = append(flattened, s)
}
}
@ -45,25 +48,54 @@ func cmdList(opts map[string]interface{}, conf config.Config) {
return flattened[i].Title < flattened[j].Title
})
// exit early if no cheatsheets are available
// filter if <cheatsheet> was specified
// NB: our docopt specification is misleading here. When used in conjunction
// with `-l`, `<cheatsheet>` is really a pattern against which to filter
// sheet titles.
if opts["<cheatsheet>"] != nil {
// initialize a slice of filtered sheets
filtered := []sheet.Sheet{}
// initialize our filter pattern
pattern := "(?i)" + opts["<cheatsheet>"].(string)
// compile the regex
reg, err := regexp.Compile(pattern)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to compile regexp: %s, %v\n", pattern, err)
os.Exit(1)
}
// iterate over each cheatsheet, and pass-through those which match the
// filter pattern
for _, s := range flattened {
if reg.MatchString(s.Title) {
filtered = append(filtered, s)
}
}
flattened = filtered
}
// return exit code 2 if no cheatsheets are available
if len(flattened) == 0 {
os.Exit(0)
os.Exit(2)
}
// initialize a tabwriter to produce cleanly columnized output
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
var out bytes.Buffer
w := tabwriter.NewWriter(&out, 0, 0, 1, ' ', 0)
// write a header row
fmt.Fprintln(w, "title:\tfile:\ttags:")
// generate sorted, columnized output
fmt.Fprintln(w, "title:\tfile:\ttags:")
for _, sheet := range flattened {
fmt.Fprintln(w, fmt.Sprintf(
"%s\t%s\t%s",
sheet.Title,
sheet.Path,
strings.Join(sheet.Tags, ","),
))
fmt.Fprintf(w, "%s\t%s\t%s\n", sheet.Title, sheet.Path, strings.Join(sheet.Tags, ","))
}
// write columnized output to stdout
w.Flush()
display.Write(out.String(), conf)
}

55
cmd/cheat/cmd_remove.go Normal file
View File

@ -0,0 +1,55 @@
package main
import (
"fmt"
"os"
"strings"
"github.com/cheat/cheat/internal/config"
"github.com/cheat/cheat/internal/sheets"
)
// cmdRemove opens a cheatsheet for editing (or creates it if it doesn't exist).
func cmdRemove(opts map[string]interface{}, conf config.Config) {
cheatsheet := opts["--rm"].(string)
// load the cheatsheets
cheatsheets, err := sheets.Load(conf.Cheatpaths)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to list cheatsheets: %v\n", err)
os.Exit(1)
}
// filter cheatcheats by tag if --tag was provided
if opts["--tag"] != nil {
cheatsheets = sheets.Filter(
cheatsheets,
strings.Split(opts["--tag"].(string), ","),
)
}
// consolidate the cheatsheets found on all paths into a single map of
// `title` => `sheet` (ie, allow more local cheatsheets to override less
// local cheatsheets)
consolidated := sheets.Consolidate(cheatsheets)
// fail early if the requested cheatsheet does not exist
sheet, ok := consolidated[cheatsheet]
if !ok {
fmt.Fprintf(os.Stderr, "No cheatsheet found for '%s'.\n", cheatsheet)
os.Exit(2)
}
// fail early if the sheet is read-only
if sheet.ReadOnly {
fmt.Fprintf(os.Stderr, "cheatsheet '%s' is read-only.\n", cheatsheet)
os.Exit(1)
}
// otherwise, attempt to delete the sheet
if err := os.Remove(sheet.Path); err != nil {
fmt.Fprintf(os.Stderr, "failed to delete sheet: %s, %v\n", sheet.Title, err)
os.Exit(1)
}
}

View File

@ -7,6 +7,7 @@ import (
"strings"
"github.com/cheat/cheat/internal/config"
"github.com/cheat/cheat/internal/display"
"github.com/cheat/cheat/internal/sheets"
)
@ -18,7 +19,7 @@ func cmdSearch(opts map[string]interface{}, conf config.Config) {
// load the cheatsheets
cheatsheets, err := sheets.Load(conf.Cheatpaths)
if err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to list cheatsheets: %v", err))
fmt.Fprintf(os.Stderr, "failed to list cheatsheets: %v\n", err)
os.Exit(1)
}
@ -30,46 +31,67 @@ func cmdSearch(opts map[string]interface{}, conf config.Config) {
)
}
// consolidate the cheatsheets found on all paths into a single map of
// `title` => `sheet` (ie, allow more local cheatsheets to override less
// local cheatsheets)
consolidated := sheets.Consolidate(cheatsheets)
// iterate over each cheatpath
out := ""
for _, pathcheats := range cheatsheets {
// sort the cheatsheets alphabetically, and search for matches
for _, sheet := range sheets.Sort(consolidated) {
// sort the cheatsheets alphabetically, and search for matches
for _, sheet := range sheets.Sort(pathcheats) {
// colorize output?
colorize := false
if conf.Colorize == true || opts["--colorize"] == true {
colorize = true
}
// assume that we want to perform a case-insensitive search for <phrase>
pattern := "(?i)" + phrase
// unless --regex is provided, in which case we pass the regex unaltered
if opts["--regex"] == true {
pattern = phrase
}
// compile the regex
reg, err := regexp.Compile(pattern)
if err != nil {
fmt.Errorf("failed to compile regexp: %s, %v", pattern, err)
os.Exit(1)
}
// search the sheet
matches := sheet.Search(reg, colorize)
// display the results
if len(matches) > 0 {
fmt.Printf("%s:\n", sheet.Title)
for _, m := range matches {
fmt.Printf(" %d: %s\n", m.Line, m.Text)
// if <cheatsheet> was provided, constrain the search only to
// matching cheatsheets
if opts["<cheatsheet>"] != nil && sheet.Title != opts["<cheatsheet>"] {
continue
}
fmt.Print("\n")
// assume that we want to perform a case-insensitive search for <phrase>
pattern := "(?i)" + phrase
// unless --regex is provided, in which case we pass the regex unaltered
if opts["--regex"] == true {
pattern = phrase
}
// compile the regex
reg, err := regexp.Compile(pattern)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to compile regexp: %s, %v\n", pattern, err)
os.Exit(1)
}
// `Search` will return text entries that match the search terms.
// We're using it here to overwrite the prior cheatsheet Text,
// filtering it to only what is relevant.
sheet.Text = sheet.Search(reg)
// if the sheet did not match the search, ignore it and move on
if sheet.Text == "" {
continue
}
// if colorization was requested, apply it here
if conf.Color(opts) {
sheet.Colorize(conf)
}
// display the cheatsheet body
out += fmt.Sprintf(
"%s %s\n%s\n",
// append the cheatsheet title
sheet.Title,
// append the cheatsheet path
display.Faint(fmt.Sprintf("(%s)", sheet.CheatPath), conf),
// indent each line of content
display.Indent(sheet.Text),
)
}
}
// trim superfluous newlines
out = strings.TrimSpace(out)
// display the output
// NB: resist the temptation to call `display.Write` multiple times in the
// loop above. That will not play nicely with the paginator.
display.Write(out, conf)
}

30
cmd/cheat/cmd_tags.go Normal file
View File

@ -0,0 +1,30 @@
package main
import (
"fmt"
"os"
"github.com/cheat/cheat/internal/config"
"github.com/cheat/cheat/internal/display"
"github.com/cheat/cheat/internal/sheets"
)
// cmdTags lists all tags in use.
func cmdTags(_ map[string]interface{}, conf config.Config) {
// load the cheatsheets
cheatsheets, err := sheets.Load(conf.Cheatpaths)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to list cheatsheets: %v\n", err)
os.Exit(1)
}
// assemble the output
out := ""
for _, tag := range sheets.Tags(cheatsheets) {
out += fmt.Sprintln(tag)
}
// display the output
display.Write(out, conf)
}

View File

@ -5,9 +5,8 @@ import (
"os"
"strings"
"github.com/alecthomas/chroma/quick"
"github.com/cheat/cheat/internal/config"
"github.com/cheat/cheat/internal/display"
"github.com/cheat/cheat/internal/sheets"
)
@ -19,7 +18,7 @@ func cmdView(opts map[string]interface{}, conf config.Config) {
// load the cheatsheets
cheatsheets, err := sheets.Load(conf.Cheatpaths)
if err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to list cheatsheets: %v", err))
fmt.Fprintf(os.Stderr, "failed to list cheatsheets: %v\n", err)
os.Exit(1)
}
@ -31,42 +30,53 @@ func cmdView(opts map[string]interface{}, conf config.Config) {
)
}
// consolidate the cheatsheets found on all paths into a single map of
// `title` => `sheet` (ie, allow more local cheatsheets to override less
// local cheatsheets)
// if --all was passed, display cheatsheets from all cheatpaths
if opts["--all"].(bool) {
// iterate over the cheatpaths
out := ""
for _, cheatpath := range cheatsheets {
// if the cheatpath contains the specified cheatsheet, display it
if sheet, ok := cheatpath[cheatsheet]; ok {
// identify the matching cheatsheet
out += fmt.Sprintf("%s %s\n",
sheet.Title,
display.Faint(fmt.Sprintf("(%s)", sheet.CheatPath), conf),
)
// apply colorization if requested
if conf.Color(opts) {
sheet.Colorize(conf)
}
// display the cheatsheet
out += display.Indent(sheet.Text) + "\n"
}
}
// display and exit
display.Write(strings.TrimSuffix(out, "\n"), conf)
os.Exit(0)
}
// otherwise, consolidate the cheatsheets found on all paths into a single
// map of `title` => `sheet` (ie, allow more local cheatsheets to override
// less local cheatsheets)
consolidated := sheets.Consolidate(cheatsheets)
// fail early if the requested cheatsheet does not exist
sheet, ok := consolidated[cheatsheet]
if !ok {
fmt.Printf("No cheatsheet found for '%s'.\n", cheatsheet)
os.Exit(0)
os.Exit(2)
}
// if colorization is not desired, output un-colorized text and exit
if conf.Colorize == false && opts["--colorize"] == false {
fmt.Print(sheet.Text)
os.Exit(0)
// apply colorization if requested
if conf.Color(opts) {
sheet.Colorize(conf)
}
// otherwise, colorize the output
// if the syntax was not specified, default to bash
lex := sheet.Syntax
if lex == "" {
lex = "bash"
}
// apply syntax highlighting
err = quick.Highlight(
os.Stdout,
sheet.Text,
lex,
conf.Formatter,
conf.Style,
)
// if colorization somehow failed, output non-colorized text
if err != nil {
fmt.Print(sheet.Text)
}
// display the cheatsheet
display.Write(sheet.Text, conf)
}

View File

@ -2,16 +2,20 @@ Usage:
cheat [options] [<cheatsheet>]
Options:
--init Write a default config file to stdout
-c --colorize Colorize output
-d --directories List cheatsheet directories
-e --edit=<sheet> Edit cheatsheet
-l --list List cheatsheets
-p --path=<name> Return only sheets found on path <name>
-r --regex Treat search <phrase> as a regex
-s --search=<phrase> Search cheatsheets for <phrase>
-t --tag=<tag> Return only sheets matching <tag>
-v --version Print the version number
--init Write a default config file to stdout
-a --all Search among all cheatpaths
-c --colorize Colorize output
-d --directories List cheatsheet directories
-e --edit=<cheatsheet> Edit <cheatsheet>
-l --list List cheatsheets
-p --path=<name> Return only sheets found on cheatpath <name>
-r --regex Treat search <phrase> as a regex
-s --search=<phrase> Search cheatsheets for <phrase>
-t --tag=<tag> Return only sheets matching <tag>
-T --tags List all tags in use
-v --version Print the version number
--rm=<cheatsheet> Remove (delete) <cheatsheet>
--conf Display the config file path
Examples:
@ -33,6 +37,12 @@ Examples:
To list all available cheatsheets:
cheat -l
To list all cheatsheets whose titles match "apt":
cheat -l apt
To list all tags in use:
cheat -T
To list available cheatsheets that are tagged as "personal":
cheat -l -t personal
@ -41,3 +51,9 @@ Examples:
To search (by regex) for cheatsheets that contain an IP address:
cheat -c -r -s '(?:[0-9]{1,3}\.){3}[0-9]{1,3}'
To remove (delete) the foo/bar cheatsheet:
cheat --rm foo/bar
To view the configuration file path:
cheat --conf

View File

@ -1,3 +1,4 @@
// Package main serves as the executable entrypoint.
package main
//go:generate go run ../../build/embed.go
@ -6,19 +7,22 @@ import (
"fmt"
"os"
"runtime"
"strings"
"github.com/docopt/docopt-go"
"github.com/mitchellh/go-homedir"
"github.com/cheat/cheat/internal/cheatpath"
"github.com/cheat/cheat/internal/config"
"github.com/cheat/cheat/internal/installer"
)
const version = "3.0.2"
const version = "4.4.2"
func main() {
// initialize options
opts, err := docopt.Parse(usage(), nil, true, version, false)
opts, err := docopt.ParseArgs(usage(), nil, version)
if err != nil {
// panic here, because this should never happen
panic(fmt.Errorf("docopt failed to parse: %v", err))
@ -31,15 +35,65 @@ func main() {
os.Exit(0)
}
// load the config file
confpath, err := config.Path(runtime.GOOS)
// get the user's home directory
home, err := homedir.Dir()
if err != nil {
fmt.Fprintln(os.Stderr, "could not locate config file")
fmt.Fprintf(os.Stderr, "failed to get user home directory: %v\n", err)
os.Exit(1)
}
// read the envvars into a map of strings
envvars := map[string]string{}
for _, e := range os.Environ() {
pair := strings.SplitN(e, "=", 2)
if runtime.GOOS == "windows" {
pair[0] = strings.ToUpper(pair[0])
}
envvars[pair[0]] = pair[1]
}
// identify the os-specifc paths at which configs may be located
confpaths, err := config.Paths(runtime.GOOS, home, envvars)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to load config: %v\n", err)
os.Exit(1)
}
// search for the config file in the above paths
confpath, err := config.Path(confpaths)
if err != nil {
// prompt the user to create a config file
yes, err := installer.Prompt(
"A config file was not found. Would you like to create one now? [Y/n]",
true,
)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to create config: %v\n", err)
os.Exit(1)
}
// exit early on a negative answer
if !yes {
os.Exit(0)
}
// choose a confpath
confpath = confpaths[0]
// run the installer
if err := installer.Run(configs(), confpath); err != nil {
fmt.Fprintf(os.Stderr, "failed to run installer: %v\n", err)
os.Exit(1)
}
// notify the user and exit
fmt.Printf("Created config file: %s\n", confpath)
fmt.Println("Please read this file for advanced configuration information.")
os.Exit(0)
}
// initialize the configs
conf, err := config.New(opts, confpath)
conf, err := config.New(opts, confpath, true)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to load config: %v\n", err)
os.Exit(1)
@ -67,6 +121,9 @@ func main() {
var cmd func(map[string]interface{}, config.Config)
switch {
case opts["--conf"].(bool):
cmd = cmdConf
case opts["--directories"].(bool):
cmd = cmdDirectories
@ -76,12 +133,21 @@ func main() {
case opts["--list"].(bool):
cmd = cmdList
case opts["--tags"].(bool):
cmd = cmdTags
case opts["--search"] != nil:
cmd = cmdSearch
case opts["--rm"] != nil:
cmd = cmdRemove
case opts["<cheatsheet>"] != nil:
cmd = cmdView
case opts["--tag"] != nil && opts["--tag"].(string) != "":
cmd = cmdList
default:
fmt.Println(usage())
os.Exit(0)

View File

@ -9,10 +9,10 @@ import (
func configs() string {
return strings.TrimSpace(`---
# The editor to use with 'cheat -e <sheet>'. Defaults to $EDITOR or $VISUAL.
editor: vim
editor: EDITOR_PATH
# Should 'cheat' always colorize output?
colorize: true
colorize: false
# Which 'chroma' colorscheme should be applied to the output?
# Options are available here:
@ -21,45 +21,73 @@ style: monokai
# Which 'chroma' "formatter" should be applied?
# One of: "terminal", "terminal256", "terminal16m"
formatter: terminal16m
formatter: terminal256
# The paths at which cheatsheets are available. Tags associated with a cheatpath
# are automatically attached to all cheatsheets residing on that path.
# Through which pager should output be piped?
# 'less -FRX' is recommended on Unix systems
# 'more' is recommended on Windows
pager: PAGER_PATH
# Cheatpaths are paths at which cheatsheets are available on your local
# filesystem.
#
# Whenever cheatsheets share the same title (like 'tar'), the most local
# cheatsheets (those which come later in this file) take precedent over the
# less local sheets. This allows you to create your own "overides" for
# "upstream" cheatsheets.
# It is useful to sort cheatsheets into different cheatpaths for organizational
# purposes. For example, you might want one cheatpath for community
# cheatsheets, one for personal cheatsheets, one for cheatsheets pertaining to
# your day job, one for code snippets, etc.
#
# But what if you want to view the "upstream" cheatsheets instead of your own?
# Cheatsheets may be filtered via 'cheat -f <tag>' in combination with other
# commands. So, if you want to view the 'tar' cheatsheet that is tagged as
# 'community' rather than your own, you can use: cheat tar -f community
# Cheatpaths are scoped, such that more "local" cheatpaths take priority over
# more "global" cheatpaths. (The most global cheatpath is listed first in this
# file; the most local is listed last.) For example, if there is a 'tar'
# cheatsheet on both global and local paths, you'll be presented with the local
# one by default. ('cheat -p' can be used to view cheatsheets from alternative
# cheatpaths.)
#
# Cheatpaths can also be tagged as "read only". This instructs cheat not to
# automatically create cheatsheets on a read-only cheatpath. Instead, when you
# would like to edit a read-only cheatsheet using 'cheat -e', cheat will
# perform a copy-on-write of that cheatsheet from a read-only cheatpath to a
# writeable cheatpath.
#
# This is very useful when you would like to maintain, for example, a
# "pristine" repository of community cheatsheets on one cheatpath, and an
# editable personal reponsity of cheatsheets on another cheatpath.
#
# Cheatpaths can be also configured to automatically apply tags to cheatsheets
# on certain paths, which can be useful for querying purposes.
# Example: 'cheat -t work jenkins'.
#
# Community cheatsheets must be installed separately, though you may have
# downloaded them automatically when installing 'cheat'. If not, you may
# download them here:
#
# https://github.com/cheat/cheatsheets
cheatpaths:
# Paths that come earlier are considered to be the most "global", and will
# thus be overridden by more local cheatsheets. That being the case, you
# should probably list community cheatsheets first.
#
# Note that the paths and tags listed below are just examples. You may freely
# change them to suit your needs.
# Cheatpath properties mean the following:
# 'name': the name of the cheatpath (view with 'cheat -d', filter with 'cheat -p')
# 'path': the filesystem path of the cheatsheet directory (view with 'cheat -d')
# 'tags': tags that should be automatically applied to sheets on this path
# 'readonly': shall user-created ('cheat -e') cheatsheets be saved here?
- name: community
path: ~/.dotfiles/cheat/community
path: COMMUNITY_PATH
tags: [ community ]
readonly: true
# Maybe your company or department maintains a repository of cheatsheets as
# well. It's probably sensible to list those second.
- name: work
path: ~/.dotfiles/cheat/work
tags: [ work ]
readonly: false
# If you have personalized cheatsheets, list them last. They will take
# precedence over the more global cheatsheets.
- name: personal
path: ~/.dotfiles/cheat/personal
path: PERSONAL_PATH
tags: [ personal ]
readonly: false
# While it requires no configuration here, it's also worth noting that
# cheat will automatically append directories named '.cheat' within the
# current working directory to the 'cheatpath'. This can be very useful if
# you'd like to closely associate cheatsheets with, for example, a directory
# containing source code.
#
# Such "directory-scoped" cheatsheets will be treated as the most "local"
# cheatsheets, and will override less "local" cheatsheets. Similarly,
# directory-scoped cheatsheets will always be editable ('readonly: false').
`)
}

View File

@ -11,16 +11,20 @@ func usage() string {
cheat [options] [<cheatsheet>]
Options:
--init Write a default config file to stdout
-c --colorize Colorize output
-d --directories List cheatsheet directories
-e --edit=<sheet> Edit cheatsheet
-l --list List cheatsheets
-p --path=<name> Return only sheets found on path <name>
-r --regex Treat search <phrase> as a regex
-s --search=<phrase> Search cheatsheets for <phrase>
-t --tag=<tag> Return only sheets matching <tag>
-v --version Print the version number
--init Write a default config file to stdout
-a --all Search among all cheatpaths
-c --colorize Colorize output
-d --directories List cheatsheet directories
-e --edit=<cheatsheet> Edit <cheatsheet>
-l --list List cheatsheets
-p --path=<name> Return only sheets found on cheatpath <name>
-r --regex Treat search <phrase> as a regex
-s --search=<phrase> Search cheatsheets for <phrase>
-t --tag=<tag> Return only sheets matching <tag>
-T --tags List all tags in use
-v --version Print the version number
--rm=<cheatsheet> Remove (delete) <cheatsheet>
--conf Display the config file path
Examples:
@ -42,6 +46,12 @@ Examples:
To list all available cheatsheets:
cheat -l
To list all cheatsheets whose titles match "apt":
cheat -l apt
To list all tags in use:
cheat -T
To list available cheatsheets that are tagged as "personal":
cheat -l -t personal
@ -50,5 +60,11 @@ Examples:
To search (by regex) for cheatsheets that contain an IP address:
cheat -c -r -s '(?:[0-9]{1,3}\.){3}[0-9]{1,3}'
To remove (delete) the foo/bar cheatsheet:
cheat --rm foo/bar
To view the configuration file path:
cheat --conf
`)
}

View File

@ -1,9 +1,9 @@
---
# The editor to use with 'cheat -e <sheet>'. Defaults to $EDITOR or $VISUAL.
editor: vim
editor: EDITOR_PATH
# Should 'cheat' always colorize output?
colorize: true
colorize: false
# Which 'chroma' colorscheme should be applied to the output?
# Options are available here:
@ -12,43 +12,71 @@ style: monokai
# Which 'chroma' "formatter" should be applied?
# One of: "terminal", "terminal256", "terminal16m"
formatter: terminal16m
formatter: terminal256
# The paths at which cheatsheets are available. Tags associated with a cheatpath
# are automatically attached to all cheatsheets residing on that path.
# Through which pager should output be piped?
# 'less -FRX' is recommended on Unix systems
# 'more' is recommended on Windows
pager: PAGER_PATH
# Cheatpaths are paths at which cheatsheets are available on your local
# filesystem.
#
# Whenever cheatsheets share the same title (like 'tar'), the most local
# cheatsheets (those which come later in this file) take precedent over the
# less local sheets. This allows you to create your own "overides" for
# "upstream" cheatsheets.
# It is useful to sort cheatsheets into different cheatpaths for organizational
# purposes. For example, you might want one cheatpath for community
# cheatsheets, one for personal cheatsheets, one for cheatsheets pertaining to
# your day job, one for code snippets, etc.
#
# But what if you want to view the "upstream" cheatsheets instead of your own?
# Cheatsheets may be filtered via 'cheat -f <tag>' in combination with other
# commands. So, if you want to view the 'tar' cheatsheet that is tagged as
# 'community' rather than your own, you can use: cheat tar -f community
# Cheatpaths are scoped, such that more "local" cheatpaths take priority over
# more "global" cheatpaths. (The most global cheatpath is listed first in this
# file; the most local is listed last.) For example, if there is a 'tar'
# cheatsheet on both global and local paths, you'll be presented with the local
# one by default. ('cheat -p' can be used to view cheatsheets from alternative
# cheatpaths.)
#
# Cheatpaths can also be tagged as "read only". This instructs cheat not to
# automatically create cheatsheets on a read-only cheatpath. Instead, when you
# would like to edit a read-only cheatsheet using 'cheat -e', cheat will
# perform a copy-on-write of that cheatsheet from a read-only cheatpath to a
# writeable cheatpath.
#
# This is very useful when you would like to maintain, for example, a
# "pristine" repository of community cheatsheets on one cheatpath, and an
# editable personal reponsity of cheatsheets on another cheatpath.
#
# Cheatpaths can be also configured to automatically apply tags to cheatsheets
# on certain paths, which can be useful for querying purposes.
# Example: 'cheat -t work jenkins'.
#
# Community cheatsheets must be installed separately, though you may have
# downloaded them automatically when installing 'cheat'. If not, you may
# download them here:
#
# https://github.com/cheat/cheatsheets
cheatpaths:
# Paths that come earlier are considered to be the most "global", and will
# thus be overridden by more local cheatsheets. That being the case, you
# should probably list community cheatsheets first.
#
# Note that the paths and tags listed below are just examples. You may freely
# change them to suit your needs.
# Cheatpath properties mean the following:
# 'name': the name of the cheatpath (view with 'cheat -d', filter with 'cheat -p')
# 'path': the filesystem path of the cheatsheet directory (view with 'cheat -d')
# 'tags': tags that should be automatically applied to sheets on this path
# 'readonly': shall user-created ('cheat -e') cheatsheets be saved here?
- name: community
path: ~/.dotfiles/cheat/community
path: COMMUNITY_PATH
tags: [ community ]
readonly: true
# Maybe your company or department maintains a repository of cheatsheets as
# well. It's probably sensible to list those second.
- name: work
path: ~/.dotfiles/cheat/work
tags: [ work ]
readonly: false
# If you have personalized cheatsheets, list them last. They will take
# precedence over the more global cheatsheets.
- name: personal
path: ~/.dotfiles/cheat/personal
path: PERSONAL_PATH
tags: [ personal ]
readonly: false
# While it requires no configuration here, it's also worth noting that
# cheat will automatically append directories named '.cheat' within the
# current working directory to the 'cheatpath'. This can be very useful if
# you'd like to closely associate cheatsheets with, for example, a directory
# containing source code.
#
# Such "directory-scoped" cheatsheets will be treated as the most "local"
# cheatsheets, and will override less "local" cheatsheets. Similarly,
# directory-scoped cheatsheets will always be editable ('readonly: false').

186
doc/cheat.1 Normal file
View File

@ -0,0 +1,186 @@
.\" Automatically generated by Pandoc 2.17.1.1
.\"
.\" Define V font for inline verbatim, using C font in formats
.\" that render this, and otherwise B font.
.ie "\f[CB]x\f[]"x" \{\
. ftr V B
. ftr VI BI
. ftr VB B
. ftr VBI BI
.\}
.el \{\
. ftr V CR
. ftr VI CI
. ftr VB CB
. ftr VBI CBI
.\}
.TH "CHEAT" "1" "" "" "General Commands Manual"
.hy
.SH NAME
.PP
\f[B]cheat\f[R] \[em] create and view command-line cheatsheets
.SH SYNOPSIS
.PP
\f[B]cheat\f[R] [options] [\f[I]CHEATSHEET\f[R]]
.SH DESCRIPTION
.PP
\f[B]cheat\f[R] allows you to create and view interactive cheatsheets on
the command-line.
It was designed to help remind *nix system administrators of options for
commands that they use frequently, but not frequently enough to
remember.
.SH OPTIONS
.TP
\[en]init
Print a config file to stdout.
.TP
-c, \[en]colorize
Colorize output.
.TP
-d, \[en]directories
List cheatsheet directories.
.TP
-e, \[en]edit=\f[I]CHEATSHEET\f[R]
Open \f[I]CHEATSHEET\f[R] for editing.
.TP
-l, \[en]list
List available cheatsheets.
.TP
-p, \[en]path=\f[I]PATH\f[R]
Filter only to sheets found on path \f[I]PATH\f[R].
.TP
-r, \[en]regex
Treat search \f[I]PHRASE\f[R] as a regular expression.
.TP
-s, \[en]search=\f[I]PHRASE\f[R]
Search cheatsheets for \f[I]PHRASE\f[R].
.TP
-t, \[en]tag=\f[I]TAG\f[R]
Filter only to sheets tagged with \f[I]TAG\f[R].
.TP
-T, \[en]tags
List all tags in use.
.TP
-v, \[en]version
Print the version number.
.TP
\[en]rm=\f[I]CHEATSHEET\f[R]
Remove (deletes) \f[I]CHEATSHEET\f[R].
.SH EXAMPLES
.TP
To view the foo cheatsheet:
cheat \f[I]foo\f[R]
.TP
To edit (or create) the foo cheatsheet:
cheat -e \f[I]foo\f[R]
.TP
To edit (or create) the foo/bar cheatsheet on the `work' cheatpath:
cheat -p \f[I]work\f[R] -e \f[I]foo/bar\f[R]
.TP
To view all cheatsheet directories:
cheat -d
.TP
To list all available cheatsheets:
cheat -l
.TP
To list all cheatsheets whose titles match `apt':
cheat -l \f[I]apt\f[R]
.TP
To list all tags in use:
cheat -T
.TP
To list available cheatsheets that are tagged as `personal':
cheat -l -t \f[I]personal\f[R]
.TP
To search for `ssh' among all cheatsheets, and colorize matches:
cheat -c -s \f[I]ssh\f[R]
.TP
To search (by regex) for cheatsheets that contain an IP address:
cheat -c -r -s \f[I]`(?:[0-9]{1,3}.){3}[0-9]{1,3}'\f[R]
.TP
To remove (delete) the foo/bar cheatsheet:
cheat \[en]rm \f[I]foo/bar\f[R]
.SH FILES
.SS Configuration
.PP
\f[B]cheat\f[R] is configured via a YAML file that is conventionally
named \f[I]conf.yaml\f[R].
\f[B]cheat\f[R] will search for \f[I]conf.yaml\f[R] in varying
locations, depending upon your platform:
.SS Linux, OSX, and other Unixes
.IP "1." 3
\f[B]CHEAT_CONFIG_PATH\f[R]
.IP "2." 3
\f[B]XDG_CONFIG_HOME\f[R]/cheat/conf.yaml
.IP "3." 3
\f[B]$HOME\f[R]/.config/cheat/conf.yml
.IP "4." 3
\f[B]$HOME\f[R]/.cheat/conf.yml
.SS Windows
.IP "1." 3
\f[B]CHEAT_CONFIG_PATH\f[R]
.IP "2." 3
\f[B]APPDATA\f[R]/cheat/conf.yml
.IP "3." 3
\f[B]PROGRAMDATA\f[R]/cheat/conf.yml
.PP
\f[B]cheat\f[R] will search in the order specified above.
The first \f[I]conf.yaml\f[R] encountered will be respected.
.PP
If \f[B]cheat\f[R] cannot locate a config file, it will ask if you\[cq]d
like to generate one automatically.
Alternatively, you may also generate a config file manually by running
\f[B]cheat \[en]init\f[R] and saving its output to the appropriate
location for your platform.
.SS Cheatpaths
.PP
\f[B]cheat\f[R] reads its cheatsheets from \[lq]cheatpaths\[rq], which
are the directories in which cheatsheets are stored.
Cheatpaths may be configured in \f[I]conf.yaml\f[R], and viewed via
\f[B]cheat -d\f[R].
.PP
For detailed instructions on how to configure cheatpaths, please refer
to the comments in conf.yml.
.SS Autocompletion
.PP
Autocompletion scripts for \f[B]bash\f[R], \f[B]zsh\f[R], and
\f[B]fish\f[R] are available for download:
.IP \[bu] 2
<https://github.com/cheat/cheat/blob/master/scripts/cheat.bash>
.IP \[bu] 2
<https://github.com/cheat/cheat/blob/master/scripts/cheat.fish>
.IP \[bu] 2
<https://github.com/cheat/cheat/blob/master/scripts/cheat.zsh>
.PP
The \f[B]bash\f[R] and \f[B]zsh\f[R] scripts provide optional
integration with \f[B]fzf\f[R], if the latter is available on your
\f[B]PATH\f[R].
.PP
The installation process will vary per system and shell configuration,
and thus will not be discussed here.
.SH ENVIRONMENT
.TP
\f[B]CHEAT_CONFIG_PATH\f[R]
The path at which the config file is available.
If \f[B]CHEAT_CONFIG_PATH\f[R] is set, all other config paths will be
ignored.
.TP
\f[B]CHEAT_USE_FZF\f[R]
If set, autocompletion scripts will attempt to integrate with
\f[B]fzf\f[R].
.SH RETURN VALUES
.IP "0." 3
Successful termination
.IP "1." 3
Application error
.IP "2." 3
Cheatsheet(s) not found
.SH BUGS
.PP
See GitHub issues: <https://github.com/cheat/cheat/issues>
.SH AUTHOR
.PP
Christopher Allen Lane <chris@chris-allen-lane.com>
.SH SEE ALSO
.PP
\f[B]fzf(1)\f[R]

192
doc/cheat.1.md Normal file
View File

@ -0,0 +1,192 @@
% CHEAT(1) | General Commands Manual
NAME
====
**cheat** — create and view command-line cheatsheets
SYNOPSIS
========
| **cheat** \[options] \[_CHEATSHEET_]
DESCRIPTION
===========
**cheat** allows you to create and view interactive cheatsheets on the
command-line. It was designed to help remind \*nix system administrators of
options for commands that they use frequently, but not frequently enough to
remember.
OPTIONS
=======
--init
: Print a config file to stdout.
-c, --colorize
: Colorize output.
-d, --directories
: List cheatsheet directories.
-e, --edit=_CHEATSHEET_
: Open _CHEATSHEET_ for editing.
-l, --list
: List available cheatsheets.
-p, --path=_PATH_
: Filter only to sheets found on path _PATH_.
-r, --regex
: Treat search _PHRASE_ as a regular expression.
-s, --search=_PHRASE_
: Search cheatsheets for _PHRASE_.
-t, --tag=_TAG_
: Filter only to sheets tagged with _TAG_.
-T, --tags
: List all tags in use.
-v, --version
: Print the version number.
--rm=_CHEATSHEET_
: Remove (deletes) _CHEATSHEET_.
EXAMPLES
========
To view the foo cheatsheet:
: cheat _foo_
To edit (or create) the foo cheatsheet:
: cheat -e _foo_
To edit (or create) the foo/bar cheatsheet on the 'work' cheatpath:
: cheat -p _work_ -e _foo/bar_
To view all cheatsheet directories:
: cheat -d
To list all available cheatsheets:
: cheat -l
To list all cheatsheets whose titles match 'apt':
: cheat -l _apt_
To list all tags in use:
: cheat -T
To list available cheatsheets that are tagged as 'personal':
: cheat -l -t _personal_
To search for 'ssh' among all cheatsheets, and colorize matches:
: cheat -c -s _ssh_
To search (by regex) for cheatsheets that contain an IP address:
: cheat -c -r -s _'(?:[0-9]{1,3}\.){3}[0-9]{1,3}'_
To remove (delete) the foo/bar cheatsheet:
: cheat --rm _foo/bar_
FILES
=====
Configuration
-------------
**cheat** is configured via a YAML file that is conventionally named
_conf.yaml_. **cheat** will search for _conf.yaml_ in varying locations,
depending upon your platform:
### Linux, OSX, and other Unixes ###
1. **CHEAT_CONFIG_PATH**
2. **XDG_CONFIG_HOME**/cheat/conf.yaml
3. **$HOME**/.config/cheat/conf.yml
4. **$HOME**/.cheat/conf.yml
### Windows ###
1. **CHEAT_CONFIG_PATH**
2. **APPDATA**/cheat/conf.yml
3. **PROGRAMDATA**/cheat/conf.yml
**cheat** will search in the order specified above. The first _conf.yaml_
encountered will be respected.
If **cheat** cannot locate a config file, it will ask if you'd like to generate
one automatically. Alternatively, you may also generate a config file manually
by running **cheat --init** and saving its output to the appropriate location
for your platform.
Cheatpaths
----------
**cheat** reads its cheatsheets from "cheatpaths", which are the directories in
which cheatsheets are stored. Cheatpaths may be configured in _conf.yaml_, and
viewed via **cheat -d**.
For detailed instructions on how to configure cheatpaths, please refer to the
comments in conf.yml.
Autocompletion
--------------
Autocompletion scripts for **bash**, **zsh**, and **fish** are available for
download:
- <https://github.com/cheat/cheat/blob/master/scripts/cheat.bash>
- <https://github.com/cheat/cheat/blob/master/scripts/cheat.fish>
- <https://github.com/cheat/cheat/blob/master/scripts/cheat.zsh>
The **bash** and **zsh** scripts provide optional integration with **fzf**, if
the latter is available on your **PATH**.
The installation process will vary per system and shell configuration, and thus
will not be discussed here.
ENVIRONMENT
===========
**CHEAT_CONFIG_PATH**
: The path at which the config file is available. If **CHEAT_CONFIG_PATH** is
set, all other config paths will be ignored.
**CHEAT_USE_FZF**
: If set, autocompletion scripts will attempt to integrate with **fzf**.
RETURN VALUES
=============
0. Successful termination
1. Application error
2. Cheatsheet(s) not found
BUGS
====
See GitHub issues: <https://github.com/cheat/cheat/issues>
AUTHOR
======
Christopher Allen Lane <chris@chris-allen-lane.com>
SEE ALSO
========
**fzf(1)**

36
go.mod
View File

@ -1,14 +1,38 @@
module github.com/cheat/cheat
go 1.13
go 1.19
require (
github.com/alecthomas/chroma v0.6.7
github.com/alecthomas/chroma/v2 v2.12.0
github.com/davecgh/go-spew v1.1.1
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
github.com/go-git/go-git/v5 v5.11.0
github.com/mattn/go-isatty v0.0.20
github.com/mitchellh/go-homedir v1.1.0
github.com/tj/front v0.0.0-20170212063142-739be213b0a1
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 // indirect
gopkg.in/yaml.v2 v2.2.4
gopkg.in/yaml.v3 v3.0.1
)
require (
dario.cat/mergo v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/skeema/knownhosts v1.2.1 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/tools v0.16.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)

181
go.sum
View File

@ -1,62 +1,143 @@
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
github.com/alecthomas/chroma v0.6.7 h1:1hKci+AyKOxJrugR9veaocu9DQGR2/GecI72BpaO0Rg=
github.com/alecthomas/chroma v0.6.7/go.mod h1:zVlgtbRS7BJDrDY9SB238RmpoCBCYFlLmcfZ3durxTk=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA=
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE=
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
github.com/alecthomas/chroma/v2 v2.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF7cmWgw=
github.com/alecthomas/chroma/v2 v2.12.0/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.1.6 h1:CqB4MjHw0MFCDj+PHHjiESmHX+N7t0tJzKvC6M97BRg=
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4=
github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ=
github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/tj/front v0.0.0-20170212063142-739be213b0a1 h1:lA+aPRvltlx2fwv/BnxyYSDQo3pIeqzHgMO5GvK0T9E=
github.com/tj/front v0.0.0-20170212063142-739be213b0a1/go.mod h1:deJrtusCTptAW4EUn5vBLpl3dhNqPqUwEjWJz5UNxpQ=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU=
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,9 +1,11 @@
// Package cheatpath implements functions pertaining to cheatsheet file path
// management.
package cheatpath
// Cheatpath encapsulates cheatsheet path information
type Cheatpath struct {
Name string `yaml:name`
Path string `yaml:path`
ReadOnly bool `yaml:readonly`
Tags []string `yaml:tags`
Name string `yaml:"name"`
Path string `yaml:"path"`
ReadOnly bool `yaml:"readonly"`
Tags []string `yaml:"tags"`
}

View File

@ -46,7 +46,7 @@ func TestFilterFailure(t *testing.T) {
}
// filter the paths
paths, err := Filter(paths, "qux")
_, err := Filter(paths, "qux")
if err == nil {
t.Errorf("failed to return an error on non-existent cheatpath")
}

View File

@ -11,12 +11,10 @@ func Writeable(cheatpaths []Cheatpath) (Cheatpath, error) {
// NB: we're going backwards because we assume that the most "local"
// cheatpath will be specified last in the configs
for i := len(cheatpaths) - 1; i >= 0; i-- {
// if the cheatpath is not read-only, it is writeable, and thus returned
if cheatpaths[i].ReadOnly == false {
if !cheatpaths[i].ReadOnly {
return cheatpaths[i], nil
}
}
// otherwise, return an error

26
internal/config/color.go Normal file
View File

@ -0,0 +1,26 @@
package config
import (
"os"
"github.com/mattn/go-isatty"
)
// Color indicates whether colorization should be applied to the output
func (c *Config) Color(opts map[string]interface{}) bool {
// default to the colorization specified in the configs...
colorize := c.Colorize
// ... however, only apply colorization if we're writing to a tty...
if !isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()) {
colorize = false
}
// ... *unless* the --colorize flag was passed
if opts["--colorize"] == true {
colorize = true
}
return colorize
}

View File

@ -0,0 +1,22 @@
package config
import (
"testing"
)
// TestColor asserts that colorization rules are properly respected
func TestColor(t *testing.T) {
// mock a config
conf := Config{}
opts := map[string]interface{}{"--colorize": false}
if conf.Color(opts) {
t.Errorf("failed to respect --colorize (false)")
}
opts = map[string]interface{}{"--colorize": true}
if !conf.Color(opts) {
t.Errorf("failed to respect --colorize (true)")
}
}

View File

@ -1,30 +1,34 @@
// Package config implements functions pertaining to configuration management.
package config
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
cp "github.com/cheat/cheat/internal/cheatpath"
"github.com/mitchellh/go-homedir"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)
// Config encapsulates configuration parameters
type Config struct {
Colorize bool `yaml:colorize`
Editor string `yaml:editor`
Cheatpaths []cp.Cheatpath `yaml:cheatpaths`
Style string `yaml:style`
Formatter string `yaml:formatter`
Colorize bool `yaml:"colorize"`
Editor string `yaml:"editor"`
Cheatpaths []cp.Cheatpath `yaml:"cheatpaths"`
Style string `yaml:"style"`
Formatter string `yaml:"formatter"`
Pager string `yaml:"pager"`
Path string
}
// New returns a new Config struct
func New(opts map[string]interface{}, confPath string) (Config, error) {
func New(_ map[string]interface{}, confPath string, resolve bool) (Config, error) {
// read the config file
buf, err := ioutil.ReadFile(confPath)
buf, err := os.ReadFile(confPath)
if err != nil {
return Config{}, fmt.Errorf("could not read config file: %v", err)
}
@ -32,31 +36,71 @@ func New(opts map[string]interface{}, confPath string) (Config, error) {
// initialize a config object
conf := Config{}
// store the config path
conf.Path = confPath
// unmarshal the yaml
err = yaml.UnmarshalStrict(buf, &conf)
err = yaml.Unmarshal(buf, &conf)
if err != nil {
return Config{}, fmt.Errorf("could not unmarshal yaml: %v", err)
}
// expand ~ in config paths
// if a .cheat directory exists locally, append it to the cheatpaths
cwd, err := os.Getwd()
if err != nil {
return Config{}, fmt.Errorf("failed to get cwd: %v", err)
}
local := filepath.Join(cwd, ".cheat")
if _, err := os.Stat(local); err == nil {
path := cp.Cheatpath{
Name: "cwd",
Path: local,
ReadOnly: false,
Tags: []string{},
}
conf.Cheatpaths = append(conf.Cheatpaths, path)
}
// process cheatpaths
for i, cheatpath := range conf.Cheatpaths {
// expand ~ in config paths
expanded, err := homedir.Expand(cheatpath.Path)
if err != nil {
return Config{}, fmt.Errorf("failed to expand ~: %v", err)
}
// follow symlinks
//
// NB: `resolve` is an ugly kludge that exists for the sake of unit-tests.
// It's necessary because `EvalSymlinks` will error if the symlink points
// to a non-existent location on the filesystem. When unit-testing,
// however, we don't want to have dependencies on the filesystem. As such,
// `resolve` is a switch that allows us to turn off symlink resolution when
// running the config tests.
if resolve {
evaled, err := filepath.EvalSymlinks(expanded)
if err != nil {
return Config{}, fmt.Errorf(
"failed to resolve symlink: %s: %v",
expanded,
err,
)
}
expanded = evaled
}
conf.Cheatpaths[i].Path = expanded
}
// if an editor was not provided in the configs, look to envvars
// if an editor was not provided in the configs, attempt to choose one
// that's appropriate for the environment
if conf.Editor == "" {
if os.Getenv("VISUAL") != "" {
conf.Editor = os.Getenv("VISUAL")
} else if os.Getenv("EDITOR") != "" {
conf.Editor = os.Getenv("EDITOR")
} else {
return Config{}, fmt.Errorf("no editor set")
if conf.Editor, err = Editor(); err != nil {
return Config{}, err
}
}
@ -67,8 +111,11 @@ func New(opts map[string]interface{}, confPath string) (Config, error) {
// if a chroma formatter was not provided, set a default
if conf.Formatter == "" {
conf.Formatter = "terminal16m"
conf.Formatter = "terminal"
}
// load the pager
conf.Pager = strings.TrimSpace(conf.Pager)
return conf, nil
}

View File

@ -17,7 +17,7 @@ import (
func TestConfigSuccessful(t *testing.T) {
// initialize a config
conf, err := New(map[string]interface{}{}, mock.Path("conf/conf.yml"))
conf, err := New(map[string]interface{}{}, mock.Path("conf/conf.yml"), false)
if err != nil {
t.Errorf("failed to parse config file: %v", err)
}
@ -39,17 +39,17 @@ func TestConfigSuccessful(t *testing.T) {
// assert that the cheatpaths are correct
want := []cheatpath.Cheatpath{
cheatpath.Cheatpath{
Path: filepath.Join(home, ".dotfiles/cheat/community"),
Path: filepath.Join(home, ".dotfiles", "cheat", "community"),
ReadOnly: true,
Tags: []string{"community"},
},
cheatpath.Cheatpath{
Path: filepath.Join(home, ".dotfiles/cheat/work"),
Path: filepath.Join(home, ".dotfiles", "cheat", "work"),
ReadOnly: false,
Tags: []string{"work"},
},
cheatpath.Cheatpath{
Path: filepath.Join(home, ".dotfiles/cheat/personal"),
Path: filepath.Join(home, ".dotfiles", "cheat", "personal"),
ReadOnly: false,
Tags: []string{"personal"},
},
@ -69,7 +69,7 @@ func TestConfigSuccessful(t *testing.T) {
func TestConfigFailure(t *testing.T) {
// attempt to read a non-existent config file
_, err := New(map[string]interface{}{}, "/does-not-exit")
_, err := New(map[string]interface{}{}, "/does-not-exit", false)
if err == nil {
t.Errorf("failed to error on unreadable config")
}
@ -84,14 +84,14 @@ func TestEmptyEditor(t *testing.T) {
os.Setenv("EDITOR", "")
// initialize a config
conf, err := New(map[string]interface{}{}, mock.Path("conf/empty.yml"))
if err == nil {
t.Errorf("failed to return an error on empty editor")
conf, err := New(map[string]interface{}{}, mock.Path("conf/empty.yml"), false)
if err != nil {
t.Errorf("failed to initialize test: %v", err)
}
// set editor, and assert that it is respected
os.Setenv("EDITOR", "foo")
conf, err = New(map[string]interface{}{}, mock.Path("conf/empty.yml"))
conf, err = New(map[string]interface{}{}, mock.Path("conf/empty.yml"), false)
if err != nil {
t.Errorf("failed to init configs: %v", err)
}
@ -101,7 +101,7 @@ func TestEmptyEditor(t *testing.T) {
// set visual, and assert that it overrides editor
os.Setenv("VISUAL", "bar")
conf, err = New(map[string]interface{}{}, mock.Path("conf/empty.yml"))
conf, err = New(map[string]interface{}{}, mock.Path("conf/empty.yml"), false)
if err != nil {
t.Errorf("failed to init configs: %v", err)
}

41
internal/config/editor.go Normal file
View File

@ -0,0 +1,41 @@
package config
import (
"fmt"
"os"
"os/exec"
"runtime"
)
// Editor attempts to locate an editor that's appropriate for the environment.
func Editor() (string, error) {
// default to `notepad.exe` on Windows
if runtime.GOOS == "windows" {
return "notepad", nil
}
// look for `nano` and `vim` on the `PATH`
def, _ := exec.LookPath("editor") // default `editor` wrapper
nano, _ := exec.LookPath("nano")
vim, _ := exec.LookPath("vim")
// set editor priority
editors := []string{
os.Getenv("VISUAL"),
os.Getenv("EDITOR"),
def,
nano,
vim,
}
// return the first editor that was found per the priority above
for _, editor := range editors {
if editor != "" {
return editor, nil
}
}
// return an error if no path is found
return "", fmt.Errorf("no editor set")
}

23
internal/config/init.go Normal file
View File

@ -0,0 +1,23 @@
package config
import (
"fmt"
"os"
"path/filepath"
)
// Init initializes a config file
func Init(confpath string, configs string) error {
// assert that the config directory exists
if err := os.MkdirAll(filepath.Dir(confpath), 0755); err != nil {
return fmt.Errorf("failed to create directory: %v", err)
}
// write the config file
if err := os.WriteFile(confpath, []byte(configs), 0644); err != nil {
return fmt.Errorf("failed to create file: %v", err)
}
return nil
}

View File

@ -0,0 +1,37 @@
package config
import (
"os"
"testing"
)
// TestInit asserts that configs are properly initialized
func TestInit(t *testing.T) {
// initialize a temporary config file
confFile, err := os.CreateTemp("", "cheat-test")
if err != nil {
t.Errorf("failed to create temp file: %v", err)
}
// clean up the temp file
defer os.Remove(confFile.Name())
// initialize the config file
conf := "mock config data"
if err = Init(confFile.Name(), conf); err != nil {
t.Errorf("failed to init config file: %v", err)
}
// read back the config file contents
bytes, err := os.ReadFile(confFile.Name())
if err != nil {
t.Errorf("failed to read config file: %v", err)
}
// assert that the contents were written correctly
got := string(bytes)
if got != conf {
t.Errorf("failed to write configs: want: %s, got: %s", conf, got)
}
}

32
internal/config/pager.go Normal file
View File

@ -0,0 +1,32 @@
package config
import (
"os"
"os/exec"
"runtime"
)
// Pager attempts to locate a pager that's appropriate for the environment.
func Pager() string {
// default to `more` on Windows
if runtime.GOOS == "windows" {
return "more"
}
// if $PAGER is set, return the corresponding pager
if os.Getenv("PAGER") != "" {
return os.Getenv("PAGER")
}
// Otherwise, search for `pager`, `less`, and `more` on the `$PATH`. If
// none are found, return an empty pager.
for _, pager := range []string{"pager", "less", "more"} {
if path, err := exec.LookPath(pager); err != nil {
return path
}
}
// default to no pager
return ""
}

View File

@ -3,58 +3,10 @@ package config
import (
"fmt"
"os"
"path"
"github.com/mitchellh/go-homedir"
)
// Path returns the config file path
func Path(sys string) (string, error) {
var paths []string
// if CHEAT_CONFIG_PATH is set, return it
if os.Getenv("CHEAT_CONFIG_PATH") != "" {
// expand ~
expanded, err := homedir.Expand(os.Getenv("CHEAT_CONFIG_PATH"))
if err != nil {
return "", fmt.Errorf("failed to expand ~: %v", err)
}
return expanded, nil
// OSX config paths
} else if sys == "darwin" {
paths = []string{
path.Join(os.Getenv("XDG_CONFIG_HOME"), "/cheat/conf.yml"),
path.Join(os.Getenv("HOME"), ".config/cheat/conf.yml"),
path.Join(os.Getenv("HOME"), ".cheat/conf.yml"),
}
// Linux config paths
} else if sys == "linux" {
paths = []string{
path.Join(os.Getenv("XDG_CONFIG_HOME"), "/cheat/conf.yml"),
path.Join(os.Getenv("HOME"), ".config/cheat/conf.yml"),
path.Join(os.Getenv("HOME"), ".cheat/conf.yml"),
"/etc/cheat/conf.yml",
}
// Windows config paths
} else if sys == "windows" {
paths = []string{
fmt.Sprintf("%s/cheat/conf.yml", os.Getenv("APPDATA")),
fmt.Sprintf("%s/cheat/conf.yml", os.Getenv("PROGRAMDATA")),
}
// Unsupported platforms
} else {
return "", fmt.Errorf("unsupported os: %s", sys)
}
func Path(paths []string) (string, error) {
// check if the config file exists on any paths
for _, p := range paths {

View File

@ -0,0 +1,52 @@
package config
import (
"os"
"testing"
)
// TestPathConfigNotExists asserts that `Path` identifies non-existent config
// files
func TestPathConfigNotExists(t *testing.T) {
// package (invalid) cheatpaths
paths := []string{"/cheat-test-conf-does-not-exist"}
// assert
if _, err := Path(paths); err == nil {
t.Errorf("failed to identify non-existent config file")
}
}
// TestPathConfigExists asserts that `Path` identifies existent config files
func TestPathConfigExists(t *testing.T) {
// initialize a temporary config file
confFile, err := os.CreateTemp("", "cheat-test")
if err != nil {
t.Errorf("failed to create temp file: %v", err)
}
// clean up the temp file
defer os.Remove(confFile.Name())
// package cheatpaths
paths := []string{
"/cheat-test-conf-does-not-exist",
confFile.Name(),
}
// assert
got, err := Path(paths)
if err != nil {
t.Errorf("failed to identify config file: %v", err)
}
if got != confFile.Name() {
t.Errorf(
"failed to return config path: want: %s, got: %s",
confFile.Name(),
got,
)
}
}

59
internal/config/paths.go Normal file
View File

@ -0,0 +1,59 @@
package config
import (
"fmt"
"path/filepath"
"github.com/mitchellh/go-homedir"
)
// Paths returns config file paths that are appropriate for the operating
// system
func Paths(
sys string,
home string,
envvars map[string]string,
) ([]string, error) {
// if `CHEAT_CONFIG_PATH` is set, expand ~ and return it
if confpath, ok := envvars["CHEAT_CONFIG_PATH"]; ok {
// expand ~
expanded, err := homedir.Expand(confpath)
if err != nil {
return []string{}, fmt.Errorf("failed to expand ~: %v", err)
}
return []string{expanded}, nil
}
switch sys {
// darwin/linux/unix
case "aix", "android", "darwin", "dragonfly", "freebsd", "illumos", "ios",
"linux", "netbsd", "openbsd", "plan9", "solaris":
paths := []string{}
// don't include the `XDG_CONFIG_HOME` path if that envvar is not set
if xdgpath, ok := envvars["XDG_CONFIG_HOME"]; ok {
paths = append(paths, filepath.Join(xdgpath, "cheat", "conf.yml"))
}
paths = append(paths, []string{
filepath.Join(home, ".config", "cheat", "conf.yml"),
filepath.Join(home, ".cheat", "conf.yml"),
"/etc/cheat/conf.yml",
}...)
return paths, nil
// windows
case "windows":
return []string{
filepath.Join(envvars["APPDATA"], "cheat", "conf.yml"),
filepath.Join(envvars["PROGRAMDATA"], "cheat", "conf.yml"),
}, nil
default:
return []string{}, fmt.Errorf("unsupported os: %s", sys)
}
}

View File

@ -0,0 +1,176 @@
package config
import (
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
)
// TestValidatePathsNix asserts that the proper config paths are returned on
// *nix platforms
func TestValidatePathsNix(t *testing.T) {
// mock the user's home directory
home := "/home/foo"
// mock some envvars
envvars := map[string]string{
"XDG_CONFIG_HOME": "/home/bar",
}
// specify the platforms to test
oses := []string{
"android",
"darwin",
"freebsd",
"linux",
}
// test each *nix os
for _, os := range oses {
// get the paths for the platform
paths, err := Paths(os, home, envvars)
if err != nil {
t.Errorf("paths returned an error: %v", err)
}
// specify the expected output
want := []string{
"/home/bar/cheat/conf.yml",
"/home/foo/.config/cheat/conf.yml",
"/home/foo/.cheat/conf.yml",
"/etc/cheat/conf.yml",
}
// assert that output matches expectations
if !reflect.DeepEqual(paths, want) {
t.Errorf(
"failed to return expected paths: want:\n%s, got:\n%s",
spew.Sdump(want),
spew.Sdump(paths),
)
}
}
}
// TestValidatePathsNixNoXDG asserts that the proper config paths are returned
// on *nix platforms when `XDG_CONFIG_HOME is not set
func TestValidatePathsNixNoXDG(t *testing.T) {
// mock the user's home directory
home := "/home/foo"
// mock some envvars
envvars := map[string]string{}
// specify the platforms to test
oses := []string{
"darwin",
"freebsd",
"linux",
}
// test each *nix os
for _, os := range oses {
// get the paths for the platform
paths, err := Paths(os, home, envvars)
if err != nil {
t.Errorf("paths returned an error: %v", err)
}
// specify the expected output
want := []string{
"/home/foo/.config/cheat/conf.yml",
"/home/foo/.cheat/conf.yml",
"/etc/cheat/conf.yml",
}
// assert that output matches expectations
if !reflect.DeepEqual(paths, want) {
t.Errorf(
"failed to return expected paths: want:\n%s, got:\n%s",
spew.Sdump(want),
spew.Sdump(paths),
)
}
}
}
// TestValidatePathsWindows asserts that the proper config paths are returned
// on Windows platforms
func TestValidatePathsWindows(t *testing.T) {
// mock the user's home directory
home := "not-used-on-windows"
// mock some envvars
envvars := map[string]string{
"APPDATA": "/apps",
"PROGRAMDATA": "/programs",
}
// get the paths for the platform
paths, err := Paths("windows", home, envvars)
if err != nil {
t.Errorf("paths returned an error: %v", err)
}
// specify the expected output
want := []string{
"/apps/cheat/conf.yml",
"/programs/cheat/conf.yml",
}
// assert that output matches expectations
if !reflect.DeepEqual(paths, want) {
t.Errorf(
"failed to return expected paths: want:\n%s, got:\n%s",
spew.Sdump(want),
spew.Sdump(paths),
)
}
}
// TestValidatePathsUnsupported asserts that an error is returned on
// unsupported platforms
func TestValidatePathsUnsupported(t *testing.T) {
_, err := Paths("unsupported", "", map[string]string{})
if err == nil {
t.Errorf("failed to return error on unsupported platform")
}
}
// TestValidatePathsCheatConfigPath asserts that the proper config path is
// returned when `CHEAT_CONFIG_PATH` is explicitly specified.
func TestValidatePathsCheatConfigPath(t *testing.T) {
// mock the user's home directory
home := "/home/foo"
// mock some envvars
envvars := map[string]string{
"XDG_CONFIG_HOME": "/home/bar",
"CHEAT_CONFIG_PATH": "/home/baz/conf.yml",
}
// get the paths for the platform
paths, err := Paths("linux", home, envvars)
if err != nil {
t.Errorf("paths returned an error: %v", err)
}
// specify the expected output
want := []string{
"/home/baz/conf.yml",
}
// assert that output matches expectations
if !reflect.DeepEqual(paths, want) {
t.Errorf(
"failed to return expected paths: want:\n%s, got:\n%s",
spew.Sdump(want),
spew.Sdump(paths),
)
}
}

21
internal/display/faint.go Normal file
View File

@ -0,0 +1,21 @@
// Package display implement functions pertaining to writing formatted
// cheatsheet content to stdout, or alternatively the system pager.
package display
import (
"fmt"
"github.com/cheat/cheat/internal/config"
)
// Faint returns a faintly-colored string that's used to de-prioritize text
// written to stdout
func Faint(str string, conf config.Config) string {
// make `str` faint only if colorization has been requested
if conf.Colorize {
return fmt.Sprintf("\033[2m%s\033[0m", str)
}
// otherwise, return the string unmodified
return str
}

View File

@ -0,0 +1,27 @@
package display
import (
"testing"
"github.com/cheat/cheat/internal/config"
)
// TestFaint asserts that Faint applies faint formatting
func TestFaint(t *testing.T) {
// case: apply colorization
conf := config.Config{Colorize: true}
want := "\033[2mfoo\033[0m"
got := Faint("foo", conf)
if want != got {
t.Errorf("failed to faint: want: %s, got: %s", want, got)
}
// case: do not apply colorization
conf.Colorize = false
want = "foo"
got = Faint("foo", conf)
if want != got {
t.Errorf("failed to faint: want: %s, got: %s", want, got)
}
}

View File

@ -0,0 +1,21 @@
package display
import (
"fmt"
"strings"
)
// Indent prepends each line of a string with a tab
func Indent(str string) string {
// trim superfluous whitespace
str = strings.TrimSpace(str)
// prepend each line with a tab character
out := ""
for _, line := range strings.Split(str, "\n") {
out += fmt.Sprintf("\t%s\n", line)
}
return out
}

View File

@ -0,0 +1,12 @@
package display
import "testing"
// TestIndent asserts that Indent prepends a tab to each line
func TestIndent(t *testing.T) {
got := Indent("foo\nbar\nbaz")
want := "\tfoo\n\tbar\n\tbaz\n"
if got != want {
t.Errorf("failed to indent: want: %s, got: %s", want, got)
}
}

36
internal/display/write.go Normal file
View File

@ -0,0 +1,36 @@
package display
import (
"fmt"
"os"
"os/exec"
"strings"
"github.com/cheat/cheat/internal/config"
)
// Write writes output either directly to stdout, or through a pager,
// depending upon configuration.
func Write(out string, conf config.Config) {
// if no pager was configured, print the output to stdout and exit
if conf.Pager == "" {
fmt.Print(out)
os.Exit(0)
}
// otherwise, pipe output through the pager
parts := strings.Split(conf.Pager, " ")
pager := parts[0]
args := parts[1:]
// configure the pager
cmd := exec.Command(pager, args...)
cmd.Stdin = strings.NewReader(out)
cmd.Stdout = os.Stdout
// run the pager and handle errors
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "failed to write to pager: %v\n", err)
os.Exit(1)
}
}

View File

@ -0,0 +1,39 @@
// Package installer implements functions that provide a first-time
// installation wizard.
package installer
import (
"bufio"
"fmt"
"os"
"strings"
)
// Prompt prompts the user for a answer
func Prompt(prompt string, def bool) (bool, error) {
// initialize a line reader
reader := bufio.NewReader(os.Stdin)
// display the prompt
fmt.Printf("%s: ", prompt)
// read the answer
ans, err := reader.ReadString('\n')
if err != nil {
return false, fmt.Errorf("failed to parse input: %v", err)
}
// normalize the answer
ans = strings.ToLower(strings.TrimSpace(ans))
// return the appropriate response
switch ans {
case "y":
return true, nil
case "":
return def, nil
default:
return false, nil
}
}

66
internal/installer/run.go Normal file
View File

@ -0,0 +1,66 @@
package installer
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/cheat/cheat/internal/config"
"github.com/cheat/cheat/internal/repo"
)
// Run runs the installer
func Run(configs string, confpath string) error {
// determine the appropriate paths for config data and (optional) community
// cheatsheets based on the user's platform
confdir := filepath.Dir(confpath)
// create paths for community and personal cheatsheets
community := filepath.Join(confdir, "cheatsheets", "community")
personal := filepath.Join(confdir, "cheatsheets", "personal")
// set default cheatpaths
configs = strings.Replace(configs, "COMMUNITY_PATH", community, -1)
configs = strings.Replace(configs, "PERSONAL_PATH", personal, -1)
// locate and set a default pager
configs = strings.Replace(configs, "PAGER_PATH", config.Pager(), -1)
// locate and set a default editor
if editor, err := config.Editor(); err == nil {
configs = strings.Replace(configs, "EDITOR_PATH", editor, -1)
}
// prompt the user to download the community cheatsheets
yes, err := Prompt(
"Would you like to download the community cheatsheets? [Y/n]",
true,
)
if err != nil {
return fmt.Errorf("failed to prompt: %v", err)
}
// clone the community cheatsheets if so instructed
if yes {
// clone the community cheatsheets
fmt.Printf("Cloning community cheatsheets to %s.\n", community)
if err := repo.Clone(community); err != nil {
return fmt.Errorf("failed to clone cheatsheets: %v", err)
}
// also create a directory for personal cheatsheets
fmt.Printf("Cloning personal cheatsheets to %s.\n", personal)
if err := os.MkdirAll(personal, os.ModePerm); err != nil {
return fmt.Errorf("failed to create directory: %v", err)
}
}
// the config file does not exist, so we'll try to create one
if err = config.Init(confpath, configs); err != nil {
return fmt.Errorf("failed to create config file: %v", err)
}
return nil
}

View File

@ -1,3 +1,4 @@
// Package mock implements mock functions used in unit-tests.
package mock
import (
@ -13,7 +14,7 @@ func Path(filename string) string {
// determine the path of this file during runtime
_, thisfile, _, _ := runtime.Caller(0)
// compute the config path
// compute the mock path
file, err := filepath.Abs(
path.Join(
filepath.Dir(thisfile),
@ -22,7 +23,7 @@ func Path(filename string) string {
),
)
if err != nil {
panic(fmt.Errorf("failed to resolve config path: %v", err))
panic(fmt.Errorf("failed to resolve mock path: %v", err))
}
return file

26
internal/repo/clone.go Normal file
View File

@ -0,0 +1,26 @@
// Package repo implements functions pertaining to the management of git repos.
package repo
import (
"fmt"
"os"
"github.com/go-git/go-git/v5"
)
// Clone clones the repo available at `url`
func Clone(url string) error {
// clone the community cheatsheets
_, err := git.PlainClone(url, false, &git.CloneOptions{
URL: "https://github.com/cheat/cheatsheets.git",
Depth: 1,
Progress: os.Stdout,
})
if err != nil {
return fmt.Errorf("failed to clone cheatsheets: %v", err)
}
return nil
}

110
internal/repo/gitdir.go Normal file
View File

@ -0,0 +1,110 @@
package repo
import (
"fmt"
"os"
"strings"
)
// GitDir returns `true` if we are iterating over a directory contained within
// a repositories `.git` directory.
func GitDir(path string) (bool, error) {
/*
A bit of context is called for here, because this functionality has
previously caused a number of tricky, subtle bugs.
Fundamentally, here we are simply trying to avoid walking over the
contents of the `.git` directory. Doing so potentially makes
hundreds/thousands of needless syscalls, and can noticeably harm
performance on machines with slow disks.
The earliest effort to solve this problem involved simply returning
`fs.SkipDir` when the cheatsheet file path began with `.`, signifying a
hidden directory. This, however, caused two problems:
1. The `.cheat` directory was ignored
2. Cheatsheets installed by `brew` (which were by default installed to
`~/.config/cheat`) were ignored
See: https://github.com/cheat/cheat/issues/690
To remedy this, the exclusion criteria were narrowed, and the search
for a literal `.` was replaced with a search for a literal `.git`.
This, however, broke user installations that stored cheatsheets in
`git` submodules, because such an installation would contain a `.git`
file that pointed to the upstream repository.
See: https://github.com/cheat/cheat/issues/694
The next attempt at solving this was to search for a `.git` literal
string in the cheatsheet file path. If a match was not found, we would
continue to walk the directory, as before.
If a match *was* found, we determined whether `.git` referred to a file
or directory, and would only stop walking the path in the latter case.
This, however, caused crashes if a cheatpath contained a `.gitignore`
file. (Presumably, a crash would likewise occur on the presence of
`.gitattributes`, `.gitmodules`, etc.)
See: https://github.com/cheat/cheat/issues/699
Accounting for all of the above (hopefully?), the current solution is
not to search for `.git`, but `.git/` (including the directory
separator), and then only ceasing to walk the directory on a match.
To summarize, this code must account for the following possibilities:
1. A cheatpath is not a repository
2. A cheatpath is a repository
3. A cheatpath is a repository, and contains a `.git*` file
4. A cheatpath is a submodule
5. A cheatpath is a hidden directory
Care must be taken to support the above on both Unix and Windows
systems, which have different directory separators and line-endings.
There is a lot of nuance to all of this, and it would be worthwhile to
do two things to stop writing bugs here:
1. Build integration tests around all of this
2. Discard string-matching solutions entirely, and use `go-git` instead
NB: A reasonable smoke-test for ensuring that skipping is being applied
correctly is to run the following command:
make && strace ./dist/cheat -l | wc -l
That check should be run twice: once normally, and once after
commenting out the "skip" check in `sheets.Load`.
The specific line counts don't matter; what matters is that the number
of syscalls should be significantly lower with the skip check enabled.
*/
// determine if the literal string `.git` appears within `path`
pos := strings.Index(path, fmt.Sprintf(".git%s", string(os.PathSeparator)))
// if it does not, we know for certain that we are not within a `.git`
// directory.
if pos == -1 {
return false, nil
}
// If `path` does contain the string `.git`, we need to determine if we're
// inside of a `.git` directory, or if `path` points to a cheatsheet that's
// stored within a `git` submodule.
//
// See: https://github.com/cheat/cheat/issues/694
// truncate `path` to the occurrence of `.git`
f, err := os.Stat(path[:pos+5])
if err != nil {
return false, fmt.Errorf("failed to stat path %s: %v", path, err)
}
// return true or false depending on whether the truncated path is a
// directory
return f.Mode().IsDir(), nil
}

1
internal/repo/update.go Normal file
View File

@ -0,0 +1 @@
package repo

View File

@ -0,0 +1,37 @@
package sheet
import (
"bytes"
"github.com/cheat/cheat/internal/config"
"github.com/alecthomas/chroma/v2/quick"
)
// Colorize applies syntax-highlighting to a cheatsheet's Text.
func (s *Sheet) Colorize(conf config.Config) {
// if the syntax was not specified, default to bash
lex := s.Syntax
if lex == "" {
lex = "bash"
}
// write colorized text into a buffer
var buf bytes.Buffer
err := quick.Highlight(
&buf,
s.Text,
lex,
conf.Formatter,
conf.Style,
)
// if colorization somehow failed, do nothing
if err != nil {
return
}
// otherwise, swap the cheatsheet's Text with its colorized equivalent
s.Text = buf.String()
}

View File

@ -0,0 +1,34 @@
package sheet
import (
"testing"
"github.com/cheat/cheat/internal/config"
)
// TestColorize asserts that syntax-highlighting is correctly applied
func TestColorize(t *testing.T) {
// mock configs
conf := config.Config{
Formatter: "terminal16m",
Style: "solarized-dark",
}
// mock a sheet
s := Sheet{
Text: "echo 'foo'",
}
// colorize the sheet text
s.Colorize(conf)
// initialize expectations
want := "echo"
want += " 'foo'"
// assert
if s.Text != want {
t.Errorf("failed to colorize sheet: want: %s, got: %s", want, s.Text)
}
}

View File

@ -4,7 +4,7 @@ import (
"fmt"
"io"
"os"
"path"
"path/filepath"
)
// Copy copies a cheatsheet to a new location
@ -22,7 +22,7 @@ func (s *Sheet) Copy(dest string) error {
defer infile.Close()
// create any necessary subdirectories
dirs := path.Dir(dest)
dirs := filepath.Dir(dest)
if dirs != "." {
if err := os.MkdirAll(dirs, 0755); err != nil {
return fmt.Errorf("failed to create directory: %s, %v", dirs, err)

View File

@ -1,7 +1,6 @@
package sheet
import (
"io/ioutil"
"os"
"path"
"testing"
@ -13,7 +12,7 @@ func TestCopyFlat(t *testing.T) {
// mock a cheatsheet file
text := "this is the cheatsheet text"
src, err := ioutil.TempFile("", "foo-src")
src, err := os.CreateTemp("", "foo-src")
if err != nil {
t.Errorf("failed to mock cheatsheet: %v", err)
}
@ -25,7 +24,7 @@ func TestCopyFlat(t *testing.T) {
}
// mock a cheatsheet struct
sheet, err := New("foo", src.Name(), []string{}, false)
sheet, err := New("foo", "community", src.Name(), []string{}, false)
if err != nil {
t.Errorf("failed to init cheatsheet: %v", err)
}
@ -41,7 +40,7 @@ func TestCopyFlat(t *testing.T) {
}
// assert that the destination file contains the correct text
got, err := ioutil.ReadFile(outpath)
got, err := os.ReadFile(outpath)
if err != nil {
t.Errorf("failed to read destination file: %v", err)
}
@ -60,7 +59,7 @@ func TestCopyDeep(t *testing.T) {
// mock a cheatsheet file
text := "this is the cheatsheet text"
src, err := ioutil.TempFile("", "foo-src")
src, err := os.CreateTemp("", "foo-src")
if err != nil {
t.Errorf("failed to mock cheatsheet: %v", err)
}
@ -72,7 +71,13 @@ func TestCopyDeep(t *testing.T) {
}
// mock a cheatsheet struct
sheet, err := New("/cheat-tests/alpha/bravo/foo", src.Name(), []string{}, false)
sheet, err := New(
"/cheat-tests/alpha/bravo/foo",
"community",
src.Name(),
[]string{},
false,
)
if err != nil {
t.Errorf("failed to init cheatsheet: %v", err)
}
@ -88,7 +93,7 @@ func TestCopyDeep(t *testing.T) {
}
// assert that the destination file contains the correct text
got, err := ioutil.ReadFile(outpath)
got, err := os.ReadFile(outpath)
if err != nil {
t.Errorf("failed to read destination file: %v", err)
}

View File

@ -1,7 +0,0 @@
package sheet
// Match encapsulates search matches within cheatsheets
type Match struct {
Line int
Text string
}

45
internal/sheet/parse.go Normal file
View File

@ -0,0 +1,45 @@
package sheet
import (
"fmt"
"runtime"
"strings"
"gopkg.in/yaml.v3"
)
// Parse parses cheatsheet frontmatter
func parse(markdown string) (frontmatter, string, error) {
// determine the appropriate line-break for the platform
linebreak := "\n"
if runtime.GOOS == "windows" {
linebreak = "\r\n"
}
// specify the frontmatter delimiter
delim := fmt.Sprintf("---%s", linebreak)
// initialize a frontmatter struct
var fm frontmatter
// if the markdown does not contain frontmatter, pass it through unmodified
if !strings.HasPrefix(markdown, delim) {
return fm, markdown, nil
}
// otherwise, split the frontmatter and cheatsheet text
parts := strings.SplitN(markdown, delim, 3)
// return an error if the frontmatter parses into the wrong number of parts
if len(parts) != 3 {
return fm, markdown, fmt.Errorf("failed to delimit frontmatter")
}
// return an error if the YAML cannot be unmarshalled
if err := yaml.Unmarshal([]byte(parts[1]), &fm); err != nil {
return fm, markdown, fmt.Errorf("failed to unmarshal frontmatter: %v", err)
}
return fm, parts[2], nil
}

View File

@ -0,0 +1,95 @@
package sheet
import (
"testing"
)
// TestHasFrontmatter asserts that markdown is properly parsed when it contains
// frontmatter
func TestHasFrontmatter(t *testing.T) {
// stub our cheatsheet content
markdown := `---
syntax: go
tags: [ test ]
---
To foo the bar: baz`
// parse the frontmatter
fm, text, err := parse(markdown)
// assert expectations
if err != nil {
t.Errorf("failed to parse markdown: %v", err)
}
want := "To foo the bar: baz"
if text != want {
t.Errorf("failed to parse text: want: %s, got: %s", want, text)
}
want = "go"
if fm.Syntax != want {
t.Errorf("failed to parse syntax: want: %s, got: %s", want, fm.Syntax)
}
want = "test"
if fm.Tags[0] != want {
t.Errorf("failed to parse tags: want: %s, got: %s", want, fm.Tags[0])
}
if len(fm.Tags) != 1 {
t.Errorf("failed to parse tags: want: len 0, got: len %d", len(fm.Tags))
}
}
// TestHasFrontmatter asserts that markdown is properly parsed when it does not
// contain frontmatter
func TestHasNoFrontmatter(t *testing.T) {
// stub our cheatsheet content
markdown := "To foo the bar: baz"
// parse the frontmatter
fm, text, err := parse(markdown)
// assert expectations
if err != nil {
t.Errorf("failed to parse markdown: %v", err)
}
if text != markdown {
t.Errorf("failed to parse text: want: %s, got: %s", markdown, text)
}
if fm.Syntax != "" {
t.Errorf("failed to parse syntax: want: '', got: %s", fm.Syntax)
}
if len(fm.Tags) != 0 {
t.Errorf("failed to parse tags: want: len 0, got: len %d", len(fm.Tags))
}
}
// TestHasInvalidFrontmatter asserts that markdown is properly parsed when it
// contains invalid frontmatter
func TestHasInvalidFrontmatter(t *testing.T) {
// stub our cheatsheet content (with invalid frontmatter)
markdown := `---
syntax: go
tags: [ test ]
To foo the bar: baz`
// parse the frontmatter
_, text, err := parse(markdown)
// assert that an error was returned
if err == nil {
t.Error("failed to error on invalid frontmatter")
}
// assert that the "raw" markdown was returned
if text != markdown {
t.Errorf("failed to parse text: want: %s, got: %s", markdown, text)
}
}

View File

@ -3,43 +3,22 @@ package sheet
import (
"regexp"
"strings"
"github.com/mgutz/ansi"
)
// Search searches for regexp matches in a cheatsheet's text, and optionally
// colorizes matching strings.
func (s *Sheet) Search(reg *regexp.Regexp, colorize bool) []Match {
// Search returns lines within a sheet's Text that match the search regex
func (s *Sheet) Search(reg *regexp.Regexp) string {
// record matches
matches := []Match{}
matches := ""
// search through the cheatsheet's text line by line
// TODO: searching line-by-line is surely the "naive" approach. Revisit this
// later with an eye for performance improvements.
for linenum, line := range strings.Split(s.Text, "\n") {
for _, line := range strings.Split(s.Text, "\n\n") {
// exit early if the line doesn't match the regex
if !reg.MatchString(line) {
continue
if reg.MatchString(line) {
matches += line + "\n\n"
}
// init the match
m := Match{
Line: linenum + 1,
Text: strings.TrimSpace(line),
}
// colorize the matching text if so configured
if colorize {
m.Text = reg.ReplaceAllStringFunc(m.Text, func(matched string) string {
return ansi.Color(matched, "red+b")
})
}
// record the match
matches = append(matches, m)
}
return matches
return strings.TrimSpace(matches)
}

View File

@ -4,8 +4,6 @@ import (
"reflect"
"regexp"
"testing"
"github.com/davecgh/go-spew/spew"
)
// TestSearchNoMatch ensures that the expected output is returned when no
@ -24,21 +22,21 @@ func TestSearchNoMatch(t *testing.T) {
}
// search the sheet
matches := sheet.Search(reg, false)
matches := sheet.Search(reg)
// assert that no matches were found
if len(matches) != 0 {
t.Errorf("failure: expected no matches: got: %s", spew.Sdump(matches))
if matches != "" {
t.Errorf("failure: expected no matches: got: %s", matches)
}
}
// TestSearchSingleMatchNoColor asserts that the expected output is returned
// when a single match is returned, and no colorization is applied.
func TestSearchSingleMatchNoColor(t *testing.T) {
// TestSearchSingleMatch asserts that the expected output is returned
// when a single match is returned
func TestSearchSingleMatch(t *testing.T) {
// mock a cheatsheet
sheet := Sheet{
Text: "The quick brown fox\njumped over\nthe lazy dog.",
Text: "The quick brown fox\njumped over\n\nthe lazy dog.",
}
// compile the search regex
@ -48,69 +46,28 @@ func TestSearchSingleMatchNoColor(t *testing.T) {
}
// search the sheet
matches := sheet.Search(reg, false)
matches := sheet.Search(reg)
// specify the expected results
want := []Match{
Match{
Line: 1,
Text: "The quick brown fox",
},
}
want := "The quick brown fox\njumped over"
// assert that the correct matches were returned
if !reflect.DeepEqual(matches, want) {
if matches != want {
t.Errorf(
"failed to return expected matches: want:\n%s, got:\n%s",
spew.Sdump(want),
spew.Sdump(matches),
want,
matches,
)
}
}
// TestSearchSingleMatchColorized asserts that the expected output is returned
// when a single match is returned, and colorization is applied
func TestSearchSingleMatchColorized(t *testing.T) {
// TestSearchMultiMatch asserts that the expected output is returned
// when a multiple matches are returned
func TestSearchMultiMatch(t *testing.T) {
// mock a cheatsheet
sheet := Sheet{
Text: "The quick brown fox\njumped over\nthe lazy dog.",
}
// compile the search regex
reg, err := regexp.Compile("(?i)fox")
if err != nil {
t.Errorf("failed to compile regex: %v", err)
}
// search the sheet
matches := sheet.Search(reg, true)
// specify the expected results
want := []Match{
Match{
Line: 1,
Text: "The quick brown \x1b[1;31mfox\x1b[0m",
},
}
// assert that the correct matches were returned
if !reflect.DeepEqual(matches, want) {
t.Errorf(
"failed to return expected matches: want:\n%s, got:\n%s",
spew.Sdump(want),
spew.Sdump(matches),
)
}
}
// TestSearchMultiMatchNoColor asserts that the expected output is returned
// when a multiple matches are returned, and no colorization is applied
func TestSearchMultiMatchNoColor(t *testing.T) {
// mock a cheatsheet
sheet := Sheet{
Text: "The quick brown fox\njumped over\nthe lazy dog.",
Text: "The quick brown fox\n\njumped over\n\nthe lazy dog.",
}
// compile the search regex
@ -120,66 +77,17 @@ func TestSearchMultiMatchNoColor(t *testing.T) {
}
// search the sheet
matches := sheet.Search(reg, false)
matches := sheet.Search(reg)
// specify the expected results
want := []Match{
Match{
Line: 1,
Text: "The quick brown fox",
},
Match{
Line: 3,
Text: "the lazy dog.",
},
}
want := "The quick brown fox\n\nthe lazy dog."
// assert that the correct matches were returned
if !reflect.DeepEqual(matches, want) {
t.Errorf(
"failed to return expected matches: want:\n%s, got:\n%s",
spew.Sdump(want),
spew.Sdump(matches),
)
}
}
// TestSearchMultiMatchColorized asserts that the expected output is returned
// when a multiple matches are returned, and colorization is applied
func TestSearchMultiMatchColorized(t *testing.T) {
// mock a cheatsheet
sheet := Sheet{
Text: "The quick brown fox\njumped over\nthe lazy dog.",
}
// compile the search regex
reg, err := regexp.Compile("(?i)the")
if err != nil {
t.Errorf("failed to compile regex: %v", err)
}
// search the sheet
matches := sheet.Search(reg, true)
// specify the expected results
want := []Match{
Match{
Line: 1,
Text: "\x1b[1;31mThe\x1b[0m quick brown fox",
},
Match{
Line: 3,
Text: "\x1b[1;31mthe\x1b[0m lazy dog.",
},
}
// assert that the correct matches were returned
if !reflect.DeepEqual(matches, want) {
t.Errorf(
"failed to return expected matches: want:\n%s, got:\n%s",
spew.Sdump(want),
spew.Sdump(matches),
want,
matches,
)
}
}

View File

@ -1,15 +1,14 @@
// Package sheet implements functions pertaining to parsing, searching, and
// displaying cheatsheets.
package sheet
import (
"fmt"
"io/ioutil"
"os"
"sort"
"strings"
"github.com/tj/front"
)
// frontmatter is an un-exported helper struct used in parsing cheatsheets
// Frontmatter encapsulates cheatsheet frontmatter data
type frontmatter struct {
Tags []string
Syntax string
@ -17,31 +16,32 @@ type frontmatter struct {
// Sheet encapsulates sheet information
type Sheet struct {
Title string
Path string
Text string
Tags []string
Syntax string
ReadOnly bool
Title string
CheatPath string
Path string
Text string
Tags []string
Syntax string
ReadOnly bool
}
// New initializes a new Sheet
func New(
title string,
cheatpath string,
path string,
tags []string,
readOnly bool,
) (Sheet, error) {
// read the cheatsheet file
markdown, err := ioutil.ReadFile(path)
markdown, err := os.ReadFile(path)
if err != nil {
return Sheet{}, fmt.Errorf("failed to read file: %s, %v", path, err)
}
// parse the front-matter
var fm frontmatter
text, err := front.Unmarshal(markdown, &fm)
// parse the raw cheatsheet text
fm, text, err := parse(string(markdown))
if err != nil {
return Sheet{}, fmt.Errorf("failed to parse front-matter: %v", err)
}
@ -54,11 +54,12 @@ func New(
// initialize and return a sheet
return Sheet{
Title: title,
Path: path,
Text: strings.TrimSpace(string(text)) + "\n",
Tags: tags,
Syntax: fm.Syntax,
ReadOnly: readOnly,
Title: title,
CheatPath: cheatpath,
Path: path,
Text: text,
Tags: tags,
Syntax: fm.Syntax,
ReadOnly: readOnly,
}, nil
}

View File

@ -13,6 +13,7 @@ func TestSheetSuccess(t *testing.T) {
// initialize a sheet
sheet, err := New(
"foo",
"community",
mock.Path("sheet/foo"),
[]string{"alpha", "bravo"},
false,
@ -61,6 +62,7 @@ func TestSheetFailure(t *testing.T) {
// initialize a sheet
_, err := New(
"foo",
"community",
mock.Path("/does-not-exist"),
[]string{"alpha", "bravo"},
false,
@ -69,3 +71,20 @@ func TestSheetFailure(t *testing.T) {
t.Errorf("failed to return an error on unreadable sheet")
}
}
// TestSheetFrontMatterFailure asserts that an error is returned if the sheet's
// frontmatter cannot be parsed.
func TestSheetFrontMatterFailure(t *testing.T) {
// initialize a sheet
_, err := New(
"foo",
"community",
mock.Path("sheet/bad-fm"),
[]string{"alpha", "bravo"},
false,
)
if err == nil {
t.Errorf("failed to return an error on malformed front-matter")
}
}

View File

@ -1,3 +1,5 @@
// Package sheets implements functions pertaining to loading, sorting,
// filtering, and tagging cheatsheets.
package sheets
import (

View File

@ -2,11 +2,13 @@ package sheets
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
cp "github.com/cheat/cheat/internal/cheatpath"
"github.com/cheat/cheat/internal/repo"
"github.com/cheat/cheat/internal/sheet"
)
@ -33,7 +35,7 @@ func Load(cheatpaths []cp.Cheatpath) ([]map[string]sheet.Sheet, error) {
// fail if an error occurred while walking the directory
if err != nil {
return fmt.Errorf("error walking path: %v", err)
return fmt.Errorf("failed to walk path: %v", err)
}
// don't register directories as cheatsheets
@ -45,18 +47,35 @@ func Load(cheatpaths []cp.Cheatpath) ([]map[string]sheet.Sheet, error) {
// accessed. Eg: `cheat tar` - `tar` is the title)
title := strings.TrimPrefix(
strings.TrimPrefix(path, cheatpath.Path),
"/",
string(os.PathSeparator),
)
// ignore dotfiles. Otherwise, we'll likely load .git/*
if strings.HasPrefix(title, ".") {
return nil
// Don't walk the `.git` directory. Doing so creates
// hundreds/thousands of needless syscalls and could
// potentially harm performance on machines with slow disks.
skip, err := repo.GitDir(path)
if err != nil {
return fmt.Errorf("failed to identify .git directory: %v", err)
}
if skip {
return fs.SkipDir
}
// parse the cheatsheet file into a `sheet` struct
s, err := sheet.New(title, path, cheatpath.Tags, cheatpath.ReadOnly)
s, err := sheet.New(
title,
cheatpath.Name,
path,
cheatpath.Tags,
cheatpath.ReadOnly,
)
if err != nil {
return fmt.Errorf("could not create sheet: %v", err)
return fmt.Errorf(
"failed to load sheet: %s, path: %s, err: %v",
title,
path,
err,
)
}
// register the cheatsheet on its cheatpath, keyed by its title

View File

@ -1,3 +1,62 @@
package sheets
// TODO
import (
"path"
"testing"
"github.com/cheat/cheat/internal/cheatpath"
"github.com/cheat/cheat/internal/mock"
)
// TestLoad asserts that sheets on valid cheatpaths can be loaded successfully
func TestLoad(t *testing.T) {
// mock cheatpaths
cheatpaths := []cheatpath.Cheatpath{
{
Name: "community",
Path: path.Join(mock.Path("cheatsheets"), "community"),
ReadOnly: true,
},
{
Name: "personal",
Path: path.Join(mock.Path("cheatsheets"), "personal"),
ReadOnly: false,
},
}
// load cheatsheets
sheets, err := Load(cheatpaths)
if err != nil {
t.Errorf("failed to load cheatsheets: %v", err)
}
// assert that the correct number of sheets loaded
// (sheet load details are tested in `sheet_test.go`)
want := 4
if len(sheets) != want {
t.Errorf(
"failed to load correct number of cheatsheets: want: %d, got: %d",
want,
len(sheets),
)
}
}
// TestLoadBadPath asserts that an error is returned if a cheatpath is invalid
func TestLoadBadPath(t *testing.T) {
// mock a bad cheatpath
cheatpaths := []cheatpath.Cheatpath{
{
Name: "badpath",
Path: "/cheat/test/path/does/not/exist",
ReadOnly: true,
},
}
// attempt to load the cheatpath
if _, err := Load(cheatpaths); err == nil {
t.Errorf("failed to reject invalid cheatpath")
}
}

36
internal/sheets/tags.go Normal file
View File

@ -0,0 +1,36 @@
package sheets
import (
"sort"
"github.com/cheat/cheat/internal/sheet"
)
// Tags returns a slice of all tags in use in any sheet
func Tags(cheatpaths []map[string]sheet.Sheet) []string {
// create a map of all tags in use in any sheet
tags := make(map[string]bool)
// iterate over all tags on all sheets on all cheatpaths
for _, path := range cheatpaths {
for _, sheet := range path {
for _, tag := range sheet.Tags {
tags[tag] = true
}
}
}
// restructure the map into a slice
sorted := []string{}
for tag := range tags {
sorted = append(sorted, tag)
}
// sort the slice
sort.Slice(sorted, func(i, j int) bool {
return sorted[i] < sorted[j]
})
return sorted
}

View File

@ -0,0 +1,51 @@
package sheets
import (
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/cheat/cheat/internal/sheet"
)
// TestTags asserts that cheatsheet tags are properly returned
func TestTags(t *testing.T) {
// mock cheatsheets available on multiple cheatpaths
cheatpaths := []map[string]sheet.Sheet{
// mock community cheatsheets
map[string]sheet.Sheet{
"foo": sheet.Sheet{Title: "foo", Tags: []string{"alpha"}},
"bar": sheet.Sheet{Title: "bar", Tags: []string{"alpha", "bravo"}},
},
// mock local cheatsheets
map[string]sheet.Sheet{
"bar": sheet.Sheet{Title: "bar", Tags: []string{"bravo", "charlie"}},
"baz": sheet.Sheet{Title: "baz", Tags: []string{"delta"}},
},
}
// consolidate the cheatsheets
tags := Tags(cheatpaths)
// specify the expected output
want := []string{
"alpha",
"bravo",
"charlie",
"delta",
}
// assert that the cheatsheets properly consolidated
if !reflect.DeepEqual(tags, want) {
t.Errorf(
"failed to return tags: want:\n%s, got:\n%s",
spew.Sdump(want),
spew.Sdump(tags),
)
}
}

View File

View File

@ -0,0 +1,4 @@
---
tags: [ community ]
---
This is the bar cheatsheet.

View File

@ -0,0 +1,4 @@
---
tags: [ community ]
---
This is the foo cheatsheet.

View File

@ -0,0 +1,4 @@
---
tags: [ personal ]
---
This is the bat cheatsheet.

View File

@ -0,0 +1,4 @@
---
tags: [ personal ]
---
This is the baz cheatsheet.

4
mocks/sheet/bad-fm Normal file
View File

@ -0,0 +1,4 @@
---
syntax: sh
This is malformed frontmatter.

74
scripts/cheat.bash Executable file
View File

@ -0,0 +1,74 @@
# cheat(1) completion -*- shell-script -*-
# generate cheatsheet completions, optionally using `fzf`
_cheat_complete_cheatsheets()
{
if [[ "$CHEAT_USE_FZF" = true ]]; then
FZF_COMPLETION_TRIGGER='' _fzf_complete "--no-multi" "$@" < <(
cheat -l | tail -n +2 | cut -d' ' -f1
)
else
COMPREPLY=( $(compgen -W "$(cheat -l | tail -n +2 | cut -d' ' -f1)" -- "$cur") )
fi
}
# generate tag completions, optionally using `fzf`
_cheat_complete_tags()
{
if [ "$CHEAT_USE_FZF" = true ]; then
FZF_COMPLETION_TRIGGER='' _fzf_complete "--no-multi" "$@" < <(cheat -T)
else
COMPREPLY=( $(compgen -W "$(cheat -T)" -- "$cur") )
fi
}
# implement the `cheat` autocompletions
_cheat()
{
local cur prev words cword split
_init_completion -s || return
# complete options that are currently being typed: `--col` => `--colorize`
if [[ $cur == -* ]]; then
COMPREPLY=( $(compgen -W '$(_parse_help "$1" | sed "s/=//g")' -- "$cur") )
[[ $COMPREPLY == *= ]] && compopt -o nospace
return
fi
# implement completions
case $prev in
--colorize|-c|\
--directories|-d|\
--init|\
--regex|-r|\
--search|-s|\
--tags|-T|\
--version|-v)
# noop the above, which should implement no completions
;;
--edit|-e)
_cheat_complete_cheatsheets
;;
--list|-l)
_cheat_complete_cheatsheets
;;
--path|-p)
COMPREPLY=( $(compgen -W "$(cheat -d | cut -d':' -f1)" -- "$cur") )
;;
--rm)
_cheat_complete_cheatsheets
;;
--tag|-t)
_cheat_complete_tags
;;
*)
_cheat_complete_cheatsheets
;;
esac
$split && return
} &&
complete -F _cheat cheat
# ex: filetype=sh

13
scripts/cheat.fish Executable file
View File

@ -0,0 +1,13 @@
complete -c cheat -f -a "(cheat -l | tail -n +2 | cut -d ' ' -f 1)"
complete -c cheat -l init -d "Write a default config file to stdout"
complete -c cheat -s c -l colorize -d "Colorize output"
complete -c cheat -s d -l directories -d "List cheatsheet directories"
complete -c cheat -s e -l edit -x -a "(cheat -l | tail -n +2 | cut -d ' ' -f 1)" -d "Edit cheatsheet"
complete -c cheat -s l -l list -d "List cheatsheets"
complete -c cheat -s p -l path -x -a "(cheat -d | cut -d ':' -f 1)" -d "Return only sheets found on given path"
complete -c cheat -s r -l regex -d "Treat search phrase as a regex"
complete -c cheat -s s -l search -x -d "Search cheatsheets for given phrase"
complete -c cheat -s t -l tag -x -a "(cheat -T)" -d "Return only sheets matching the given tag"
complete -c cheat -s T -l tags -d "List all tags in use"
complete -c cheat -s v -l version -d "Print the version number"
complete -c cheat -l rm -x -a "(cheat -l | tail -n +2 | cut -d ' ' -f 1)" -d "Remove (delete) cheatsheet"

65
scripts/cheat.zsh Executable file
View File

@ -0,0 +1,65 @@
#compdef cheat
local cheats taglist pathlist
_cheat_complete_personal_cheatsheets()
{
cheats=("${(f)$(cheat -l -t personal | tail -n +2 | cut -d' ' -f1)}")
_describe -t cheats 'cheats' cheats
}
_cheat_complete_full_cheatsheets()
{
cheats=("${(f)$(cheat -l | tail -n +2 | cut -d' ' -f1)}")
_describe -t cheats 'cheats' cheats
}
_cheat_complete_tags()
{
taglist=("${(f)$(cheat -T)}")
_describe -t taglist 'taglist' taglist
}
_cheat_complete_paths()
{
pathlist=("${(f)$(cheat -d | cut -d':' -f1)}")
_describe -t pathlist 'pathlist' pathlist
}
_cheat() {
_arguments -C \
'(--init)--init[Write a default config file to stdout]: :->none' \
'(-c --colorize)'{-c,--colorize}'[Colorize output]: :->none' \
'(-d --directories)'{-d,--directories}'[List cheatsheet directories]: :->none' \
'(-e --edit)'{-e,--edit}'[Edit <sheet>]: :->personal' \
'(-l --list)'{-l,--list}'[List cheatsheets]: :->full' \
'(-p --path)'{-p,--path}'[Return only sheets found on path <name>]: :->pathlist' \
'(-r --regex)'{-r,--regex}'[Treat search <phrase> as a regex]: :->none' \
'(-s --search)'{-s,--search}'[Search cheatsheets for <phrase>]: :->none' \
'(-t --tag)'{-t,--tag}'[Return only sheets matching <tag>]: :->taglist' \
'(-T --tags)'{-T,--tags}'[List all tags in use]: :->none' \
'(-v --version)'{-v,--version}'[Print the version number]: :->none' \
'(--rm)--rm[Remove (delete) <sheet>]: :->personal'
case $state in
(none)
;;
(full)
_cheat_complete_full_cheatsheets
;;
(personal)
_cheat_complete_personal_cheatsheets
;;
(taglist)
_cheat_complete_tags
;;
(pathlist)
_cheat_complete_paths
;;
(*)
;;
esac
}
compdef _cheat cheat

View File

@ -1,11 +0,0 @@
#!/bin/bash
# This function enables you to choose a cheatsheet to view by selecting output
# from `cheat -l`. `source` it in your shell to enable it. (Consider renaming
# or aliasing it to something convenient.)
# Arguments passed to this function (like --color) will be passed to the second
# invokation of `cheat`.
function cheat-fzf {
eval `cheat -l | tail -n +2 | fzf | awk -v vars="$*" '{ print "cheat " $1 " -t " $3, vars }'`
}

46
scripts/git/cheatsheets Executable file
View File

@ -0,0 +1,46 @@
#!/bin/sh -e
pull() {
for d in `cheat -d | awk '{print $2}'`;
do
echo "Update $d"
cd "$d"
[ -d ".git" ] && git pull || :
done
echo
echo "Finished update"
}
push() {
for d in `cheat -d | grep -v "community" | awk '{print $2}'`;
do
cd "$d"
if [ -d ".git" ]
then
echo "Push modifications $d"
files=$(git ls-files -mo | tr '\n' ' ')
git add -A && git commit -m "Edited files: $files" && git push || :
else
echo "$(pwd) is not a git managed folder"
echo "First connect this to your personal git repository"
fi
done
echo
echo "Finished push operation"
}
if [ "$1" = "pull" ]; then
pull
elif [ "$1" = "push" ]; then
push
else
echo "Usage:
# pull changes
cheatsheets pull
# push changes
cheatsheets push"
fi

12
vendor/dario.cat/mergo/.deepsource.toml vendored Normal file
View File

@ -0,0 +1,12 @@
version = 1
test_patterns = [
"*_test.go"
]
[[analyzers]]
name = "go"
enabled = true
[analyzers.meta]
import_path = "dario.cat/mergo"

33
vendor/dario.cat/mergo/.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
#### joe made this: http://goel.io/joe
#### go ####
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
#### vim ####
# Swap
[._]*.s[a-v][a-z]
[._]*.sw[a-p]
[._]s[a-v][a-z]
[._]sw[a-p]
# Session
Session.vim
# Temporary
.netrwhist
*~
# Auto-generated tag files
tags

12
vendor/dario.cat/mergo/.travis.yml vendored Normal file
View File

@ -0,0 +1,12 @@
language: go
arch:
- amd64
- ppc64le
install:
- go get -t
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls
script:
- go test -race -v ./...
after_script:
- $HOME/gopath/bin/goveralls -service=travis-ci -repotoken $COVERALLS_TOKEN

View File

@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In 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.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project 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.
Project 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.
## Scope
This 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.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at i@dario.im. 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.
Project 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.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

112
vendor/dario.cat/mergo/CONTRIBUTING.md vendored Normal file
View File

@ -0,0 +1,112 @@
<!-- omit in toc -->
# Contributing to mergo
First off, thanks for taking the time to contribute! ❤️
All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉
> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
> - Star the project
> - Tweet about it
> - Refer this project in your project's readme
> - Mention the project at local meetups and tell your friends/colleagues
<!-- omit in toc -->
## Table of Contents
- [Code of Conduct](#code-of-conduct)
- [I Have a Question](#i-have-a-question)
- [I Want To Contribute](#i-want-to-contribute)
- [Reporting Bugs](#reporting-bugs)
- [Suggesting Enhancements](#suggesting-enhancements)
## Code of Conduct
This project and everyone participating in it is governed by the
[mergo Code of Conduct](https://github.com/imdario/mergoblob/master/CODE_OF_CONDUCT.md).
By participating, you are expected to uphold this code. Please report unacceptable behavior
to <>.
## I Have a Question
> If you want to ask a question, we assume that you have read the available [Documentation](https://pkg.go.dev/github.com/imdario/mergo).
Before you ask a question, it is best to search for existing [Issues](https://github.com/imdario/mergo/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first.
If you then still feel the need to ask a question and need clarification, we recommend the following:
- Open an [Issue](https://github.com/imdario/mergo/issues/new).
- Provide as much context as you can about what you're running into.
- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant.
We will then take care of the issue as soon as possible.
## I Want To Contribute
> ### Legal Notice <!-- omit in toc -->
> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
### Reporting Bugs
<!-- omit in toc -->
#### Before Submitting a Bug Report
A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
- Make sure that you are using the latest version.
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](). If you are looking for support, you might want to check [this section](#i-have-a-question)).
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/imdario/mergoissues?q=label%3Abug).
- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue.
- Collect information about the bug:
- Stack trace (Traceback)
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
- Possibly your input and the output
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
<!-- omit in toc -->
#### How Do I Submit a Good Bug Report?
> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to .
<!-- You may add a PGP key to allow the messages to be sent encrypted as well. -->
We use GitHub issues to track bugs and errors. If you run into an issue with the project:
- Open an [Issue](https://github.com/imdario/mergo/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
- Explain the behavior you would expect and the actual behavior.
- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
- Provide the information you collected in the previous section.
Once it's filed:
- The project team will label the issue accordingly.
- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced.
- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be implemented by someone.
### Suggesting Enhancements
This section guides you through submitting an enhancement suggestion for mergo, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.
<!-- omit in toc -->
#### Before Submitting an Enhancement
- Make sure that you are using the latest version.
- Read the [documentation]() carefully and find out if the functionality is already covered, maybe by an individual configuration.
- Perform a [search](https://github.com/imdario/mergo/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
<!-- omit in toc -->
#### How Do I Submit a Good Enhancement Suggestion?
Enhancement suggestions are tracked as [GitHub issues](https://github.com/imdario/mergo/issues).
- Use a **clear and descriptive title** for the issue to identify the suggestion.
- Provide a **step-by-step description of the suggested enhancement** in as many details as possible.
- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you.
- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. <!-- this should only be included if the project has a GUI -->
- **Explain why this enhancement would be useful** to most mergo users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
<!-- omit in toc -->
## Attribution
This guide is based on the **contributing-gen**. [Make your own](https://github.com/bttger/contributing-gen)!

28
vendor/dario.cat/mergo/LICENSE vendored Normal file
View File

@ -0,0 +1,28 @@
Copyright (c) 2013 Dario Castañé. All rights reserved.
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

248
vendor/dario.cat/mergo/README.md vendored Normal file
View File

@ -0,0 +1,248 @@
# Mergo
[![GitHub release][5]][6]
[![GoCard][7]][8]
[![Test status][1]][2]
[![OpenSSF Scorecard][21]][22]
[![OpenSSF Best Practices][19]][20]
[![Coverage status][9]][10]
[![Sourcegraph][11]][12]
[![FOSSA status][13]][14]
[![GoDoc][3]][4]
[![Become my sponsor][15]][16]
[![Tidelift][17]][18]
[1]: https://github.com/imdario/mergo/workflows/tests/badge.svg?branch=master
[2]: https://github.com/imdario/mergo/actions/workflows/tests.yml
[3]: https://godoc.org/github.com/imdario/mergo?status.svg
[4]: https://godoc.org/github.com/imdario/mergo
[5]: https://img.shields.io/github/release/imdario/mergo.svg
[6]: https://github.com/imdario/mergo/releases
[7]: https://goreportcard.com/badge/imdario/mergo
[8]: https://goreportcard.com/report/github.com/imdario/mergo
[9]: https://coveralls.io/repos/github/imdario/mergo/badge.svg?branch=master
[10]: https://coveralls.io/github/imdario/mergo?branch=master
[11]: https://sourcegraph.com/github.com/imdario/mergo/-/badge.svg
[12]: https://sourcegraph.com/github.com/imdario/mergo?badge
[13]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=shield
[14]: https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_shield
[15]: https://img.shields.io/github/sponsors/imdario
[16]: https://github.com/sponsors/imdario
[17]: https://tidelift.com/badges/package/go/github.com%2Fimdario%2Fmergo
[18]: https://tidelift.com/subscription/pkg/go-github.com-imdario-mergo
[19]: https://bestpractices.coreinfrastructure.org/projects/7177/badge
[20]: https://bestpractices.coreinfrastructure.org/projects/7177
[21]: https://api.securityscorecards.dev/projects/github.com/imdario/mergo/badge
[22]: https://api.securityscorecards.dev/projects/github.com/imdario/mergo
A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region of Marche.
## Status
It is ready for production use. [It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, Microsoft, etc](https://github.com/imdario/mergo#mergo-in-the-wild).
### Important notes
#### 1.0.0
In [1.0.0](//github.com/imdario/mergo/releases/tag/1.0.0) Mergo moves to a vanity URL `dario.cat/mergo`.
#### 0.3.9
Please keep in mind that a problematic PR broke [0.3.9](//github.com/imdario/mergo/releases/tag/0.3.9). I reverted it in [0.3.10](//github.com/imdario/mergo/releases/tag/0.3.10), and I consider it stable but not bug-free. Also, this version adds support for go modules.
Keep in mind that in [0.3.2](//github.com/imdario/mergo/releases/tag/0.3.2), Mergo changed `Merge()`and `Map()` signatures to support [transformers](#transformers). I added an optional/variadic argument so that it won't break the existing code.
If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with ```go get -u dario.cat/mergo```. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0).
### Donations
If Mergo is useful to you, consider buying me a coffee, a beer, or making a monthly donation to allow me to keep building great free software. :heart_eyes:
<a href='https://ko-fi.com/B0B58839' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://az743702.vo.msecnd.net/cdn/kofi1.png?v=0' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
<a href="https://liberapay.com/dario/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>
<a href='https://github.com/sponsors/imdario' target='_blank'><img alt="Become my sponsor" src="https://img.shields.io/github/sponsors/imdario?style=for-the-badge" /></a>
### Mergo in the wild
- [moby/moby](https://github.com/moby/moby)
- [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes)
- [vmware/dispatch](https://github.com/vmware/dispatch)
- [Shopify/themekit](https://github.com/Shopify/themekit)
- [imdario/zas](https://github.com/imdario/zas)
- [matcornic/hermes](https://github.com/matcornic/hermes)
- [OpenBazaar/openbazaar-go](https://github.com/OpenBazaar/openbazaar-go)
- [kataras/iris](https://github.com/kataras/iris)
- [michaelsauter/crane](https://github.com/michaelsauter/crane)
- [go-task/task](https://github.com/go-task/task)
- [sensu/uchiwa](https://github.com/sensu/uchiwa)
- [ory/hydra](https://github.com/ory/hydra)
- [sisatech/vcli](https://github.com/sisatech/vcli)
- [dairycart/dairycart](https://github.com/dairycart/dairycart)
- [projectcalico/felix](https://github.com/projectcalico/felix)
- [resin-os/balena](https://github.com/resin-os/balena)
- [go-kivik/kivik](https://github.com/go-kivik/kivik)
- [Telefonica/govice](https://github.com/Telefonica/govice)
- [supergiant/supergiant](supergiant/supergiant)
- [SergeyTsalkov/brooce](https://github.com/SergeyTsalkov/brooce)
- [soniah/dnsmadeeasy](https://github.com/soniah/dnsmadeeasy)
- [ohsu-comp-bio/funnel](https://github.com/ohsu-comp-bio/funnel)
- [EagerIO/Stout](https://github.com/EagerIO/Stout)
- [lynndylanhurley/defsynth-api](https://github.com/lynndylanhurley/defsynth-api)
- [russross/canvasassignments](https://github.com/russross/canvasassignments)
- [rdegges/cryptly-api](https://github.com/rdegges/cryptly-api)
- [casualjim/exeggutor](https://github.com/casualjim/exeggutor)
- [divshot/gitling](https://github.com/divshot/gitling)
- [RWJMurphy/gorl](https://github.com/RWJMurphy/gorl)
- [andrerocker/deploy42](https://github.com/andrerocker/deploy42)
- [elwinar/rambler](https://github.com/elwinar/rambler)
- [tmaiaroto/gopartman](https://github.com/tmaiaroto/gopartman)
- [jfbus/impressionist](https://github.com/jfbus/impressionist)
- [Jmeyering/zealot](https://github.com/Jmeyering/zealot)
- [godep-migrator/rigger-host](https://github.com/godep-migrator/rigger-host)
- [Dronevery/MultiwaySwitch-Go](https://github.com/Dronevery/MultiwaySwitch-Go)
- [thoas/picfit](https://github.com/thoas/picfit)
- [mantasmatelis/whooplist-server](https://github.com/mantasmatelis/whooplist-server)
- [jnuthong/item_search](https://github.com/jnuthong/item_search)
- [bukalapak/snowboard](https://github.com/bukalapak/snowboard)
- [containerssh/containerssh](https://github.com/containerssh/containerssh)
- [goreleaser/goreleaser](https://github.com/goreleaser/goreleaser)
- [tjpnz/structbot](https://github.com/tjpnz/structbot)
## Install
go get dario.cat/mergo
// use in your .go code
import (
"dario.cat/mergo"
)
## Usage
You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as [they are zero values](https://golang.org/ref/spec#The_zero_value) too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
```go
if err := mergo.Merge(&dst, src); err != nil {
// ...
}
```
Also, you can merge overwriting values using the transformer `WithOverride`.
```go
if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil {
// ...
}
```
Additionally, you can map a `map[string]interface{}` to a struct (and otherwise, from struct to map), following the same restrictions as in `Merge()`. Keys are capitalized to find each corresponding exported field.
```go
if err := mergo.Map(&dst, srcMap); err != nil {
// ...
}
```
Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as `map[string]interface{}`. They will be just assigned as values.
Here is a nice example:
```go
package main
import (
"fmt"
"dario.cat/mergo"
)
type Foo struct {
A string
B int64
}
func main() {
src := Foo{
A: "one",
B: 2,
}
dest := Foo{
A: "two",
}
mergo.Merge(&dest, src)
fmt.Println(dest)
// Will print
// {two 2}
}
```
Note: if test are failing due missing package, please execute:
go get gopkg.in/yaml.v3
### Transformers
Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, `time.Time` is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero `time.Time`?
```go
package main
import (
"fmt"
"dario.cat/mergo"
"reflect"
"time"
)
type timeTransformer struct {
}
func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
if typ == reflect.TypeOf(time.Time{}) {
return func(dst, src reflect.Value) error {
if dst.CanSet() {
isZero := dst.MethodByName("IsZero")
result := isZero.Call([]reflect.Value{})
if result[0].Bool() {
dst.Set(src)
}
}
return nil
}
}
return nil
}
type Snapshot struct {
Time time.Time
// ...
}
func main() {
src := Snapshot{time.Now()}
dest := Snapshot{}
mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{}))
fmt.Println(dest)
// Will print
// { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 }
}
```
## Contact me
If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): [@im_dario](https://twitter.com/im_dario)
## About
Written by [Dario Castañé](http://dario.im).
## License
[BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE).
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_large)

14
vendor/dario.cat/mergo/SECURITY.md vendored Normal file
View File

@ -0,0 +1,14 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 0.3.x | :white_check_mark: |
| < 0.3 | :x: |
## Security contact information
To report a security vulnerability, please use the
[Tidelift security contact](https://tidelift.com/security).
Tidelift will coordinate the fix and disclosure.

148
vendor/dario.cat/mergo/doc.go vendored Normal file
View File

@ -0,0 +1,148 @@
// Copyright 2013 Dario Castañé. All rights reserved.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
# Status
It is ready for production use. It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc.
# Important notes
1.0.0
In 1.0.0 Mergo moves to a vanity URL `dario.cat/mergo`.
0.3.9
Please keep in mind that a problematic PR broke 0.3.9. We reverted it in 0.3.10. We consider 0.3.10 as stable but not bug-free. . Also, this version adds suppot for go modules.
Keep in mind that in 0.3.2, Mergo changed Merge() and Map() signatures to support transformers. We added an optional/variadic argument so that it won't break the existing code.
If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with go get -u dario.cat/mergo. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0).
# Install
Do your usual installation procedure:
go get dario.cat/mergo
// use in your .go code
import (
"dario.cat/mergo"
)
# Usage
You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as they are zero values too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
if err := mergo.Merge(&dst, src); err != nil {
// ...
}
Also, you can merge overwriting values using the transformer WithOverride.
if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil {
// ...
}
Additionally, you can map a map[string]interface{} to a struct (and otherwise, from struct to map), following the same restrictions as in Merge(). Keys are capitalized to find each corresponding exported field.
if err := mergo.Map(&dst, srcMap); err != nil {
// ...
}
Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as map[string]interface{}. They will be just assigned as values.
Here is a nice example:
package main
import (
"fmt"
"dario.cat/mergo"
)
type Foo struct {
A string
B int64
}
func main() {
src := Foo{
A: "one",
B: 2,
}
dest := Foo{
A: "two",
}
mergo.Merge(&dest, src)
fmt.Println(dest)
// Will print
// {two 2}
}
# Transformers
Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, time.Time is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero time.Time?
package main
import (
"fmt"
"dario.cat/mergo"
"reflect"
"time"
)
type timeTransformer struct {
}
func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
if typ == reflect.TypeOf(time.Time{}) {
return func(dst, src reflect.Value) error {
if dst.CanSet() {
isZero := dst.MethodByName("IsZero")
result := isZero.Call([]reflect.Value{})
if result[0].Bool() {
dst.Set(src)
}
}
return nil
}
}
return nil
}
type Snapshot struct {
Time time.Time
// ...
}
func main() {
src := Snapshot{time.Now()}
dest := Snapshot{}
mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{}))
fmt.Println(dest)
// Will print
// { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 }
}
# Contact me
If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): https://twitter.com/im_dario
# About
Written by Dario Castañé: https://da.rio.hn
# License
BSD 3-Clause license, as Go language.
*/
package mergo

178
vendor/dario.cat/mergo/map.go vendored Normal file
View File

@ -0,0 +1,178 @@
// Copyright 2014 Dario Castañé. All rights reserved.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Based on src/pkg/reflect/deepequal.go from official
// golang's stdlib.
package mergo
import (
"fmt"
"reflect"
"unicode"
"unicode/utf8"
)
func changeInitialCase(s string, mapper func(rune) rune) string {
if s == "" {
return s
}
r, n := utf8.DecodeRuneInString(s)
return string(mapper(r)) + s[n:]
}
func isExported(field reflect.StructField) bool {
r, _ := utf8.DecodeRuneInString(field.Name)
return r >= 'A' && r <= 'Z'
}
// Traverses recursively both values, assigning src's fields values to dst.
// The map argument tracks comparisons that have already been seen, which allows
// short circuiting on recursive types.
func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
overwrite := config.Overwrite
if dst.CanAddr() {
addr := dst.UnsafeAddr()
h := 17 * addr
seen := visited[h]
typ := dst.Type()
for p := seen; p != nil; p = p.next {
if p.ptr == addr && p.typ == typ {
return nil
}
}
// Remember, remember...
visited[h] = &visit{typ, seen, addr}
}
zeroValue := reflect.Value{}
switch dst.Kind() {
case reflect.Map:
dstMap := dst.Interface().(map[string]interface{})
for i, n := 0, src.NumField(); i < n; i++ {
srcType := src.Type()
field := srcType.Field(i)
if !isExported(field) {
continue
}
fieldName := field.Name
fieldName = changeInitialCase(fieldName, unicode.ToLower)
if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v), !config.ShouldNotDereference) || overwrite) {
dstMap[fieldName] = src.Field(i).Interface()
}
}
case reflect.Ptr:
if dst.IsNil() {
v := reflect.New(dst.Type().Elem())
dst.Set(v)
}
dst = dst.Elem()
fallthrough
case reflect.Struct:
srcMap := src.Interface().(map[string]interface{})
for key := range srcMap {
config.overwriteWithEmptyValue = true
srcValue := srcMap[key]
fieldName := changeInitialCase(key, unicode.ToUpper)
dstElement := dst.FieldByName(fieldName)
if dstElement == zeroValue {
// We discard it because the field doesn't exist.
continue
}
srcElement := reflect.ValueOf(srcValue)
dstKind := dstElement.Kind()
srcKind := srcElement.Kind()
if srcKind == reflect.Ptr && dstKind != reflect.Ptr {
srcElement = srcElement.Elem()
srcKind = reflect.TypeOf(srcElement.Interface()).Kind()
} else if dstKind == reflect.Ptr {
// Can this work? I guess it can't.
if srcKind != reflect.Ptr && srcElement.CanAddr() {
srcPtr := srcElement.Addr()
srcElement = reflect.ValueOf(srcPtr)
srcKind = reflect.Ptr
}
}
if !srcElement.IsValid() {
continue
}
if srcKind == dstKind {
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
return
}
} else if dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface {
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
return
}
} else if srcKind == reflect.Map {
if err = deepMap(dstElement, srcElement, visited, depth+1, config); err != nil {
return
}
} else {
return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind)
}
}
}
return
}
// Map sets fields' values in dst from src.
// src can be a map with string keys or a struct. dst must be the opposite:
// if src is a map, dst must be a valid pointer to struct. If src is a struct,
// dst must be map[string]interface{}.
// It won't merge unexported (private) fields and will do recursively
// any exported field.
// If dst is a map, keys will be src fields' names in lower camel case.
// Missing key in src that doesn't match a field in dst will be skipped. This
// doesn't apply if dst is a map.
// This is separated method from Merge because it is cleaner and it keeps sane
// semantics: merging equal types, mapping different (restricted) types.
func Map(dst, src interface{}, opts ...func(*Config)) error {
return _map(dst, src, opts...)
}
// MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overridden by
// non-empty src attribute values.
// Deprecated: Use Map(…) with WithOverride
func MapWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
return _map(dst, src, append(opts, WithOverride)...)
}
func _map(dst, src interface{}, opts ...func(*Config)) error {
if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
return ErrNonPointerArgument
}
var (
vDst, vSrc reflect.Value
err error
)
config := &Config{}
for _, opt := range opts {
opt(config)
}
if vDst, vSrc, err = resolveValues(dst, src); err != nil {
return err
}
// To be friction-less, we redirect equal-type arguments
// to deepMerge. Only because arguments can be anything.
if vSrc.Kind() == vDst.Kind() {
return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
}
switch vSrc.Kind() {
case reflect.Struct:
if vDst.Kind() != reflect.Map {
return ErrExpectedMapAsDestination
}
case reflect.Map:
if vDst.Kind() != reflect.Struct {
return ErrExpectedStructAsDestination
}
default:
return ErrNotSupported
}
return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, config)
}

409
vendor/dario.cat/mergo/merge.go vendored Normal file
View File

@ -0,0 +1,409 @@
// Copyright 2013 Dario Castañé. All rights reserved.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Based on src/pkg/reflect/deepequal.go from official
// golang's stdlib.
package mergo
import (
"fmt"
"reflect"
)
func hasMergeableFields(dst reflect.Value) (exported bool) {
for i, n := 0, dst.NumField(); i < n; i++ {
field := dst.Type().Field(i)
if field.Anonymous && dst.Field(i).Kind() == reflect.Struct {
exported = exported || hasMergeableFields(dst.Field(i))
} else if isExportedComponent(&field) {
exported = exported || len(field.PkgPath) == 0
}
}
return
}
func isExportedComponent(field *reflect.StructField) bool {
pkgPath := field.PkgPath
if len(pkgPath) > 0 {
return false
}
c := field.Name[0]
if 'a' <= c && c <= 'z' || c == '_' {
return false
}
return true
}
type Config struct {
Transformers Transformers
Overwrite bool
ShouldNotDereference bool
AppendSlice bool
TypeCheck bool
overwriteWithEmptyValue bool
overwriteSliceWithEmptyValue bool
sliceDeepCopy bool
debug bool
}
type Transformers interface {
Transformer(reflect.Type) func(dst, src reflect.Value) error
}
// Traverses recursively both values, assigning src's fields values to dst.
// The map argument tracks comparisons that have already been seen, which allows
// short circuiting on recursive types.
func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
overwrite := config.Overwrite
typeCheck := config.TypeCheck
overwriteWithEmptySrc := config.overwriteWithEmptyValue
overwriteSliceWithEmptySrc := config.overwriteSliceWithEmptyValue
sliceDeepCopy := config.sliceDeepCopy
if !src.IsValid() {
return
}
if dst.CanAddr() {
addr := dst.UnsafeAddr()
h := 17 * addr
seen := visited[h]
typ := dst.Type()
for p := seen; p != nil; p = p.next {
if p.ptr == addr && p.typ == typ {
return nil
}
}
// Remember, remember...
visited[h] = &visit{typ, seen, addr}
}
if config.Transformers != nil && !isReflectNil(dst) && dst.IsValid() {
if fn := config.Transformers.Transformer(dst.Type()); fn != nil {
err = fn(dst, src)
return
}
}
switch dst.Kind() {
case reflect.Struct:
if hasMergeableFields(dst) {
for i, n := 0, dst.NumField(); i < n; i++ {
if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil {
return
}
}
} else {
if dst.CanSet() && (isReflectNil(dst) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc) {
dst.Set(src)
}
}
case reflect.Map:
if dst.IsNil() && !src.IsNil() {
if dst.CanSet() {
dst.Set(reflect.MakeMap(dst.Type()))
} else {
dst = src
return
}
}
if src.Kind() != reflect.Map {
if overwrite && dst.CanSet() {
dst.Set(src)
}
return
}
for _, key := range src.MapKeys() {
srcElement := src.MapIndex(key)
if !srcElement.IsValid() {
continue
}
dstElement := dst.MapIndex(key)
switch srcElement.Kind() {
case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice:
if srcElement.IsNil() {
if overwrite {
dst.SetMapIndex(key, srcElement)
}
continue
}
fallthrough
default:
if !srcElement.CanInterface() {
continue
}
switch reflect.TypeOf(srcElement.Interface()).Kind() {
case reflect.Struct:
fallthrough
case reflect.Ptr:
fallthrough
case reflect.Map:
srcMapElm := srcElement
dstMapElm := dstElement
if srcMapElm.CanInterface() {
srcMapElm = reflect.ValueOf(srcMapElm.Interface())
if dstMapElm.IsValid() {
dstMapElm = reflect.ValueOf(dstMapElm.Interface())
}
}
if err = deepMerge(dstMapElm, srcMapElm, visited, depth+1, config); err != nil {
return
}
case reflect.Slice:
srcSlice := reflect.ValueOf(srcElement.Interface())
var dstSlice reflect.Value
if !dstElement.IsValid() || dstElement.IsNil() {
dstSlice = reflect.MakeSlice(srcSlice.Type(), 0, srcSlice.Len())
} else {
dstSlice = reflect.ValueOf(dstElement.Interface())
}
if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy {
if typeCheck && srcSlice.Type() != dstSlice.Type() {
return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
}
dstSlice = srcSlice
} else if config.AppendSlice {
if srcSlice.Type() != dstSlice.Type() {
return fmt.Errorf("cannot append two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
}
dstSlice = reflect.AppendSlice(dstSlice, srcSlice)
} else if sliceDeepCopy {
i := 0
for ; i < srcSlice.Len() && i < dstSlice.Len(); i++ {
srcElement := srcSlice.Index(i)
dstElement := dstSlice.Index(i)
if srcElement.CanInterface() {
srcElement = reflect.ValueOf(srcElement.Interface())
}
if dstElement.CanInterface() {
dstElement = reflect.ValueOf(dstElement.Interface())
}
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
return
}
}
}
dst.SetMapIndex(key, dstSlice)
}
}
if dstElement.IsValid() && !isEmptyValue(dstElement, !config.ShouldNotDereference) {
if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice {
continue
}
if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map && reflect.TypeOf(dstElement.Interface()).Kind() == reflect.Map {
continue
}
}
if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement, !config.ShouldNotDereference)) {
if dst.IsNil() {
dst.Set(reflect.MakeMap(dst.Type()))
}
dst.SetMapIndex(key, srcElement)
}
}
// Ensure that all keys in dst are deleted if they are not in src.
if overwriteWithEmptySrc {
for _, key := range dst.MapKeys() {
srcElement := src.MapIndex(key)
if !srcElement.IsValid() {
dst.SetMapIndex(key, reflect.Value{})
}
}
}
case reflect.Slice:
if !dst.CanSet() {
break
}
if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy {
dst.Set(src)
} else if config.AppendSlice {
if src.Type() != dst.Type() {
return fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type())
}
dst.Set(reflect.AppendSlice(dst, src))
} else if sliceDeepCopy {
for i := 0; i < src.Len() && i < dst.Len(); i++ {
srcElement := src.Index(i)
dstElement := dst.Index(i)
if srcElement.CanInterface() {
srcElement = reflect.ValueOf(srcElement.Interface())
}
if dstElement.CanInterface() {
dstElement = reflect.ValueOf(dstElement.Interface())
}
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
return
}
}
}
case reflect.Ptr:
fallthrough
case reflect.Interface:
if isReflectNil(src) {
if overwriteWithEmptySrc && dst.CanSet() && src.Type().AssignableTo(dst.Type()) {
dst.Set(src)
}
break
}
if src.Kind() != reflect.Interface {
if dst.IsNil() || (src.Kind() != reflect.Ptr && overwrite) {
if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) {
dst.Set(src)
}
} else if src.Kind() == reflect.Ptr {
if !config.ShouldNotDereference {
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
return
}
} else {
if overwriteWithEmptySrc || (overwrite && !src.IsNil()) || dst.IsNil() {
dst.Set(src)
}
}
} else if dst.Elem().Type() == src.Type() {
if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil {
return
}
} else {
return ErrDifferentArgumentsTypes
}
break
}
if dst.IsNil() || overwrite {
if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) {
dst.Set(src)
}
break
}
if dst.Elem().Kind() == src.Elem().Kind() {
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
return
}
break
}
default:
mustSet := (isEmptyValue(dst, !config.ShouldNotDereference) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc)
if mustSet {
if dst.CanSet() {
dst.Set(src)
} else {
dst = src
}
}
}
return
}
// Merge will fill any empty for value type attributes on the dst struct using corresponding
// src attributes if they themselves are not empty. dst and src must be valid same-type structs
// and dst must be a pointer to struct.
// It won't merge unexported (private) fields and will do recursively any exported field.
func Merge(dst, src interface{}, opts ...func(*Config)) error {
return merge(dst, src, opts...)
}
// MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overridden by
// non-empty src attribute values.
// Deprecated: use Merge(…) with WithOverride
func MergeWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
return merge(dst, src, append(opts, WithOverride)...)
}
// WithTransformers adds transformers to merge, allowing to customize the merging of some types.
func WithTransformers(transformers Transformers) func(*Config) {
return func(config *Config) {
config.Transformers = transformers
}
}
// WithOverride will make merge override non-empty dst attributes with non-empty src attributes values.
func WithOverride(config *Config) {
config.Overwrite = true
}
// WithOverwriteWithEmptyValue will make merge override non empty dst attributes with empty src attributes values.
func WithOverwriteWithEmptyValue(config *Config) {
config.Overwrite = true
config.overwriteWithEmptyValue = true
}
// WithOverrideEmptySlice will make merge override empty dst slice with empty src slice.
func WithOverrideEmptySlice(config *Config) {
config.overwriteSliceWithEmptyValue = true
}
// WithoutDereference prevents dereferencing pointers when evaluating whether they are empty
// (i.e. a non-nil pointer is never considered empty).
func WithoutDereference(config *Config) {
config.ShouldNotDereference = true
}
// WithAppendSlice will make merge append slices instead of overwriting it.
func WithAppendSlice(config *Config) {
config.AppendSlice = true
}
// WithTypeCheck will make merge check types while overwriting it (must be used with WithOverride).
func WithTypeCheck(config *Config) {
config.TypeCheck = true
}
// WithSliceDeepCopy will merge slice element one by one with Overwrite flag.
func WithSliceDeepCopy(config *Config) {
config.sliceDeepCopy = true
config.Overwrite = true
}
func merge(dst, src interface{}, opts ...func(*Config)) error {
if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
return ErrNonPointerArgument
}
var (
vDst, vSrc reflect.Value
err error
)
config := &Config{}
for _, opt := range opts {
opt(config)
}
if vDst, vSrc, err = resolveValues(dst, src); err != nil {
return err
}
if vDst.Type() != vSrc.Type() {
return ErrDifferentArgumentsTypes
}
return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
}
// IsReflectNil is the reflect value provided nil
func isReflectNil(v reflect.Value) bool {
k := v.Kind()
switch k {
case reflect.Interface, reflect.Slice, reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr:
// Both interface and slice are nil if first word is 0.
// Both are always bigger than a word; assume flagIndir.
return v.IsNil()
default:
return false
}
}

81
vendor/dario.cat/mergo/mergo.go vendored Normal file
View File

@ -0,0 +1,81 @@
// Copyright 2013 Dario Castañé. All rights reserved.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Based on src/pkg/reflect/deepequal.go from official
// golang's stdlib.
package mergo
import (
"errors"
"reflect"
)
// Errors reported by Mergo when it finds invalid arguments.
var (
ErrNilArguments = errors.New("src and dst must not be nil")
ErrDifferentArgumentsTypes = errors.New("src and dst must be of same type")
ErrNotSupported = errors.New("only structs, maps, and slices are supported")
ErrExpectedMapAsDestination = errors.New("dst was expected to be a map")
ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct")
ErrNonPointerArgument = errors.New("dst must be a pointer")
)
// During deepMerge, must keep track of checks that are
// in progress. The comparison algorithm assumes that all
// checks in progress are true when it reencounters them.
// Visited are stored in a map indexed by 17 * a1 + a2;
type visit struct {
typ reflect.Type
next *visit
ptr uintptr
}
// From src/pkg/encoding/json/encode.go.
func isEmptyValue(v reflect.Value, shouldDereference bool) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
if v.IsNil() {
return true
}
if shouldDereference {
return isEmptyValue(v.Elem(), shouldDereference)
}
return false
case reflect.Func:
return v.IsNil()
case reflect.Invalid:
return true
}
return false
}
func resolveValues(dst, src interface{}) (vDst, vSrc reflect.Value, err error) {
if dst == nil || src == nil {
err = ErrNilArguments
return
}
vDst = reflect.ValueOf(dst).Elem()
if vDst.Kind() != reflect.Struct && vDst.Kind() != reflect.Map && vDst.Kind() != reflect.Slice {
err = ErrNotSupported
return
}
vSrc = reflect.ValueOf(src)
// We check if vSrc is a pointer to dereference it.
if vSrc.Kind() == reflect.Ptr {
vSrc = vSrc.Elem()
}
return
}

Some files were not shown because too many files have changed in this diff Show More