#!/home/richmit/bin/verGo.sh ruby
# -*- Mode:Ruby; Coding:us-ascii-unix; fill-column:160 -*-

################################################################################################################################################################
##
# @file      hexDump.rb
# @author    Mitch Richling <http://www.mitchr.me/>
# @Copyright Copyright 2008 by Mitch Richling.  All rights reserved.
# @brief     Hex dump files in a wide, color format on ANSI/VT100 terms.@EOL
# @Keywords  hex dump color ANSI VT100
# @Std       Ruby 2.0
# @LICENSE   @EOL
#  =============================================================================================================================================================
#  Copyright (c) 1996-2015, Mitchell Jay Richling <https://www.mitchr.me> All rights reserved.
#  
#  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
#  
#  1. Redistributions of source code must retain the above copyright notice, this list of conditions, and the following disclaimer.
#  
#  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the following disclaimer in the documentation
#     and/or other materials provided with the distribution.
#  
#  3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without
#     specific prior written permission.
#  
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
#  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
#  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
#  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
#  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#  =============================================================================================================================================================
#            
################################################################################################################################################################

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
require 'optparse'
require 'optparse/time'

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# Set defaults and process command line arguments
doTitle    = true
skipSame   = true
maxBytes   = 0
skipBytes  = 0
doColor    = true
useLess    = $stdout.isatty
bpf        = "%02x"
opts = OptionParser.new do |opts|
  opts.banner = "Usage: hexDump.rb [options]"
  opts.separator ""
  opts.separator "Options:"
  opts.on("-h",     "--help",     "Show this message")         { puts opts; exit        }
  opts.on("-n INT",               "Print only n bytes")        { |v| maxBytes = v.to_i  }
  opts.on("-p INT",               "Skip first n bytes")        { |v| skipBytes = v.to_i }
  opts.on("-t",     "--notitle",  "Don't print title line")    { |v| doTitle = false;   }
  opts.on("-o",     "--oct",      "Print in Octal")            { |v| bpf = "%03o";      }
  opts.on("-d",     "--dec",      "Print in Decimal")          { |v| bpf = "%03d";      }
  opts.on("-a",     "--all",      "Display duplicate lines")   { |v| skipSame = false   }
  opts.on("-m",     "--nocolor",  "No color output")           { |v| doColor = false    }
  opts.on("-l",     "--less",     "Use the less pager")        { |v| useLess = true     }
  opts.on("-r",     "--raw",      "Do not use the less pager") { |v| useLess = false    }
  opts.separator ""
  opts.separator "Display a hex dump just the way I like it -- wide, with color, and paged via"
  opts.separator "less when connected to a TTY.  Note that color is achieved via ANSI/VT100."
  opts.separator "escape sequences -- so they might not work if you are using a funny terminal."
  opts.separator ""
end
opts.parse!(ARGV)

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
inFile = $stdin
if ( !(ARGV[0].nil?)) then
  inFile = open(ARGV[0], 'r')
end

outFile = $stdout
if (useLess) then
  outFile = IO.popen('less -S -R', 'w')
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
hdw = 3 * 8 * 4 + 3
if ((bpf == "%03o") || (bpf == "%03d")) then
  hdw = 4 * 8 * 4 + 3
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
if (doTitle) then
  outFile.printf("%10s  ", "")
  0.upto(0x20-1) do |i|
    if ((i % 8)==0) then
      outFile.printf(" ")
    end
    outFile.printf("#{bpf} ", i)
  end
  outFile.printf("\n")
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
colCtr = colPrt = colOvr = [ '', '' ]
if (doColor) then
  colCtr = [ "\e[0;45m", "\e[0m" ]
  colPrt = [ "\e[0m",    "\e[0m" ]
  colOvr = [ "\e[0;46m", "\e[0m" ]
end
blockCount    = 0
byteCount     = 0
cLineStr      = ''
cLineAdd      = -1
cLineHex      = ''
lastLineData  = nil
sameCount     = 0
if(skipBytes > 0) then
  inFile.seek(skipBytes)
end
inFile.each_byte do |b|
  if(cLineAdd < 0) then
    cLineAdd = byteCount + skipBytes
    cLineHex=''
    cLineStr=''
  end
  byteCount += 1
  if (b<32) then
    cLineStr += colCtr[0] + (b+64).chr + colCtr[1]
  elsif (b>126) then
    cLineStr += colOvr[0] + '.'        + colOvr[1]
  else
    cLineStr += colPrt[0] + b.chr      + colPrt[1]
  end
  cLineHex += sprintf("#{bpf} ", b)
  if ( (maxBytes > 0) && (byteCount >= maxBytes) ) then
    break
  end
  if ((byteCount % 8)==0) then
    blockCount += 1
    if ((blockCount % 4)==0) then
      curLineData = cLineHex + cLineStr
      if (skipSame && (lastLineData == curLineData)) then
        sameCount += 1
      else
        if (sameCount > 0) then
          outFile.printf("* (%d duplicate lines suppressed)\n", sameCount)
        end
        sameCount = 0
        outFile.printf("%010x : %-#{hdw}s : %s\n", cLineAdd, cLineHex, cLineStr)
        lastLineData = curLineData;
      end
      cLineAdd = -1
    else
      cLineHex += sprintf(" ")
    end
  end
end
if (sameCount > 0) then
  outFile.printf("* (%d duplicate lines suppressed)\n", sameCount)
end
if ( cLineAdd >= 0) then
  outFile.printf("%010x : %-#{hdw}s : %s\n", cLineAdd, cLineHex, cLineStr)
end    
outFile.printf("\n")
outFile.close()
inFile.close()