Compare commits

..

92 Commits
4.1.1 ... 4.3.2

Author SHA1 Message Date
db3d7e53a4 Merge pull request #698 from chrisallenlane/4.3.2
4.3.2
2022-08-26 14:07:14 -04:00
06c4ff52fc chore: bump version to 4.3.2 2022-08-26 13:56:35 -04:00
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
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
5c5ed7344f chore(docs): improve configuration docs (#656)
Improve the configuration documentation in `configs/conf.yml` (#656).
2022-08-26 13:55:09 -04:00
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
2717044b62 chore(docs): update INSTALLING.md (#677)
Update `INSTALLING.md` with more package information. See: #677
2022-08-26 13:55:09 -04:00
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
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
f1540290a7 chore(deps): update dependencies 2022-08-26 11:06:21 -04:00
0b80a608c3 Merge pull request #692 from chrisallenlane/4.3.1
4.3.1
2022-08-08 21:01:56 -04:00
3c1e24a0e8 chore: bump version to 4.3.1 2022-08-08 20:38:23 -04:00
2a6586b41b fix(installer): always use more pager on Windows 2022-08-08 20:38:23 -04:00
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
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
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
b53a14b1a7 Merge pull request #691 from chrisallenlane/4.3.0
4.3.0
2022-08-07 14:45:27 -04:00
f1e8602369 chore: bump version to 4.3.0 2022-08-07 14:11:47 -04:00
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
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
4fdec50487 chore(deps): upgrade dependencies
- Upgrade to Go 1.19 in `go.mod`
- Upgrade dependencies
2022-08-07 10:19:56 -04:00
9de866dfb6 Merge pull request #689 from chrisallenlane/ci
chore(ci): YAML lints
2022-08-05 07:53:53 -04:00
eb99a070ce chore: CI template nits
- Fix YAML nits per `yamllint`
- Upgrade Go version to `1.19`
2022-08-05 07:49:20 -04:00
73f80bde48 Merge pull request #688 from chrisallenlane/4.2.7
4.2.7
2022-08-05 07:01:51 -04:00
8130b2f3bd chore: bump version to 4.2.7 2022-08-05 06:41:16 -04:00
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
85f5ae8ec7 chore: various lint corrections
Make various lint corrections in order to appease `staticcheck`.
2022-08-04 20:43:50 -04:00
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
cfd1702bc6 Merge pull request #685 from chrisallenlane/page-bug
Fix #681
2022-08-02 20:37:48 -04:00
7406ebfb5e chore(deps): update dependencies 2022-08-02 20:30:31 -04:00
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
a23d372d1f docs(INSTALLING): nix link
Update the `nix` package information.
2022-07-06 08:11:42 -04:00
fe66ff3768 Merge pull request #679 from chrisallenlane/hacking-md
docs: create `HACKING.md`
2022-07-05 15:11:09 -04:00
7fed1f63a6 docs: create HACKING.md
Create a `HACKING.md` file for onboarding new developers to `cheat`.
2022-07-05 15:07:34 -04:00
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
ef1da90a77 Merge pull request #678 from chrisallenlane/go-install
Build/CI corrections
2022-07-05 12:24:48 -04:00
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
f8403ff241 Merge pull request #676 from chrisallenlane/install-md
docs: create `INSTALLING.md`
2022-07-05 11:24:02 -04:00
65f6be3fd8 docs: create INSTALLING.md
- Create `INSTALLING.md`
- Update the `README.md`
2022-07-05 11:19:40 -04:00
1cb53697d2 Merge pull request #674 from cheat/4.2.5
4.2.5
2022-07-04 22:37:19 -04:00
14f321b0e6 chore: bump version to 4.2.5 2022-07-04 22:00:35 -04:00
d3250fda79 chore(deps): upgrade vendored dependencies 2022-07-04 22:00:35 -04:00
c482488c41 fix: replace Parse with ParseArgs
Parse() is deprecated
2022-07-04 22:00:35 -04:00
fe8f39013e Merge pull request #673 from chrisallenlane/win-compat
fix: Windows compatibility
2022-07-04 17:03:11 -04:00
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
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
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
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
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
12ffa4cb5c Merge pull request #644 from cheat/develop
Windows fixes, Android support
2021-10-09 12:13:01 -04:00
d9c602f9e1 Merge pull request #643 from chrisallenlane/android
fix(Paths): Android support
2021-10-09 11:30:18 -04:00
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
a500a621a1 chore: bump version
Bump version to 4.2.3.
2021-10-09 10:59:02 -04:00
23b6928874 Merge pull request #639 from mattn/fix-windows
Fix Windows
2021-10-09 10:10:39 -04:00
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
ad501c4cbe Merge pull request #641 from OmgImAlexis/patch-1
chore: fix typo in comment
2021-10-09 09:39:41 -04:00
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
2c097adeda chore: fix typo in comment 2021-09-30 07:30:20 +09:30
b825e0f535 Fix Windows 2021-09-29 01:33:59 +09:00
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
768d55e5d4 chore: bump version
Bump version to `4.2.2`.
2021-06-08 21:02:03 -04:00
6aedc5c116 chore: whitespace edit on Makefile 2021-06-08 20:59:57 -04:00
e881bb1f97 chore: update go.sum 2021-06-08 20:59:57 -04:00
501f9c66ad deps: upgrade dependencies 2021-06-08 20:59:57 -04:00
a2aa82d9f3 Add ARM64/ARMv8 build 2021-06-08 20:59:57 -04:00
018bce7ad5 Fix ZSH autocompletion 2021-06-07 11:42:47 +02:00
17acefdd9b Merge pull request #617 from bernermic/master
Adds some git helper scripts
2021-05-14 12:21:26 -04:00
37918e09a4 Adds some git helper scripts 2021-05-07 20:53:54 +02:00
86967873a8 Merge pull request #623 from cheat/github-actions
chore: migrate into Github Actions
2021-05-03 17:02:27 -04:00
d237d98c15 chore: migrate into Github Actions
Replace Travis CI integration in favor of Github Actions.
2021-05-03 16:43:27 -04:00
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
b0a351033d Upgrade to GitHub-native Dependabot 2021-04-29 20:40:56 +00:00
1eb44e8809 Merge pull request #621 from chrisallenlane/v4.2.1
Squashed commit of the following:
2021-04-28 12:55:17 -04:00
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
883a17092f Merge pull request #606 from chrisallenlane/4.2.0
4.2.0
2020-11-28 11:27:09 -05:00
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
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
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
fb538baba5 chore(version): bump to 4.2.0 2020-11-27 22:57:25 -05:00
1a7b5c6127 feat(display): make Faint respect Colorize
Make `display.Faint` respect the `Colorize` config value.
2020-11-27 22:50:55 -05:00
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
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
a58294859e chore: spelling
`s/pathSheets/pathsheets/g` in `cmd_list` for consistency elsewhere.
2020-11-27 22:26:14 -05:00
606092e288 feat(search): improve search output formatting
Improve the search output formatting.
2020-11-27 17:06:02 -05:00
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
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
367673d5d9 chore(dependencies): update dependencies
Run `make vendor-update`.
2020-11-27 09:51:39 -05:00
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
3f4d4bddb2 feat(tests): add unit-tests
Add unit-tests for `sheets.Load`.
2020-11-11 19:33:31 -05:00
6c6753b35c Merge pull request #599 from chrisallenlane/issue-597
fix: update installation instructions in README
2020-11-07 18:56:37 -05:00
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
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
647 changed files with 50933 additions and 26962 deletions

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

View File

@ -1,23 +1,11 @@
# For most projects, this workflow file will not need changing; you simply need ---
# to commit it to your repository. name: CodeQL
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
# ******** NOTE ********
name: "CodeQL"
on: on:
push: push:
branches: [ master ] branches: [master]
pull_request: pull_request:
# The branches below must be a subset of the branches above branches: [master]
branches: [ master ]
schedule: schedule:
- cron: '45 23 * * 0' - cron: '45 23 * * 0'
@ -25,42 +13,18 @@ jobs:
analyze: analyze:
name: Analyze name: Analyze
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
language: [ 'go' ] language: [go]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Initialize CodeQL
# Initializes the CodeQL tools for scanning. uses: github/codeql-action/init@v1
- name: Initialize CodeQL with:
uses: github/codeql-action/init@v1 languages: ${{ matrix.language }}
with: - name: Autobuild
languages: ${{ matrix.language }} uses: github/codeql-action/autobuild@v1
# If you wish to specify custom queries, you can do so here or in a config file. - name: Perform CodeQL Analysis
# By default, queries listed here will override any specified in a config file. uses: github/codeql-action/analyze@v1
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
#- name: Autobuild
#uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
- run: make
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@ -1,3 +1,4 @@
---
name: homebrew name: homebrew
on: on:
@ -11,7 +12,8 @@ jobs:
steps: steps:
- uses: mislav/bump-homebrew-formula-action@v1 - uses: mislav/bump-homebrew-formula-action@v1
with: with:
# A PR will be sent to github.com/Homebrew/homebrew-core to update this formula: # A PR will be sent to github.com/Homebrew/homebrew-core to update
# this formula:
formula-name: cheat formula-name: cheat
env: env:
COMMITTER_TOKEN: ${{ secrets.COMMITTER_TOKEN }} COMMITTER_TOKEN: ${{ secrets.COMMITTER_TOKEN }}

View File

@ -1,15 +0,0 @@
language: go
go:
- 1.14.x
os:
- linux
- osx
env:
- GO111MODULE=on
install: true
script: make ci

View File

@ -19,7 +19,8 @@ tracker][issues] to discuss with the maintainer whether it would be considered
for merging. for merging.
`cheat` is mostly mature and feature-complete, but may still have some room for `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 #### #### Add documentation ####
Did you encounter features, bugs, edge-cases, use-cases, or environment 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 `cheat` simply by telling others about it. Share it with friends and coworkers
that might benefit from using it. that might benefit from using it.
#### Pull Requests ####
Please open all pull-requests against the `develop` branch.
[cheat]: https://github.com/cheat/cheat [cheat]: https://github.com/cheat/cheat
[cheatsheets]: https://github.com/cheat/cheatsheets [cheatsheets]: https://github.com/cheat/cheatsheets
[hacking]: HACKING.md
[issues]: https://github.com/cheat/cheat/issues [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 [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 [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.3.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.3.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

View File

@ -7,6 +7,7 @@ dist_dir := ./dist
CAT := cat CAT := cat
COLUMN := column COLUMN := column
CTAGS := ctags CTAGS := ctags
DOCKER := docker
GO := go GO := go
GREP := grep GREP := grep
GZIP := gzip --best GZIP := gzip --best
@ -20,6 +21,8 @@ SED := sed
SORT := sort SORT := sort
ZIP := zip -m ZIP := zip -m
docker_image := cheat-devel:latest
# build flags # build flags
BUILD_FLAGS := -ldflags="-s -w" -mod vendor -trimpath BUILD_FLAGS := -ldflags="-s -w" -mod vendor -trimpath
GOBIN := GOBIN :=
@ -33,21 +36,18 @@ releases := \
$(dist_dir)/cheat-linux-arm5 \ $(dist_dir)/cheat-linux-arm5 \
$(dist_dir)/cheat-linux-arm6 \ $(dist_dir)/cheat-linux-arm6 \
$(dist_dir)/cheat-linux-arm7 \ $(dist_dir)/cheat-linux-arm7 \
$(dist_dir)/cheat-linux-arm64 \
$(dist_dir)/cheat-windows-amd64.exe $(dist_dir)/cheat-windows-amd64.exe
## build: build an executable for your architecture ## build: build an executable for your architecture
.PHONY: build .PHONY: build
build: $(dist_dir) clean vendor generate man build: $(dist_dir) | clean generate fmt lint vet vendor man
$(GO) build $(BUILD_FLAGS) -o $(dist_dir)/cheat $(cmd_dir) $(GO) build $(BUILD_FLAGS) -o $(dist_dir)/cheat $(cmd_dir)
## build-release: build release executables ## build-release: build release executables
.PHONY: build-release .PHONY: build-release
build-release: $(releases) build-release: $(releases)
## ci: build a "release" executable for the current architecture (used in ci)
.PHONY: ci
ci: | setup prepare build
# cheat-darwin-amd64 # cheat-darwin-amd64
$(dist_dir)/cheat-darwin-amd64: prepare $(dist_dir)/cheat-darwin-amd64: prepare
GOARCH=amd64 GOOS=darwin \ GOARCH=amd64 GOOS=darwin \
@ -77,11 +77,16 @@ $(dist_dir)/cheat-linux-arm6: prepare
$(dist_dir)/cheat-linux-arm7: prepare $(dist_dir)/cheat-linux-arm7: prepare
GOARCH=arm GOOS=linux GOARM=7 \ GOARCH=arm GOOS=linux GOARM=7 \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz $(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-windows-amd64 # cheat-windows-amd64
$(dist_dir)/cheat-windows-amd64.exe: prepare $(dist_dir)/cheat-windows-amd64.exe: prepare
GOARCH=amd64 GOOS=windows \ GOARCH=amd64 GOOS=windows \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(ZIP) $@.zip $@ $(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(ZIP) $@.zip $@ -j
# ./dist # ./dist
$(dist_dir): $(dist_dir):
@ -99,12 +104,13 @@ install: build
## clean: remove compiled executables ## clean: remove compiled executables
.PHONY: clean .PHONY: clean
clean: $(dist_dir) clean: $(dist_dir)
$(RM) -f $(dist_dir)/* $(RM) -f $(dist_dir)/* $(cmd_dir)/str_config.go $(cmd_dir)/str_usage.go
## distclean: remove the tags file ## distclean: remove the tags file
.PHONY: distclean .PHONY: distclean
distclean: distclean:
$(RM) -f tags $(RM) -f tags
@$(DOCKER) image rm -f $(docker_image)
## setup: install revive (linter) and scc (sloc tool) ## setup: install revive (linter) and scc (sloc tool)
.PHONY: setup .PHONY: setup
@ -132,6 +138,10 @@ man:
vendor: vendor:
$(GO) mod vendor && $(GO) mod tidy && $(GO) mod verify $(GO) mod vendor && $(GO) mod tidy && $(GO) mod verify
## vendor-update: update vendored dependencies
vendor-update:
$(GO) get -t -u ./... && $(GO) mod vendor
## fmt: run go fmt ## fmt: run go fmt
.PHONY: fmt .PHONY: fmt
fmt: fmt:
@ -165,6 +175,16 @@ check: | vendor fmt lint vet test
.PHONY: prepare .PHONY: prepare
prepare: | $(dist_dir) clean generate vendor fmt lint vet test prepare: | $(dist_dir) clean 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 ## help: display this help text
.PHONY: help .PHONY: help
help: help:

180
README.md
View File

@ -1,8 +1,9 @@
![Workflow status](https://github.com/cheat/cheat/actions/workflows/build.yml/badge.svg)
cheat 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 `cheat` allows you to create and view interactive cheatsheets on the
command-line. It was designed to help remind \*nix system administrators of command-line. It was designed to help remind \*nix system administrators of
options for commands that they use frequently, but not frequently enough to options for commands that they use frequently, but not frequently enough to
@ -41,99 +42,6 @@ tar -xjvf '/path/to/foo.tgz'
tar -cjvf '/path/to/foo.tgz' '/path/to/foo/' 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 will be auto-generated on first run.
Should you need to create a config file manually, you can do
so via:
```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]. 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.
Usage Usage
----- -----
To view a cheatsheet: To view a cheatsheet:
@ -194,7 +102,77 @@ 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
-------------- --------------
Shell autocompletion is currently available for `bash`, `fish`, and `zsh`. Copy Shell autocompletion is currently available for `bash`, `fish`, and `zsh`. Copy
the relevant [completion script][completions] into the appropriate directory on the relevant [completion script][completions] into the appropriate directory on
@ -207,7 +185,9 @@ Additionally, `cheat` supports enhanced autocompletion via integration with
1. Ensure that `fzf` is available on your `$PATH` 1. Ensure that `fzf` is available on your `$PATH`
2. Set an envvar: `export CHEAT_USE_FZF=true` 2. Set an envvar: `export CHEAT_USE_FZF=true`
[Releases]: https://github.com/cheat/cheat/releases [INSTALLING.md]: INSTALLING.md
[cheatsheets]: https://github.com/cheat/cheatsheets [Releases]: https://github.com/cheat/cheat/releases
[completions]: https://github.com/cheat/cheat/tree/master/scripts [cheatsheets]: https://github.com/cheat/cheatsheets
[fzf]: https://github.com/junegunn/fzf [completions]: https://github.com/cheat/cheat/tree/master/scripts
[fzf]: https://github.com/junegunn/fzf
[go]: https://golang.org

View File

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

View File

@ -18,14 +18,10 @@ func cmdDirectories(opts map[string]interface{}, conf config.Config) {
// generate sorted, columnized output // generate sorted, columnized output
for _, path := range conf.Cheatpaths { for _, path := range conf.Cheatpaths {
fmt.Fprintln(w, fmt.Sprintf( fmt.Fprintf(w, "%s:\t%s\n", path.Name, path.Path)
"%s:\t%s",
path.Name,
path.Path,
))
} }
// write columnized output to stdout // write columnized output to stdout
w.Flush() w.Flush()
display.Display(out.String(), conf) display.Write(out.String(), conf)
} }

View File

