#!/bin/sh # The MIT License (MIT) # # Copyright © 2024 pacman64 # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # book [page-height...] [filenames...] # # Layout lines on 2 page-like side-by-side columns, just like a book. # # Book shows lays out text-lines the same way pairs of pages are laid out in # books, letting you take advantage of wide screens. Every pair of pages ends # with a special dotted line to visually separate it from the next pair. # # If you're using Linux or MacOS, you may find this cmd-line shortcut useful: # # # Like A Book lays lines as pairs of pages, the same way books do it # lab() { book "$(($(tput lines) - 1))" "$@" | less -JMKiCRS; } case "$1" in -h|--h|-help|--help) awk '/^# +book/, /^$/ { gsub(/^# ?/, ""); print }' "$0" exit 0 ;; esac height=0 # detect a leading number, and use it as a height value if [ "$(echo "$1" | grep -E '^[+-]?[0-9]+$' 2> /dev/null)" ]; then height="$1" shift fi # add the current screen height to negative height values if [ "${height}" -lt 0 ]; then height="$((${height} + $(tput lines)))" fi # use the current screen height minus 1, when a height either was not # given explicitly, or is clearly too small if [ "${height}" -lt 2 ]; then height="$(($(tput lines) - 1))" fi if [ "${height}" -lt 2 ]; then printf "screen/window isn't tall enough to show content\n" > /dev/stderr exit 1 fi awk ' # ignore leading UTF-8 BOMs (byte-order marks) FNR == 1 { gsub(/^\xef\xbb\xbf/, "") } # carriage-returns will ruin side-by-side output, so remove them { gsub(/\r$/, ""); print } ' "$@" | # before laying out lines side-by-side, expand all tabs, using 4 as the # tabstop width expand -t 4 | awk -v height="${height}" ' # remember all lines; carriage-returns are already removed { lines[NR] = $0 } # width counts items in the string given, ignoring ANSI-style sequences function width(s) { gsub(/\x1b\[([0-9]*[A-HJKST]|[0-9;]*m)/, "", s) return length(s) } END { step = height - 1 if (NR <= step) { for (i in lines) print lines[i] exit } for (i = 1; i <= NR; i += 2 * step) { for (j = 0; j < step; j++) { l = width(lines[i + j]) if (maxl < l) maxl = l l = width(lines[i + step + j]) if (maxr < l) maxr = l } } # make a separator wide enough to match the length of any output line sep = "································" nsep = length(sep) widest = maxl + 3 + maxr while (nsep < widest) { sep = sep sep nsep *= 2 } # separator is used directly, so match the needed width exactly sep = substr(sep, 1, widest) # make enough spaces to hand-pad lines later; these spaces can exceed # the max-count needed, since they are always subsliced when used later spaces = " " nspaces = length(spaces) while (nspaces < widest) { spaces = spaces spaces nspaces *= 2 } # emit lines side by side for (i = 1; i <= NR; i += 2 * step) { # emit a page-bottom/separator line between page-pairs if (i > 1) print sep for (j = 0; j < step; j++) { # bottom-pad last page-pair with empty lines, so page-scrolling # on viewers like `less` stays in sync with the page boundaries if (i + j > NR) { print "" continue } l = lines[i + j] r = lines[i + step + j] #printf "%-*s █ %s\n", maxl, l, r # pick/emit lines side by side; hand-pad left pages to align # ANSI-styled text correctly padl = substr(spaces, 1, maxl - width(l)) printf "%s%s █ %s\n", l, padl, r } } # end last page with an empty line, instead of the usual page-separator if (NR % (2 * step) > 0) print "" } '