Skip to content

Commit

Permalink
Massive overhaul to Table class and render logic
Browse files Browse the repository at this point in the history
  • Loading branch information
KritantaDev authored and KritantaDev committed Jan 19, 2022
1 parent 2c0e24f commit 07ddf8a
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 49 deletions.
6 changes: 3 additions & 3 deletions bin/ktool
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ Print the symbol table
for symbol in image.exports:
table.rows.append([hex(symbol.address), symbol.fullname])

print(table.render(get_terminal_size().columns))
print(table.fetch_all(get_terminal_size().columns))

if args.get_symtab:
with open(args.filename, 'rb') as fd:
Expand All @@ -476,7 +476,7 @@ Print the symbol table
for sym in image.symbol_table.table:
table.rows.append([hex(sym.address), sym.fullname])

print(table.render(get_terminal_size().columns))
print(table.fetch_all(get_terminal_size().columns-1))

if args.get_imports:
with open(args.filename, 'rb') as fd:
Expand All @@ -497,7 +497,7 @@ Print the symbol table
for _, sym in import_symbols.items():
table.rows.append([sym.addr, sym.name, sym.image, sym.from_table])

print(table.render(get_terminal_size().columns))
print(table.fetch_all(get_terminal_size().columns))

elif args.get_actions:
with open(args.filename, 'rb') as fd:
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
long_description = (this_directory / "README.md").read_text()

