File: hawk.sh
   1 #!/bin/sh
   2 
   3 # The MIT License (MIT)
   4 #
   5 # Copyright © 2025 pacman64
   6 #
   7 # Permission is hereby granted, free of charge, to any person obtaining a copy
   8 # of this software and associated documentation files (the “Software”), to deal
   9 # in the Software without restriction, including without limitation the rights
  10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11 # copies of the Software, and to permit persons to whom the Software is
  12 # furnished to do so, subject to the following conditions:
  13 #
  14 # The above copyright notice and this permission notice shall be included in
  15 # all copies or substantial portions of the Software.
  16 #
  17 # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  23 # SOFTWARE.
  24 
  25 
  26 # hawk [options...] [awk condition...] [filenames...]
  27 #
  28 #
  29 # Highlight (lines) satisfying an AWK condition/expression, keeping all other
  30 # lines the same. When not given a condition, all lines are highlighted.
  31 #
  32 # The AWK options available only in single-dash versions are
  33 #
  34 #   -F fs               use `fs` for the field separator (the `FS` variable)
  35 #   -V                  show the AWK/GAWK version installed
  36 #   -v var=val          set a variable to a given value
  37 #
  38 # The other options are, available both in single and double-dash versions
  39 #
  40 #   -h, -help           show this help message
  41 #
  42 #   -ins                match regexes case-insensitively
  43 #   -insensitive        match regexes case-insensitively
  44 #
  45 #   -blue               use a blue style
  46 #   -gray               use a gray style
  47 #   -green              use a green style
  48 #   -orange             use an orange style
  49 #   -purple             use a purple style
  50 #   -red                use a red style
  51 #
  52 #   -bb, -blueback      use a blue-background style
  53 #        -grayback      use a gray-background style
  54 #   -gb, -greenback     use a green-background style
  55 #   -ob, -orangeback    use an orange-background style
  56 #   -pb, -purpleback    use a purple-background style
  57 #   -rb, -redback       use a red-background style
  58 
  59 
  60 case "$1" in
  61     -h|--h|-help|--help)
  62         awk '/^# +hawk /, /^$/ { gsub(/^# ?/, ""); print }' "$0"
  63         exit 0
  64     ;;
  65 esac
  66 
  67 ansi='7m'
  68 command='awk'
  69 if [ -p /dev/stdout ] || [ -t 1 ]; then
  70     command='stdbuf -oL awk'
  71 fi
  72 
  73 case_insensitive=0
  74 
  75 while [ $# -gt 0 ]; do
  76     arg="$1"
  77 
  78     if [ "${arg}" = "--" ]; then
  79         shift
  80         break
  81     fi
  82 
  83     case "${arg}" in
  84         -F)
  85             shift
  86             if [ $# -eq 0 ]; then
  87                 printf "expected value after -F option\n" >&2
  88                 exit 1
  89             fi
  90             command="${command} -F "$1""
  91             shift
  92             continue
  93         ;;
  94 
  95         -F*)
  96             command="${command} ${arg}"
  97             shift
  98             continue
  99         ;;
 100 
 101         -v)
 102             shift
 103             if [ $# -eq 0 ]; then
 104                 printf "expected variable assignment after -v option\n" >&2
 105                 exit 1
 106             fi
 107             command="${command} -v "$1""
 108             shift
 109             continue
 110         ;;
 111 
 112         -ins|--ins|-insensitive|--insensitive)
 113             case_insensitive=1
 114             shift
 115             continue
 116         ;;
 117 
 118         -blue|--blue)
 119             ansi='38;2;0;95;215m'
 120             shift
 121             continue
 122         ;;
 123 
 124         -bb|--bb|-blueback|--blueback)
 125             ansi='48;2;0;95;215m\x1b[38;2;238;238;238m'
 126             shift
 127             continue
 128         ;;
 129 
 130         -bold|--bold|-bolded|--bolded)
 131             ansi='1m'
 132             shift
 133             continue
 134         ;;
 135 
 136         -dim|--dim|-faint|--faint|-gray|--gray)
 137             ansi='38;2;168;168;168m'
 138             shift
 139             continue
 140         ;;
 141 
 142         -grayback|--grayback)
 143             ansi='48;2;168;168;168m\x1b[38;2;238;238;238m'
 144             shift
 145             continue
 146         ;;
 147 
 148         -green|--green)
 149             ansi='38;2;0;135;95m'
 150             shift
 151             continue
 152         ;;
 153 
 154         -gb|--gb|-greenback|--greenback)
 155             ansi='48;2;0;135;95m\x1b[38;2;238;238;238m'
 156             shift
 157             continue
 158         ;;
 159 
 160         -inv|--inv|-inverse|--inverse|-invert|--invert|-inverted|--inverted)
 161             ansi='7m'
 162             shift
 163             continue
 164         ;;
 165 
 166         -orange|--orange)
 167             ansi='38;2;215;95;0m'
 168             shift
 169             continue
 170         ;;
 171 
 172         -ob|--ob|-orangeback|--orangeback)
 173             ansi='48;2;215;95;0m\x1b[38;2;238;238;238m'
 174             shift
 175             continue
 176         ;;
 177 
 178         -purple|--purple)
 179             ansi='38;2;135;95;255m'
 180             shift
 181             continue
 182         ;;
 183 
 184         -pb|--pb|-purpleback|--purpleback)
 185             ansi='48;2;135;95;255m\x1b[38;2;238;238;238m'
 186             shift
 187             continue
 188         ;;
 189 
 190         -red|--red)
 191             ansi='38;2;204;0;0m'
 192             shift
 193             continue
 194         ;;
 195 
 196         -rb|--rb|-redback|--redback)
 197             ansi='48;2;204;0;0m\x1b[38;2;238;238;238m'
 198             shift
 199             continue
 200         ;;
 201 
 202         -strike|--strike|-striked|--striked|-stricken|--stricken)
 203             ansi='9m'
 204             shift
 205             continue
 206         ;;
 207 
 208         -underline|--underline|-underlined|--underlined)
 209             ansi='4m'
 210             shift
 211             continue
 212         ;;
 213 
 214         -*)
 215             command="${command} ${arg}"
 216             shift
 217             continue
 218         ;;
 219     esac
 220 
 221     break
 222 done
 223 
 224 cond="${1:-1}"
 225 [ $# -gt 0 ] && shift
 226 
 227 # show all non-existing files given
 228 failed=0
 229 for arg in "$@"; do
 230     if [ "${arg}" = "-" ]; then
 231         continue
 232     fi
 233     if [ ! -e "${arg}" ]; then
 234         printf "no file named \"%s\"\n" "${arg}" > /dev/stderr
 235         failed=1
 236     fi
 237 done
 238 
 239 if [ "${failed}" -gt 0 ]; then
 240     exit 2
 241 fi
 242 
 243 ci='
 244     BEGIN {
 245         if (IGNORECASE == "") {
 246             m = "your `awk` command lacks case-insensitive regex-matching"
 247             printf("\x1b[38;2;204;0;0m%s\x1b[0m\n", m) > "/dev/stderr"
 248             exit 125
 249         }
 250         IGNORECASE = 1
 251     }
 252 '
 253 if [ "${case_insensitive}" -eq 0 ]; then
 254     ci=''
 255 fi
 256 
 257 ${command} "${ci}"'
 258     FNR == 1 && (FS == "\t" || FS == " ") { FS = /\t/ ? "\t" : " "; $0 = $0 }
 259     { low = lower = tolower($0) }
 260     '"${cond}"' {
 261         gsub(/\x1b\[0m/, "\x1b[0m\x1b['"${ansi}"'")
 262         printf "\x1b['"${ansi}"'%s\x1b[0m%s", $0, ORS
 263         next
 264     }
 265     1
 266 ' "$@"