Class: Hexdump::Hexdump

Inherits:
Object
  • Object
show all
Defined in:
lib/hexdump/hexdump.rb

Overview

Handles the parsing of data and formatting of the hexdump.

Since:

  • 1.0.0

Constant Summary collapse

DEFAULT_COLUMNS =

Default number of columns

Since:

  • 1.0.0

16
BASES =

Numeric bases and their formatting classes.

Since:

  • 1.0.0

{
  16 => Numeric::Hexadecimal,
  10 => Numeric::Decimal,
  8  => Numeric::Octal,
  2  => Numeric::Binary
}

Reader Configuration collapse

Numeric Configuration collapse

Index Configuration collapse

Characters Configuration collapse

Instance Attribute Summary collapse

Theme Configuration collapse

Formatting Methods collapse

Instance Method Summary collapse

Constructor Details

#initialize(type: :byte, offset: nil, length: nil, zero_pad: false, repeating: false, columns: nil, group_columns: nil, group_chars: nil, base: nil, index_base: 16, index_offset: nil, chars_column: true, encoding: nil, style: nil, highlights: nil) {|self| ... } ⇒ Hexdump

Initializes a hexdump format.

Examples:

Initializing styling:

Hexdump::Hexdump.new(style: {index: :white, numeric: :green, chars: :cyan})

Initializing highlighting:

Hexdump::Hexdump.new(
  highlights: {
    index: {/00$/ => [:white, :bold]},
    numeric: {
      /^[8-f][0-9a-f]$/ => :faint,
      /f/  => :cyan,
      '00' => [:black, :on_red]
    },
    chars: {/[^\.]+/ => :green}
  }
)

Initializing with a block:

Hexdump::Hexdump.new do |hexdump|
  hexdump.type = :uint16
  # ...

  hexdump.theme do |theme|
    theme.index.highlight(/00$/, [:white, :bold])
    theme.numeric.highlight(/^[8-f][0-9a-f]$/, :faint)
    # ...
  end
end

Parameters:

  • type (:int8, :uint8, :char, :uchar, :byte, :int16, :int16_le, :int16_be, :int16_ne, :uint16, :uint16_le, :uint16_be, :uint16_ne, :short, :short_le, :short_be, :short_ne, :ushort, :ushort_le, :ushort_be, :ushort_ne, :int32, :int32_le, :int32_be, :int32_ne, :uint32, :uint32_le, :uint32_be, :uint32_ne, :int, :long, :long_le, :long_be, :long_ne, :uint, :ulong, :ulong_le, :ulong_be, :ulong_ne, :int64, :int64_le, :int64_be, :int64_ne, :uint64, :uint64_le, :uint64_be, :uint64_ne, :long_long, :long_long_le, :long_long_be, :long_long_ne, :ulong_long, :ulong_long_le, :ulong_long_be, :ulong_long_ne, :float, :float_le, :float_be, :float_ne, :double, :double_le, :double_be, :double_ne) (defaults to: :byte)

    (:byte) The type to decode the data as.

  • offset (Integer, nil) (defaults to: nil)

    Controls whether to skip N number of bytes before starting to read data.

  • length (Integer, nil) (defaults to: nil)

    Controls control many bytes to read.

  • zero_pad (Boolean) (defaults to: false)

    Enables or disables zero padding of data, so that the remaining bytes can be decoded as a uint, int, or float.

  • repeating (Boolean) (defaults to: false)

    Controls whether to omit repeating duplicate rows data with a *.

  • columns (Integer) (defaults to: nil)

    The number of columns per hexdump line. Defaults to 16 / sizeof(type).

  • group_columns (Integer, nil) (defaults to: nil)

    Separate groups of columns with an additional space.

  • group_chars (Integer, :type, nil) (defaults to: nil)

    Group chars into columns. If :type, then the chars will be grouped by the type's size.

  • base (16, 10, 8, 2) (defaults to: nil)

    The base to print bytes in. Defaults to 16, or to 10 if printing floats.

  • index_base (16, 10, 8, 2) (defaults to: 16)

    Control the base that the index is displayed in. Defaults to base 16.

  • index_offset (Integer) (defaults to: nil)

    The offset to start the index at.

  • chars_column (Boolean) (defaults to: true)

    Controls whether to display the characters column.

  • encoding (:ascii, :utf8, Encoding, nil) (defaults to: nil)

    The encoding to display the characters in.

  • style (Boolean, Hash{:index,:numeric,:chars => Symbol,Array<Symbol>}) (defaults to: nil)

    Enables theming of index, numeric, or chars columns.

  • highlights (Boolean, Hash{:index,:numeric,:chars => Hash{String,Regexp => Symbol,Array<Symbol>}}) (defaults to: nil)

    Enables selective highlighting of index, numeric, or chars columns.