setup(name='k2l',
version='0.19.4',
version='0.20.0',
description='Static mach-o/img4 analysis tool.',
long_description=long_description,
long_description_content_type='text/markdown',
Expand Down
1 change: 1 addition & 0 deletions src/ktool/dyld.py
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,7 @@ class Symbol(Constructable):
def from_image(cls, image, cmd, entry):
fullname = image.get_cstr_at(entry.str_index + cmd.stroff)
addr = entry.value

symbol = cls.from_values(fullname, addr)

N_STAB = 0xe0
Expand Down
152 changes: 130 additions & 22 deletions src/ktool/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,61 +139,149 @@ class Table:
(shutil.get_terminal_size)
"""

def __init__(self):
def __init__(self, dividers=False):
self.titles = []
self.rows = []
self.dividers = dividers
self.column_pad = 3 if dividers else 2

def render(self, width):
if len(self.rows) == 0:
return ""
self.column_maxes = []
self.most_recent_adjusted_maxes = []

self.rendered_row_cache = {}
self.header_cache = {}

# Initialize an array with zero for each column
column_maxes = [0 for _ in self.rows[0]]
def preheat(self):

self.column_maxes = [0 for _ in self.titles]
self.most_recent_adjusted_maxes = [*self.column_maxes]

# Iterate through each row,
for row in self.rows:
# And in each row, iterate through each column
for index, col in enumerate(row):
# Check the length of this column; if it's larger than the length in the array,
# set the max to the new one
column_maxes[index] = max(len(col), column_maxes[index])
col_size = max([len(i)+self.column_pad for i in col.split('\n')])
self.column_maxes[index] = max(col_size, self.column_maxes[index])

# If the titles are longer than any of the items in that column, account for those too
for i, title in enumerate(self.titles):
column_maxes[i] = max(column_maxes[i], len(title))
self.column_maxes[i] = max(self.column_maxes[i], len(title) + 1 + len(self.titles))

def fetch_all(self, screen_width):
# This function effectively replaces the previous usage of .render() and does it all in one go.
return self.fetch(0, len(self.rows), screen_width)

# Add two to the column maxes, to account for padding
column_maxes = [i + 2 for i in column_maxes]
def fetch(self, row_start, row_count, screen_width):
if row_count == 0:
return ""
rows = []
if screen_width in self.rendered_row_cache:
for i in range(row_start, row_start + row_count):
if str(i) in self.rendered_row_cache[screen_width]:
rows.append(self.rendered_row_cache[screen_width][str(i)])
else:
break
else:
self.rendered_row_cache[screen_width] = {}
r_row_count = row_count - len(rows)
r_start = row_start + len(rows)
rows_text = ''.join([i + '\n' for i in rows])
sep_line = ""

rows_text += self.render(self.rows[r_start:r_start + r_row_count], screen_width, r_start)

if self.dividers:
rows_text = rows_text[:-1] # cut off the "\n"
sep_line = '┠━'
for size in self.most_recent_adjusted_maxes:
sep_line += ''.ljust(size - 2, '━') + '╀━'
sep_line = sep_line[:-self.column_pad].ljust(screen_width - 1, '━')[:-self.column_pad] + '━━━┦'

rows_text = rows_text[:-len(sep_line)] # Use our calculated sep_line length to cut off the last one
rows_text += sep_line.replace('┠', '└').replace('╀', '┸').replace('┦', '┘')

if screen_width in self.header_cache:
rows_text = self.header_cache[screen_width] + rows_text
else:
title_row = ''
for i, title in enumerate(self.titles):
if self.dividers:
title_row += '│ ' + title.ljust(self.most_recent_adjusted_maxes[i], ' ')[:-(self.column_pad - 1)]
else:
try:
title_row += ' ' + title.ljust(self.most_recent_adjusted_maxes[i], ' ')[:-(self.column_pad - 1)]
except IndexError:
# I have no idea what causes this
title_row = ""
header_text = ""
if self.dividers:
header_text += sep_line.replace('┠', '┌').replace('╀', '┬').replace('┦', '┐') + '\n'
header_text += title_row.ljust(screen_width-1)[:-1] + ' │\n' if self.dividers else title_row + '\n'
if self.dividers:
header_text += sep_line + '\n'
self.header_cache[screen_width] = header_text
rows_text = header_text + rows_text

return rows_text

def render(self, _rows, width, row_start):

width -= 1

if len(_rows) == 0:
return ""

if not len(self.column_maxes) > 0:
self.preheat()

column_maxes = [*self.column_maxes]

# Minimum Column Size
col_min = min(column_maxes)

#while sum(column_maxes) < width:
# column_maxes = [i + 1 for i in column_maxes]

# Iterate through column maxes, subtracting one from each until they fit within the passed width arg
while sum(column_maxes) + (len(column_maxes) * 2) >= width:
last_sum = 0
while sum(column_maxes) >= width:
for index, i, in enumerate(column_maxes):
column_maxes[index] = max(col_min, column_maxes[index] - 1)
if sum(column_maxes) == last_sum:
return 'Width too small to render table'
last_sum = sum(column_maxes)

title_row = ''
for i, title in enumerate(self.titles):
title_row += title.ljust(column_maxes[i], ' ')
self.most_recent_adjusted_maxes = [*column_maxes]

rows = []

# bit complex, this just wraps strings within their columns, to create the illusion of 'cells'
for row_i, row in enumerate(self.rows):
for row_i, row in enumerate(_rows):
# cols is going to be an array of columns in this row
# each column is going to be an array of lines
cols = []

max_line_count_in_row = 0
for col_i, col in enumerate(row):
lines = []
column_width = column_maxes[col_i] - 2
column_width = column_maxes[col_i] - self.column_pad
string_cursor = 0
while len(col) - string_cursor > column_width:
lines.append(col[string_cursor:string_cursor + column_width])
string_cursor += column_width
lines.append(col[string_cursor:len(col)])
first_line_of_column = col[string_cursor:string_cursor + column_width].split('\n')[0]
lines.append(first_line_of_column)
string_cursor += len(first_line_of_column)
if col[string_cursor] == '\n':
string_cursor += 1
while string_cursor <= len(col):
first_line_of_column = col[string_cursor:len(col)].split('\n')[0]
lines.append(first_line_of_column)
string_cursor += len(first_line_of_column)
if string_cursor == len(col):
break
if col[string_cursor] == '\n':
string_cursor += 1
max_line_count_in_row = max(len(lines), max_line_count_in_row)
cols.append(lines)

Expand All @@ -204,19 +292,39 @@ def render(self, width):
col.append('')
rows.append(cols)

lines = title_row + '\n'
lines = ""
sep_line = ""

if self.dividers:
sep_line = '┠━'
for size in column_maxes:
sep_line += ''.ljust(size - 2, '━') + '╀━'
sep_line = sep_line[:-self.column_pad].ljust(width, '━')[:-self.column_pad] + '━━━┦'

for row in rows:
if self.dividers:
lines += sep_line + '\n'

for row_index, row in enumerate(rows):
row_lines = []
column_count = len(row[0])
for i in range(0, column_count):
line = ""
for j, col in enumerate(row):
line += col[i].ljust(column_maxes[j], ' ')
if self.dividers:
line = line[:-self.column_pad] + ' │ '
if self.dividers:
line = '│ ' + line[:-self.column_pad].ljust(width, ' ')[:-self.column_pad] + ' │ '
else:
line = ' ' + line[:-self.column_pad].ljust(width, ' ')[:-self.column_pad] + (' ' * self.column_pad)
row_lines.append(line)

if self.dividers:
row_lines.append(sep_line)

self.rendered_row_cache[width + 1][str(row_index + row_start)] = '\n'.join(row_lines)
lines += '\n'.join(row_lines)
lines += '\n'

return lines


Expand Down
Loading

0 comments on commit 07ddf8a

Please sign in to comment.