Compare commits

..

253 commits

Author SHA1 Message Date
Ethan P. 3860f0f148
test: Add tests for nu shell detection
Some checks failed
Test / Build (push) Has been cancelled
Test / Test (push) Has been cancelled
Test / Test_Consistency (push) Has been cancelled
Test / Test_Symlinks (push) Has been cancelled
2025-02-21 19:01:37 -08:00
Ethan P. 58e7f52717
test: Add more robust tests for shell detection 2025-02-21 18:55:25 -08:00
Ethan P. 86fd93e47e
test: Test shell detection with fake fish shell
Rather than using a real fish shell for tests, we're faking it so
package maintainers don't need to add multiple shells as build
dependencies.
2025-02-21 18:12:48 -08:00
Ethan P. a0d3a9bd63
batpipe: Add detection for nushell 2025-02-21 18:02:54 -08:00
jlkDE b977dffc54 test: Recreate snapshots with bat v0.25.0
Some checks are pending
Test / Build (push) Waiting to run
Test / Test (push) Waiting to run
Test / Test_Consistency (push) Blocked by required conditions
Test / Test_Symlinks (push) Blocked by required conditions
Due to theme detection using terminal-colorsaurus now
(https://github.com/sharkdp/bat/pull/2896) the line numbers and borders
are now colored in alignment with the foreground color.
2025-02-21 17:07:01 -08:00
Ethan P. 36c77c171c
Bump version to 2024.08.24
Some checks failed
Test / Build (push) Has been cancelled
Test / Test (push) Has been cancelled
Test / Test_Consistency (push) Has been cancelled
Test / Test_Symlinks (push) Has been cancelled
2024-08-24 14:25:19 -07:00
Ethan P. 6092fa5bad
prettybad: Have yq output tabs instead of spaces
It's a hack with `sed`, but it's better than nothing.
2024-08-24 14:22:18 -07:00
Ethan P. 1cffc910d5
docs: Mention yq as formatter in prettybat docs 2024-08-24 14:07:27 -07:00
Ethan P. 47cfb9ec73
prettybat: Use yq for YAML and JSON pretty formatting 2024-08-24 14:02:23 -07:00
Adrian Wilkins 32706dd581 Trim trailing whitespace
Otherwise `MANPAGER`'s trailing whitespace is escaped, which adds the next line of `export` as part of the command.

```bash
export MANPAGER=env\ BATMAN_IS_BEING_MANPAGER=yes\ bash\ /usr/local/bin/batman\
export MANROFFOPT=-c
```

and you get `cat: export: No such file or directory`
2024-08-24 13:50:25 -07:00
Ethan P. 01c2f9bb94
test: Ignore flags passed to shim less
This fixes #126.
2024-08-24 13:45:36 -07:00
HE7086 0acf65edde batdiff: Support filenames with special characters 2024-08-24 13:38:41 -07:00
Mehdi Rafee 92dcb78765 Allow --wrap option to be passed to bat 2024-08-24 13:36:36 -07:00
Maor Kadosh ac4f8710ba Batman unalias 2024-08-24 13:36:00 -07:00
Ethan P. aef5a424b4
Bump version to 2024.07.10 2024-06-10 12:56:58 -07:00
Ethan P. 3b3a8923a1
docs: Update banner 2024-06-10 12:56:16 -07:00
Ethan P. eb7ad6b21b
batman: Provide way to use BATMAN as MANPAGER 2024-06-10 12:55:10 -07:00
Ethan P. f5b8cfb5d7
Bump version to 2024.06.01 2024-06-01 15:33:06 -07:00
Ethan P. 38bacab368
prettybat: Don't add extra newlines to tsv for gnu 2024-06-01 15:30:30 -07:00
Ethan P. 043d2da964
prettybat: Add basic support for tsv files 2024-06-01 15:24:20 -07:00
Joe DiPilato 75c757817f remove comma after black formatter 2024-05-16 12:24:57 -07:00
Ethan P. c9e917e43a
docs: Clarify how to use batman instead of man 2024-02-12 15:47:01 -08:00
Ethan P. 6c8879f1d7
Bump version to 2024.02.12 2024-02-12 15:35:00 -08:00
Ethan P. 6bd35e5aed
ci: Fix downloading ripgrep dependency 2024-02-12 12:23:54 -08:00
Ethan P. 205e9c40e1
batman: Fix BAT_PAGER causing infinite recursion
Rather than replacing `BAT_PAGER` with `MANPAGER` every time batman is
used, only replace it when batman is called directly (and not used as
the MANPAGER).
2024-02-12 01:28:34 -08:00
Ethan P. 5011f0709e
batgrep: Add support for ripgrep-all
To use `ripgrep-all`, add the `--rga` flag when running batgrep.
2024-02-12 01:18:11 -08:00
Ethan P. 82911b5b7b
batman: Remove '-u' flag when calling sed
The `-u` flag is not available on busybox `sed`.
2024-02-12 01:04:40 -08:00
Felix Yan a427f6eeda Update Arch Linux package URL in README.md
The old URL returns 404 now.
2024-02-12 01:01:06 -08:00
Ethan P. 8460b39956
batman: Remove dependency on col program
This follows proposed solution (C) in
https://github.com/sharkdp/bat/issues/2568
2024-02-12 00:58:56 -08:00
Herby Gillot 3f259f1a07 docs: add MacPorts install instructions
https://ports.macports.org/port/bat-extras/
2023-12-13 10:29:58 -08:00
Noah Betzen bc534e9dd2 Add Elixir mix format support 2023-12-13 10:26:22 -08:00
Ethan P. a686d6742c
ci: Only run release workflow on tag push 2023-09-22 11:32:06 -07:00
Ethan P. d5ea485a0b
ci: Update deprecated Actions commands 2023-09-22 11:13:07 -07:00
Ethan P. c4b2a12f73
Bump version to 2023.09.19 2023-09-19 10:57:28 -07:00
Ethan P. f192b7a7d8
batman: Split short options 2023-09-19 10:46:46 -07:00
Ethan P. c29dfb136a
batdiff: Show error when not in git repo
This is instead of allowing `git` to dump its own warning to the
console.
2023-09-13 11:53:49 -07:00
Ethan P. 2e1e768559
batdiff: Support filtering diffs by directory
This brings parity between using `delta` and `bat --diff`.
Previously, only `delta` supported this use case.
2023-09-13 11:15:20 -07:00
Ethan P. 4039859b62
batdiff: Check delta version for --hunk-style
This adds compatibility for older `delta` versions.
2023-09-13 11:06:11 -07:00
Ethan P. 6f579b1d45
batman: Document issues with non-default themes 2023-09-13 10:54:46 -07:00
Bill Mill c26f22c768 fix typos in batgrep help 2023-09-13 10:41:31 -07:00
Ethan P. 895b4ee0ce
Merge branch 'compressed-tar' 2023-09-13 10:35:51 -07:00
Ethan P. ba984f470e
Merge branch 'master' into compressed-tar 2023-09-13 10:33:26 -07:00
David Gus Shields 162c21d012 readd exa conditionally 2023-09-13 10:29:14 -07:00
David Gus Shields d96fc92165 change all references to exa to eza 2023-09-13 10:29:14 -07:00
Adam Spiers 85aaf4875d batpipe: Add support for gzipped and bzipped tar files
This adds support for *.{tgz,tar.gz,tbz,tar.bz2} files.
2023-08-03 17:01:26 +01:00
Adam Spiers 4c220a84a3 batpipe: Extract batpipe_archive_header() helper function 2023-08-03 16:49:53 +01:00
Ethan P ceaac1b917
ci: Remove wait job 2023-06-15 17:15:14 -07:00
Rex Ackermann 03549d651a build: Add termux support
sets the OPT_PREFIX to /data/data/com.termux/files/usr if termux is detected.
2023-06-15 16:56:16 -07:00
Bill Mill f47d9c4138 add a test 2023-06-15 16:54:45 -07:00
Bill Mill 762ac557f4 use hook_help 2023-06-15 16:54:45 -07:00
Bill Mill 15876936ed add --help to batgrep 2023-06-15 16:54:45 -07:00
Ethan P 94fbeb5b27
ci: Tag-to-release 2023-06-15 16:42:49 -07:00
Ethan P d0738b7289
dev: Allow release script to be used as lib 2023-06-15 16:16:19 -07:00
Ethan P 48fe8875f7
build: Remove end-of-line whitespace 2023-06-15 16:15:42 -07:00
Ethan P 5ac224bc87
build: A way to get data from build.sh 2023-06-15 16:15:41 -07:00
Ethan P 974073fc72
build: Add option to print source files 2023-06-15 16:15:40 -07:00
Ethan P 7aa73f8989
batdiff: Add '--staged' flag 2023-05-15 20:31:48 -07:00
Ethan P 7107e1603f
build: Add support for more alt executables 2023-05-15 19:50:24 -07:00
Ethan P 3e7041da7b
test: Update test framework 2023-05-15 19:31:29 -07:00
Ethan P 14f36f51b7
batman: Use self as MANPAGER
With certain implementations of `man`, the pager is called with execve
and naiive argument splitting instead of going through the shell.

This change uses the script as the pager itself, working around
the issue.
2023-05-15 19:03:00 -07:00
Ethan P e609d1a2c5
prettybat: Fix short flags passed to bat
Fixes #87.
2023-03-24 11:01:56 -07:00
Ethan P daf888f5ed
docs: Update install instructions for brew 2023-03-21 02:10:41 -07:00
Ethan P bea36be397
Bump version to 2023.03.21 2023-03-21 01:58:48 -07:00
Ethan P 673dc6fd75
batdiff: Update docs to add BATDIFF_USE_DELTA 2023-03-21 01:58:48 -07:00
Ethan P 97006f0f09
batman: Allow spaces in manual sections 2023-03-21 01:58:48 -07:00
Ethan P 287398acbc
batman: Fix parsing manual pages in fzf preview 2023-03-21 01:58:48 -07:00
Ethan P e53db161a7
batman: Only parse first manpage when selected 2023-03-21 01:58:48 -07:00
Ethan P f07a22d536
batpipe: Fix SELF variable being wrong when compressed 2023-03-21 01:58:48 -07:00
Ethan P ea604457ea
batpipe: Add BATPIPE_TERM_WIDTH 2023-03-21 01:58:47 -07:00
Ethan P 998e2ff1a9
batpipe: Fix opened-in-less detection 2023-03-21 01:58:47 -07:00
Ethan P 26111f9cee
ci: Fix installation of ripgrep for tests
The previous `find` command was grabbing the bash completions, rather
than the executable file. This should fix that.
2023-03-20 23:40:08 -07:00
Ethan P 76906ca014
test: Add test to ensure ripgrep works
This is intended to make debugging CI issues easier in the future.
2023-03-20 23:29:36 -07:00
Ethan P 428a78837d
ci: Fix errors with permissions 2023-03-20 23:04:43 -07:00
Ethan P 36786ba570
ci: Update action to fix warnings 2023-03-20 23:00:52 -07:00
Eoin Mcloughlin e9daffde45 Fix bug where some man pages would fail to open
Some man page sections contain alphabetical characters (e.g. 3bsd,
3perl, 3ssl.) This fixes a bug where selection of such pages would
not open any man page.
2023-03-20 22:45:15 -07:00
Alex Wood 3917100c6e Add instructions for bat-extras on Fedora 2023-03-20 22:42:24 -07:00
Amory Meltzer ☀ a4237efffd batwatch docs: Fix link to entr project 2023-03-20 22:36:49 -07:00
Ethan P 38014ba72d
docs: Add install steps for Gentoo (@ran-dall)
Thanks for maintaining the package and writing up the install instructions, @ran-dall!
2022-07-30 17:03:42 -07:00
Ethan P 3e2e8df594
docs: Update README about manual files
Manual files are now generated and installed by default.
2022-07-30 17:01:03 -07:00
Ethan P abda988cbd
build: Add manuals to '--install' 2022-07-29 16:51:59 -07:00
Ethan P 49f27efea2
docs: Update CI badge on README 2022-07-29 16:03:07 -07:00
Ethan P 2f2b33391a
ci: Remove CircleCI 2022-07-29 16:00:11 -07:00
Ethan P 90bb604d74
ci: Add GitHub Actions test workflow
This will eventually replace CircleCI.
2022-07-29 15:55:51 -07:00
Ethan P ad2d788d6c
build: Add more '--no-*' options 2022-07-29 14:01:55 -07:00
Ethan P 10d976da55
developer: Add '.vscode' to gitignore 2022-07-29 14:01:34 -07:00
Ethan P 62b8f74df2
docs: Add Arch Linux install instructions to README 2022-07-28 13:47:44 -07:00
Ethan P a011290c2f
Bump version to 2022.07.27 2022-07-27 13:30:27 -07:00
Trevor Leibert 20cc74e8b4 batman: Fix error when using fzf to pick manpages
- steps to reproduce error:
  - run batman with no arguments
  - pick any entry
  - script exits and man prints an error message similar to 'No manual entry for .k5identity  in section 5'

I speculated that this error was due to whitespace being attached to the second argument in the man call.
This commit fixes this error by piping the second argument through xargs to strip these spaces.

After applying this commit the script works as expected.
2022-07-21 19:37:56 -07:00
MithicSpirit 33c2c195dc batdiff: Fix line too long (>80 chars) 2022-07-21 19:37:08 -07:00
MithicSpirit 462df32d4c batdiff: Fix use of deprecated --hunk-style
Delta has deprecated --hunk-style in favor of its synonym
--hunk-header-decoration-style. See
https://github.com/dandavison/delta/releases/tag/0.12.0
2022-07-21 19:37:08 -07:00
Robert Irelan 69792e95da batgrep: handle -u flag to ripgrep 2022-07-21 19:35:55 -07:00
Konstantin Mikhailov 8ad00bd978 batgrep: add flag to disable separators between files 2021-08-29 13:49:25 -07:00
Ethan P 7803ecaba1
batpipe: Fix colors not showing up inside less 2021-08-21 16:19:17 -07:00
Ethan P d8532bb35a
mdroff: Remove <br/> from output 2021-08-21 16:03:05 -07:00
Ethan P ef0356a2d8
mdroff: Fix text being trimmed incorrectly 2021-08-21 16:02:48 -07:00
Ethan P 33e0c168e0
batgrep: Add support for '--' 2021-08-21 15:48:14 -07:00
Ethan P 82ac5c4391
batman: If fzf is installed, use batman to search through man pages 2021-08-21 15:22:29 -07:00
Ethan P f280553fb1
batman: Only set grid style if BAT_STYLE is not defined 2021-08-21 15:19:14 -07:00
Ethan P d989b0bfde
batwatch: Buffer to reduce flickering from redraw 2021-08-11 01:15:46 -07:00
Ethan P 20cdf5a998
lib: Fix no pager being used when PAGER and BAT_PAGER are unset (#57) 2021-04-13 12:08:37 -07:00
Ethan P 75d66fa352
batpipe: Fix incorrect colors in usage instructions 2021-04-10 12:31:52 -07:00
Ethan P a4a5f5a5fd
lib: Add test for 'less' as pager instead of 'bat' 2021-04-10 12:26:54 -07:00
Ethan P 4f0b961d12
lib: Use 'less' as pager instead of 'bat' 2021-04-10 12:24:41 -07:00
Ethan P cae57be87b
batgrep: Respect the BAT_STYLE environment variable (#54) 2021-04-09 01:12:36 -07:00
Ethan P a526c7927b
batgrep: Fix to work with rg 11 (#53) 2021-04-08 14:17:13 -07:00
Ethan P eee3156e8a
test: Fix fish being incorrectly detected as installed 2021-04-07 20:38:35 -07:00
Ethan P e1a0c7a349
Bump version to 2021.04.06 2021-04-06 16:21:30 -07:00
Ethan P 08d2896b1a
lib: Fix accidental forkbomb when using release.sh 2021-04-06 16:21:30 -07:00
Ethan P 54db67408e
prettybat: Add support for SVG formatting using prettier 2021-04-06 15:57:54 -07:00
Ethan P 860638715a
prettybat: Allow reading from pipe (#51) 2021-04-06 15:45:30 -07:00
Ethan P 6497bc04a0
batpipe: Change the install commands to enable color automatically 2021-04-04 14:22:39 -07:00
Ethan P 672b4814ec
batpipe: Fix incorrect bash install commands 2021-04-04 14:20:11 -07:00
Ethan P 2aff613888
batpipe: Format compressed files with 'bat' 2021-04-04 14:14:16 -07:00
Ethan P 82b444f93b
batpipe: Replace 'bat_if_not_bat' with a smarter 'bat' function 2021-04-04 14:13:16 -07:00
Ethan P 94bb4b6a86
batpipe: Remove incorrect text from documentation 2021-04-04 14:00:52 -07:00
Ethan P 650845dee4
lib: Fix 'getargs' setting an empty string when no args are left 2021-04-03 17:16:15 -07:00
Ethan P e4e916c63b
test: Update snapshots 2021-04-02 16:32:54 -07:00
Ethan P 27c68692f1
batwatch: Show the watched command in bat's header 2021-04-02 16:32:02 -07:00
Ethan P 70c7509343
batwatch: Disable bat's pager (#23) 2021-04-02 16:26:45 -07:00
Ethan P 164142dd43
batwatch: Add mode for watching command output 2021-04-02 16:17:07 -07:00
Ethan P e16e05d1d4
lib: Add 'term_clear' function to clear the terminal screen 2021-04-02 16:09:24 -07:00
Ethan P 93e2e4d048
lib: Fix 'getargs --append' adding an empty value to the array 2021-04-02 16:09:10 -07:00
Ethan P 47152d69c3
lib: Add '--append' mode to 'getargs' 2021-04-02 15:43:45 -07:00
Ethan P 9efc5362df
lib: Add 'getargs' to 'lib_opt' 2021-04-02 15:38:26 -07:00
Ethan P 87525fca91
docs: Update README for Homebrew installation split 2021-03-30 21:31:57 -07:00
Ethan P c538eadf77
Bump version to 2021.03.30 2021-03-30 20:41:30 -07:00
Ethan P bd7cb3ea9e
build: Allow for only specific scripts to be built 2021-03-30 20:38:37 -07:00
Ethan P 0cefe6efe1
ci: Prevent fish from creating tempfiles 2021-03-30 20:24:30 -07:00
Ethan P fa311d5119
ci: Only build manuals for the build job 2021-03-30 19:57:01 -07:00
Ethan P 5f88acd1f4
build: Allow manuals to be disabled with --manuals=false 2021-03-30 19:54:06 -07:00
Ethan P cfd8d29c7b
ci: Add fish for test packages (used by batpipe tests) 2021-03-30 19:45:10 -07:00
Ethan P e98b4efb51
ci: Update python2 to python3 2021-03-30 19:44:43 -07:00
Ethan P dccceaf749
test: Add tests for batpipe 2021-03-30 19:26:08 -07:00
Ethan P 5f5a5e87a9
batpipe: Highlight unsupported files using bat 2021-03-30 19:25:51 -07:00
Ethan P af9709452f
build: Recommend './test.sh --failed' instead 2021-03-30 19:13:03 -07:00
Ethan P 08455b0cb8
Update test framework 2021-03-30 19:00:37 -07:00
Ethan P 8c11b24bef
Update test framework 2021-03-30 18:53:07 -07:00
Ethan P 5708706436
batpipe: Fix error when external viewers folder exists 2021-03-30 18:51:34 -07:00
Ethan P 2b2db2b506
batpipe: Add support for exa 2021-03-24 20:23:58 -07:00
Ethan P d429af07ed
docs: Update README to mention batpipe 2021-03-24 20:18:16 -07:00
Ethan P 98e1f2f054
Bump version to 2021.03.24 2021-03-24 20:15:29 -07:00
Ethan P dafd2addc0
batpipe: Add support for *.xz files 2021-03-24 20:13:27 -07:00
Ethan P 0d2e999b2a
batpipe: Fix viewing listings in zip files 2021-03-24 20:09:04 -07:00
Ethan P 988780bce9
batpipe: Add support for *.gz files 2021-03-24 20:08:16 -07:00
Ethan P 9072ab47a1
batpipe: Change viewer_*_supports arg order 2021-03-24 20:04:33 -07:00
Ethan P 1734cedbd9
batpipe: Add zip viewer 2021-03-24 20:01:12 -07:00
Ethan P 2a28cbff3a
batpipe: Improve batpipe when used both inside and outside less 2021-03-24 19:59:35 -07:00
Ethan P fa32c5875d
batpipe: Fix incorrect detection of inner paths 2021-03-24 19:49:28 -07:00
Ethan P 55eaf189dc
batpipe: Add better support for less 2021-03-24 19:34:41 -07:00
Ethan P c5e7f5f185
docs: Add docs for batpipe 2021-03-24 19:00:35 -07:00
Ethan P 652c3e5c3b
mdroff: Support h3-h5 2021-03-24 18:59:21 -07:00
Ethan P b85c7c43e3
batpipe: Add batpipe script 2021-03-24 18:35:56 -07:00
Ethan P dd07f98367
lib: Add NO_COLOR support 2021-03-24 17:08:51 -07:00
Ethan P d8e7cd1d1b
lib: Update copyright year to 2021 2021-03-24 15:59:43 -07:00
Ethan P b275282e36
prettybat: Add checks to ensure that files exist 2020-12-16 17:15:35 -08:00
Jeremy Paige 165fc69d34 Add python formatter black to prettybat 2020-12-16 16:57:59 -08:00
Ethan P eb69d61ff8
developer: Fix build.sh install failing when no PREFIX/bin dir 2020-12-03 04:40:34 -08:00
Ethan P 0d9468e1cf
developer: Update test framework 2020-10-29 01:35:47 -07:00
Ethan P 7549ffd986
batgrep: Clean up handling of RIPGREP_CONFIG_PATH 2020-10-29 01:28:26 -07:00
Ethan P 65a2f7561e
lib: Update option parser to better support short options 2020-10-29 01:24:32 -07:00
Ethan P bb94b33353
lib: Improve tests and docs for opt lib 2020-10-28 20:04:08 -07:00
Ethan P 0f6529a0d8
batdiff: Make --all default behavior for no arguments 2020-10-28 19:40:35 -07:00
Ethan P 4761b594a6
developer: Fix bat reading data from stdin during tests 2020-10-28 19:32:24 -07:00
Ethan P a6a1e55522
batgrep: Remove hardcoded reference to ripgrep executable 2020-10-28 19:19:51 -07:00
Ethan P 40a66d934d
batgrep: Add test for searching from stdin contents (#42) 2020-10-28 19:19:46 -07:00
Ethan P de60ea0c10
developer: Update test framework 2020-10-28 19:05:00 -07:00
Ethan P b5cb20cb23
batgrep: Allow searching through STDIN (#42) 2020-10-28 18:10:17 -07:00
Ethan P 0df5cffb84
mdroff: Fix warning that appears in tables 2020-10-04 12:15:27 -07:00
Ethan P 6b6016dad4
developer: Bump version to 2020.10.05 2020-10-04 11:58:56 -07:00
Ethan P 01a6e9cf15
developer: Include manpages in release archive 2020-10-04 11:58:44 -07:00
Ethan P 7d2d861d7d
build: Build manuals by default 2020-10-04 11:51:45 -07:00
Ethan P 5fc43a2d8c
docs: Convert usage section into code block to work around mdroff 2020-10-04 11:51:02 -07:00
Ethan P 644c817ba2
mdroff: Trim lines before emitting them 2020-10-04 11:50:29 -07:00
Ethan P 655038878b
mdroff: Add very simple support for inline code blocks 2020-10-04 11:47:41 -07:00
Ethan P cc8b70055e
mdroff: Add proper nroff table support 2020-10-04 11:26:59 -07:00
Ethan P 9d356b8bc4
Bump version to 2020.10.04 2020-10-04 00:58:28 -07:00
Ethan P 3c5651bde9
release.sh: Stop packages from inlining alternate executable names 2020-10-04 00:57:57 -07:00
Ethan P 1f7d9c3fcc
build.sh: Add option to prevent full executable paths from inlining 2020-10-04 00:56:01 -07:00
Ethan P da24c59751
release.sh: Fix error and invalid alternate executable in release.sh 2020-10-04 00:41:14 -07:00
Ethan P 1fff0bcc51
lib: Add DEFAULT color to printc 2020-10-04 00:40:05 -07:00
Ethan P 5f5023d056
batgrep: Add simple parsing of RIPGREP_CONFIG_PATH. 2020-10-02 20:09:00 -07:00
Ethan P dc50f28480
batgrep: Prevent RIPGREP_CONFIG_PATH context from breaking batgrep. 2020-10-02 19:58:41 -07:00
Ethan P 0494e41e66
Update prettybat caveats for header display fix (#39) 2020-10-02 19:50:48 -07:00
Ethan P fa58888298
bat-modules: Add --modules-file option 2020-07-06 18:15:01 -07:00
Ethan P df5b237273
batgrep: Add short flag number parsing for context options 2020-07-06 18:03:47 -07:00
Ethan P e4999a7ffb
lib: Add option parsing for short flags followed by number 2020-07-06 18:03:30 -07:00
Ethan P 38f56c1bc7
batwatch: Split hook_help into a lib script 2020-07-06 17:49:29 -07:00
Ethan P 69d9c38326
batwatch: small formatting changes 2020-07-06 17:43:37 -07:00
Alissa Huskey 990a1dc397 Minor refactor
- Use read to split on space between name and flags instead of paramater
  expansion.
2020-07-06 17:36:03 -07:00
Alissa Huskey 57be774f88 Fix gnu stat flag
- fix gnu stat formatting option from %z to %Z
- use read with a one second timeout instead of sleep, then quit if user
  responds with "q" (for testing, mostly)
- call pager_exec on entr and bat line instead of main so that --clear
  works
2020-07-06 17:36:03 -07:00
Alissa Huskey 4c09718a46 batwatch changes to reveal a bug and add --help
- Add `-h` and `--help` flags to print out basic usage.
- Refactor determine_stat_variant so that it checks to make sure the
value from `stat` is a epoch timestamp, and exit noisily if it is not.
- Change `determine_watcher` to set the `OPT_WATCHER` global directory
directly instead of echoing it then being called from a subshell. There
was an errror that the subshell was obscuring.
- Add a test to reflect the bug.
2020-07-06 17:36:03 -07:00
Ethan P 149f47fa66
Fix typo 2020-07-01 18:23:40 -07:00
Ethan P 6052d6ee17
batdiff/lib: Fix hardcoded git executable name (#35)
Thanks to @lilyball for pointing it out.
2020-06-17 17:53:20 -07:00
Ethan P 3029b6749f
ci: Build manuals 2020-05-15 21:13:15 -07:00
Ethan P da93f2f87f
doc: Clean up documentation further 2020-05-15 21:08:43 -07:00
Ethan P 9f6e5c0164
build: Add experimental support for generating manpages 2020-05-15 21:05:46 -07:00
Ethan P 85c4cfbfe8
doc: Clean up command documentation 2020-05-15 20:53:59 -07:00
Ethan P 5fbf8adf41
build: Avoid unnecessary reprinting when verifying tests 2020-05-14 00:34:18 -07:00
Ethan P 43ddb984f5
build: Print failed tests to STDERR instead of STDOUT 2020-05-12 21:54:55 -07:00
Ethan P f20a3ff9b8
build: Use detected system bat executable
You no longer have to specify the executable as batcat on Debian
or Ubuntu systems where the executable was renamed from bat.
2020-05-12 02:36:48 -07:00
Ethan P c9f3125002
*: Automatically use 'batcat' when 'bat' is missing 2020-05-12 02:31:41 -07:00
Ethan P 840d339c97
build: Use STDOUT and STDERR appropriately 2020-05-12 02:21:38 -07:00
Ethan P e250ad9b29
test: Add --no-config to bat shimexec 2020-05-06 18:57:04 -07:00
Ethan P f9b18f7e8e
test: Fix bat shimexec 2020-05-06 18:56:27 -07:00
Ethan P 875504ba06
test: Update test framework 2020-05-06 18:52:53 -07:00
Ethan P 529eb60498
build: Fix 'will_minify none' not working, leading to unnecessary warning (#30) 2020-05-03 18:28:07 -07:00
Ethan P a3c4fa1014
build: Print shfmt error to stderr instead of stdout (#29) 2020-05-03 18:24:51 -07:00
Ethan P 5af706fc7c
test: Update test framework 2020-05-02 17:16:50 -07:00
Ethan P 63101805be
lib: Fix term_width to handle cases where stty size returns '0 0' 2020-05-02 17:11:30 -07:00
Ethan P c98e21df04
lib: Reduce size of term_width function 2020-05-02 17:03:53 -07:00
Ethan P 427baf1491
lib: Add term.sh to more accurately get terminal width 2020-05-02 16:58:17 -07:00
Ethan P bdc2e6ea01
test: Update test framework 2020-05-02 16:28:38 -07:00
Ethan P e7a394a154
lib: Use 'awk' instead of '/usr/bin/awk' 2020-05-02 14:58:33 -07:00
Ethan P d6db998042
developer: Bump version 2020-05-02 14:57:59 -07:00
Ethan P 360c5b819c
test: Update test framework to fix on Bash 3.2 2020-05-02 14:54:04 -07:00
Ethan P fb9730001f
docs: Change markdown that may be parsed incorrectly 2020-05-02 01:32:00 -07:00
Ethan P f9bdcc9494
test: Update test framework 2020-05-01 23:59:31 -07:00
Ethan P 20a0058014
lib: Fix issue when pagers read an empty argument as a file 2020-05-01 23:03:59 -07:00
Ethan P d29915f141
Revert "lib: Remove unused function in pager lib"
This reverts commit 309a2169ba.
2020-05-01 22:59:09 -07:00
Ethan P d5fa65132d
lib: Remove opt_hooks.sh and edit scripts to source lib files directly 2020-04-30 22:11:36 -07:00
Ethan P 50e9cd8caa
developer: Update build.sh to remove redundant copyright comments 2020-04-30 22:06:11 -07:00
Ethan P 275c66b213
developer: Fix build.sh to inline EXECUTABLE_DELTA constant 2020-04-30 21:57:54 -07:00
Ethan P 29fb251348
test: Update tests to shim stty instead of tput
Since we removed a dependency on tput, it makes more sense to shim the
command that we replaced it with.
2020-04-30 21:47:59 -07:00
Ethan P 7dc1c509fb
developer: Update release script for improved release description 2020-04-30 21:37:08 -07:00
Ethan P 19f49bb5ee
developer: Update release script 2020-04-30 21:07:28 -07:00
Ethan P 57b129e1f7
Bump version to 2020.04.30 2020-04-30 20:44:53 -07:00
Ethan P 820eae1e9d
test: Update test framework 2020-04-30 20:42:23 -07:00
Ethan P 309a2169ba
lib: Remove unused function in pager lib 2020-04-30 20:37:17 -07:00
Ethan P e4d790ca91
lib: Fix regression in pager library 2020-04-30 20:37:01 -07:00
Ethan P 4ee7451532
batman: Forward paging options to bat instead of handling them 2020-04-30 20:21:08 -07:00
Ethan P 9f983891fd
lib: Fix behavior of PAGER environment var (#24) 2020-04-30 20:13:03 -07:00
Ethan P bad5ab13a2
docs: Update README to mention that compiled scripts are faster 2020-04-27 19:10:22 -07:00
Ethan P d81456872b
test: Add 'perf' suite to find out which libraries cost the most startup time 2020-04-27 19:08:06 -07:00
Ethan P a31b322272
lib: Add include guard to constants.sh to lower startup time 2020-04-27 19:05:42 -07:00
Ethan P 8524f873fb
docs: Fix README pointing to the wrong document for batdiff 2020-04-27 18:52:57 -07:00
Ethan P 2cc263f85c build: Use parallel test execution to speed up verification time 2020-04-27 18:51:36 -07:00
Ethan P b0a2ca70ba test: Fix argument values given to test.sh not being passed to Best 2020-04-27 18:51:36 -07:00
Ethan P 7a75c55a34 ci: Update CI config.yml to install SSH along with git 2020-04-27 18:51:36 -07:00
Ethan P 76afe39c96 test: Generate snapshot for batdiff 2020-04-27 18:51:36 -07:00
Ethan P 7754406d54 *: Fix regression introduced by opt_hook_width on non-tty output 2020-04-27 18:51:36 -07:00
Ethan P af98cf5246 test: Update testing framework
Now we can test in parallel :)
2020-04-27 18:51:36 -07:00
Ethan P 91860968f0 docs: Update docs for batgrep 2020-04-27 18:51:36 -07:00
Ethan P b2bc772ad1 batgrep/batwatch: Update to use terminal width opt hook instead of tput cols 2020-04-27 18:51:36 -07:00
Ethan P ab0f1b96d6 batdiff: Add batdiff script 2020-04-27 18:51:36 -07:00
Ethan P 0d73f2abd5
prettybat: Add support for GraphQL via Prettier 2020-04-27 02:14:34 -07:00
Ethan P 865c8b252b
prettybat: Add support for bat 0.14.0 --file-name option 2020-04-27 02:08:34 -07:00
Ethan P e8baf911d1
docs: Add CONTRIBUTING.md 2020-04-27 01:45:14 -07:00
Ethan P e79e747e71
test: Avoid using 'head' 2020-04-27 01:22:22 -07:00
Ethan P 7fa1faf7bf
bat-modules: Add working but work-in-progress code 2020-04-17 02:40:52 -07:00
Ethan P 9a92579100
test: Add suite for DSL parsing library 2020-04-17 02:25:33 -07:00
Ethan P c5927f72af
bat-modules: Start work on bat-modules script 2020-04-17 01:58:59 -07:00
Ethan P 9c489732e6
lib: Add 'dsl.sh' library for parsing simple DSL 2020-04-17 01:12:11 -07:00
Ethan P 47cf670943
developer: Add copyright/info banner to compressed scripts 2020-04-16 23:41:06 -07:00
80 changed files with 5026 additions and 542 deletions

View file

@ -1,98 +0,0 @@
version: 2.1
executors:
linux_alpine:
docker:
- image: alpine:latest
environment:
TERM: xterm
commands:
setup:
description: "Set up the environment needed to test and run the scripts."
steps:
- run:
name: "APK: Add repository."
command: |
printf "\n%s\n" "http://nl.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories
printf "\n%s\n" "http://nl.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories
- run:
name: "APK: Update cache."
command: apk update
- run:
name: "Setup: Install build packages."
command: apk add git bash python2 diffutils ca-certificates curl shfmt
- run:
name: "Setup: Install test packages."
command: apk add util-linux coreutils
- run:
name: "Setup: Install runtime packages."
command: apk add bat ripgrep ncurses
build:
description: "Build the self-contained scripts."
parameters:
minify:
type: string
default: "lib"
steps:
- run:
name: "Build"
command: ./build.sh --minify=<<parameters.minify>> --no-verify
jobs:
build:
executor: linux_alpine
steps:
- setup
- checkout
- build:
minify: "all"
- store_artifacts:
path: bin
test:
executor: linux_alpine
steps:
- setup
- checkout
- build:
minify: "all"
- run:
name: "Test: Unit Tests / Snapshots"
command: ./test.sh --verbose --strict --snapshot:show
test-consistency:
executor: linux_alpine
steps:
- setup
- checkout
- build:
minify: "all"
- run:
name: "Test: Consistency"
command: ./test.sh --compiled --verbose --snapshot:show
test-symlink:
executor: linux_alpine
steps:
- setup
- checkout
- run:
name: "Symlink"
command: ln -s "$PWD/src/batgrep.sh" /tmp/batgrep
- run:
name: "Test: Symlink"
command: /tmp/batgrep 'a' >/dev/null
workflows:
version: 2
default:
jobs:
- build
- test:
requires: [build]
- test-consistency:
requires: [build, test]
- test-symlink:
requires: [build]

54
.github/actions/build/action.yaml vendored Normal file
View file

@ -0,0 +1,54 @@
name: 'Build'
description: 'Builds bat-extras'
inputs:
manuals:
description: 'Builds manuals'
required: false
default: true
inline:
description: 'Inlines executable names in script'
required: false
default: true
verify:
description: 'Verifies after building'
required: false
default: false
minify:
description: 'Minification mode (none, all, lib)'
required: false
default: 'none'
runs:
using: composite
steps:
- name: Run build script
shell: bash
run: |
args=(
--banner
--minify="${{ inputs.minify }}"
)
if "${{ inputs.manuals }}"; then
args+=(--manuals)
else
args+=(--no-manuals)
fi
if "${{ inputs.verify }}"; then
args+=(--verify)
else
args+=(--no-verify)
fi
if "${{ inputs.inline }}"; then
args+=(--inline)
else
args+=(--no-inline)
fi
# Run the build script.
cd "${{ github.workspace }}"
PATH="${{ runner.temp }}/bin:${PATH}"
bash "${{ github.workspace }}/build.sh" "${args[@]}"

View file

@ -0,0 +1,31 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2019-2023 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$GITHUB_WORKSPACE"
# -----------------------------------------------------------------------------
# Overrides from release.sh:
# -----------------------------------------------------------------------------
batextras:get_git_workspace() {
printf "%s\n" "${GITHUB_WORKSPACE}"
}
batextras:get_current_commit() {
printf "%s\n" "${GITHUB_SHA:-$(git -C "$GITHUB_WORKSPACE" rev-parse HEAD)}"
}
# -----------------------------------------------------------------------------
# Generate changelog:
# -----------------------------------------------------------------------------
set -euo pipefail
source "${PROJECT_DIR}/release.sh"
batextras:generate_release_notes \
"$(batextras:get_previous_tag_commit)" \
"$(batextras:get_current_commit)"

31
.github/actions/build/create-zipball.sh vendored Normal file
View file

@ -0,0 +1,31 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2019-2023 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$GITHUB_WORKSPACE"
# -----------------------------------------------------------------------------
# Overrides from release.sh:
# -----------------------------------------------------------------------------
batextras:get_git_workspace() {
printf "%s\n" "${GITHUB_WORKSPACE}"
}
batextras:get_current_commit() {
printf "%s\n" "${GITHUB_SHA:-$(git -C "$GITHUB_WORKSPACE" rev-parse HEAD)}"
}
# -----------------------------------------------------------------------------
# Generate changelog:
# -----------------------------------------------------------------------------
set -euo pipefail
source "${PROJECT_DIR}/release.sh"
zipball_name="bat-extras-$(batextras:get_version | sed 's/\.//')"
zipball="${PROJECT_DIR}/${zipball_name}.zip"
batextras:create_package "$zipball"

View file

@ -0,0 +1,92 @@
name: 'Install dependencies'
description: 'Installs all the dependencies needed.'
inputs:
build:
description: 'Install build dependencies.'
required: false
default: true
test:
description: 'Install test dependencies.'
required: true
default: false
version_bat:
description: 'The version of bat to install.'
required: false
default: "latest"
version_ripgrep:
description: 'The version of ripgrep to install.'
required: false
default: "latest"
runs:
using: "composite"
steps:
- name: Create directories
shell: bash
run: |
test -d "${RUNNER_TEMP}/bin" || mkdir -p "${RUNNER_TEMP}/bin"
test -d "${RUNNER_TEMP}/dl" || mkdir -p "${RUNNER_TEMP}/dl"
- name: Install shfmt (build dependency)
shell: bash
if: ${{ env.ACT || inputs.build == 'true' }}
run: |
curl \
--silent \
--location \
--output "${RUNNER_TEMP}/bin/shfmt" \
"https://github.com/patrickvane/shfmt/releases/download/master/shfmt_linux_amd64"
chmod +x "${RUNNER_TEMP}/bin/shfmt"
- name: Download bat (test dependency)
uses: dsaltares/fetch-gh-release-asset@master
if: ${{ env.ACT || inputs.test == 'true' }}
with:
file: "bat-v[0-9\\.]+-x86_64-unknown-linux-gnu.tar.gz"
repo: "sharkdp/bat"
# version: "${{ inputs.version_bat || "latest" }}"
regex: true
target: ".dl/"
- name: Install bat
shell: bash
if: ${{ env.ACT || inputs.test == 'true' }}
run: |
tar -xf \
"${{ github.workspace }}/.dl"/bat-*.tar.* \
-C "${{ runner.temp }}/dl/"
find "${{ runner.temp }}/dl" \
-type f -iname "bat" \
-exec mv {} "${RUNNER_TEMP}/bin/" \;
- name: Download ripgrep (test dependency)
uses: dsaltares/fetch-gh-release-asset@master
if: ${{ env.ACT || inputs.test == 'true' }}
with:
file: "ripgrep_[0-9\\.-]+_amd64.deb"
repo: "BurntSushi/ripgrep"
# version: "${{ inputs.version_ripgrep || "latest" }}"
regex: true
target: ".dl/"
- name: Install ripgrep
shell: bash
if: ${{ env.ACT || inputs.test == 'true' }}
run: |
dpkg-deb -x \
"${{ github.workspace }}/.dl"/ripgrep_*.deb \
"${{ runner.temp }}/dl/"
find "${{ runner.temp }}/dl" \
-type f -path "*/bin/rg" \
-exec mv {} "${{ runner.temp }}/bin/" \;
- name: Set executable permissions
shell: bash
run: |
chmod -R +x "${{ runner.temp }}/bin"

33
.github/actions/test/action.yaml vendored Normal file
View file

@ -0,0 +1,33 @@
name: 'Test'
description: 'Tests bat-extras'
inputs:
strict:
description: 'Tests should be run under strict mode'
required: false
default: false
compiled:
description: 'Test scripts that have been built'
required: false
default: false
runs:
using: composite
steps:
- name: Run tests
shell: bash
run: |
args=()
if "${{ inputs.compiled }}"; then
args+=(--compiled)
fi
if "${{ inputs.strict }}"; then
args+=(--strict)
fi
cd "${{ github.workspace }}"
PATH="${{ runner.temp }}/bin:${PATH}"
bash "${{ github.workspace }}/test.sh" "${args[@]}" \
--verbose --snapshot:show

45
.github/workflows/release.yaml vendored Normal file
View file

@ -0,0 +1,45 @@
name: Release
on:
push:
tags: ['*']
jobs:
"Release":
runs-on: ubuntu-latest
steps:
- name: Check out sources
uses: actions/checkout@v3
- name: Install build dependencies
uses: ./.github/actions/install-dependencies
with:
build: true
test: true
- name: Build
uses: ./.github/actions/build
with:
minify: lib
manuals: true
verify: true
inline: false
- name: Create zipball
run: bash "${{ github.workspace }}/.github/actions/build/create-zipball.sh"
- name: Generate changelog
id: changelog
run: |
output_file="release-notes.md"
output_title="Release: $(date '+%Y-%m-%d')"
bash "${{ github.workspace }}/.github/actions/build/create-release-notes.sh" | tee "${output_file}"
echo "file=${output_file}" >> "$GITHUB_OUTPUT"
echo "title=${output_title}" >> "$GITHUB_OUTPUT"
- name: Create release
if: ${{ !env.ACT }}
uses: ncipollo/release-action@v1
with:
artifacts: "bat-extras-*.zip"
bodyFile: "${{steps.changelog.outputs.file}}"
name: "${{steps.changelog.outputs.title}}"

96
.github/workflows/test.yaml vendored Normal file
View file

@ -0,0 +1,96 @@
name: Test
on:
push: {}
pull_request:
types: [opened, synchronize]
jobs:
"Build":
runs-on: ubuntu-latest
steps:
- name: Check out sources
uses: actions/checkout@v3
- name: Install build dependencies
uses: ./.github/actions/install-dependencies
with:
build: true
test: false
- name: Build artifacts
uses: ./.github/actions/build
with:
minify: lib
manuals: true
verify: false
inline: false
- name: Upload artifacts
uses: actions/upload-artifact@v3
if: ${{ !env.ACT && !failure() }}
with:
path: |
${{ github.workspace }}/bin/
${{ github.workspace }}/doc/
"Test":
runs-on: ubuntu-latest
steps:
- name: Check out sources
uses: actions/checkout@v3
- name: Install test dependencies
uses: ./.github/actions/install-dependencies
with:
build: false
test: true
- name: Test scripts
uses: ./.github/actions/test
"Test_Consistency":
runs-on: ubuntu-latest
needs: "Test"
steps:
- name: Check out sources
uses: actions/checkout@v3
- name: Install dependencies
uses: ./.github/actions/install-dependencies
with:
build: true
test: true
- name: Build scripts
uses: ./.github/actions/build
with:
minify: lib
manuals: false
verify: false
inline: false
- name: Test built scripts
uses: ./.github/actions/test
with:
compiled: true
"Test_Symlinks":
runs-on: ubuntu-latest
needs: "Test"
env:
BAT_PAGER: 'cat'
steps:
- name: Check out sources
uses: actions/checkout@v3
- name: Install dependencies
uses: ./.github/actions/install-dependencies
with:
build: false
test: true
- name: Prepare symlinks
run: |
chmod +x "${{ github.workspace }}/src/batgrep.sh"
ln -s "${{ github.workspace }}/src/batgrep.sh" "${{ runner.temp }}/absolute-batgrep"
(cd "${{ github.workspace }}" && ln -s "src/batgrep.sh" relative-batgrep)
- name: Test absolute symlink
run: |
PATH="${{ runner.temp }}/bin:${PATH}"
"${{ runner.temp }}/absolute-batgrep" 'a' <<< 'abc'
- name: Test relative symlink
run: |
PATH="${{ runner.temp }}/bin:${PATH}"
"${{ github.workspace }}/relative-batgrep" 'a' <<< 'abc'

9
.gitignore vendored
View file

@ -4,9 +4,14 @@
# Developer
.idea
.vscode
*.iml
# Project
/.circleci/.config.yml
.download
bin
/bin
/man
# Side-Effects
/test/data/.config/fish
/test/data/.local/share/fish

@ -1 +1 @@
Subproject commit f66c4e03b4577317aec6a739c42890334aefe6b9
Subproject commit 6b97e0a531f77d2e1f10f48ebb68d4033d69e04d

18
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,18 @@
# Contributing
Are you interested in contributing to `bat-extras`? That's great!
All we ask is that you keep a few things in mind:
## DOs:
- Use Bashisms whenever possible (unless it would make the code unclear). These are scripts written specifically for the `bash` shell, and they should take advantage of built-in functionality instead of spawning external processes.
- Use `awk` or `sed` when it would be faster or less verbose than only using `bash` builtins.
## DON'Ts:
- Include crude or offensive language inside issues or scripts.
- Use GNU/BSD-only features in external programs like `sed` or `awk`.
- Use `bash` features that require a Bash version newer than 3.2.
- Use `head` in a pipe. See [Issue #19](https://github.com/eth-p/bat-extras/issues/19) for more details.
- Use any external program that isn't likely to come installed by default (e.g. avoid `perl` and `python`).
- Automatically reformat scripts unless it's a pull request specifically intended for reformatting. The formatting style that we use differs from what `shfmt` and other shell script formatters will emit.

View file

@ -18,6 +18,12 @@ Read system manual pages (`man`) using `bat` as the manual page formatter.
### [`batpipe`](doc/batpipe.md)
A `less` (and soon `bat`) preprocessor for viewing more types of files in the terminal.
### [`batwatch`](doc/batwatch.md)
Watch for changes in one or more files, and print them with `bat`.
@ -25,6 +31,12 @@ Watch for changes in one or more files, and print them with `bat`.
### [`batdiff`](doc/batdiff.md)
Diff a file against the current git index, or display the diff between two files.
<u>Requirements:</u> `bat`, `delta` (optional)
### [`prettybat`](doc/prettybat.md)
Pretty-print source code and highlight it with `bat`.
@ -34,16 +46,67 @@ Pretty-print source code and highlight it with `bat`.
## Installation via Package Manager
**Homebrew:** `brew install eth-p/software/bat-extras`
### Homebrew
All of the `bat-extras` scripts can be installed with `brew install bat-extras`.
If you would prefer to only install the specific scripts you need, you can use the `eth-p/software` tap to install individual scripts: `brew install eth-p/software/bat-extras-[SCRIPT]`
### MacPorts
The `bat-extras` scripts can also be installed via [MacPorts](https://www.macports.org) on macOS:
```bash
sudo port install bat-extras
```
Port info [here](https://ports.macports.org/port/bat-extras/).
### Pacman
`bat-extras` is [officially available](https://archlinux.org/packages/extra/any/bat-extras/) on the Arch extra repository!
If you have the extra repository enabled, you can install `bat-extras` by running:
```bash
pacman -S bat-extras
```
### Gentoo
`bat-extras` is available on **Gentoo's Guru Overlay** as `sys-apps/bat-extras`.
To install, first make sure you've added the [Gentoo Guru Overlay](https://wiki.gentoo.org/wiki/Project:GURU) to your local repositories, then emerge accordingly...
```bash
emerge sys-apps/bat-extras
```
### Fedora (Unofficial)
`bat-extras` is available in an unofficial Fedora Copr
[repository](https://copr.fedorainfracloud.org/coprs/awood/bat-extras/).
**Note**: this package does not contain `prettybat` since `prettier` is not yet
packaged for Fedora.
Install the Copr plugin, enable the repository, and then install the package
by running:
```bash
dnf install dnf-plugins-core
dnf copr enable awood/bat-extras
dnf install bat-extras
```
&nbsp;
## Installation (![CircleCI](https://circleci.com/gh/eth-p/bat-extras.svg?style=svg))
## Installation
[![Test](https://github.com/eth-p/bat-extras/actions/workflows/test.yaml/badge.svg)](https://github.com/eth-p/bat-extras/actions/workflows/test.yaml)
The scripts in this repository are designed to run as-is, provided that they aren't moved around.
This means that you're free to just symlink `src/[script].sh` to your local bin folder.
If you would rather have self-contained scripts that you can place and run anywhere, you can use the `build.sh` script to create (and optionally install) them.
If you would rather have *faster*, self-contained scripts that you can place and run anywhere, you can use the `build.sh` script to create (and optionally install) them.
&nbsp;
@ -80,13 +143,27 @@ If you only want to install a single script, you can run the build process and c
**Manuals:**
The build script will automatically generate a `man` page for each of the markdown documentation files.
This is a beta feature that uses a non-compliant Markdown "parser" written in Bash, and there is no guarantee towards the quality of the generated manual pages. If you do not want to generate manual files, you can provide the `--no-manuals` option to disable manual file generation.
**Alternate Executable:**
Depending on the distribution, bat may have been renamed to avoid package conflicts.
If you wish to use these scripts on a distribution where this is the case, there is an `--alternate-executable=NAME` option which will build the scripts to use an alternate executable name.
You may also specify alternate executables for `ripgrep`, `delta`, `fzf`, or `git` with `--alternate-executable:PROGRAM NAME` where `PROGRAM` is one the aforementioned programs. Note that doing so may cause verification to fail.
**Verification:**
The build script will attempt to verify the correctness of the "bin" scripts by comparing their output with their source counterparts. It is recommended to let it do this, but you can disable verification with the `--no-verify` option.
&nbsp;
## Contributing
If you would like to contribute to `bat-extras`, please feel free to [open an issue on GitHub](https://github.com/eth-p/bat-extras/issues), or make a pull request. If you do the latter, please keep our [contributing guidelines](./CONTRIBUTING.md) in mind.

View file

@ -1,10 +1,6 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2019 eth-p | MIT License
# bat-extras | Copyright (C) 2020-2024 eth-p and contributors | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
source "${LIB}/opt_hook_color.sh"
source "${LIB}/opt_hook_pager.sh"
source "${LIB}/opt_hook_version.sh"

434
build.sh
View file

@ -1,19 +1,122 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2019 eth-p | MIT License
# bat-extras | Copyright (C) 2019-2023 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BIN="$HERE/bin"
SRC="$HERE/src"
# -----------------------------------------------------------------------------
# Build-as-a-Library Functions:
# -----------------------------------------------------------------------------
# Redefines a function to print a constant string whenever called.
# This is used for lazy-loading of some getter functions.
#
# Arguments:
# 1 -- The function name.
# 2 -- The constant string to print.
#
# Output:
# The constant string.
batextras:lazy_done() {
eval "$(printf "%s() { printf \"%%s\n\" %q; }" "$1" "$2")"
"$1"
}
# Checks to see if a function is defined.
# Arguments:
# 1 -- The function name.
# If prefixed with "::", it will use "batextras:" as a namespace.
batextras:is_function_defined() {
local name="$1"
if [[ "${name:0:2}" = "::" ]]; then name="batextras:${name:2}"; fi
[[ "$(type -t "$name" || echo 'undefined')" = "function" ]]
return $?
}
# Prints the path to the project directory.
if ! batextras:is_function_defined ::get_project_directory; then
batextras:get_project_directory() {
batextras:lazy_done "${FUNCNAME[0]}" \
"$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
}
fi
# Prints the path to the project source directory.
if ! batextras:is_function_defined ::get_source_directory; then
batextras:get_source_directory() {
batextras:lazy_done "${FUNCNAME[0]}" \
"$(batextras:get_project_directory)/src"
}
fi
# Prints the path to the project output directory for executables.
if ! batextras:is_function_defined ::get_output_bin_directory; then
batextras:get_output_bin_directory() {
batextras:lazy_done "${FUNCNAME[0]}" \
"$(batextras:get_project_directory)/bin"
}
fi
# Prints the path to the project output directory for manuals.
if ! batextras:is_function_defined ::get_output_man_directory; then
batextras:get_output_man_directory() {
batextras:lazy_done "${FUNCNAME[0]}" \
"$(batextras:get_project_directory)/man"
}
fi
# Prints the path to the project directory for documentation.
if ! batextras:is_function_defined ::get_docs_directory; then
batextras:get_docs_directory() {
batextras:lazy_done "${FUNCNAME[0]}" \
"$(batextras:get_project_directory)/doc"
}
fi
# Prints the declared version (in version.txt).
if ! batextras:is_function_defined ::get_version; then
batextras:get_version() {
batextras:lazy_done "${FUNCNAME[0]}" \
"$(cat "$(batextras:get_project_directory)/version.txt")"
}
fi
# Prints the paths for all source scripts in this project.
#
# Output:
# One line for each script with the full path to the script.
if ! batextras:is_function_defined ::get_source_paths; then
batextras:get_source_paths() {
for file in "$(batextras:get_source_directory)"/*.sh; do
printf "%s\n" "$file"
file_bin="$(basename -- "$file" ".sh")"
done
}
fi
# -----------------------------------------------------------------------------
# Main:
# Only run everything past this point if the script is not sourced.
# -----------------------------------------------------------------------------
(return 0 2>/dev/null) && return 0
HERE="$(batextras:get_project_directory)"
SRC="$(batextras:get_source_directory)"
BIN="$(batextras:get_output_bin_directory)"
MAN="$(batextras:get_output_man_directory)"
MAN_SRC="$(batextras:get_docs_directory)"
LIB="$HERE/lib"
source "${LIB}/print.sh"
source "${LIB}/opt.sh"
source "${LIB}/constants.sh"
source "${HERE}/mdroff.sh"
# -----------------------------------------------------------------------------
set -eo pipefail
exec 3>&1
# Runs the next build step.
#
@ -34,32 +137,101 @@ next() {
# Prints a build step message.
smsg() {
case "$2" in
"SKIP") printc " %{YELLOW} %{DIM}%s [skipped]%{CLEAR}\n" "$1" 1>&2 ;;
*) printc " %{YELLOW} %s...%{CLEAR}\n" "$1" 1>&2 ;;
"SKIP") printc_msg " %{YELLOW} %{DIM}%s [skipped]%{CLEAR}\n" "$1" ;;
*) printc_msg " %{YELLOW} %s...%{CLEAR}\n" "$1" ;;
esac
}
# Prints a message to STDOUT (via FD 3).
# Works the same as printc.
printc_msg() {
printc "$@" 1>&3
}
# Prints a message to STDERR.
# Works the same as printc.
printc_err() {
printc "$@" 1>&2
}
# Escapes a sed pattern.
# Arguments:
# 1 -- The pattern.
#
# Output:
# The escaped string.
sed_escape() {
sed 's/\([][\\\/\^\$\*\.\-]\)/\\\1/g' <<< "$1"
}
# Checks if the output scripts will be minified.
# Arguments:
# 1 "all" -- All scripts will be minified.
# "any" -- Any scripts will be minified.
# "lib" -- Library scripts will be minified.
# "unsafe" -- Unsafe minifications will be applied.
will_minify() {
case "$1" in
all)
[[ "$OPT_MINIFY" =~ ^all($|\+.*) ]]
return $? ;;
unsafe)
[[ "$OPT_MINIFY" =~ ^.*+unsafe(\+.*)*$ ]]
return $? ;;
lib)
[[ "$OPT_MINIFY" =~ ^lib($|\+.*) ]]
return $? ;;
any|"")
[[ "$OPT_MINIFY" != "none" ]]
return $? ;;
none)
! will_minify any
return $? ;;
esac
return 1
}
# Generates the banner for the output files.
#
# Output:
# The contents of banner.txt
generate_banner() {
local step="$1"
if ! "$OPT_BANNER"; then
return 0
fi
# Don't run it unless the comments are removed or hidden.
if ! { will_minify all || "$OPT_COMPRESS"; }; then
return 0
fi
# Only run it in the compression step if both minifying and compressing.
if will_minify all && "$OPT_COMPRESS" && [[ "$step" != "step_compress" ]]; then
return 0
fi
# Write the banner.
bat "${HERE}/banner.txt"
}
# Build step: read
# Reads the file from its source.
#
# Arguments:
# 1 -- The source file.
# 2 -- A description of what is being read.
#
# Output:
# The file contents.
step_read() {
local what=""
if [[ -n "${2:-}" ]]; then
what=" $2"
fi
cat "$1"
smsg "Reading"
smsg "Reading${what}"
}
# Build step: preprocess
@ -96,13 +268,14 @@ step_preprocess() {
# Output:
# The minified file contents.
step_minify() {
if [[ "$OPT_MINIFY" =~ ^all($|+.*) ]]; then
if ! will_minify all; then
cat
smsg "Minifying" "SKIP"
return 0
fi
printf "#!/usr/bin/env bash\n"
generate_banner step_minify
pp_minify | pp_minify_unsafe
smsg "Minifying"
}
@ -125,6 +298,7 @@ step_compress() {
local wrapper
wrapper="$({
printf '#!/usr/bin/env bash\n'
generate_banner step_compress
printf "(exec -a \"\$0\" bash -c 'eval \"\$(cat <&3)\"' \"\$0\" \"\$@\" 3< <(dd bs=1 if=\"\$0\" skip=::: 2>/dev/null | gunzip)); exit \$?;\n"
})"
@ -150,7 +324,7 @@ step_write() {
smsg "Building"
}
# Build step: write
# Build step: write_install
# Optionally writes the output script to a file.
#
# Arguments:
@ -161,7 +335,6 @@ step_write() {
#
# Output:
# The file contents.
step_write_install() {
if [[ "$OPT_INSTALL" != true ]]; then
cat
@ -174,8 +347,50 @@ step_write_install() {
smsg "Installing"
}
# Build step: manpage_install
# Optionally writes the manpage to a gzipped file.
#
# Arguments:
# 1 -- The file to write to.
#
# Input:
# The file contents.
#
# Output:
# The file contents.
step_manpage_install() {
if [[ "$OPT_INSTALL" != true ]]; then
cat
smsg "Installing manual" "SKIP"
return 0
fi
gzip | tee "$1"
smsg "Installing manual"
}
# Build step: manpage_generate
# Generates a manpage document from a markdown document.
#
# Input:
# The markdown doc contents.
#
# Output:
# The roff manpage contents.
step_manpage_generate() {
if [[ "$OPT_MANUALS" != true ]]; then
cat
smsg "Generating manual" "SKIP"
return 0
fi
(mdroff)
smsg "Generating manual"
}
# -----------------------------------------------------------------------------
# Preprocessor:
# -----------------------------------------------------------------------------
# Preprocessor.
# Consolidates all scripts into a single file.
# This follows all `source "${LIB}/..."` files and embeds them into the script.
@ -208,10 +423,10 @@ pp_consolidate__do() {
# Embed the script.
echo "${indent}# --- BEGIN LIBRARY FILE: ${BASH_REMATCH[1]} ---"
{
if [[ "$OPT_MINIFY" = "lib" ]]; then
if will_minify lib; then
pp_strip_comments | pp_minify | pp_minify_unsafe
else
cat
pp_strip_copyright | pp_strip_separators
fi
} < <(pp_consolidate__do "$((depth + 1))" < "$script") | sed "s/^/${indent}/"
echo "${indent}# --- END LIBRARY FILE ---"
@ -224,13 +439,13 @@ pp_consolidate__do() {
}
# Inlines constants:
# EXECUTABLE_BAT
# EXECUTABLE_*
# PROGRAM_*
pp_inline_constants() {
local constants=("EXECUTABLE_BAT" "PROGRAM")
local constants=("PROGRAM")
# Determine the PROGRAM_ constants.
local nf_constants="$( ( set -o posix ; set) | grep '^PROGRAM_' | cut -d'=' -f1)"
local nf_constants="$( ( set -o posix ; set) | grep '^\(PROGRAM_\|EXECUTABLE_\)' | cut -d'=' -f1)"
local line
while read -r line; do
constants+=("$line")
@ -250,15 +465,26 @@ pp_inline_constants() {
sed "${constants_pattern}"
}
# Strips comments from a Bash source file.
pp_strip_comments() {
sed '/^[[:space:]]*#.*$/d'
}
# Strips copyright comments from the start of a Bash source file.
pp_strip_copyright() {
awk '/^#/ {if(!p){ next }} { p=1; print $0 }'
}
# Strips separator comments from the start of a Bash source file.
pp_strip_separators() {
awk '/^#\s*-{5,}/ { next; } {print $0}'
}
# Minify a Bash source file.
# https://github.com/mvdan/sh
pp_minify() {
if [[ "$OPT_MINIFY" = "none" ]]; then
if will_minify none; then
cat
return
fi
@ -271,7 +497,7 @@ pp_minify() {
# Right now, this doesn't do anything.
# This should be applied after shfmt minification.
pp_minify_unsafe() {
if ! [[ "$OPT_MINIFY" =~ ^.*+unsafe(+.*)*$ ]]; then
if ! will_minify unsafe; then
cat
return 0
fi
@ -280,76 +506,161 @@ pp_minify_unsafe() {
}
# -----------------------------------------------------------------------------
# Options.
# Options:
# -----------------------------------------------------------------------------
OPT_INSTALL=false
OPT_COMPRESS=false
OPT_VERIFY=true
OPT_BANNER=true
OPT_MANUALS=true
OPT_INLINE=true
OPT_MINIFY="lib"
OPT_PREFIX="/usr/local"
OPT_BAT="bat"
EXECUTABLE_BAT="$(basename -- "$EXECUTABLE_BAT")"
ALT_EXECS=()
BUILD_FILTER=()
DOCS_URL="https://github.com/eth-p/bat-extras/blob/master/doc"
DOCS_MAINTAINER="eth-p <eth-p@hidden.email>"
# -----------------------------------------------------------------------------
# Use a different default prefix when running on Termux.
if [[ "$(uname -o)" = "Android" ]] && [[ -n "${TERMUX_VERSION:-}" ]]; then
OPT_PREFIX="/data/data/com.termux/files/usr/"
else
OPT_PREFIX="/usr/local"
fi
# -----------------------------------------------------------------------------
# Parse arguments.
while shiftopt; do
# shellcheck disable=SC2034
case "$OPT" in
--install) OPT_INSTALL=true ;;
--compress) OPT_COMPRESS=true ;;
--manuals) OPT_MANUALS="${OPT_VAL:-true}" ;;
--no-manuals) OPT_MANUALS=false ;;
--verify) OPT_VERIFY=true ;;
--no-verify) OPT_VERIFY=false ;;
--banner) OPT_BANNER=true ;;
--no-banner) OPT_BANNER=false ;;
--inline) OPT_INLINE=true ;;
--no-inline) OPT_INLINE=false ;;
--prefix) shiftval; OPT_PREFIX="$OPT_VAL" ;;
--alternate-executable) shiftval; OPT_BAT="$OPT_VAL" ;;
--alternate-executable) shiftval; ALT_EXECS+=("bat"); EXECUTABLE_BAT="$OPT_VAL" ;;
--alternate-executable:bat) shiftval; ALT_EXECS+=("bat"); EXECUTABLE_BAT="$OPT_VAL" ;;
--alternate-executable:ripgrep) shiftval; ALT_EXECS+=("ripgrep"); EXECUTABLE_RIPGREP="$OPT_VAL" ;;
--alternate-executable:delta) shiftval; ALT_EXECS+=("delta"); EXECUTABLE_DELTA="$OPT_VAL" ;;
--alternate-executable:fzf) shiftval; ALT_EXECS+=("fzf"); EXECUTABLE_FZF="$OPT_VAL" ;;
--alternate-executable:git) shiftval; ALT_EXECS+=("git"); EXECUTABLE_GIT="$OPT_VAL" ;;
--minify) shiftval; OPT_MINIFY="$OPT_VAL" ;;
# Print scripts.
--show:source-paths) get_source_paths; exit 0 ;;
# Unknown options.
*)
printc "%{RED}%s: unknown option '%s'%{CLEAR}" "$PROGRAM" "$OPT"
exit 1
if ! [[ -f "${SRC}/${OPT}.sh" ]]; then
printc_err "%{RED}%s: unknown option '%s'%{CLEAR}" "$PROGRAM" "$OPT"
exit 1
fi
BUILD_FILTER+=("$OPT")
;;
esac
done
if [[ "$OPT_BAT" != "bat" ]]; then
printc "%{YELLOW}Building executable scripts with an alternate bat executable at %{CLEAR}%s%{YELLOW}.%{CLEAR}\n" "$OPT_BAT" 1>&2
if ! command -v "$OPT_BAT"; then
printc "%{YELLOW}WARNING: Bash cannot execute the specified file.\n" 1>&2
printc "%{YELLOW} The finished scripts may not run properly.%{CLEAR}\n" 1>&2
if [[ "${#ALT_EXECS[@]}" -gt 0 ]]; then
printc_msg "%{YELLOW}Building executable scripts with alternate executables for:%{CLEAR}\n"
printc_msg "%{YELLOW} - %{CLEAR}%s\n" "${ALT_EXECS[@]}"
printc_msg "\n"
if ! command -v "$EXECUTABLE_BAT" &>/dev/null; then
printc_err "%{YELLOW}WARNING: Bash cannot execute bat's executable file.\n"
printc_err "%{YELLOW} The finished scripts may not run properly.%{CLEAR}\n"
fi
EXECUTABLE_BAT="$OPT_BAT"
printc "\n" 1>&2
# shellcheck disable=SC2034
printc_msg "\n"
fi
if [[ "$OPT_INSTALL" = true ]]; then
printc "%{YELLOW}Installing to %{MAGENTA}%s%{YELLOW}.%{CLEAR}\n" "$OPT_PREFIX" 1>&2
printc_msg "%{YELLOW}Installing to %{MAGENTA}%s%{YELLOW}.%{CLEAR}\n" "$OPT_PREFIX"
else
printc "%{YELLOW}This will not install the script.%{CLEAR}\n" 1>&2
printc "%{YELLOW}Use %{BLUE}--install%{YELLOW} for a global install.%{CLEAR}\n\n" 1>&2
printc_msg "%{YELLOW}This will not install the script.%{CLEAR}\n"
printc_msg "%{YELLOW}Use %{BLUE}--install%{YELLOW} for a global install.%{CLEAR}\n\n"
fi
if [[ "$OPT_INLINE" = false ]]; then
# Prevent full executable paths from being inlined.
while read -r exec; do
declare "$exec=$(basename "${!exec}")"
done < <(set | grep '^EXECUTABLE' | cut -d'=' -f1)
fi
# -----------------------------------------------------------------------------
# Check for resources.
[[ -d "$BIN" ]] || mkdir "$BIN"
if [[ "$OPT_MINIFY" != "none" ]] && ! command -v shfmt &>/dev/null; then
printc "%{RED}Warning: cannot find shfmt. Unable to minify scripts.%{CLEAR}\n"
if ! will_minify none && ! command -v shfmt &>/dev/null; then
printc_err "%{RED}Warning: cannot find shfmt. Unable to minify scripts.%{CLEAR}\n"
OPT_MINIFY=none
fi
# -----------------------------------------------------------------------------
# Check target directories exist.
[[ -d "$BIN" ]] || mkdir -p "$BIN"
if "$OPT_INSTALL"; then
[[ -d "${OPT_PREFIX}/bin" ]] || mkdir -p "${OPT_PREFIX}/bin"
[[ "$OPT_MANUALS" = "true" && ! -d "${OPT_PREFIX}/share/man/man1" ]] \
&& mkdir -p "${OPT_PREFIX}/share/man/man1"
fi
if [[ "$OPT_MANUALS" = "true" ]]; then
[[ -d "$MAN" ]] || mkdir -p "$MAN"
fi
# -----------------------------------------------------------------------------
# Find files.
SOURCES=()
printc "%{YELLOW}Preparing scripts...%{CLEAR}\n" 1>&2
for file in "$SRC"/*.sh; do
SOURCES+=("$file")
done
printc_msg "%{YELLOW}Preparing scripts...%{CLEAR}\n"
while read -r file; do
file_bin="$(basename -- "$file" ".sh")"
buildable=false
if ! "$buildable" && [[ "${#BUILD_FILTER[@]}" -eq 0 ]]; then
buildable=true
elif ! "$buildable"; then
for buildable_file in "${BUILD_FILTER[@]}"; do
if [[ "$buildable_file" = "$file_bin" ]]; then
buildable=true
break
fi
done
fi
# If that one is allowed to build, add it to the sources list.
if "$buildable"; then
SOURCES+=("$file")
else
printc_msg " %{YELLOW}Skipping %{MAGENTA}%s%{CLEAR}\n" "$(basename "$file_bin")"
fi
done < <(batextras:get_source_paths)
if [[ "${#BUILD_FILTER[@]}" -gt 0 ]]; then
printf "\n"
fi
# -----------------------------------------------------------------------------
# Build files.
printc "%{YELLOW}Building scripts...%{CLEAR}\n" 1>&2
printc_msg "%{YELLOW}Building scripts...%{CLEAR}\n"
file_i=0
file_n="${#SOURCES[@]}"
for file in "${SOURCES[@]}"; do
@ -359,7 +670,7 @@ for file in "${SOURCES[@]}"; do
PROGRAM="$filename"
PROGRAM_VERSION="$(<"${HERE}/version.txt")"
printc " %{YELLOW}[%s/%s] %{MAGENTA}%s%{CLEAR}\n" "$file_i" "$file_n" "$file" 1>&2
printc_msg " %{YELLOW}[%s/%s] %{MAGENTA}%s%{CLEAR}\n" "$file_i" "$file_n" "$file"
step_read "$file" |
next step_preprocess |
next step_minify |
@ -367,46 +678,59 @@ for file in "${SOURCES[@]}"; do
next step_write "${BIN}/${filename}" |
next step_write_install "${OPT_PREFIX}/bin/${filename}" |
cat >/dev/null
# Build manuals.
if [[ -f "${HERE}/doc/${filename}.md" && "$OPT_MANUALS" = "true" ]]; then
step_read "${HERE}/doc/${filename}.md" "manual" |
next step_manpage_generate |
next step_write "${MAN}/${filename}.1" |
next step_manpage_install "${OPT_PREFIX}/share/man/man1/${filename}.1.gz" |
cat >/dev/null
fi
done
# -----------------------------------------------------------------------------
# Verify files by running the tests.
if "$OPT_VERIFY"; then
printc "\n%{YELLOW}Verifying scripts...%{CLEAR}\n" 1>&2
printc_msg "\n%{YELLOW}Verifying scripts...%{CLEAR}\n"
# Run the tests.
FAIL=0
SKIP=0
while read -r action data1 data2 splat; do
[[ "$action" == "result" ]] || continue
printf "\x1B[G\x1B[K%s" "$data1" 1>&2
case "$data2" in
fail)
printf " failed.\n" 1>&2
printc_err "\x1B[G\x1B[K%s failed.\n" "$data1"
((FAIL++)) || true
;;
skip)
printc_msg "\x1B[G\x1B[K%s skipped.\n" "$data1"
((SKIP++)) || true
;;
*)
printc_msg "\x1B[G\x1B[K%s" "$data1"
;;
esac
done < <("${HERE}/test.sh" --compiled --porcelain)
done < <("${HERE}/test.sh" --compiled --porcelain --jobs=8)
# Print the overall result.
printf "\x1B[G\x1B[K%s" 1>&2
printc_msg "\x1B[G\x1B[K"
if [[ "$FAIL" -ne 0 ]]; then
printc "%{RED}One or more tests failed.\n" 1>&2
printc "Run ./test.sh for more detailed information.%{CLEAR}\n" 1>&2
printc_err "%{RED}%s\n" "One or more tests failed."
printc_msg "\x1B[A\x1B[G\x1B[K%{RED}%s\n" "One or more tests failed."
printc_err "%{RED}%s%{CLEAR}\n" "Run './test.sh --failed' for more detailed information."
exit 1
fi
if [[ "$SKIP" -gt 0 ]]; then
printc "%{CYAN}One or more tests were skipped.\n" 1>&2
printc "Run ./test.sh for more detailed information.%{CLEAR}\n" 1>&2
printc_err "%{CYAN}One or more tests were skipped.\n"
printc_err "Run ./test.sh for more detailed information.%{CLEAR}\n"
fi
printc "%{YELLOW}Verified successfully.%{CLEAR}\n" 1>&2
printc_msg "%{YELLOW}Verified successfully.%{CLEAR}\n"
fi

59
doc/batdiff.md Normal file
View file

@ -0,0 +1,59 @@
# bat-extras: batdiff
Diff a file against the current git index, or display the diff between two files.
This script supports using [delta](https://github.com/dandavison/delta) as an alternative highlighter for diffs.
## Usage
batdiff [OPTIONS] FILE
batdiff [OPTIONS] FILE OTHER_FILE
batdiff --staged
## Environment
| Variable | Description |
| ------------------------ | ------------------------------------------------ |
| `BATDIFF_USE_DELTA=true` | If `delta` is installed, use `delta` by default. |
## Options
| Short | Long | Description |
| ----- | ----------------------------- | ------------------------------------------------------------ |
| `-C` | `--context=[LINES]` | The number of lines to show before and after the differing lines. |
| | `--delta` | Display diffs using `delta`. |
| | `--color` | Force color output. |
| | `--no-color` | Force disable color output. |
| | `--paging=["never"/"always"]` | Enable/disable paging. |
| | `--pager=[PAGER]` | Specify the pager to use. |
| | `--terminal-width=[COLS]` | Generate output for the specified terminal width. |
| | `--staged` | Show staged changes. |
## Installation
This script is a part of the `bat-extras` suite of scripts. You can find install instructions [here](../README.md#installation).
## Caveats
**When using `bat` as the printer:**
- Syntax highlighting in diffs between two files is not supported.
- Syntax highlighting in a single-file diff requires `bat` >= 0.15.
**When using `delta` as the printer:**
- The `--no-color` option does not remove all color output.
## Issues?
If you find an issue or have a feature suggestion, make a pull request or issue through GitHub!
Contributions are always welcome.

View file

@ -6,34 +6,34 @@ Search through files or directories looking for matching regular expressions (or
## Command Line
## Usage
**Synopsis:**
- `batgrep [OPTIONS] PATTERN [PATH...] `
batgrep [OPTIONS] PATTERN [PATH...]
**Options:**
## Options
| Short | Long | Description |
| ----- | -------------------------- | ------------------------------------------------------------ |
| `-i` | `--ignore-case` | Use case insensitive searching. |
| `-s` | `--case-sensitive` | Use case sensitive searching. |
| `-S` | `--smart-case` | Use smart case searching. |
| `-A` | `--after-context=[LINES]` | Display the next *n* lines after a matched line. |
| `-B` | `--before-context=[LINES]` | Display the previous `n` lines before a matched line. |
| `-C` | `--context=[LINES]` | A combination of `--after-context` and `--before-context`. |
| `-p` | `--search-pattern` | Tell pager to search for `PATTERN`. Currently supported pagers: `less`. |
| | `--no-follow` | Do not follow symlinks. |
| | `--no-snip` | Do not show the `snip` decoration.<br /><br />This is automatically enabled when `--context=0` or when `bat --version` is less than `0.12.x`. |
| | `--no-highlight` | Do not highlight matching lines.<br /><br />This is automatically enabled when `--context=0`. |
| | `--color` | Force color output. |
| | `--no-color` | Force disable color output. |
| Short | Long | Description |
| ----- | ---------------------------- | ------------------------------------------------------------ |
| `-i` | `--ignore-case` | Use case insensitive searching. |
| `-s` | `--case-sensitive` | Use case sensitive searching. |
| `-S` | `--smart-case` | Use smart case searching. |
| `-A` | `--after-context=[LINES]` | Display the next *n* lines after a matched line. |
| `-B` | `--before-context=[LINES]` | Display the previous `n` lines before a matched line. |
| `-C` | `--context=[LINES]` | A combination of `--after-context` and `--before-context`. |
| `-p` | `--search-pattern` | Tell pager to search for `PATTERN`. Currently supported pagers: `less`. |
| | `--no-follow` | Do not follow symlinks. |
| | `--no-snip` | Do not show the `snip` decoration.<br /><br />This is automatically enabled when `--context=0` or when `bat --version` is less than `0.12.x`. |
| | `--no-highlight` | Do not highlight matching lines.<br /><br />This is automatically enabled when `--context=0`. |
| | `--color` | Force color output. |
| | `--no-color` | Force disable color output. |
| | `--paging=["never"/"always"]`| Enable/disable paging. |
| | `--pager=[PAGER]` | Specify the pager to use. |
| | `--terminal-width=[COLS]` | Generate output for the specified terminal width. |
| | `--no-separator` | Disable printing separator between files. |
| | `--rga` | Use `ripgrep-all` instead of `ripgrep`. |
**Options (Passthrough)**:
The following options are passed directly to ripgrep, and are not handled by this script.
| Short | Long | Notes |

View file

@ -4,17 +4,28 @@ Read system manual pages (`man`) using `bat` as the manual page formatter.
Gone are the days of losing your place while reading through monotone manual pages. With `bat` and `batman`, you can read `man ifconfig` with beautiful 24-bit color and syntax higlighting.
If you have `fzf` installed, you can even use `batman` to search through manual pages!
## Command Line
## Usage
**Synopsis:**
batman [SECTION] [ENTRY]
- `batman [SECTION] [ENTRY] `
### As a Replacement for Man
With bash:
```bash
eval "$(batman --export-env)"
```
**Environment:**
With fish:
```fish
batman --export-env | source
```
## Environment
| Variable | Description |
| ---------- | ------------------------------------------------------------ |
@ -22,12 +33,19 @@ Gone are the days of losing your place while reading through monotone manual pag
## Customization
### Changing the Theme
You can change the syntax highlighting theme for `batman` by setting the `BAT_THEME` environment variable before calling `batman`. The following wrapper function will change the theme to `Solarized (dark)` without affecting any other `bat` command.
## Caveats
```bash
batman() {
BAT_THEME="Solarized (dark)" batman "$@"
return $?
}
```
None so far.
@ -37,6 +55,18 @@ This script is a part of the `bat-extras` suite of scripts. You can find install
## Caveats
**Flags aren't highlighted:**
- This happens when you change `bat`'s theme through `bat`'s config file or the `BAT_THEME` environment variable. Not all themes provide colours for flags, and [it's a known issue](https://github.com/sharkdp/bat/issues/2115).
- You can overriding the theme for `batman` by wrapping it in a function that sets `BAT_THEME`.
- The following themes support manpage highlighting:
- `Monokai Extended` / ``Monokai Extended Light`
- `Solarized (dark)` / `Solarized (light)`
## Acknowledgements
Thanks to [@sharkdp](https://github.com/sharkdp) and [@LunarLambda](https://github.com/LunarLambda) for debugging how to make this work properly in [certain environments](https://github.com/sharkdp/bat/issues/652).
@ -46,4 +76,4 @@ Thanks to [@sharkdp](https://github.com/sharkdp) and [@LunarLambda](https://gith
## Issues?
If you find an issue or have a feature suggestion, make a pull request or issue through GitHub!
Contributions are always welcome.
Contributions are always welcome.

83
doc/batpipe.md Normal file
View file

@ -0,0 +1,83 @@
# bat-extras: batpipe
A `less` (and `bat`) preprocessor for viewing more types of files in the terminal.
## Usage
Like [lesspipe](https://github.com/wofr06/lesspipe), `batpipe` is designed to work with programs that support preprocessing with the `LESSOPEN` environment variable. Setting up `batpipe` will depend on your shell:
**Bash:**
eval "$(batpipe)"
**Fish:**
eval (batpipe)
## Environment
| Variable | Description |
| -------------------- | ------------------------------------------------------------ |
| `BATPIPE_TERM_WIDTH` | Sets the terminal width provided to `bat`. If this variable starts with a hyphen (`-`), the number provided will be relative to the detected terminal size. |
## Built-in Viewers
| Files | Program |
| -------------------- | --------------------------- |
| Directories | `eza`, `ls` |
| `*.tar`, `*.tar.gz` | `tar` |
| `*.zip`, `*.jar` | `unzip` |
| `*.gz` | `gunzip` |
| `*.xz` | `xz` |
## External Viewers
For file formats that aren't supported by default, an external file viewer can be added to `batpipe` through the external viewer API.
External viewers are be added to batpipe by creating bash scripts inside the `~/.config/batpipe/viewers.d/` directory.
### Creating Viewers
Viewers must define two functions and append the viewer's name to the `$BATPIPE_VIEWERS` array.
- `viewer_${viewer}_supports [file_basename] [file_path] [inner_file_path]`
- `viewer_${viewer}_process [file_path] [inner_file_path]`
The `viewer_${viewer}_supports` function is called to determine if the external viewer is capable of viewing the provided file. If this function returns successfully, the corresponding `process` function will be called.
### API
$BATPIPE_VIEWERS -- An array of loaded file viewers.
$BATPIPE_ENABLE_COLOR -- Whether color is supported. (`true`|`false`)
$BATPIPE_INSIDE_LESS -- Whether batpipe is inside less. (`true`|`false`)
batpipe_header [pattern] [...] -- Print a viewer header line.
batpipe_subheader [pattern] [...] -- Print a viewer subheader line.
strip_trailing_slashes [path] -- Strips trailing slashes from a path.
## Caveats
- By default, `batpipe` will not use colors when previewed inside `less`.
Colors must be explicitly enabled with `BATPIPE=color`.
## Installation
This script is a part of the `bat-extras` suite of scripts. You can find install instructions [here](../README.md#installation).
## Issues?
If you find an issue or have a feature suggestion, make a pull request or issue through GitHub!
Contributions are always welcome.

View file

@ -1,32 +1,30 @@
# bat-extras: batwatch
Watch for changes in one or more files, and print them with `bat`.
Watch for changes in files or command output, and print them with `bat`.
## Usage
batwatch [OPTIONS] FILE...
## Command Line
**Synopsis:**
- `batwatch [OPTIONS] FILE... `
batwatch [OPTIONS] --command COMMAND [ARG...]
**Options:**
## Options
| Short | Long | Description |
| ----- | --------------------- | ------------------------------------------------------------ |
| | `--watcher=[watcher]` | Use a specific program to watch for file changes. See [below](#watchers) for more details. |
| | `--clear` | Clear the screen before printing the files.<br />This is enabled by default. |
| | `--no-clear` | Do not clear the screen before printing the files. |
| | `--color` | Force color output. |
| | `--no-color` | Force disable color output. |
| Short | Long | Description |
| ----- | ---------------------- | ------------------------------------------------------------ |
| | `--command`\|`-x` | Execute a command on an interval. |
| | `--file`\|`-f` | Watch a file for changes. |
| | `--watcher=[watcher]` | Use a specific program to watch for file changes. See [below](#watchers) for more details. |
| | `--interval=[seconds]` | The interval for executing commands. |
| | `--clear` | Clear the screen before printing the files.<br />This is enabled by default. |
| | `--no-clear` | Do not clear the screen before printing the files. |
| | `--color` | Force color output. |
| | `--no-color` | Force disable color output. |
**Options (Passthrough)**:
All remaining options are passed through to bat.
@ -36,7 +34,7 @@ All remaining options are passed through to bat.
Batwatch uses external programs to watch for file changes.
Currently, the following programs are supported:
- [entr](http://entrproject.org/)
- [entr](https://eradman.com/entrproject/)
There is also a fallback `poll` watcher available.
@ -48,13 +46,8 @@ This script is a part of the `bat-extras` suite of scripts. You can find install
## Caveats
None so far.
## Issues?
If you find an issue or have a feature suggestion, make a pull request or issue through GitHub!
Contributions are always welcome.
Contributions are always welcome.

View file

@ -6,15 +6,14 @@ The only thing better than syntax highlighting? Syntax highlighting *and* code f
## Command Line
## Usage
**Synopsis:**
- `prettybat [OPTIONS] [PATH...] `
prettybat [OPTIONS] [PATH...]
**Options:**
## Options
Every option is passed through to `bat`.
See `man bat` for more information.
@ -22,20 +21,23 @@ See `man bat` for more information.
## Languages
| Language | Formatter |
| -------------------- | ----------------------------------------------------------- |
| JavaScript (JS, JSX) | [prettier](https://prettier.io/) |
| TypeScript (TS, TSX) | [prettier](https://prettier.io/) |
| CSS, SCSS, SASS | [prettier](https://prettier.io/) |
| Markdown | [prettier](https://prettier.io/) |
| JSON | [prettier](https://prettier.io/) |
| YAML | [prettier](https://prettier.io/) |
| HTML | [prettier](https://prettier.io/) |
| Rust | [rustfmt](https://github.com/rust-lang/rustfmt) |
| Bash | [shfmt](https://github.com/mvdan/sh) |
| C | [ClangFormat](https://clang.llvm.org/docs/ClangFormat.html) |
| C++ | [ClangFormat](https://clang.llvm.org/docs/ClangFormat.html) |
| Objective-C | [ClangFormat](https://clang.llvm.org/docs/ClangFormat.html) |
| Language | Formatter |
| -------------------- | ------------------------------------------------------------------------------------------------------ |
| JavaScript (JS, JSX) | [prettier](https://prettier.io/) |
| TypeScript (TS, TSX) | [prettier](https://prettier.io/) |
| CSS, SCSS, SASS | [prettier](https://prettier.io/) |
| Markdown | [prettier](https://prettier.io/) |
| JSON | [yq](https://mikefarah.gitbook.io/yq), [prettier](https://prettier.io/) |
| YAML | [yq](https://mikefarah.gitbook.io/yq), [prettier](https://prettier.io/) |
| HTML | [prettier](https://prettier.io/) |
| SVG | [prettier](https://prettier.io/) |
| Rust | [rustfmt](https://github.com/rust-lang/rustfmt) |
| Bash | [shfmt](https://github.com/mvdan/sh) |
| C | [ClangFormat](https://clang.llvm.org/docs/ClangFormat.html) |
| C++ | [ClangFormat](https://clang.llvm.org/docs/ClangFormat.html) |
| Objective-C | [ClangFormat](https://clang.llvm.org/docs/ClangFormat.html) |
| Python | [black](https://black.readthedocs.io/) |
| Elixir | [mix format](https://hexdocs.pm/mix/main/Mix.Tasks.Format.html) |
@ -43,11 +45,10 @@ See `man bat` for more information.
## Caveats
- The header displayed by bat will show `STDIN` instead of the filename.
- The header displayed by bat will show `STDIN` instead of the filename (`bat 0.13` or older).
- The git changes sidebar will not work with files that have been formatted.
## Installation
This script is a part of the `bat-extras` suite of scripts. You can find install instructions [here](../README.md#installation).
@ -58,4 +59,3 @@ This script is a part of the `bat-extras` suite of scripts. You can find install
If you find an issue or have a feature suggestion, make a pull request or issue through GitHub!
Contributions are always welcome.

27
lib/check.sh Normal file
View file

@ -0,0 +1,27 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2020 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
# Checks that the file or directory exists.
# Arguments:
# 1 -- The path to check.
check_exists() {
[[ -e "$1" ]] && return 0
print_error "%s: No such file or directory" "$1"
return 1
}
# Checks that the file is a file.
# Arguments:
# 1 -- The path to check.
check_is_file() {
[[ -f "$1" ]] && return 0
print_error "%s: Not a file" "$1"
return 1
}

View file

@ -1,18 +1,30 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2019 eth-p | MIT License
# bat-extras | Copyright (C) 2021 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
EXECUTABLE_BAT="bat"
if [[ -z "$__LIB_CONSTANTS_INITIALIZED" ]]; then
__LIB_CONSTANTS_INITIALIZED=true
# Constants: Executables
EXECUTABLE_BAT="$(command -v bat 2>/dev/null || command -v batcat 2>/dev/null || echo "bat")"
EXECUTABLE_GIT="git"
EXECUTABLE_DELTA="delta"
EXECUTABLE_RIPGREP="rg"
EXECUTABLE_FZF="fzf"
# Constants: Program
PROGRAM="$(basename "$0" .sh)"
PROGRAM_HOMEPAGE="https://github.com/eth-p/bat-extras"
PROGRAM_COPYRIGHT="Copyright (C) 2019-2020 eth-p | MIT License"
PROGRAM_COPYRIGHT="Copyright (C) 2019-2021 eth-p | MIT License"
PROGRAM_VERSION="$({
TOP="$(dirname "$(dirname "${BASH_SOURCE[0]}")")"
printf "%s" "$(cat "${TOP}/version.txt" 2>/dev/null || echo "unknown")"
if [[ -e "${TOP}/.git" ]]; then
printf "%s-git (%s)" "" "$(git -C "${TOP}" rev-parse --short HEAD)"
printf "%s-git (%s)" "" "$("${EXECUTABLE_GIT}" -C "${TOP}" rev-parse --short HEAD)"
fi
})"
fi

18
lib/dirs.sh Normal file
View file

@ -0,0 +1,18 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2021 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
# Gets a configuration directory for a command-line program.
# Arguments:
# 1 -- The program name.
config_dir() {
if [[ -n "${XDG_CONFIG_HOME+x}" ]]; then
echo "${XDG_CONFIG_HOME}/$1"
else
echo "${HOME}/.config/$1"
fi
}

172
lib/dsl.sh Normal file
View file

@ -0,0 +1,172 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2020 eth-p | MIT License
#
# ReRSTARTitory: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
# Parses a DSL file.
#
# Arguments:
# 1 -- The DSL file.
dsl_parse_file() {
dsl_parse < "$1"
return $?
}
# Parses DSL data.
# This calls callback functions to handle the parsed data:
#
# Format:
# | command arg1 arg2
# | option arg1 arg2
#
# Callbacks:
# dsl_on_raw "$indent" "$line" -- Called after every line.
# dsl_on_command "$command" "$arg1" "$arg2" ... -- Called on command lines.
# dsl_on_command_commit -- Called after commands and their options.
# dsl_on_option "$option" "$arg1" "$arg2" ... -- Called on option lines.
#
# Variables:
# DSL_LINE_NUMBER -- The line number being parsed at the time of a callback.
# DSL_COMMAND -- The command being parsed at the time of a callback.
#
# Input:
# The DSL data to parse.
dsl_parse() {
local line
local line_raw
local line_fields
local indent
local command
DSL_LINE_NUMBER=0
DSL_COMMAND=''
while IFS='' read -r line_raw; do
((DSL_LINE_NUMBER++)) || true
# Parse the indentation.
# If the indentation is greater than zero, it's considered an option.
[[ "$line_raw" =~ ^( |[[:space:]]{2,}) ]] || true
indent="${BASH_REMATCH[1]}"
line="${line_raw:${#indent}}"
if [[ -n "$line" ]] && ! [[ "$line" =~ ^# ]]; then
# Parse the line items.
eval "$(dsl_parse_line <<< "$line")"
# Call the appropriate on_* function.
if [[ "${#indent}" -eq 0 ]]; then
if [[ -n "$DSL_COMMAND" ]]; then
dsl_on_command_commit
fi
DSL_COMMAND="${line_fields[0]}"
dsl_on_command "${line_fields[@]}"
else
dsl_on_option "${line_fields[@]}"
fi
fi
# Call the on_raw function.
# This function can be used to echo back a line to rewrite the file.
dsl_on_raw "$indent" "$line"
done
if [[ -n "$DSL_COMMAND" ]]; then
dsl_on_command_commit
fi
return 0
}
# Parses a line into fields.
# This parses fields with a bash-like command parameter syntax:
#
# "arg 1" "" arg3
#
# Input:
# The line to parse.
#
# Output:
# A series of bash statemtents that write the fields into an array named "line_fields".
dsl_parse_line() {
awk '
{
print "line_fields=()"
n=0
buffer=""
quoted=0
while ($0 != "") {
quoted_once=0
while ($0 != "") {
# Match " ", "\", or quote.
if (!match($0, /[\t \\"]/)) {
buffer=sprintf("%s%s", buffer, $0)
$0=""
break
}
# Extract the character and previous literal string.
buffer=sprintf("%s%s", buffer, substr($0, 0, RSTART - 1))
chr=substr($0, RSTART, RLENGTH)
$0=substr($0, RSTART + RLENGTH)
# Handle the matched character.
if (chr == "\\") {
buffer=sprintf("%s%s", buffer, substr($0, 0, 1))
$0=substr($0, 2)
continue
}
if (chr == "\"") {
quoted=!quoted
quoted_once=1
continue
}
if ((chr == " " || chr == "\t") && quoted) {
buffer=sprintf("%s ", buffer)
continue
}
break
}
# If the buffer is empty and it is not intentionally empty,
# it should not be considered a separate field.
if (buffer == "" && !quoted_once) {
continue
}
# Escape the parsed value.
sub(/"/, "\\\"", buffer)
sub(/\$/, "\\$", buffer)
# Print the parsed value.
print sprintf("line_fields[%s]=\"%s\"", n, buffer)
buffer=""
n=n+1
}
}
'
}
dsl_on_raw() {
# Stub
:
}
#
#dsl_on_command() {
# :
#}
#
#dsl_on_command_commit() {
# :
#}
#
#dsl_on_option() {
# :
#}

View file

@ -1,19 +1,86 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2019 eth-p | MIT License
# bat-extras | Copyright (C) 2019-2020 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
source "${LIB}/constants.sh"
# An array of functions to call before returning from `shiftopt`.
#
# If one of these functions returns a successful exit code, the
# option will be transparently skipped instead of handled.
SHIFTOPT_HOOKS=()
# A setting to change how `shiftopt` will interpret short options that consist
# of more than one character.
#
# Values:
#
# SPLIT -- Splits the option into multiple single-character short options.
# "-abc" -> ("-a" "-b" "-c")
#
# VALUE -- Uses the remaining characters as the value for the short option.
# "-abc" -> ("-a=bc")
#
# CONV -- Converts the argument to a long option.
# "-abc" -> ("--abc")
#
# PASS -- Pass the argument along as-is.
# "-abc" -> ("-abc")
#
SHIFTOPT_SHORT_OPTIONS="VALUE"
# Sets the internal _ARGV, _ARGV_INDEX, and _ARGV_LAST variables used when
# parsing options with the shiftopt and shiftval functions.
#
# Arguments:
# ... -- The program arguments.
#
# Example:
# setargs "--long=3" "file.txt"
setargs() {
_ARGV=("$@")
_ARGV_LAST="$((${#_ARGV[@]} - 1))"
_ARGV_INDEX=0
_ARGV_SUBINDEX=1
}
# Gets all the remaining unparsed arguments and saves them to a variable.
#
# Arguments:
# "-a" -- Append the arguments to the variable instead of replacing it.
# $1 -- The variable to save the args to.
#
# Example:
# getargs remaining_args
getargs() {
if [[ "$1" = "-a" || "$1" = "--append" ]]; then
if [[ "${_ARGV_INDEX}" -ne "$((_ARGV_LAST+1))" ]]; then
eval "$2=(\"\${$2[@]}\" $(printf '%q ' "${_ARGV[@]:$_ARGV_INDEX}"))"
fi
else
if [[ "${_ARGV_INDEX}" -ne "$((_ARGV_LAST+1))" ]]; then
eval "$1=($(printf '%q ' "${_ARGV[@]:$_ARGV_INDEX}"))"
else
eval "$1=()"
fi
fi
}
# Resets the internal _ARGV* variables to the original script arguments.
# This is the equivalent of storing the top-level $@ and using setargs with it.
resetargs() {
setargs "${_ARGV_ORIGINAL[@]}"
}
# INTERNAL.
#
# Increments the argv index pointer used by `shiftopt`.
_shiftopt_next() {
_ARGV_SUBINDEX=1
((_ARGV_INDEX++)) || true
}
# Gets the next option passed to the script.
@ -36,13 +103,47 @@ shiftopt() {
OPT="${_ARGV[$_ARGV_INDEX]}"
unset OPT_VAL
if [[ "$OPT" =~ ^--[a-zA-Z0-9_-]+=.* ]]; then
if [[ "$OPT" =~ ^-[a-zA-Z0-9_-]+=.* ]]; then
OPT_VAL="${OPT#*=}"
OPT="${OPT%%=*}"
fi
# Handle short options.
if [[ "$OPT" =~ ^-[^-]{2,} ]]; then
case "$SHIFTOPT_SHORT_OPTIONS" in
# PASS mode: "-abc=0" -> ("-abc=0")
PASS) _shiftopt_next ;;
# Pop array.
((_ARGV_INDEX++))
# CONV mode: "-abc=0" -> ("--abc=0")
CONV) OPT="-${OPT}"; _shiftopt_next ;;
# VALUE mode: "-abc=0" -> ("-a=bc=0")
VALUE) {
OPT="${_ARGV[$_ARGV_INDEX]}"
OPT_VAL="${OPT:2}"
OPT="${OPT:0:2}"
_shiftopt_next
} ;;
# SPLIT mode: "-abc=0" -> ("-a=0" "-b=0" "-c=0")
SPLIT) {
OPT="-${OPT:$_ARGV_SUBINDEX:1}"
((_ARGV_SUBINDEX++)) || true
if [[ "$_ARGV_SUBINDEX" -gt "${#OPT}" ]]; then
_shiftopt_next
fi
} ;;
# ????? mode: Treat it as pass.
*)
printf "shiftopt: unknown SHIFTOPT_SHORT_OPTIONS mode '%s'" \
"$SHIFTOPT_SHORT_OPTIONS" 1>&2
_shiftopt_next
;;
esac
else
_shiftopt_next
fi
# Handle hooks.
local hook
@ -69,9 +170,15 @@ shiftval() {
if [[ -n "${OPT_VAL+x}" ]]; then
return 0
fi
OPT_VAL="${_ARGV[$_ARGV_INDEX]}"
((_ARGV_INDEX++))
if [[ "$_ARGV_SUBINDEX" -gt 1 && "$SHIFTOPT_SHORT_OPTIONS" = "SPLIT" ]]; then
# If it's a short group argument in SPLIT mode, we grab the next argument.
OPT_VAL="${_ARGV[$((_ARGV_INDEX+1))]}"
else
# Otherwise, we can handle it normally.
OPT_VAL="${_ARGV[$_ARGV_INDEX]}"
_shiftopt_next
fi
# Error if no value is provided.
if [[ "$OPT_VAL" =~ -.* ]]; then
@ -82,3 +189,4 @@ shiftval() {
# -----------------------------------------------------------------------------
setargs "$@"
_ARGV_ORIGINAL=("$@")

32
lib/opt_hook_help.sh Normal file
View file

@ -0,0 +1,32 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2019-2020 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
# Option parser hook: --help support.
# This will accept -h or --help, which prints the usage information and exits.
hook_help() {
SHIFTOPT_HOOKS+=("__shiftopt_hook__help")
if [[ "$1" == "--no-short" ]]; then
__shiftopt_hook__help() {
if [[ "$OPT" = "--help" ]]; then
show_help
exit 0
fi
return 1
}
else
__shiftopt_hook__help() {
if [[ "$OPT" = "--help" ]] || [[ "$OPT" = "-h" ]]; then
show_help
exit 0
fi
return 1
}
fi
}

28
lib/opt_hook_width.sh Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2019 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
source "${LIB}/term.sh"
# Option parser hook: --terminal-width support.
# This will accept --terminal-width=number.
#
# The variable OPT_TERMINAL_WIDTH will be set.
hook_width() {
SHIFTOPT_HOOKS+=("__shiftopt_hook__width")
__shiftopt_hook__width() {
case "$OPT" in
--terminal-width) shiftval; OPT_TERMINAL_WIDTH="$OPT_VAL" ;;
*) return 1 ;;
esac
return 0
}
# Default terminal width.
OPT_TERMINAL_WIDTH="$(term_width)"
}

View file

@ -12,6 +12,12 @@ is_pager_less() {
return $?
}
# Returns 0 (true) if the current pager is bat, otherwise 1 (false).
is_pager_bat() {
[[ "$(pager_name)" = "bat" ]]
return $?
}
# Returns 0 (true) if the current pager is disabled, otherwise 1 (false).
is_pager_disabled() {
[[ -z "$(pager_name)" ]]
@ -20,13 +26,13 @@ is_pager_disabled() {
# Prints the detected pager name.
pager_name() {
_detect_pager
_detect_pager 1>&2
echo "$_SCRIPT_PAGER_NAME"
}
# Prints the detected pager version.
pager_version() {
_detect_pager
_detect_pager 1>&2
echo "$_SCRIPT_PAGER_VERSION"
}
@ -36,14 +42,12 @@ pager_version() {
# Example:
# pager_exec echo hi
pager_exec() {
if [[ -n "$1" ]]; then
if [[ -n "$SCRIPT_PAGER_CMD" ]]; then
"$@" | "${SCRIPT_PAGER_CMD[@]}" "${SCRIPT_PAGER_ARGS[@]}"
return $?
else
"$@"
return $?
fi
if [[ -n "$SCRIPT_PAGER_CMD" ]]; then
"$@" | pager_display
return $?
else
"$@"
return $?
fi
}
@ -53,9 +57,16 @@ pager_exec() {
# bat | pager_display
pager_display() {
if [[ -n "$SCRIPT_PAGER_CMD" ]]; then
"${SCRIPT_PAGER_CMD[@]}" "${SCRIPT_PAGER_ARGS[@]}"
if [[ -n "$SCRIPT_PAGER_ARGS" ]]; then
"${SCRIPT_PAGER_CMD[@]}" "${SCRIPT_PAGER_ARGS[@]}"
return $?
else
"${SCRIPT_PAGER_CMD[@]}"
return $?
fi
else
cat
return $?
fi
}
@ -64,7 +75,7 @@ pager_display() {
# Detect the pager information.
# shellcheck disable=SC2120
_detect_pager() {
if [[ "$_SCRIPT_PAGER_DETECTED" = "true" && "$1" != "--force" ]]; then return; fi
if [[ "$_SCRIPT_PAGER_DETECTED" = "true" ]]; then return; fi
_SCRIPT_PAGER_DETECTED=true
# If the pager command is empty, the pager is disabled.
@ -81,9 +92,13 @@ _detect_pager() {
output1="$(head -n 1 <<<"$output")"
if [[ "$output1" =~ ^less[[:blank:]]([[:digit:]]+) ]]; then
# shellcheck disable=SC2001
_SCRIPT_PAGER_VERSION="${BASH_REMATCH[1]}"
_SCRIPT_PAGER_NAME="less"
elif [[ "$output1" =~ ^bat(cat)?[[:blank:]]([[:digit:]]+) ]]; then
# shellcheck disable=SC2034
__BAT_VERSION="${BASH_REMATCH[2]}"
_SCRIPT_PAGER_VERSION="${BASH_REMATCH[2]}"
_SCRIPT_PAGER_NAME="bat"
else
_SCRIPT_PAGER_VERSION=0
_SCRIPT_PAGER_NAME="$(basename "${SCRIPT_PAGER_CMD[0]}")"
@ -97,10 +112,15 @@ _detect_pager() {
# 2. Use PAGER with special arguments for less
# 3. Use PAGER
_configure_pager() {
SCRIPT_PAGER_CMD=("$PAGER")
# shellcheck disable=SC2206
SCRIPT_PAGER_ARGS=()
if [[ -n "${PAGER+x}" ]]; then
SCRIPT_PAGER_CMD=($PAGER)
else
SCRIPT_PAGER_CMD=("less")
fi
# Prefer the bat pager.
# Prefer the BAT_PAGER environment variable.
if [[ -n "${BAT_PAGER+x}" ]]; then
# [note]: This is intentional.
# shellcheck disable=SC2206
@ -108,12 +128,18 @@ _configure_pager() {
SCRIPT_PAGER_ARGS=()
return
fi
# If the pager is bat, use less instead.
if is_pager_bat; then
SCRIPT_PAGER_CMD=("less")
SCRIPT_PAGER_ARGS=()
fi
# Add arguments for the less pager.
if is_pager_less; then
SCRIPT_PAGER_ARGS=(-R --quit-if-one-screen)
SCRIPT_PAGER_CMD=("${SCRIPT_PAGER_CMD[0]}" -R --quit-if-one-screen)
if [[ "$(pager_version)" -lt 500 ]]; then
SCRIPT_PAGER_ARGS+=(--no-init)
SCRIPT_PAGER_CMD+=(--no-init)
fi
fi
}

26
lib/path.sh Normal file
View file

@ -0,0 +1,26 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2021 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
# Gets the file extension from a file path.
# Arguments:
# 1 -- The file path.
extname() {
local file="$1"
echo ".${file##*.}"
}
# Strips trailing slashes from a file path.
# Arguments:
# 1 -- The file path.
strip_trailing_slashes() {
local file="$1"
while [[ -n "$file" && "${file: -1}" = "/" ]]; do
file="${file:0:$((${#file}-1))}"
done
echo "$file"
}

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2019 eth-p | MIT License
# bat-extras | Copyright (C) 2021 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
@ -44,7 +44,7 @@ printc_init() {
_PRINTC_PATTERN_ANSI="${_PRINTC_PATTERN_ANSI}s/%{${name}}/${ansi}/g;"
done
if [ -t 1 ]; then
if [[ -t 1 && -z "${NO_COLOR+x}" ]]; then
_PRINTC_PATTERN="$_PRINTC_PATTERN_ANSI"
else
_PRINTC_PATTERN="$_PRINTC_PATTERN_PLAIN"
@ -81,5 +81,6 @@ printc_init "[DEFINE]" <<END
MAGENTA \x1B[35m
CYAN \x1B[36m
DEFAULT \x1B[39m
DIM \x1B[2m
END

70
lib/proc.sh Normal file
View file

@ -0,0 +1,70 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2021 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
# Gets the path to the parent executable file.
# Arguments:
# 1 -- The target pid. If not provided, the script's parent is used.
parent_executable() {
local target_pid="${1:-$PPID}"
ps -f -p "$target_pid" | tail -n 1 | awk '{for(i=8;i<=NF;i++) printf $i" "; printf "\n"}'
}
# Gets the PID of the parent executable file.
# Arguments:
# 1 -- The target pid. If not provided, the script's parent is used.
parent_executable_pid() {
local target_pid="${1:-$PPID}"
ps -f -p "$target_pid" | tail -n 1 | awk '{print $3}'
}
# Gets the path to the parent login shell.
# Arguments:
# 1 -- The target pid. If not provided, the script's parent is used.
parent_shell() {
local target_pid="${1:-$PPID}"
local target_name
while true; do
{
read -r target_pid
read -r target_name
# If the parent process starts with a "-", it's a login shell.
if [[ "${target_name:0:1}" = "-" ]]; then
target_name="$(cut -f1 -d' ' <<< "${target_name:1}")"
break
fi
# If the parent process is one of:
# - `*sh`; or
# - `nu`
# Followed by "-l", it's probably a login shell.
if [[ "$target_name" =~ ^(.*sh|nu)\ .*-l ]]; then
target_name="$(cut -f1 -d' ' <<< "${target_name}")"
break
fi
# If the parent process is pid 0 (init), then we haven't found a parent shell.
# At this point, it's best to assume the shell is whatever is defined in $SHELL.
if [[ "$target_pid" -eq 0 ]]; then
target_name="$SHELL"
break
fi
} < <({
ps -f -p "$target_pid" \
| tail -n 1 \
| awk '{print $3; for(i=8;i<=NF;i++) printf $i" "; printf "\n"}'
})
done
# Ensure that the detected shell is an executable path.
if [[ -f "$target_name" ]]; then
echo "$target_name"
elif ! command -v "$target_name" 2>/dev/null; then
echo "$target_name" # It's not, but we have nothing else we can do here.
fi
}

28
lib/term.sh Normal file
View file

@ -0,0 +1,28 @@
# ----------------------------------------------------------------------------------------------------------------------
# bat-extras | Copyright (C) 2020 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# ----------------------------------------------------------------------------------------------------------------------
# Gets the width of the terminal.
# This will return 80 unless stdin is attached to the terminal.
#
# Returns:
# The terminal width, or 80 if there's no TTY.
#
term_width() {
# shellcheck disable=SC2155
local width="$({ stty size 2>/dev/null || echo "22 80"; } | cut -d ' ' -f2)"
if [[ "$width" -ne 0 ]]; then
echo "$width"
else
echo "80"
fi
return 0
}
# Clears the terminal using the ANSI escape sequences for erase screen and cursor absolute positioning.
term_clear() {
printf "\x1B[3J\x1B[2J\x1B[H"
}

View file

@ -8,8 +8,11 @@
# Gets the current bat version.
bat_version() {
"$EXECUTABLE_BAT" --version | cut -d ' ' -f 2
return
if [[ -z "${__BAT_VERSION}" ]]; then
__BAT_VERSION="$(command "$EXECUTABLE_BAT" --version | cut -d ' ' -f 2)"
fi
echo "${__BAT_VERSION}"
}
# Compares two version strings.

382
mdroff.sh Executable file
View file

@ -0,0 +1,382 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2020 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
# shellcheck disable=SC2021
# shellcheck disable=SC2155
printf_msg() {
# shellcheck disable=SC2059
printf "$@" 1>&2
}
printf_err() {
# shellcheck disable=SC2059
printf "$@" 1>&2
}
# -----------------------------------------------------------------------------
# MDroff:
# -----------------------------------------------------------------------------
mdroff:emit:h1() {
printf '.TH "%s" 1\n' "$1"
}
mdroff:emit:h2() {
printf '.SH "%s"\n' "$1"
}
mdroff:emit:h3() {
printf '.SS "%s"\n' "$1"
}
mdroff:emit:h4() {
printf '.SS "%s"\n' "$1"
}
mdroff:emit:h5() {
printf '.SS "%s"\n' "$1"
}
mdroff:emit:line() {
if "$MDROFF_PARAGRAPH"; then
MDROFF_PARAGRAPH=false
printf ".P\n%s\n" "$1"
else
printf ".br\n%s\n" "$1"
fi
}
mdroff:emit:attr() {
printf '\\fR'
if "$MDROFF_ATTR_STRONG"; then
printf '\\fB'
fi
if "$MDROFF_ATTR_EMPHASIS"; then
printf '\\fI'
fi
if "$MDROFF_ATTR_CODE"; then
printf '\\fI'
fi
}
mdroff:emit:link() {
printf "%s" "$1"
}
# shellcheck disable=SC2034
mdroff:emit:table_start() {
printf ".TS\n"
printf "tab(|) box;\n"
# Print Header Alignment
local temp
for temp in "$@"; do printf "| cB "; done
printf "|\n"
# Print Separator
for temp in "$@"; do printf "| _ "; done
printf "|\n"
# Print Column Alignment
local cols=("$@")
printf "| "
printf "%s0 |1 " "${cols:0:$((${#@}-1))}"
printf "%s " "${cols[$((${#@}-1))]}"
printf "|.\n"
}
mdroff:emit:table_end() {
printf ".TE\n\n"
}
mdroff:emit:table_heading() {
local heading="$(printf "| %s " "$@")"
printf "%s\n" "${heading:1}"
# Prevent tbl warning.
for temp in "${@:2}"; do printf "|"; done
printf "\n"
# Start table data.
printf ".SP\n"
}
mdroff:emit:table_row() {
local row="$(printf "| %s " "$@")"
printf "%s\n" "${row:1}"
}
mdroff:emit:text() {
local text="$1"
text="${text//<br>/ }"
text="${text//<br\/>/ }"
text="${text//<br \/>/ }"
printf "%s" "$text"
}
mdroff:emit() {
local type="$1"
local data="$2"
if type "mdroff:rewrite:${type}" &>/dev/null; then
data="$("mdroff:rewrite:${type}" "${@:2}")"
fi
if type "mdroff:emit_hook:${type}" &>/dev/null; then
"mdroff:emit_hook:${type}" "$data" "${@:3}"
return
fi
"mdroff:emit:${type}" "$data" "${@:3}"
}
mdroff:trim_right() {
# shellcheck disable=SC2001
sed 's/[[:space:]]*$//' <<< "$1"
}
mdroff:trim() {
sed 's/^[[:space:]]*//; s/[[:space:]]*$//' <<< "$1"
}
mdroff:parseln() {
MDROFF_ATTR_STRONG=false
MDROFF_ATTR_EMPHASIS=false
MDROFF_ATTR_CODE=false
local buffer="$1"
local before
local found
local pos
while [[ "${#buffer}" -gt 0 ]]; do
[[ "$buffer" =~ \*{1,3}|\`|\[([^\]]+)\]\(([^\)]+)\) ]] || {
mdroff:emit text "$(mdroff:trim_right "$buffer")"
mdroff:emit text $'\n'
return
}
found="${BASH_REMATCH[0]}"
pos="$(awk -v search="$found" '{print index($0,search) - 1}' <<< "$buffer")"
before="${buffer:0:$pos}"
buffer="${buffer:$(($pos + ${#found}))}"
mdroff:emit text "$before"
case "$found" in
'***')
if "$MDROFF_ATTR_STRONG" && "$MDROFF_ATTR_EMPHASIS"; then
MDROFF_ATTR_STRONG=false
MDROFF_ATTR_EMPHASIS=false
else
MDROFF_ATTR_STRONG=true
MDROFF_ATTR_EMPHASIS=true
fi
mdroff:emit attr
;;
'**')
if "$MDROFF_ATTR_STRONG"; then
MDROFF_ATTR_STRONG=false
else
MDROFF_ATTR_STRONG=true
fi
mdroff:emit attr
;;
'*')
if "$MDROFF_ATTR_EMPHASIS"; then
MDROFF_ATTR_EMPHASIS=false
else
MDROFF_ATTR_EMPHASIS=true
fi
mdroff:emit attr
;;
'`')
if "$MDROFF_ATTR_CODE"; then
MDROFF_ATTR_CODE=false
else
MDROFF_ATTR_CODE=true
fi
mdroff:emit attr
;;
'['*)
mdroff:emit link "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}"
esac
done
}
mdroff() {
MDROFF_HEADING_LEVEL=0
MDROFF_HEADING=''
MDROFF_ATTR_STRONG=false
MDROFF_ATTR_EMPHASIS=false
MDROFF_ATTR_CODE=false
MDROFF_IN_TABLE=false
MDROFF_PARAGRAPH=false
MDROFF_TABLE_HEADER=()
local line
local empty=0
local empty_continue=0
while IFS='' read -r line; do
line="$(mdroff:parseln "$line")"
# Empty
if [[ "$line" =~ ^[[:space:]]*$ ]]; then
((empty_continue++)) || true
if "$MDROFF_IN_TABLE"; then
mdroff:emit table_end
fi
MDROFF_PARAGRAPH=true
MDROFF_TABLE_HEADER=()
MDROFF_IN_TABLE=false
continue
fi
empty="$empty_continue"
empty_continue=0
# Headings
if [[ "$line" =~ ^(#{1,})[[:space:]]{1,}(.*)$ ]]; then
local level="${#BASH_REMATCH[1]}"
local text="${BASH_REMATCH[2]}"
MDROFF_HEADING_LEVEL="$level"
MDROFF_HEADING="$text"
mdroff:emit "h${level}" "$text"
MDROFF_PARAGRAPH=true
continue
fi
# Tables (Partially Supported)
if [[ "$line" =~ ^[[:space:]]*\| ]]; then
local raw_cells=()
local cells=()
local table_cell
line="$(sed 's/^[[:space:]]*|//; s/|[[:space:]]*$//' <<< "$line")"
# shellcheck disable=SC2206
IFS='|' raw_cells=($line)
for table_cell in "${raw_cells[@]}"; do
cells+=("$(mdroff:trim "$table_cell")")
done
if [[ "${cells[0]}" =~ ^[[:space:]]*-+[[:space:]]*$ ]]; then
# Calculate the column alignments.
local table_alignments=()
local table_cell
for table_cell in "${cells[@]}"; do
case "$table_cell" in
:-*:)
table_alignments+=('c')
;;
:-*)
table_alignments+=('l')
;;
*-:)
table_alignments+=('r')
;;
*)
table_alignments+=('l') # Unknown, but let's assume left.
esac
done
# Emit the table start and table header.
mdroff:emit table_start "${table_alignments[@]}"
mdroff:emit table_heading "${MDROFF_TABLE_HEADER[@]}"
MDROFF_TABLE_HEADER=()
continue
fi
if ! "$MDROFF_IN_TABLE"; then
MDROFF_IN_TABLE=true
MDROFF_TABLE_HEADER=("${cells[@]}")
else
if [[ "${#MDROFF_TABLE_HEADER[@]}" -ne 0 ]]; then
mdroff:emit table_heading "${MDROFF_TABLE_HEADER[@]}"
MDROFF_TABLE_HEADER=()
fi
mdroff:emit table_row "${cells[@]}"
fi
MDROFF_PARAGRAPH=true
continue
fi
mdroff:emit line "$line"
done < <(sed 's/<br *\/?>//')
}
# -----------------------------------------------------------------------------
# bat-extras:
# -----------------------------------------------------------------------------
mdroff:rewrite:h1() {
emitted_name=false
emitted_description=false
sed 's/^bat-extras: //' <<< "$1" | tr '[[:lower:]]' '[[:upper:]]'
}
mdroff:emit_hook:h2() {
case "$MDROFF_HEADING" in
"Installation") return ;;
"Issues?") return ;;
esac
mdroff:emit:h2 "$(tr '[[:lower:]]' '[[:upper:]]' <<< "$1")"
}
mdroff:emit_hook:line() {
if [[ "$MDROFF_HEADING_LEVEL" = "1" ]] && [[ "$emitted_name" != true ]]; then
emitted_name=true
printf ".SH NAME\n%s - %s\n" "$(sed 's/^bat-extras: //' <<< "$MDROFF_HEADING" | tr '[[:upper:]]' '[[:lower:]]')" "$1"
printf ".SH DESCRIPTION\n"
return
fi
case "$MDROFF_HEADING" in
"Installation") return ;;
"Issues?") return ;;
esac
mdroff:emit:line "$@"
}
# -----------------------------------------------------------------------------
# Main:
# -----------------------------------------------------------------------------
if [[ "${#BASH_SOURCE[@]}" -eq 1 ]]; then
case "$1" in
"") mdroff ;;
*) {
if ! [ -f "$1" ]; then
printf_err "%s: cannot find or read file %s\n" "$0" "$1"
exit 1;
fi
mdroff < "$1"
} ;;
esac
fi

View file

@ -1,143 +1,350 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2019 eth-p | MIT License
# bat-extras | Copyright (C) 2019-2023 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/build.sh"
# -----------------------------------------------------------------------------
# Release-as-a-Library Functions:
# -----------------------------------------------------------------------------
# Prints the path to the git workspace.
if ! batextras:is_function_defined ::get_git_workspace; then
batextras:get_git_workspace() {
batextras:lazy_done "${FUNCNAME[0]}" \
"$(batextras:get_project_directory)"
}
fi
# Prints the commit of the latest-tagged version of bat-extras.
if ! batextras:is_function_defined ::get_current_commit; then
batextras:get_current_commit() {
batextras:lazy_done "${FUNCNAME[0]}" \
"$(batextras:git rev-parse HEAD)"
}
fi
# Prints the commit of the latest-tagged version of bat-extras.
if ! batextras:is_function_defined ::get_previous_tag_commit; then
batextras:get_previous_tag_commit() {
local latest_tag
local before_latest_tag
{
read -r latest_tag
read -r before_latest_tag
} < <(batextras:git rev-list --tags --max-count=2)
# If the latest commit is a tag, go to the one before that.
if [[ "$(batextras:get_current_commit)" = "$latest_tag" ]]; then
latest_tag="${before_latest_tag}"
fi
batextras:lazy_done "${FUNCNAME[0]}" "$latest_tag"
}
fi
# Prints the ref name of the latest-tagged version of bat-extras.
if ! batextras:is_function_defined ::get_previous_tag_name; then
batextras:get_previous_tag_name() {
batextras:lazy_done "${FUNCNAME[0]}" \
"$(batextras:git describe --tags --abbrev=0 "$(batextras:get_previous_tag_commit)")"
}
fi
# Returns the suffix for a day of the month.
#
# Arguments:
# 1 -- The day number.
#
# Output:
# The suffix.
batextras:day_suffix() {
case "$1" in
11 | 12 | 13) echo "th" ;;
*1) echo "st" ;;
*2) echo "nd" ;;
*3) echo "rd" ;;
*) echo "th" ;;
esac
}
# Runs `git` within the project directory.
#
# This takes the same arguments as git (with the exception of `-C`), and
# does exactly what `git` would normally do.
batextras:git() {
git -C "$(batextras:get_git_workspace)" "$@"
return $?
}
# Creates the zipball for release.
# YOU MUST BUILD THE PROJECT FIRST!
#
# Arguments:
# 1 -- The absolute path to the output zip file.
#
# Stderr:
# Messages.
batextras:create_package() {
local artifact="$1"
local bin_dir man_dir doc_dir
bin_dir="$(batextras:get_output_bin_directory)"
man_dir="$(batextras:get_output_man_directory)"
doc_dir="$(batextras:get_docs_directory)"
(
# Remove the old zipball, if one exists.
if [[ -f "$artifact" ]]; then
rm "$artifact" || return $?
fi
# Add the bin directory.
cd "$(dirname -- "$bin_dir")" || return $?
zip -r "$artifact" "$(basename -- "$bin_dir")"
# Add the doc directory.
cd "$(dirname -- "$doc_dir")" || return $?
zip -ru "$artifact" "$(basename -- "$doc_dir")"
# Add the man directory.
if [[ -d "$man_dir" ]]; then
cd "$(dirname -- "$man_dir")" || return $?
zip -ru "$artifact" "$(basename -- "$man_dir")"
fi
) 1>&2
}
# Generates a Markdown changelog for all changes between two commits.
#
# Arguments:
# 1 -- The first commit, exclusive.
# 2 -- The second commit, inclusive.
# 3 -- A filter in regex.
#
# Output:
# The changelog.
batextras:generate_changelog() {
local start_commit="$1"
local end_commit="$2"
local filter="${3}"
# Generate sed replacement patterns.
local script_links=()
local script_names=()
local script script_name
while read -r script; do
script_name="$(basename "$script" .sh)"
script_names+=("$script_name")
done < <(batextras:get_source_paths)
local script_pattern
script_pattern="$(printf 's/\\(%s\\)/`\\1`/;' "${script_names[@]}")"
# Generate the changelog.
local changelog=''
local commit
local affected_module
local commit_message
while read -r commit; do
commit_message="$(batextras:git show -s --format=%s "$commit")"
if ! [[ "$commit_message" =~ ^([a-z-]+):[[:space:]]*(.*)$ ]]; then
continue
fi
affected_module="${BASH_REMATCH[1]}"
# Make module names consistent.
case "$affected_module" in
dev | lib | mdroff) affected_module="developer" ;;
tests) affected_module="test" ;;
doc) affected_module="docs" ;;
esac
# Append to changelog.
if [[ "$affected_module" =~ ^($filter)$ ]]; then
changelog="$changelog"$'\n'" - ${commit_message}"
fi
done < <(batextras:git rev-list "${start_commit}..${end_commit}")
# Print the changelog.
changelog="$(sed "$script_pattern" <<< "$changelog")"
printf "%s\n" "${changelog:1}"
return 0
}
# Generates the Markdown release notes.
#
# Arguments:
# 1 -- The oldest commit, exclusive.
# 2 -- The newest commit, inclusive.
#
# Output:
# The changelog.
batextras:generate_release_notes() {
local commit_oldest="$1"
local commit_newest="$2"
local commit_newest_url="https://github.com/eth-p/bat-extras/tree/${commit_newest}"
local date_str
# Get the commit date.
local date_year date_month date_day date_month_text date_day_suffix
read -r date_year date_month date_day date_month_text \
< <(batextras:git show -s --format="%cd" --date="format:%Y %m %d %B" "$commit_newest")
date_day_suffix="$(batextras:day_suffix "$date_day")"
date_str="${date_month_text} ${date_day}${date_day_suffix}, ${date_year}"
# For each built script, we want to:
# - Get the name of the script.
# - Get a link to the documentation.
# - Add it to the filter for non-developer items.
local script_name script_names script_links script_filters script_list_markdown
script_links=()
script_names=()
script_filters=''
while read -r script; do
script_name="$(basename "$script" .sh)"
script_names+=("$script_name")
script_links+=("[\`${script_name}\`](https://github.com/eth-p/bat-extras/blob/${commit_newest}/doc/${script_name}.md)")
script_filters="${script_filters}|$(printf "%q" "$script_name")"
done < <(batextras:get_source_paths)
script_filters="${script_filters:1}" # Remove the leading "|"
script_list_markdown="$(printf "%s, " "${script_links[@]:0:$((${#script_links[@]} - 1))}")"
script_list_markdown="${script_list_markdown}and ${script_links[$((${#script_links[@]} - 1))]}"
# Get the changelog.
local changelog changelog_dev
changelog="$(batextras:generate_changelog "$commit_oldest" "$commit_newest" "$script_filters")"
changelog_dev="$(batextras:generate_changelog "$commit_oldest" "$commit_newest" "test|developer|ci|build")"
# Print the template.
{ sed '/\\$/{N;s/\\\n//;s/\n//p;}'; } <<- EOF
This contains the latest versions of ${script_list_markdown} as of commit [${commit_newest}](${commit_newest_url}) (${date_str}).
**This is provided as a convenience only.**
I would still recommend following the installation instructions in [the README](https://github.com/eth-p/bat-extras#installation-) for the most up-to-date versions.
### Changes
${changelog}
### Developer
<details>
<div markdown="1">
${changelog_dev}
</div>
</details>
EOF
}
# -----------------------------------------------------------------------------
# Main:
# Only run everything past this point if the script is not sourced.
# -----------------------------------------------------------------------------
(return 0 2>/dev/null) && return 0
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DATE="$(date +%Y%m%d)"
VERSION="$(<"${HERE}/version.txt")"
VERSION="$(< "${HERE}/version.txt")"
VERSION_EXPECTED="$(date +%Y.%m.%d)"
LIB="$HERE/lib"
SRC="$HERE/src"
source "${LIB}/print.sh"
source "${LIB}/opt.sh"
# -----------------------------------------------------------------------------
set -euo pipefail
# -----------------------------------------------------------------------------
# Options.
OPT_ARTIFACT="bat-extras-${DATE}.zip"
OPT_SINCE=
OPT_BIN_DIR="$HERE/bin"
OPT_DOC_DIR="$HERE/doc"
OPT_BAD_IDEA=false
OPT_BIN_DIR="$(batextras:get_output_bin_directory)"
OPT_DOC_DIR="$(batextras:get_docs_directory)"
OPT_MAN_DIR="$(batextras:get_output_man_directory)"
while shiftopt; do
case "$OPT" in
--since)
shiftval
OPT_SINCE="$OPT_VAL"
if ! git rev-parse "$OPT_SINCE" &>/dev/null; then
printc "%{RED}%s: unknown commit or tag for '%s'\n" "$PROGRAM" "$OPT"
exit 1
fi
;;
--since)
shiftval
OPT_SINCE="$OPT_VAL"
if ! batextras:git rev-parse "$OPT_SINCE" &> /dev/null; then
printc "%{RED}%s: unknown commit or tag for '%s'\n" "$PROGRAM" "$OPT"
exit 1
fi
;;
*)
printc "%{RED}%s: unknown option '%s'%{CLEAR}" "$PROGRAM" "$OPT"
exit 1
;;
--badidea)
OPT_BAD_IDEA=true
;;
*)
printc "%{RED}%s: unknown option '%s'%{CLEAR}" "$PROGRAM" "$OPT"
exit 1
;;
esac
done
# -----------------------------------------------------------------------------
# Verify the version matches today's date.
if [[ "$VERSION" != "$VERSION_EXPECTED" ]]; then
printc "%{RED}The expected version does not match %{WHITE}version.txt%{RED}!%{CLEAR}\n"
VERSION="$(source "${LIB}/constants.sh" && echo "${PROGRAM_VERSION}")"
VERSION_EXPECTED="$(date +%Y.%m.%d)"
if [[ "$VERSION" != "$VERSION_EXPECTED" ]] && ! "$OPT_BAD_IDEA"; then
printc "%{RED}The expected version does not match %{DEFAULT}version.txt%{RED}!%{CLEAR}\n"
printc "%{RED}Expected: %{YELLOW}%s%{CLEAR}\n" "$VERSION_EXPECTED"
printc "%{RED}Actual: %{YELLOW}%s%{CLEAR}\n" "$VERSION"
exit 1
fi
# -----------------------------------------------------------------------------
# Verify the working tree is clean-ish.
if ! "$OPT_BAD_IDEA"; then
while read -r flags file; do
if [[ "$flags" =~ M ]]; then
printc "%{RED}Found an uncommitted change in %{DEFAULT}%s%{RED}!%{CLEAR}\n" "$file"
exit 1
fi
done < <(batextras:git status --porcelain)
fi
# -----------------------------------------------------------------------------
# Build files.
# Clean the old bin files.
# Make sure it's not trying to delete /bin first, though.
if [[ "$OPT_BIN_DIR" != "/bin" ]]; then
rm -rf "$OPT_BIN_DIR"
fi
# Clean the old files.
# Make sure it's not trying to delete /bin or /man first, though.
if [[ "$OPT_BIN_DIR" != "/bin" ]]; then rm -rf "$OPT_BIN_DIR"; fi
if [[ "$OPT_MAN_DIR" != "/man" ]]; then rm -rf "$OPT_MAN_DIR"; fi
# Generate the new bin files.
printc "%{YELLOW}Building scripts...%{CLEAR}\n"
"$HERE/build.sh" --minify=all &>/dev/null || {
"$HERE/build.sh" --minify=all --alternate-executable='bat' --no-inline &>/dev/null || {
printc "%{RED}FAILED TO BUILD SCRIPTS.%{CLEAR}\n"
printc "%{RED}CAN NOT PROCEED WITH RELEASE.%{CLEAR}\n"
exit 1
}
# -----------------------------------------------------------------------------
# Tag release.
if ! "$OPT_SKIP_TAG"; then
printc "%{YELLOW}Tagging release...%{CLEAR}\n"
git tag "$OPT_TAG"
fi
# -----------------------------------------------------------------------------
# Build package.
printc "%{YELLOW}Packaging artifacts...%{CLEAR}\n"
zip -r "$OPT_ARTIFACT" \
"$OPT_BIN_DIR" \
"$OPT_DOC_DIR"
batextras:create_package "$OPT_ARTIFACT"
printc "%{YELLOW}Package created as %{BLUE}%s%{YELLOW}.%{CLEAR}\n" "$OPT_ARTIFACT"
# -----------------------------------------------------------------------------
# Print template description package.
printc "%{YELLOW}Release description:%{CLEAR}\n"
# Get the commit hash.
COMMIT="$(git rev-parse HEAD)"
# Get the release date string.
DATE_DAY="$(date +%e | sed 's/ //')"
DATE_SUFFIX=""
case "$DATE_DAY" in
11|12|13) DATE_SUFFIX="th" ;;
*1) DATE_SUFFIX="st" ;;
*2) DATE_SUFFIX="nd" ;;
*3) DATE_SUFFIX="rd" ;;
*) DATE_SUFFIX="th" ;;
esac
DATE_STR="$(date +'%B') ${DATE_DAY}${DATE_SUFFIX}, $(date +'%Y')"
# Get the changelog.
CHANGELOG=''
if [[ -n "$OPT_SINCE" ]]; then
ref="$(git rev-parse HEAD)"
end="$(git rev-parse "$OPT_SINCE")"
while [[ "$ref" != "$end" ]]; do
ref_message="$(git show -s --format=%s "$ref")"
ref="$(git rev-parse "${ref}~1")"
if [[ "$ref_message" =~ ^([a-z]):[[:space:]]*(.*)$ ]]; then
ref_message="\`${BASH_REMATCH[1]}\`: ${BASH_REMATCH[2]}"
fi
# Append to changelog.
if [[ -z "$CHANGELOG" ]]; then
CHANGELOG=" - ${ref_message}"
else
CHANGELOG="$CHANGELOG"$'\n'" - ${ref_message}"
fi
done
fi
# Print the template.
sed '/\\$/{N;s/\\\n//;s/\n//p;}' <<-EOF
This contains the latest versions of \
[\`batgrep\`](https://github.com/eth-p/bat-extras/blob/${COMMIT}/doc/batgrep.md), \
[\`batman\`](https://github.com/eth-p/bat-extras/blob/${COMMIT}/doc/batman.md), \
[\`batwatch\`](https://github.com/eth-p/bat-extras/blob/${COMMIT}/doc/batwatch.md), and \
[\`prettybat\`](https://github.com/eth-p/bat-extras/blob/${COMMIT}/doc/prettybat.md) \
as of commit $(git rev-parse --short HEAD) (${DATE_STR}).
**This is provided as a convenience only.**
I would still recommend following the installation instructions in \
[the README](https://github.com/eth-p/bat-extras#installation-) for the most up-to-date versions.
### Changes
$CHANGELOG
EOF
batextras:generate_release_notes \
"$(batextras:get_previous_tag_name)" \
"$(batextras:get_current_commit)"

251
src/bat-modules.sh Executable file
View file

@ -0,0 +1,251 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2020 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
# shellcheck disable=SC1090
LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo ".")")/../lib" && pwd)"
source "${LIB}/constants.sh"
source "${LIB}/print.sh"
source "${LIB}/opt.sh"
source "${LIB}/opt_hook_color.sh"
source "${LIB}/opt_hook_version.sh"
source "${LIB}/dsl.sh"
source "${LIB}/str.sh"
# -----------------------------------------------------------------------------
# Init:
# -----------------------------------------------------------------------------
hook_color
hook_version
# -----------------------------------------------------------------------------
COMMON_URL_GITHUB="https://github.com/%s.git"
COMMON_URL_GITLAB="https://gitlab.com/%s.git"
CONFIG_DIR="$(bat --config-dir)"
SYNTAX_DIR="${CONFIG_DIR}/syntaxes"
THEME_DIR="${CONFIG_DIR}/themes"
MODULES_FILE="${CONFIG_DIR}/modules.txt"
# -----------------------------------------------------------------------------
# Options:
# -----------------------------------------------------------------------------
ACTION="help"
# Parse arguments.
while shiftopt; do
case "$OPT" in
--help) ACTION="help" ;;
--update) ACTION="update" ;;
--clear) ACTION="clear" ;;
--setup) ACTION="setup" ;;
--modules-file) ACTION="show_file" ;;
# ???
-*) {
printc "%{RED}%s: unknown option '%s'%{CLEAR}\n" "$PROGRAM" "$OPT" 1>&2
exit 1
} ;;
esac
done
# -----------------------------------------------------------------------------
# Functions:
# -----------------------------------------------------------------------------
# Ensures that the modules file at $MODULES_FILE exists.
# If it doesn't, this will print a friendly warning and exit with exit code 1.
#
# This will also make sure the syntaxes and themes directories exist.
ensure_setup() {
if ! [[ -f "$MODULES_FILE" ]]; then
printc "%{YELLOW}The bat-modules modules file wasn't found.%{CLEAR}\n"
printc "%{YELLOW}Use %{CLEAR}%s --setup%{YELLOW} to set up bat-modules, or%{CLEAR}\n" "${PROGRAM}"
printc "%{YELLOW}read the documentation at %{CLEAR}%s%{YELLOW} for more info.%{CLEAR}\n" "${PROGRAM_HOMEPAGE}"
exit 1
fi
mkdir -p "${SYNTAX_DIR}" &>/dev/null || true
mkdir -p "${THEME_DIR}" &>/dev/null || true
}
# Prints an error message that parsing
fail_parsing() {
print_warning "Failed to parse bat-modules file."
print_warning "Line %s: %s" "$DSL_LINE" "$1"
exit 1
}
# -----------------------------------------------------------------------------
# Parsing:
# -----------------------------------------------------------------------------
dsl_on_command() {
BM_TYPE="$(tolower "$1")"
BM_SOURCE="$(parse_source "$2")"
BM_OPT_CHECKOUT="master"
case "$BM_TYPE" in
"syntax" | "theme") : ;;
*) fail "unknown module type '$BM_TYPE'" ;;
esac
}
dsl_on_option() {
# Common options.
case "$(tolower "$1")" in
checkout)
BM_OPT_CHECKOUT="$2"
return 0 ;;
esac
# Type-specific options.
case "$BM_TYPE" in
"syntax") on_option_for_syntax "$@" && return 0 ;;
"theme") on_option_for_theme "$@" && return 0 ;;
esac
# Unknown options.
fail "unknown %s option '%s'" "$BM_TYPE" "$*"
}
on_option_for_syntax() {
:
}
on_option_for_theme() {
:
}
# Parses a module source.
# This takes a git url or pseudo-URL patterns such as:
#
# example/my-syntax-on-github
# github:example/my-syntax
# gitlab:example/my-syntax
#
# Arguments:
# 1 -- The source string.
parse_source() {
local source="$1"
# shellcheck disable=SC2059
case "$source" in
"github:"* | "gh:"*)
source="$(printf "$COMMON_URL_GITHUB" "$(cut -d':' -f2- <<< "$source")")"
;;
"gitlab:"* | "gl:"*)
source="$(printf "$COMMON_URL_GITLAB" "$(cut -d':' -f2- <<< "$source")")"
;;
*)
if [[ "$1" =~ ^([A-Za-z0-9-])+/([A-Za-z0-9-])+$ ]]; then
parse_source "github:$1" "${@:2}"
return $?
fi
;;
esac
echo "$source"
}
# Parses the clone directory name of a git repo URL.
# Arguments:
# 1 -- The repo URL.
parse_source_name() {
basename "$1" .git
}
# -----------------------------------------------------------------------------
# Actions:
# -----------------------------------------------------------------------------
action:show_file() {
printf "%s\n" "$MODULES_FILE"
}
action:setup() {
if ! [[ -f "$MODULES_FILE" ]]; then
cat > "$MODULES_FILE" <<-EOF
# bat-modules example file.
# See ${PROGRAM_HOMEPAGE} for documentation and help.
# syntax example/syntax
# theme https://github.com/example/theme.git
# checkout abcdef1
EOF
fi
"${EDITOR:-vi}" "$MODULES_FILE"
}
action:help() {
{
printc "%{YELLOW}%s help:%{CLEAR}\n" "$PROGRAM"
printc " --clear -- Clear the cached themes and syntaxes.\n"
printc " --update -- Update themes and syntaxes.\n"
printc " --setup -- Edit the bat-modules modules.txt file.\n"
printc " --modules-file -- Show the bat-modules modules.txt file.\n"
} 1>&2
}
action:clear() {
printc "%{YELLOW}Clearing bat syntax and theme cache...%{CLEAR}\n"
"$EXECUTABLE_BAT" cache --clear
}
action:update() {
CHANGES=false
dsl_on_command_commit() {
case "$BM_TYPE" in
syntax) cd "$SYNTAX_DIR" ;;
theme) cd "$THEME_DIR" ;;
esac
local hash
local name="$(parse_source_name "$BM_SOURCE")"
printc "%{BLUE}----- %s: %s -----%{CLEAR}\n" "$BM_TYPE" "$name"
# If it isn't cloned, clone it.
if ! [[ -d "$name" ]]; then
printc "%{YELLOW}Cloning...%{CLEAR}\n"
"$EXECUTABLE_GIT" clone "$BM_SOURCE" "$name"
CHANGES=true
fi
# If it is cloned, fetch/checkout.
printc "%{YELLOW}Updating...%{CLEAR}\n"
cd "$name"
hash="$("$EXECUTABLE_GIT" rev-parse HEAD)"
"$EXECUTABLE_GIT" fetch origin --quiet
"$EXECUTABLE_GIT" checkout "$BM_OPT_CHECKOUT" --quiet
hash_new="$("$EXECUTABLE_GIT" rev-parse HEAD)"
if [[ "$hash" != "$hash_new" ]]; then
printc "%{YELLOW}Updated to %s.%{CLEAR}\n" "$hash_new"
CHANGES=true
fi
}
# Parse the DSL.
ensure_setup
dsl_parse_file "$MODULES_FILE"
# If there are changes, update.
printc "%{BLUE}----- bat-modules -----%{CLEAR}\n" "$BM_TYPE" "$name"
printc "%{YELLOW}Done.%{CLEAR}\n"
if "$CHANGES"; then
printc "%{YELLOW}Rebuilding cache...%{CLEAR}\n"
"$EXECUTABLE_BAT" cache --build
fi
}
# -----------------------------------------------------------------------------
# Main:
# -----------------------------------------------------------------------------
action:"$ACTION"
exit $?

227
src/batdiff.sh Executable file
View file

@ -0,0 +1,227 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2020 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
# shellcheck disable=SC1090
LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo ".")")/../lib" && pwd)"
source "${LIB}/constants.sh"
source "${LIB}/print.sh"
source "${LIB}/pager.sh"
source "${LIB}/opt.sh"
source "${LIB}/opt_hook_color.sh"
source "${LIB}/opt_hook_pager.sh"
source "${LIB}/opt_hook_version.sh"
source "${LIB}/opt_hook_width.sh"
source "${LIB}/version.sh"
# -----------------------------------------------------------------------------
# Init:
# -----------------------------------------------------------------------------
hook_color
hook_pager
hook_version
hook_width
# -----------------------------------------------------------------------------
# Options:
# -----------------------------------------------------------------------------
BATDIFF_USE_DELTA="${BATDIFF_USE_DELTA:-}"
SUPPORTS_BAT_DIFF=false
SUPPORTS_DELTA=false
BAT_VERSION="$(bat_version)"
BAT_ARGS=()
DELTA_ARGS=()
DELTA_VERSION='unsupported'
GIT_ARGS=()
FILES=()
OPT_TABS=
OPT_CONTEXT=2
OPT_STAGED=false
OPT_ALL_CHANGES=false
# Set options based on bat version.
if version_compare "$BAT_VERSION" -ge "0.15"; then
SUPPORTS_BAT_DIFF=true
fi
# Set options based on delta availability.
if command -v "$EXECUTABLE_DELTA" &>/dev/null; then
SUPPORTS_DELTA=true
DELTA_VERSION="$("$EXECUTABLE_DELTA" --version | cut -d' ' -f2)"
fi
# Set options based on delta version.
# - 0.12 -- Renamed `--hunk-style` to `--hunk-header-decoration-style`.
if version_compare "$DELTA_VERSION" -ge "0.12"; then
DELTA_ARGS+=("--hunk-header-decoration-style=plain")
else
DELTA_ARGS+=("--hunk-style=plain")
fi
# Parse arguments.
while shiftopt; do
case "$OPT" in
# bat options
-C | --context | --diff-context) shiftval; OPT_CONTEXT="$OPT_VAL" ;;
--terminal-width) shiftval; OPT_TERMINAL_WIDTH="$OPT_VAL" ;;
--tabs) shiftval; OPT_TABS="$OPT_VAL" ;;
# Script options
--all) OPT_ALL_CHANGES=true ;;
--staged) OPT_STAGED=true; GIT_ARGS+=("--staged") ;;
--delta) BATDIFF_USE_DELTA=true ;;
# ???
-*) {
printc "%{RED}%s: unknown option '%s'%{CLEAR}\n" "$PROGRAM" "$OPT" 1>&2
exit 1
} ;;
# Files
*) FILES+=("$OPT") ;;
esac
done
# Append arguments for delta/bat.
BAT_ARGS+=("--terminal-width=${OPT_TERMINAL_WIDTH}" "--paging=never")
DELTA_ARGS+=(
"--width=${OPT_TERMINAL_WIDTH}"
"--paging=never"
)
if "$OPT_COLOR"; then
BAT_ARGS+=("--color=always")
else
BAT_ARGS+=("--color=never")
DELTA_ARGS+=("--theme=none")
fi
if [[ -n "$OPT_TABS" ]]; then
BAT_ARGS+=("--tabs=${OPT_TABS}")
DELTA_ARGS+=("--tabs=${OPT_TABS}")
fi
# Append arguments for git.
GIT_ARGS+=(-U"$OPT_CONTEXT")
# -----------------------------------------------------------------------------
# Functions:
# -----------------------------------------------------------------------------
ensure_git_directory() {
if ! "$EXECUTABLE_GIT" rev-parse --show-toplevel &>/dev/null; then
print_error "Not a git repository."
printc "\n%s\nTo view a diff between two files, use %{CYAN}%s [file] [file]%{CLEAR}." \
"To view a diff between staged files and the working directory, enter a git repository." \
"$PROGRAM"
exit 1
fi
}
# -----------------------------------------------------------------------------
# Printing:
# -----------------------------------------------------------------------------
print_bat_diff() {
local files=("$@")
# Diff two files.
if [[ "${#files[@]}" -eq 2 ]]; then
diff --unified="$OPT_CONTEXT" "${files[@]}" | "$EXECUTABLE_BAT" --language=diff - "${BAT_ARGS[@]}"
return $?
fi
# Diff staged git file.
ensure_git_directory
if "$OPT_STAGED"; then
if false && "$SUPPORTS_DELTA"; then
# bat doesn't support diffing staged changes against the HEAD.
# Delta is better suited for printing diffs in this case.
print_delta_diff "$@"
else
difftext="$("$EXECUTABLE_GIT" diff "${GIT_ARGS[@]}" "${files[0]}")"
if [[ "${#difftext}" -gt 0 ]]; then
"$EXECUTABLE_BAT" --language=diff --file-name="${files[0]}" - "${BAT_ARGS[@]}" <<< "$difftext"
fi
fi
return $?
fi
# Diff git file.
if "$SUPPORTS_BAT_DIFF"; then
"$EXECUTABLE_GIT" diff "${GIT_ARGS[@]}" --name-only -z "${files[0]}" \
| xargs --null "$EXECUTABLE_BAT" --diff --diff-context="$OPT_CONTEXT" "${BAT_ARGS[@]}"
else
"$EXECUTABLE_GIT" diff "${GIT_ARGS[@]}" "${files[0]}" | "$EXECUTABLE_BAT" --language=diff - "${BAT_ARGS[@]}"
fi
}
print_delta_diff() {
local files=("$@")
# Diff two files.
if [[ "${#files[@]}" -eq 2 ]]; then
diff --unified="$OPT_CONTEXT" "${files[@]}" | "$EXECUTABLE_DELTA" "${DELTA_ARGS[@]}"
return $?
fi
# Diff git file.
ensure_git_directory
"$EXECUTABLE_GIT" diff "${GIT_ARGS[@]}" "${files[0]}" | "$EXECUTABLE_DELTA" "${DELTA_ARGS[@]}"
}
if [[ "$BATDIFF_USE_DELTA" = "true" && "$SUPPORTS_DELTA" = "true" ]]; then
print_diff() {
print_delta_diff "$@"
return $?
}
else
print_diff() {
print_bat_diff "$@"
return $?
}
fi
# -----------------------------------------------------------------------------
# Validation:
# -----------------------------------------------------------------------------
# Handle too many files.
if [[ "${#FILES[@]}" -gt 2 ]]; then
print_error "too many files provided"
exit 1
fi
# Handle deprecated --all.
if "$OPT_ALL_CHANGES"; then
print_warning "argument --all is deprecated. Use '%s' instead" "$0"
fi
# -----------------------------------------------------------------------------
# Main:
# -----------------------------------------------------------------------------
main() {
if [[ "${#FILES[@]}" -eq 0 ]] || "$OPT_ALL_CHANGES"; then
ensure_git_directory
local file
while read -r file; do
if [[ -f "$file" ]]; then
print_diff "$file"
fi
done < <("${EXECUTABLE_GIT}" diff "${GIT_ARGS[@]}" --name-only --diff-filter=d)
return
fi
print_diff "${FILES[@]}"
}
pager_exec main
exit $?

View file

@ -5,23 +5,146 @@
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
# shellcheck disable=SC1090
LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo ".")")/../lib" && pwd)"
source "${LIB}/constants.sh"
source "${LIB}/print.sh"
source "${LIB}/pager.sh"
source "${LIB}/opt.sh"
source "${LIB}/opt_hooks.sh"
source "${LIB}/opt_hook_color.sh"
source "${LIB}/opt_hook_help.sh"
source "${LIB}/opt_hook_pager.sh"
source "${LIB}/opt_hook_version.sh"
source "${LIB}/opt_hook_width.sh"
source "${LIB}/version.sh"
# -----------------------------------------------------------------------------
# Init:
# -----------------------------------------------------------------------------
hook_color
hook_help
hook_pager
hook_version
hook_width
# -----------------------------------------------------------------------------
# Help:
# -----------------------------------------------------------------------------
show_help() {
cat <<-'EOF'
Quickly search through and highlight files using ripgrep.
Search through files or directories looking for matching regular expressions (or fixed strings with -F), and print the output using bat for an easy and syntax-highlighted experience.
Usage: batgrep [OPTIONS] PATTERN [PATH...]
Arguments:
[OPTIONS]
See Options below
PATTERN
Pattern passed to ripgrep
[PATH...]
Path(s) to search
Options:
-i, --ignore-case:
Use case insensitive searching.
-s, --case-sensitive:
Use case sensitive searching.
-S, --smart-case:
Use smart case searching
-A, --after-context=[LINES]:
Display the next n lines after a matched line.
-B, --before-context=[LINES]:
Display the previous n lines before a matched line.
-C, --context=[LINES]:
A combination of --after-context and --before-context
-p, --search-pattern:
Tell pager to search for PATTERN. Currently supported pagers: less.
--no-follow:
Do not follow symlinks
--no-snip:
Do not show the snip decoration
This is automatically enabled when --context=0 or when bat --version is less than 0.12.x
--no-highlight:
Do not highlight matching lines.
This is automatically enabled when --context=0.
--color:
Force color output.
--no-color:
Force disable color output.
--paging=["never"/"always"]:
Enable/disable paging.
--pager=[PAGER]:
Specify the pager to use.
--terminal-width=[COLS]:
Generate output for the specified terminal width.
--no-separator:
Disable printing separator between files.
--rga:
Use ripgrep-all instead of ripgrep.
Options passed directly to ripgrep:
-F, --fixed-strings
-U, --multiline
-P, --pcre2
-z, --search-zip
-w, --word-regexp
--one-file-system
--multiline-dotall
--ignore, --no-ignore
--crlf, --no-crlf
--hidden, --no-hidden
-E --encoding:
This is unsupported by bat, and may cause issues when trying to display unsupported encodings.
-g, --glob
-t, --type
-T, --type-not
-m, --max-count
--max-depth
--iglob
--ignore-file
EOF
}
# -----------------------------------------------------------------------------
# Options:
# -----------------------------------------------------------------------------
RIPGREP="$EXECUTABLE_RIPGREP"
RG_ARGS=()
BAT_ARGS=()
PATTERN=""
@ -34,24 +157,61 @@ OPT_SNIP=""
OPT_HIGHLIGHT=true
OPT_SEARCH_PATTERN=false
OPT_FIXED_STRINGS=false
BAT_STYLE="header,numbers"
OPT_NO_SEPARATOR=false
BAT_STYLE="${BAT_STYLE:-header,numbers}"
# Set options based on the bat version.
if version_compare "$(bat_version)" -gt "0.12"; then
OPT_SNIP=",snip"
fi
# Parse RIPGREP_CONFIG_PATH.
if [[ -n "$RIPGREP_CONFIG_PATH" && -e "$RIPGREP_CONFIG_PATH" ]]; then
# shellcheck disable=SC2046
setargs $(cat "$RIPGREP_CONFIG_PATH")
while shiftopt; do
case "$OPT" in
-A | --after-context) shiftval; OPT_CONTEXT_AFTER="$OPT_VAL" ;;
-B | --before-context) shiftval; OPT_CONTEXT_BEFORE="$OPT_VAL" ;;
-C | --context)
shiftval
OPT_CONTEXT_BEFORE="$OPT_VAL"
OPT_CONTEXT_AFTER="$OPT_VAL"
;;
esac
done
fi
# Parse arguments.
shopt -s extglob # Needed to handle -u
# First handle -u specially - it can be repeated multiple times in a single
# short argument, and repeating it 1, 2, or 3 times causes different effects.
resetargs
SHIFTOPT_SHORT_OPTIONS="PASS"
while shiftopt; do
case "$OPT" in
[-]+(u) )
RG_ARGS+=("$OPT")
;;
esac
done
resetargs
SHIFTOPT_SHORT_OPTIONS="VALUE"
while shiftopt; do
case "$OPT" in
# ripgrep options
[-]+([u]) ) ;; # Ignore - handled in first loop.
--unrestricted)
RG_ARGS+=("$OPT")
;;
-i | --ignore-case) OPT_CASE_SENSITIVITY="--ignore-case" ;;
-s | --case-sensitive) OPT_CASE_SENSITIVITY="--case-sensitive" ;;
-S | --smart-case) OPT_CASE_SENSITIVITY="--smart-case" ;;
-A | --after-context) shiftval; OPT_CONTEXT_AFTER="$OPT_VAL" ;;
-B | --before-context) shiftval; OPT_CONTEXT_BEFORE="$OPT_VAL" ;;
-C | --context)
shiftval
OPT_CONTEXT_BEFORE="$OPT_VAL"
@ -95,6 +255,15 @@ while shiftopt; do
--no-highlight) OPT_HIGHLIGHT=false ;;
-p | --search-pattern) OPT_SEARCH_PATTERN=true ;;
--no-search-pattern) OPT_SEARCH_PATTERN=false ;;
--no-separator) OPT_NO_SEPARATOR=true ;;
--rga) {
if ! rga --version | grep 'ripgrep-all' &>/dev/null; then
printc "%{RED}%s: option '--rga' requires ripgrep-all to be installed%{CLEAR}\n" "$PROGRAM" 1>&2
exit 1
fi
RIPGREP='rga'
};;
# Option forwarding
--rg:*) {
@ -108,6 +277,9 @@ while shiftopt; do
fi
} ;;
# --
--) getargs -a FILES; break ;;
# ???
-*) {
printc "%{RED}%s: unknown option '%s'%{CLEAR}\n" "$PROGRAM" "$OPT" 1>&2
@ -115,25 +287,22 @@ while shiftopt; do
} ;;
# Search
*) {
if [ -z "$PATTERN" ]; then
PATTERN="$OPT"
else
FILES+=("$OPT")
fi
} ;;
*) FILES+=("$OPT") ;;
esac
done
# Use the first file as a pattern.
PATTERN="${FILES[0]}"
FILES=("${FILES[@]:1}")
if [[ -z "$PATTERN" ]]; then
print_error "no pattern provided"
exit 1
fi
# Generate separator.
COLS="$(tput cols)"
SEP="$(printc "%{DIM}%${COLS}s%{CLEAR}" | sed "s/ /─/g")"
SEP="$(printc "%{DIM}%${OPT_TERMINAL_WIDTH}s%{CLEAR}" | sed "s/ /─/g")"
# Append ripgrep and bat arguments.
if [[ -n "$OPT_CASE_SENSITIVITY" ]]; then
@ -191,12 +360,50 @@ main() {
LAST_LR=()
LAST_LH=()
LAST_FILE=''
READ_FROM_STDIN=false
NO_SEPARATOR="$OPT_NO_SEPARATOR"
if [[ "$BAT_STYLE" = *grid* ]]; then
NO_SEPARATOR=true
fi
# If we found no files being provided and STDIN to not be attached to a tty,
# we capture STDIN to a variable. This variable will later be written to
# the STDIN file descriptors of both ripgrep and bat.
if ! [[ -t 0 ]] && [[ "${#FILES[@]}" -eq 0 ]]; then
READ_FROM_STDIN=true
IFS='' STDIN_DATA="$(cat)"
fi
do_ripgrep_search() {
local COMMON_RG_ARGS=(
--with-filename \
--vimgrep \
"${RG_ARGS[@]}" \
--context 0 \
--sort path \
-- \
"$PATTERN" \
"${FILES[@]}" \
)
if "$READ_FROM_STDIN"; then
"$RIPGREP" "${COMMON_RG_ARGS[@]}" <<< "$STDIN_DATA"
return $?
else
"$RIPGREP" "${COMMON_RG_ARGS[@]}"
return $?
fi
}
do_print() {
[[ -z "$LAST_FILE" ]] && return 0
# Print the separator.
"$FIRST_PRINT" && echo "$SEP"
if ! "$NO_SEPARATOR"; then
"$FIRST_PRINT" && echo "$SEP"
fi
FIRST_PRINT=false
# Print the file.
@ -205,19 +412,34 @@ main() {
"${LAST_LH[@]}" \
--style="${BAT_STYLE}${OPT_SNIP}" \
--paging=never \
--terminal-width="$COLS" \
--terminal-width="$OPT_TERMINAL_WIDTH" \
"$LAST_FILE"
# Print the separator.
echo "$SEP"
if ! "$NO_SEPARATOR"; then
echo "$SEP"
fi
}
do_print_from_file_or_stdin() {
if [[ "$LAST_FILE" = "<stdin>" ]]; then
# If the file is from STDIN, we provide the STDIN
# contents to bat and tell it to read from STDIN.
LAST_FILE="-"
do_print <<< "$STDIN_DATA"
return $?
else
do_print
return $?
fi
}
# shellcheck disable=SC2034
while IFS=':' read -r file line column; do
while IFS=':' read -r file line column text; do
((FOUND++))
if [[ "$LAST_FILE" != "$file" ]]; then
do_print
do_print_from_file_or_stdin
LAST_FILE="$file"
LAST_LR=()
LAST_LH=()
@ -230,8 +452,8 @@ main() {
LAST_LR+=("--line-range=${line_start}:${line_end}")
[[ "$OPT_HIGHLIGHT" = "true" ]] && LAST_LH+=("--highlight-line=${line}")
done < <(rg --with-filename --vimgrep "${RG_ARGS[@]}" --sort path "$PATTERN" "${FILES[@]}")
do_print
done < <(do_ripgrep_search)
do_print_from_file_or_stdin
# Exit.
if [[ "$FOUND" -eq 0 ]]; then

View file

@ -6,36 +6,110 @@
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
# shellcheck disable=SC1090 disable=SC2155
LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo ".")")/../lib" && pwd)"
if [[ -n "${MANPAGER}" ]]; then BAT_PAGER="$MANPAGER"; fi
SELF_NC="${BASH_SOURCE:-$0}"
SELF="$(cd "$(dirname "${SELF_NC}")" && cd "$(dirname "$(readlink "${SELF_NC}" || echo ".")")" && pwd)/$(basename "$(readlink "${SELF_NC}" || echo "${SELF_NC}")")"
LIB="$(cd "$(dirname "${SELF_NC}")" && cd "$(dirname "$(readlink "${SELF_NC}" || echo ".")")/../lib" && pwd)"
source "${LIB}/constants.sh"
source "${LIB}/pager.sh"
source "${LIB}/print.sh"
source "${LIB}/opt.sh"
source "${LIB}/opt_hooks.sh"
source "${LIB}/opt_hook_color.sh"
source "${LIB}/opt_hook_version.sh"
# -----------------------------------------------------------------------------
hook_color
hook_pager
hook_version
# -----------------------------------------------------------------------------
FORWARDED_ARGS=()
MAN_ARGS=()
BAT_ARGS=()
OPT_EXPORT_ENV=false
SHIFTOPT_SHORT_OPTIONS="SPLIT"
while shiftopt; do
case "$OPT" in
--export-env) OPT_EXPORT_ENV=true ;;
--paging|--pager|--wrap) shiftval; FORWARDED_ARGS+=("${OPT}=${OPT_VAL}");
BAT_ARGS+=("${OPT}=${OPT_VAL}") ;;
*) MAN_ARGS+=("$OPT") ;;
esac
done
while shiftopt; do MAN_ARGS+=("$OPT"); done
if "$OPT_COLOR"; then
BAT_ARGS=("--color=always --decorations=always")
BAT_ARGS+=("--color=always" "--decorations=always")
else
BAT_ARGS=("--color=never --decorations=never")
BAT_ARGS+=("--color=never" "--decorations=never")
fi
if [[ -z "${BAT_STYLE+x}" ]]; then
export BAT_STYLE="grid"
fi
# -----------------------------------------------------------------------------
export MANPAGER='sh -c "col -bx | '"$(printf "%q" "$EXECUTABLE_BAT")"' --language=man --style=grid '"${BAT_ARGS[*]}"'"'
# When called as the manpager, do some preprocessing and feed everything to bat.
if [[ "${BATMAN_IS_BEING_MANPAGER:-}" = "yes" ]]; then
print_manpage() {
sed -e 's/\x1B\[[0-9;]*m//g; s/.\x08//g' \
| "$EXECUTABLE_BAT" --language=man "${BAT_ARGS[@]}"
exit $?
}
if [[ "${#MAN_ARGS[@]}" -eq 1 ]]; then
# The input was passed as a file.
cat "${MAN_ARGS[0]}" | print_manpage
else
# The input was passed via stdin.
cat | print_manpage
fi
exit
fi
# -----------------------------------------------------------------------------
if [[ -n "${MANPAGER}" ]]; then BAT_PAGER="$MANPAGER"; fi
export MANPAGER="env BATMAN_IS_BEING_MANPAGER=yes bash $(printf "%q " "$SELF" "${FORWARDED_ARGS[@]}")"
export MANPAGER="${MANPAGER%"${MANPAGER##*[![:space:]]}"}"
export MANROFFOPT='-c'
if [[ -n "${SCRIPT_PAGER_CMD}" ]]; then
export BAT_PAGER="$(printf "%q " "${SCRIPT_PAGER_CMD[@]}" "${SCRIPT_PAGER_ARGS[@]}")"
else
unset BAT_PAGER
# If `--export-env`, print exports to use batman as the manpager directly.
if "$OPT_EXPORT_ENV"; then
printf "export %s=%q\n" \
"MANPAGER" "$MANPAGER" \
"MANROFFOPT" "$MANROFFOPT"
exit 0
fi
# If no argument is provided and fzf is installed, use fzf to search for man pages.
if [[ "${#MAN_ARGS[@]}" -eq 0 ]] && [[ -z "$BATMAN_LEVEL" ]] && command -v "$EXECUTABLE_FZF" &>/dev/null; then
export BATMAN_LEVEL=1
selected_page="$(man -k . | "$EXECUTABLE_FZF" --delimiter=" - " --reverse -e --preview="
echo {1} \
| sed 's/, /\n/g;' \
| sed 's/\([^(]*\)(\([0-9A-Za-z ]\))/\2\t\1/g' \
| BAT_STYLE=plain xargs -n2 batman --color=always --paging=never
")"
if [[ -z "$selected_page" ]]; then
exit 0
fi
# Some entries from `man -k .` may include "synonyms" of a man page's title.
# For example, the manual for kubectl-edit will appear as:
#
# kubectl-edit(1), kubectl edit(1) - Edit a resource on the server
#
# `man` only needs one name/title, so we're taking the first one here.
selected_page_unaliased="$(echo "$selected_page" | cut -d, -f1)"
# Convert the page(section) format to something that can be fed to the man command.
while read -r line; do
if [[ "$line" =~ ^(.*)\(([0-9a-zA-Z ]+)\) ]]; then
MAN_ARGS+=("${BASH_REMATCH[2]}" "$(echo ${BASH_REMATCH[1]} | xargs)")
fi
done <<< "$selected_page_unaliased"
fi
# Run man.
command man "${MAN_ARGS[@]}"
exit $?

523
src/batpipe.sh Executable file
View file

@ -0,0 +1,523 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2021 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
#
# EXTERNAL VIEWERS FOR BATPIPE:
#
# External viewers can be added to batpipe by creating bash scripts
# inside the `~/.config/batpipe/viewers.d/` directory.
#
# CREATING A VIEWER:
#
# Viewers must define two functions and append the viewer's name to the
# `BATPIPE_VIEWERS` array.
#
# - viewer_${viewer}_supports [file_basename] [file_path] [inner_file_path]
# If this returns 0, the viewer's process function will be used.
#
# - viewer_${viewer}_process [file_path] [inner_file_path]
#
# VIEWER API:
#
# $BATPIPE_VIEWERS -- An array of loaded file viewers.
# $BATPIPE_ENABLE_COLOR -- Whether color is supported. (`true`|`false`)
# $BATPIPE_INSIDE_LESS -- Whether batpipe is inside less. (`true`|`false`)
# $TERM_WIDTH -- The terminal width. (only supported in `less`)
#
# batpipe_header [pattern] [...] -- Print a viewer header line.
# batpipe_subheader [pattern] [...] -- Print a viewer subheader line.
#
# bat -- Use `bat` for highlighting. If running inside `bat`, does nothing.
#
# strip_trailing_slashes [path] -- Strips trailing slashes from a path.
#
# -----------------------------------------------------------------------------
# shellcheck disable=SC1090 disable=SC2155
SELF_NC="${BASH_SOURCE:-$0}"
SELF="$(cd "$(dirname "${SELF_NC}")" && cd "$(dirname "$(readlink "${SELF_NC}" || echo ".")")" && pwd)/$(basename "${SELF_NC}")"
LIB="$(cd "$(dirname "${SELF_NC}")" && cd "$(dirname "$(readlink "${SELF_NC}" || echo ".")")/../lib" && pwd)"
source "${LIB}/constants.sh"
source "${LIB}/dirs.sh"
source "${LIB}/str.sh"
source "${LIB}/print.sh"
source "${LIB}/path.sh"
source "${LIB}/proc.sh"
source "${LIB}/opt.sh"
source "${LIB}/version.sh"
source "${LIB}/term.sh"
# -----------------------------------------------------------------------------
# Usage/Install:
# -----------------------------------------------------------------------------
if [[ "$#" -eq 0 ]]; then
# If writing to a terminal, display instructions and help.
if [[ -t 1 ]]; then
printc "%{DIM}# %s, %s.\n# %s\n# %s\n# %s\n# \n# %s%{CLEAR}\n" \
"$PROGRAM" \
"a bat-based preprocessor for less and bat" \
"Version: $PROGRAM_VERSION" \
"Homepage: $PROGRAM_HOMEPAGE" \
"$PROGRAM_COPYRIGHT" \
"To use $PROGRAM, eval the output of this command in your shell init script."
fi
# Detect the shell.
#
# This will directly check if the parent is a non-sh/bash shell, since
# there's a good chance that `bash` or `sh` will be invoking it.
case "$(basename -- "$(parent_executable | cut -f1 -d' ')")" in
fish) detected_shell="fish" ;;
nu) detected_shell="nu" ;;
*) detected_shell="$(parent_shell)" ;;
esac
# Print the commands required to add `batpipe` to the environment variables.
case "$(basename -- "${detected_shell:bash}")" in
fish) # Fish
printc '%{YELLOW}set -x %{CLEAR}LESSOPEN %{CYAN}"|%q %%s"%{CLEAR};\n' "$SELF"
printc '%{YELLOW}set -e %{CLEAR}LESSCLOSE;\n'
;;
nu) # Nushell
printc '%{BLUE}$env%{CLEAR}.LESSOPEN = %{CYAN}"|%q %%s"%{CLEAR}\n' "$SELF"
if [[ "${LESSCLOSE:-}" != "" ]]; then
printc '%{BLUE}hide-env%{CLEAR} LESSCLOSE\n' "$SELF"
fi
;;
*) # Bash-like
printc '%{MAGENTA}LESSOPEN%{CLEAR}=%{CYAN}"|%s %%s"%{CLEAR};\n' "$SELF"
printc '%{YELLOW}export%{CLEAR} LESSOPEN;\n' "$SELF"
printc '%{YELLOW}unset%{CLEAR} LESSCLOSE;\n'
;;
esac
# Print the commands required to use color in `less` with `batpipe`.
if [[ -t 1 ]]; then
printc "\n%{DIM}# The following will enable colors when using batpipe with less:%{CLEAR}\n"
fi
# shellcheck disable=SC2016
case "$(basename -- "${detected_shell:bash}")" in
fish) # Fish
printc '%{YELLOW}set -x %{CLEAR}LESS %{CYAN}"%{MAGENTA}$LESS%{CYAN} -R"%{CLEAR};\n' "$SELF"
printc '%{YELLOW}set -x %{CLEAR}BATPIPE %{CYAN}"color"%{CLEAR};\n'
;;
nu) # Nushell
printc '%{BLUE}$env%{CLEAR}.LESS = %{CYAN}$"%{MAGENTA}($env.LESS)%{CYAN} -R"%{CLEAR}\n' "$SELF"
printc '%{BLUE}$env%{CLEAR}.BATPIPE = %{CYAN}"color"%{CLEAR}\n' "$SELF"
;;
*) # Bash-like
printc '%{MAGENTA}LESS%{CLEAR}=%{CYAN}"%{MAGENTA}$LESS%{CYAN} -R"%{CLEAR};\n' "$SELF"
printc '%{MAGENTA}BATPIPE%{CLEAR}=%{CYAN}"color"%{CLEAR};\n' "$SELF"
printc '%{YELLOW}export%{CLEAR} LESS;\n' "$SELF"
printc '%{YELLOW}export%{CLEAR} BATPIPE;\n' "$SELF"
;;
esac
exit 0
fi
# -----------------------------------------------------------------------------
# Init:
# -----------------------------------------------------------------------------
BATPIPE_INSIDE_LESS=false
BATPIPE_INSIDE_BAT=false
TERM_WIDTH="$(term_width)"
if [[ -n "${BATPIPE_TERM_WIDTH:-}" ]]; then
if [[ "${BATPIPE_TERM_WIDTH:0:1}" = "-" ]]; then
TERM_WIDTH=$((TERM_WIDTH + BATPIPE_TERM_WIDTH)) || true
else
TERM_WIDTH="$BATPIPE_TERM_WIDTH"
fi
fi
BATPIPE_PARENT_EXECUTABLE_PID="$PPID"
for i in 1 2 3; do
BATPIPE_PARENT_EXECUTABLE="${BATPIPE_DEBUG_PARENT_EXECUTABLE:-$(parent_executable "$BATPIPE_PARENT_EXECUTABLE_PID")}"
BATPIPE_PARENT_EXECUTABLE_BASENAME="$(basename -- "${BATPIPE_PARENT_EXECUTABLE}" | cut -d' ' -f1)"
BATPIPE_PARENT_EXECUTABLE_PID="$(parent_executable_pid "$BATPIPE_PARENT_EXECUTABLE_PID")"
if [[ "${BATPIPE_PARENT_EXECUTABLE_BASENAME}" = "less" ]]; then
BATPIPE_INSIDE_LESS=true
break
elif [[ "${BATPIPE_PARENT_EXECUTABLE_BASENAME}" == "$(basename -- "$EXECUTABLE_BAT")" ]]; then
BATPIPE_INSIDE_BAT=true
break
fi
done
if [[ -n "${BATPIPE_DEBUG:-}" ]]; then
printf "batpipe debug:\n"
printf " %s: %s\n" \
"BATPIPE_INSIDE_LESS" "${BATPIPE_INSIDE_LESS}" \
"BATPIPE_INSIDE_BAT" "${BATPIPE_INSIDE_BAT}"
printf "\n"
fi
# -----------------------------------------------------------------------------
# Viewers:
# -----------------------------------------------------------------------------
if ! command -v eza &> /dev/null
then
BATPIPE_VIEWERS=("eza" "ls" "tar" "tar_gz" "unzip" "gunzip" "xz")
else
BATPIPE_VIEWERS=("exa" "ls" "tar" "tar_bz2" "unzip" "gunzip" "xz")
fi
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
viewer_exa_supports() {
[[ -d "$2" ]] || return 1
command -v "exa" &> /dev/null || return 1
return 0
}
viewer_eza_supports() {
[[ -d "$2" ]] || return 1
command -v "eza" &> /dev/null || return 1
return 0
}
viewer_exa_process() {
local dir="$(strip_trailing_slashes "$1")"
batpipe_header "Viewing contents of directory: %{PATH}%s" "$dir"
if "$BATPIPE_ENABLE_COLOR"; then
exa -la --color=always "$1" 2>&1
else
exa -la --color=never "$1" 2>&1
fi
return $?
}
viewer_eza_process() {
local dir="$(strip_trailing_slashes "$1")"
batpipe_header "Viewing contents of directory: %{PATH}%s" "$dir"
if "$BATPIPE_ENABLE_COLOR"; then
eza -la --color=always "$1" 2>&1
else
eza -la --color=never "$1" 2>&1
fi
return $?
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
viewer_ls_supports() {
[[ -d "$2" ]]
return $?
}
viewer_ls_process() {
local dir="$(strip_trailing_slashes "$1")"
batpipe_header "Viewing contents of directory: %{PATH}%s" "$dir"
ls -lA "$1" 2>&1
return $?
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
viewer_tar_supports() {
command -v "tar" &> /dev/null || return 1
case "$1" in
*.tar | *.tar.*) return 0 ;;
esac
return 1
}
viewer_tar_process() {
if [[ -n "$2" ]]; then
tar $3 -xf "$1" -O "$2" | bat --file-name="$1/$2"
else
batpipe_archive_header
tar $3 -tvf "$1"
return $?
fi
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
viewer_tar_gz_supports() {
command -v "tar" &> /dev/null || return 1
command -v "gzip" &> /dev/null || return 1
case "$1" in
*.tar.gz | *.tgz) return 0 ;;
esac
return 1
}
viewer_tar_gz_process() {
viewer_tar_process "$1" "$2" -z
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
viewer_tar_bz2_supports() {
command -v "tar" &> /dev/null || return 1
command -v "bzip2" &> /dev/null || return 1
case "$1" in
*.tar.bz2 | *.tbz) return 0 ;;
esac
return 1
}
viewer_tar_bz2_process() {
viewer_tar_process "$1" "$2" -j
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
viewer_unzip_supports() {
command -v "unzip" &> /dev/null || return 1
case "$1" in
*.zip) return 0 ;;
esac
return 1
}
viewer_unzip_process() {
if [[ -n "$2" ]]; then
unzip -p "$1" "$2" | bat --file-name="$1/$2"
else
batpipe_archive_header
unzip -l "$1"
return $?
fi
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
viewer_gunzip_supports() {
command -v "gunzip" &> /dev/null || return 1
[[ -z "$3" ]] || return 1
case "$2" in
*.gz) return 0 ;;
esac
return 1
}
viewer_gunzip_process() {
gunzip -k -c "$1" | bat --file-name="$1"
return $?
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
viewer_xz_supports() {
command -v "xz" &> /dev/null || return 1
[[ -z "$3" ]] || return 1
case "$2" in
*.xz) return 0 ;;
esac
return 1
}
viewer_xz_process() {
xz --decompress -k -c "$1" | bat --file-name="$1"
return $?
}
# -----------------------------------------------------------------------------
# Functions:
# -----------------------------------------------------------------------------
# Print a header for batpipe messages.
# Arguments:
# 1 -- The printc formatting string.
# ... -- The printc formatting arguments.
batpipe_header() {
local pattern="${1//%{C\}/%{C\}%{HEADER\}}"
printc "%{HEADER}==> $pattern%{C}\n" "${@:2}"
}
# Print a subheader for batpipe messages.
# Arguments:
# 1 -- The printc formatting string.
# ... -- The printc formatting arguments.
batpipe_subheader() {
local pattern="${1//%{C\}/%{C\}%{SUBHEADER\}}"
printc "%{SUBHEADER}==> $pattern%{C}\n" "${@:2}"
}
batpipe_archive_header() {
batpipe_header "Viewing contents of archive: %{PATH}%s" "$1"
batpipe_subheader "To view files within the archive, add the file path after the archive."
}
# Executes `bat` (or `cat`, if already running from within `bat`).
# Supports the `--file-name` argument if the bat version is new enough.
#
# NOTE: The `--key=value` option syntax is required for full compatibility.
bat() {
# Conditionally enable forwarding of certain arguments.
if [[ -z "$__BAT_VERSION" ]]; then
__BAT_VERSION="$(bat_version)"
__bat_forward_arg_file_name() { :; }
if version_compare "$__BAT_VERSION" -ge "0.14"; then
__bat_forward_arg_file_name() {
__bat_forward_args+=("--file-name" "$1")
}
fi
fi
# Parse arguments intended for bat.
__bat_batpipe_args=()
__bat_forward_args=()
__bat_files=()
setargs "$@"
while shiftopt; do
case "$OPT" in
--file-name)
shiftval
__bat_forward_arg_file_name "$OPT_VAL"
;;
# Disallowed forwarding.
--paging) shiftval ;;
--decorations) shiftval ;;
--style) shiftval ;;
--terminal-width) shiftval ;;
--plain | -p | -pp | -ppp) : ;;
# Forward remaining.
-*) {
__bat_forward_args+=("$OPT")
if [[ -n "$OPT_VAL" ]]; then
__bat_forward_args+=("$OPT_VAL")
fi
} ;;
*) {
__bat_forward_args+=("$OPT")
__bat_files=("$OPT")
} ;;
esac
done
# Insert batpipe arguments.
if "$BATPIPE_INSIDE_LESS"; then
__bat_batpipe_args+=(--decorations=always)
__bat_batpipe_args+=(--terminal-width="$TERM_WIDTH")
if "$BATPIPE_ENABLE_COLOR"; then
__bat_batpipe_args+=(--color=always)
else
__bat_batpipe_args+=(--color=never)
fi
fi
if "$BATPIPE_INSIDE_BAT"; then
if [[ "${#__bat_files[@]}" -eq 0 ]]; then
cat
else
cat "${__bat_files[@]}"
fi
fi
# Execute the real bat.
command "$EXECUTABLE_BAT" --paging=never "${__bat_batpipe_args[@]}" "${__bat_forward_args[@]}"
}
# -----------------------------------------------------------------------------
# Colors:
# -----------------------------------------------------------------------------
printc_init "[DEFINE]" << END
C \x1B[0m
SUBPATH \x1B[2;35m
PATH \x1B[0;35m
HEADER \x1B[0;36m
SUBHEADER \x1B[2;36m
END
# Enable color output if:
# - Parent is not less OR BATPIPE=color; AND
# - NO_COLOR is not defined.
#
# shellcheck disable=SC2034
if [[ "$BATPIPE_INSIDE_LESS" == "false" || "$BATPIPE" == "color" ]] && [[ -z "${NO_COLOR+x}" ]]; then
BATPIPE_ENABLE_COLOR=true
printc_init true
else
BATPIPE_ENABLE_COLOR=false
printc_init false
fi
# -----------------------------------------------------------------------------
# Main:
# -----------------------------------------------------------------------------
__CONFIG_DIR="$(config_dir batpipe)"
__TARGET_INSIDE=""
__TARGET_FILE="$(strip_trailing_slashes "$1")"
# Determine the target file by walking upwards from the specified path.
# This allows inner paths of archives to be used.
while ! [[ -e "$__TARGET_FILE" ]]; do
__TARGET_INSIDE="$(basename -- "${__TARGET_FILE}")/${__TARGET_INSIDE}"
__TARGET_FILE="$(dirname -- "${__TARGET_FILE}")"
done
# If the target file isn't actually a file, then the inner path should be appended.
if ! [[ -f "$__TARGET_FILE" ]]; then
__TARGET_FILE="${__TARGET_FILE}/${__TARGET_INSIDE}"
__TARGET_INSIDE=""
fi
# If an inner path exists or the target file isn't a directory, the target file should not have trailing slashes.
if [[ -n "$__TARGET_INSIDE" ]] || ! [[ -d "$__TARGET_FILE" ]]; then
__TARGET_FILE="$(strip_trailing_slashes "$__TARGET_FILE")"
fi
# Remove trailing slash of the inner target path.
__TARGET_INSIDE="$(strip_trailing_slashes "$__TARGET_INSIDE")"
__TARGET_BASENAME="$(basename -- "$__TARGET_FILE")"
# Stop bat from calling this recursively.
unset LESSOPEN
unset LESSCLOSE
# Load external viewers.
if [[ -d "${__CONFIG_DIR}/viewers.d" ]]; then
unset LIB
unset SELF
shopt -s nullglob
for viewer_script in "${__CONFIG_DIR}/viewers.d"/*; do
source "${viewer_script}"
done
shopt -u nullglob
fi
# Try opening the file with the first viewer that supports it.
for viewer in "${BATPIPE_VIEWERS[@]}"; do
if "viewer_${viewer}_supports" "$__TARGET_BASENAME" "$__TARGET_FILE" "$__TARGET_INSIDE" 1>&2; then
"viewer_${viewer}_process" "$__TARGET_FILE" "$__TARGET_INSIDE"
exit $?
fi
done
# No supported viewer. Just pass it through (if using bat).
if [[ "$BATPIPE_INSIDE_BAT" == true ]]; then
exit 1
fi
# No supported viewer... highlight it using bat.
if [[ -f "$1" ]]; then
bat "$1"
fi

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2019 eth-p | MIT License
# bat-extras | Copyright (C) 2019-2020 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues: https://github.com/eth-p/bat-extras/issues
@ -9,15 +9,27 @@
LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo ".")")/../lib" && pwd)"
source "${LIB}/constants.sh"
source "${LIB}/opt.sh"
source "${LIB}/opt_hooks.sh"
source "${LIB}/opt_hook_color.sh"
source "${LIB}/opt_hook_help.sh"
source "${LIB}/opt_hook_version.sh"
source "${LIB}/opt_hook_width.sh"
source "${LIB}/print.sh"
source "${LIB}/pager.sh"
source "${LIB}/version.sh"
# -----------------------------------------------------------------------------
# Init:
# -----------------------------------------------------------------------------
hook_color
hook_pager
hook_version
hook_width
hook_help
# -----------------------------------------------------------------------------
# Help:
# -----------------------------------------------------------------------------
show_help() {
echo 'Usage: batwatch --file [--watcher entr|poll][--[no-]clear] <file> [<file> ...]'
echo ' batwatch --command [-n<interval>] <command> [<arg> ...]'
}
# -----------------------------------------------------------------------------
# Watchers:
# -----------------------------------------------------------------------------
@ -29,20 +41,20 @@ WATCHERS=("entr" "poll")
watcher_entr_watch() {
ENTR_ARGS=()
if [[ "$OPT_CLEAR" = "true" ]]; then
if [[ "$OPT_CLEAR" == "true" ]]; then
ENTR_ARGS+=('-c')
fi
entr "${ENTR_ARGS[@]}" \
"$EXECUTABLE_BAT" "${BAT_ARGS[@]}" \
--terminal-width="$TERM_WIDTH" \
--terminal-width="$OPT_TERMINAL_WIDTH" \
--paging=never \
"$@" \
< <(printf "%s\n" "$@")
}
watcher_entr_supported() {
command -v entr &>/dev/null
command -v entr &> /dev/null
return $?
}
@ -56,19 +68,22 @@ determine_stat_variant() {
return 0
fi
# Try GNU stat.
if stat -c '%z' "$0" &>/dev/null; then
POLL_STAT_COMMAND=(stat -c '%z')
POLL_STAT_VARIANT='gnu'
return 0
fi
local variant name flags ts
for variant in "gnu -c %Z" "bsd -f %m"; do
read -r name flags <<< "$variant"
# Try BSD stat.
if stat -f '%m' "$0" &>/dev/null; then
POLL_STAT_COMMAND=(stat -f '%m')
POLL_STAT_VARIANT='bsd'
return 0
fi
# save the results of the stat command
if read -r ts < <(stat ${flags} "$0" 2> /dev/null); then
# verify that the value is an epoch timestamp
# before proceeding
if [[ "${ts}" =~ ^[0-9]+$ ]]; then
POLL_STAT_COMMAND=(stat ${flags})
POLL_STAT_VARIANT="$name"
return 0
fi
fi
done
return 1
}
@ -92,17 +107,15 @@ watcher_poll_watch() {
while true; do
if "$modified"; then
modified=false
if [[ "$OPT_CLEAR" = "true" ]]; then
clear
fi
clear
"$EXECUTABLE_BAT" "${BAT_ARGS[@]}" \
--terminal-width="$TERM_WIDTH" \
--terminal-width="$OPT_TERMINAL_WIDTH" \
--paging=never \
"${files[@]}"
fi
# Check if the file has been modified.
local i=0
for file in "${files[@]}"; do
time="$("${POLL_STAT_COMMAND[@]}" "$file")"
@ -115,7 +128,12 @@ watcher_poll_watch() {
((i++))
done
sleep 1
# Wait for "q" to exit, or check again after a few seconds.
local input
read -r -t "${OPT_INTERVAL}" input
if [[ "$input" =~ [q|Q] ]]; then
exit
fi
done
"${POLL_STAT_COMMAND[@]}" "$@"
@ -135,7 +153,7 @@ determine_watcher() {
local watcher
for watcher in "${WATCHERS[@]}"; do
if "watcher_${watcher}_supported"; then
echo "$watcher"
OPT_WATCHER="$watcher"
return 0
fi
done
@ -146,12 +164,13 @@ determine_watcher() {
# -----------------------------------------------------------------------------
# Options:
# -----------------------------------------------------------------------------
BAT_ARGS=()
BAT_ARGS=(--paging=never)
FILES=()
FILES_HAS_DIRECTORY=false
OPT_MODE=file
OPT_INTERVAL=3
OPT_CLEAR=true
OPT_WATCHER=""
TERM_WIDTH="$(tput cols)"
# Set options based on tty.
if [[ -t 1 ]]; then
@ -162,38 +181,52 @@ fi
while shiftopt; do
case "$OPT" in
# Script options
--watcher) shiftval; OPT_WATCHER="$OPT_VAL" ;;
--clear) OPT_CLEAR=true ;;
--no-clear) OPT_CLEAR=false ;;
--terminal-width) shiftval; TERM_WIDTH="$OPT_VAL" ;;
# Script options
--watcher) shiftval; OPT_WATCHER="$OPT_VAL" ;;
--interval|-n) shiftval; OPT_INTERVAL="$OPT_VAL" ;;
--file|-f) OPT_MODE=file ;;
--command|-x) OPT_MODE=command ;;
--clear) OPT_CLEAR=true ;;
--no-clear) OPT_CLEAR=false ;;
# bat/Pager options
-*) BAT_ARGS+=("$OPT=$OPT_VAL") ;;
# bat/Pager options
-*) BAT_ARGS+=("$OPT=$OPT_VAL") ;;
# Files
*) {
FILES+=("$OPT")
} ;;
# Files
*) {
FILES+=("$OPT")
if [[ "$OPT_MODE" = "command" ]]; then
getargs --append FILES
break
fi
} ;;
esac
done
# Validate that a file/command was provided.
if [[ ${#FILES[@]} -eq 0 ]]; then
print_error "no files provided"
if [[ "$OPT_MODE" = "file" ]]; then
print_error "no files provided"
else
print_error "no command provided"
fi
exit 1
fi
for file in "${FILES[@]}"; do
if ! [[ -e "$file" ]]; then
print_error "'%s' does not exist" "$file"
exit 1
fi
if [[ -d "$file" ]]; then
FILES_HAS_DIRECTORY=true
fi
done
# Validate that the provided files exist.
if [[ "$OPT_MODE" = "file" ]]; then
for file in "${FILES[@]}"; do
if ! [[ -e "$file" ]]; then
print_error "'%s' does not exist" "$file"
exit 1
fi
if [[ -d "$file" ]]; then
FILES_HAS_DIRECTORY=true
fi
done
fi
# Append bat arguments.
if "$OPT_COLOR"; then
@ -202,33 +235,76 @@ else
BAT_ARGS+=("--color=never")
fi
# Initialize clear command based on whether or not ANSI should be used.
if [[ "$OPT_CLEAR" == "true" ]]; then
if "$OPT_COLOR"; then
clear() {
term_clear || return $?
}
fi
else
clear() {
:
}
fi
# -----------------------------------------------------------------------------
# Main:
# -----------------------------------------------------------------------------
# Determine the watcher.
if [[ -z "$OPT_WATCHER" ]]; then
if ! OPT_WATCHER="$(determine_watcher)"; then
print_error "Your system does not have any supported watchers."
printc "Please read the documentation at %{BLUE}%s%{CLEAR} for more details.\n" "$PROGRAM_HOMEPAGE" 1>&2
exit 2
if [[ "$OPT_MODE" = "file" ]]; then
# Determine the watcher.
if [[ -z "$OPT_WATCHER" ]]; then
if ! determine_watcher; then
print_error "Your system does not have any supported watchers."
printc "Please read the documentation at %{BLUE}%s%{CLEAR} for more details.\n" "$PROGRAM_HOMEPAGE" 1>&2
exit 2
fi
else
if ! type "watcher_${OPT_WATCHER}_supported" &> /dev/null; then
print_error "Unknown watcher: '%s'" "$OPT_WATCHER"
exit 1
fi
if ! "watcher_${OPT_WATCHER}_supported" &> /dev/null; then
print_error "Unsupported watcher: '%s'" "$OPT_WATCHER"
exit 1
fi
fi
main() {
"watcher_${OPT_WATCHER}_watch" "${FILES[@]}"
return $?
}
else
if ! type "watcher_${OPT_WATCHER}_supported" &>/dev/null; then
print_error "Unknown watcher: '%s'" "$OPT_WATCHER"
exit 1
# Set bat's header to show the command.
BAT_VERSION="$(bat_version)"
if version_compare "$BAT_VERSION" -ge "0.14"; then
BAT_ARGS+=(--file-name="${FILES[*]}")
fi
if ! "watcher_${OPT_WATCHER}_supported" &>/dev/null; then
print_error "Unsupported watcher: '%s'" "$OPT_WATCHER"
exit 1
fi
main() {
local last_rendered
local rendered
local term_width="$(term_width)"
BAT_ARGS+=("--terminal-width=$term_width")
while true; do
IFS='' rendered="$("${FILES[@]}" 2>&1 | "$EXECUTABLE_BAT" "${BAT_ARGS[@]}")"
if [ "$rendered" != "$last_rendered" ]; then
# Only clear and redraw if there's a change.
# This reduces excessive flickering.
last_rendered="$rendered"
clear
printf "%s\n" "$rendered"
rendered=''
fi
sleep "${OPT_INTERVAL}" || exit 1
done
}
fi
# Run the main function.
main() {
"watcher_${OPT_WATCHER}_watch" "${FILES[@]}"
return $?
}
pager_exec main
main
exit $?

View file

@ -12,6 +12,9 @@ source "${LIB}/opt.sh"
source "${LIB}/opt_hook_version.sh"
source "${LIB}/str.sh"
source "${LIB}/print.sh"
source "${LIB}/version.sh"
source "${LIB}/check.sh"
source "${LIB}/term.sh"
# -----------------------------------------------------------------------------
# Init:
# -----------------------------------------------------------------------------
@ -20,16 +23,20 @@ hook_version
# Formatters:
# -----------------------------------------------------------------------------
FORMATTERS=("prettier" "rustfmt" "shfmt" "clangformat")
FORMATTERS=(
"yq" "prettier" "rustfmt" "shfmt" "clangformat"
"black" "mix_format" "column"
)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
formatter_prettier_supports() {
case "$1" in
.js | .jsx | \
.js | .jsx | \
.ts | .tsx | \
.css | .scss | .sass | \
.html | \
.graphql | .gql | \
.html | .svg | \
.json | \
.md | \
.yml)
@ -41,7 +48,14 @@ formatter_prettier_supports() {
}
formatter_prettier_process() {
prettier --stdin --stdin-filepath "$1" 2>/dev/null
# Rewrite the file extension to hackily support SVG.
local file="$1"
local fext="$(extname "$file")"
case "$fext" in
.svg) file="$(basename -- "$file" "$fext").html" ;;
esac
prettier --stdin --stdin-filepath "$file" 2>/dev/null
return $?
}
@ -88,12 +102,122 @@ formatter_shfmt_process() {
return $?
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
formatter_black_supports() {
case "$1" in
.py | \
.py3 | \
.pyw | \
.pyi)
return 0
;;
esac
return 1
}
formatter_black_process() {
black --code "$(cat -)"
return $?
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
formatter_mix_format_supports() {
case "$1" in
.ex | \
.exs | \
.eex | \
.heex)
return 0
;;
esac
return 1
}
formatter_mix_format_process() {
mix format
return $?
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
formatter_column_supports() {
case "$1" in
.tsv)
return 0
;;
esac
return 1
}
formatter_column_process() {
local needs_newline=true
local args=(
-t
-s $'\t'
-c "$TERMINAL_WIDTH"
)
if column --help &>/dev/null; then
# GNU `column`
args+=(--keep-empty-lines)
needs_newline=false
fi
{ { "$needs_newline" && sed 's/$/\n/'; } || cat; } | column "${args[@]}"
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
formatter_yq_supports__version_ok() {
local yq_version
yq_version=$(yq --version | sed 's/^.* version v//')
# If it's older than yq version 4, replace the functions to save processing.
if version_compare "$yq_version" -lt 4.0; then
formatter_yq_supports() { return 1; }
formatter_yq_supports__version_ok() { return 1; }
return 1
fi
# It's supported.
formatter_yq_supports__version_ok() { return 0; }
return 0
}
formatter_yq_supports() {
case "$1" in
.yaml | yml | \
.json)
formatter_yq_supports__version_ok
return $?
;;
esac
return 1
}
formatter_yq_process() {
local args=()
case "$1" in
*.json) args+=(--output-format json) ;;
esac
yq --prettyPrint --indent 4 "${args[@]}" \
| sed -e ':l' -e 's/^\(\t*\) /\1\t/g; t l'
return $?
}
# -----------------------------------------------------------------------------
# Functions:
# -----------------------------------------------------------------------------
# This function will map a bat `--language=...` argument into an appropriate
# file extension for the language provided. This must be hardcoded for
# file extension for the language provided. This should be hardcoded for
# performance reasons.
map_language_to_extension() {
local ext=".txt"
@ -107,11 +231,17 @@ map_language_to_extension() {
css) ext=".css" ;;
scss) ext=".scss" ;;
sass) ext=".sass" ;;
svg ) ext=".svg" ;;
html | htm | shtml | xhtml) ext=".html" ;;
json) ext=".json" ;;
md | mdown | markdown) ext=".md" ;;
yaml | yml) ext=".yml" ;;
rust | rs) ext=".rs" ;;
graphql | gql) ext=".graphql" ;;
python | py) ext=".py" ;;
elixir | ex) ext=".ex" ;;
exs) ext=".exs" ;;
tsv) ext=".tsv" ;;
esac
echo "$ext"
@ -138,28 +268,38 @@ extname() {
}
print_file() {
if [[ "${#BAT_ARGS[@]}" -eq 0 ]]; then
if [[ "${#PRINT_ARGS[@]}" -eq 0 ]]; then
"$EXECUTABLE_BAT" "$@"
return $?
else
"$EXECUTABLE_BAT" "${BAT_ARGS[@]}" "$@"
"$EXECUTABLE_BAT" "${PRINT_ARGS[@]}" "$@"
return $?
fi
}
process_file() {
PRINT_ARGS=("${BAT_ARGS[@]}")
local file="$1"
local ext="$2"
local fext="$ext"
local lang="${ext:1}"
local formatter
# Check that the file exists, and is a file.
if [[ "$file" != "-" ]]; then
check_exists "$file" || return 1
check_is_file "$file" || return 1
fi
# Determine the formatter.
if [[ -n "$OPT_LANGUAGE" ]]; then
lang="$OPT_LANGUAGE"
fext="$(map_language_to_extension "$lang")"
fi
formatter="$(map_extension_to_formatter "$fext")"
if [[ "$ext" != "-" ]]; then
formatter="$(map_extension_to_formatter "$fext")"
fi
# Debug: Print the name and formatter.
if "$DEBUG_PRINT_FORMATTER"; then
@ -167,6 +307,9 @@ process_file() {
return 0
fi
# Calculate additional print arguments.
forward_file_name "$file"
# Print the formatted file.
if [[ "$formatter" = "none" ]]; then
if [[ -z "$OPT_LANGUAGE" ]]; then
@ -184,7 +327,7 @@ process_file() {
# shellcheck disable=SC2094 disable=SC2181
if [[ "$file" = "-" ]]; then
data_raw="$(cat -)"
data_formatted="$("formatter_${formatter}_process" "$file" 2>/dev/null <<<"$data_raw")"
data_formatted="$("formatter_${formatter}_process" "STDIN${fext}" 2>/dev/null <<<"$data_raw")"
if [[ $? -ne 0 ]]; then
print_warning "'STDIN': Unable to format with '%s'" "$formatter"
@ -205,6 +348,19 @@ process_file() {
return $?
}
# -----------------------------------------------------------------------------
# Version-Specific Features:
# -----------------------------------------------------------------------------
BAT_VERSION="$(bat_version)"
forward_file_name() { :; }
if version_compare "$BAT_VERSION" -ge "0.14"; then
forward_file_name() {
PRINT_ARGS+=("--file-name" "$1")
}
fi
# -----------------------------------------------------------------------------
# Main:
# -----------------------------------------------------------------------------
@ -213,6 +369,8 @@ OPT_LANGUAGE=
FILES=()
DEBUG_PRINT_FORMATTER=false
TERMINAL_WIDTH="$(term_width)"
# Parse arguments.
while shiftopt; do
case "$OPT" in
@ -225,9 +383,14 @@ while shiftopt; do
# Debug options
--debug:formatter) DEBUG_PRINT_FORMATTER=true ;;
# Read from stdin
-) FILES+=("-") ;;
# bat options
-*) {
BAT_ARGS+=("$OPT=$OPT_VAL")
if [[ -n "$OPT_VAL" ]]; then BAT_ARGS+=("$OPT=$OPT_VAL");
else BAT_ARGS+=("$OPT");
fi
} ;;
# Files

View file

@ -23,13 +23,19 @@ export TEST_SHIM_PATH="${HERE}/test/shim"
export SNAPSHOT_DIR="${HERE}/test/snapshot"
OPT_ARGV=()
SHIFTOPT_SHORT_OPTIONS="PASS"
while shiftopt; do
case "$OPT" in
--compiled)
TEST_ENV_BIN_DIR="${HERE}/bin"
TEST_ENV_BIN_SUFFIX=""
;;
*) OPT_ARGV+=("$OPT") ;;
*)
if [[ "$OPT_VAL" ]]; then
OPT_ARGV+=("${OPT}=${OPT_VAL}")
else
OPT_ARGV+=("$OPT")
fi ;;
esac
done

BIN
test/data/compressed.txt.gz Normal file

Binary file not shown.

3
test/shim/batdiff.sh Normal file
View file

@ -0,0 +1,3 @@
batdiff() {
"${BIN_DIR}/batdiff${BIN_SUFFIX}" "$@" || return $?
}

7
test/shim/batpipe.sh Normal file
View file

@ -0,0 +1,7 @@
batpipe() {
"$(batpipe_path)" "$@" || return $?
}
batpipe_path() {
echo "${BIN_DIR}/batpipe${BIN_SUFFIX}"
}

View file

@ -1,9 +1,15 @@
#!/usr/bin/env bash
exec 3<&0
# Find the real bat.
HERE="$(cd "$(dirname "$0")" && pwd)"
while read -d ':' -r dir; do
if [[ "$dir" == "$HERE" ]]; then continue; fi
if [[ "$dir" == "$HERE" || -z "$dir" ]]; then continue; fi
if [[ -f "${dir}/bat" ]]; then
exec "${dir}/bat" "$@" --theme="Monokai Extended"
exec "${dir}/bat" "$@" --no-config --theme="Monokai Extended" <&3
fi
done <<<"$PATH"
done <<<"$PATH:"
# Print error and exit.
echo "bat was not found on \$PATH" 1>&2
exit 127

4
test/shimexec/fish Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env bash
# Run bash, but with executable name as `fish`.
exec -a "${SHIM_ARGV0:-fish}" bash "$@"
exit $?

View file

@ -12,4 +12,10 @@ EOF
exit 0
fi
cat "$1"
FILES=()
while [[ $# -gt 0 ]]; do
-*) : ;;
*) FILES+=("$1")
done
cat "${FILES[@]}"

7
test/shimexec/nu Executable file
View file

@ -0,0 +1,7 @@
#!/usr/bin/env bash
# Run bash, but with executable name as `nu`.
#
# Spawn the process in the background and wait on it to ensure we keep
# the fake shell as a parent process.
exec -a "${SHIM_ARGV0:-nu}" bash "$@"
exit $?

View file

@ -1,5 +1,4 @@
#!/usr/bin/env bash
case "$1" in
cols) echo 80 ;;
lines) echo 22 ;;
size) echo "20 80" ;;
esac

View file

@ -0,0 +1,3 @@
batdiff
Copyright (C) 2019-2021 eth-p | MIT License
https://github.com/eth-p/bat-extras

View file

@ -0,0 +1,107 @@
Quickly search through and highlight files using ripgrep.
Search through files or directories looking for matching regular expressions (or fixed strings with -F), and print the output using bat for an easy and syntax-highlighted experience.
Usage: batgrep [OPTIONS] PATTERN [PATH...]
Arguments:
[OPTIONS]
See Options below
PATTERN
Pattern passed to ripgrep
[PATH...]
Path(s) to search
Options:
-i, --ignore-case:
Use case insensitive searching.
-s, --case-sensitive:
Use case sensitive searching.
-S, --smart-case:
Use smart case searching
-A, --after-context=[LINES]:
Display the next n lines after a matched line.
-B, --before-context=[LINES]:
Display the previous n lines before a matched line.
-C, --context=[LINES]:
A combination of --after-context and --before-context
-p, --search-pattern:
Tell pager to search for PATTERN. Currently supported pagers: less.
--no-follow:
Do not follow symlinks
--no-snip:
Do not show the snip decoration
This is automatically enabled when --context=0 or when bat --version is less than 0.12.x
--no-highlight:
Do not highlight matching lines.
This is automatically enabled when --context=0.
--color:
Force color output.
--no-color:
Force disable color output.
--paging=["never"/"always"]:
Enable/disable paging.
--pager=[PAGER]:
Specify the pager to use.
--terminal-width=[COLS]:
Generate output for the specified terminal width.
--no-separator:
Disable printing separator between files.
--rga:
Use ripgrep-all instead of ripgrep.
Options passed directly to ripgrep:
-F, --fixed-strings
-U, --multiline
-P, --pcre2
-z, --search-zip
-w, --word-regexp
--one-file-system
--multiline-dotall
--ignore, --no-ignore
--crlf, --no-crlf
--hidden, --no-hidden
-E --encoding:
This is unsupported by bat, and may cause issues when trying to display unsupported encodings.
-g, --glob
-t, --type
-T, --type-not
-m, --max-count
--max-depth
--iglob
--ignore-file

View file

@ -1,8 +1,8 @@
────────────────────────────────────────────────────────────────────────────────
File: file.txt
 1 cat 
 2 dog
 3 car 
 4 frog
 5 fox
 1 cat 
 2 dog
 3 car 
 4 frog
 5 fox
────────────────────────────────────────────────────────────────────────────────

View file

@ -0,0 +1,6 @@
File: file.txt
 1 cat 
 2 dog
 3 car 
 4 frog
 5 fox

View file

@ -0,0 +1,7 @@
────────────────────────────────────────────────────────────────────────────────
cat 
dog
car 
frog
fox
────────────────────────────────────────────────────────────────────────────────

View file

@ -0,0 +1,7 @@
────────────────────────────────────────────────────────────────────────────────
cat
dog
car
frog
fox
────────────────────────────────────────────────────────────────────────────────

View file

@ -1,3 +1,3 @@
batgrep
Copyright (C) 2019-2020 eth-p | MIT License
Copyright (C) 2019-2021 eth-p | MIT License
https://github.com/eth-p/bat-extras

View file

@ -1,3 +1,3 @@
batman
Copyright (C) 2019-2020 eth-p | MIT License
Copyright (C) 2019-2021 eth-p | MIT License
https://github.com/eth-p/bat-extras

View file

@ -0,0 +1,26 @@
───────┬────────────────────────────────
│ File: file.txt
───────┼────────────────────────────────
 1 │ cat
 2 │ dog
 3 │ car
 4 │ frog
 5 │ fox
 6 │ clocks
 7 │ bash
 8 │ $300
 9 │ ^$!@
───────┴────────────────────────────────
───────┬────────────────────────────────────────────────────
│ File: file.txt
───────┼────────────────────────────────────────────────────
 1 │ cat
 2 │ dog
 3 │ car
 4 │ frog
 5 │ fox
 6 │ clocks
 7 │ bash
 8 │ $300
 9 │ ^$!@
───────┴────────────────────────────────────────────────────

View file

@ -0,0 +1,4 @@
echo Hello, test world!
if true; then
! true
fi

View file

@ -0,0 +1,2 @@
Usage: batwatch --file [--watcher entr|poll][--[no-]clear] <file> [<file> ...]
batwatch --command [-n<interval>] <command> [<arg> ...]

View file

@ -1,3 +1,3 @@
batwatch
Copyright (C) 2019-2020 eth-p | MIT License
Copyright (C) 2019-2021 eth-p | MIT License
https://github.com/eth-p/bat-extras

View file

@ -1,3 +1,3 @@
prettybat
Copyright (C) 2019-2020 eth-p | MIT License
Copyright (C) 2019-2021 eth-p | MIT License
https://github.com/eth-p/bat-extras

12
test/suite/batdiff.sh Normal file
View file

@ -0,0 +1,12 @@
setup() {
use_shim 'batdiff'
}
test:version() {
description "Test 'batdiff --version'"
snapshot stdout
snapshot stderr
batdiff --version | awk 'FNR <= 1 { print $1 }'
batdiff --version | awk 'p{print} /^$/ { p=1 }'
}

View file

@ -2,6 +2,8 @@ HAS_RIPGREP=false
setup() {
use_shim 'batgrep'
unset BAT_STYLE
if command -v rg &>/dev/null; then
HAS_RIPGREP=true
@ -14,12 +16,21 @@ require_rg() {
fi
}
test:help() {
description "Test 'batgrep --help'"
snapshot stdout
batgrep --help
assert batgrep --help
batgrep --help | grep -q 'Usage'
}
test:version() {
description "Test 'batgrep --version'"
snapshot stdout
snapshot stderr
batgrep --version | head -n1 | cut -d' ' -f1
batgrep --version | awk 'FNR <= 1 { print $1 }'
batgrep --version | awk 'p{print} /^$/ { p=1 }'
}
@ -92,3 +103,43 @@ test:option_context() {
batgrep -C 0 '\$' file.txt
}
test:search_from_stdin() {
description "Should be able to search through stdin."
snapshot stdout
snapshot stderr
require_rg
cat file.txt | batgrep "^ca"
}
test:respects_bat_style() {
description "Should respect the BAT_STYLE variable."
snapshot stdout
snapshot stderr
require_rg
BAT_STYLE="grid" batgrep "ca" file.txt --color=always
}
test:output_without_separator() {
description "Snapshot test for output without separator"
snapshot stdout
snapshot stderr
require_rg
batgrep "ca" file.txt --no-separator --color=always
}
test:sanity_rg_works() {
description "Ensure the ripgrep executable works"
require_rg
rg --version
rg "ca" file.txt | grep "ca" || fail "Ripgrep executable not working."
}

View file

@ -7,6 +7,6 @@ test:version() {
snapshot stdout
snapshot stderr
batman --version | head -n1 | cut -d' ' -f1
batman --version | awk 'FNR <= 1 { print $1 }'
batman --version | awk 'p{print} /^$/ { p=1 }'
}

61
test/suite/batpipe.sh Normal file
View file

@ -0,0 +1,61 @@
setup() {
use_shim 'batpipe'
}
test:detected_bash_shell() {
description "Test it can detect a bash shell."
command -v "bash" &>/dev/null || skip "Test requires bash shell."
output="$(SHELL="bash" bash --login -c "{ \"$(batpipe_path)\"; }")" # This hack prevents bash from exec()'ing itself.
grep '^LESSOPEN=' <<< "$output" >/dev/null || fail "Detected the wrong shell for bash."
}
test:detected_fish_shell() {
description "Test it can detect a fish shell."
# Note: We don't use bash's `-c` option when testing with a fake fish shell.
# Bash `-c` will automatically exec() into the last process, which loses the
# argv0 we intentionally named after a different shell.
# Test detection via `*sh -l` parent process.
output="$(printf "%q" "$(batpipe_path)" | fish -l)"
grep '^set -x' <<< "$output" >/dev/null || fail 'Detected wrong shell when checking parent process args.'
# Test detection via hypen-prefixed parent process.
output="$(printf "%q" "$(batpipe_path)" | SHIM_ARGV0='-fish' fish)"
grep '^set -x' <<< "$output" >/dev/null || fail 'Detected wrong shell when checking parent process.'
}
test:detected_nu_shell() {
description "Test it can detect a nushell shell."
# Note: We don't use bash's `-c` option when testing with a fake nu shell.
# Bash `-c` will automatically exec() into the last process, which loses the
# argv0 we intentionally named after a different shell.
# Test detection via `*sh -l` parent process.
output="$(printf "%q" "$(batpipe_path)" | nu -l)"
grep '^\$env' <<< "$output" >/dev/null || fail 'Detected wrong shell when checking parent process args.'
# Test detection via hypen-prefixed parent process.
output="$(printf "%q" "$(batpipe_path)" | SHIM_ARGV0='-nu' nu -l)"
grep '^\$env' <<< "$output" >/dev/null || fail 'Detected wrong shell when checking parent process.'
}
test:viewer_gzip() {
description "Test it can view .gz files."
command -v "gunzip" &>/dev/null || skip "Test requires gunzip."
assert_equal "$(batpipe compressed.txt.gz)" "OK"
}
test:batpipe_term_width() {
description "Test support for BATPIPE_TERM_WIDTH"
snapshot STDOUT
export BATPIPE=color
export BATPIPE_DEBUG_PARENT_EXECUTABLE=less
BATPIPE_TERM_WIDTH=40 batpipe file.txt
BATPIPE_TERM_WIDTH=-20 batpipe file.txt
}

View file

@ -1,3 +1,5 @@
#!/bin/bash
setup() {
use_shim 'batwatch'
}
@ -7,6 +9,23 @@ test:version() {
snapshot stdout
snapshot stderr
batwatch --version | head -n1 | cut -d' ' -f1
batwatch --version | awk 'FNR <= 1 { print $1 }'
batwatch --version | awk 'p{print} /^$/ { p=1 }'
}
test:help() {
description "Test 'batwatch --help'"
snapshot stdout
batwatch --help
assert batwatch --help
batwatch --help | grep -q 'Usage'
}
test:displayed() {
description "Test 'batwatch <file>': <file> should be displayed'"
snapshot stdout
batwatch --no-clear --watcher poll file.sh <<< "q"
batwatch --watcher poll file.sh <<< "q" | grep -q "Hello"
}

98
test/suite/lib_dsl.sh Normal file
View file

@ -0,0 +1,98 @@
setup() {
source "${LIB}/dsl.sh"
}
# Expect functions.
expect_dsl_command() {
EXPECTED_DSL_ARGS=("$@")
CALLED="not called"
dsl_on_command() {
expect_equal "$# args" "${#EXPECTED_DSL_ARGS[@]} args"
CALLED="called"
local arg
local i=0
for arg in "$@"; do
expect_equal "$arg" "${EXPECTED_DSL_ARGS[$i]}"
((i++)) || true
done
}
dsl_parse
expect_equal "$CALLED" "called"
}
expect_dsl_option() {
EXPECTED_DSL_ARGS=("$@")
CALLED="not called"
dsl_on_option() {
expect_equal "$# args" "${#EXPECTED_DSL_ARGS[@]} args"
CALLED="called"
local arg
local i=0
for arg in "$@"; do
expect_equal "$arg" "${EXPECTED_DSL_ARGS[$i]}"
((i++)) || true
done
}
dsl_parse
expect_equal "$CALLED" "called"
}
# Stub methods.
dsl_on_command() {
:
}
dsl_on_command_commit() {
:
}
dsl_on_option() {
:
}
# Test cases.
test:parse_command() {
description "Parses a DSL command."
expect_dsl_command "my-command" <<-EOF
my-command
EOF
}
test:parse_simple_args() {
description "Parses a DSL command with simple args"
expect_dsl_command "my-command" "arg1" "arg2" "arg3" <<-EOF
my-command arg1 arg2 arg3
EOF
}
test:parse_quoted_args() {
description "Parses a DSL command with quoted args"
expect_dsl_command "my-command" "arg 1" "" "arg3" <<-EOF
my-command "arg 1" "" "arg3
EOF
}
test:parse_escaped_args() {
description "Parses a DSL command with escaped args"
# Note: Bash will escape the \\ into \. It's only doubled in this heredoc.
expect_dsl_command "my-command" "arg\"1" "arg 2" "arg\\3" <<-EOF
my-command arg\"1 arg\ 2 arg\\\\3
EOF
}
test:parse_option() {
description "Parses a DSL option with simple arguments"
expect_dsl_option "my-option" "1" "2" "3" <<-EOF
my-command
my-option 1 2 3
EOF
}

View file

@ -1,16 +1,22 @@
setup() {
set - pos1 \
--val1 for_val1 \
--val2=for_val2 \
pos2 \
--flag1 \
-v3=for_val3 \
-v4 for_val4 \
--flag2
set - --long-implicit implicit_value \
--long-explicit=explicit_value \
-i implicit \
-x=explicit \
-I1 group_implicit \
-X2=group_explicit \
positional_1 \
positional_2 \
--after-positional
source "${LIB}/print.sh"
source "${LIB}/opt.sh"
}
assert_opt_name() {
assert [ "$OPT" = "$1" ]
}
assert_opt_value() {
assert [ "$OPT_VAL" = "$1" ]
}
@ -19,11 +25,12 @@ assert_opt_valueless() {
assert [ -z "$OPT_VAL" ]
}
test:long() {
description "Parse long options."
while shiftopt; do
if [[ "$OPT" = "--flag1" ]]; then
if [[ "$OPT" = "--long-implicit" ]]; then
assert_opt_valueless
return
fi
@ -32,13 +39,13 @@ test:long() {
fail 'Failed to find option.'
}
test:long_value_implicit() {
test:long_implicit() {
description "Parse long options in '--long value' syntax."
while shiftopt; do
if [[ "$OPT" = "--val1" ]]; then
if [[ "$OPT" = "--long-implicit" ]]; then
shiftval
assert_opt_value 'for_val1'
assert_opt_value 'implicit_value'
return
fi
done
@ -46,13 +53,14 @@ test:long_value_implicit() {
fail 'Failed to find option.'
}
test:long_value_explicit() {
test:long_explicit() {
description "Parse long options in '--long=value' syntax."
while shiftopt; do
if [[ "$OPT" = "--val2" ]]; then
if [[ "$OPT" = "--long-explicit" ]]; then
assert_opt_value 'explicit_value'
shiftval
assert_opt_value 'for_val2'
assert_opt_value 'explicit_value'
return
fi
done
@ -60,13 +68,40 @@ test:long_value_explicit() {
fail 'Failed to find option.'
}
test:short_value_implicit() {
test:short_default() {
description "Parse short options in '-k' syntax."
while shiftopt; do
if [[ "$OPT" = "-i" ]]; then
assert_opt_valueless
return
fi
done
fail 'Failed to find option.'
}
test:short() {
description "Parse short options in '-k val' syntax."
while shiftopt; do
if [[ "$OPT" = "-i" ]]; then
shiftopt
assert_opt_valueless
return
fi
done
fail 'Failed to find option.'
}
test:short_implicit() {
description "Parse short options in '-k value' syntax."
while shiftopt; do
if [[ "$OPT" = "-v4" ]]; then
if [[ "$OPT" = "-i" ]]; then
shiftval
assert_opt_value 'for_val4'
assert_opt_value 'implicit'
return
fi
done
@ -74,15 +109,341 @@ test:short_value_implicit() {
fail 'Failed to find option.'
}
test:short_value_explicit() {
test:short_explicit() {
description "Parse short options in '-k=value' syntax."
while shiftopt; do
if [[ "$OPT" =~ ^-v3 ]]; then
assert_equal "$OPT" "-v3=for_val3"
if [[ "$OPT" = "-x" ]]; then
assert_opt_value 'explicit'
shiftval
assert_opt_value 'explicit'
return
fi
done
fail 'Failed to find option.'
}
test:short_default_mode() {
description "Ensure the default mode for '-abc' is VALUE."
assert_equal "$SHIFTOPT_SHORT_OPTIONS" "VALUE"
}
test:short_split_none() {
description "Parse short options in '-abc' syntax with SPLIT mode."
SHIFTOPT_SHORT_OPTIONS="SPLIT"
local found=0
while shiftopt; do
case "$OPT" in
"-I"|"-1")
assert_opt_valueless
((found++)) || true
;;
esac
done
assert_equal "$found" 2
}
test:short_split_implicit() {
description "Parse short options in '-abc val' syntax with SPLIT mode."
SHIFTOPT_SHORT_OPTIONS="SPLIT"
local found=0
while shiftopt; do
case "$OPT" in
"-I"|"-1")
assert_opt_valueless
shiftval
assert_opt_value "group_implicit"
((found++)) || true
;;
esac
done
assert_equal "$found" 2
}
test:short_split_explicit() {
description "Parse short options in '-abc=val' syntax with SPLIT mode."
SHIFTOPT_SHORT_OPTIONS="SPLIT"
local found=0
while shiftopt; do
case "$OPT" in
"-X"|"-2")
assert_opt_value "group_explicit"
((found++)) || true
;;
esac
done
assert_equal "$found" 2
}
test:short_pass_none() {
description "Parse short options in '-abc' syntax with PASS mode."
SHIFTOPT_SHORT_OPTIONS="PASS"
local found=0
while shiftopt; do
case "$OPT" in
"-I"|"-1") fail "Short group -I1 was split." ;;
"-I1")
assert_opt_valueless
((found++)) || true
;;
esac
done
assert_equal "$found" 1
}
test:short_pass_implicit() {
description "Parse short options in '-abc val' syntax with PASS mode."
SHIFTOPT_SHORT_OPTIONS="PASS"
local found=0
while shiftopt; do
case "$OPT" in
"-I"|"-1") fail "Short group -I1 was split." ;;
"-I1")
assert_opt_valueless
shiftval
assert_opt_value "group_implicit"
((found++)) || true
;;
esac
done
assert_equal "$found" 1
}
test:short_pass_explicit() {
description "Parse short options in '-abc=val' syntax with PASS mode."
SHIFTOPT_SHORT_OPTIONS="PASS"
local found=0
while shiftopt; do
case "$OPT" in
"-X"|"-2") fail "Short group -X2 was split." ;;
"-X2")
assert_opt_value "group_explicit"
((found++)) || true
;;
esac
done
assert_equal "$found" 1
}
test:short_conv_none() {
description "Parse short options in '-abc' syntax with CONV mode."
SHIFTOPT_SHORT_OPTIONS="CONV"
local found=0
while shiftopt; do
case "$OPT" in
"-I"|"-1") fail "Short group -I1 was split." ;;
"-I1") fail "Short group -I1 was not converted." ;;
"--I1")
assert_opt_valueless
((found++)) || true
;;
esac
done
assert_equal "$found" 1
}
test:short_conv_implicit() {
description "Parse short options in '-abc val' syntax with CONV mode."
SHIFTOPT_SHORT_OPTIONS="CONV"
local found=0
while shiftopt; do
case "$OPT" in
"-I"|"-1") fail "Short group -I1 was split." ;;
"-I1") fail "Short group -I1 was not converted." ;;
"--I1")
assert_opt_valueless
shiftval
assert_opt_value "group_implicit"
((found++)) || true
;;
esac
done
assert_equal "$found" 1
}
test:short_conv_explicit() {
description "Parse short options in '-abc=val' syntax with CONV mode."
SHIFTOPT_SHORT_OPTIONS="CONV"
local found=0
while shiftopt; do
case "$OPT" in
"-X"|"-2") fail "Short group -X2 was split." ;;
"-X2") fail "Short group -X2 was not converted." ;;
"--X2")
assert_opt_value "group_explicit"
((found++)) || true
;;
esac
done
assert_equal "$found" 1
}
test:short_value_none() {
description "Parse short options in '-abc' syntax with VALUE mode."
SHIFTOPT_SHORT_OPTIONS="VALUE"
local found=0
while shiftopt; do
case "$OPT" in
"-I1") fail "Short group -I1 was not truncated." ;;
"-I")
assert_opt_value "1"
((found++)) || true
;;
esac
done
assert_equal "$found" 1
}
test:short_value_implicit() {
description "Parse short options in '-abc val' syntax with VALUE mode."
SHIFTOPT_SHORT_OPTIONS="VALUE"
local found=0
while shiftopt; do
case "$OPT" in
"-I1") fail "Short group -I1 was not truncated." ;;
"-I")
shiftval
assert_opt_value "1"
((found++)) || true
;;
esac
done
assert_equal "$found" 1
}
test:short_value_explicit() {
description "Parse short options in '-abc=val' syntax with VALUE mode."
SHIFTOPT_SHORT_OPTIONS="VALUE"
local found=0
while shiftopt; do
case "$OPT" in
"-X2") fail "Short group -X2 was not truncated." ;;
"-X")
assert_opt_value "2=group_explicit"
((found++)) || true
;;
esac
done
assert_equal "$found" 1
}
test:hook() {
description "Option hooks."
SHIFTOPT_HOOKS=("example_hook")
found=false
example_hook() {
if [[ "$OPT" = "--long-implicit" ]]; then
found=true
return 0
fi
return 1
}
while shiftopt; do
if [[ "$OPT" = "--long-implicit" ]]; then
fail "Option was not filtered by hook."
fi
done
if ! "$found"; then
fail "Option was not found by hook."
fi
}
test:fn_setargs() {
description "Function setargs."
setargs "--setarg=true"
shiftopt || true
shiftval
assert_opt_name "--setarg"
assert_opt_value "true"
}
test:fn_resetargs() {
description "Function resetargs."
setargs "--setarg=true"
resetargs
shiftopt || true
assert_opt_name "--long-implicit"
}
# shellcheck disable=SC2154
test:fn_getargs() {
description "Function getargs."
setargs "--one=two" "three" "--four"
shiftopt
getargs args
assert_equal 2 "${#args[@]}"
assert_equal "three" "${args[0]}"
assert_equal "--four" "${args[1]}"
# Ensure getargs doesn't remove remaining arguments.
shiftopt
assert_opt_name "three"
# Ensure it doesn't set an empty string.
args=(one two)
setargs
getargs args
assert_equal 0 "${#args[@]}"
}
test:fn_getargs_append() {
description "Function getargs -a."
setargs "--two=three" "four"
args=(zero one)
getargs -a args
assert_equal 4 "${#args[@]}"
assert_equal "zero" "${args[0]}"
assert_equal "one" "${args[1]}"
assert_equal "--two=three" "${args[2]}"
assert_equal "four" "${args[3]}"
# Ensure it doesn't append empty strings.
args=()
setargs "zero" "one"
getargs -a args
assert_equal 2 "${#args[@]}"
args=(zero one)
setargs
getargs -a args
assert_equal 2 "${#args[@]}"
}

View file

@ -6,69 +6,119 @@ use_pager() {
unset BAT_PAGER
export PAGER="$1"
SCRIPT_PAGER_CMD=("$PAGER")
SCRIPT_PAGER_ARGS=()
_detect_pager --force
_configure_pager
_detect_pager
}
use_no_pager() {
unset BAT_PAGER
unset PAGER
_configure_pager
_detect_pager
}
use_bat_pager() {
unset PAGER
export BAT_PAGER="$1"
SCRIPT_PAGER_CMD=($BAT_PAGER)
SCRIPT_PAGER_ARGS=()
_detect_pager --force
_configure_pager
_detect_pager
}
test:less_detection() {
description "Identify less"
use_pager "less" && expect_equal "$(pager_name)" "less"
use_pager "less_but_renamed" && expect_equal "$(pager_name)" "less"
use_pager "tput" && expect_equal "$(pager_name)" "tput"
(use_pager "less" && expect_equal "$(pager_name)" "less")
(use_pager "less_but_renamed" && expect_equal "$(pager_name)" "less")
(use_pager "stty" && expect_equal "$(pager_name)" "stty")
}
test:bat_detection() {
description "Ensure bat is replaced with less as pager"
use_pager "bat"
expect_equal "$(pager_name)" "less"
expect array_contains "-R" in "${SCRIPT_PAGER_CMD[@]}"
}
test:less_version() {
description "Identify less version"
export MOCK_LESS_VERSION=473
use_pager "less" && expect_equal "$(pager_version)" "473"
(
export MOCK_LESS_VERSION=473
use_pager "less" && expect_equal "$(pager_version)" "473"
)
export MOCK_LESS_VERSION=551
use_pager "less" && expect_equal "$(pager_version)" "551"
(
export MOCK_LESS_VERSION=551
use_pager "less" && expect_equal "$(pager_version)" "551"
)
}
test:less_args() {
description "Automatically select appropriate less args"
export MOCK_LESS_VERSION=473
use_pager "less"
expect array_contains "-R" in "${SCRIPT_PAGER_ARGS[@]}"
expect array_contains "--quit-if-one-screen" in "${SCRIPT_PAGER_ARGS[@]}"
expect array_contains "--no-init" in "${SCRIPT_PAGER_ARGS[@]}"
# When pager is "less".
(
export MOCK_LESS_VERSION=473
use_pager "less"
expect array_contains "-R" in "${SCRIPT_PAGER_CMD[@]}"
expect array_contains "--quit-if-one-screen" in "${SCRIPT_PAGER_CMD[@]}"
expect array_contains "--no-init" in "${SCRIPT_PAGER_CMD[@]}"
)
export MOCK_LESS_VERSION=551
use_pager "less"
expect array_contains "-R" in "${SCRIPT_PAGER_ARGS[@]}"
expect array_contains "--quit-if-one-screen" in "${SCRIPT_PAGER_ARGS[@]}"
expect ! array_contains "--no-init" in "${SCRIPT_PAGER_ARGS[@]}"
# When pager is equivalent of "less".
(
export MOCK_LESS_VERSION=551
use_pager "less"
expect array_contains "-R" in "${SCRIPT_PAGER_CMD[@]}"
expect array_contains "--quit-if-one-screen" in "${SCRIPT_PAGER_CMD[@]}"
expect ! array_contains "--no-init" in "${SCRIPT_PAGER_CMD[@]}"
)
}
test:less_args_not_less() {
description "Don't give non-less pagers the less args"
use_pager "not_less"
expect_equal "${#SCRIPT_PAGER_ARGS[@]}" 0
echo "${SCRIPT_PAGER_CMD[@]}"
expect_equal "${#SCRIPT_PAGER_CMD[@]}" 1
}
test:env_bat_pager() {
description "Check that BAT_PAGER is being used"
use_bat_pager "not_less"
expect_equal "$SCRIPT_PAGER_CMD" "not_less"
expect_equal "${SCRIPT_PAGER_CMD[0]}" "not_less"
use_bat_pager "not_less but not_more"
expect_equal "$SCRIPT_PAGER_CMD" "not_less"
expect_equal "${SCRIPT_PAGER_CMD[0]}" "not_less"
expect_equal "${SCRIPT_PAGER_CMD[1]}" "but"
expect_equal "${SCRIPT_PAGER_CMD[2]}" "not_more"
}
test:env_no_pager() {
description "Check that no PAGER or BAT_PAGER defaults to less"
use_no_pager
expect_equal "${SCRIPT_PAGER_CMD[0]}" "less"
expect array_contains "-R" in "${SCRIPT_PAGER_CMD[@]}"
}
test:args_copied_from_pager() {
description "Check that the pager args are correct with PAGER."
use_pager "less --some-argument"
expect_equal "${SCRIPT_PAGER_CMD[0]}" "less"
expect_not_equal "${SCRIPT_PAGER_CMD[1]}" "--some-argument"
}
test:args_copied_from_bat_pager() {
description "Check that the pager args are correct with PAGER."
use_bat_pager "less --some-argument"
assert_equal "${#SCRIPT_PAGER_CMD[@]}" 2
expect_equal "${SCRIPT_PAGER_CMD[0]}" "less"
expect_equal "${SCRIPT_PAGER_CMD[1]}" "--some-argument"
}

74
test/suite/perf.sh Normal file
View file

@ -0,0 +1,74 @@
# This test suite is meant to check the performance impact of loading the library scripts used in bat-extras.
# Whether or not it completes successfully doesn't matter.
# Test cases.
test:perf_baseline() {
description "The baseline for test execution time"
exit 0
}
test:perf_lib_constants() {
description "Record how long it takes to load the constants.sh library"
source "${LIB}/constants.sh"
exit 0
}
test:perf_lib_dsl() {
description "Record how long it takes to load the dsl.sh library"
source "${LIB}/dsl.sh"
exit 0
}
test:perf_lib_opt() {
description "Record how long it takes to load the opt.sh library"
source "${LIB}/opt.sh"
exit 0
}
test:perf_lib_opt_hook_color() {
description "Record how long it takes to load the opt_hook_color.sh library"
source "${LIB}/opt_hook_color.sh"
exit 0
}
test:perf_lib_opt_hook_pager() {
description "Record how long it takes to load the opt_hook_pager.sh library"
source "${LIB}/opt_hook_pager.sh"
exit 0
}
test:perf_lib_opt_hook_version() {
description "Record how long it takes to load the opt_hook_version.sh library"
source "${LIB}/opt_hook_version.sh"
exit 0
}
test:perf_lib_opt_hook_width() {
description "Record how long it takes to load the opt_hook_width.sh library"
source "${LIB}/opt_hook_width.sh"
exit 0
}
test:perf_lib_pager() {
description "Record how long it takes to load the pager.sh library"
source "${LIB}/pager.sh"
exit 0
}
test:perf_lib_print() {
description "Record how long it takes to load the print.sh library"
source "${LIB}/print.sh"
exit 0
}
test:perf_lib_str() {
description "Record how long it takes to load the str.sh library"
source "${LIB}/str.sh"
exit 0
}
test:perf_lib_version() {
description "Record how long it takes to load the version.sh library"
source "${LIB}/version.sh"
exit 0
}

View file

@ -7,6 +7,19 @@ test:version() {
snapshot stdout
snapshot stderr
prettybat --version | head -n1 | cut -d' ' -f1
prettybat --version | awk 'FNR <= 1 { print $1 }'
prettybat --version | awk 'p{print} /^$/ { p=1 }'
}
test:read_from_pipe() {
description "Test 'prettybat -'"
assert_equal "ABC" "$(echo "ABC" | prettybat)"
assert_equal "ABC" "$(echo "ABC" | prettybat -)"
}
test:read_from_pipe_with_formatter() {
description "Test 'prettybat - -lsh'"
assert_equal "-: shfmt" "$(echo "" | prettybat - -lsh --debug:formatter)"
}

View file

@ -1 +1 @@
2020.04.08
2024.08.24