#!/bin/bash /home/richmit/bin/ruby20
# -*- Mode:Ruby; Coding:us-ascii-unix; fill-column:158 -*-
################################################################################################################################################################
##
# @file      ascii.rb
# @author    Mitch Richling <https://www.mitchr.me>
# @brief     Prints out code page tables for various character sets (ascii, ISO-8859-1, Windows-1252, & UTF-8).@EOL
# @std       Ruby 1.8
# @copyright 
#  @parblock
#  Copyright (c) 1992,1999,2005,2011,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.
#  @endparblock
################################################################################################################################################################

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

################################################################################################################################################################
# Get options
numRows   = 16
doUTF     = FALSE
doDec     = FALSE
doOct     = FALSE
doHex     = TRUE
doNames   = TRUE
pages     = [0]
enc       = Encoding::UTF_8
opts = OptionParser.new do |opts|
  opts.banner = "Usage: ascii.rb [options]"
  opts.separator ""
  opts.separator "Options:"
  opts.on("-h",          "--help",            "Show this message")   { puts opts; exit                           }
  opts.on("-r INT",      "--row INT",         "Set rows")            { |v| numRows=v.to_i                        }
  opts.on("-p INT,..",   "--pages INT,..",    "Pages to display")    { |v| pages=Array.new
                                                                           v.split(',').each do |x|
                                                                             if (x.match('-')) then
                                                                               (a,b)=x.split('-')
                                                                               a.to_i.upto(b.to_i) do |i|
                                                                                 pages.push(i)
                                                                               end
                                                                             else
                                                                               pages.push(x.to_i)
                                                                             end
                                                                           end
                                                                     }
  opts.on("-E ENCODING", "--enc ENCODING",    "Encoding for table")  { |v| enc=v                                 }
  opts.on(               "--names [y/n]",     "Use ctrl char names") { |v| doNames=v.match(/^[YyTt]/)            }
  opts.on(               "--utf [y/n]",       "Display UTF-8 bytes") { |v| doUTF=v.match(/^[YyTt]/)              }
  opts.on(               "--oct [y/n]",       "Display OCT")         { |v| doOct=v.match(/^[YyTt]/)              }
  opts.on(               "--dec [y/n]",       "Display DEC")         { |v| doDec=v.match(/^[YyTt]/)              }
  opts.on(               "--hex [y/n]",       "Display HEX")         { |v| doHex=v.match(/^[YyTt]/)              }
  opts.separator ""
  opts.separator "Display character tables for ascii, ISO-8859-1, Windows-1252, & UTF-8."
  opts.separator "Terminal must be set to UTF-8 for non-ASCII (7-bit) character sets"
  opts.separator ""
end
opts.parse!(ARGV)

################################################################################################################################################################
# Figure out encoding
if(enc.class == String) then
  if(enc.downcase.match(/utf/)) then
    enc = Encoding::UTF_8
  elsif(enc.downcase.match(/(8859|latan|latin|ascii)/)) then
    enc = Encoding::ISO_8859_1
  elsif(enc.downcase.match(/(windows|1252)/)) then
    enc = Encoding::Windows_1252
  else
    puts("ERROR: Unknown encodeing: #{enc}")
    exit
  end
end

################################################################################################################################################################
numCols  = 128 / numRows
maxPage = pages.max
decWide = (128*maxPage+127).to_s(10).length
octWide = (128*maxPage+127).to_s( 8).length
hexWide = (128*maxPage+127).to_s(16).length
charDefWide = 3

utfDefWide = 0
pages.each do |page|
  0.upto(numRows-1) do |rowNum|
    0.upto(numCols-1) do |colNum|
      x = ((rowNum+colNum*numRows+128*page).chr(enc).encode(Encoding::UTF_8).bytes.map { |x| sprintf("%02x", x) }).join('_').length
      if(x>utfDefWide) then
        utfDefWide = x
      end
    end
  end
end
utfDefWide = [ utfDefWide, 3].max

################################################################################################################################################################
# Names for non-printable ASCII/ISO-8859-1
desc = Array.new
desc = [ "NUL", "SOH", "STX", "ETX", "EOT", "NEQ", "ACK", "BEL", "BS ", "HT ", "NL ", "VT ", "NP ", "CR ", "SO ", "SI ", "DLE", 
         "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", "CAN", "EM ", "SUB", "ESC", "FS ", "GS ", "RS ", "US ", "SP "]
desc[0x7f] = 'DEL';
desc[0xa0] = 'NSP'
desc[0xad] = 'SHY'
if (enc == Encoding::Windows_1252) then
  [0x81, 0x8d, 0x8f, 0x90, 0x9d].each do |i|
    desc[i] = ''
  end
else
  0x80.upto(0x9f) do |i|
    desc[i] = ''
  end
end

if( !(doNames)) then
  desc.map! { |x| (x ? '' : nil) }
end


################################################################################################################################################################
pages.each do |page|
  charWide   = (page < 6 ? charDefWide : 1)
  utfWide = (page < 6 ? utfDefWide : 1) 

  # Print titles
  if(page < 6) then
    0.upto(numCols-1) do |colNum|
      print "| CHR";
      if(doUTF) then
        printf(" %#{utfWide}s", 'UTF')
      end
      if(doOct) then
        printf(" %#{octWide}s", "Oct");
      end
      if(doDec) then
        printf(" %#{decWide}s", "Dec");
      end
      if(doHex) then
        printf(" %#{hexWide}s", "Hx");
      end
      print(" ")
    end
    print("|\n")
  end

  0.upto(numRows-1) do |rowNum|
    print("|");
    0.upto(numCols-1) do |colNum|
      curCharNum = rowNum+colNum*numRows+128*page      
      curChar    = curCharNum.chr(enc)
      curCharLab = curChar
      if ((curCharNum<desc.length) && (desc[curCharNum])) then
        curCharLab = desc[curCharNum]
      end
      curCharLab = curCharLab.encode(Encoding::UTF_8)
      printf(" %-#{charWide}s", curCharLab);
      if(doUTF) then
        charUTFbytes = (curChar.encode(Encoding::UTF_8).bytes.map { |x| sprintf("%02x", x) }).join('_')
        printf(" %#{utfWide}s", charUTFbytes)
      end
      if(doOct) then
        printf(" %0#{octWide}o", curCharNum)
      end
      if(doDec) then
        printf(" %#{decWide}d", curCharNum)
      end
      if(doHex) then
        printf(" %0#{hexWide}x", curCharNum)
      end
      printf(" |");
    end
    print("\n")
  end
  print("\n")
end