Import the remaining utilities.
[utils/utils.git] / lesspipe.sh
diff --git a/lesspipe.sh b/lesspipe.sh
new file mode 100755 (executable)
index 0000000..372abb5
--- /dev/null
@@ -0,0 +1,196 @@
+#!/bin/bash
+# Matt's improved version of Fedora's lesspipe.sh
+#
+# To use this filter with less, define LESSOPEN:
+# export LESSOPEN="|lesspipe.sh %s"
+
+# Updated for "less" that checks our exit code ~ Matt 2010-12-28
+
+# This script is entirely driven by the file content, with type detected by
+# file(1).  For reproducibility and to avoid problems with strange names and
+# even symlinks, we do not use the original name of the file for anything.
+
+set -e
+set -o pipefail
+#set -x
+
+# Attach the file to an FD.  We will reopen /dev/fd/$f to get as many fresh FDs as
+# we need.
+exec {f}<"$1"
+shift
+
+function run_stream_stdin {
+       if [ "$decompressor" == cat ]; then
+               <&$f "$@"
+       else
+               <&$f "$decompressor" | "$@"
+       fi
+}
+
+function stage_to_file_stdin {
+       if [ "$decompressor" != cat ]; then
+               # There is an unavoidable race here, so I am not trying to avoid
+               # it.  If we rewrite the script in Perl, we can use open(undef).
+               # Integration with in2tempfile looks too hard.
+               tmpfile="$(mktemp --tmpdir lesspipe.XXXXXXXXXX)"
+               exec {tf}>"$tmpfile"
+               rm -f "$tmpfile"
+               <&$f "$decompressor" >&$tf # Synchronous
+               # Get a new FD for reading on the tempfile.  This might not be
+               # necessary if the command is just going to reopen it.
+               exec {f}>&- </dev/fd/$tf {tf}>&-
+       else
+               exec <&$f-
+       fi
+}
+
+# Note: the bash file tests (except -h) follow symlinks.
+if [ -f /dev/fd/$f ]; then
+       # Specify the input as -.  This avoids irrelevant "set[ug]id" output for
+       # executables and also follows the symlink without us having to pass -L.
+       # We have to reopen stdin to not consume it.
+       content_type="$(</dev/fd/$f file -b -)"
+
+       case "$content_type" in
+       ('gzip compressed data'*)
+               decompressor=gunzip;;
+       ('bzip2 compressed data'*)
+               decompressor=bunzip2;;
+       ('XZ compressed data'*)
+               decompressor=unxz;;
+       (*)
+               decompressor=cat;;
+       esac
+
+       if [ "$decompressor" != cat ]; then
+               # Get the real content type.  Whatever work the decompressor
+               # does before it is killed with SIGPIPE, we will duplicate later
+               # when we read the file, but there is no good alternative:
+               # - If we do a "tee" with one pipe held for later use, "tee"
+               #   will block once it fills up that pipe.  So if "file" needs
+               #   more data than the pipe buffer to make its decision
+               #   (probably unlikely), we could hang. 
+               # - Going ahead and dumping the decompressed data to a temporary
+               #   file is a waste of work if the content type turns out to be
+               #   one for which we have a streaming viewer, but of course we
+               #   don't know that yet.
+               content_type="$(</dev/fd/$f "$decompressor" | file -b -)" || [ $? == 141 ] # SIGPIPE
+       fi
+
+       case "$content_type" in
+       ('troff or preprocessor input'*)
+               ## Presuming our parent is "less", set COLUMNS based on the width
+               ## of the terminal.
+               #exec {pt}>/proc/$PPID/fd/1
+               #if [ -t $pt ]; then
+               #       # XXX Does this really use stderr rather than the controlling terminal?
+               #       export COLUMNS="$(bash -i -c 'echo $COLUMNS' 2>&$pt)"
+               #fi
+               #exec {pt}>&-
+               # viapty is a more comprehensive solution than the above. ~ 2011-09-05
+               # Hm, by the time we hack in PAGER=cat, maybe it's not any better... ~ 2011-09-06
+               run_stream_stdin viapty man -l -;;
+       (*'tar archive'*)
+               run_stream_stdin tar -tvv;;
+       (*'cpio archive'*)
+               run_stream_stdin cpio --quiet -itv;;
+       (RPM*)
+               run_stream_stdin rpm -qivl --changelog -p -;;
+       (*image*) # FIXME: will catch disk "images" as well as graphical images
+               if type identify &>/dev/null; then
+                       identify_cmd=(identify)
+               elif type gm &>/dev/null; then
+                       identify_cmd=(gm identify)
+               else
+                       ### Give them the binary data.
+                       #echo "No identify available"
+                       #echo "Install ImageMagick or GraphicsMagick to browse images"
+                       identify_cmd=(false)
+               fi
+               if [ "${identify_cmd[0]}" != false ]; then
+                       # Make a file so identify won't stage it again with a
+                       # nonreproducible name.  (Note that "identify -" will
+                       # stage again even if /dev/stdin is a file!)
+                       stage_to_file_stdin
+                       "${identify_cmd[@]}" /dev/stdin
+               fi;;
+       ('OpenDocument Text'|_,'OpenDocument Text Template')
+               stage_to_file_stdin
+               ### CUSTOM SCRIPT -- This part cannot be upstreamed unless odt2text is.
+               odt2text /dev/stdin # based on unzip, needs a file
+               ;;
+       # This has to come after the special case for odt2text.
+       ('Zip archive data'*|_,'OpenDocument'*|_,'OpenOffice 1.x'*)
+               stage_to_file_stdin
+               zipinfo /dev/stdin # needs a file
+               ;;
+       ('PDF document'*)
+               run_stream_stdin pdftotext - -;;
+       ('PostScript document'*)
+               # If you wanted to see the PostScript source code, sorry...
+               run_stream_stdin ps2ascii;;
+       (ELF*'dynamically linked'*)
+               stage_to_file_stdin # read twice
+               objdump -xRTdgl /dev/stdin
+               # The above does not seem to show data sections.  Run a separate command
+               # to show them.  (Of course I would prefer a single command.)
+               objdump -s -j .rodata -j .data /dev/stdin
+               ;;
+       (ELF*)
+               stage_to_file_stdin # read twice
+               objdump -xdgl /dev/stdin
+               objdump -s -j .rodata -j .data /dev/stdin
+               ;;
+       ('SQLite 3.x database'*)
+               # TODO: Reimplement copy to a temporary directory to
+               # handle databases that use write-ahead mode, etc.  I had
+               # implemented this in main VM and I forgot to copy it to
+               # main-template before shutting down. :( ~ Noted 2015-12-28
+               stage_to_file_stdin
+               # I believe that this will honor a write lock held on an
+               # uncompressed database by another instance of SQLite.  I
+               # further believe that is what we want.
+               sqlite3 /dev/stdin .dump # needs a file
+               ;;
+       ('compiled Java class data'*)
+               # Agghhh... why couldn't javap just take a filename?  It's clear
+               # that all it is doing is translating our argument to a file and
+               # opening that, without checking the correctness of the package
+               # or class name.
+               # XXX: Proper cleanup on failure
+               dir="$(mktemp -d --tmpdir lesspipe-javap.XXXXXXXXXX)"
+               ln -s /dev/stdin "$dir/Stdin.class"
+               run_stream_stdin javap -classpath "$dir" -private -s -c -verbose Stdin
+               rm -rf "$dir"
+               ;;
+       # Disabled for now.
+       #(*.grl)
+       #       stage_to_file_stdin
+       #       head -n 1 /dev/stdin | fold -w 28
+       #       tail -n +2;;
+       (*) # Unknown content type.
+               if [ "$decompressor" != cat ]; then
+                       # Just decompress the file.
+                       <&$f "$decompressor"
+               else
+                       # We cannot add value in this case.
+                       # Let "less" show the original file.
+                       exit 77
+               fi;;
+       esac
+
+elif [ -d /dev/fd/$f ]; then
+       # nip blank line at the end
+       # XXX: how to make sure "less" has -R enabled?
+       #ls -H -al --color=always -- /dev/fd/$f/ | head --bytes=-3
+       ### Between coreutils-7.6-9.matt1.fc12 and coreutils-8.5-7.fc14, the
+       ### output has changed to put the \n at the end, so the special hack is
+       ### no longer necessary (less ignores a final \n) and in fact corrupts
+       ### the colors. ~ Matt 2011-06-19
+       ls -H -al --color=always -- /dev/fd/$f/
+else
+       # Device/special file.  Don't print anything; let less complain about it.
+       # (For now, we are not interested in content sniffing on pipes.  If less
+       # gained support for calling LESSOPEN for stdin, we might reconsider.)
+       exit 77
+fi