Land #48, Adds color support for wrapped tables

This commit is contained in:
adfoster-r7 2022-09-02 13:12:51 +01:00 committed by GitHub
commit 17a41105cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 676 additions and 64 deletions

View File

@ -98,6 +98,21 @@ module Rex
end
# @return [Regexp] Matches a valid color code, i.e. "%blu,%yel,...etc"
COLOR_CODES_REGEX = /#{Regexp.union(Rex::Text::Color::SUPPORTED_FORMAT_CODES.compact).source}/
private_constant :COLOR_CODES_REGEX
#
# Function that aims to calculate the display width of the given string.
# In the future this will be aware of East Asian characters having different display
# widths. For now it simply returns the string's length ignoring color codes.
#
# @param [String] str
# @return [Integer]
def self.display_width(str)
str.gsub(COLOR_CODES_REGEX, '').length
end
#
# Convert 16-byte string to a GUID string
#

View File

@ -10,6 +10,7 @@ module Text
#
###
module Color
SUPPORTED_FORMAT_CODES = %w[%cya %red %grn %blu %yel %whi %mag %blk %dred %dgrn %dblu %dyel %dcya %dwhi %dmag %und %bld %clr %bgblu %bgyel %bggrn %bgmag %bgblk %bgred %bgcyn %bgwhi]
AnsiAttributes =
{

View File

@ -390,6 +390,7 @@ protected
# Converts a row to a string.
#
def row_to_s(row) # :nodoc:
row = row.each_with_index.map { |cell, index| style_table_field(cell, index) }
optimal_widths = calculate_optimal_widths
values_as_chunks = chunk_values(row, optimal_widths)
chunks_to_s(values_as_chunks, optimal_widths)
@ -401,17 +402,134 @@ protected
# widths. For now it simply returns the string's length.
#
def display_width(str)
str.length
Rex::Text.display_width(str)
end
#
# Returns a string of color/formatting codes made up of the previously stored color_state
# e.g. if a `%blu` color code spans multiple lines this will return a string of `%blu` to be appended to
# the beginning of each row
#
# @param [Hash<String, String>] color_state tracks current color/formatting codes within table row
# @returns [String] Color code string such as `%blu%grn'
def color_code_string_for(color_state)
result = ''.dup
color_state.each do |_format, value|
if value.is_a?(Array)
result << value.uniq.join
else
result << value
end
end
result
end
# @returns [Hash<String, String>] The supported color codes from {Rex::Text::Color} grouped into sections
def color_code_groups
return @color_code_groups if @color_code_groups
@color_code_groups = {
foreground: %w[
%cya %red %grn %blu %yel %whi %mag %blk
%dred %dgrn %dblu %dyel %dcya %dwhi %dmag
],
background: %w[
%bgblu %bgyel %bggrn %bgmag %bgblk %bgred %bgcyn %bgwhi
],
decoration: %w[
%und %bld
],
clear: %w[
%clr
]
}
# Developer exception raised to ensure all color codes are accounted for. Verified via tests.
missing_color_codes = (Rex::Text::Color::SUPPORTED_FORMAT_CODES - @color_code_groups.values.flatten)
raise "Unsupported color codes #{missing_color_codes.join(', ')}" if missing_color_codes.any?
@color_code_groups
end
# Find the preceding color type and value of a given string
# @param [String] string A string such as '%bgyel etc'
# @returns [Array,nil] A tuple with the color type and value, or nil
def find_color_type_and_value(string)
color_code_groups.each do |color_type, color_values|
color_value = color_values.find { |color_value| string.start_with?(color_value) }
if color_value
return [color_type, color_value]
end
end
nil
end
#
# Takes an array of row values and an integer of optimal column width, loops over array and parses
# each string to gather color/formatting tags and handles those appropriately while not increasing the column width
#
# e.g. if a formatting "%blu" spans across multiple lines it needs to be added to the beginning off every following
# line, and each line will have a "%clr" added to the end of each row
#
# @param [Array<String>] values
# @param [Integer] optimal_widths
def chunk_values(values, optimal_widths)
# First split long strings into an array of chunks, where each chunk size is the calculated column width
values_as_chunks = values.each_with_index.map do |value, idx|
color_state = {}
column_width = optimal_widths[idx]
value
.chars
.each_slice(column_width)
.map(&:join)
chunks = []
current_chunk = nil
chars = value.chars
char_index = 0
# Check if any color code(s) from previous the string need appended
while char_index < chars.length do
# If a new chunk has started, start the chunk with any previous color codes
if current_chunk.nil? && color_state.any?
current_chunk = color_code_string_for(color_state)
end
current_chunk ||= ''.dup
# Check if the remaining chars start with a color code such as %blu
color_type_and_value = chars[char_index] == '%' ? find_color_type_and_value(chars[char_index..].join) : nil
if color_type_and_value.nil?
current_chunk << chars[char_index]
char_index += 1
else
color_type, color_code = color_type_and_value
if color_type == :clear
color_state.clear
elsif color_type == :decoration
# Multiple decorations can be enabled
color_state[:decoration] ||= []
color_state[:decoration] << color_code
else
# There can only be one foreground or background color
color_state[color_type] = color_code
end
current_chunk << color_code
char_index += color_code.length
end
# If we've reached the final character of the string, or need to word wrap
# it's time to push the current chunk, and start a new row. Also discard
# any values that are purely colors and have no display_width
is_final_character = char_index >= chars.length
display_width = display_width(current_chunk)
if (is_final_character && display_width != 0) || display_width == column_width
if color_state.any? && !current_chunk.end_with?('%clr')
current_chunk << '%clr'
end
chunks.push(current_chunk)
current_chunk = nil
end
end
chunks
end
values_as_chunks
@ -424,12 +542,13 @@ protected
line = ""
row_chunks.each_with_index do |chunk, idx|
column_width = optimal_widths[idx]
chunk_length_with_padding = column_width + (chunk.to_s.length - display_width(chunk.to_s))
if idx == 0
line << ' ' * indent
end
line << chunk.to_s.ljust(column_width)
line << chunk.to_s.ljust(chunk_length_with_padding)
line << ' ' * cellpad
end
@ -519,13 +638,12 @@ protected
str_cp
end
def style_table_field(str, _idx)
def style_table_field(str, idx)
str_cp = str.dup
# Not invoking as color currently conflicts with the wrapping of tables
# colprops[idx]['Stylers'].each do |s|
# str_cp = s.style(str_cp)
# end
colprops[idx]['Stylers'].each do |s|
str_cp = s.style(str_cp)
end
str_cp
end

View File

@ -61,10 +61,75 @@ describe Rex::Text::Table do
it "ignores the user preference when set to false" do
expect(described_class.wrap_table?(disable_wrapped_table_options)).to be false
end
end
end
end
it 'should return a blank table as no search terms were matched' do
col_1_field = "A" * 5
col_2_field = "B" * 50
col_3_field = "C" * 15
options = {
'Header' => 'Header',
'SearchTerm' => 'jim|bob',
'Columns' => [
'Column 1',
'Column 2',
'Column 3'
]
}
tbl = Rex::Text::Table.new(options)
tbl << [
col_1_field,
col_2_field,
col_3_field
]
expect(tbl.to_s).to eql <<~TABLE
Header
======
Column 1 Column 2 Column 3
-------- -------- --------
TABLE
end
it 'should return the row as the row contains a match for the search term' do
col_1_field = "jim"
col_2_field = "B" * 50
col_3_field = "C" * 15
options = {
'Header' => 'Header',
'SearchTerm' => 'jim|bob',
'Columns' => [
'Column 1',
'Column 2',
'Column 3'
]
}
tbl = Rex::Text::Table.new(options)
tbl << [
col_1_field,
col_2_field,
col_3_field
]
expect(tbl.to_s).to eql <<~TABLE
Header
======
Column 1 Column 2 Column 3
-------- -------- --------
jim BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCC
TABLE
end
it 'should space columns correctly' do
col_1_field = "A" * 5
col_2_field = "B" * 50
@ -72,7 +137,6 @@ describe Rex::Text::Table do
options = {
'Header' => 'Header',
'SearchTerm' => ['jim', 'bob'],
'Columns' => [
'Column 1',
'Column 2',
@ -105,7 +169,6 @@ describe Rex::Text::Table do
options = {
'Header' => 'Header',
'SearchTerm' => ['jim', 'bob'],
'Columns' => [
'Column 1',
'Column 2',
@ -144,7 +207,6 @@ describe Rex::Text::Table do
options = {
'Header' => 'Header',
'SearchTerm' => ['jim', 'bob'],
'Columns' => [
'Column 1',
'Column 2',

View File

@ -17,7 +17,7 @@ describe Rex::Text::Table do
let(:styler) do
clazz = Class.new do
def style(str)
"IHAVEBEENSTYLED#{str}"
"%blu#{str}%clr"
end
end
@ -89,7 +89,7 @@ describe Rex::Text::Table do
"<EFBFBD>_<EFBFBD><EFBFBD>\u0010\u007F\u0011N<EFBFBD><EFBFBD><EFBFBD>:T3A<33>","四Ⅰ"
"👍👍👍👍👍👍","hello 日本"
TABLE
expect(tbl.to_s.lines).to all(have_maximum_width(80))
expect(tbl.to_s.lines).to all(have_maximum_display_width(80))
end
end
@ -124,6 +124,71 @@ describe Rex::Text::Table do
end
end
it 'should return a blank table as no search terms were matched' do
col_1_field = "A" * 5
col_2_field = "B" * 50
col_3_field = "C" * 15
options = {
'Header' => 'Header',
'SearchTerm' => 'jim|bob',
'Columns' => [
'Column 1',
'Column 2',
'Column 3'
]
}
tbl = Rex::Text::Table.new(options)
tbl << [
col_1_field,
col_2_field,
col_3_field
]
expect(tbl.to_s).to eql <<~TABLE
Header
======
Column 1 Column 2 Column 3
-------- -------- --------
TABLE
end
it 'should return the row as the row contains a match for the search term' do
col_1_field = "jim"
col_2_field = "B" * 50
col_3_field = "C" * 15
options = {
'Header' => 'Header',
'SearchTerm' => 'jim|bob',
'Columns' => [
'Column 1',
'Column 2',
'Column 3'
]
}
tbl = Rex::Text::Table.new(options)
tbl << [
col_1_field,
col_2_field,
col_3_field
]
expect(tbl.to_s).to eql <<~TABLE
Header
======
Column 1 Column 2 Column 3
-------- -------- --------
jim BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCC
TABLE
end
it 'should space columns correctly' do
col_1_field = "A" * 5
col_2_field = "B" * 50
@ -131,7 +196,6 @@ describe Rex::Text::Table do
options = {
'Header' => 'Header',
'SearchTerm' => ['jim', 'bob'],
'Columns' => [
'Column 1',
'Column 2',
@ -161,9 +225,9 @@ describe Rex::Text::Table do
col_1_field = "A" * 5
col_2_field = "B" * 50
col_3_field = "C" * 15
options = {
'Header' => 'Header',
'SearchTerm' => ['jim', 'bob'],
'Columns' => [
'Column 1',
'Column 2',
@ -195,18 +259,12 @@ describe Rex::Text::Table do
end
it 'should apply field stylers correctly and NOT increase column length' do
skip(
"Functionality not implemented. Currently if there are colors present in a cell, the colors will break " \
"when word wrapping occurs. This is a regression in functionality with a normal Rex Table."
)
col_1_field = "A" * 5
col_2_field = "B" * 50
col_3_field = "C" * 15
options = {
'Header' => 'Header',
'SearchTerm' => ['jim', 'bob'],
'Columns' => [
'Column 1',
'Column 2',
@ -233,7 +291,59 @@ describe Rex::Text::Table do
Column 1 Column 2 Column 3
-------- -------- --------
AAAAA IHAVEBEENSTYLEDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCC
AAAAA %bluBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB%clr CCCCCCCCCCCCCCC
TABLE
end
it 'should apply field stylers correctly and NOT increase column length when having a low width value' do
options = {
'Header' => 'Header',
'Width' => 3,
'Columns' => [
'Column 1',
'Column 2',
'Column 3'
],
'ColProps' => {
'Column 2' => {
'Stylers' => [styler]
}
}
}
tbl = Rex::Text::Table.new(options)
tbl << [
"A" * 5,
"ABC ABCD ABC" * 1,
"C" * 5
]
expect(tbl).to match_table <<~TABLE
Header
======
C C C
o o o
l l l
u u u
m m m
n n n
1 2 3
- - -
A %bluA%clr C
A %bluB%clr C
A %bluC%clr C
A %blu %clr C
A %bluA%clr C
%bluB%clr
%bluC%clr
%bluD%clr
%blu %clr
%bluA%clr
%bluB%clr
%bluC%clr
TABLE
end
@ -277,7 +387,7 @@ describe Rex::Text::Table do
------- --- ---- ------- --------- ----- ------- ---- --------
127.0.0.1 192.168.1.10 macOS Mojave (macOS 10.14.6) device
TABLE
expect(tbl.to_s.lines).to all(have_maximum_width(120))
expect(tbl.to_s.lines).to all(have_maximum_display_width(120))
end
it 'makes use of all available space' do
@ -323,7 +433,7 @@ describe Rex::Text::Table do
1 10 ve (macOS
10.14.6)
TABLE
expect(tbl.to_s.lines).to all(have_maximum_width(80))
expect(tbl.to_s.lines).to all(have_maximum_display_width(80))
end
context 'when word wrapping occurs' do
@ -354,7 +464,7 @@ describe Rex::Text::Table do
1 Lorem ipsum dolor sit amet, consecte Pellentesque ac tellus lobortis, vol
tur adipiscing elite utpat nibh sit amet
TABLE
expect(tbl.to_s.lines).to all(have_maximum_width(80))
expect(tbl.to_s.lines).to all(have_maximum_display_width(80))
end
it "Evenly allows columns to have specified widths" do
@ -391,7 +501,7 @@ describe Rex::Text::Table do
olor sit amet, consectetur adipisci
ng elite
TABLE
expect(tbl.to_s.lines).to all(have_maximum_width(80))
expect(tbl.to_s.lines).to all(have_maximum_display_width(80))
end
it "handles multiple columns and rows" do
@ -445,7 +555,7 @@ describe Rex::Text::Table do
SESSION yes The session to run this module o
n.
TABLE
expect(tbl.to_s.lines).to all(have_maximum_width(80))
expect(tbl.to_s.lines).to all(have_maximum_display_width(80))
end
it "handles strings in different encodings" do
@ -513,7 +623,7 @@ describe Rex::Text::Table do
<EFBFBD>_<EFBFBD><EFBFBD>\u0010\u007F\u0011N<EFBFBD><EFBFBD><EFBFBD>:T3A<EFBFBD>
👍👍👍👍👍👍 hello
TABLE
expect(tbl.to_s.lines).to all(have_maximum_width(80))
expect(tbl.to_s.lines).to all(have_maximum_display_width(80))
end
it "Wraps columns as well as values" do
@ -548,18 +658,46 @@ describe Rex::Text::Table do
Foo Bar
Foo Bar
TABLE
expect(tbl.to_s.lines).to all(have_maximum_width(80))
expect(tbl.to_s.lines).to all(have_maximum_display_width(80))
end
it "safely wordwraps cells that have colors present" do
skip(
"Functionality not implemented. Currently if there are colors present in a cell, the colors will break " \
"when word wrapping occurs"
)
it "safely wordwraps cells when an % symbol that is not associated with a color/format codes is present" do
options = {
'Header' => 'Header',
'Indent' => 2,
'Indent' => 0,
'Width' => 80,
'Columns' => [
'Blue Column',
'Red Column'
]
}
tbl = Rex::Text::Table.new(options)
tbl << [
"%#{'A' * 49}%#{'A' * 49}",
"%#{'A' * 49}%#{'A' * 49}",
]
expect(tbl).to match_table <<~TABLE
Header
======
Blue Column Red Column
----------- ----------
%AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA %AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAA%AAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAA%AAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA
TABLE
expect(tbl.to_s.lines).to all(have_maximum_display_width(80))
end
it "safely wordwraps cells that have a single color/format across multiple lines" do
options = {
'Header' => 'Header',
'Indent' => 0,
'Width' => 80,
'Columns' => [
'Blue Column',
@ -572,17 +710,243 @@ describe Rex::Text::Table do
"%blu#{'A' * 100}%clr",
"%red#{'A' * 100}%clr",
]
expect(tbl).to match_table <<~TABLE
Header
======
Blue Column Red Column
----------- ----------
%bluAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr %redAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr
%bluAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr %redAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr
%bluAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr %redAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr
Blue Column Red Column
----------- ----------
%bluAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr %redAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr
%bluAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr %redAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr
%bluAAAAAAAAAAAAAAAAAAAAAAAA%clr %redAAAAAAAAAAAAAAAAAAAAAAAA%clr
TABLE
expect(tbl.to_s.lines).to all(have_maximum_width(80))
expect(tbl.to_s.lines).to all(have_maximum_display_width(80))
end
it "safely wordwraps cells that have a single color/format across a single line" do
options = {
'Header' => 'Header',
'Indent' => 0,
'Width' => 80,
'Columns' => [
'Blue Column',
'Red Column'
]
}
tbl = Rex::Text::Table.new(options)
tbl << [
"#{'A' * 40}%bluA%clr#{'A' * 59}",
"#{'A' * 40}%redA%clr#{'A' * 59}",
]
expect(tbl).to match_table <<~TABLE
Header
======
Blue Column Red Column
----------- ----------
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AA%bluA%clrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AA%redA%clrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAA
TABLE
expect(tbl.to_s.lines).to all(have_maximum_display_width(80))
end
it "safely wordwraps cells that have a multiple color/format across a multiple line" do
options = {
'Header' => 'Header',
'Indent' => 0,
'Width' => 80,
'Columns' => [
'Blue Column',
'Red Column'
]
}
tbl = Rex::Text::Table.new(options)
tbl << [
"%blu%undA%magA%yel#{'A' * 109}%clr",
"%red%undA%magA%yel#{'A' * 109}%clr",
]
expect(tbl).to match_table <<~TABLE
Header
======
Blue Column Red Column
----------- ----------
%blu%undA%magA%yelAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr %red%undA%magA%yelAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr
%yel%undAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr %yel%undAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr
%yel%undAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr %yel%undAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr
TABLE
expect(tbl.to_s.lines).to all(have_maximum_display_width(80))
end
it "safely wordwraps cells when there is no color/formatting codes present" do
options = {
'Header' => 'Header',
'Indent' => 0,
'Width' => 80,
'Columns' => [
'Blue Column',
'Red Column'
]
}
tbl = Rex::Text::Table.new(options)
tbl << [
"#{'A' * 40}",
"#{'A' * 40}",
]
expect(tbl).to match_table <<~TABLE
Header
======
Blue Column Red Column
----------- ----------
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AA AA
TABLE
expect(tbl.to_s.lines).to all(have_maximum_display_width(80))
end
it "verify that formatting aligns correctly" do
options = {
'Header' => 'Header',
'Indent' => 0,
'Width' => 80,
'Columns' => [
'Blue Column',
'Red Column'
]
}
tbl = Rex::Text::Table.new(options)
tbl << [
"%blu#{'A' * 40}%clr",
"%red#{'A' * 40}%clr",
]
expect(tbl).to match_table <<~TABLE
Header
======
Blue Column Red Column
----------- ----------
%bluAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr %redAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr
%bluAA%clr %redAA%clr
TABLE
expect(tbl.to_s.lines).to all(have_maximum_display_width(80))
end
it "verify that formatting aligns correctly" do
options = {
'Header' => 'Header',
'Indent' => 0,
'Width' => 80,
'Columns' => [
'Blue Column',
'Red Column'
]
}
tbl = Rex::Text::Table.new(options)
tbl << [
"%blu#{'A' * 40}%clr",
"%red#{'A' * 40}%clr",
]
expect(tbl).to match_table <<~TABLE
Header
======
Blue Column Red Column
----------- ----------
%bluAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr %redAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr
%bluAA%clr %redAA%clr
TABLE
expect(tbl.to_s.lines).to all(have_maximum_display_width(80))
end
it 'supports dark color codes' do
options = {
'Header' => 'Header',
'Indent' => 0,
'Width' => 80,
'Columns' => [
'Blue Column',
'Red Column'
]
}
tbl = Rex::Text::Table.new(options)
tbl << [
"%dyel#{'A' * 40}%clr",
"%dcya#{'A' * 40}%clr",
]
expect(tbl).to match_table <<~TABLE
Header
======
Blue Column Red Column
----------- ----------
%dyelAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr %dcyaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr
%dyelAA%clr %dcyaAA%clr
TABLE
expect(tbl.to_s.lines).to all(have_maximum_display_width(80))
end
it "verify that formatting aligns correctly" do
options = {
'Indent' => 0,
'Width' => 1,
'Columns' => [
'Blue Column',
'Red Column'
]
}
tbl = Rex::Text::Table.new(options)
tbl << %w[%bgyel%blu%bld%undAAA%clr %bgyel%blu%bld%undAAA%clr]
expect(tbl).to match_table <<~TABLE
B R
l e
u d
e
C
C o
o l
l u
u m
m n
n
- -
%bgyel%blu%bld%undA%clr %bgyel%blu%bld%undA%clr
%bgyel%blu%bld%undA%clr %bgyel%blu%bld%undA%clr
%bgyel%blu%bld%undA%clr %bgyel%blu%bld%undA%clr
TABLE
expect(tbl.to_s.lines).to all(have_maximum_display_width(7))
end
it "ensures specific columns can disable wordwrapping" do
@ -618,7 +982,7 @@ describe Rex::Text::Table do
expect(tbl).to match_table <<~TABLE
...
TABLE
expect(tbl.to_s.lines).to all(have_maximum_width(80))
expect(tbl.to_s.lines).to all(have_maximum_display_width(80))
end
it "continues to work when it's not possible to fit all of the columns into the available width" do
@ -659,25 +1023,25 @@ describe Rex::Text::Table do
#
# For simplicity the first option is chosen, as in either scenario the user will have to resize their terminal.
expect(tbl).to match_table <<~TABLE
Header
======
Header
======
N V R D
a a e e
m l q s
e u u c
e i r
r i
e p
d t
i
o
n
- - - -
A A Y A
B B e B
C C s C
D D D
N V R D
a a e e
m l q s
e u u c
e i r
r i
e p
d t
i
o
n
- - - -
A A Y A
B B e B
C C s C
D D D
TABLE
end
end

View File

@ -95,6 +95,49 @@ RSpec.describe Rex::Text do
end
end
describe ".display_width" do
it "verifies that display width calculates correct display width when no format codes are present" do
str = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
expect(described_class.display_width(str)).to eq(100)
end
it "verifies that display width calculates correct display width when format codes are present" do
str = "%bluAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr"
expect(described_class.display_width(str)).to eq(100)
end
it "verifies that display width calculates correct display width when format codes are chained together" do
str = "%bgyel%blu%bld%grn%undAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%clr"
expect(described_class.display_width(str)).to eq(100)
end
it "verifies that display width calculates correct display width when format codes are chained together with multiple seperate formatting instances" do
str = "%bgyel%blu%bld%undhello%clr %bgyel%blu%bld%undworld%clr"
expect(described_class.display_width(str)).to eq(11)
end
it "verifies that display width calculates correct display width when format codes are chained together with multiple seperate formatting instances" do
str = "%dgrnhello world%clr"
expect(described_class.display_width(str)).to eq(11)
end
it "verifies that display width calculates correct display width when emojis with modifiers are passed" do
# https://stackoverflow.com/questions/42778709/the-longest-character-in-utf-8
str = "🤦🏻‍♂️".force_encoding('UTF-8')
# For future travelers, this could technically be a width of 1, but is dependant on how the console renders it
expect(described_class.display_width(str)).to eq(5)
end
it "verifies that display width calculates correct display width when emojis with modifiers are passed" do
# https://stackoverflow.com/questions/42778709/the-longest-character-in-utf-8
str = "🤦🏻‍♂️".force_encoding('ASCII-8BIT')
# For future travelers, this could technically be a width of 1, but is dependant on how the console renders it
expect(described_class.display_width(str)).to eq(17)
end
end
context ".gzip" do
it "should return a properly formatted gzip file" do
str = described_class.gzip("hi mom")

View File

@ -0,0 +1,9 @@
RSpec::Matchers.define :have_maximum_display_width do |expected|
match do |actual|
Rex::Text.display_width(actual) <= expected
end
failure_message do |actual|
"expected '#{actual}' to have a length than or equal to #{expected}, instead got #{actual.length}"
end
end