# frozen_string_literal: true require_relative 'fd/version' # Fd dumps the content of a file to the standard output. # class Fd class Error < StandardError; end ESCAPE_CHARACTERS = %w[␀ ␇ ␈ ␉ ␊ ␋ ␌ ␍ ␡ ␛ ␠] attr_reader :line_length, :char_table # _line_length_ sets how many characters are displayed per @line. # Some special non-printable/invisible characters are displayed as their names. # # Name :: Char val # NULL :: 0 # BEL :: 7 # BS :: 8 # TAB :: 9 # LF :: 10 # VT :: 11 # FF :: 12 # CR :: 13 # DEL :: 16 # ESC :: 27 # __ :: 32 # def initialize(line_length) raise ArgumentError, "Line width must be a positive integer, was given '#{line_length}'" unless line_length.is_a?(Integer) && line_length.positive? @line_length = line_length @char_table = {} @char_table[0] = '␀' @char_table[7] = '␇' @char_table[8] = '␈' @char_table[9] = '␉' @char_table[10] = '␊' @char_table[11] = '␋' @char_table[12] = '␌' @char_table[13] = '␍' @char_table[16] = '␡' @char_table[27] = '␛' @char_table[32] = '␠' end def add_esc_sequence(chr) "\e[31m#{chr}\e[0m" end # dumps the given file _file_name_ to stdout. def dump(content) raise "Not the expected encoding of UFT-8, got #{content.encoding}" unless content.encoding == Encoding::UTF_8 initialize_fields(content) process_current_character while @char_index < @chars.size print_single_line unless line.empty? end def dump_file(file_name) puts file_name dump(File.read(file_name)) end def dump_stdin(content) puts 'STDIN' dump(content) end private def process_current_character char = @chars[@char_index] bytes = char.bytes if enough_space_in_line?(@byte_count_in_line, bytes) append_to_line(bytes, char) else print_single_line reset_line end end def initialize_fields(content) @chars = content.chars @byte_count_in_line = 0 @line = '' @hex_values = [] @char_index = 0 end attr_reader :hex_values, :line def reset_line @byte_count_in_line = 0 @hex_values.clear @line = '' end def append_to_line(bytes, char) @byte_count_in_line += bytes.size bytes.each { |bt| @hex_values << format('%02x', bt) } @line += format('%2s', (char_table[char.ord] || char)) @char_index += 1 end def enough_space_in_line?(byte_count_in_line, bytes) byte_count_in_line + bytes.size <= line_length end def print_single_line puts("#{format("%#{(3 * line_length) - 1}s", hex_values.join(' '))} |#{format("%#{2 * line_length}s", line)}". gsub(/([#{ESCAPE_CHARACTERS.join('')}])/, add_esc_sequence('\1')) ) end end