docs: def hexdump(data, cols=8, folded=False, stream=False, offset=0, header=False):
      """
      yields the rows of the hex dump
  
      Arguments:
          data -- data to dump
          cols -- number of octets per row
          folded -- fold long ranges of equal bytes
          stream -- dont use len on data
  
      >>> from string import ascii_uppercase
      >>> print('\\n'.join(hexdump("".join(chr(i) for i in range(256)))))
      00: 00 01 02 03 04 05 06 07 ........
      08: 08 09 0A 0B 0C 0D 0E 0F ........
      10: 10 11 12 13 14 15 16 17 ........
      18: 18 19 1A 1B 1C 1D 1E 1F ........
      20: 20 21 22 23 24 25 26 27  !"#$%&'
      28: 28 29 2A 2B 2C 2D 2E 2F ()*+,-./
      30: 30 31 32 33 34 35 36 37 01234567
      38: 38 39 3A 3B 3C 3D 3E 3F 89:;<=>?
      40: 40 41 42 43 44 45 46 47 @ABCDEFG
      48: 48 49 4A 4B 4C 4D 4E 4F HIJKLMNO
      50: 50 51 52 53 54 55 56 57 PQRSTUVW
      58: 58 59 5A 5B 5C 5D 5E 5F XYZ[\]^_
      60: 60 61 62 63 64 65 66 67 `abcdefg
      68: 68 69 6A 6B 6C 6D 6E 6F hijklmno
      70: 70 71 72 73 74 75 76 77 pqrstuvw
      78: 78 79 7A 7B 7C 7D 7E 7F xyz{|}~.
      80: 80 81 82 83 84 85 86 87 ........
      88: 88 89 8A 8B 8C 8D 8E 8F ........
      90: 90 91 92 93 94 95 96 97 ........
      98: 98 99 9A 9B 9C 9D 9E 9F ........
      A0: A0 A1 A2 A3 A4 A5 A6 A7 ........
      A8: A8 A9 AA AB AC AD AE AF ........
      B0: B0 B1 B2 B3 B4 B5 B6 B7 ........
      B8: B8 B9 BA BB BC BD BE BF ........
      C0: C0 C1 C2 C3 C4 C5 C6 C7 ........
      C8: C8 C9 CA CB CC CD CE CF ........
      D0: D0 D1 D2 D3 D4 D5 D6 D7 ........
      D8: D8 D9 DA DB DC DD DE DF ........
      E0: E0 E1 E2 E3 E4 E5 E6 E7 ........
      E8: E8 E9 EA EB EC ED EE EF ........
      F0: F0 F1 F2 F3 F4 F5 F6 F7 ........
      F8: F8 F9 FA FB FC FD FE FF ........
  
      >>> print('\\n'.join(hexdump(ascii_uppercase)))
      00: 41 42 43 44 45 46 47 48 ABCDEFGH
      08: 49 4A 4B 4C 4D 4E 4F 50 IJKLMNOP
      10: 51 52 53 54 55 56 57 58 QRSTUVWX
      18: 59 5A                   YZ
      >>> print('\\n'.join(hexdump(ascii_uppercase, stream=True)))
      00000: 41 42 43 44 45 46 47 48 ABCDEFGH
      00008: 49 4A 4B 4C 4D 4E 4F 50 IJKLMNOP
      00010: 51 52 53 54 55 56 57 58 QRSTUVWX
      00018: 59 5A                   YZ
      >>> print('\\n'.join(hexdump(ascii_uppercase + "0"*256, folded=True)))
      000: 41 42 43 44 45 46 47 48 ABCDEFGH
      008: 49 4A 4B 4C 4D 4E 4F 50 IJKLMNOP
      010: 51 52 53 54 55 56 57 58 QRSTUVWX
      018: 59 5A 30 30 30 30 30 30 YZ000000
           *
      118: 30 30                   00
      >>> print('\\n'.join(hexdump(ascii_uppercase + "0"*256, folded=True, stream=True)))
      00000: 41 42 43 44 45 46 47 48 ABCDEFGH
      00008: 49 4A 4B 4C 4D 4E 4F 50 IJKLMNOP
      00010: 51 52 53 54 55 56 57 58 QRSTUVWX
      00018: 59 5A 30 30 30 30 30 30 YZ000000
             *
      00118: 30 30                   00
      >>> print('\\n'.join(hexdump(ascii_uppercase, cols=16)))
      00: 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 ABCDEFGHIJKLMNOP
      10: 51 52 53 54 55 56 57 58 59 5A                   QRSTUVWXYZ
      """
      last_byte = None
      fold = False
  
      # determine index width
      if not stream:
          size = len(data)
          hexlen = len(hex(offset + size - 1)) - 2
          offset_fmt = '{{:0{}X}}: '.format(hexlen)
          if header:
              line = ' ' * (hexlen + 2)
              for i in range(min(cols, size + offset)):
                  line += '{:2X} '.format(i)
              yield line.rstrip()
      else:
          size = None
          hexlen = 5
          offset_fmt = '{:05X}: '
          if header:
              line = ' ' * (5 + 2)
              for i in range(cols):
                  line += '{:2X} '.format(i)
              yield line.rstrip()
  
      # convert input into iter if needed
      if hasattr(data, 'read'):
          def _tmp():
              return data.read(1)
          data_iter = iter(_tmp, '')
      else:
          data_iter = iter(data)
  
      run = True
      while run:
          line = offset_fmt.format(offset - (offset % cols))
          # ASCII dump
          string = ''
          skipped = 0
          prefix = (offset % cols)
  
          # padding
          if offset % cols != 0:
              line += '   ' * prefix
              string += ' ' * prefix
  
          try:
              for _ in range(cols - (offset % cols)):
                  byte = next(data_iter)
                  # byte is equal to previous => count range of equal bytes
                  if last_byte == byte:
                      skipped += 1
                  last_byte = byte
                  offset += 1
                  if isinstance(byte, str):
                      bval = ord(byte)
                  elif isinstance(byte, int):
                      bval = byte
                      if 31 < bval < 127:
                          byte = chr(bval)
                  else:
                      bval = list(byte)[0]
                      if 31 < bval < 127:
                          byte = chr(bval)
  
                  if 31 < bval < 127:
                      string += byte
                  else:
                      string += '.'
                  line += '{:02X} '.format(bval)
          except StopIteration:
              run = False
  
          # if folding is requested
          if folded:
              # all bytes are equal to the last byte of the previous block
              if skipped == cols:
                  # show * the first time
                  if not fold:
                      yield ' ' * (hexlen + 2) + '*'
                  fold = True
                  continue
              else:
                  fold = False
  
          # padding
          if offset % cols != 0:
              line += '   ' * (cols - (offset % cols))
  
          # yield only if no StopIteration was thrown or bytes are remaining
          if offset % cols != 0 or run:
              line += string
              yield line.rstrip()