Yields:

  • (self)

    If a block is given, it will be passed the newly initialized hexdump instance.

Raises:

  • (ArgumentError)

    The values for :base or :endian were unknown.

Since:

  • 1.0.0



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/hexdump/hexdump.rb', line 143

def initialize(type: :byte, offset: nil, length: nil, zero_pad: false, repeating: false, columns: nil, group_columns: nil, group_chars: nil, base: nil, index_base: 16, index_offset: nil, chars_column: true, encoding: nil, style: nil, highlights: nil)
  # reader options
  self.type      = type
  self.offset    = offset
  self.length    = length
  self.zero_pad  = zero_pad
  self.repeating = repeating

  # numeric formatting options
  self.base          = base if base
  self.columns       = columns
  self.group_columns = group_columns

  # index options
  self.index_base   = index_base
  self.index_offset = index_offset || offset

  # chars formatting options
  self.encoding     = encoding
  self.chars_column = chars_column
  self.group_chars  = group_chars

  @theme = if (style.kind_of?(Hash) || highlights.kind_of?(Hash))
             Theme.new(
               style:      style || {},
               highlights: highlights || {}
             )
           end

  yield self if block_given?

  @reader = Reader.new(@type, offset:   @offset,
                              length:   @length,
                              zero_pad: @zero_pad)

  # default the numeric base
  @base ||= case @type
            when Type::Float, Type::Char, Type::UChar then 10
            else                                           16
            end

  # default the number of columns based on the type's size
  @columns ||= (DEFAULT_COLUMNS / @type.size)

  @index   = BASES.fetch(@index_base).new(TYPES[:uint32])
  @numeric = BASES.fetch(@base).new(@type)

  case @type
  when Type::Char, Type::UChar
    # display characters inline for the :char and :uchar type, and disable
    # the characters column
    @numeric = Numeric::CharOrInt.new(@numeric,@encoding)

    @chars        = nil
    @chars_column = false
  else
    @chars = Chars.new(@encoding) if @chars_column
  end
end

Instance Attribute Details

#base16, ...

The base to dump words as.

Returns:

  • (16, 10, 8, 2)

Since:

  • 1.0.0



272
273
274
# File 'lib/hexdump/hexdump.rb', line 272

def base
  @base
end

#charsChars? (readonly)

The characters formatter.

Returns:

Since:

  • 1.0.0



56
57
58
# File 'lib/hexdump/hexdump.rb', line 56

def chars
  @chars
end

#chars_columnBoolean Also known as: chars_column?

Controls whether to display the characters column.

Returns:

  • (Boolean)

Since:

  • 1.0.0



382
383
384
# File 'lib/hexdump/hexdump.rb', line 382

def chars_column
  @chars_column
end

#columnsInteger

The number of columns per hexdump line.

Returns:

  • (Integer)

Since:

  • 1.0.0



299
300
301
# File 'lib/hexdump/hexdump.rb', line 299

def columns
  @columns
end

#encodingEncoding?

The encoding to use when decoding characters.

Returns:

  • (Encoding, nil)

Since:

  • 1.0.0



355
356
357
# File 'lib/hexdump/hexdump.rb', line 355

def encoding
  @encoding
end

#group_charsInteger?

Groups the characters together into groups.

Returns:

  • (Integer, nil)

Since:

  • 1.0.0