@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"path" "path/filepath"
"strings" "strings"
"github.com/cheat/cheat/internal/cheatpath" "github.com/cheat/cheat/internal/cheatpath"
@ -20,7 +20,7 @@ func cmdEdit(opts map[string]interface{}, conf config.Config) {
// load the cheatsheets // load the cheatsheets
cheatsheets, err := sheets.Load(conf.Cheatpaths) cheatsheets, err := sheets.Load(conf.Cheatpaths)
if err != nil { 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) os.Exit(1)
} }
@ -58,10 +58,10 @@ func cmdEdit(opts map[string]interface{}, conf config.Config) {
} }
// compute the new edit path // compute the new edit path
editpath = path.Join(writepath.Path, sheet.Title) editpath = filepath.Join(writepath.Path, sheet.Title)
// create any necessary subdirectories // create any necessary subdirectories
dirs := path.Dir(editpath) dirs := filepath.Dir(editpath)
if dirs != "." { if dirs != "." {
if err := os.MkdirAll(dirs, 0755); err != nil { if err := os.MkdirAll(dirs, 0755); err != nil {
fmt.Fprintf(os.Stderr, "failed to create directory: %s, %v\n", dirs, err) 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 // compute the new edit path
editpath = path.Join(writepath.Path, cheatsheet) editpath = filepath.Join(writepath.Path, cheatsheet)
// create any necessary subdirectories // create any necessary subdirectories
dirs := path.Dir(editpath) dirs := filepath.Dir(editpath)
if dirs != "." { if dirs != "." {
if err := os.MkdirAll(dirs, 0755); err != nil { if err := os.MkdirAll(dirs, 0755); err != nil {
fmt.Fprintf(os.Stderr, "failed to create directory: %s, %v\n", dirs, err) fmt.Fprintf(os.Stderr, "failed to create directory: %s, %v\n", dirs, err)

View File

@ -3,7 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"path" "path/filepath"
"runtime" "runtime"
"strings" "strings"
@ -42,11 +42,11 @@ func cmdInit() {
// determine the appropriate paths for config data and (optional) community // determine the appropriate paths for config data and (optional) community
// cheatsheets based on the user's platform // cheatsheets based on the user's platform
confpath := confpaths[0] confpath := confpaths[0]
confdir := path.Dir(confpath) confdir := filepath.Dir(confpath)
// create paths for community and personal cheatsheets // create paths for community and personal cheatsheets
community := path.Join(confdir, "/cheatsheets/community") community := filepath.Join(confdir, "cheatsheets", "community")
personal := path.Join(confdir, "/cheatsheets/personal") personal := filepath.Join(confdir, "cheatsheets", "personal")
// template the above paths into the default configs // template the above paths into the default configs
configs = strings.Replace(configs, "COMMUNITY_PATH", community, -1) configs = strings.Replace(configs, "COMMUNITY_PATH", community, -1)

View File

@ -21,11 +21,11 @@ func cmdList(opts map[string]interface{}, conf config.Config) {
// load the cheatsheets // load the cheatsheets
cheatsheets, err := sheets.Load(conf.Cheatpaths) cheatsheets, err := sheets.Load(conf.Cheatpaths)
if err != nil { 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) os.Exit(1)
} }
// filter cheatcheats by tag if --tag was provided // filter cheatsheets by tag if --tag was provided
if opts["--tag"] != nil { if opts["--tag"] != nil {
cheatsheets = sheets.Filter( cheatsheets = sheets.Filter(
cheatsheets, cheatsheets,
@ -37,8 +37,8 @@ func cmdList(opts map[string]interface{}, conf config.Config) {
// sheets with local sheets), here we simply want to create a slice // sheets with local sheets), here we simply want to create a slice
// containing all sheets. // containing all sheets.
flattened := []sheet.Sheet{} flattened := []sheet.Sheet{}
for _, pathSheets := range cheatsheets { for _, pathsheets := range cheatsheets {
for _, s := range pathSheets { for _, s := range pathsheets {
flattened = append(flattened, s) flattened = append(flattened, s)
} }
} }
@ -63,10 +63,7 @@ func cmdList(opts map[string]interface{}, conf config.Config) {
// compile the regex // compile the regex
reg, err := regexp.Compile(pattern) reg, err := regexp.Compile(pattern)
if err != nil { if err != nil {
fmt.Fprintln( fmt.Fprintf(os.Stderr, "failed to compile regexp: %s, %v\n", pattern, err)
os.Stderr,
fmt.Sprintf("failed to compile regexp: %s, %v", pattern, err),
)
os.Exit(1) os.Exit(1)
} }
@ -95,15 +92,10 @@ func cmdList(opts map[string]interface{}, conf config.Config) {
// generate sorted, columnized output // generate sorted, columnized output
for _, sheet := range flattened { for _, sheet := range flattened {
fmt.Fprintln(w, fmt.Sprintf( fmt.Fprintf(w, "%s\t%s\t%s\n", sheet.Title, sheet.Path, strings.Join(sheet.Tags, ","))
"%s\t%s\t%s",
sheet.Title,
sheet.Path,
strings.Join(sheet.Tags, ","),
))
} }
// write columnized output to stdout // write columnized output to stdout
w.Flush() w.Flush()
display.Display(out.String(), conf) display.Write(out.String(), conf)
} }

View File

@ -17,7 +17,7 @@ func cmdRemove(opts map[string]interface{}, conf config.Config) {
// load the cheatsheets // load the cheatsheets
cheatsheets, err := sheets.Load(conf.Cheatpaths) cheatsheets, err := sheets.Load(conf.Cheatpaths)
if err != nil { 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) os.Exit(1)
} }
@ -37,19 +37,19 @@ func cmdRemove(opts map[string]interface{}, conf config.Config) {
// fail early if the requested cheatsheet does not exist // fail early if the requested cheatsheet does not exist
sheet, ok := consolidated[cheatsheet] sheet, ok := consolidated[cheatsheet]
if !ok { if !ok {
fmt.Fprintln(os.Stderr, fmt.Sprintf("No cheatsheet found for '%s'.\n", cheatsheet)) fmt.Fprintf(os.Stderr, "No cheatsheet found for '%s'.\n", cheatsheet)
os.Exit(2) os.Exit(2)
} }
// fail early if the sheet is read-only // fail early if the sheet is read-only
if sheet.ReadOnly { if sheet.ReadOnly {
fmt.Fprintln(os.Stderr, fmt.Sprintf("cheatsheet '%s' is read-only.", cheatsheet)) fmt.Fprintf(os.Stderr, "cheatsheet '%s' is read-only.\n", cheatsheet)
os.Exit(1) os.Exit(1)
} }
// otherwise, attempt to delete the sheet // otherwise, attempt to delete the sheet
if err := os.Remove(sheet.Path); err != nil { if err := os.Remove(sheet.Path); err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to delete sheet: %s, %v", sheet.Title, err)) fmt.Fprintf(os.Stderr, "failed to delete sheet: %s, %v\n", sheet.Title, err)
os.Exit(1) os.Exit(1)
} }
} }

View File

@ -8,7 +8,6 @@ import (
"github.com/cheat/cheat/internal/config" "github.com/cheat/cheat/internal/config"
"github.com/cheat/cheat/internal/display" "github.com/cheat/cheat/internal/display"
"github.com/cheat/cheat/internal/sheet"
"github.com/cheat/cheat/internal/sheets" "github.com/cheat/cheat/internal/sheets"
) )
@ -20,7 +19,7 @@ func cmdSearch(opts map[string]interface{}, conf config.Config) {
// load the cheatsheets // load the cheatsheets
cheatsheets, err := sheets.Load(conf.Cheatpaths) cheatsheets, err := sheets.Load(conf.Cheatpaths)
if err != nil { 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) os.Exit(1)
} }
@ -32,71 +31,67 @@ func cmdSearch(opts map[string]interface{}, conf config.Config) {
) )
} }
// consolidate the cheatsheets found on all paths into a single map of // iterate over each cheatpath
// `title` => `sheet` (ie, allow more local cheatsheets to override less
// local cheatsheets)
consolidated := sheets.Consolidate(cheatsheets)
// if <cheatsheet> was provided, search that single sheet only
if opts["<cheatsheet>"] != nil {
cheatsheet := opts["<cheatsheet>"].(string)
// assert that the cheatsheet exists
s, ok := consolidated[cheatsheet]
if !ok {
fmt.Printf("No cheatsheet found for '%s'.\n", cheatsheet)
os.Exit(2)
}
consolidated = map[string]sheet.Sheet{
cheatsheet: s,
}
}
// sort the cheatsheets alphabetically, and search for matches
out := "" out := ""
for _, sheet := range sheets.Sort(consolidated) { for _, pathcheats := range cheatsheets {
// assume that we want to perform a case-insensitive search for <phrase> // sort the cheatsheets alphabetically, and search for matches
pattern := "(?i)" + phrase for _, sheet := range sheets.Sort(pathcheats) {
// unless --regex is provided, in which case we pass the regex unaltered // if <cheatsheet> was provided, constrain the search only to
if opts["--regex"] == true { // matching cheatsheets
pattern = phrase if opts["<cheatsheet>"] != nil && sheet.Title != opts["<cheatsheet>"] {
} continue
}
// compile the regex // assume that we want to perform a case-insensitive search for <phrase>
reg, err := regexp.Compile(pattern) pattern := "(?i)" + phrase
if err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf("failed to compile regexp: %s, %v", pattern, err))
os.Exit(1)
}
// `Search` will return text entries that match the search terms. We're // unless --regex is provided, in which case we pass the regex unaltered
// using it here to overwrite the prior cheatsheet Text, filtering it to if opts["--regex"] == true {
// only what is relevant pattern = phrase
sheet.Text = sheet.Search(reg) }
// if the sheet did not match the search, ignore it and move on // compile the regex
if sheet.Text == "" { reg, err := regexp.Compile(pattern)
continue if err != nil {
} fmt.Fprintf(os.Stderr, "failed to compile regexp: %s, %v\n", pattern, err)
os.Exit(1)
}
// if colorization was requested, apply it here // `Search` will return text entries that match the search terms.
if conf.Color(opts) { // We're using it here to overwrite the prior cheatsheet Text,
sheet.Colorize(conf) // filtering it to only what is relevant.
} sheet.Text = sheet.Search(reg)
// output the cheatsheet title // if the sheet did not match the search, ignore it and move on
out += fmt.Sprintf("%s:\n", sheet.Title) if sheet.Text == "" {
continue
}
// indent each line of content with two spaces // if colorization was requested, apply it here
for _, line := range strings.Split(sheet.Text, "\n") { if conf.Color(opts) {
out += fmt.Sprintf(" %s\n", line) 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 // display the output
display.Display(out, conf) // 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)
} }

View File

@ -15,7 +15,7 @@ func cmdTags(opts map[string]interface{}, conf config.Config) {
// load the cheatsheets // load the cheatsheets
cheatsheets, err := sheets.Load(conf.Cheatpaths) cheatsheets, err := sheets.Load(conf.Cheatpaths)
if err != nil { 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) os.Exit(1)
} }
@ -26,5 +26,5 @@ func cmdTags(opts map[string]interface{}, conf config.Config) {
} }
// display the output // display the output
display.Display(out, conf) display.Write(out, conf)
} }

View File

@ -18,7 +18,7 @@ func cmdView(opts map[string]interface{}, conf config.Config) {
// load the cheatsheets // load the cheatsheets
cheatsheets, err := sheets.Load(conf.Cheatpaths) cheatsheets, err := sheets.Load(conf.Cheatpaths)
if err != nil { 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) os.Exit(1)
} }
@ -30,9 +30,39 @@ func cmdView(opts map[string]interface{}, conf config.Config) {
) )
} }
// consolidate the cheatsheets found on all paths into a single map of // if --all was passed, display cheatsheets from all cheatpaths
// `title` => `sheet` (ie, allow more local cheatsheets to override less if opts["--all"].(bool) {
// local cheatsheets) // 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) consolidated := sheets.Consolidate(cheatsheets)
// fail early if the requested cheatsheet does not exist // fail early if the requested cheatsheet does not exist
@ -48,5 +78,5 @@ func cmdView(opts map[string]interface{}, conf config.Config) {
} }
// display the cheatsheet // display the cheatsheet
display.Display(sheet.Text, conf) display.Write(sheet.Text, conf)
} }

View File

@ -3,17 +3,19 @@ Usage:
Options: Options:
--init Write a default config file to stdout --init Write a default config file to stdout
-a --all Search among all cheatpaths
-c --colorize Colorize output -c --colorize Colorize output
-d --directories List cheatsheet directories -d --directories List cheatsheet directories
-e --edit=<cheatsheet> Edit <cheatsheet> -e --edit=<cheatsheet> Edit <cheatsheet>
-l --list List cheatsheets -l --list List cheatsheets
-p --path=<name> Return only sheets found on path <name> -p --path=<name> Return only sheets found on cheatpath <name>
-r --regex Treat search <phrase> as a regex -r --regex Treat search <phrase> as a regex
-s --search=<phrase> Search cheatsheets for <phrase> -s --search=<phrase> Search cheatsheets for <phrase>
-t --tag=<tag> Return only sheets matching <tag> -t --tag=<tag> Return only sheets matching <tag>
-T --tags List all tags in use -T --tags List all tags in use
-v --version Print the version number -v --version Print the version number
--rm=<cheatsheet> Remove (delete) <cheatsheet> --rm=<cheatsheet> Remove (delete) <cheatsheet>
--conf Display the config file path
Examples: Examples:
@ -52,3 +54,6 @@ Examples:
To remove (delete) the foo/bar cheatsheet: To remove (delete) the foo/bar cheatsheet:
cheat --rm foo/bar cheat --rm foo/bar
To view the configuration file path:
cheat --conf

View File

