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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 749df288d86a56887e10173789fa6f5cf48338f0
4
- data.tar.gz: 2816e209fbf1f7023bddfba45685547f39135e65
2
+ SHA256:
3
+ metadata.gz: ed0a3f361cc220f42bd061edb39b914add50ad3a52f196f1d02bce1e3e56254a
4
+ data.tar.gz: b9317aea70870fc37394cf4d60c53b12b314e0c0f5da7ed949e0af658f54a2de
5
5
  SHA512:
6
- metadata.gz: 13065d1896bf02cd692079389e4d67b870f8a429914c91a2cf238c6c4f6e9e0123de531cedd8845888958404e00d6c96af570f910e9741bee5309a60634d3d35
7
- data.tar.gz: 9cf93a5d8f0c83d09a79841de00afaf39a329a95024d9f7345620f07442dd317f3592ef1351cddd0eace24772f51039434159073e94a62cf22adc9c0498671cb
6
+ metadata.gz: 984bc5972b4ac1abe5dbd98dbe651bcdc785724b61062062ee1c0aa5811cbd78d54c45bef34f5103e191b878b42fd341ebe37ad0bd482b7e99959b558f5aba4f
7
+ data.tar.gz: f21d06b23911998f4d178c081d420e18e1692e41855cf67d44c0d15509501efdc6745c592fbd3d5681171921c71a67c3e686004bf48e6c7bafdd4aa8747f2ad8
Binary file
data.tar.gz.sig CHANGED
Binary file
data/Gemfile CHANGED
@@ -2,3 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in rex-text.gemspec
4
4
  gemspec
5
+
6
+ group :development do
7
+ gem 'pry-byebug'
8
+ end
@@ -29,7 +29,7 @@ require 'rex/text/xor'
29
29
 
30
30
  require 'rex/text/color'
31
31
  require 'rex/text/table'
32
-
32
+ require 'rex/text/wrapped_table'
33
33
 
34
34
  module Rex
35
35
 
@@ -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
@@ -152,5 +152,13 @@ module Rex
152
152
  return wordwrap(str, 0, wrap, '', '# ')
153
153
  end
154
154
 
155
+ #
156
+ # Creates a psh-style comment
157
+ #
158
+ def self.to_psh_comment(str, wrap = DefaultWrap)
159
+ return wordwrap(str, 0, wrap, '', '# ')
160
+ end
161
+
162
+
155
163
  end
156
164
  end