391
392
393
# File 'lib/hexdump/hexdump.rb', line 391

def group_chars
  @group_chars
end

#group_columnsInteger?

The number of columns to group together.

Returns:

  • (Integer, nil)

Since:

  • 1.0.0



306
307
308
# File 'lib/hexdump/hexdump.rb', line 306

def group_columns
  @group_columns
end

#indexNumeric::Hexadecimal, ... (readonly)

The format of the index number.



43
44
45
# File 'lib/hexdump/hexdump.rb', line 43

def index
  @index
end

#index_base16, ...

The base to format the index column as.

Returns:

  • (16, 10, 8, 2)

Since:

  • 1.0.0



317
318
319
# File 'lib/hexdump/hexdump.rb', line 317

def index_base
  @index_base
end

#index_offsetInteger?

Starts the index at the given offset.

Returns:

  • (Integer, nil)

Since:

  • 1.0.0



344
345
346
# File 'lib/hexdump/hexdump.rb', line 344

def index_offset
  @index_offset
end

#lengthInteger?

The optional length of data to read.

Returns:

  • (Integer, nil)

Since:

  • 1.0.0



226
227
228
# File 'lib/hexdump/hexdump.rb', line 226

def length
  @length
end

#numericNumeric::Hexadecimal, ... (readonly)

The numeric base format.



51
52
53
# File 'lib/hexdump/hexdump.rb', line 51

def numeric
  @numeric
end

#offsetInteger?

The optional offset to start the index at.

Returns:

  • (Integer, nil)

Since:

  • 1.0.0



219
220
221
# File 'lib/hexdump/hexdump.rb', line 219

def offset
  @offset
end

#readerReader (readonly)

The reader object.

Returns:

Since:

  • 1.0.0



35
36
37
# File 'lib/hexdump/hexdump.rb', line 35

def reader
  @reader
end

#repeatingBoolean Also known as: repeating?

Controls whether repeating duplicate rows will be omitted with a *.

Returns:

  • (Boolean)

Since:

  • 1.0.0



242
243
244
# File 'lib/hexdump/hexdump.rb', line 242

def repeating
  @repeating
end

#typeType

The word type to decode the byte stream as.

Returns:

Since:

  • 1.0.0



212
213
214
# File 'lib/hexdump/hexdump.rb', line 212

def type
  @type
end

#zero_padBoolean Also known as: zero_pad?

Controls whether to zero-pad the data so it aligns with the type's size.

Returns:

  • (Boolean)

Since:

  • 1.0.0



233
234
235
# File 'lib/hexdump/hexdump.rb', line 233

def zero_pad
  @zero_pad
end

Instance Method Details

#dump(data) ⇒ String

Note:

Caution: this method appends each line of the hexdump to a String, and that String can grow quite large and consume a lot of memory.

Outputs the hexdump to a String.

