diff --git a/README.md b/README.md index 0c2b77e..e34136a 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,12 @@ Watch for changes in one or more files, and print them with `bat`. +### [`batdiff`](doc/batgrep.md) +Diff a file against the current git index, or display the diff between two files. +Requirements: `bat`, `delta` (optional) + + + ### [`prettybat`](doc/prettybat.md) Pretty-print source code and highlight it with `bat`. diff --git a/doc/batdiff.md b/doc/batdiff.md new file mode 100644 index 0000000..7477eb0 --- /dev/null +++ b/doc/batdiff.md @@ -0,0 +1,55 @@ +# 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. + + + + +## Command Line + +**Synopsis:** + +- `batdiff [OPTIONS] FILE` +- `batdiff [OPTIONS] FILE OTHER_FILE` + + + +**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. | + + + +## 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. diff --git a/lib/constants.sh b/lib/constants.sh index a5abd0f..23407e3 100644 --- a/lib/constants.sh +++ b/lib/constants.sh @@ -7,6 +7,7 @@ # ----------------------------------------------------------------------------- EXECUTABLE_BAT="bat" EXECUTABLE_GIT="git" +EXECUTABLE_DELTA="delta" PROGRAM="$(basename "$0" .sh)" PROGRAM_HOMEPAGE="https://github.com/eth-p/bat-extras" PROGRAM_COPYRIGHT="Copyright (C) 2019-2020 eth-p | MIT License" diff --git a/lib/opt_hook_width.sh b/lib/opt_hook_width.sh new file mode 100644 index 0000000..054f9b2 --- /dev/null +++ b/lib/opt_hook_width.sh @@ -0,0 +1,27 @@ +#!/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 +# ----------------------------------------------------------------------------- + +# 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="$(stty size | cut -d' ' -f 2)" +} diff --git a/src/batdiff.sh b/src/batdiff.sh new file mode 100755 index 0000000..b5ef2de --- /dev/null +++ b/src/batdiff.sh @@ -0,0 +1,173 @@ +#!/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_hooks.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=() + +FILES=() +OPT_TABS= +OPT_CONTEXT=2 +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 +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 ;; + --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" "--hunk-style=plain") + +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 + +# ----------------------------------------------------------------------------- +# 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 git file. + if "$SUPPORTS_BAT_DIFF"; then + "$EXECUTABLE_BAT" --diff --diff-context="$OPT_CONTEXT" "${files[0]}" "${BAT_ARGS[@]}" + else + "$EXECUTABLE_GIT" diff -U"$OPT_CONTEXT" "${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. + "$EXECUTABLE_GIT" diff -U"$OPT_CONTEXT" "${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 no files. +if [[ "${#FILES[@]}" -eq 0 ]] && ! "$OPT_ALL_CHANGES"; then + print_error "no files provided" + exit 1 +fi + +# Handle too many files. +if [[ "${#FILES[@]}" -gt 2 ]]; then + print_error "too many files provided" + exit 1 +fi + + +# ----------------------------------------------------------------------------- +# Main: +# ----------------------------------------------------------------------------- +main() { + if "$OPT_ALL_CHANGES"; then + local file + while read -r file; do + if [[ -f "$file" ]]; then + print_diff "$file" + fi + done < <(git diff --name-only --diff-filter=d) + return + fi + + print_diff "${FILES[@]}" +} + +pager_exec main +exit $? diff --git a/test/shim/batdiff.sh b/test/shim/batdiff.sh new file mode 100644 index 0000000..beecad8 --- /dev/null +++ b/test/shim/batdiff.sh @@ -0,0 +1,3 @@ +batdiff() { + "${BIN_DIR}/batdiff${BIN_SUFFIX}" "$@" || return $? +} diff --git a/test/suite/batdiff.sh b/test/suite/batdiff.sh new file mode 100644 index 0000000..265912a --- /dev/null +++ b/test/suite/batdiff.sh @@ -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 }' +}