@@ -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.each_with_index { |field, idx|
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 << fields
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
- remainder = max - utf8_buf.length
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
 
@@ -1,5 +1,5 @@
1
1
  module Rex
2
2
  module Text
3
- VERSION = "0.2.23"
3
+ VERSION = "0.2.28"
4
4
  end
5
5
  end
@@ -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.23
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
- MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
14
- A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
15
- Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
16
- MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG
17
- A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
18
- hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8
19
- RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT
20
- gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm
21
- KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd
22
- QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ
23
- XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw
24
- DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o
25
- LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU
26
- RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp
27
- jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK
28
- 6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX
29
- mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs
30
- Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH
31
- WD9f
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
- MIIElDCCA3ygAwIBAgIOSBtqBybS6D8mAtSCWs0wDQYJKoZIhvcNAQELBQAwTDEg
36
- MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2Jh
37
- bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTYwNjE1MDAwMDAwWhcNMjQw
38
- NjE1MDAwMDAwWjBaMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBu
39
- di1zYTEwMC4GA1UEAxMnR2xvYmFsU2lnbiBDb2RlU2lnbmluZyBDQSAtIFNIQTI1
40
- NiAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjYVVI6kfU6/J
41
- 7TbCKbVu2PlC9SGLh/BDoS/AP5fjGEfUlk6Iq8Zj6bZJFYXx2Zt7G/3YSsxtToZA
42
- F817ukcotdYUQAyG7h5LM/MsVe4hjNq2wf6wTjquUZ+lFOMQ5pPK+vldsZCH7/g1
43
- LfyiXCbuexWLH9nDoZc1QbMw/XITrZGXOs5ynQYKdTwfmOPLGC+MnwhKkQrZ2TXZ
44
- g5J2Yl7fg67k1gFOzPM8cGFYNx8U42qgr2v02dJsLBkwXaBvUt/RnMngDdl1EWWW
45
- 2UO0p5A5rkccVMuxlW4l3o7xEhzw127nFE2zGmXWhEpX7gSvYjjFEJtDjlK4Prau
46
- niyX/4507wIDAQABo4IBZDCCAWAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdJQQWMBQG
47
- CCsGAQUFBwMDBggrBgEFBQcDCTASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQW
48
- BBQPOueslJF0LZYCc4OtnC5JPxmqVDAfBgNVHSMEGDAWgBSP8Et/qC5FJK5NUPpj
49
- move4t0bvDA+BggrBgEFBQcBAQQyMDAwLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3Nw
50
- Mi5nbG9iYWxzaWduLmNvbS9yb290cjMwNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDov
51
- L2NybC5nbG9iYWxzaWduLmNvbS9yb290LXIzLmNybDBjBgNVHSAEXDBaMAsGCSsG
52
- AQQBoDIBMjAIBgZngQwBBAEwQQYJKwYBBAGgMgFfMDQwMgYIKwYBBQUHAgEWJmh0
53
- dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9zaXRvcnkvMA0GCSqGSIb3DQEB
54
- CwUAA4IBAQAVhCgM7aHDGYLbYydB18xjfda8zzabz9JdTAKLWBoWCHqxmJl/2DOK
55
- XJ5iCprqkMLFYwQL6IdYBgAHglnDqJQy2eAUTaDVI+DH3brwaeJKRWUtTUmQeGYy
56
- DrBowLCIsI7tXAb4XBBIPyNzujtThFKAzfCzFcgRCosFeEZZCNS+t/9L9ZxqTJx2
57
- ohGFRYzUN+5Q3eEzNKmhHzoL8VZEim+zM9CxjtEMYAfuMsLwJG+/r/uBAXZnxKPo
58
- 4KvcM1Uo42dHPOtqpN+U6fSmwIHRUphRptYCtzzqSu/QumXSN4NTS35nfIxA9gcc
59
- sK8EBtz4bEaIcpzrTp3DsLlUo7lOl8oU
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
- MIIE5jCCA86gAwIBAgIMKDuO03uv6RWXR1uAMA0GCSqGSIb3DQEBCwUAMFoxCzAJ
64
- BgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTAwLgYDVQQDEydH
65
- bG9iYWxTaWduIENvZGVTaWduaW5nIENBIC0gU0hBMjU2IC0gRzMwHhcNMTYwOTEz
66
- MTgxMDIyWhcNMTkxMTExMTUxNTM4WjBgMQswCQYDVQQGEwJVUzEWMBQGA1UECBMN
67
- TWFzc2FjaHVzZXR0czEPMA0GA1UEBxMGQm9zdG9uMRMwEQYDVQQKEwpSYXBpZDcg
68
- TExDMRMwEQYDVQQDEwpSYXBpZDcgTExDMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
69
- MIIBCgKCAQEAl0HeC0FzN1BJ4nQkxsBng3peS9Bdi9rpSGx+g0Ximd+M/7twmund
70
- bzn2JPbNK/Gp/rq/SytrNSLcUzcbH/0z5Ltyw1/jQsGtRBrns0NZSRXqupQDW5R6
71
- HFpaIAl3OdsesmIQc/fm0uhh8dkfHVo7UsZO/TeCPoy0uHXTI6aFBPzMMsdz+gf3
72
- cCCLsnNKQh/T2Q/jwBs3NTPoyza/pPZcvGogKcWCeNihTO5Rn1Fc71sMHSjQsDtn
73
- 1fWGKYGi0qjvZ4lpGM9IFZMTbySKHbPLhhHnBOoV7avGemdky3AEsUeiT+6DY0P1
74
- IydBy24uVNhGATglME1ttlT4Eme/to0M6wIDAQABo4IBpDCCAaAwDgYDVR0PAQH/
75
- BAQDAgeAMIGUBggrBgEFBQcBAQSBhzCBhDBIBggrBgEFBQcwAoY8aHR0cDovL3Nl
76
- Y3VyZS5nbG9iYWxzaWduLmNvbS9jYWNlcnQvZ3Njb2Rlc2lnbnNoYTJnM29jc3Au
77
- Y3J0MDgGCCsGAQUFBzABhixodHRwOi8vb2NzcDIuZ2xvYmFsc2lnbi5jb20vZ3Nj
78
- b2Rlc2lnbnNoYTJnMzBWBgNVHSAETzBNMEEGCSsGAQQBoDIBMjA0MDIGCCsGAQUF
79
- BwIBFiZodHRwczovL3d3dy5nbG9iYWxzaWduLmNvbS9yZXBvc2l0b3J5LzAIBgZn
80
- gQwBBAEwCQYDVR0TBAIwADA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmds
81
- b2JhbHNpZ24uY29tL2dzY29kZXNpZ25zaGEyZzMuY3JsMBMGA1UdJQQMMAoGCCsG
82
- AQUFBwMDMB0GA1UdDgQWBBSm8RBpBC/cK9VmxzO2+RWnacN8CTAfBgNVHSMEGDAW
83
- gBQPOueslJF0LZYCc4OtnC5JPxmqVDANBgkqhkiG9w0BAQsFAAOCAQEANVO3uYQl
84
- h8iicbaXE3odrL+kXXmeeNgt4BD3x7GKAVIVixtwBS6pvrshjc1LN0tm3ruiv8oy
85
- cq4FiEmVUXZejSRvVVtABeWdZWo+lJ8NxCBUEYYmnMrjgFIbGiEbBsg7PGtyeQsA
86
- 5Wbg7Lx889mS1tKfQBcPif8EjpTiXNfMiywmpaMYmvm+yQgzrRLDbjz6JV0Rc5Ga
87
- WChka+LTPnMtsWJuFM8ka8icMeS28/nAGERdewxWvz+DeAPMORdTJ7aqb6+Y9xuz
88
- G+Hmcg1v810agasPdoydE0RTVZgEOOMoQ07qu7JFXVWZ9ZQpHT7qJATWL/b2csFG
89
- 8mVuTXnyJOKRJA==
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: 2019-08-27 00:00:00.000000000 Z
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.6.13
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