r_o_v 0.0.1
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 +7 -0
- data/lib/r_o_v.rb +541 -0
- metadata +57 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4ea03b50c9ce97d3300f76bf892de2bc9b3c1d04d1424066a47921c55363fbe1
|
4
|
+
data.tar.gz: 10bd393dae1fab92df73a5788e0d3fa60ec799ae97eeb2fc9aef2703aeef310f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ab6da8547bd0e72dbf5021e2b74f6b28c5fc06ce34ee2674deb4b599eb4ebc643f0124c7961e278644ad75161d626c6ea43740ea5159fb8383e5ab2683d109d1
|
7
|
+
data.tar.gz: b815fcad8ad483256f2dee3cf4be668fbc58f8d28ddfc527196f8c3e790dda906527939a8f40edada37c30afff94ace2a46723ae309e490abfcb1463a8fdd438
|
data/lib/r_o_v.rb
ADDED
@@ -0,0 +1,541 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# TODO:
|
4
|
+
# - close parent should work on the child too (go to parent and then close)
|
5
|
+
# - sluggishness on M1 + Rails + pry
|
6
|
+
# - fuzzy search - jump
|
7
|
+
# - parallel open (same trail)
|
8
|
+
# - memory slabs
|
9
|
+
|
10
|
+
class ROV
|
11
|
+
class Util
|
12
|
+
class << self
|
13
|
+
def red(s); escape(s, 91); end
|
14
|
+
def green(s); escape(s, 92); end
|
15
|
+
def yellow(s); escape(s, 93); end
|
16
|
+
def blue(s); escape(s, 94); end
|
17
|
+
def magenta(s); escape(s, 95); end
|
18
|
+
def cyan(s); escape(s, 96); end
|
19
|
+
def bold(s); escape(s, 1); end
|
20
|
+
def dim(s); escape(s, 22); end
|
21
|
+
def invert(s); escape(s, 7); end
|
22
|
+
def console_lines; %x`tput lines`.to_i; end
|
23
|
+
def console_cols; %x`tput cols`.to_i; end
|
24
|
+
|
25
|
+
def visible_truncate(s, lim)
|
26
|
+
return s unless s.size > lim # Quick escape to save gsub use.
|
27
|
+
return s unless visible_str_len(s) > lim
|
28
|
+
|
29
|
+
vis_count = 0
|
30
|
+
full_count = nil
|
31
|
+
in_escape = false
|
32
|
+
|
33
|
+
|
34
|
+
s.chars.each_with_index do |c, idx|
|
35
|
+
if in_escape
|
36
|
+
in_escape = false if c == 'm'
|
37
|
+
else
|
38
|
+
if c == "\e"
|
39
|
+
in_escape = true
|
40
|
+
else
|
41
|
+
vis_count += 1
|
42
|
+
|
43
|
+
if vis_count >= lim - 1
|
44
|
+
full_count = idx
|
45
|
+
break
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
s[..full_count] + "\x1B[0m…"
|
52
|
+
end
|
53
|
+
|
54
|
+
def visible_str_len(str); str.gsub(/\e\[\d+m/, '').size; end
|
55
|
+
|
56
|
+
def simple_type?(o)
|
57
|
+
case o
|
58
|
+
when String, Numeric, Symbol, TrueClass, FalseClass, NilClass then true
|
59
|
+
else false
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def escape(s, color_code); "\x1B[#{color_code}m#{s}\x1B[0m"; end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Ctx
|
70
|
+
attr_reader(:parent_ctx)
|
71
|
+
attr_reader(:children_ctx)
|
72
|
+
attr_reader(:tag)
|
73
|
+
attr_reader(:current_level)
|
74
|
+
attr_reader(:obj)
|
75
|
+
attr_reader(:selection)
|
76
|
+
|
77
|
+
def initialize(obj, parent_ctx, current_level:)
|
78
|
+
@obj = obj
|
79
|
+
@parent_ctx = parent_ctx
|
80
|
+
@selection = children_size > 0 ? 0 : nil
|
81
|
+
@children_ctx = [nil] * children_size
|
82
|
+
@tag = obj.class.name
|
83
|
+
@current_level = current_level
|
84
|
+
end
|
85
|
+
|
86
|
+
def select_next
|
87
|
+
return unless children_size > 0
|
88
|
+
self.selection = (selection + 1) % children_size
|
89
|
+
end
|
90
|
+
|
91
|
+
def select_prev
|
92
|
+
return unless children_size > 0
|
93
|
+
self.selection = (selection - 1) % children_size
|
94
|
+
end
|
95
|
+
|
96
|
+
def select_first
|
97
|
+
self.selection = 0
|
98
|
+
end
|
99
|
+
|
100
|
+
def select_last
|
101
|
+
self.selection = children_size - 1
|
102
|
+
end
|
103
|
+
|
104
|
+
def at_last_child?
|
105
|
+
selection == children_size - 1
|
106
|
+
end
|
107
|
+
|
108
|
+
def at_first_child?
|
109
|
+
selection == 0
|
110
|
+
end
|
111
|
+
|
112
|
+
def children_size
|
113
|
+
@children_size ||= case obj
|
114
|
+
when Enumerable
|
115
|
+
obj.respond_to?(:size) ? obj.size : obj.to_a.size
|
116
|
+
# TODO Maybe this can coexist with enumerable (eg sg that fakes enumarable).
|
117
|
+
else
|
118
|
+
obj.instance_variables.size
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def has_children?
|
123
|
+
children_size > 0
|
124
|
+
end
|
125
|
+
|
126
|
+
def children_names
|
127
|
+
@children_names ||= case obj
|
128
|
+
when Hash then obj.keys
|
129
|
+
when Enumerable then children_size.times.to_a
|
130
|
+
else obj.instance_variables
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
#
|
135
|
+
# Actual object children (raw object).
|
136
|
+
#
|
137
|
+
def child_at(index)
|
138
|
+
case obj
|
139
|
+
when Hash then obj.values[index]
|
140
|
+
when Enumerable then obj.to_a[index]
|
141
|
+
else obj.instance_variable_get(obj.instance_variables[index])
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def active_child
|
146
|
+
return if selection.nil?
|
147
|
+
|
148
|
+
raise("Selection must be positive") unless selection >= 0
|
149
|
+
raise("Selection is out of bounds") unless selection < children_size
|
150
|
+
|
151
|
+
child_at(selection)
|
152
|
+
end
|
153
|
+
|
154
|
+
def active_child_var_name
|
155
|
+
case obj
|
156
|
+
when Hash
|
157
|
+
key = obj.keys[selection]
|
158
|
+
|
159
|
+
if Util.simple_type?(key)
|
160
|
+
"[#{key.inspect.gsub('"', '\'')}]"
|
161
|
+
else
|
162
|
+
".values[#{selection}]"
|
163
|
+
end
|
164
|
+
when Enumerable
|
165
|
+
"[#{selection}]"
|
166
|
+
else
|
167
|
+
".#{obj.instance_variables[selection][1..-1]}"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def active_child_open?
|
172
|
+
!children_ctx[selection].nil?
|
173
|
+
end
|
174
|
+
|
175
|
+
def child_openable?(index)
|
176
|
+
case (elem = child_at(index))
|
177
|
+
when IO then elem.instance_variables.size > 0
|
178
|
+
when Enumerable then elem.to_a.size > 0
|
179
|
+
else elem.instance_variables.size > 0
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def active_child_openable?
|
184
|
+
child_openable?(selection)
|
185
|
+
end
|
186
|
+
|
187
|
+
def open_active_child
|
188
|
+
raise("Child is not openable") unless active_child_openable?
|
189
|
+
children_ctx[selection] ||= Ctx.new(active_child, self, current_level: current_level + 1)
|
190
|
+
end
|
191
|
+
|
192
|
+
def open_nth_child(idx)
|
193
|
+
raise("Child is not openable") unless child_openable?(idx)
|
194
|
+
children_ctx[idx] ||= Ctx.new(child_at(idx), self, current_level: current_level + 1)
|
195
|
+
end
|
196
|
+
|
197
|
+
def open_children
|
198
|
+
children_size.times do |i|
|
199
|
+
next unless children_ctx[i].nil?
|
200
|
+
next unless child_openable?(i)
|
201
|
+
|
202
|
+
children_ctx[i] = Ctx.new(child_at(i), self, current_level: current_level + 1)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# TODO: Lets not lose the object, lets have a prop for closed.
|
207
|
+
def close_active_child
|
208
|
+
children_ctx[selection] = nil
|
209
|
+
end
|
210
|
+
|
211
|
+
def close_children
|
212
|
+
children_size.times { |i| children_ctx[i] = nil }
|
213
|
+
end
|
214
|
+
|
215
|
+
#
|
216
|
+
# @return [String, Boolean] = [Output, Is-cursor-line?]
|
217
|
+
#
|
218
|
+
def pretty_print(active_ctx, indent = ' ')
|
219
|
+
out = []
|
220
|
+
|
221
|
+
children_names.zip(children_ctx).each_with_index do |(elem_name, child_ctx), index|
|
222
|
+
value_suffix = child_openable?(index) ? '' : " = #{Util.cyan(child_at(index).to_s)}"
|
223
|
+
tag_suffix = " (#{Util.magenta(child_at(index).class.name)})#{value_suffix}"
|
224
|
+
|
225
|
+
is_active_line = self == active_ctx && selection == index
|
226
|
+
active_pos_marker = is_active_line ? '>' : ' '
|
227
|
+
nesting_symbol = index == children_size - 1 ? '└' : '├'
|
228
|
+
tree_more_symbol = child_openable?(index) ? '+ ': ' '
|
229
|
+
|
230
|
+
line = <<~LINE.lines(chomp: true).join
|
231
|
+
#{indent}
|
232
|
+
#{Util.bold(Util.yellow(active_pos_marker))}
|
233
|
+
#{nesting_symbol}─
|
234
|
+
#{tree_more_symbol}
|
235
|
+
#{is_active_line ? Util.invert(Util.blue(elem_name)) : Util.blue(elem_name)}
|
236
|
+
#{tag_suffix}
|
237
|
+
LINE
|
238
|
+
|
239
|
+
out << [line, is_active_line]
|
240
|
+
|
241
|
+
if child_ctx
|
242
|
+
tree_guide = index == children_size - 1 ? ' ' : '¦'
|
243
|
+
out += child_ctx.pretty_print(active_ctx, indent + " #{tree_guide}")
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
out
|
248
|
+
end
|
249
|
+
|
250
|
+
def is_list
|
251
|
+
@obj.is_a?(Enumerable)
|
252
|
+
end
|
253
|
+
|
254
|
+
private
|
255
|
+
|
256
|
+
def children_ctx=
|
257
|
+
raise
|
258
|
+
end
|
259
|
+
|
260
|
+
def parent_ctx=
|
261
|
+
raise
|
262
|
+
end
|
263
|
+
|
264
|
+
def obj=
|
265
|
+
raise
|
266
|
+
end
|
267
|
+
|
268
|
+
attr_writer(:selection)
|
269
|
+
end
|
270
|
+
|
271
|
+
class << self
|
272
|
+
def [](obj)
|
273
|
+
ROV.new(obj).loop
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def initialize(obj)
|
278
|
+
@root_ctx = @active_ctx = Ctx.new(obj, nil, current_level: 0)
|
279
|
+
@is_running = true
|
280
|
+
@terminal_width = Util.console_cols
|
281
|
+
@variable_name = get_input_presentation
|
282
|
+
end
|
283
|
+
|
284
|
+
def loop
|
285
|
+
return unless root_ctx.has_children?
|
286
|
+
|
287
|
+
while @is_running
|
288
|
+
print_root
|
289
|
+
execute(read_char)
|
290
|
+
end
|
291
|
+
|
292
|
+
current_variable_as_expression
|
293
|
+
end
|
294
|
+
|
295
|
+
def execute(input)
|
296
|
+
case input
|
297
|
+
when 'q' then stop_loop
|
298
|
+
when 'w' then step_up
|
299
|
+
when 's' then step_down
|
300
|
+
when 'a' then step_parent
|
301
|
+
when 'd' then step_child
|
302
|
+
when 'h' then step_home
|
303
|
+
when 'e' then close_active_child
|
304
|
+
when '0'..'9' then open_tree_level(input.to_i)
|
305
|
+
when 'i' then idbg_ext_log
|
306
|
+
when 'p' then open_parallel_children
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
def current_variable_as_expression
|
311
|
+
@variable_name + active_var_path
|
312
|
+
end
|
313
|
+
|
314
|
+
private
|
315
|
+
|
316
|
+
attr_reader :root_ctx
|
317
|
+
attr_accessor :active_ctx
|
318
|
+
|
319
|
+
def root_ctx=
|
320
|
+
raise
|
321
|
+
end
|
322
|
+
|
323
|
+
#
|
324
|
+
# Quit.
|
325
|
+
#
|
326
|
+
def stop_loop
|
327
|
+
@is_running = false
|
328
|
+
end
|
329
|
+
|
330
|
+
#
|
331
|
+
# Set current CTX to the parent.
|
332
|
+
#
|
333
|
+
def step_parent
|
334
|
+
self.active_ctx = active_ctx.parent_ctx unless active_ctx.parent_ctx.nil?
|
335
|
+
end
|
336
|
+
|
337
|
+
#
|
338
|
+
# Set current CTX to active child.
|
339
|
+
#
|
340
|
+
def step_child
|
341
|
+
self.active_ctx = active_ctx.open_active_child if active_ctx.active_child_openable?
|
342
|
+
end
|
343
|
+
|
344
|
+
#
|
345
|
+
# Set current CTX to root.
|
346
|
+
#
|
347
|
+
def step_home
|
348
|
+
self.active_ctx = root_ctx
|
349
|
+
active_ctx.select_first
|
350
|
+
end
|
351
|
+
|
352
|
+
#
|
353
|
+
# Clost current CTX active child.
|
354
|
+
#
|
355
|
+
def close_active_child
|
356
|
+
active_ctx.close_active_child
|
357
|
+
end
|
358
|
+
|
359
|
+
#
|
360
|
+
# Set active CTX to previous child or previous opened subtree last leaf.
|
361
|
+
#
|
362
|
+
def step_up
|
363
|
+
if active_ctx.at_first_child?
|
364
|
+
if active_ctx.parent_ctx
|
365
|
+
self.active_ctx = active_ctx.parent_ctx
|
366
|
+
else
|
367
|
+
active_ctx.select_last
|
368
|
+
|
369
|
+
while active_ctx.active_child_open?
|
370
|
+
self.active_ctx = active_ctx.open_active_child
|
371
|
+
active_ctx.select_last
|
372
|
+
end
|
373
|
+
end
|
374
|
+
return
|
375
|
+
end
|
376
|
+
|
377
|
+
active_ctx.select_prev
|
378
|
+
|
379
|
+
while active_ctx.active_child_open?
|
380
|
+
self.active_ctx = active_ctx.open_active_child
|
381
|
+
active_ctx.select_last
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
#
|
386
|
+
# Set active CTX to next child or next opened subtree first node.
|
387
|
+
#
|
388
|
+
def step_down
|
389
|
+
if active_ctx.active_child_open?
|
390
|
+
self.active_ctx = active_ctx.open_active_child
|
391
|
+
active_ctx.select_first
|
392
|
+
return
|
393
|
+
end
|
394
|
+
|
395
|
+
unless active_ctx.at_last_child?
|
396
|
+
active_ctx.select_next
|
397
|
+
return
|
398
|
+
end
|
399
|
+
|
400
|
+
while active_ctx.at_last_child? && active_ctx.parent_ctx
|
401
|
+
self.active_ctx = active_ctx.parent_ctx
|
402
|
+
end
|
403
|
+
|
404
|
+
active_ctx.select_next
|
405
|
+
end
|
406
|
+
|
407
|
+
#
|
408
|
+
# Open all nodes on level N.
|
409
|
+
#
|
410
|
+
def open_tree_level(n)
|
411
|
+
while n < active_ctx.current_level
|
412
|
+
self.active_ctx = active_ctx.parent_ctx
|
413
|
+
end
|
414
|
+
|
415
|
+
open_tree_level_until(root_ctx, n)
|
416
|
+
end
|
417
|
+
|
418
|
+
def open_tree_level_until(ctx, n)
|
419
|
+
if n == 0
|
420
|
+
ctx.close_children
|
421
|
+
return
|
422
|
+
end
|
423
|
+
|
424
|
+
ctx.open_children
|
425
|
+
ctx.children_ctx.each do |child_ctx|
|
426
|
+
next unless child_ctx
|
427
|
+
|
428
|
+
open_tree_level_until(child_ctx, n - 1)
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
def idbg_ext_log
|
433
|
+
return unless Object.const_defined?("IDbg")
|
434
|
+
IDbg.log("ROV", current_variable_as_expression, active_ctx.active_child)
|
435
|
+
end
|
436
|
+
|
437
|
+
def open_parallel_children
|
438
|
+
# Find first enumerable parent.
|
439
|
+
enumerable_parent_ctx = active_ctx
|
440
|
+
trail = []
|
441
|
+
|
442
|
+
while enumerable_parent_ctx
|
443
|
+
break if enumerable_parent_ctx.is_list
|
444
|
+
|
445
|
+
trail.unshift(enumerable_parent_ctx.selection)
|
446
|
+
|
447
|
+
enumerable_parent_ctx = enumerable_parent_ctx.parent_ctx
|
448
|
+
end
|
449
|
+
return unless enumerable_parent_ctx
|
450
|
+
|
451
|
+
# Set expected type.
|
452
|
+
expected_class = enumerable_parent_ctx.active_child.class
|
453
|
+
|
454
|
+
# Check if all child is the same.
|
455
|
+
is_uniform = enumerable_parent_ctx.obj.to_a.all? { |e| e.is_a?(expected_class) }
|
456
|
+
|
457
|
+
return unless is_uniform
|
458
|
+
|
459
|
+
enumerable_parent_ctx.children_size.times do |i|
|
460
|
+
next unless enumerable_parent_ctx.child_openable?(i)
|
461
|
+
|
462
|
+
trail_ctx = enumerable_parent_ctx.open_nth_child(i)
|
463
|
+
|
464
|
+
# trail.shift # First item is the current iteration.
|
465
|
+
trail.each do |child_idx|
|
466
|
+
break unless trail_ctx.child_openable?(child_idx)
|
467
|
+
|
468
|
+
trail_ctx = trail_ctx.open_nth_child(child_idx)
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
def active_var_path
|
474
|
+
current_ctx = active_ctx
|
475
|
+
path = []
|
476
|
+
while current_ctx
|
477
|
+
path.unshift(current_ctx.active_child_var_name)
|
478
|
+
current_ctx = current_ctx.parent_ctx
|
479
|
+
end
|
480
|
+
|
481
|
+
path.join
|
482
|
+
end
|
483
|
+
|
484
|
+
def print_root
|
485
|
+
clear_terminal
|
486
|
+
|
487
|
+
lines = [[Util.magenta(root_ctx.tag) + ":", false]]
|
488
|
+
lines += root_ctx.pretty_print(active_ctx)
|
489
|
+
active_line_index = lines.index { |_, is_active| is_active }
|
490
|
+
|
491
|
+
puts (lines[presentable_line_range(active_line_index, lines.size)].map do |line, _|
|
492
|
+
Util.visible_truncate(line, @terminal_width)
|
493
|
+
end.join("\n"))
|
494
|
+
puts "\n📋 #{@variable_name}#{Util.green(active_var_path)}"
|
495
|
+
end
|
496
|
+
|
497
|
+
def presentable_line_range(mid_index, len)
|
498
|
+
padding = (Util.console_lines - 4) / 2
|
499
|
+
|
500
|
+
if mid_index <= padding
|
501
|
+
from = 0
|
502
|
+
to = [padding * 2 + 1, len - 1].min
|
503
|
+
elsif mid_index + padding >= len
|
504
|
+
to = len - 1
|
505
|
+
from = [0, to - 1 - 2 * padding].max
|
506
|
+
else
|
507
|
+
from = [0, mid_index - padding - 1].max
|
508
|
+
to = [mid_index + padding, len].min
|
509
|
+
end
|
510
|
+
|
511
|
+
from..to
|
512
|
+
end
|
513
|
+
|
514
|
+
def clear_terminal
|
515
|
+
print `clear`
|
516
|
+
end
|
517
|
+
|
518
|
+
def read_char
|
519
|
+
system('stty', 'raw', '-echo')
|
520
|
+
char = STDIN.getc
|
521
|
+
system('stty', '-raw', 'echo')
|
522
|
+
char
|
523
|
+
end
|
524
|
+
|
525
|
+
def get_input_presentation
|
526
|
+
rx = /ROV\[(?<varname>.+)\]$/
|
527
|
+
|
528
|
+
if Object.const_defined?("Pry")
|
529
|
+
last_pry_call = Pry.history.to_a.last
|
530
|
+
return rx.match(last_pry_call.rstrip)["varname"]
|
531
|
+
elsif Object.const_defined?("IRB")
|
532
|
+
IRB.CurrentContext.io.save_history
|
533
|
+
last_irb_call = IO.readlines(File.expand_path(IRB.rc_file("_history"))).last
|
534
|
+
return rx.match(last_irb_call.rstrip)["varname"]
|
535
|
+
end
|
536
|
+
|
537
|
+
"_"
|
538
|
+
rescue
|
539
|
+
"-"
|
540
|
+
end
|
541
|
+
end
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: r_o_v
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- itarato
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-08-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: minitest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description:
|
28
|
+
email: it.arato@gmail.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- lib/r_o_v.rb
|
34
|
+
homepage: https://github.com/itarato/Ruby-Object-Viewer/
|
35
|
+
licenses:
|
36
|
+
- GPL-3.0-or-later
|
37
|
+
metadata: {}
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 3.0.0
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubygems_version: 3.4.10
|
54
|
+
signing_key:
|
55
|
+
specification_version: 4
|
56
|
+
summary: Tree style Ruby object viewer (for the terminal)
|
57
|
+
test_files: []
|