Initial commit.

This commit is contained in:
Ethan P 2019-06-19 14:15:10 -07:00
commit 248d7077f4
No known key found for this signature in database
GPG Key ID: 1F8DF8091CD46FBC
7 changed files with 633 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# System
.DS_Store
._*
# Project
.download
bin

49
README.md Normal file
View File

@ -0,0 +1,49 @@
# bat-extras
Bash scripts that integrate [bat](https://github.com/sharkdp/bat) with various command line tools.
## Scripts
- [`batgrep`](doc/batgrep.md) (ripgrep + bat)
## Installation
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 any run anywhere, you can use the `build.sh` script to create (and optionally install) them.
**Build:**
```bash
./build.sh [OPTIONS...]
```
This will combine and preprocess each script under the `src` directory, and create corresponding self-contained scripts in the `bin` folder. Any library scripts that are sourced using `source "${LIB}/[NAME].sh"` will be embedded automatically.
**Minification:**
There are three different options for minification:
| Option | Description |
| --------------- | ---------------------------------------------------- |
| `--minify=none` | Nothing will be minified. |
| `--minify=lib` | Embedded library scripts will be minified. [default] |
| `--minify=all` | Everything will be minified. |
This uses [bash_minifier](https://github.com/precious/bash_minifier) to perform minification, and requires Python 2 to be installed as either `python2` or `python`.
**Installation:**
You can also specify `--install` and `--prefix=PATH` to have the build script automatically install the scripts for all users on the system. You may need to run the build script as root.

239
build.sh Executable file
View File

@ -0,0 +1,239 @@
#!/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
# -----------------------------------------------------------------------------
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DL="$HERE/.download"
BIN="$HERE/bin"
SRC="$HERE/src"
LIB="$HERE/lib"
source "${LIB}/print.sh"
source "${LIB}/opt.sh"
# -----------------------------------------------------------------------------
# Runs the next build step.
#
# Arguments:
# 1 -- The build step function name.
# @ -- The function arguments.
#
# Input:
# The unprocessed file data.
#
# Output:
# The processed file data.
next() {
local buffer="$(cat)"
"$@" <<< "$buffer"
return $?
}
# 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;;
esac
}
# Build step: read
# Reads the file from its source.
#
# Arguments:
# 1 -- The source file.
#
# Output:
# The file contents.
step_read() {
smsg "Reading"
cat "$1"
}
# Build step: preprocess
# Preprocesses the script.
#
# This will embed library scripts.
#
# Input:
# The original file contents.
#
# Output:
# The processed file contents.
step_preprocess() {
smsg "Preprocessing"
local line
while IFS='' read -r line; do
# Skip certain lines.
[[ "$line" =~ ^LIB=.*$ ]] && continue
# Embed library scripts.
if [[ "$line" =~ ^[[:space:]]*source[[:space:]]+[\"\']\$\{?LIB\}/([a-z-]+\.sh)[\"\'] ]]; then
echo "# --- BEGIN LIBRARY FILE: ${BASH_REMATCH[1]} ---"
cat "$LIB/${BASH_REMATCH[1]}" | {
if [[ "$OPT_MINIFY" = "lib" ]]; then
pp_strip_comments | pp_minify
else
cat
fi
}
echo "# --- END LIBRARY FILE ---"
continue
fi
# Forward data.
echo "$line"
done
}
# Build step: minify
# Minifies the output script.
#
# Input:
# The original file contents.
#
# Output:
# The minified file contents.
step_minify() {
if [[ "$OPT_MINIFY" != "all" ]]; then
smsg "Minifying" "SKIP"
cat
return 0
fi
smsg "Minifying"
printf "#!/usr/bin/env bash\n"
pp_minify
}
# Build step: write
# Writes the output script to a file.
#
# Arguments:
# 1 -- The file to write to.
#
# Input:
# The file contents.
#
# Output:
# The file contents.
step_write() {
smsg "Building"
tee "$1"
chmod +x "$1"
}
# Build step: write
# Optionally writes the output script to a file.
#
# Arguments:
# 1 -- The file to write to.
#
# Input:
# The file contents.
#
# Output:
# The file contents.
step_write_install() {
if [[ "$OPT_INSTALL" != true ]]; then
smsg "Installing" "SKIP"
cat
return 0
fi
smsg "Installing"
tee "$1"
chmod +x "$1"
}
# -----------------------------------------------------------------------------
# Preprocessor.
# Strips comments from a Bash source file.
pp_strip_comments() {
sed '/^[[:space:]]*#.*$/d'
}
# Minify a Bash source file.
# https://github.com/precious/bash_minifier
pp_minify() {
local python="python"
if command -v python2 &>/dev/null; then
python="python2"
fi
"$python" "$DL/minifier.py"
printf "\n"
}
# -----------------------------------------------------------------------------
# Options.
OPT_INSTALL=false
OPT_MINIFY="lib"
OPT_PREFIX="/usr/local"
while shiftopt; do
case "$OPT" in
--install) OPT_INSTALL=true;;
--prefix) shiftval; OPT_PREFIX="$OPT_VAL";;
--minify) shiftval; OPT_MINIFY="$OPT_VAL";;
*) printc "%{RED}%s: unknown option '%s'%{CLEAR}" "$PROGRAM" "$OPT";
exit 1;;
esac
done
if [[ "$OPT_INSTALL" = true ]]; then
printc "%{YELLOW}Installing to %{MAGENTA}%s%{YELLOW}.%{CLEAR}\n" "$OPT_PREFIX" 1>&2
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
fi
# -----------------------------------------------------------------------------
# Download resources.
[[ -d "$DL" ]] || mkdir "$DL"
[[ -d "$BIN" ]] || mkdir "$BIN"
if [[ "$OPT_MINIFY" != "none" ]] && ! [[ -f "$DL/minifier.py" ]]; then
printc "%{YELLOW}Downloading %{BLUE}https://github.com/precious/bash_minifier%{YELLOW}...%{CLEAR}\n" 1>&2
curl -sL "https://gitcdn.xyz/repo/precious/bash_minifier/master/minifier.py" > "$DL/minifier.py"
fi
# -----------------------------------------------------------------------------
# Find files.
SOURCES=()
printc "%{YELLOW}Preparing scripts...%{CLEAR}\n" 1>&2
for file in "$SRC"/*.sh; do
SOURCES+=("$file")
done
# -----------------------------------------------------------------------------
# Build files.
printc "%{YELLOW}Building scripts...%{CLEAR}\n" 1>&2
file_i=0
file_n="${#SOURCES[@]}"
for file in "${SOURCES[@]}"; do
((file_i++))
filename="$(basename "$file" .sh)"
printc " %{YELLOW}[%s/%s] %{MAGENTA}%s%{CLEAR}\n" "$file_i" "$file_n" "$file" 1>&2
step_read "$file" |\
next step_preprocess |\
next step_minify |\
next step_write "${BIN}/${filename}" |\
next step_write_install "${OPT_PREFIX}/bin/${filename}" |\
cat >/dev/null
done

66
doc/batgrep.md Normal file
View File

@ -0,0 +1,66 @@
# bat-extras: batgrep
A script that combines [ripgrep](https://github.com/burntsushi/ripgrep) with bat's syntax highlighting and output formatting.
## Command Line
**Synopsis:**
- `batgrep [OPTIONS] PATTERN [PATH...] `
**Options:**
| Short | Long | Description |
| ----- | -------------------------- | ---------------------------------------------------------- |
| `-i` | `--ignore-case` | Use case insensitive 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`. |
| | `--no-follow` | Do not follow symlinks. |
**Options (Passthrough)**:
The following options are passed directly to ripgrep, and are not handled by this script.
| Short | Long | Notes |
| ----- | -------------------------- | ------------------------------------------------------------ |
| `-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` | |
## Caveats
**Differences from ripgrep:**
- `--follow` is enabled by default for `batgrep`.
- Not all the `ripgrep` options are supported.
## Issues?
If you find an issue or have a feature suggestion, make a pull request or issue through GitHub!
Contributions are always welcome.

69
lib/opt.sh Normal file
View File

@ -0,0 +1,69 @@
#!/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
# -----------------------------------------------------------------------------
PROGRAM="$(basename "${BASH_SOURCE[0]}")"
# Gets the next option passed to the script.
#
# Variables:
# OPT -- The option name.
#
# Returns:
# 0 -- An option was read.
# 1 -- No more options were read.
#
# Example:
# while shiftopt; do
# shiftval
# echo "$OPT = $OPT_VAL"
# done
shiftopt() {
# Ensure _ARGV exists and has the program arguments.
if [[ -z ${_ARGV+x} ]]; then
_ARGV=("${BASH_ARGV[@]}")
_ARGV_INDEX="$((${#_ARGV[@]} - 1))"
fi
# Read the top of _ARGV.
[[ "$_ARGV_INDEX" -lt 0 ]] && return 1
OPT="${_ARGV[$_ARGV_INDEX]}"
unset OPT_VAL
if [[ "$OPT" =~ ^--[a-zA-Z-]+=.* ]]; then
OPT_VAL="${OPT#*=}"
OPT="${OPT%%=*}"
fi
# Pop array.
((_ARGV_INDEX--))
return 0
}
# Gets the value for the current option.
#
# Variables:
# OPT_VAL -- The option value.
#
# Returns:
# 0 -- An option value was read.
# EXIT 1 -- No option value was available.
shiftval() {
# Skip if a value was already provided.
if [[ -z "${ARG_VAL+x}" ]]; then
return 0
fi
OPT_VAL="${_ARGV[$_ARGV_INDEX]}"
((_ARGV_INDEX--))
# Error if no value is provided.
if [[ "$OPT_VAL" =~ -.* ]]; then
printc "%{RED}%s: '%s' requires a value%{CLEAR}\n" "$PROGRAM" "$ARG"
exit 1
fi
}

67
lib/print.sh Normal file
View File

@ -0,0 +1,67 @@
#!/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
# -----------------------------------------------------------------------------
# Printf, but with optional colors.
# This uses the same syntax and arguments as printf.
#
# Example:
# printc "%{RED}This is red %s.%{CLEAR}\n" "text"
#
printc() {
printf "$(sed "$_PRINTC_PATTERN" <<< "$1")" "${@:2}"
}
# Initializes the color tags for printc.
#
# Arguments:
# color=on -- Turns on color output.
# color=off -- Turns off color output.
printc_init() {
case "$1" in
color=on) _PRINTC_PATTERN="$_PRINTC_PATTERN_ANSI";;
color=off) _PRINTC_PATTERN="$_PRINTC_PATTERN_PLAIN";;
"") {
_PRINTC_PATTERN_ANSI=""
_PRINTC_PATTERN_PLAIN=""
local name
local ansi
while read -r name ansi; do
if [[ -z "${name}" && -z "${ansi}" ]] || [[ "${name:0:1}" = "#" ]]; then
continue
fi
ansi="$(sed 's/\\/\\\\/' <<< "$ansi")"
_PRINTC_PATTERN_PLAIN="${_PRINTC_PATTERN_PLAIN}s/%{${name}}//g;"
_PRINTC_PATTERN_ANSI="${_PRINTC_PATTERN_ANSI}s/%{${name}}/${ansi}/g;"
done
if [ -t 1 ]; then
_PRINTC_PATTERN="$_PRINTC_PATTERN_ANSI"
else
_PRINTC_PATTERN="$_PRINTC_PATTERN_PLAIN"
fi
};;
esac
}
# Initialization.
printc_init <<END
CLEAR \x1B[0m
RED \x1B[31m
GREEN \x1B[32m
YELLOW \x1B[33m
BLUE \x1B[34m
MAGENTA \x1B[35m
CYAN \x1B[36m
DIM \x1B[2m
END

135
src/batgrep.sh Normal file
View File

@ -0,0 +1,135 @@
#!/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
# -----------------------------------------------------------------------------
LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../lib"
source "${LIB}/print.sh"
source "${LIB}/opt.sh"
# -----------------------------------------------------------------------------
SEP="$(printc "%{DIM}%$(tput cols)s%{CLEAR}" | tr ' ' '─')"
RG_ARGS=()
BAT_ARGS=()
PATTERN=
FILES=()
OPT_CONTEXT_BEFORE=2
OPT_CONTEXT_AFTER=2
OPT_FOLLOW=true
# Parse arguments.
while shiftopt; do
case "$OPT" in
# Ripgrep Options
-i|--ignore-case) RG_ARGS+=("--ignore-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";
OPT_CONTEXT_AFTER="$OPT_VAL";;
-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) RG_ARGS+=("$OPT");;
-E|--encoding|\
-g|--glob|\
-t|--type|\
-T|--type-not|\
-m|--max-count|\
--max-depth|\
--iglob|\
--ignore-file) shiftval; RG_ARGS+=("$OPT" "$OPT_VAL");;
# Bat Options
# Script Options
--no-follow) OPT_FOLLOW=false;;
# ???
-*) {
printc "%{RED}%s: unknown option '%s'%{CLEAR}\n" "$PROGRAM" "$OPT" 1>&2
exit 1
};;
# Search
*) {
if [ -z "$PATTERN" ]; then
PATTERN="$OPT"
else
FILES+=("$OPT")
fi
};;
esac
done
if [[ -z "$PATTERN" ]]; then
printc "%{RED}%s: no pattern provided%{CLEAR}\n" "$PROGRAM" 1>&2
exit 1
fi
if "$OPT_FOLLOW"; then
RG_ARGS+=("--follow")
fi
# Invoke ripgrep.
FOUND_FILES=()
FOUND=0
FIRST_PRINT=true
LAST_LR=()
LAST_LH=()
LAST_FILE=''
do_print() {
[[ -z "$LAST_FILE" ]] && return 0
# Print the separator.
"$FIRST_PRINT" && echo "$SEP"
FIRST_PRINT=false
# Print the file.
unset LAST_LR[0]
unset LAST_LH[0]
bat "${BAT_ARGS[@]}" \
"${LAST_LR[@]}" \
"${LAST_LH[@]}" \
--style="header,numbers" \
"$LAST_FILE"
# Print the separator.
echo "$SEP"
}
while IFS=':' read -r file line column; do
((FOUND++))
if [[ "$LAST_FILE" != "$file" ]]; then
do_print
LAST_FILE="$file"
LAST_LR=''
LAST_LH=''
fi
# Calculate the context line numbers.
line_start=$((line - OPT_CONTEXT_BEFORE))
line_end=$((line + OPT_CONTEXT_AFTER))
[[ "$line_start" -gt 0 ]] || line_start=''
LAST_LR+=("--line-range=${line_start}:${line_end}")
LAST_LH+=("--highlight-line=${line}")
done < <(rg --with-filename --vimgrep "${RG_ARGS[@]}" --sort path "$PATTERN" "${FILES[@]}")
do_print
# Exit.
if [[ "$FOUND" -eq 0 ]]; then
exit 2
fi