rex-text 0.2.23 → 0.2.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/Gemfile +4 -0
- data/lib/rex/text.rb +1 -1
- data/lib/rex/text/color.rb +1 -1
- data/lib/rex/text/lang.rb +8 -0
- data/lib/rex/text/table.rb +67 -6
- data/lib/rex/text/version.rb +1 -1
- data/lib/rex/text/wrapped_table.rb +538 -0
- metadata +80 -74
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ed0a3f361cc220f42bd061edb39b914add50ad3a52f196f1d02bce1e3e56254a
|
4
|
+
data.tar.gz: b9317aea70870fc37394cf4d60c53b12b314e0c0f5da7ed949e0af658f54a2de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 984bc5972b4ac1abe5dbd98dbe651bcdc785724b61062062ee1c0aa5811cbd78d54c45bef34f5103e191b878b42fd341ebe37ad0bd482b7e99959b558f5aba4f
|
7
|
+
data.tar.gz: f21d06b23911998f4d178c081d420e18e1692e41855cf67d44c0d15509501efdc6745c592fbd3d5681171921c71a67c3e686004bf48e6c7bafdd4aa8747f2ad8
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/Gemfile
CHANGED
data/lib/rex/text.rb
CHANGED
data/lib/rex/text/color.rb
CHANGED
@@ -52,7 +52,7 @@ module Color
|
|
52
52
|
def substitute_colors(msg, in_prompt = nil)
|
53
53
|
str = msg.dup
|
54
54
|
pre_color = post_color = ''
|
55
|
-
if (in_prompt)
|
55
|
+
if (in_prompt && supports_color?)
|
56
56
|
pre_color = "\x01" # RL_PROMPT_START_IGNORE
|
57
57
|
post_color = "\x02" # RL_PROMPT_END_IGNORE
|
58
58
|
end
|
data/lib/rex/text/lang.rb
CHANGED
data/lib/rex/text/table.rb
CHANGED
@@ -12,6 +12,34 @@ module Text
|
|
12
12
|
###
|
13
13
|
class Table
|
14
14
|
|
15
|
+
# Temporary forking logic for using the prototype `WrappedTable` implementation.
|
16
|
+
#
|
17
|
+
# This method replaces the default `Table.new` with the ability to call the `WrappedTable` class instead,
|
18
|
+
# to allow users to safely toggle between wrapped/unwrapped tables at a global level without changing
|
19
|
+
# their existing codebases. This approach will reduce the risk of enabling wrapped table behavior by default.
|
20
|
+
#
|
21
|
+
# To enforce all tables to be wrapped to the terminal's current width, call `Table.wrap_tables!`
|
22
|
+
# before invoking `Table.new` as normal.
|
23
|
+
def self.new(*args, &block)
|
24
|
+
if wrap_tables?
|
25
|
+
table_options = args[0]
|
26
|
+
return ::Rex::Text::WrappedTable.new(table_options)
|
27
|
+
end
|
28
|
+
return super(*args, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.wrap_tables?
|
32
|
+
@@wrapped_tables_enabled ||= false
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.wrap_tables!
|
36
|
+
@@wrapped_tables_enabled = true
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.unwrap_tables!
|
40
|
+
@@wrapped_tables_enabled = false
|
41
|
+
end
|
42
|
+
|
15
43
|
#
|
16
44
|
# Initializes a text table instance using the supplied properties. The
|
17
45
|
# Table class supports the following hash attributes:
|
@@ -57,6 +85,10 @@ class Table
|
|
57
85
|
#
|
58
86
|
# The column to sort the table on, -1 disables sorting.
|
59
87
|
#
|
88
|
+
# ColProps
|
89
|
+
#
|
90
|
+
# A hash specifying column MaxWidth, Stylers, and Formatters.
|
91
|
+
#
|
60
92
|
def initialize(opts = {})
|
61
93
|
self.header = opts['Header']
|
62
94
|
self.headeri = opts['HeaderIndent'] || 0
|
@@ -79,6 +111,8 @@ class Table
|
|
79
111
|
self.columns.length.times { |idx|
|
80
112
|
self.colprops[idx] = {}
|
81
113
|
self.colprops[idx]['MaxWidth'] = self.columns[idx].length
|
114
|
+
self.colprops[idx]['Stylers'] = []
|
115
|
+
self.colprops[idx]['Formatters'] = []
|
82
116
|
}
|
83
117
|
|
84
118
|
# ensure all our internal state gets updated with the given rows by
|
@@ -172,16 +206,19 @@ class Table
|
|
172
206
|
if fields.length != self.columns.length
|
173
207
|
raise RuntimeError, 'Invalid number of columns!'
|
174
208
|
end
|
175
|
-
fields.
|
209
|
+
formatted_fields = fields.map.with_index { |field, idx|
|
176
210
|
# Remove whitespace and ensure String format
|
177
|
-
field = field.to_s.strip
|
211
|
+
field = format_table_field(field.to_s.strip, idx)
|
212
|
+
|
178
213
|
if (colprops[idx]['MaxWidth'] < field.to_s.length)
|
179
214
|
old = colprops[idx]['MaxWidth']
|
180
215
|
colprops[idx]['MaxWidth'] = field.to_s.length
|
181
216
|
end
|
217
|
+
|
218
|
+
field
|
182
219
|
}
|
183
220
|
|
184
|
-
rows <<
|
221
|
+
rows << formatted_fields
|
185
222
|
end
|
186
223
|
|
187
224
|
def ip_cmp(a, b)
|
@@ -389,9 +426,9 @@ protected
|
|
389
426
|
# Limit wide cells
|
390
427
|
if colprops[idx]['MaxChar']
|
391
428
|
last_cell = cell.to_s[0..colprops[idx]['MaxChar'].to_i]
|
392
|
-
line << last_cell
|
429
|
+
line << style_table_field(last_cell, idx)
|
393
430
|
else
|
394
|
-
line << cell.to_s
|
431
|
+
line << style_table_field(cell.to_s, idx)
|
395
432
|
last_cell = cell
|
396
433
|
end
|
397
434
|
last_idx = idx
|
@@ -414,7 +451,12 @@ protected
|
|
414
451
|
__method__.to_s << ': String with unsupported encoding caught!'
|
415
452
|
end
|
416
453
|
utf8_buf = buf.dup.force_encoding("UTF-8")
|
417
|
-
|
454
|
+
if !utf8_buf.valid_encoding?
|
455
|
+
hans_size = 0
|
456
|
+
else
|
457
|
+
hans_size = utf8_buf.size - utf8_buf.gsub(/\p{Han}+/u, '').size
|
458
|
+
end
|
459
|
+
remainder = max - utf8_buf.length - hans_size
|
418
460
|
remainder = 0 if remainder < 0
|
419
461
|
val = chr * remainder
|
420
462
|
|
@@ -425,6 +467,25 @@ protected
|
|
425
467
|
return val
|
426
468
|
end
|
427
469
|
|
470
|
+
def format_table_field(str, idx)
|
471
|
+
str_cp = str.clone
|
472
|
+
|
473
|
+
colprops[idx]['Formatters'].each do |f|
|
474
|
+
str_cp = f.format(str_cp)
|
475
|
+
end
|
476
|
+
|
477
|
+
str_cp
|
478
|
+
end
|
479
|
+
|
480
|
+
def style_table_field(str, idx)
|
481
|
+
str_cp = str.clone
|
482
|
+
|
483
|
+
colprops[idx]['Stylers'].each do |s|
|
484
|
+
str_cp = s.style(str_cp)
|
485
|
+
end
|
486
|
+
|
487
|
+
str_cp
|
488
|
+
end
|
428
489
|
|
429
490
|
end
|
430
491
|
|
data/lib/rex/text/version.rb
CHANGED
@@ -0,0 +1,538 @@
|
|
1
|
+
# -*- coding: binary -*-
|
2
|
+
require 'ipaddr'
|
3
|
+
require 'io/console'
|
4
|
+
|
5
|
+
module Rex
|
6
|
+
module Text
|
7
|
+
|
8
|
+
###
|
9
|
+
#
|
10
|
+
# Prints text in a tablized format. Pretty lame at the moment, but
|
11
|
+
# whatever.
|
12
|
+
#
|
13
|
+
###
|
14
|
+
# private_constant
|
15
|
+
class WrappedTable
|
16
|
+
|
17
|
+
#
|
18
|
+
# Initializes a text table instance using the supplied properties. The
|
19
|
+
# Table class supports the following hash attributes:
|
20
|
+
#
|
21
|
+
# Header
|
22
|
+
#
|
23
|
+
# The string to display as a heading above the table. If none is
|
24
|
+
# specified, no header will be displayed.
|
25
|
+
#
|
26
|
+
# HeaderIndent
|
27
|
+
#
|
28
|
+
# The amount of space to indent the header. The default is zero.
|
29
|
+
#
|
30
|
+
# Columns
|
31
|
+
#
|
32
|
+
# The array of columns that will exist within the table.
|
33
|
+
#
|
34
|
+
# Rows
|
35
|
+
#
|
36
|
+
# The array of rows that will exist.
|
37
|
+
#
|
38
|
+
# Width
|
39
|
+
#
|
40
|
+
# The maximum width of the table in characters.
|
41
|
+
#
|
42
|
+
# Indent
|
43
|
+
#
|
44
|
+
# The number of characters to indent the table.
|
45
|
+
#
|
46
|
+
# CellPad
|
47
|
+
#
|
48
|
+
# The number of characters to put between each horizontal cell.
|
49
|
+
#
|
50
|
+
# Prefix
|
51
|
+
#
|
52
|
+
# The text to prefix before the table.
|
53
|
+
#
|
54
|
+
# Postfix
|
55
|
+
#
|
56
|
+
# The text to affix to the end of the table.
|
57
|
+
#
|
58
|
+
# Sortindex
|
59
|
+
#
|
60
|
+
# The column to sort the table on, -1 disables sorting.
|
61
|
+
#
|
62
|
+
# ColProps
|
63
|
+
#
|
64
|
+
# A hash specifying column MaxWidth, Stylers, and Formatters.
|
65
|
+
#
|
66
|
+
def initialize(opts = {})
|
67
|
+
self.header = opts['Header']
|
68
|
+
self.headeri = opts['HeaderIndent'] || 0
|
69
|
+
self.columns = opts['Columns'] || []
|
70
|
+
# updated below if we got a "Rows" option
|
71
|
+
self.rows = []
|
72
|
+
|
73
|
+
# TODO: Discuss a cleaner way to handle this information
|
74
|
+
self.width = opts['Width'] || ::IO.console.winsize[1]
|
75
|
+
self.indent = opts['Indent'] || 0
|
76
|
+
self.cellpad = opts['CellPad'] || 2
|
77
|
+
self.prefix = opts['Prefix'] || ''
|
78
|
+
self.postfix = opts['Postfix'] || ''
|
79
|
+
self.colprops = []
|
80
|
+
self.scterm = /#{opts['SearchTerm']}/mi if opts['SearchTerm']
|
81
|
+
|
82
|
+
self.sort_index = opts['SortIndex'] || 0
|
83
|
+
self.sort_order = opts['SortOrder'] || :forward
|
84
|
+
|
85
|
+
# Default column properties
|
86
|
+
self.columns.length.times { |idx|
|
87
|
+
self.colprops[idx] = {}
|
88
|
+
self.colprops[idx]['MaxWidth'] = self.columns[idx].length
|
89
|
+
self.colprops[idx]['WordWrap'] = true
|
90
|
+
self.colprops[idx]['Stylers'] = []
|
91
|
+
self.colprops[idx]['Formatters'] = []
|
92
|
+
}
|
93
|
+
|
94
|
+
# ensure all our internal state gets updated with the given rows by
|
95
|
+
# using add_row instead of just adding them to self.rows. See #3825.
|
96
|
+
opts['Rows'].each { |row| add_row(row) } if opts['Rows']
|
97
|
+
|
98
|
+
# Merge in options
|
99
|
+
if (opts['ColProps'])
|
100
|
+
opts['ColProps'].each_key { |col|
|
101
|
+
idx = self.columns.index(col)
|
102
|
+
|
103
|
+
if (idx)
|
104
|
+
self.colprops[idx].merge!(opts['ColProps'][col])
|
105
|
+
end
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
#
|
112
|
+
# Converts table contents to a string.
|
113
|
+
#
|
114
|
+
def to_s
|
115
|
+
str = prefix.dup
|
116
|
+
str << header_to_s || ''
|
117
|
+
str << columns_to_s || ''
|
118
|
+
str << hr_to_s || ''
|
119
|
+
|
120
|
+
sort_rows
|
121
|
+
rows.each { |row|
|
122
|
+
if (is_hr(row))
|
123
|
+
str << hr_to_s
|
124
|
+
else
|
125
|
+
str << row_to_s(row) if row_visible(row)
|
126
|
+
end
|
127
|
+
}
|
128
|
+
|
129
|
+
str << postfix
|
130
|
+
|
131
|
+
return str
|
132
|
+
end
|
133
|
+
|
134
|
+
#
|
135
|
+
# Converts table contents to a csv
|
136
|
+
#
|
137
|
+
def to_csv
|
138
|
+
str = ''
|
139
|
+
str << ( columns.join(",") + "\n" )
|
140
|
+
rows.each { |row|
|
141
|
+
next if is_hr(row) || !row_visible(row)
|
142
|
+
str << ( row.map{|x|
|
143
|
+
x = x.to_s
|
144
|
+
x.gsub(/[\r\n]/, ' ').gsub(/\s+/, ' ').gsub('"', '""')
|
145
|
+
}.map{|x| "\"#{x}\"" }.join(",") + "\n" )
|
146
|
+
}
|
147
|
+
str
|
148
|
+
end
|
149
|
+
|
150
|
+
#
|
151
|
+
#
|
152
|
+
# Returns the header string.
|
153
|
+
#
|
154
|
+
def header_to_s # :nodoc:
|
155
|
+
if (header)
|
156
|
+
pad = " " * headeri
|
157
|
+
|
158
|
+
return pad + header + "\n" + pad + "=" * header.length + "\n\n"
|
159
|
+
end
|
160
|
+
|
161
|
+
return ''
|
162
|
+
end
|
163
|
+
|
164
|
+
#
|
165
|
+
# Prints the contents of the table.
|
166
|
+
#
|
167
|
+
def print
|
168
|
+
puts to_s
|
169
|
+
end
|
170
|
+
|
171
|
+
#
|
172
|
+
# Adds a row using the supplied fields.
|
173
|
+
#
|
174
|
+
def <<(fields)
|
175
|
+
add_row(fields)
|
176
|
+
end
|
177
|
+
|
178
|
+
#
|
179
|
+
# Adds a row with the supplied fields.
|
180
|
+
#
|
181
|
+
def add_row(fields = [])
|
182
|
+
if fields.length != self.columns.length
|
183
|
+
raise RuntimeError, 'Invalid number of columns!'
|
184
|
+
end
|
185
|
+
formatted_fields = fields.map.with_index { |field, idx|
|
186
|
+
# Remove whitespace and ensure String format
|
187
|
+
field = format_table_field(field.to_s.strip, idx)
|
188
|
+
|
189
|
+
if (colprops[idx]['MaxWidth'] < display_width(field.to_s))
|
190
|
+
old = colprops[idx]['MaxWidth']
|
191
|
+
colprops[idx]['MaxWidth'] = display_width(field.to_s)
|
192
|
+
end
|
193
|
+
|
194
|
+
field
|
195
|
+
}
|
196
|
+
|
197
|
+
rows << formatted_fields
|
198
|
+
end
|
199
|
+
|
200
|
+
def ip_cmp(a, b)
|
201
|
+
begin
|
202
|
+
a = IPAddr.new(a.to_s)
|
203
|
+
b = IPAddr.new(b.to_s)
|
204
|
+
return 1 if a.ipv6? && b.ipv4?
|
205
|
+
return -1 if a.ipv4? && b.ipv6?
|
206
|
+
a <=> b
|
207
|
+
rescue IPAddr::Error
|
208
|
+
nil
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
#
|
213
|
+
# Sorts the rows based on the supplied index of sub-arrays
|
214
|
+
# If the supplied index is an IPv4 address, handle it differently, but
|
215
|
+
# avoid actually resolving domain names.
|
216
|
+
#
|
217
|
+
def sort_rows(index = sort_index, order = sort_order)
|
218
|
+
return if index == -1
|
219
|
+
return unless rows
|
220
|
+
rows.sort! do |a,b|
|
221
|
+
if a[index].nil?
|
222
|
+
cmp = -1
|
223
|
+
elsif b[index].nil?
|
224
|
+
cmp = 1
|
225
|
+
elsif a[index] =~ /^[0-9]+$/ and b[index] =~ /^[0-9]+$/
|
226
|
+
cmp = a[index].to_i <=> b[index].to_i
|
227
|
+
elsif (cmp = ip_cmp(a[index], b[index])) != nil
|
228
|
+
else
|
229
|
+
cmp = a[index] <=> b[index] # assumes otherwise comparable.
|
230
|
+
end
|
231
|
+
cmp ||= 0
|
232
|
+
order == :forward ? cmp : -cmp
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
#
|
237
|
+
# Adds a horizontal line.
|
238
|
+
#
|
239
|
+
def add_hr
|
240
|
+
rows << '__hr__'
|
241
|
+
end
|
242
|
+
|
243
|
+
#
|
244
|
+
# Returns new sub-table with headers and rows maching column names submitted
|
245
|
+
#
|
246
|
+
#
|
247
|
+
# Flips table 90 degrees left
|
248
|
+
#
|
249
|
+
def drop_left
|
250
|
+
tbl = self.class.new(
|
251
|
+
'Columns' => Array.new(self.rows.count+1,' '),
|
252
|
+
'Header' => self.header,
|
253
|
+
'Indent' => self.indent)
|
254
|
+
(self.columns.count+1).times do |ti|
|
255
|
+
row = self.rows.map {|r| r[ti]}.unshift(self.columns[ti]).flatten
|
256
|
+
# insert our col|row break. kind of hackish
|
257
|
+
row[1] = "| #{row[1]}" unless row.all? {|e| e.nil? || e.empty?}
|
258
|
+
tbl << row
|
259
|
+
end
|
260
|
+
return tbl
|
261
|
+
end
|
262
|
+
|
263
|
+
def valid_ip?(value)
|
264
|
+
begin
|
265
|
+
IPAddr.new value
|
266
|
+
true
|
267
|
+
rescue IPAddr::Error
|
268
|
+
false
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
#
|
273
|
+
# Build table from CSV dump
|
274
|
+
#
|
275
|
+
def self.new_from_csv(csv)
|
276
|
+
# Read in or keep data, get CSV or die
|
277
|
+
if csv.is_a?(String)
|
278
|
+
csv = File.file?(csv) ? CSV.read(csv) : CSV.parse(csv)
|
279
|
+
end
|
280
|
+
# Adjust for skew
|
281
|
+
if csv.first == ["Keys", "Values"]
|
282
|
+
csv.shift # drop marker
|
283
|
+
cols = []
|
284
|
+
rows = []
|
285
|
+
csv.each do |row|
|
286
|
+
cols << row.shift
|
287
|
+
rows << row
|
288
|
+
end
|
289
|
+
tbl = self.new('Columns' => cols)
|
290
|
+
rows.in_groups_of(cols.count) {|r| tbl << r.flatten}
|
291
|
+
else
|
292
|
+
tbl = self.new('Columns' => csv.shift)
|
293
|
+
while !csv.empty? do
|
294
|
+
tbl << csv.shift
|
295
|
+
end
|
296
|
+
end
|
297
|
+
return tbl
|
298
|
+
end
|
299
|
+
|
300
|
+
def [](*col_names)
|
301
|
+
tbl = self.class.new('Indent' => self.indent,
|
302
|
+
'Header' => self.header,
|
303
|
+
'Columns' => col_names)
|
304
|
+
indexes = []
|
305
|
+
|
306
|
+
col_names.each do |col_name|
|
307
|
+
index = self.columns.index(col_name)
|
308
|
+
raise RuntimeError, "Invalid column name #{col_name}" if index.nil?
|
309
|
+
indexes << index
|
310
|
+
end
|
311
|
+
|
312
|
+
self.rows.each do |old_row|
|
313
|
+
new_row = []
|
314
|
+
indexes.map {|i| new_row << old_row[i]}
|
315
|
+
tbl << new_row
|
316
|
+
end
|
317
|
+
|
318
|
+
return tbl
|
319
|
+
end
|
320
|
+
|
321
|
+
|
322
|
+
alias p print
|
323
|
+
|
324
|
+
attr_accessor :header, :headeri # :nodoc:
|
325
|
+
attr_accessor :columns, :rows, :colprops # :nodoc:
|
326
|
+
attr_accessor :width, :indent, :cellpad # :nodoc:
|
327
|
+
attr_accessor :prefix, :postfix # :nodoc:
|
328
|
+
attr_accessor :sort_index, :sort_order, :scterm # :nodoc:
|
329
|
+
|
330
|
+
protected
|
331
|
+
|
332
|
+
#
|
333
|
+
# Returns if a row should be visible or not
|
334
|
+
#
|
335
|
+
def row_visible(row)
|
336
|
+
return true if self.scterm.nil?
|
337
|
+
row_to_s(row).match(self.scterm)
|
338
|
+
end
|
339
|
+
|
340
|
+
#
|
341
|
+
# Defaults cell widths and alignments.
|
342
|
+
#
|
343
|
+
def defaults # :nodoc:
|
344
|
+
self.columns.length.times { |idx|
|
345
|
+
}
|
346
|
+
end
|
347
|
+
|
348
|
+
#
|
349
|
+
# Checks to see if the row is an hr.
|
350
|
+
#
|
351
|
+
def is_hr(row) # :nodoc:
|
352
|
+
return ((row.kind_of?(String)) && (row == '__hr__'))
|
353
|
+
end
|
354
|
+
|
355
|
+
#
|
356
|
+
# Converts the columns to a string.
|
357
|
+
#
|
358
|
+
def columns_to_s # :nodoc:
|
359
|
+
optimal_widths = calculate_optimal_widths
|
360
|
+
values_as_chunks = chunk_values(columns, optimal_widths)
|
361
|
+
result = chunks_to_s(values_as_chunks, optimal_widths)
|
362
|
+
|
363
|
+
barline = ""
|
364
|
+
columns.each.with_index do |_column, idx|
|
365
|
+
bar_width = display_width(values_as_chunks[idx].first)
|
366
|
+
column_width = optimal_widths[idx]
|
367
|
+
|
368
|
+
if idx == 0
|
369
|
+
barline << ' ' * indent
|
370
|
+
end
|
371
|
+
|
372
|
+
barline << '-' * bar_width
|
373
|
+
is_last_column = (idx + 1) == columns.length
|
374
|
+
unless is_last_column
|
375
|
+
barline << ' ' * (column_width - bar_width)
|
376
|
+
barline << ' ' * cellpad
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
result + barline
|
381
|
+
end
|
382
|
+
|
383
|
+
#
|
384
|
+
# Converts an hr to a string.
|
385
|
+
#
|
386
|
+
def hr_to_s # :nodoc:
|
387
|
+
return "\n"
|
388
|
+
end
|
389
|
+
|
390
|
+
#
|
391
|
+
# Converts a row to a string.
|
392
|
+
#
|
393
|
+
def row_to_s(row) # :nodoc:
|
394
|
+
optimal_widths = calculate_optimal_widths
|
395
|
+
values_as_chunks = chunk_values(row, optimal_widths)
|
396
|
+
chunks_to_s(values_as_chunks, optimal_widths)
|
397
|
+
end
|
398
|
+
|
399
|
+
#
|
400
|
+
# Placeholder function that aims to calculate the display width of the given string.
|
401
|
+
# In the future this will be aware of East Asian characters having different display
|
402
|
+
# widths. For now it simply returns the string's length.
|
403
|
+
#
|
404
|
+
def display_width(str)
|
405
|
+
str.length
|
406
|
+
end
|
407
|
+
|
408
|
+
def chunk_values(values, optimal_widths)
|
409
|
+
# First split long strings into an array of chunks, where each chunk size is the calculated column width
|
410
|
+
values_as_chunks = values.each_with_index.map do |value, idx|
|
411
|
+
column_width = optimal_widths[idx]
|
412
|
+
value
|
413
|
+
.split('')
|
414
|
+
.each_slice(column_width)
|
415
|
+
.map(&:join)
|
416
|
+
end
|
417
|
+
|
418
|
+
values_as_chunks
|
419
|
+
end
|
420
|
+
|
421
|
+
def chunks_to_s(values_as_chunks, optimal_widths)
|
422
|
+
result = ''
|
423
|
+
|
424
|
+
interleave(values_as_chunks).each do |row_chunks|
|
425
|
+
line = ""
|
426
|
+
row_chunks.each_with_index do |chunk, idx|
|
427
|
+
column_width = optimal_widths[idx]
|
428
|
+
|
429
|
+
if idx == 0
|
430
|
+
line << ' ' * indent
|
431
|
+
end
|
432
|
+
|
433
|
+
line << chunk.to_s.ljust(column_width)
|
434
|
+
line << ' ' * cellpad
|
435
|
+
end
|
436
|
+
|
437
|
+
result << line.rstrip << "\n"
|
438
|
+
end
|
439
|
+
|
440
|
+
result
|
441
|
+
end
|
442
|
+
|
443
|
+
def interleave(arrays)
|
444
|
+
max_length = arrays.map(&:size).max
|
445
|
+
padding = [nil] * max_length
|
446
|
+
with_left_extra_column = padding.zip(*arrays)
|
447
|
+
without_extra_column = with_left_extra_column.map { |columns| columns.drop(1) }
|
448
|
+
|
449
|
+
without_extra_column
|
450
|
+
end
|
451
|
+
|
452
|
+
def calculate_optimal_widths
|
453
|
+
# Calculate the minimum width each column can be. This is dictated by the user.
|
454
|
+
user_influenced_column_widths = colprops.map do |colprop|
|
455
|
+
if colprop['WordWrap'] == false
|
456
|
+
colprop['MaxWidth']
|
457
|
+
raise 'Not implemented'
|
458
|
+
else
|
459
|
+
nil
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
required_padding = indent + (colprops.length) * cellpad
|
464
|
+
available_space = self.width - user_influenced_column_widths.sum(&:to_i) - required_padding
|
465
|
+
remaining_column_calculations = user_influenced_column_widths.select(&:nil?).count
|
466
|
+
|
467
|
+
# Calculate the initial widths, which will need an additional refinement to reallocate surplus space
|
468
|
+
naive_optimal_width_calculations = colprops.map.with_index do |colprop, index|
|
469
|
+
shared_column_width = available_space / [remaining_column_calculations, 1].max
|
470
|
+
remaining_column_calculations -= 1
|
471
|
+
|
472
|
+
if user_influenced_column_widths[index]
|
473
|
+
{ width: user_influenced_column_widths[index], wrapped: false }
|
474
|
+
elsif colprop['MaxWidth'] < shared_column_width
|
475
|
+
available_space -= colprop['MaxWidth']
|
476
|
+
{ width: colprop['MaxWidth'], wrapped: false }
|
477
|
+
else
|
478
|
+
available_space -= shared_column_width
|
479
|
+
{ width: shared_column_width, wrapped: true }
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
# Naively redistribute any surplus space to columns that were wrapped, and try to fit the cell on one line still
|
484
|
+
current_width = naive_optimal_width_calculations.sum { |width| width[:width] }
|
485
|
+
surplus_width = self.width - current_width - required_padding
|
486
|
+
# revisit all columns that were wrapped and add add additional characters
|
487
|
+
revisiting_column_counts = naive_optimal_width_calculations.count { |width| width[:wrapped] }
|
488
|
+
optimal_widths = naive_optimal_width_calculations.map.with_index do |naive_width, index|
|
489
|
+
additional_column_width = surplus_width / [revisiting_column_counts, 1].max
|
490
|
+
revisiting_column_counts -= 1
|
491
|
+
|
492
|
+
if naive_width[:wrapped]
|
493
|
+
max_width = colprops[index]['MaxWidth']
|
494
|
+
if max_width < (naive_width[:width] + additional_column_width)
|
495
|
+
surplus_width -= max_width - naive_width[:width]
|
496
|
+
max_width
|
497
|
+
else
|
498
|
+
surplus_width -= additional_column_width
|
499
|
+
naive_width[:width] + additional_column_width
|
500
|
+
end
|
501
|
+
else
|
502
|
+
naive_width[:width]
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
# In certain scenarios columns can be allocated 0 widths if it's completely impossible to fit the columns into the
|
507
|
+
# given space. There's different ways to handle that, for instance truncating data in the table to the initial
|
508
|
+
# columns that can fit. For now, we just ensure every width is at least 1 or more character wide, and in the future
|
509
|
+
# it may have to truncate columns entirely.
|
510
|
+
optimal_widths.map { |width| [1, width].max }
|
511
|
+
end
|
512
|
+
|
513
|
+
def format_table_field(str, idx)
|
514
|
+
str_cp = str.dup
|
515
|
+
|
516
|
+
colprops[idx]['Formatters'].each do |f|
|
517
|
+
str_cp = f.format(str_cp)
|
518
|
+
end
|
519
|
+
|
520
|
+
str_cp.dup.force_encoding('UTF-8')
|
521
|
+
end
|
522
|
+
|
523
|
+
def style_table_field(str, _idx)
|
524
|
+
str_cp = str.dup
|
525
|
+
|
526
|
+
# Not invoking as color currently conflicts with the wrapping of tables
|
527
|
+
# colprops[idx]['Stylers'].each do |s|
|
528
|
+
# str_cp = s.style(str_cp)
|
529
|
+
# end
|
530
|
+
|
531
|
+
str_cp
|
532
|
+
end
|
533
|
+
|
534
|
+
end
|
535
|
+
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rex-text
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.28
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David 'thelightcosine' Maloney
|
@@ -10,85 +10,90 @@ bindir: exe
|
|
10
10
|
cert_chain:
|
11
11
|
- |
|
12
12
|
-----BEGIN CERTIFICATE-----
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
13
|
+
MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl
|
14
|
+
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
15
|
+
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
|
16
|
+
b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG
|
17
|
+
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
|
18
|
+
cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi
|
19
|
+
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c
|
20
|
+
JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP
|
21
|
+
mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+
|
22
|
+
wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4
|
23
|
+
VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/
|
24
|
+
AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB
|
25
|
+
AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
|
26
|
+
BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun
|
27
|
+
pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC
|
28
|
+
dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf
|
29
|
+
fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm
|
30
|
+
NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx
|
31
|
+
H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
|
32
|
+
+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
|
32
33
|
-----END CERTIFICATE-----
|
33
34
|
- |
|
34
35
|
-----BEGIN CERTIFICATE-----
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
36
|
+
MIIFMDCCBBigAwIBAgIQBAkYG1/Vu2Z1U0O1b5VQCDANBgkqhkiG9w0BAQsFADBl
|
37
|
+
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
38
|
+
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
|
39
|
+
b3QgQ0EwHhcNMTMxMDIyMTIwMDAwWhcNMjgxMDIyMTIwMDAwWjByMQswCQYDVQQG
|
40
|
+
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
|
41
|
+
cnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgQ29kZSBT
|
42
|
+
aWduaW5nIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+NOzHH8O
|
43
|
+
Ea9ndwfTCzFJGc/Q+0WZsTrbRPV/5aid2zLXcep2nQUut4/6kkPApfmJ1DcZ17aq
|
44
|
+
8JyGpdglrA55KDp+6dFn08b7KSfH03sjlOSRI5aQd4L5oYQjZhJUM1B0sSgmuyRp
|
45
|
+
wsJS8hRniolF1C2ho+mILCCVrhxKhwjfDPXiTWAYvqrEsq5wMWYzcT6scKKrzn/p
|
46
|
+
fMuSoeU7MRzP6vIK5Fe7SrXpdOYr/mzLfnQ5Ng2Q7+S1TqSp6moKq4TzrGdOtcT3
|
47
|
+
jNEgJSPrCGQ+UpbB8g8S9MWOD8Gi6CxR93O8vYWxYoNzQYIH5DiLanMg0A9kczye
|
48
|
+
n6Yzqf0Z3yWT0QIDAQABo4IBzTCCAckwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNV
|
49
|
+
HQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwMweQYIKwYBBQUHAQEEbTBr
|
50
|
+
MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUH
|
51
|
+
MAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJ
|
52
|
+
RFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2lj
|
53
|
+
ZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6
|
54
|
+
Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmww
|
55
|
+
TwYDVR0gBEgwRjA4BgpghkgBhv1sAAIEMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v
|
56
|
+
d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCgYIYIZIAYb9bAMwHQYDVR0OBBYEFFrEuXsq
|
57
|
+
CqOl6nEDwGD5LfZldQ5YMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgP
|
58
|
+
MA0GCSqGSIb3DQEBCwUAA4IBAQA+7A1aJLPzItEVyCx8JSl2qB1dHC06GsTvMGHX
|
59
|
+
fgtg/cM9D8Svi/3vKt8gVTew4fbRknUPUbRupY5a4l4kgU4QpO4/cY5jDhNLrddf
|
60
|
+
RHnzNhQGivecRk5c/5CxGwcOkRX7uq+1UcKNJK4kxscnKqEpKBo6cSgCPC6Ro8Al
|
61
|
+
EeKcFEehemhor5unXCBc2XGxDI+7qPjFEmifz0DLQESlE/DmZAwlCEIysjaKJAL+
|
62
|
+
L3J+HNdJRZboWR3p+nRka7LrZkPas7CM1ekN3fYBIM6ZMWM9CBoYs4GbT8aTEAb8
|
63
|
+
B4H6i9r5gkn3Ym6hU/oSlBiFLpKR6mhsRDKyZqHnGKSaZFHv
|
60
64
|
-----END CERTIFICATE-----
|
61
65
|
- |
|
62
66
|
-----BEGIN CERTIFICATE-----
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
67
|
+
MIIFIzCCBAugAwIBAgIQDX9ZkVJ2eNVTlibR5ALyJTANBgkqhkiG9w0BAQsFADBy
|
68
|
+
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
69
|
+
d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQg
|
70
|
+
SUQgQ29kZSBTaWduaW5nIENBMB4XDTE5MTAxNjAwMDAwMFoXDTIwMTAxOTEyMDAw
|
71
|
+
MFowYDELMAkGA1UEBhMCVVMxFjAUBgNVBAgTDU1hc3NhY2h1c2V0dHMxDzANBgNV
|
72
|
+
BAcTBkJvc3RvbjETMBEGA1UEChMKUmFwaWQ3IExMQzETMBEGA1UEAxMKUmFwaWQ3
|
73
|
+
IExMQzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANHnKegPAghKuZk4
|
74
|
+
Gy1jKaZEXbWc4fxioTemv/F1yIYzAjCWP65qjKtyeeFDe4/kJzG9nseF9oa93YBf
|
75
|
+
1nyEqxNSZMw/sCAZ87lOl713dRi73uxOoszy2PT5xEB+Q5R6cbzExkWG2zrLdXDr
|
76
|
+
so0Bd6VHw+IsAoBBkAq5FrZOJQYGn5VY20xw/2DqtCeoW4QDWyqTnbJmwO9tZrfr
|
77
|
+
3Le2crfk2eOgafaPNhLon5uuIKCZsk2YkUSNURSS3M7gosMwU9Gg4JTBi7X5+oww
|
78
|
+
rY43dJT28YklxmNVu8o5kJxW4dqLKJLOIgSXZ63nceT/EaCSg7DcofHNcUzejFwb
|
79
|
+
M7Zbb2kCAwEAAaOCAcUwggHBMB8GA1UdIwQYMBaAFFrEuXsqCqOl6nEDwGD5LfZl
|
80
|
+
dQ5YMB0GA1UdDgQWBBR18CAeMsIEU+0pXal/XXw9LCtMADAOBgNVHQ8BAf8EBAMC
|
81
|
+
B4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwdwYDVR0fBHAwbjA1oDOgMYYvaHR0cDov
|
82
|
+
L2NybDMuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5jcmwwNaAzoDGG
|
83
|
+
L2h0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtY3MtZzEuY3Js
|
84
|
+
MEwGA1UdIARFMEMwNwYJYIZIAYb9bAMBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v
|
85
|
+
d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCAYGZ4EMAQQBMIGEBggrBgEFBQcBAQR4MHYw
|
86
|
+
JAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBOBggrBgEFBQcw
|
87
|
+
AoZCaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkFzc3Vy
|
88
|
+
ZWRJRENvZGVTaWduaW5nQ0EuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQEL
|
89
|
+
BQADggEBAFpzR9s7lcYKDzSJucOHztEPj+iSIeCzxEw34NTE9M2AfkYIu82c4r2a
|
90
|
+
bzIGmzZWiCGufjOp0gF5xW6sSSJ9n0TqH0nhHhvjtZQkmkGtOBbN1zeYDFS2ozAp
|
91
|
+
sljF/g68Y1eYs3NaFf7kQUa6vb6RdjW3J8M9AQ8gthBt7gr/guVxd/gJUYbdDdBX
|
92
|
+
cWfJJi/X7GVBOBmmvA43qoKideuhOBrVGBHvIF/yO9p23dIiUrGmW9kxXCSxgute
|
93
|
+
JI/W23RbIRksG2pioMhd4dCXq3FLLlkOV1YfCwWixNB+iIhQPPZVaPNfgPhCn4Dt
|
94
|
+
DeGjje/qA4fkLtRmOtb9PUBq3ToRDE4=
|
90
95
|
-----END CERTIFICATE-----
|
91
|
-
date:
|
96
|
+
date: 2020-08-07 00:00:00.000000000 Z
|
92
97
|
dependencies:
|
93
98
|
- !ruby/object:Gem::Dependency
|
94
99
|
name: bundler
|
@@ -172,6 +177,7 @@ files:
|
|
172
177
|
- lib/rex/text/table.rb
|
173
178
|
- lib/rex/text/unicode.rb
|
174
179
|
- lib/rex/text/version.rb
|
180
|
+
- lib/rex/text/wrapped_table.rb
|
175
181
|
- lib/rex/text/xor.rb
|
176
182
|
- rex-text.gemspec
|
177
183
|
homepage: https://github.com/rapid7/rex-text
|
@@ -193,7 +199,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
193
199
|
version: '0'
|
194
200
|
requirements: []
|
195
201
|
rubyforge_project:
|
196
|
-
rubygems_version: 2.
|
202
|
+
rubygems_version: 2.7.10
|
197
203
|
signing_key:
|
198
204
|
specification_version: 4
|
199
205
|
summary: Provides Text Manipulation Methods for Exploitation
|
metadata.gz.sig
CHANGED
Binary file
|