File: vrt.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 # vrt [option...] [files...]
  27 #
  28 #
  29 # View Rich Text is a simple non-interactive plain-text file viewer, which
  30 # handles ANSI-codes and renders pictures out of data-URI lines.
  31 #
  32 # The options are, available in single and double-dashed versions
  33 #
  34 #   -h           show this help message
  35 #   -help        show this help message
  36 #
  37 #   -header=n    always show first `n` lines at the top
  38 #
  39 #   -n           show line numbers for overall results
  40 #   -N           show line numbers for overall results
  41 #
  42 #   -s           show pictures using the sixel format (experimental)
  43 #   -sixel       show pictures using the sixel format (experimental)
  44 #
  45 #   -t           show first line at the top at all times
  46 #   -top         show first line at the top at all times
  47 
  48 
  49 case "$1" in
  50     -h|--h|-help|--help)
  51         awk '/^# +vrt /, /^$/ { gsub(/^# ?/, ""); print }' "$0"
  52         exit 0
  53     ;;
  54 esac
  55 
  56 width=0
  57 sixels=0
  58 # less_options='-MKiCRS'
  59 less_options='-MKiCS'
  60 
  61 for arg in "$@"; do
  62     if [ "${arg}" = '--' ]; then
  63         shift
  64         break
  65     fi
  66 
  67     case "${arg}" in
  68         -n|--n|-N|--N)
  69             less_options="${less_options} -N"
  70             shift
  71         ;;
  72 
  73         --header=*)
  74             less_options="${less_options} ${arg}"
  75             shift
  76         ;;
  77 
  78         -s|--s|-sixel|--sixel|-sixels|--sixels)
  79             sixels=1
  80             # less_options="${less_options} -r"
  81             shift
  82         ;;
  83 
  84         -t|--t|-top|--top)
  85             less_options="${less_options} --header=1"
  86             shift
  87         ;;
  88 
  89         -w|--w|-width|--width)
  90             shift
  91             if [ $# -eq 0 ]; then
  92                 printf "expected number after width option\n" >&2
  93                 exit 1
  94             fi
  95             width="$1"
  96             shift
  97         ;;
  98     esac
  99 done
 100 
 101 if [ "${sixels}" -eq 0 ]; then
 102     less_options="${less_options} -R"
 103 fi
 104 
 105 if [ "${width}" -eq 0 ]; then
 106     width="$(tput cols)"
 107 fi
 108 
 109 command='awk'
 110 if [ -e /usr/bin/gawk ]; then
 111     command='gawk'
 112 fi
 113 
 114 if [ $# -eq 0 ]; then
 115     less "${less_options}" -f -
 116     exit $?
 117 fi
 118 
 119 if [ $# -lt 2 ]; then
 120     less_options="${less_options} --header=1"
 121 fi
 122 
 123 # show all non-existing files given
 124 failed=0
 125 for arg in "$@"; do
 126     if [ "${arg}" = "-" ]; then
 127         continue
 128     fi
 129     if [ ! -e "${arg}" ]; then
 130         printf "no file named \"%s\"\n" "${arg}" > /dev/stderr
 131         failed=1
 132     fi
 133 done
 134 
 135 if [ "${failed}" -gt 0 ]; then
 136     exit 2
 137 fi
 138 
 139 view_pic=''
 140 if [ -e /usr/bin/chafa ]; then
 141     view_pic='chafa'
 142     if [ "${sixels}" -eq 1 ]; then
 143         view_pic='chafa --format sixels'
 144     fi
 145 fi
 146 
 147 ${command} -v width="${width}" -v view_pic="${view_pic}" '
 148     BEGIN {
 149         if (width >= 170) width = (width - 10) / 2
 150         fmt = "sh -c \"base64 -d | %s --size %dx\""
 151         render_pic_cmd = sprintf(fmt, view_pic, width)
 152     }
 153 
 154     FNR == 1 {
 155         if (files++ > 0) print ""
 156         printf "\x1b[7m%-80s\x1b[0m\n", FILENAME
 157     }
 158 
 159     { gsub(/\r$/, "") }
 160 
 161     /^data:image\/[a-z]+;base64,[A-Za-z0-9/+]*=*$/ {
 162         if (view_pic == "") {
 163             print "image renderer \"chafa\" not found" > "/dev/stderr"
 164             exit 1
 165         }
 166 
 167         gsub(/^data:image\/[a-z]+;base64,/, "")
 168         print | render_pic_cmd
 169         close(render_pic_cmd)
 170         next
 171     }
 172 
 173     # { print; next }
 174 
 175     /\t/ { print; next }
 176 
 177     {
 178         pos = 0
 179 
 180         for (i = 1; i <= NF; i++) {
 181             plain = $i
 182             gsub(/\x1b\[[0-9;]*[A-Za-z]/, "", plain)
 183             w = length(plain) + (i > 1)
 184 
 185             if (pos + w >= width || w >= width) {
 186                 print ""
 187                 pos = 0
 188             }
 189 
 190             pos += w
 191 
 192             if (i == 1) printf "%s", $i
 193             else printf " %s", $i
 194         }
 195 
 196         if (pos > 0 || NF == 0) print ""
 197     }
 198 ' "$@" | less "${less_options}"