Parameters:

  • data (#each_byte)

    The data to be hexdumped.

Returns:

  • (String)

    The output of the hexdump.

Since:

  • 1.0.0



770
771
772
773
774
# File 'lib/hexdump/hexdump.rb', line 770

def dump(data)
  String.new.tap do |string|
    hexdump(data, output: string)
  end
end

#each_formatted_row(data, ansi: theme?, , **kwargs) {|index, numeric, chars| ... } ⇒ String, Enumerator

Enumerates each formatted row of hexdumped data.

Parameters:

  • data (#each_byte)

    The data to be hexdumped.

  • ansi (Boolean) (defaults to: theme?, )

    Whether to enable styling/highlighting.

Yields:

  • (index, numeric, chars)

    The given block will be passed the hexdump break-down of each row.

Yield Parameters:

  • index (String)

    The index of the hexdumped row. If the index is '*', then it indicates the beginning of repeating rows of data.

  • numeric (Array<String>, nil)

    The numeric representation of the row.

  • chars (String, nil)

    The printable representation of the row.

Returns:

  • (String, Enumerator)

    If a block is given, the final number of bytes read will be returned. If no block is given, an Enumerator will be returned.

Since:

  • 1.0.0



600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
# File 'lib/hexdump/hexdump.rb', line 600

def each_formatted_row(data, ansi: theme?, **kwargs)
  return enum_for(__method__,data, ansi: ansi) unless block_given?

  format_index = lambda { |index|
    formatted = @index % index
    formatted = @theme.index.apply(formatted) if ansi
    formatted
  }

  blank = ' ' * @numeric.width

  format_numeric = lambda { |value|
    if value
      formatted = @numeric % value
      formatted = @theme.numeric.apply(formatted) if ansi
      formatted
    else
      blank
    end
  }

  # cache the formatted numbers for 8bit and 16bit values
  numeric_cache = if @type.size <= 2
                    Hash.new do |hash,value|
                      hash[value] = format_numeric.call(value)
                    end
                  else
                    format_numeric
                  end

  if @chars
    format_chars = lambda { |chars|
      formatted = @chars.scrub(chars.join)
      formatted = @theme.chars.apply(formatted) if ansi
      formatted
    }
  end

  enum = if @repeating then each_row(data)
         else               each_non_repeating_row(data)
         end

  index = enum.each do |index,numeric,chars=nil|
    if index == '*'
      yield index
    else
      formatted_index   = format_index[index]
      formatted_numbers = numeric.map { |value| numeric_cache[value] }

      formatted_chars = if @chars
                          if @group_chars
                            chars.join.chars.each_slice(@group_chars).map(&format_chars)
                          else
                            format_chars.call(chars)
                          end
                        end

      yield formatted_index, formatted_numbers, formatted_chars
    end
  end

  return format_index[index]
end

#each_line(data, **kwargs) {|line| ... } ⇒ Enumerator?

Enumerates over each line in the hexdump.

Parameters:

  • data (#each_byte)

    The data to be hexdumped.

  • kwargs (Hash{Symbol => Object})

    Additional keyword arguments for #each_formatted_row.

Yields:

  • (line)

    The given block will be passed each line from the hexdump.

Yield Parameters:

  • line (String)

    Each line from the hexdump output, terminated with a newline character.

Returns:

  • (Enumerator)

    If no block is given, an Enumerator object will be returned

  • (nil)

Since:

  • 1.0.0



684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
# File 'lib/hexdump/hexdump.rb', line 684

def each_line(data,**kwargs)
  return enum_for(__method__,data,**kwargs) unless block_given?

  join_numeric = if @group_columns
                   lambda { |numeric|
                     numeric.each_slice(@group_columns).map { |numbers|
                       numbers.join(' ')
                     }.join('  ')
                   }
                 else
                   lambda { |numeric| numeric.join(' ') }
                 end

  index = each_formatted_row(data,**kwargs) do |index,numeric,chars=nil|
    if index == '*'
      yield "#{index}#{$/}"
    else
      numeric_column = join_numeric.call(numeric)

      if numeric.length < @columns
        missing_columns = (@columns - numeric.length)
        column_width    = @numeric.width + 1

        spaces = (missing_columns * column_width)
        spaces += ((missing_columns / @group_columns) - 1) if @group_columns

        numeric_column << ' ' * spaces
      end

      line = if @chars
               if @group_chars
                 chars = chars.join('|')
               end

               "#{index}  #{numeric_column}  |#{chars}|#{$/}"
             else
               "#{index}  #{numeric_column}#{$/}"
             end

      yield line
    end
  end

  yield "#{index}#{$/}"
  return nil
end

#each_non_repeating_row(data) {|index, numeric, chars| ... } ⇒ Integer, Enumerator

Enumerates each non-repeating row of hexdumped data.

Parameters:

  • data (#each_byte)

    The data to be hexdumped.

Yields:

  • (index, numeric, chars)

    The given block will be passed the hexdump break-down of each row.

Yield Parameters:

  • index (Integer, '*')

    The index of the hexdumped row. If the index is '*', then it indicates the beginning of repeating rows of data.

  • values (Array<Integer>, Array<Float>, nil)

    The decoded values.

  • chars (String, nil)

    A raw characters that were read.

Returns:

  • (Integer, Enumerator)

    If a block is given, the final number of bytes read will be returned. If no block is given, an Enumerator will be returned.

Since:

  • 1.0.0



548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
# File 'lib/hexdump/hexdump.rb', line 548

def each_non_repeating_row(data)
  return enum_for(__method__,data) unless block_given?

  previous_row = nil
  is_repeating = false

  each_row(data) do |index,*row|
    if row == previous_row
      unless is_repeating
        yield '*'
        is_repeating = true
      end
    else
      if is_repeating
        previous_row = nil
        is_repeating = false
      end

      yield index, *row
      previous_row = row
    end
  end
end

#each_row(data) {|index, values, chars| ... } ⇒ Integer, Enumerator

Enumerates each row of values read from the given data.

Parameters:

  • data (#each_byte)

    The data to be hexdumped.

Yields:

  • (index, values, chars)

    The given block will be passed the hexdump break-down of each row.

Yield Parameters:

  • index (Integer)

    The index of the hexdumped row.

  • values (Array<Integer>, Array<Float>)

    The decoded values.

  • chars (Array<String>, nil)

    The underlying raw characters that were read.

Returns:

  • (Integer, Enumerator)

    If a block is given, then the final number of bytes read is returned. If no block is given, an Enumerator will be returned.

Since:

  • 1.0.0



497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
# File 'lib/hexdump/hexdump.rb', line 497

def each_row(data,&block)
  return enum_for(__method__,data) unless block_given?

  index = @index_offset || 0
  chars = nil

  each_slice(data) do |slice|
    numeric = []
    chars   = [] if @chars

    next_index = index

    slice.each do |(raw,value)|
      numeric << value
      chars   << raw if @chars

      next_index += raw.length
    end

    yield index, numeric, chars
    index = next_index
  end

  return index
end

#each_slice(data) {|slice| ... } ⇒ Enumerator

Enumerates over each slice of read values.

Parameters:

  • data (#each_byte)

    The data to be hexdumped.

Yields:

  • (slice)

    The given block will be passed the hexdump break-down of each row.

Yield Parameters:

  • slice (Array<(String, Integer)>, Array<(String, Float)>)

    The decoded values.

Returns:

  • (Enumerator)

    If no block is given, an Enumerator will be returned.

Since:

  • 1.0.0



470
471
472
# File 'lib/hexdump/hexdump.rb', line 470

def each_slice(data,&block)
  @reader.each(data).each_slice(@columns,&block)
end

#hexdump(data, output: $stdout) ⇒ nil

Prints the hexdump.

Parameters:

  • data (#each_byte)

    The data to be hexdumped.

  • output (#<<) (defaults to: $stdout)

    The output to dump the hexdump to.

Returns:

  • (nil)

Raises:

  • (ArgumentError)

    The output value does not support the #<< method.

Since:

  • 1.0.0



745
746
747
748
749
750
751
752
753
754
755
# File 'lib/hexdump/hexdump.rb', line 745

def hexdump(data, output: $stdout)
  unless output.respond_to?(:<<)
    raise(ArgumentError,"output must support the #<< method")
  end

  ansi = theme? && output.tty?

  each_line(data, ansi: ansi) do |line|
    output << line
  end
end

#theme {|theme| ... } ⇒ Theme?

The hexdump theme.

Yields:

  • (theme)

    If a block is given, the theme will be auto-initialized and yielded.

Yield Parameters:

  • theme (Theme)

    The hexdump theme.

Returns:

  • (Theme, nil)

    The initialized hexdump theme.

Since:

  • 1.0.0



441
442
443
444
445
446
447
448
# File 'lib/hexdump/hexdump.rb', line 441

def theme(&block)
  if block
    @theme ||= Theme.new
    @theme.tap(&block)
  else
    @theme
  end
end

#theme?Boolean

Determines if hexdump styling/highlighting has been enabled.

Returns:

  • (Boolean)

Since:

  • 1.0.0



423
424
425
# File 'lib/hexdump/hexdump.rb', line 423

def theme?
  !@theme.nil?
end