@ -5,7 +5,6 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"path"
"runtime" "runtime"
"strings" "strings"
@ -17,12 +16,12 @@ import (
"github.com/cheat/cheat/internal/installer" "github.com/cheat/cheat/internal/installer"
) )
const version = "4.1.1" const version = "4.3.2"
func main() { func main() {
// initialize options // initialize options
opts, err := docopt.Parse(usage(), nil, true, version, false) opts, err := docopt.ParseArgs(usage(), nil, version)
if err != nil { if err != nil {
// panic here, because this should never happen // panic here, because this should never happen
panic(fmt.Errorf("docopt failed to parse: %v", err)) panic(fmt.Errorf("docopt failed to parse: %v", err))
@ -46,6 +45,9 @@ func main() {
envvars := map[string]string{} envvars := map[string]string{}
for _, e := range os.Environ() { for _, e := range os.Environ() {
pair := strings.SplitN(e, "=", 2) pair := strings.SplitN(e, "=", 2)
if runtime.GOOS == "windows" {
pair[0] = strings.ToUpper(pair[0])
}
envvars[pair[0]] = pair[1] envvars[pair[0]] = pair[1]
} }
@ -74,62 +76,16 @@ func main() {
os.Exit(0) os.Exit(0)
} }
// read the config template // choose a confpath
configs := configs()
// determine the appropriate paths for config data and (optional) community
// cheatsheets based on the user's platform
confpath = confpaths[0] confpath = confpaths[0]
confdir := path.Dir(confpath)
// create paths for community and personal cheatsheets // run the installer
community := path.Join(confdir, "/cheatsheets/community") if err := installer.Run(configs(), confpath); err != nil {
personal := path.Join(confdir, "/cheatsheets/personal") fmt.Fprintf(os.Stderr, "failed to run installer: %v\n", err)
// template the above paths into the default configs
configs = strings.Replace(configs, "COMMUNITY_PATH", community, -1)
configs = strings.Replace(configs, "PERSONAL_PATH", personal, -1)
// prompt the user to download the community cheatsheets
yes, err = installer.Prompt(
"Would you like to download the community cheatsheets? [Y/n]",
true,
)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to create config: %v\n", err)
os.Exit(1)
}
// clone the community cheatsheets if so instructed
if yes {
// clone the community cheatsheets
if err := installer.Clone(community); err != nil {
fmt.Fprintf(os.Stderr, "failed to create config: %v\n", err)
os.Exit(1)
}
// also create a directory for personal cheatsheets
if err := os.MkdirAll(personal, os.ModePerm); err != nil {
fmt.Fprintf(
os.Stderr,
"failed to create config: failed to create directory: %s: %v\n",
personal,
err)
os.Exit(1)
}
}
// the config file does not exist, so we'll try to create one
if err = config.Init(confpath, configs); err != nil {
fmt.Fprintf(
os.Stderr,
"failed to create config file: %s: %v\n",
confpath,
err,
)
os.Exit(1) os.Exit(1)
} }
// notify the user and exit
fmt.Printf("Created config file: %s\n", confpath) fmt.Printf("Created config file: %s\n", confpath)
fmt.Println("Please read this file for advanced configuration information.") fmt.Println("Please read this file for advanced configuration information.")
os.Exit(0) os.Exit(0)
@ -164,6 +120,9 @@ func main() {
var cmd func(map[string]interface{}, config.Config) var cmd func(map[string]interface{}, config.Config)
switch { switch {
case opts["--conf"].(bool):
cmd = cmdConf
case opts["--directories"].(bool): case opts["--directories"].(bool):
cmd = cmdDirectories cmd = cmdDirectories

View File

@ -9,10 +9,10 @@ import (
func configs() string { func configs() string {
return strings.TrimSpace(`--- return strings.TrimSpace(`---
# The editor to use with 'cheat -e <sheet>'. Defaults to $EDITOR or $VISUAL. # The editor to use with 'cheat -e <sheet>'. Defaults to $EDITOR or $VISUAL.
editor: vim editor: EDITOR_PATH
# Should 'cheat' always colorize output? # Should 'cheat' always colorize output?
colorize: true colorize: false
# Which 'chroma' colorscheme should be applied to the output? # Which 'chroma' colorscheme should be applied to the output?
# Options are available here: # Options are available here:
@ -21,40 +21,53 @@ style: monokai
# Which 'chroma' "formatter" should be applied? # Which 'chroma' "formatter" should be applied?
# One of: "terminal", "terminal256", "terminal16m" # One of: "terminal", "terminal256", "terminal16m"
formatter: terminal16m formatter: terminal256
# Through which pager should output be piped? (Unset this key for no pager.) # Through which pager should output be piped?
pager: less -FRX # 'less -FRX' is recommended on Unix systems
# 'more' is recommended on Windows
pager: PAGER_PATH
# The paths at which cheatsheets are available. Tags associated with a cheatpath # Cheatpaths are paths at which cheatsheets are available on your local
# are automatically attached to all cheatsheets residing on that path. # filesystem.
# #
# Whenever cheatsheets share the same title (like 'tar'), the most local # It is useful to sort cheatsheets into different cheatpaths for organizational
# cheatsheets (those which come later in this file) take precedent over the # purposes. For example, you might want one cheatpath for community
# less local sheets. This allows you to create your own "overides" for # cheatsheets, one for personal cheatsheets, one for cheatsheets pertaining to
# "upstream" cheatsheets. # your day job, one for code snippets, etc.
# #
# But what if you want to view the "upstream" cheatsheets instead of your own? # Cheatpaths are scoped, such that more "local" cheatpaths take priority over
# Cheatsheets may be filtered via 'cheat -t <tag>' in combination with other # more "global" cheatpaths. (The most global cheatpath is listed first in this
# commands. So, if you want to view the 'tar' cheatsheet that is tagged as # file; the most local is listed last.) For example, if there is a 'tar'
# 'community' rather than your own, you can use: cheat tar -t community # 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: cheatpaths:
# Cheatpath properties mean the following:
# Paths that come earlier are considered to be the most "global", and will # 'name': the name of the cheatpath (view with 'cheat -d', filter with 'cheat -p')
# thus be overridden by more local cheatsheets. That being the case, you # 'path': the filesystem path of the cheatsheet directory (view with 'cheat -d')
# should probably list community cheatsheets first. # 'tags': tags that should be automatically applied to sheets on this path
# # 'readonly': shall user-created ('cheat -e') cheatsheets be saved here?
# Note that the paths and tags listed below are placeholders. You may freely
# change them to suit your needs.
#
# 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
#
# Once downloaded, ensure that 'path' below points to the location at which
# you downloaded the community cheatsheets.
- name: community - name: community
path: COMMUNITY_PATH path: COMMUNITY_PATH
tags: [ community ] tags: [ community ]
@ -68,13 +81,13 @@ cheatpaths:
readonly: false readonly: false
# While it requires no configuration here, it's also worth noting that # While it requires no configuration here, it's also worth noting that
# 'cheat' will automatically append directories named '.cheat' within the # cheat will automatically append directories named '.cheat' within the
# current working directory to the 'cheatpath'. This can be very useful if # current working directory to the 'cheatpath'. This can be very useful if
# you'd like to closely associate cheatsheets with, for example, a directory # you'd like to closely associate cheatsheets with, for example, a directory
# containing source code. # containing source code.
# #
# Such "directory-scoped" cheatsheets will be treated as the most "local" # Such "directory-scoped" cheatsheets will be treated as the most "local"
# cheatsheets, and will override less "local" cheatsheets. Likewise, # cheatsheets, and will override less "local" cheatsheets. Similarly,
# directory-scoped cheatsheets will always be editable ('readonly: false'). # directory-scoped cheatsheets will always be editable ('readonly: false').
`) `)
} }

View File

@ -12,17 +12,19 @@ func usage() string {
Options: Options:
--init Write a default config file to stdout --init Write a default config file to stdout
-a --all Search among all cheatpaths
-c --colorize Colorize output -c --colorize Colorize output
-d --directories List cheatsheet directories -d --directories List cheatsheet directories
-e --edit=<cheatsheet> Edit <cheatsheet> -e --edit=<cheatsheet> Edit <cheatsheet>
-l --list List cheatsheets -l --list List cheatsheets
-p --path=<name> Return only sheets found on path <name> -p --path=<name> Return only sheets found on cheatpath <name>
-r --regex Treat search <phrase> as a regex -r --regex Treat search <phrase> as a regex
-s --search=<phrase> Search cheatsheets for <phrase> -s --search=<phrase> Search cheatsheets for <phrase>
-t --tag=<tag> Return only sheets matching <tag> -t --tag=<tag> Return only sheets matching <tag>
-T --tags List all tags in use -T --tags List all tags in use
-v --version Print the version number -v --version Print the version number
--rm=<cheatsheet> Remove (delete) <cheatsheet> --rm=<cheatsheet> Remove (delete) <cheatsheet>
--conf Display the config file path
Examples: Examples:
@ -61,5 +63,8 @@ Examples:
To remove (delete) the foo/bar cheatsheet: To remove (delete) the foo/bar cheatsheet:
cheat --rm foo/bar 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. # The editor to use with 'cheat -e <sheet>'. Defaults to $EDITOR or $VISUAL.
editor: vim editor: EDITOR_PATH
# Should 'cheat' always colorize output? # Should 'cheat' always colorize output?
colorize: true colorize: false
# Which 'chroma' colorscheme should be applied to the output? # Which 'chroma' colorscheme should be applied to the output?
# Options are available here: # Options are available here:
@ -12,40 +12,53 @@ style: monokai
# Which 'chroma' "formatter" should be applied? # Which 'chroma' "formatter" should be applied?
# One of: "terminal", "terminal256", "terminal16m" # One of: "terminal", "terminal256", "terminal16m"
formatter: terminal16m formatter: terminal256
# Through which pager should output be piped? (Unset this key for no pager.) # Through which pager should output be piped?
pager: less -FRX # 'less -FRX' is recommended on Unix systems
# 'more' is recommended on Windows
pager: PAGER_PATH
# The paths at which cheatsheets are available. Tags associated with a cheatpath # Cheatpaths are paths at which cheatsheets are available on your local
# are automatically attached to all cheatsheets residing on that path. # filesystem.
# #
# Whenever cheatsheets share the same title (like 'tar'), the most local # It is useful to sort cheatsheets into different cheatpaths for organizational
# cheatsheets (those which come later in this file) take precedent over the # purposes. For example, you might want one cheatpath for community
# less local sheets. This allows you to create your own "overides" for # cheatsheets, one for personal cheatsheets, one for cheatsheets pertaining to
# "upstream" cheatsheets. # your day job, one for code snippets, etc.
# #
# But what if you want to view the "upstream" cheatsheets instead of your own? # Cheatpaths are scoped, such that more "local" cheatpaths take priority over
# Cheatsheets may be filtered via 'cheat -t <tag>' in combination with other # more "global" cheatpaths. (The most global cheatpath is listed first in this
# commands. So, if you want to view the 'tar' cheatsheet that is tagged as # file; the most local is listed last.) For example, if there is a 'tar'
# 'community' rather than your own, you can use: cheat tar -t community # 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: cheatpaths:
# Cheatpath properties mean the following:
# Paths that come earlier are considered to be the most "global", and will # 'name': the name of the cheatpath (view with 'cheat -d', filter with 'cheat -p')
# thus be overridden by more local cheatsheets. That being the case, you # 'path': the filesystem path of the cheatsheet directory (view with 'cheat -d')
# should probably list community cheatsheets first. # 'tags': tags that should be automatically applied to sheets on this path
# # 'readonly': shall user-created ('cheat -e') cheatsheets be saved here?
# Note that the paths and tags listed below are placeholders. You may freely
# change them to suit your needs.
#
# 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
#
# Once downloaded, ensure that 'path' below points to the location at which
# you downloaded the community cheatsheets.
- name: community - name: community
path: COMMUNITY_PATH path: COMMUNITY_PATH
tags: [ community ] tags: [ community ]
@ -59,11 +72,11 @@ cheatpaths:
readonly: false readonly: false
# While it requires no configuration here, it's also worth noting that # While it requires no configuration here, it's also worth noting that
# 'cheat' will automatically append directories named '.cheat' within the # cheat will automatically append directories named '.cheat' within the
# current working directory to the 'cheatpath'. This can be very useful if # current working directory to the 'cheatpath'. This can be very useful if
# you'd like to closely associate cheatsheets with, for example, a directory # you'd like to closely associate cheatsheets with, for example, a directory
# containing source code. # containing source code.
# #
# Such "directory-scoped" cheatsheets will be treated as the most "local" # Such "directory-scoped" cheatsheets will be treated as the most "local"
# cheatsheets, and will override less "local" cheatsheets. Likewise, # cheatsheets, and will override less "local" cheatsheets. Similarly,
# directory-scoped cheatsheets will always be editable ('readonly: false'). # directory-scoped cheatsheets will always be editable ('readonly: false').

22
go.mod
View File

@ -1,19 +1,21 @@
module github.com/cheat/cheat module github.com/cheat/cheat
go 1.14 go 1.19
require ( require (
github.com/alecthomas/chroma v0.8.1 github.com/alecthomas/chroma v0.10.0
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
github.com/kr/text v0.2.0 // indirect github.com/mattn/go-isatty v0.0.16
github.com/mattn/go-isatty v0.0.12
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/sergi/go-diff v1.1.0 // indirect
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0
gopkg.in/yaml.v2 v2.3.0 gopkg.in/yaml.v2 v2.4.0
)
require (
github.com/dlclark/regexp2 v1.7.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
) )

60
go.sum
View File

@ -1,68 +1,38 @@
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
github.com/alecthomas/chroma v0.8.1 h1:ym20sbvyC6RXz45u4qDglcgr8E313oPROshcuCHqiEE=
github.com/alecthomas/chroma v0.8.1/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
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.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
github.com/dlclark/regexp2 v1.7.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 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 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/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 h1:a/mKvvZr9Jcc8oKfcmgzyp7OwF73JPWsQLvH1z2Kxck=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -46,7 +46,7 @@ func TestFilterFailure(t *testing.T) {
} }
// filter the paths // filter the paths
paths, err := Filter(paths, "qux") _, err := Filter(paths, "qux")
if err == nil { if err == nil {
t.Errorf("failed to return an error on non-existent cheatpath") 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" // NB: we're going backwards because we assume that the most "local"
// cheatpath will be specified last in the configs // cheatpath will be specified last in the configs
for i := len(cheatpaths) - 1; i >= 0; i-- { for i := len(cheatpaths) - 1; i >= 0; i-- {
// if the cheatpath is not read-only, it is writeable, and thus returned // 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 return cheatpaths[i], nil
} }
} }
// otherwise, return an error // otherwise, return an error

View File

@ -2,7 +2,6 @@ package config
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -21,13 +20,14 @@ type Config struct {
Style string `yaml:"style"` Style string `yaml:"style"`
Formatter string `yaml:"formatter"` Formatter string `yaml:"formatter"`
Pager string `yaml:"pager"` Pager string `yaml:"pager"`
Path string
} }
// New returns a new Config struct // New returns a new Config struct
func New(opts map[string]interface{}, confPath string, resolve bool) (Config, error) { func New(opts map[string]interface{}, confPath string, resolve bool) (Config, error) {
// read the config file // read the config file
buf, err := ioutil.ReadFile(confPath) buf, err := os.ReadFile(confPath)
if err != nil { if err != nil {
return Config{}, fmt.Errorf("could not read config file: %v", err) return Config{}, fmt.Errorf("could not read config file: %v", err)
} }
@ -35,6 +35,9 @@ func New(opts map[string]interface{}, confPath string, resolve bool) (Config, er
// initialize a config object // initialize a config object
conf := Config{} conf := Config{}
// store the config path
conf.Path = confPath
// unmarshal the yaml // unmarshal the yaml
err = yaml.UnmarshalStrict(buf, &conf) err = yaml.UnmarshalStrict(buf, &conf)
if err != nil { if err != nil {
@ -92,14 +95,11 @@ func New(opts map[string]interface{}, confPath string, resolve bool) (Config, er
conf.Cheatpaths[i].Path = expanded 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 conf.Editor == "" {
if os.Getenv("VISUAL") != "" { if conf.Editor, err = Editor(); err != nil {
conf.Editor = os.Getenv("VISUAL") return Config{}, err
} else if os.Getenv("EDITOR") != "" {
conf.Editor = os.Getenv("EDITOR")
} else {
return Config{}, fmt.Errorf("no editor set")
} }
} }
@ -110,13 +110,11 @@ func New(opts map[string]interface{}, confPath string, resolve bool) (Config, er
// if a chroma formatter was not provided, set a default // if a chroma formatter was not provided, set a default
if conf.Formatter == "" { if conf.Formatter == "" {
conf.Formatter = "terminal16m" conf.Formatter = "terminal"
} }
// if a pager was not provided, set a default // load the pager
if strings.TrimSpace(conf.Pager) == "" { conf.Pager = strings.TrimSpace(conf.Pager)
conf.Pager = ""
}
return conf, nil return conf, nil
} }

View File

@ -39,17 +39,17 @@ func TestConfigSuccessful(t *testing.T) {
// assert that the cheatpaths are correct // assert that the cheatpaths are correct
want := []cheatpath.Cheatpath{ want := []cheatpath.Cheatpath{
cheatpath.Cheatpath{ cheatpath.Cheatpath{
Path: filepath.Join(home, ".dotfiles/cheat/community"), Path: filepath.Join(home, ".dotfiles", "cheat", "community"),
ReadOnly: true, ReadOnly: true,
Tags: []string{"community"}, Tags: []string{"community"},
}, },
cheatpath.Cheatpath{ cheatpath.Cheatpath{
Path: filepath.Join(home, ".dotfiles/cheat/work"), Path: filepath.Join(home, ".dotfiles", "cheat", "work"),
ReadOnly: false, ReadOnly: false,
Tags: []string{"work"}, Tags: []string{"work"},
}, },
cheatpath.Cheatpath{ cheatpath.Cheatpath{
Path: filepath.Join(home, ".dotfiles/cheat/personal"), Path: filepath.Join(home, ".dotfiles", "cheat", "personal"),
ReadOnly: false, ReadOnly: false,
Tags: []string{"personal"}, Tags: []string{"personal"},
}, },
@ -85,8 +85,8 @@ func TestEmptyEditor(t *testing.T) {
// initialize a config // initialize a config
conf, err := New(map[string]interface{}{}, mock.Path("conf/empty.yml"), false) conf, err := New(map[string]interface{}{}, mock.Path("conf/empty.yml"), false)
if err == nil { if err != nil {
t.Errorf("failed to return an error on empty editor") t.Errorf("failed to initialize test: %v", err)
} }
// set editor, and assert that it is respected // set editor, and assert that it is respected

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")
}

View File

@ -2,7 +2,6 @@ package config
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
) )
@ -16,7 +15,7 @@ func Init(confpath string, configs string) error {
} }
// write the config file // write the config file
if err := ioutil.WriteFile(confpath, []byte(configs), 0644); err != nil { if err := os.WriteFile(confpath, []byte(configs), 0644); err != nil {
return fmt.Errorf("failed to create file: %v", err) return fmt.Errorf("failed to create file: %v", err)
} }

View File

@ -1,7 +1,6 @@
package config package config
import ( import (
"io/ioutil"
"os" "os"
"testing" "testing"
) )
@ -10,7 +9,7 @@ import (
func TestInit(t *testing.T) { func TestInit(t *testing.T) {
// initialize a temporary config file // initialize a temporary config file
confFile, err := ioutil.TempFile("", "cheat-test") confFile, err := os.CreateTemp("", "cheat-test")
if err != nil { if err != nil {
t.Errorf("failed to create temp file: %v", err) t.Errorf("failed to create temp file: %v", err)
} }
@ -25,7 +24,7 @@ func TestInit(t *testing.T) {
} }
// read back the config file contents // read back the config file contents
bytes, err := ioutil.ReadFile(confFile.Name()) bytes, err := os.ReadFile(confFile.Name())
if err != nil { if err != nil {
t.Errorf("failed to read config file: %v", err) t.Errorf("failed to read config file: %v", err)
} }

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

@ -1,7 +1,6 @@
package config package config
import ( import (
"io/ioutil"
"os" "os"
"testing" "testing"
) )
@ -24,7 +23,7 @@ func TestPathConfigNotExists(t *testing.T) {
func TestPathConfigExists(t *testing.T) { func TestPathConfigExists(t *testing.T) {
// initialize a temporary config file // initialize a temporary config file
confFile, err := ioutil.TempFile("", "cheat-test") confFile, err := os.CreateTemp("", "cheat-test")
if err != nil { if err != nil {
t.Errorf("failed to create temp file: %v", err) t.Errorf("failed to create temp file: %v", err)
} }

View File

@ -2,7 +2,7 @@ package config
import ( import (
"fmt" "fmt"
"path" "path/filepath"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
) )
@ -28,25 +28,25 @@ func Paths(
} }
switch sys { switch sys {
case "darwin", "linux", "freebsd": case "android", "darwin", "linux", "freebsd":
paths := []string{} paths := []string{}
// don't include the `XDG_CONFIG_HOME` path if that envvar is not set // don't include the `XDG_CONFIG_HOME` path if that envvar is not set
if xdgpath, ok := envvars["XDG_CONFIG_HOME"]; ok { if xdgpath, ok := envvars["XDG_CONFIG_HOME"]; ok {
paths = append(paths, path.Join(xdgpath, "/cheat/conf.yml")) paths = append(paths, filepath.Join(xdgpath, "cheat", "conf.yml"))
} }
paths = append(paths, []string{ paths = append(paths, []string{
path.Join(home, ".config/cheat/conf.yml"), filepath.Join(home, ".config", "cheat", "conf.yml"),
path.Join(home, ".cheat/conf.yml"), filepath.Join(home, ".cheat", "conf.yml"),
"/etc/cheat/conf.yml", "/etc/cheat/conf.yml",
}...) }...)
return paths, nil return paths, nil
case "windows": case "windows":
return []string{ return []string{
path.Join(envvars["APPDATA"], "/cheat/conf.yml"), filepath.Join(envvars["APPDATA"], "cheat", "conf.yml"),
path.Join(envvars["PROGRAMDATA"], "/cheat/conf.yml"), filepath.Join(envvars["PROGRAMDATA"], "cheat", "conf.yml"),
}, nil }, nil
default: default:
return []string{}, fmt.Errorf("unsupported os: %s", sys) return []string{}, fmt.Errorf("unsupported os: %s", sys)

View File

@ -21,6 +21,7 @@ func TestValidatePathsNix(t *testing.T) {
// specify the platforms to test // specify the platforms to test
oses := []string{ oses := []string{
"android",
"darwin", "darwin",
"freebsd", "freebsd",
"linux", "linux",

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

@ -0,0 +1,18 @@
package display
import (
"fmt"
"github.com/cheat/cheat/internal/config"
)
// Faint returns an faint string
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)
}
}

View File

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

View File

@ -8,8 +8,8 @@ import (
const cloneURL = "https://github.com/cheat/cheatsheets.git" const cloneURL = "https://github.com/cheat/cheatsheets.git"
// Clone clones the community cheatsheets // clone clones the community cheatsheets
func Clone(path string) error { func clone(path string) error {
// perform the clone in a shell // perform the clone in a shell
cmd := exec.Command("git", "clone", cloneURL, path) cmd := exec.Command("git", "clone", cloneURL, path)

View File

@ -14,7 +14,7 @@ func Prompt(prompt string, def bool) (bool, error) {
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
// display the prompt // display the prompt
fmt.Print(fmt.Sprintf("%s: ", prompt)) fmt.Printf("%s: ", prompt)
// read the answer // read the answer
ans, err := reader.ReadString('\n') ans, err := reader.ReadString('\n')
@ -23,7 +23,7 @@ func Prompt(prompt string, def bool) (bool, error) {
} }
// normalize the answer // normalize the answer
ans = strings.ToLower(strings.TrimRight(ans, "\n")) ans = strings.ToLower(strings.TrimSpace(ans))
// return the appropriate response // return the appropriate response
switch ans { switch ans {

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

@ -0,0 +1,65 @@
package installer
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/cheat/cheat/internal/config"
)
// 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 := 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

@ -13,7 +13,7 @@ func Path(filename string) string {
// determine the path of this file during runtime // determine the path of this file during runtime
_, thisfile, _, _ := runtime.Caller(0) _, thisfile, _, _ := runtime.Caller(0)
// compute the config path // compute the mock path
file, err := filepath.Abs( file, err := filepath.Abs(
path.Join( path.Join(
filepath.Dir(thisfile), filepath.Dir(thisfile),
@ -22,7 +22,7 @@ func Path(filename string) string {
), ),
) )
if err != nil { 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 return file

View File

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

View File

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

View File

@ -1,30 +1,31 @@
package frontmatter package sheet
import ( import (
"fmt" "fmt"
"runtime"
"strings" "strings"
"gopkg.in/yaml.v1" "gopkg.in/yaml.v1"
) )
// Frontmatter encapsulates cheatsheet frontmatter data
type Frontmatter struct {
Tags []string
Syntax string
}
// Parse parses cheatsheet frontmatter // Parse parses cheatsheet frontmatter
func Parse(markdown string) (string, Frontmatter, error) { 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 // specify the frontmatter delimiter
delim := "---" delim := fmt.Sprintf("---%s", linebreak)
// initialize a frontmatter struct // initialize a frontmatter struct
var fm Frontmatter var fm frontmatter
// if the markdown does not contain frontmatter, pass it through unmodified // if the markdown does not contain frontmatter, pass it through unmodified
if !strings.HasPrefix(markdown, delim) { if !strings.HasPrefix(markdown, delim) {
return strings.TrimSpace(markdown), fm, nil return fm, markdown, nil
} }
// otherwise, split the frontmatter and cheatsheet text // otherwise, split the frontmatter and cheatsheet text
@ -32,13 +33,13 @@ func Parse(markdown string) (string, Frontmatter, error) {
// return an error if the frontmatter parses into the wrong number of parts // return an error if the frontmatter parses into the wrong number of parts
if len(parts) != 3 { if len(parts) != 3 {
return markdown, fm, fmt.Errorf("failed to delimit frontmatter") return fm, markdown, fmt.Errorf("failed to delimit frontmatter")
} }
// return an error if the YAML cannot be unmarshalled // return an error if the YAML cannot be unmarshalled
if err := yaml.Unmarshal([]byte(parts[1]), &fm); err != nil { if err := yaml.Unmarshal([]byte(parts[1]), &fm); err != nil {
return markdown, fm, fmt.Errorf("failed to unmarshal frontmatter: %v", err) return fm, markdown, fmt.Errorf("failed to unmarshal frontmatter: %v", err)
} }
return strings.TrimSpace(parts[2]), fm, nil return fm, parts[2], nil
} }

View File

@ -1,4 +1,4 @@
package frontmatter package sheet
import ( import (
"testing" "testing"
@ -16,7 +16,7 @@ tags: [ test ]
To foo the bar: baz` To foo the bar: baz`
// parse the frontmatter // parse the frontmatter
text, fm, err := Parse(markdown) fm, text, err := parse(markdown)
// assert expectations // assert expectations
if err != nil { if err != nil {
@ -50,7 +50,7 @@ func TestHasNoFrontmatter(t *testing.T) {
markdown := "To foo the bar: baz" markdown := "To foo the bar: baz"
// parse the frontmatter // parse the frontmatter
text, fm, err := Parse(markdown) fm, text, err := parse(markdown)
// assert expectations // assert expectations
if err != nil { if err != nil {
@ -81,7 +81,7 @@ tags: [ test ]
To foo the bar: baz` To foo the bar: baz`
// parse the frontmatter // parse the frontmatter
text, _, err := Parse(markdown) _, text, err := parse(markdown)
// assert that an error was returned // assert that an error was returned
if err == nil { if err == nil {

View File

@ -2,38 +2,44 @@ package sheet
import ( import (
"fmt" "fmt"
"io/ioutil" "os"
"sort" "sort"
"github.com/cheat/cheat/internal/frontmatter"
) )
// Frontmatter encapsulates cheatsheet frontmatter data
type frontmatter struct {
Tags []string
Syntax string
}
// Sheet encapsulates sheet information // Sheet encapsulates sheet information
type Sheet struct { type Sheet struct {
Title string Title string
Path string CheatPath string
Text string Path string
Tags []string Text string
Syntax string Tags []string
ReadOnly bool Syntax string
ReadOnly bool
} }
// New initializes a new Sheet // New initializes a new Sheet
func New( func New(
title string, title string,
cheatpath string,
path string, path string,
tags []string, tags []string,
readOnly bool, readOnly bool,
) (Sheet, error) { ) (Sheet, error) {
// read the cheatsheet file // read the cheatsheet file
markdown, err := ioutil.ReadFile(path) markdown, err := os.ReadFile(path)
if err != nil { if err != nil {
return Sheet{}, fmt.Errorf("failed to read file: %s, %v", path, err) return Sheet{}, fmt.Errorf("failed to read file: %s, %v", path, err)
} }
// parse the cheatsheet frontmatter // parse the raw cheatsheet text
text, fm, err := frontmatter.Parse(string(markdown)) fm, text, err := parse(string(markdown))
if err != nil { if err != nil {
return Sheet{}, fmt.Errorf("failed to parse front-matter: %v", err) return Sheet{}, fmt.Errorf("failed to parse front-matter: %v", err)
} }
@ -46,11 +52,12 @@ func New(
// initialize and return a sheet // initialize and return a sheet
return Sheet{ return Sheet{
Title: title, Title: title,
Path: path, CheatPath: cheatpath,
Text: text + "\n", Path: path,
Tags: tags, Text: text,
Syntax: fm.Syntax, Tags: tags,
ReadOnly: readOnly, Syntax: fm.Syntax,
ReadOnly: readOnly,
}, nil }, nil
} }

View File

@ -13,6 +13,7 @@ func TestSheetSuccess(t *testing.T) {
// initialize a sheet // initialize a sheet
sheet, err := New( sheet, err := New(
"foo", "foo",
"community",
mock.Path("sheet/foo"), mock.Path("sheet/foo"),
[]string{"alpha", "bravo"}, []string{"alpha", "bravo"},
false, false,
@ -61,6 +62,7 @@ func TestSheetFailure(t *testing.T) {
// initialize a sheet // initialize a sheet
_, err := New( _, err := New(
"foo", "foo",
"community",
mock.Path("/does-not-exist"), mock.Path("/does-not-exist"),
[]string{"alpha", "bravo"}, []string{"alpha", "bravo"},
false, false,
@ -77,6 +79,7 @@ func TestSheetFrontMatterFailure(t *testing.T) {
// initialize a sheet // initialize a sheet
_, err := New( _, err := New(
"foo", "foo",
"community",
mock.Path("sheet/bad-fm"), mock.Path("sheet/bad-fm"),
[]string{"alpha", "bravo"}, []string{"alpha", "bravo"},
false, false,

72
internal/sheets/gitdir.go Normal file
View File

@ -0,0 +1,72 @@
package sheets
import (
"fmt"
"os"
"strings"
)
// isGitDir returns `true` if `path` is within a `.git` directory, or `false`
// otherwise
func isGitDir(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
Accounting for all of the above, we are now searching for the presence
of a `.git` literal string in the cheatsheet file path. If it is not
found, we continue to walk the directory, as before.
If it *is* found, we determine if `.git` refers to a file or directory,
and only stop walking the path in the latter case.
*/
// determine if the literal string `.git` appears within `path`
pos := strings.Index(path, ".git")
// 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+4])
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
}

View File

@ -2,6 +2,7 @@ package sheets
import ( import (
"fmt" "fmt"
"io/fs"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -48,18 +49,25 @@ func Load(cheatpaths []cp.Cheatpath) ([]map[string]sheet.Sheet, error) {
string(os.PathSeparator), string(os.PathSeparator),
) )
// ignore hidden files and directories. Otherwise, we'll likely load // Don't walk the `.git` directory. Doing so creates
// .git/* and .DS_Store. // hundreds/thousands of needless syscalls and could
// // potentially harm performance on machines with slow disks.
// NB: this is still somewhat brittle in that it will miss files skip, err := isGitDir(path)
// contained within hidden directories in the middle of a path, though if err != nil {
// that should not realistically occur. return fmt.Errorf("failed to identify .git directory: %v", err)
if strings.HasPrefix(title, ".") || strings.HasPrefix(info.Name(), ".") { }
return nil if skip {
return fs.SkipDir
} }
// parse the cheatsheet file into a `sheet` struct // 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 { if err != nil {
return fmt.Errorf( return fmt.Errorf(
"failed to load sheet: %s, path: %s, err: %v", "failed to load sheet: %s, path: %s, err: %v",

View File

@ -1,3 +1,62 @@
package sheets 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")
}
}

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.

View File

@ -40,8 +40,7 @@ _cheat() {
'(-t --tag)'{-t,--tag}'[Return only sheets matching <tag>]: :->taglist' \ '(-t --tag)'{-t,--tag}'[Return only sheets matching <tag>]: :->taglist' \
'(-T --tags)'{-T,--tags}'[List all tags in use]: :->none' \ '(-T --tags)'{-T,--tags}'[List all tags in use]: :->none' \
'(-v --version)'{-v,--version}'[Print the version number]: :->none' \ '(-v --version)'{-v,--version}'[Print the version number]: :->none' \
'(--rm)--rm[Remove (delete) <sheet>]: :->personal' \ '(--rm)--rm[Remove (delete) <sheet>]: :->personal'
'(-)*: :->full'
case $state in case $state in
(none) (none)
@ -63,4 +62,4 @@ _cheat() {
esac esac
} }
_cheat compdef _cheat cheat

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

View File

@ -25,6 +25,17 @@ linters:
- testpackage - testpackage
- godot - godot
- nestif - nestif
- paralleltest
- nlreturn
- cyclop
- exhaustivestruct
- gci
- gofumpt
- errorlint
- exhaustive
- ifshort
- wrapcheck
- stylecheck
linters-settings: linters-settings:
govet: govet:
@ -36,6 +47,11 @@ linters-settings:
goconst: goconst:
min-len: 8 min-len: 8
min-occurrences: 3 min-occurrences: 3
forbidigo:
forbid:
- (Must)?NewLexer
exclude_godoc_examples: false
issues: issues:
max-per-linter: 0 max-per-linter: 0

View File

@ -6,17 +6,21 @@ release:
brews: brews:
- -
install: bin.install "chroma" install: bin.install "chroma"
env:
- CGO_ENABLED=0
builds: builds:
- goos: - goos:
- linux - linux
- darwin - darwin
- windows - windows
goarch: goarch:
- arm64
- amd64 - amd64
- "386" - "386"
goarm: goarm:
- "6" - "6"
main: ./cmd/chroma/main.go dir: ./cmd/chroma
main: .
ldflags: -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} ldflags: -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}
binary: chroma binary: chroma
archives: archives:

View File

@ -1,12 +0,0 @@
sudo: false
language: go
go:
- "1.13.x"
script:
- go test -v ./...
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s v1.26.0
- ./bin/golangci-lint run
- git clean -fdx .
after_success:
curl -sL https://git.io/goreleaser | bash && goreleaser

View File

@ -1,5 +1,7 @@
.PHONY: chromad upload all .PHONY: chromad upload all
VERSION ?= $(shell git describe --tags --dirty --always)
all: README.md tokentype_string.go all: README.md tokentype_string.go
README.md: lexers/*/*.go README.md: lexers/*/*.go
@ -9,10 +11,8 @@ tokentype_string.go: types.go
go generate go generate
chromad: chromad:
(cd ./cmd/chromad && go get github.com/GeertJohan/go.rice/rice@master && go install github.com/GeertJohan/go.rice/rice)
rm -f chromad rm -f chromad
(export CGOENABLED=0 GOOS=linux ; cd ./cmd/chromad && go build -o ../../chromad .) (export CGOENABLED=0 GOOS=linux GOARCH=amd64; cd ./cmd/chromad && go build -ldflags="-X 'main.version=$(VERSION)'" -o ../../chromad .)
rice append -i ./cmd/chromad --exec=./chromad
upload: chromad upload: chromad
scp chromad root@swapoff.org: && \ scp chromad root@swapoff.org: && \

View File

@ -1,4 +1,5 @@
# Chroma — A general purpose syntax highlighter in pure Go [![Golang Documentation](https://godoc.org/github.com/alecthomas/chroma?status.svg)](https://godoc.org/github.com/alecthomas/chroma) [![Build Status](https://travis-ci.org/alecthomas/chroma.svg)](https://travis-ci.org/alecthomas/chroma) [![Gitter chat](https://badges.gitter.im/alecthomas.svg)](https://gitter.im/alecthomas/Lobby) # Chroma — A general purpose syntax highlighter in pure Go
[![Golang Documentation](https://godoc.org/github.com/alecthomas/chroma?status.svg)](https://godoc.org/github.com/alecthomas/chroma) [![CI](https://github.com/alecthomas/chroma/actions/workflows/ci.yml/badge.svg)](https://github.com/alecthomas/chroma/actions/workflows/ci.yml) [![Slack chat](https://img.shields.io/static/v1?logo=slack&style=flat&label=slack&color=green&message=gophers)](https://invite.slack.golangbridge.org/)
> **NOTE:** As Chroma has just been released, its API is still in flux. That said, the high-level interface should not change significantly. > **NOTE:** As Chroma has just been released, its API is still in flux. That said, the high-level interface should not change significantly.
@ -36,29 +37,30 @@ translators for Pygments lexers and styles.
Prefix | Language Prefix | Language
:----: | -------- :----: | --------
A | ABAP, ABNF, ActionScript, ActionScript 3, Ada, Angular2, ANTLR, ApacheConf, APL, AppleScript, Arduino, Awk A | ABAP, ABNF, ActionScript, ActionScript 3, Ada, Angular2, ANTLR, ApacheConf, APL, AppleScript, Arduino, Awk
B | Ballerina, Base Makefile, Bash, Batchfile, BlitzBasic, BNF, Brainfuck B | Ballerina, Base Makefile, Bash, Batchfile, BibTeX, Bicep, BlitzBasic, BNF, Brainfuck
C | C, C#, C++, Cap'n Proto, Cassandra CQL, Ceylon, CFEngine3, cfstatement, ChaiScript, Cheetah, Clojure, CMake, COBOL, CoffeeScript, Common Lisp, Coq, Crystal, CSS, Cython C | C, C#, C++, Caddyfile, Caddyfile Directives, Cap'n Proto, Cassandra CQL, Ceylon, CFEngine3, cfstatement, ChaiScript, Cheetah, Clojure, CMake, COBOL, CoffeeScript, Common Lisp, Coq, Crystal, CSS, Cython
D | D, Dart, Diff, Django/Jinja, Docker, DTD D | D, Dart, Diff, Django/Jinja, Docker, DTD, Dylan
E | EBNF, Elixir, Elm, EmacsLisp, Erlang E | EBNF, Elixir, Elm, EmacsLisp, Erlang
F | Factor, Fish, Forth, Fortran, FSharp F | Factor, Fish, Forth, Fortran, FSharp
G | GAS, GDScript, Genshi, Genshi HTML, Genshi Text, GLSL, Gnuplot, Go, Go HTML Template, Go Text Template, GraphQL, Groovy G | GAS, GDScript, Genshi, Genshi HTML, Genshi Text, Gherkin, GLSL, Gnuplot, Go, Go HTML Template, Go Text Template, GraphQL, Groff, Groovy
H | Handlebars, Haskell, Haxe, HCL, Hexdump, HTML, HTTP, Hy H | Handlebars, Haskell, Haxe, HCL, Hexdump, HLB, HTML, HTTP, Hy
I | Idris, INI, Io I | Idris, Igor, INI, Io
J | J, Java, JavaScript, JSON, Julia, Jungle J | J, Java, JavaScript, JSON, Julia, Jungle
K | Kotlin K | Kotlin
L | Lighttpd configuration file, LLVM, Lua L | Lighttpd configuration file, LLVM, Lua
M | Mako, markdown, Mason, Mathematica, Matlab, MiniZinc, MLIR, Modula-2, MonkeyC, MorrowindScript, Myghty, MySQL M | Mako, markdown, Mason, Mathematica, Matlab, MiniZinc, MLIR, Modula-2, MonkeyC, MorrowindScript, Myghty, MySQL
N | NASM, Newspeak, Nginx configuration file, Nim, Nix N | NASM, Newspeak, Nginx configuration file, Nim, Nix
O | Objective-C, OCaml, Octave, OpenSCAD, Org Mode O | Objective-C, OCaml, Octave, OnesEnterprise, OpenEdge ABL, OpenSCAD, Org Mode
P | PacmanConf, Perl, PHP, Pig, PkgConfig, PL/pgSQL, plaintext, PostgreSQL SQL dialect, PostScript, POVRay, PowerShell, Prolog, Protocol Buffer, Puppet, Python, Python 3 P | PacmanConf, Perl, PHP, PHTML, Pig, PkgConfig, PL/pgSQL, plaintext, Pony, PostgreSQL SQL dialect, PostScript, POVRay, PowerShell, Prolog, PromQL, Protocol Buffer, Puppet, Python 2, Python
Q | QBasic Q | QBasic
R | R, Racket, Ragel, react, reg, reStructuredText, Rexx, Ruby, Rust R | R, Racket, Ragel, Raku, react, ReasonML, reg, reStructuredText, Rexx, Ruby, Rust
S | Sass, Scala, Scheme, Scilab, SCSS, Smalltalk, Smarty, SML, Snobol, Solidity, SPARQL, SQL, SquidConf, Swift, SYSTEMD, systemverilog S | SAS, Sass, Scala, Scheme, Scilab, SCSS, Smalltalk, Smarty, Snobol, Solidity, SPARQL, SQL, SquidConf, Standard ML, Stylus, Svelte, Swift, SYSTEMD, systemverilog
T | TableGen, TASM, Tcl, Tcsh, Termcap, Terminfo, Terraform, TeX, Thrift, TOML, TradingView, Transact-SQL, Turing, Turtle, Twig, TypeScript, TypoScript, TypoScriptCssData, TypoScriptHtmlData T | TableGen, TASM, Tcl, Tcsh, Termcap, Terminfo, Terraform, TeX, Thrift, TOML, TradingView, Transact-SQL, Turing, Turtle, Twig, TypeScript, TypoScript, TypoScriptCssData, TypoScriptHtmlData
V | VB.net, verilog, VHDL, VimL, vue V | VB.net, verilog, VHDL, VimL, vue
W | WDTE W | WDTE
X | XML, Xorg X | XML, Xorg
Y | YAML Y | YAML, YANG
Z | Zig
_I will attempt to keep this section up to date, but an authoritative list can be _I will attempt to keep this section up to date, but an authoritative list can be
@ -183,7 +185,7 @@ following constructor options:
- `ClassPrefix(prefix)` - prefix each generated CSS class. - `ClassPrefix(prefix)` - prefix each generated CSS class.
- `TabWidth(width)` - Set the rendered tab width, in characters. - `TabWidth(width)` - Set the rendered tab width, in characters.
- `WithLineNumbers()` - Render line numbers (style with `LineNumbers`). - `WithLineNumbers()` - Render line numbers (style with `LineNumbers`).
- `LinkableLineNumbers()` - Make the line numbers linkable. - `LinkableLineNumbers()` - Make the line numbers linkable and be a link to themselves.
- `HighlightLines(ranges)` - Highlight lines in these ranges (style with `LineHighlight`). - `HighlightLines(ranges)` - Highlight lines in these ranges (style with `LineHighlight`).
- `LineNumbersInTable()` - Use a table for formatting line numbers and code, rather than spans. - `LineNumbersInTable()` - Use a table for formatting line numbers and code, rather than spans.
@ -209,13 +211,13 @@ using the included Python 3 script `pygments2chroma.py`. I use something like
the following: the following:
```sh ```sh
python3 ~/Projects/chroma/_tools/pygments2chroma.py \ python3 _tools/pygments2chroma.py \
pygments.lexers.jvm.KotlinLexer \ pygments.lexers.jvm.KotlinLexer \
> ~/Projects/chroma/lexers/kotlin.go \ > lexers/k/kotlin.go \
&& gofmt -s -w ~/Projects/chroma/lexers/*.go && gofmt -s -w lexers/k/kotlin.go
``` ```
See notes in [pygments-lexers.go](https://github.com/alecthomas/chroma/blob/master/pygments-lexers.txt) See notes in [pygments-lexers.txt](https://github.com/alecthomas/chroma/blob/master/pygments-lexers.txt)
for a list of lexers, and notes on some of the issues importing them. for a list of lexers, and notes on some of the issues importing them.
<a id="markdown-formatters" name="formatters"></a> <a id="markdown-formatters" name="formatters"></a>
@ -248,18 +250,34 @@ For a quick overview of the available styles and how they look, check out the [C
<a id="markdown-command-line-interface" name="command-line-interface"></a> <a id="markdown-command-line-interface" name="command-line-interface"></a>
## Command-line interface ## Command-line interface
A command-line interface to Chroma is included. It can be installed with: A command-line interface to Chroma is included.
```sh Binaries are available to install from [the releases page](https://github.com/alecthomas/chroma/releases).
go get -u github.com/alecthomas/chroma/cmd/chroma
The CLI can be used as a preprocessor to colorise output of `less(1)`,
see documentation for the `LESSOPEN` environment variable.
The `--fail` flag can be used to suppress output and return with exit status
1 to facilitate falling back to some other preprocessor in case chroma
does not resolve a specific lexer to use for the given file. For example:
```shell
export LESSOPEN='| p() { chroma --fail "$1" || cat "$1"; }; p "%s"'
``` ```
Replace `cat` with your favourite fallback preprocessor.
When invoked as `.lessfilter`, the `--fail` flag is automatically turned
on under the hood for easy integration with [lesspipe shipping with
Debian and derivatives](https://manpages.debian.org/lesspipe#USER_DEFINED_FILTERS);
for that setup the `chroma` executable can be just symlinked to `~/.lessfilter`.
<a id="markdown-whats-missing-compared-to-pygments" name="whats-missing-compared-to-pygments"></a> <a id="markdown-whats-missing-compared-to-pygments" name="whats-missing-compared-to-pygments"></a>
## What's missing compared to Pygments? ## What's missing compared to Pygments?
- Quite a few lexers, for various reasons (pull-requests welcome): - Quite a few lexers, for various reasons (pull-requests welcome):
- Pygments lexers for complex languages often include custom code to - Pygments lexers for complex languages often include custom code to
handle certain aspects, such as Perl6's ability to nest code inside handle certain aspects, such as Raku's ability to nest code inside
regular expressions. These require time and effort to convert. regular expressions. These require time and effort to convert.
- I mostly only converted languages I had heard of, to reduce the porting cost. - I mostly only converted languages I had heard of, to reduce the porting cost.
- Some more esoteric features of Pygments are omitted for simplicity. - Some more esoteric features of Pygments are omitted for simplicity.

View File

@ -46,6 +46,13 @@ func WithPreWrapper(wrapper PreWrapper) Option {
} }
} }
// WrapLongLines wraps long lines.
func WrapLongLines(b bool) Option {
return func(f *Formatter) {
f.wrapLongLines = b
}
}
// WithLineNumbers formats output with line numbers. // WithLineNumbers formats output with line numbers.
func WithLineNumbers(b bool) Option { func WithLineNumbers(b bool) Option {
return func(f *Formatter) { return func(f *Formatter) {
@ -131,10 +138,18 @@ var (
} }
defaultPreWrapper = preWrapper{ defaultPreWrapper = preWrapper{
start: func(code bool, styleAttr string) string { start: func(code bool, styleAttr string) string {
return fmt.Sprintf("<pre%s>", styleAttr) if code {
return fmt.Sprintf(`<pre tabindex="0"%s><code>`, styleAttr)
}
return fmt.Sprintf(`<pre tabindex="0"%s>`, styleAttr)
}, },
end: func(code bool) string { end: func(code bool) string {
return "</pre>" if code {
return `</code></pre>`
}
return `</pre>`
}, },
} }
) )
@ -147,6 +162,7 @@ type Formatter struct {
allClasses bool allClasses bool
preWrapper PreWrapper preWrapper PreWrapper
tabWidth int tabWidth int
wrapLongLines bool
lineNumbers bool lineNumbers bool
lineNumbersInTable bool lineNumbersInTable bool
linkableLineNumbers bool linkableLineNumbers bool
@ -197,10 +213,10 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.
if wrapInTable { if wrapInTable {
// List line numbers in its own <td> // List line numbers in its own <td>
fmt.Fprintf(w, "<div%s>\n", f.styleAttr(css, chroma.Background)) fmt.Fprintf(w, "<div%s>\n", f.styleAttr(css, chroma.PreWrapper))
fmt.Fprintf(w, "<table%s><tr>", f.styleAttr(css, chroma.LineTable)) fmt.Fprintf(w, "<table%s><tr>", f.styleAttr(css, chroma.LineTable))
fmt.Fprintf(w, "<td%s>\n", f.styleAttr(css, chroma.LineTableTD)) fmt.Fprintf(w, "<td%s>\n", f.styleAttr(css, chroma.LineTableTD))
fmt.Fprintf(w, f.preWrapper.Start(false, f.styleAttr(css, chroma.Background))) fmt.Fprintf(w, f.preWrapper.Start(false, f.styleAttr(css, chroma.PreWrapper)))
for index := range lines { for index := range lines {
line := f.baseLineNumber + index line := f.baseLineNumber + index
highlight, next := f.shouldHighlight(highlightIndex, line) highlight, next := f.shouldHighlight(highlightIndex, line)
@ -211,7 +227,7 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.
fmt.Fprintf(w, "<span%s>", f.styleAttr(css, chroma.LineHighlight)) fmt.Fprintf(w, "<span%s>", f.styleAttr(css, chroma.LineHighlight))
} }
fmt.Fprintf(w, "<span%s%s>%*d\n</span>", f.styleAttr(css, chroma.LineNumbersTable), f.lineIDAttribute(line), lineDigits, line) fmt.Fprintf(w, "<span%s%s>%s\n</span>", f.styleAttr(css, chroma.LineNumbersTable), f.lineIDAttribute(line), f.lineTitleWithLinkIfNeeded(lineDigits, line))
if highlight { if highlight {
fmt.Fprintf(w, "</span>") fmt.Fprintf(w, "</span>")
@ -222,7 +238,7 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.
fmt.Fprintf(w, "<td%s>\n", f.styleAttr(css, chroma.LineTableTD, "width:100%")) fmt.Fprintf(w, "<td%s>\n", f.styleAttr(css, chroma.LineTableTD, "width:100%"))
} }
fmt.Fprintf(w, f.preWrapper.Start(true, f.styleAttr(css, chroma.Background))) fmt.Fprintf(w, f.preWrapper.Start(true, f.styleAttr(css, chroma.PreWrapper)))
highlightIndex = 0 highlightIndex = 0
for index, tokens := range lines { for index, tokens := range lines {
@ -232,14 +248,28 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.
if next { if next {
highlightIndex++ highlightIndex++
} }
// Start of Line
fmt.Fprint(w, `<span`)
if highlight { if highlight {
fmt.Fprintf(w, "<span%s>", f.styleAttr(css, chroma.LineHighlight)) // Line + LineHighlight
if f.Classes {
fmt.Fprintf(w, ` class="%s %s"`, f.class(chroma.Line), f.class(chroma.LineHighlight))
} else {
fmt.Fprintf(w, ` style="%s %s"`, css[chroma.Line], css[chroma.LineHighlight])
}
fmt.Fprint(w, `>`)
} else {
fmt.Fprintf(w, "%s>", f.styleAttr(css, chroma.Line))
} }
// Line number
if f.lineNumbers && !wrapInTable { if f.lineNumbers && !wrapInTable {
fmt.Fprintf(w, "<span%s%s>%*d</span>", f.styleAttr(css, chroma.LineNumbers), f.lineIDAttribute(line), lineDigits, line) fmt.Fprintf(w, "<span%s%s>%s</span>", f.styleAttr(css, chroma.LineNumbers), f.lineIDAttribute(line), f.lineTitleWithLinkIfNeeded(lineDigits, line))
} }
fmt.Fprintf(w, `<span%s>`, f.styleAttr(css, chroma.CodeLine))
for _, token := range tokens { for _, token := range tokens {
html := html.EscapeString(token.String()) html := html.EscapeString(token.String())
attr := f.styleAttr(css, token.Type) attr := f.styleAttr(css, token.Type)
@ -248,9 +278,10 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.
} }
fmt.Fprint(w, html) fmt.Fprint(w, html)
} }
if highlight {
fmt.Fprintf(w, "</span>") fmt.Fprint(w, `</span>`) // End of CodeLine
}
fmt.Fprint(w, `</span>`) // End of Line
} }
fmt.Fprintf(w, f.preWrapper.End(true)) fmt.Fprintf(w, f.preWrapper.End(true))
@ -272,7 +303,19 @@ func (f *Formatter) lineIDAttribute(line int) string {
if !f.linkableLineNumbers { if !f.linkableLineNumbers {
return "" return ""
} }
return fmt.Sprintf(" id=\"%s%d\"", f.lineNumbersIDPrefix, line) return fmt.Sprintf(" id=\"%s\"", f.lineID(line))
}
func (f *Formatter) lineTitleWithLinkIfNeeded(lineDigits, line int) string {
title := fmt.Sprintf("%*d", lineDigits, line)
if !f.linkableLineNumbers {
return title
}
return fmt.Sprintf("<a style=\"outline: none; text-decoration:none; color:inherit\" href=\"#%s\">%s</a>", f.lineID(line), title)
}
func (f *Formatter) lineID(line int) string {
return fmt.Sprintf("%s%d", f.lineNumbersIDPrefix, line)
} }
func (f *Formatter) shouldHighlight(highlightIndex, line int) (bool, bool) { func (f *Formatter) shouldHighlight(highlightIndex, line int) (bool, bool) {
@ -339,7 +382,11 @@ func (f *Formatter) tabWidthStyle() string {
func (f *Formatter) WriteCSS(w io.Writer, style *chroma.Style) error { func (f *Formatter) WriteCSS(w io.Writer, style *chroma.Style) error {
css := f.styleToCSS(style) css := f.styleToCSS(style)
// Special-case background as it is mapped to the outer ".chroma" class. // Special-case background as it is mapped to the outer ".chroma" class.
if _, err := fmt.Fprintf(w, "/* %s */ .%schroma { %s }\n", chroma.Background, f.prefix, css[chroma.Background]); err != nil { if _, err := fmt.Fprintf(w, "/* %s */ .%sbg { %s }\n", chroma.Background, f.prefix, css[chroma.Background]); err != nil {
return err
}
// Special-case PreWrapper as it is the ".chroma" class.
if _, err := fmt.Fprintf(w, "/* %s */ .%schroma { %s }\n", chroma.PreWrapper, f.prefix, css[chroma.PreWrapper]); err != nil {
return err return err
} }
// Special-case code column of table to expand width. // Special-case code column of table to expand width.
@ -363,7 +410,8 @@ func (f *Formatter) WriteCSS(w io.Writer, style *chroma.Style) error {
sort.Ints(tts) sort.Ints(tts)
for _, ti := range tts { for _, ti := range tts {
tt := chroma.TokenType(ti) tt := chroma.TokenType(ti)
if tt == chroma.Background { switch tt {
case chroma.Background, chroma.PreWrapper:
continue continue
} }
class := f.class(tt) class := f.class(tt)
@ -393,12 +441,21 @@ func (f *Formatter) styleToCSS(style *chroma.Style) map[chroma.TokenType]string
classes[t] = StyleEntryToCSS(entry) classes[t] = StyleEntryToCSS(entry)
} }
classes[chroma.Background] += f.tabWidthStyle() classes[chroma.Background] += f.tabWidthStyle()
lineNumbersStyle := "margin-right: 0.4em; padding: 0 0.4em 0 0.4em;" classes[chroma.PreWrapper] += classes[chroma.Background] + `;`
// Make PreWrapper a grid to show highlight style with full width.
if len(f.highlightRanges) > 0 {
classes[chroma.PreWrapper] += `display: grid;`
}
// Make PreWrapper wrap long lines.
if f.wrapLongLines {
classes[chroma.PreWrapper] += `white-space: pre-wrap; word-break: break-word;`
}
lineNumbersStyle := `white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;`
// All rules begin with default rules followed by user provided rules // All rules begin with default rules followed by user provided rules
classes[chroma.Line] = `display: flex;` + classes[chroma.Line]
classes[chroma.LineNumbers] = lineNumbersStyle + classes[chroma.LineNumbers] classes[chroma.LineNumbers] = lineNumbersStyle + classes[chroma.LineNumbers]
classes[chroma.LineNumbersTable] = lineNumbersStyle + classes[chroma.LineNumbersTable] classes[chroma.LineNumbersTable] = lineNumbersStyle + classes[chroma.LineNumbersTable]
classes[chroma.LineHighlight] = "display: block; width: 100%;" + classes[chroma.LineHighlight] classes[chroma.LineTable] = "border-spacing: 0; padding: 0; margin: 0; border: 0;" + classes[chroma.LineTable]
classes[chroma.LineTable] = "border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block;" + classes[chroma.LineTable]
classes[chroma.LineTableTD] = "vertical-align: top; padding: 0; margin: 0; border: 0;" + classes[chroma.LineTableTD] classes[chroma.LineTableTD] = "vertical-align: top; padding: 0; margin: 0; border: 0;" + classes[chroma.LineTableTD]
return classes return classes
} }

View File

@ -120,7 +120,7 @@ func maxLineWidth(lines [][]chroma.Token) int {
for _, tokens := range lines { for _, tokens := range lines {
length := 0 length := 0
for _, token := range tokens { for _, token := range tokens {
length += len(strings.Replace(token.String(), ` `, " ", -1)) length += len(strings.ReplaceAll(token.String(), ` `, " "))
} }
if length > maxWidth { if length > maxWidth {
maxWidth = length maxWidth = length
@ -136,7 +136,7 @@ func (f *Formatter) writeTokenBackgrounds(w io.Writer, lines [][]chroma.Token, s
for index, tokens := range lines { for index, tokens := range lines {
lineLength := 0 lineLength := 0
for _, token := range tokens { for _, token := range tokens {
length := len(strings.Replace(token.String(), ` `, " ", -1)) length := len(strings.ReplaceAll(token.String(), ` `, " "))
tokenBackground := style.Get(token.Type).Background tokenBackground := style.Get(token.Type).Background
if tokenBackground.IsSet() && tokenBackground != style.Get(chroma.Background).Background { if tokenBackground.IsSet() && tokenBackground != style.Get(chroma.Background).Background {
fmt.Fprintf(w, "<rect id=\"%s\" x=\"%dch\" y=\"%fem\" width=\"%dch\" height=\"1.2em\" fill=\"%s\" />\n", escapeString(token.String()), lineLength, 1.2*float64(index)+0.25, length, style.Get(token.Type).Background.String()) fmt.Fprintf(w, "<rect id=\"%s\" x=\"%dch\" y=\"%fem\" width=\"%dch\" height=\"1.2em\" fill=\"%s\" />\n", escapeString(token.String()), lineLength, 1.2*float64(index)+0.25, length, style.Get(token.Type).Background.String())

View File

@ -17,6 +17,20 @@ var c = chroma.MustParseColour
var ttyTables = map[int]*ttyTable{ var ttyTables = map[int]*ttyTable{
8: { 8: {
foreground: map[chroma.Colour]string{
c("#000000"): "\033[30m", c("#7f0000"): "\033[31m", c("#007f00"): "\033[32m", c("#7f7fe0"): "\033[33m",
c("#00007f"): "\033[34m", c("#7f007f"): "\033[35m", c("#007f7f"): "\033[36m", c("#e5e5e5"): "\033[37m",
c("#555555"): "\033[1m\033[30m", c("#ff0000"): "\033[1m\033[31m", c("#00ff00"): "\033[1m\033[32m", c("#ffff00"): "\033[1m\033[33m",
c("#0000ff"): "\033[1m\033[34m", c("#ff00ff"): "\033[1m\033[35m", c("#00ffff"): "\033[1m\033[36m", c("#ffffff"): "\033[1m\033[37m",
},
background: map[chroma.Colour]string{
c("#000000"): "\033[40m", c("#7f0000"): "\033[41m", c("#007f00"): "\033[42m", c("#7f7fe0"): "\033[43m",
c("#00007f"): "\033[44m", c("#7f007f"): "\033[45m", c("#007f7f"): "\033[46m", c("#e5e5e5"): "\033[47m",
c("#555555"): "\033[1m\033[40m", c("#ff0000"): "\033[1m\033[41m", c("#00ff00"): "\033[1m\033[42m", c("#ffff00"): "\033[1m\033[43m",
c("#0000ff"): "\033[1m\033[44m", c("#ff00ff"): "\033[1m\033[45m", c("#00ffff"): "\033[1m\033[46m", c("#ffffff"): "\033[1m\033[47m",
},
},
16: {
foreground: map[chroma.Colour]string{ foreground: map[chroma.Colour]string{
c("#000000"): "\033[30m", c("#7f0000"): "\033[31m", c("#007f00"): "\033[32m", c("#7f7fe0"): "\033[33m", c("#000000"): "\033[30m", c("#7f0000"): "\033[31m", c("#007f00"): "\033[32m", c("#7f7fe0"): "\033[33m",
c("#00007f"): "\033[34m", c("#7f007f"): "\033[35m", c("#007f7f"): "\033[36m", c("#e5e5e5"): "\033[37m", c("#00007f"): "\033[34m", c("#7f007f"): "\033[35m", c("#007f7f"): "\033[36m", c("#e5e5e5"): "\033[37m",
@ -227,15 +241,11 @@ type indexedTTYFormatter struct {
func (c *indexedTTYFormatter) Format(w io.Writer, style *chroma.Style, it chroma.Iterator) (err error) { func (c *indexedTTYFormatter) Format(w io.Writer, style *chroma.Style, it chroma.Iterator) (err error) {
theme := styleToEscapeSequence(c.table, style) theme := styleToEscapeSequence(c.table, style)
for token := it(); token != chroma.EOF; token = it() { for token := it(); token != chroma.EOF; token = it() {
// TODO: Cache token lookups?
clr, ok := theme[token.Type] clr, ok := theme[token.Type]
if !ok { if !ok {
clr, ok = theme[token.Type.SubCategory()] clr, ok = theme[token.Type.SubCategory()]
if !ok { if !ok {
clr = theme[token.Type.Category()] clr = theme[token.Type.Category()]
// if !ok {
// clr = theme[chroma.InheritStyle]
// }
} }
} }
if clr != "" { if clr != "" {
@ -249,10 +259,22 @@ func (c *indexedTTYFormatter) Format(w io.Writer, style *chroma.Style, it chroma
return nil return nil
} }
// TTY is an 8-colour terminal formatter.
//
// The Lab colour space is used to map RGB values to the most appropriate index colour.
var TTY = Register("terminal", &indexedTTYFormatter{ttyTables[8]})
// TTY8 is an 8-colour terminal formatter. // TTY8 is an 8-colour terminal formatter.
// //
// The Lab colour space is used to map RGB values to the most appropriate index colour. // The Lab colour space is used to map RGB values to the most appropriate index colour.
var TTY8 = Register("terminal", &indexedTTYFormatter{ttyTables[8]}) var TTY8 = Register("terminal8", &indexedTTYFormatter{ttyTables[8]})
// TTY16 is a 16-colour terminal formatter.
//
// It uses \033[3xm for normal colours and \033[90Xm for bright colours.
//
// The Lab colour space is used to map RGB values to the most appropriate index colour.
var TTY16 = Register("terminal16", &indexedTTYFormatter{ttyTables[16]})
// TTY256 is a 256-colour terminal formatter. // TTY256 is a 256-colour terminal formatter.
// //

View File

@ -1,18 +0,0 @@
module github.com/alecthomas/chroma
go 1.13
require (
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 // indirect
github.com/alecthomas/kong v0.2.4
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 // indirect
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964
github.com/dlclark/regexp2 v1.2.0
github.com/mattn/go-colorable v0.1.6
github.com/mattn/go-isatty v0.0.12
github.com/pkg/errors v0.9.1 // indirect
github.com/sergi/go-diff v1.0.0 // indirect
github.com/stretchr/testify v1.3.0 // indirect
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 // indirect
)

View File

@ -1,36 +0,0 @@
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/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.2.4 h1:Y0ZBCHAvHhTHw7FFJ2FzCAAG4pkbTgA45nc7BpMhDNk=
github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
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/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
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.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
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=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -4,7 +4,7 @@ import "strings"
// An Iterator across tokens. // An Iterator across tokens.
// //
// nil will be returned at the end of the Token stream. // EOF will be returned at the end of the Token stream.
// //
// If an error occurs within an Iterator, it may propagate this in a panic. Formatters should recover. // If an error occurs within an Iterator, it may propagate this in a panic. Formatters should recover.
type Iterator func() Token type Iterator func() Token

View File

@ -2,6 +2,7 @@ package chroma
import ( import (
"fmt" "fmt"
"strings"
) )
var ( var (
@ -98,9 +99,11 @@ type Lexer interface {
// Lexers is a slice of lexers sortable by name. // Lexers is a slice of lexers sortable by name.
type Lexers []Lexer type Lexers []Lexer
func (l Lexers) Len() int { return len(l) } func (l Lexers) Len() int { return len(l) }
func (l Lexers) Swap(i, j int) { l[i], l[j] = l[j], l[i] } func (l Lexers) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
func (l Lexers) Less(i, j int) bool { return l[i].Config().Name < l[j].Config().Name } func (l Lexers) Less(i, j int) bool {
return strings.ToLower(l[i].Config().Name) < strings.ToLower(l[j].Config().Name)
}
// PrioritisedLexers is a slice of lexers sortable by priority. // PrioritisedLexers is a slice of lexers sortable by priority.
type PrioritisedLexers []Lexer type PrioritisedLexers []Lexer

View File

@ -3,6 +3,9 @@
The tests in this directory feed a known input `testdata/<name>.actual` into the parser for `<name>` and check The tests in this directory feed a known input `testdata/<name>.actual` into the parser for `<name>` and check
that its output matches `<name>.exported`. that its output matches `<name>.exported`.
It is also possible to perform several tests on a same parser `<name>`, by placing know inputs `*.actual` into a
directory `testdata/<name>/`.
## Running the tests ## Running the tests
Run the tests as normal: Run the tests as normal:

View File

@ -6,7 +6,7 @@ import (
) )
// ABAP lexer. // ABAP lexer.
var Abap = internal.Register(MustNewLexer( var Abap = internal.Register(MustNewLazyLexer(
&Config{ &Config{
Name: "ABAP", Name: "ABAP",
Aliases: []string{"abap"}, Aliases: []string{"abap"},
@ -14,7 +14,11 @@ var Abap = internal.Register(MustNewLexer(
MimeTypes: []string{"text/x-abap"}, MimeTypes: []string{"text/x-abap"},
CaseInsensitive: true, CaseInsensitive: true,
}, },
Rules{ abapRules,
))
func abapRules() Rules {
return Rules{
"common": { "common": {
{`\s+`, Text, nil}, {`\s+`, Text, nil},
{`^\*.*$`, CommentSingle, nil}, {`^\*.*$`, CommentSingle, nil},
@ -52,5 +56,5 @@ var Abap = internal.Register(MustNewLexer(
{`[/;:()\[\],.]`, Punctuation, nil}, {`[/;:()\[\],.]`, Punctuation, nil},
{`(!)(\w+)`, ByGroups(Operator, Name), nil}, {`(!)(\w+)`, ByGroups(Operator, Name), nil},
}, },
}, }
)) }

View File

@ -6,14 +6,18 @@ import (
) )
// Abnf lexer. // Abnf lexer.
var Abnf = internal.Register(MustNewLexer( var Abnf = internal.Register(MustNewLazyLexer(
&Config{ &Config{
Name: "ABNF", Name: "ABNF",
Aliases: []string{"abnf"}, Aliases: []string{"abnf"},
Filenames: []string{"*.abnf"}, Filenames: []string{"*.abnf"},
MimeTypes: []string{"text/x-abnf"}, MimeTypes: []string{"text/x-abnf"},
}, },
Rules{ abnfRules,
))
func abnfRules() Rules {
return Rules{
"root": { "root": {
{`;.*$`, CommentSingle, nil}, {`;.*$`, CommentSingle, nil},
{`(%[si])?"[^"]*"`, Literal, nil}, {`(%[si])?"[^"]*"`, Literal, nil},
@ -34,5 +38,5 @@ var Abnf = internal.Register(MustNewLexer(
{`\s+`, Text, nil}, {`\s+`, Text, nil},
{`.`, Text, nil}, {`.`, Text, nil},
}, },
}, }
)) }

View File

@ -6,7 +6,7 @@ import (
) )
// Actionscript lexer. // Actionscript lexer.
var Actionscript = internal.Register(MustNewLexer( var Actionscript = internal.Register(MustNewLazyLexer(
&Config{ &Config{
Name: "ActionScript", Name: "ActionScript",
Aliases: []string{"as", "actionscript"}, Aliases: []string{"as", "actionscript"},
@ -15,7 +15,11 @@ var Actionscript = internal.Register(MustNewLexer(
NotMultiline: true, NotMultiline: true,
DotAll: true, DotAll: true,
}, },
Rules{ actionscriptRules,
))
func actionscriptRules() Rules {
return Rules{
"root": { "root": {
{`\s+`, Text, nil}, {`\s+`, Text, nil},
{`//.*?\n`, CommentSingle, nil}, {`//.*?\n`, CommentSingle, nil},
@ -35,5 +39,5 @@ var Actionscript = internal.Register(MustNewLexer(
{`"(\\\\|\\"|[^"])*"`, LiteralStringDouble, nil}, {`"(\\\\|\\"|[^"])*"`, LiteralStringDouble, nil},
{`'(\\\\|\\'|[^'])*'`, LiteralStringSingle, nil}, {`'(\\\\|\\'|[^'])*'`, LiteralStringSingle, nil},
}, },
}, }
)) }

View File

@ -6,7 +6,7 @@ import (
) )
// Actionscript 3 lexer. // Actionscript 3 lexer.
var Actionscript3 = internal.Register(MustNewLexer( var Actionscript3 = internal.Register(MustNewLazyLexer(
&Config{ &Config{
Name: "ActionScript 3", Name: "ActionScript 3",
Aliases: []string{"as3", "actionscript3"}, Aliases: []string{"as3", "actionscript3"},
@ -14,7 +14,11 @@ var Actionscript3 = internal.Register(MustNewLexer(
MimeTypes: []string{"application/x-actionscript3", "text/x-actionscript3", "text/actionscript3"}, MimeTypes: []string{"application/x-actionscript3", "text/x-actionscript3", "text/actionscript3"},
DotAll: true, DotAll: true,
}, },
Rules{ actionscript3Rules,
))
func actionscript3Rules() Rules {
return Rules{
"root": { "root": {
{`\s+`, Text, nil}, {`\s+`, Text, nil},
{`(function\s+)([$a-zA-Z_]\w*)(\s*)(\()`, ByGroups(KeywordDeclaration, NameFunction, Text, Operator), Push("funcparams")}, {`(function\s+)([$a-zA-Z_]\w*)(\s*)(\()`, ByGroups(KeywordDeclaration, NameFunction, Text, Operator), Push("funcparams")},
@ -52,5 +56,5 @@ var Actionscript3 = internal.Register(MustNewLexer(
{`,`, Operator, Pop(1)}, {`,`, Operator, Pop(1)},
Default(Pop(1)), Default(Pop(1)),
}, },
}, }
)) }

View File

@ -6,7 +6,7 @@ import (
) )
// Ada lexer. // Ada lexer.
var Ada = internal.Register(MustNewLexer( var Ada = internal.Register(MustNewLazyLexer(
&Config{ &Config{
Name: "Ada", Name: "Ada",
Aliases: []string{"ada", "ada95", "ada2005"}, Aliases: []string{"ada", "ada95", "ada2005"},
@ -14,7 +14,11 @@ var Ada = internal.Register(MustNewLexer(
MimeTypes: []string{"text/x-ada"}, MimeTypes: []string{"text/x-ada"},
CaseInsensitive: true, CaseInsensitive: true,
}, },
Rules{ adaRules,
))
func adaRules() Rules {
return Rules{
"root": { "root": {
{`[^\S\n]+`, Text, nil}, {`[^\S\n]+`, Text, nil},
{`--.*?\n`, CommentSingle, nil}, {`--.*?\n`, CommentSingle, nil},
@ -110,5 +114,5 @@ var Ada = internal.Register(MustNewLexer(
{`\)`, Punctuation, Pop(1)}, {`\)`, Punctuation, Pop(1)},
Include("root"), Include("root"),
}, },
}, }
)) }

47
vendor/github.com/alecthomas/chroma/lexers/a/al.go generated vendored Normal file
View File

@ -0,0 +1,47 @@
package a
import (
. "github.com/alecthomas/chroma" // nolint
"github.com/alecthomas/chroma/lexers/internal"
)
// Al lexer.
var Al = internal.Register(MustNewLazyLexer(
&Config{
Name: "AL",
Aliases: []string{"al"},
Filenames: []string{"*.al", "*.dal"},
MimeTypes: []string{"text/x-al"},
DotAll: true,
CaseInsensitive: true,
},
alRules,
))
// https://github.com/microsoft/AL/blob/master/grammar/alsyntax.tmlanguage
func alRules() Rules {
return Rules{
"root": {
{`\s+`, TextWhitespace, nil},
{`(?s)\/\*.*?\\*\*\/`, CommentMultiline, nil},
{`(?s)//.*?\n`, CommentSingle, nil},
{`\"([^\"])*\"`, Text, nil},
{`'([^'])*'`, LiteralString, nil},
{`\b(?i:(ARRAY|ASSERTERROR|BEGIN|BREAK|CASE|DO|DOWNTO|ELSE|END|EVENT|EXIT|FOR|FOREACH|FUNCTION|IF|IMPLEMENTS|IN|INDATASET|INTERFACE|INTERNAL|LOCAL|OF|PROCEDURE|PROGRAM|PROTECTED|REPEAT|RUNONCLIENT|SECURITYFILTERING|SUPPRESSDISPOSE|TEMPORARY|THEN|TO|TRIGGER|UNTIL|VAR|WHILE|WITH|WITHEVENTS))\b`, Keyword, nil},
{`\b(?i:(AND|DIV|MOD|NOT|OR|XOR))\b`, OperatorWord, nil},
{`\b(?i:(AVERAGE|CONST|COUNT|EXIST|FIELD|FILTER|LOOKUP|MAX|MIN|ORDER|SORTING|SUM|TABLEDATA|UPPERLIMIT|WHERE|ASCENDING|DESCENDING))\b`, Keyword, nil},
{`\b(?i:(CODEUNIT|PAGE|PAGEEXTENSION|PAGECUSTOMIZATION|DOTNET|ENUM|ENUMEXTENSION|VALUE|QUERY|REPORT|TABLE|TABLEEXTENSION|XMLPORT|PROFILE|CONTROLADDIN|REPORTEXTENSION|INTERFACE|PERMISSIONSET|PERMISSIONSETEXTENSION|ENTITLEMENT))\b`, Keyword, nil},
{`\b(?i:(Action|Array|Automation|BigInteger|BigText|Blob|Boolean|Byte|Char|ClientType|Code|Codeunit|CompletionTriggerErrorLevel|ConnectionType|Database|DataClassification|DataScope|Date|DateFormula|DateTime|Decimal|DefaultLayout|Dialog|Dictionary|DotNet|DotNetAssembly|DotNetTypeDeclaration|Duration|Enum|ErrorInfo|ErrorType|ExecutionContext|ExecutionMode|FieldClass|FieldRef|FieldType|File|FilterPageBuilder|Guid|InStream|Integer|Joker|KeyRef|List|ModuleDependencyInfo|ModuleInfo|None|Notification|NotificationScope|ObjectType|Option|OutStream|Page|PageResult|Query|Record|RecordId|RecordRef|Report|ReportFormat|SecurityFilter|SecurityFiltering|Table|TableConnectionType|TableFilter|TestAction|TestField|TestFilterField|TestPage|TestPermissions|TestRequestPage|Text|TextBuilder|TextConst|TextEncoding|Time|TransactionModel|TransactionType|Variant|Verbosity|Version|XmlPort|HttpContent|HttpHeaders|HttpClient|HttpRequestMessage|HttpResponseMessage|JsonToken|JsonValue|JsonArray|JsonObject|View|Views|XmlAttribute|XmlAttributeCollection|XmlComment|XmlCData|XmlDeclaration|XmlDocument|XmlDocumentType|XmlElement|XmlNamespaceManager|XmlNameTable|XmlNode|XmlNodeList|XmlProcessingInstruction|XmlReadOptions|XmlText|XmlWriteOptions|WebServiceActionContext|WebServiceActionResultCode|SessionSettings))\b`, Keyword, nil},
{`\b([<>]=|<>|<|>)\b?`, Operator, nil},
{`\b(\-|\+|\/|\*)\b`, Operator, nil},
{`\s*(\:=|\+=|-=|\/=|\*=)\s*?`, Operator, nil},
{`\b(?i:(ADD|ADDFIRST|ADDLAST|ADDAFTER|ADDBEFORE|ACTION|ACTIONS|AREA|ASSEMBLY|CHARTPART|CUEGROUP|CUSTOMIZES|COLUMN|DATAITEM|DATASET|ELEMENTS|EXTENDS|FIELD|FIELDGROUP|FIELDATTRIBUTE|FIELDELEMENT|FIELDGROUPS|FIELDS|FILTER|FIXED|GRID|GROUP|MOVEAFTER|MOVEBEFORE|KEY|KEYS|LABEL|LABELS|LAYOUT|MODIFY|MOVEFIRST|MOVELAST|MOVEBEFORE|MOVEAFTER|PART|REPEATER|USERCONTROL|REQUESTPAGE|SCHEMA|SEPARATOR|SYSTEMPART|TABLEELEMENT|TEXTATTRIBUTE|TEXTELEMENT|TYPE))\b`, Keyword, nil},
{`\s*[(\.\.)&\|]\s*`, Operator, nil},
{`\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)(L|l|UL|ul|u|U|F|f|ll|LL|ull|ULL)?\b`, LiteralNumber, nil},
{`[;:,]`, Punctuation, nil},
{`#[ \t]*(if|else|elif|endif|define|undef|region|endregion|pragma)\b.*?\n`, CommentPreproc, nil},
{`\w+`, Text, nil},
{`.`, Text, nil},
},
}
}

View File

@ -6,14 +6,18 @@ import (
) )
// Angular2 lexer. // Angular2 lexer.
var Angular2 = internal.Register(MustNewLexer( var Angular2 = internal.Register(MustNewLazyLexer(
&Config{ &Config{
Name: "Angular2", Name: "Angular2",
Aliases: []string{"ng2"}, Aliases: []string{"ng2"},
Filenames: []string{}, Filenames: []string{},
MimeTypes: []string{}, MimeTypes: []string{},
}, },
Rules{ angular2Rules,
))
func angular2Rules() Rules {
return Rules{
"root": { "root": {
{`[^{([*#]+`, Other, nil}, {`[^{([*#]+`, Other, nil},
{`(\{\{)(\s*)`, ByGroups(CommentPreproc, Text), Push("ngExpression")}, {`(\{\{)(\s*)`, ByGroups(CommentPreproc, Text), Push("ngExpression")},
@ -38,5 +42,5 @@ var Angular2 = internal.Register(MustNewLexer(
{`'.*?'`, LiteralString, Pop(1)}, {`'.*?'`, LiteralString, Pop(1)},
{`[^\s>]+`, LiteralString, Pop(1)}, {`[^\s>]+`, LiteralString, Pop(1)},
}, },
}, }
)) }

View File

@ -6,14 +6,18 @@ import (
) )
// ANTLR lexer. // ANTLR lexer.
var ANTLR = internal.Register(MustNewLexer( var ANTLR = internal.Register(MustNewLazyLexer(
&Config{ &Config{
Name: "ANTLR", Name: "ANTLR",
Aliases: []string{"antlr"}, Aliases: []string{"antlr"},
Filenames: []string{}, Filenames: []string{},
MimeTypes: []string{}, MimeTypes: []string{},
}, },
Rules{ antlrRules,
))
func antlrRules() Rules {
return Rules{
"whitespace": { "whitespace": {
{`\s+`, TextWhitespace, nil}, {`\s+`, TextWhitespace, nil},
}, },
@ -97,5 +101,5 @@ var ANTLR = internal.Register(MustNewLexer(
{`(\$[a-zA-Z]+)(\.?)(text|value)?`, ByGroups(NameVariable, Punctuation, NameProperty), nil}, {`(\$[a-zA-Z]+)(\.?)(text|value)?`, ByGroups(NameVariable, Punctuation, NameProperty), nil},
{`(\\\\|\\\]|\\\[|[^\[\]])+`, Other, nil}, {`(\\\\|\\\]|\\\[|[^\[\]])+`, Other, nil},
}, },
}, }
)) }

View File

@ -6,7 +6,7 @@ import (
) )
// Apacheconf lexer. // Apacheconf lexer.
var Apacheconf = internal.Register(MustNewLexer( var Apacheconf = internal.Register(MustNewLazyLexer(
&Config{ &Config{
Name: "ApacheConf", Name: "ApacheConf",
Aliases: []string{"apacheconf", "aconf", "apache"}, Aliases: []string{"apacheconf", "aconf", "apache"},
@ -14,7 +14,11 @@ var Apacheconf = internal.Register(MustNewLexer(
MimeTypes: []string{"text/x-apacheconf"}, MimeTypes: []string{"text/x-apacheconf"},
CaseInsensitive: true, CaseInsensitive: true,
}, },
Rules{ apacheconfRules,
))
func apacheconfRules() Rules {
return Rules{
"root": { "root": {
{`\s+`, Text, nil}, {`\s+`, Text, nil},
{`(#.*?)$`, Comment, nil}, {`(#.*?)$`, Comment, nil},
@ -34,5 +38,5 @@ var Apacheconf = internal.Register(MustNewLexer(
{`"([^"\\]*(?:\\.[^"\\]*)*)"`, LiteralStringDouble, nil}, {`"([^"\\]*(?:\\.[^"\\]*)*)"`, LiteralStringDouble, nil},
{`[^\s"\\]+`, Text, nil}, {`[^\s"\\]+`, Text, nil},
}, },
}, }
)) }

View File

@ -6,14 +6,18 @@ import (
) )
// Apl lexer. // Apl lexer.
var Apl = internal.Register(MustNewLexer( var Apl = internal.Register(MustNewLazyLexer(
&Config{ &Config{
Name: "APL", Name: "APL",
Aliases: []string{"apl"}, Aliases: []string{"apl"},
Filenames: []string{"*.apl"}, Filenames: []string{"*.apl"},
MimeTypes: []string{}, MimeTypes: []string{},
}, },
Rules{ aplRules,
))
func aplRules() Rules {
return Rules{
"root": { "root": {
{`\s+`, Text, nil}, {`\s+`, Text, nil},
{`[⍝#].*$`, CommentSingle, nil}, {`[⍝#].*$`, CommentSingle, nil},
@ -22,15 +26,15 @@ var Apl = internal.Register(MustNewLexer(
{`[⋄◇()]`, Punctuation, nil}, {`[⋄◇()]`, Punctuation, nil},
{`[\[\];]`, LiteralStringRegex, nil}, {`[\[\];]`, LiteralStringRegex, nil},
{`⎕[A-Za-zΔ∆⍙][A-Za-zΔ∆⍙_¯0-9]*`, NameFunction, nil}, {`⎕[A-Za-zΔ∆⍙][A-Za-zΔ∆⍙_¯0-9]*`, NameFunction, nil},
{`[A-Za-zΔ∆⍙][A-Za-zΔ∆⍙_¯0-9]*`, NameVariable, nil}, {`[A-Za-zΔ∆⍙_][A-Za-zΔ∆⍙_¯0-9]*`, NameVariable, nil},
{`¯?(0[Xx][0-9A-Fa-f]+|[0-9]*\.?[0-9]+([Ee][+¯]?[0-9]+)?|¯|∞)([Jj]¯?(0[Xx][0-9A-Fa-f]+|[0-9]*\.?[0-9]+([Ee][+¯]?[0-9]+)?|¯|∞))?`, LiteralNumber, nil}, {`¯?(0[Xx][0-9A-Fa-f]+|[0-9]*\.?[0-9]+([Ee][+¯]?[0-9]+)?|¯|∞)([Jj]¯?(0[Xx][0-9A-Fa-f]+|[0-9]*\.?[0-9]+([Ee][+¯]?[0-9]+)?|¯|∞))?`, LiteralNumber, nil},
{`[\.\\/⌿⍀¨⍣⍨⍠⍤∘]`, NameAttribute, nil}, {`[\.\\/⌿⍀¨⍣⍨⍠⍤∘⍥@⌺⌶⍢]`, NameAttribute, nil},
{`[+\-×÷⌈⌊∣|?*⍟○!⌹<≤=>≥≠≡≢∊⍷∪∩~∨∧⍱⍲⍴,⍪⌽⊖⍉↑↓⊂⊃⌷⍋⍒⊤⊥⍕⍎⊣⊢⍁⍂≈⌸⍯↗]`, Operator, nil}, {`[+\-×÷⌈⌊∣|?*⍟○!⌹<≤=>≥≠≡≢∊⍷∪∩~∨∧⍱⍲⍴,⍪⌽⊖⍉↑↓⊂⊃⌷⍋⍒⊤⊥⍕⍎⊣⊢⍁⍂≈⌸⍯↗⊆⍸]`, Operator, nil},
{``, NameConstant, nil}, {``, NameConstant, nil},
{`[⎕⍞]`, NameVariableGlobal, nil}, {`[⎕⍞]`, NameVariableGlobal, nil},
{`[←→]`, KeywordDeclaration, nil}, {`[←→]`, KeywordDeclaration, nil},
{`[⍺⍵⍶⍹∇:]`, NameBuiltinPseudo, nil}, {`[⍺⍵⍶⍹∇:]`, NameBuiltinPseudo, nil},
{`[{}]`, KeywordType, nil}, {`[{}]`, KeywordType, nil},
}, },
}, }
)) }

View File

@ -6,7 +6,7 @@ import (
) )
// Applescript lexer. // Applescript lexer.
var Applescript = internal.Register(MustNewLexer( var Applescript = internal.Register(MustNewLazyLexer(
&Config{ &Config{
Name: "AppleScript", Name: "AppleScript",
Aliases: []string{"applescript"}, Aliases: []string{"applescript"},
@ -14,7 +14,11 @@ var Applescript = internal.Register(MustNewLexer(
MimeTypes: []string{}, MimeTypes: []string{},
DotAll: true, DotAll: true,
}, },
Rules{ applescriptRules,
))
func applescriptRules() Rules {
return Rules{
"root": { "root": {
{`\s+`, Text, nil}, {`\s+`, Text, nil},
{`¬\n`, LiteralStringEscape, nil}, {`¬\n`, LiteralStringEscape, nil},
@ -51,5 +55,5 @@ var Applescript = internal.Register(MustNewLexer(
{`[^*(]+`, CommentMultiline, nil}, {`[^*(]+`, CommentMultiline, nil},
{`[*(]`, CommentMultiline, nil}, {`[*(]`, CommentMultiline, nil},
}, },
}, }
)) }

View File

@ -6,7 +6,7 @@ import (
) )
// Arduino lexer. // Arduino lexer.
var Arduino = internal.Register(MustNewLexer( var Arduino = internal.Register(MustNewLazyLexer(
&Config{ &Config{
Name: "Arduino", Name: "Arduino",
Aliases: []string{"arduino"}, Aliases: []string{"arduino"},
@ -14,7 +14,11 @@ var Arduino = internal.Register(MustNewLexer(
MimeTypes: []string{"text/x-arduino"}, MimeTypes: []string{"text/x-arduino"},
EnsureNL: true, EnsureNL: true,
}, },
Rules{ arduinoRules,
))
func arduinoRules() Rules {
return Rules{
"statements": { "statements": {
{Words(``, `\b`, `catch`, `const_cast`, `delete`, `dynamic_cast`, `explicit`, `export`, `friend`, `mutable`, `namespace`, `new`, `operator`, `private`, `protected`, `public`, `reinterpret_cast`, `restrict`, `static_cast`, `template`, `this`, `throw`, `throws`, `try`, `typeid`, `typename`, `using`, `virtual`, `constexpr`, `nullptr`, `decltype`, `thread_local`, `alignas`, `alignof`, `static_assert`, `noexcept`, `override`, `final`), Keyword, nil}, {Words(``, `\b`, `catch`, `const_cast`, `delete`, `dynamic_cast`, `explicit`, `export`, `friend`, `mutable`, `namespace`, `new`, `operator`, `private`, `protected`, `public`, `reinterpret_cast`, `restrict`, `static_cast`, `template`, `this`, `throw`, `throws`, `try`, `typeid`, `typename`, `using`, `virtual`, `constexpr`, `nullptr`, `decltype`, `thread_local`, `alignas`, `alignof`, `static_assert`, `noexcept`, `override`, `final`), Keyword, nil},
{`char(16_t|32_t)\b`, KeywordType, nil}, {`char(16_t|32_t)\b`, KeywordType, nil},
@ -106,5 +110,5 @@ var Arduino = internal.Register(MustNewLexer(
{`^\s*#endif.*?(?<!\\)\n`, CommentPreproc, Pop(1)}, {`^\s*#endif.*?(?<!\\)\n`, CommentPreproc, Pop(1)},
{`.*?\n`, Comment, nil}, {`.*?\n`, Comment, nil},
}, },
}, }
)) }

72
vendor/github.com/alecthomas/chroma/lexers/a/armasm.go generated vendored Normal file
View File

@ -0,0 +1,72 @@
package a
import (
. "github.com/alecthomas/chroma" // nolint
"github.com/alecthomas/chroma/lexers/internal"
)
var ArmAsm = internal.Register(MustNewLazyLexer(
&Config{
Name: "ArmAsm",
Aliases: []string{"armasm"},
EnsureNL: true,
Filenames: []string{"*.s", "*.S"},
MimeTypes: []string{"text/x-armasm", "text/x-asm"},
},
armasmRules,
))
func armasmRules() Rules {
return Rules{
"commentsandwhitespace": {
{`\s+`, Text, nil},
{`[@;].*?\n`, CommentSingle, nil},
{`/\*.*?\*/`, CommentMultiline, nil},
},
"literal": {
// Binary
{`0b[01]+`, NumberBin, Pop(1)},
// Hex
{`0x\w{1,8}`, NumberHex, Pop(1)},
// Octal
{`0\d+`, NumberOct, Pop(1)},
// Float
{`\d+?\.\d+?`, NumberFloat, Pop(1)},
// Integer
{`\d+`, NumberInteger, Pop(1)},
// String
{`(")(.+)(")`, ByGroups(Punctuation, StringDouble, Punctuation), Pop(1)},
// Char
{`(')(.{1}|\\.{1})(')`, ByGroups(Punctuation, StringChar, Punctuation), Pop(1)},
},
"opcode": {
// Escape at line end
{`\n`, Text, Pop(1)},
// Comment
{`(@|;).*\n`, CommentSingle, Pop(1)},
// Whitespace
{`(\s+|,)`, Text, nil},
// Register by number
{`[rapcfxwbhsdqv]\d{1,2}`, NameClass, nil},
// Address by hex
{`=0x\w+`, ByGroups(Text, NameLabel), nil},
// Pseudo address by label
{`(=)(\w+)`, ByGroups(Text, NameLabel), nil},
// Immediate
{`#`, Text, Push("literal")},
},
"root": {
Include("commentsandwhitespace"),
// Directive with optional param
{`(\.\w+)([ \t]+\w+\s+?)?`, ByGroups(KeywordNamespace, NameLabel), nil},
// Label with data
{`(\w+)(:)(\s+\.\w+\s+)`, ByGroups(NameLabel, Punctuation, KeywordNamespace), Push("literal")},
// Label
{`(\w+)(:)`, ByGroups(NameLabel, Punctuation), nil},
// Syscall Op
{`svc\s+\w+`, NameNamespace, nil},
// Opcode
{`[a-zA-Z]+`, Text, Push("opcode")},
},
}
}

View File

@ -6,14 +6,18 @@ import (
) )
// Awk lexer. // Awk lexer.
var Awk = internal.Register(MustNewLexer( var Awk = internal.Register(MustNewLazyLexer(
&Config{ &Config{
Name: "Awk", Name: "Awk",
Aliases: []string{"awk", "gawk", "mawk", "nawk"}, Aliases: []string{"awk", "gawk", "mawk", "nawk"},
Filenames: []string{"*.awk"}, Filenames: []string{"*.awk"},
MimeTypes: []string{"application/x-awk"}, MimeTypes: []string{"application/x-awk"},
}, },
Rules{ awkRules,
))
func awkRules() Rules {
return Rules{
"commentsandwhitespace": { "commentsandwhitespace": {
{`\s+`, Text, nil}, {`\s+`, Text, nil},
{`#.*$`, CommentSingle, nil}, {`#.*$`, CommentSingle, nil},
@ -44,5 +48,5 @@ var Awk = internal.Register(MustNewLexer(
{`"(\\\\|\\"|[^"])*"`, LiteralStringDouble, nil}, {`"(\\\\|\\"|[^"])*"`, LiteralStringDouble, nil},
{`'(\\\\|\\'|[^'])*'`, LiteralStringSingle, nil}, {`'(\\\\|\\'|[^'])*'`, LiteralStringSingle, nil},
}, },
}, }
)) }

View File

@ -6,7 +6,7 @@ import (
) )
// Ballerina lexer. // Ballerina lexer.
var Ballerina = internal.Register(MustNewLexer( var Ballerina = internal.Register(MustNewLazyLexer(
&Config{ &Config{
Name: "Ballerina", Name: "Ballerina",
Aliases: []string{"ballerina"}, Aliases: []string{"ballerina"},
@ -14,7 +14,11 @@ var Ballerina = internal.Register(MustNewLexer(
MimeTypes: []string{"text/x-ballerina"}, MimeTypes: []string{"text/x-ballerina"},
DotAll: true, DotAll: true,
}, },
Rules{ ballerinaRules,
))
func ballerinaRules() Rules {
return Rules{
"root": { "root": {
{`[^\S\n]+`, Text, nil}, {`[^\S\n]+`, Text, nil},
{`//.*?\n`, CommentSingle, nil}, {`//.*?\n`, CommentSingle, nil},
@ -42,5 +46,5 @@ var Ballerina = internal.Register(MustNewLexer(
"import": { "import": {
{`[\w.]+`, NameNamespace, Pop(1)}, {`[\w.]+`, NameNamespace, Pop(1)},
}, },
}, }
)) }

View File

@ -7,17 +7,27 @@ import (
"github.com/alecthomas/chroma/lexers/internal" "github.com/alecthomas/chroma/lexers/internal"
) )
// TODO(moorereason): can this be factored away?
var bashAnalyserRe = regexp.MustCompile(`(?m)^#!.*/bin/(?:env |)(?:bash|zsh|sh|ksh)`) var bashAnalyserRe = regexp.MustCompile(`(?m)^#!.*/bin/(?:env |)(?:bash|zsh|sh|ksh)`)
// Bash lexer. // Bash lexer.
var Bash = internal.Register(MustNewLexer( var Bash = internal.Register(MustNewLazyLexer(
&Config{ &Config{
Name: "Bash", Name: "Bash",
Aliases: []string{"bash", "sh", "ksh", "zsh", "shell"}, Aliases: []string{"bash", "sh", "ksh", "zsh", "shell"},
Filenames: []string{"*.sh", "*.ksh", "*.bash", "*.ebuild", "*.eclass", "*.exheres-0", "*.exlib", "*.zsh", "*.zshrc", ".bashrc", "bashrc", ".bash_*", "bash_*", "zshrc", ".zshrc", "PKGBUILD"}, Filenames: []string{"*.sh", "*.ksh", "*.bash", "*.ebuild", "*.eclass", ".env", "*.env", "*.exheres-0", "*.exlib", "*.zsh", "*.zshrc", ".bashrc", "bashrc", ".bash_*", "bash_*", "zshrc", ".zshrc", "PKGBUILD"},
MimeTypes: []string{"application/x-sh", "application/x-shellscript"}, MimeTypes: []string{"application/x-sh", "application/x-shellscript"},
}, },
Rules{ bashRules,
).SetAnalyser(func(text string) float32 {
if bashAnalyserRe.FindString(text) != "" {
return 1.0
}
return 0.0
}))
func bashRules() Rules {
return Rules{
"root": { "root": {
Include("basic"), Include("basic"),
{"`", LiteralStringBacktick, Push("backticks")}, {"`", LiteralStringBacktick, Push("backticks")},
@ -86,10 +96,5 @@ var Bash = internal.Register(MustNewLexer(
{"`", LiteralStringBacktick, Pop(1)}, {"`", LiteralStringBacktick, Pop(1)},
Include("root"), Include("root"),
}, },
},
).SetAnalyser(func(text string) float32 {
if bashAnalyserRe.FindString(text) != "" {
return 1.0
} }
return 0.0 }
}))

View File

@ -0,0 +1,27 @@
package b
import (
. "github.com/alecthomas/chroma" // nolint
"github.com/alecthomas/chroma/lexers/internal"
)
// BashSession lexer.
var BashSession = internal.Register(MustNewLazyLexer(
&Config{
Name: "BashSession",
Aliases: []string{"bash-session", "console", "shell-session"},
Filenames: []string{".sh-session"},
MimeTypes: []string{"text/x-sh"},
EnsureNL: true,
},
bashsessionRules,
))
func bashsessionRules() Rules {
return Rules{
"root": {
{`^((?:\[[^]]+@[^]]+\]\s?)?[#$%>])(\s*)(.*\n?)`, ByGroups(GenericPrompt, Text, Using(Bash)), nil},
{`^.+\n?`, GenericOutput, nil},
},
}
}

View File

@ -6,7 +6,7 @@ import (
) )
// Batchfile lexer. // Batchfile lexer.
var Batchfile = internal.Register(MustNewLexer( var Batchfile = internal.Register(MustNewLazyLexer(
&Config{ &Config{
Name: "Batchfile", Name: "Batchfile",
Aliases: []string{"bat", "batch", "dosbatch", "winbatch"}, Aliases: []string{"bat", "batch", "dosbatch", "winbatch"},
@ -14,7 +14,11 @@ var Batchfile = internal.Register(MustNewLexer(
MimeTypes: []string{"application/x-dos-batch"}, MimeTypes: []string{"application/x-dos-batch"},
CaseInsensitive: true, CaseInsensitive: true,
}, },
Rules{ batchfileRules,
))
func batchfileRules() Rules {
return Rules{
"root": { "root": {
{`\)((?=\()|(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a]))(?:(?:[^\n\x1a^]|\^[\n\x1a]?[\w\W])*)`, CommentSingle, nil}, {`\)((?=\()|(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a]))(?:(?:[^\n\x1a^]|\^[\n\x1a]?[\w\W])*)`, CommentSingle, nil},
{`(?=((?:(?<=^[^:])|^[^:]?)[\t\v\f\r ,;=\xa0]*)(:))`, Text, Push("follow")}, {`(?=((?:(?<=^[^:])|^[^:]?)[\t\v\f\r ,;=\xa0]*)(:))`, Text, Push("follow")},
@ -190,5 +194,5 @@ var Batchfile = internal.Register(MustNewLexer(
{`else(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a])`, Keyword, Pop(1)}, {`else(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a])`, Keyword, Pop(1)},
Default(Pop(1)), Default(Pop(1)),
}, },
}, }
)) }

View File

@ -6,7 +6,7 @@ import (
) )
// Bibtex lexer. // Bibtex lexer.
var Bibtex = internal.Register(MustNewLexer( var Bibtex = internal.Register(MustNewLazyLexer(
&Config{ &Config{
Name: "BibTeX", Name: "BibTeX",
Aliases: []string{"bib", "bibtex"}, Aliases: []string{"bib", "bibtex"},
@ -15,7 +15,11 @@ var Bibtex = internal.Register(MustNewLexer(
NotMultiline: true, NotMultiline: true,
CaseInsensitive: true, CaseInsensitive: true,
}, },
Rules{ bibtexRules,
))
func bibtexRules() Rules {
return Rules{
"root": { "root": {
Include("whitespace"), Include("whitespace"),
{`@comment`, Comment, nil}, {`@comment`, Comment, nil},
@ -72,5 +76,5 @@ var Bibtex = internal.Register(MustNewLexer(
"whitespace": { "whitespace": {
{`\s+`, Text, nil}, {`\s+`, Text, nil},
}, },
}, }
)) }

112
vendor/github.com/alecthomas/chroma/lexers/b/bicep.go generated vendored Normal file
View File

@ -0,0 +1,112 @@
package b
import (
"strings"
. "github.com/alecthomas/chroma" // nolint
"github.com/alecthomas/chroma/lexers/internal"
)
// Bicep lexer.
var Bicep = internal.Register(MustNewLazyLexer(
&Config{
Name: "Bicep",
Aliases: []string{"bicep"},
Filenames: []string{"*.bicep"},
},
bicepRules,
))
func bicepRules() Rules {
bicepFunctions := []string{
"any",
"array",
"concat",
"contains",
"empty",
"first",
"intersection",
"items",
"last",
"length",
"min",
"max",
"range",
"skip",
"take",
"union",
"dateTimeAdd",
"utcNow",
"deployment",
"environment",
"loadFileAsBase64",
"loadTextContent",
"int",
"json",
"extensionResourceId",
"getSecret",
"list",
"listKeys",
"listKeyValue",
"listAccountSas",
"listSecrets",
"pickZones",
"reference",
"resourceId",
"subscriptionResourceId",
"tenantResourceId",
"managementGroup",
"resourceGroup",
"subscription",
"tenant",
"base64",
"base64ToJson",
"base64ToString",
"dataUri",
"dataUriToString",
"endsWith",
"format",
"guid",
"indexOf",
"lastIndexOf",
"length",
"newGuid",
"padLeft",
"replace",
"split",
"startsWith",
"string",
"substring",
"toLower",
"toUpper",
"trim",
"uniqueString",
"uri",
"uriComponent",
"uriComponentToString",
}
return Rules{
"root": {
{`//[^\n\r]+`, CommentSingle, nil},
{`/\*.*?\*/`, CommentMultiline, nil},
{`([']?\w+[']?)(:)`, ByGroups(NameProperty, Punctuation), nil},
{`\b('(resourceGroup|subscription|managementGroup|tenant)')\b`, KeywordNamespace, nil},
{`'[\w\$\{\(\)\}\.]{1,}?'`, LiteralStringInterpol, nil},
{`('''|').*?('''|')`, LiteralString, nil},
{`\b(allowed|batchSize|description|maxLength|maxValue|metadata|minLength|minValue|secure)\b`, NameDecorator, nil},
{`\b(az|sys)\.`, NameNamespace, nil},
{`\b(` + strings.Join(bicepFunctions, "|") + `)\b`, NameFunction, nil},
// https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/bicep-functions-logical
{`\b(bool)(\()`, ByGroups(NameFunction, Punctuation), nil},
{`\b(for|if|in)\b`, Keyword, nil},
{`\b(module|output|param|resource|var)\b`, KeywordDeclaration, nil},
{`\b(array|bool|int|object|string)\b`, KeywordType, nil},
// https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/operators
{`(>=|>|<=|<|==|!=|=~|!~|::|&&|\?\?|!|-|%|\*|\/|\+)`, Operator, nil},
{`[\(\)\[\]\.:\?{}@=]`, Punctuation, nil},
{`[\w_-]+`, Text, nil},
{`\s+`, TextWhitespace, nil},
},
}
}

View File

@ -6,7 +6,7 @@ import (
) )
// Blitzbasic lexer. // Blitzbasic lexer.
var Blitzbasic = internal.Register(MustNewLexer( var Blitzbasic = internal.Register(MustNewLazyLexer(
&Config{ &Config{
Name: "BlitzBasic", Name: "BlitzBasic",
Aliases: []string{"blitzbasic", "b3d", "bplus"}, Aliases: []string{"blitzbasic", "b3d", "bplus"},
@ -14,7 +14,11 @@ var Blitzbasic = internal.Register(MustNewLexer(
MimeTypes: []string{"text/x-bb"}, MimeTypes: []string{"text/x-bb"},
CaseInsensitive: true, CaseInsensitive: true,
}, },
Rules{ blitzbasicRules,
))
func blitzbasicRules() Rules {
return Rules{
"root": { "root": {
{`[ \t]+`, Text, nil}, {`[ \t]+`, Text, nil},
{`;.*?\n`, CommentSingle, nil}, {`;.*?\n`, CommentSingle, nil},
@ -44,5 +48,5 @@ var Blitzbasic = internal.Register(MustNewLexer(
{`"C?`, LiteralStringDouble, Pop(1)}, {`"C?`, LiteralStringDouble, Pop(1)},
{`[^"]+`, LiteralStringDouble, nil}, {`[^"]+`, LiteralStringDouble, nil},
}, },
}, }
)) }

View File

@ -6,19 +6,23 @@ import (
) )
// Bnf lexer. // Bnf lexer.
var Bnf = internal.Register(MustNewLexer( var Bnf = internal.Register(MustNewLazyLexer(
&Config{ &Config{
Name: "BNF", Name: "BNF",
Aliases: []string{"bnf"}, Aliases: []string{"bnf"},
Filenames: []string{"*.bnf"}, Filenames: []string{"*.bnf"},
MimeTypes: []string{"text/x-bnf"}, MimeTypes: []string{"text/x-bnf"},
}, },
Rules{ bnfRules,
))
func bnfRules() Rules {
return Rules{
"root": { "root": {
{`(<)([ -;=?-~]+)(>)`, ByGroups(Punctuation, NameClass, Punctuation), nil}, {`(<)([ -;=?-~]+)(>)`, ByGroups(Punctuation, NameClass, Punctuation), nil},
{`::=`, Operator, nil}, {`::=`, Operator, nil},
{`[^<>:]+`, Text, nil}, {`[^<>:]+`, Text, nil},
{`.`, Text, nil}, {`.`, Text, nil},
}, },
}, }
)) }

View File

@ -6,14 +6,18 @@ import (
) )
// Brainfuck lexer. // Brainfuck lexer.
var Brainfuck = internal.Register(MustNewLexer( var Brainfuck = internal.Register(MustNewLazyLexer(
&Config{ &Config{
Name: "Brainfuck", Name: "Brainfuck",
Aliases: []string{"brainfuck", "bf"}, Aliases: []string{"brainfuck", "bf"},
Filenames: []string{"*.bf", "*.b"}, Filenames: []string{"*.bf", "*.b"},
MimeTypes: []string{"application/x-brainfuck"}, MimeTypes: []string{"application/x-brainfuck"},
}, },
Rules{ brainfuckRules,
))
func brainfuckRules() Rules {
return Rules{
"common": { "common": {
{`[.,]+`, NameTag, nil}, {`[.,]+`, NameTag, nil},
{`[+-]+`, NameBuiltin, nil}, {`[+-]+`, NameBuiltin, nil},
@ -30,5 +34,5 @@ var Brainfuck = internal.Register(MustNewLexer(
{`\]`, Keyword, Pop(1)}, {`\]`, Keyword, Pop(1)},
Include("common"), Include("common"),
}, },
}, }
)) }

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