scryglass 0.1.0
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/.gitignore +8 -0
- data/.tool-versions +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +33 -0
- data/LICENSE.txt +21 -0
- data/README.md +252 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/example_config.rb +30 -0
- data/lib/example_material.rb +97 -0
- data/lib/hexes.rb +135 -0
- data/lib/prog.rb +89 -0
- data/lib/refinements/ansiless_string_refinement.rb +11 -0
- data/lib/refinements/array_fit_to_refinement.rb +67 -0
- data/lib/refinements/clip_string_refinement.rb +27 -0
- data/lib/refinements/constant_defined_string_refinement.rb +11 -0
- data/lib/scryglass.rb +177 -0
- data/lib/scryglass/config.rb +103 -0
- data/lib/scryglass/lens_helper.rb +22 -0
- data/lib/scryglass/lens_panel.rb +140 -0
- data/lib/scryglass/ro.rb +237 -0
- data/lib/scryglass/ro_builder.rb +402 -0
- data/lib/scryglass/session.rb +514 -0
- data/lib/scryglass/tree_panel.rb +122 -0
- data/lib/scryglass/version.rb +3 -0
- data/lib/scryglass/view_panel.rb +91 -0
- data/lib/scryglass/view_wrapper.rb +23 -0
- data/scryglass.gemspec +46 -0
- metadata +117 -0
@@ -0,0 +1,402 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Scryglass
|
4
|
+
module RoBuilder
|
5
|
+
private
|
6
|
+
|
7
|
+
def roify(object,
|
8
|
+
parent_ro:,
|
9
|
+
key: nil,
|
10
|
+
key_value_relationship_indicator: false,
|
11
|
+
special_sub_ro_type: nil,
|
12
|
+
depth:)
|
13
|
+
|
14
|
+
given_ro_params = {
|
15
|
+
key: key,
|
16
|
+
key_value_relationship_indicator: key_value_relationship_indicator,
|
17
|
+
special_sub_ro_type: special_sub_ro_type,
|
18
|
+
parent_ro: parent_ro,
|
19
|
+
depth: depth
|
20
|
+
}
|
21
|
+
|
22
|
+
object_is_an_activerecord_enum = %w[ActiveRecord_Relation
|
23
|
+
ActiveRecord_Associations_CollectionProxy]
|
24
|
+
.include?(object.class.to_s.split('::').last)
|
25
|
+
ro =
|
26
|
+
if object.class == Hash
|
27
|
+
roify_hash(object, **given_ro_params)
|
28
|
+
elsif object.class == Array
|
29
|
+
roify_array(object, **given_ro_params)
|
30
|
+
elsif object_is_an_activerecord_enum
|
31
|
+
roify_ar_relation(object, **given_ro_params)
|
32
|
+
else
|
33
|
+
Scryglass::Ro.new(
|
34
|
+
scry_session: self,
|
35
|
+
val: object,
|
36
|
+
val_type: :nugget,
|
37
|
+
**given_ro_params
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
ro
|
42
|
+
end
|
43
|
+
|
44
|
+
def roify_array(array,
|
45
|
+
key:,
|
46
|
+
key_value_relationship_indicator:,
|
47
|
+
parent_ro:,
|
48
|
+
special_sub_ro_type: nil,
|
49
|
+
depth:)
|
50
|
+
new_ro = Scryglass::Ro.new(
|
51
|
+
scry_session: self,
|
52
|
+
val: array,
|
53
|
+
val_type: :bucket,
|
54
|
+
key: key,
|
55
|
+
key_value_relationship_indicator: key_value_relationship_indicator,
|
56
|
+
special_sub_ro_type: special_sub_ro_type,
|
57
|
+
parent_ro: parent_ro,
|
58
|
+
depth: depth
|
59
|
+
)
|
60
|
+
return new_ro if array.empty?
|
61
|
+
|
62
|
+
task = Prog::Task.new(max_count: array.count)
|
63
|
+
progress_bar << task
|
64
|
+
|
65
|
+
array.each do |o|
|
66
|
+
new_ro.sub_ros << roify(o, parent_ro: new_ro, depth: depth + 1)
|
67
|
+
task.tick
|
68
|
+
print_progress_bar
|
69
|
+
end
|
70
|
+
|
71
|
+
new_ro
|
72
|
+
end
|
73
|
+
|
74
|
+
def roify_ar_relation(ar_relation,
|
75
|
+
key:,
|
76
|
+
key_value_relationship_indicator:,
|
77
|
+
special_sub_ro_type: nil,
|
78
|
+
parent_ro:,
|
79
|
+
depth:)
|
80
|
+
new_ro = Scryglass::Ro.new(
|
81
|
+
scry_session: self,
|
82
|
+
val: ar_relation,
|
83
|
+
val_type: :bucket,
|
84
|
+
key: key,
|
85
|
+
key_value_relationship_indicator: key_value_relationship_indicator,
|
86
|
+
special_sub_ro_type: special_sub_ro_type,
|
87
|
+
parent_ro: parent_ro,
|
88
|
+
depth: depth
|
89
|
+
)
|
90
|
+
return new_ro if ar_relation.empty?
|
91
|
+
|
92
|
+
task = Prog::Task.new(max_count: ar_relation.count)
|
93
|
+
progress_bar << task
|
94
|
+
|
95
|
+
ar_relation.each do |o|
|
96
|
+
new_ro.sub_ros << roify(o, parent_ro: new_ro, depth: depth + 1)
|
97
|
+
task.tick
|
98
|
+
print_progress_bar
|
99
|
+
end
|
100
|
+
|
101
|
+
new_ro
|
102
|
+
end
|
103
|
+
|
104
|
+
def roify_hash(hash,
|
105
|
+
key:,
|
106
|
+
key_value_relationship_indicator:,
|
107
|
+
special_sub_ro_type: nil,
|
108
|
+
parent_ro:,
|
109
|
+
depth:)
|
110
|
+
new_ro = Scryglass::Ro.new(
|
111
|
+
scry_session: self,
|
112
|
+
val: hash,
|
113
|
+
val_type: :bucket,
|
114
|
+
key: key,
|
115
|
+
key_value_relationship_indicator: key_value_relationship_indicator,
|
116
|
+
special_sub_ro_type: special_sub_ro_type,
|
117
|
+
parent_ro: parent_ro,
|
118
|
+
depth: depth
|
119
|
+
)
|
120
|
+
return new_ro if hash.empty?
|
121
|
+
|
122
|
+
task = Prog::Task.new(max_count: hash.count)
|
123
|
+
progress_bar << task
|
124
|
+
|
125
|
+
hash.each do |k, v|
|
126
|
+
new_ro.sub_ros << roify(v,
|
127
|
+
parent_ro: new_ro,
|
128
|
+
key: k,
|
129
|
+
key_value_relationship_indicator: ' => ',
|
130
|
+
depth: depth + 1)
|
131
|
+
task.tick
|
132
|
+
print_progress_bar
|
133
|
+
end
|
134
|
+
|
135
|
+
new_ro
|
136
|
+
end
|
137
|
+
|
138
|
+
def build_instance_variables_for_target_ros
|
139
|
+
original_ro_total = all_ros.count
|
140
|
+
|
141
|
+
if special_command_targets.any?
|
142
|
+
task = Prog::Task.new(max_count: special_command_targets.count)
|
143
|
+
progress_bar << task
|
144
|
+
|
145
|
+
target_ros = special_command_targets.dup # dup because some commands
|
146
|
+
# create ros which are added to all_ros and then this process starts
|
147
|
+
# adding them to the list of things it tries to act on!
|
148
|
+
target_ros.each do |target_ro|
|
149
|
+
build_iv_sub_ros_for(target_ro)
|
150
|
+
task.tick
|
151
|
+
print_progress_bar
|
152
|
+
end
|
153
|
+
self.special_command_targets = []
|
154
|
+
else
|
155
|
+
build_iv_sub_ros_for(current_ro)
|
156
|
+
expand!(current_ro) if current_ro.iv_sub_ros.any?
|
157
|
+
end
|
158
|
+
|
159
|
+
new_ro_total = all_ros.count
|
160
|
+
recalculate_indeces unless new_ro_total == original_ro_total
|
161
|
+
end
|
162
|
+
|
163
|
+
def build_activerecord_relations_for_target_ros
|
164
|
+
original_ro_total = all_ros.count
|
165
|
+
|
166
|
+
if special_command_targets.any?
|
167
|
+
task = Prog::Task.new(max_count: special_command_targets.count)
|
168
|
+
progress_bar << task
|
169
|
+
|
170
|
+
target_ros = special_command_targets.dup # dup because some commands
|
171
|
+
# create ros which are added to all_ros and then this process starts
|
172
|
+
# adding them to the list of things it tries to act on!
|
173
|
+
target_ros.each do |target_ro|
|
174
|
+
build_ar_sub_ros_for(target_ro)
|
175
|
+
task.tick
|
176
|
+
print_progress_bar
|
177
|
+
end
|
178
|
+
self.special_command_targets = []
|
179
|
+
else
|
180
|
+
build_ar_sub_ros_for(current_ro)
|
181
|
+
expand!(current_ro) if current_ro.ar_sub_ros.any?
|
182
|
+
end
|
183
|
+
new_ro_total = all_ros.count
|
184
|
+
|
185
|
+
recalculate_indeces unless new_ro_total == original_ro_total
|
186
|
+
end
|
187
|
+
|
188
|
+
def build_enum_children_for_target_ros
|
189
|
+
original_ro_total = all_ros.count
|
190
|
+
|
191
|
+
if special_command_targets.any?
|
192
|
+
task = Prog::Task.new(max_count: special_command_targets.count)
|
193
|
+
progress_bar << task
|
194
|
+
|
195
|
+
target_ros = special_command_targets.dup # dup because some commands
|
196
|
+
# create ros which are added to all_ros and then this process starts
|
197
|
+
# adding them to the list of things it tries to act on!
|
198
|
+
target_ros.each do |target_ro|
|
199
|
+
build_enum_children_for(target_ro)
|
200
|
+
task.tick
|
201
|
+
print_progress_bar
|
202
|
+
end
|
203
|
+
self.special_command_targets = []
|
204
|
+
else
|
205
|
+
build_enum_children_for(current_ro)
|
206
|
+
expand!(current_ro) if current_ro.enum_sub_ros.any?
|
207
|
+
end
|
208
|
+
|
209
|
+
new_ro_total = all_ros.count
|
210
|
+
recalculate_indeces unless new_ro_total == original_ro_total
|
211
|
+
end
|
212
|
+
|
213
|
+
def recalculate_indeces
|
214
|
+
all_ordered_ros = []
|
215
|
+
scanning_ro = top_ro
|
216
|
+
|
217
|
+
task = Prog::Task.new(max_count: all_ros.count)
|
218
|
+
progress_bar << task
|
219
|
+
|
220
|
+
while scanning_ro
|
221
|
+
all_ordered_ros << scanning_ro
|
222
|
+
next_ro = scanning_ro.next_ro_without_using_index
|
223
|
+
scanning_ro = next_ro
|
224
|
+
task.tick
|
225
|
+
print_progress_bar
|
226
|
+
end
|
227
|
+
|
228
|
+
self.all_ros = all_ordered_ros
|
229
|
+
all_ros.each.with_index { |ro, i| ro.index = i }
|
230
|
+
task.force_finish # Just in case
|
231
|
+
end
|
232
|
+
|
233
|
+
def build_iv_sub_ros_for(ro)
|
234
|
+
return if ro.iv_sub_ros.any?
|
235
|
+
|
236
|
+
iv_names = ro.value.instance_variables
|
237
|
+
return if iv_names.empty?
|
238
|
+
|
239
|
+
prog_task = Prog::Task.new(max_count: iv_names.count)
|
240
|
+
progress_bar << prog_task
|
241
|
+
|
242
|
+
iv_names.each do |iv_name|
|
243
|
+
iv_value = rescue_to_viewwrapped_error do
|
244
|
+
ro.value.instance_variable_get(iv_name)
|
245
|
+
end
|
246
|
+
iv_key = Scryglass::ViewWrapper.new(iv_name,
|
247
|
+
string: iv_name.to_s) # to_s removes ':'
|
248
|
+
ro.sub_ros << roify(iv_value,
|
249
|
+
parent_ro: ro,
|
250
|
+
key: iv_key,
|
251
|
+
key_value_relationship_indicator: ' : ',
|
252
|
+
special_sub_ro_type: :iv,
|
253
|
+
depth: ro.depth + 1)
|
254
|
+
prog_task.tick
|
255
|
+
print_progress_bar
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def build_ar_sub_ros_for(ro)
|
260
|
+
return if ro.ar_sub_ros.any?
|
261
|
+
return unless ro.value.class.respond_to?(:reflections)
|
262
|
+
|
263
|
+
reflections = ro.value.class.reflections
|
264
|
+
|
265
|
+
include_empty_associations =
|
266
|
+
Scryglass.config.include_empty_associations
|
267
|
+
include_through_associations =
|
268
|
+
Scryglass.config.include_through_associations
|
269
|
+
include_scoped_associations =
|
270
|
+
Scryglass.config.include_scoped_associations
|
271
|
+
show_association_types =
|
272
|
+
Scryglass.config.show_association_types
|
273
|
+
|
274
|
+
through_filter = lambda do |info|
|
275
|
+
include_through_associations || !info.options[:through]
|
276
|
+
end
|
277
|
+
|
278
|
+
scope_filter = lambda do |info|
|
279
|
+
include_scoped_associations || !info.scope
|
280
|
+
# This... `info.scope`
|
281
|
+
# is to get rid of extraneous custom scoped associations
|
282
|
+
# like current_primary_phone_number_record.
|
283
|
+
end
|
284
|
+
|
285
|
+
direct_close_reflections = reflections.select do |_, info|
|
286
|
+
through_filter.call(info) &&
|
287
|
+
scope_filter.call(info)
|
288
|
+
end
|
289
|
+
|
290
|
+
relation_names = direct_close_reflections.keys
|
291
|
+
return if relation_names.empty?
|
292
|
+
|
293
|
+
task = Prog::Task.new(max_count: relation_names.count)
|
294
|
+
progress_bar << task
|
295
|
+
|
296
|
+
direct_close_reflections.sort_by { |relation_name, _info| relation_name }
|
297
|
+
.each do |relation_name, info|
|
298
|
+
ar_value = Hexes.hide_db_outputs do
|
299
|
+
rescue_to_viewwrapped_error do
|
300
|
+
ro.value.send(relation_name)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
relationship_type = info.macro.to_s.split('_')
|
305
|
+
.map { |s| s[0].upcase }.join('')
|
306
|
+
relationship_type = "(#{relationship_type})"
|
307
|
+
|
308
|
+
is_through = info.options.keys.include?(:through)
|
309
|
+
if include_through_associations
|
310
|
+
through_indicator = is_through ? '(t)' : ' '
|
311
|
+
end
|
312
|
+
|
313
|
+
is_scoped = info.scope.present?
|
314
|
+
if include_scoped_associations
|
315
|
+
scoped_indicator = is_scoped ? '(s)' : ' '
|
316
|
+
end
|
317
|
+
|
318
|
+
relation_representation =
|
319
|
+
if show_association_types
|
320
|
+
"#{relationship_type}#{through_indicator}#{scoped_indicator} #{relation_name}"
|
321
|
+
else
|
322
|
+
relation_name.to_s
|
323
|
+
end
|
324
|
+
|
325
|
+
if ar_value.present? || include_empty_associations
|
326
|
+
ar_key = Scryglass::ViewWrapper.new(
|
327
|
+
relation_name,
|
328
|
+
string: relation_representation
|
329
|
+
)
|
330
|
+
ro.sub_ros << roify(ar_value,
|
331
|
+
parent_ro: ro,
|
332
|
+
key: ar_key,
|
333
|
+
key_value_relationship_indicator: ': ',
|
334
|
+
special_sub_ro_type: :ar,
|
335
|
+
depth: ro.depth + 1)
|
336
|
+
end
|
337
|
+
|
338
|
+
task.tick
|
339
|
+
print_progress_bar
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
def build_enum_children_for(ro)
|
344
|
+
return if ro.enum_sub_ros.any?
|
345
|
+
return if ro.bucket?
|
346
|
+
return unless ro.value.is_a?(Enumerable)
|
347
|
+
|
348
|
+
pretend_klass = if ro.value.respond_to?(:keys)
|
349
|
+
Hash
|
350
|
+
elsif ro.value.respond_to?(:each)
|
351
|
+
Array
|
352
|
+
end
|
353
|
+
|
354
|
+
if pretend_klass == Hash
|
355
|
+
key_names = ro.value.keys
|
356
|
+
return if key_names.empty?
|
357
|
+
|
358
|
+
prog_task = Prog::Task.new(max_count: key_names.count)
|
359
|
+
progress_bar << prog_task
|
360
|
+
|
361
|
+
ro.value.each do |key, value|
|
362
|
+
ro.sub_ros << roify(value,
|
363
|
+
parent_ro: ro,
|
364
|
+
key: key,
|
365
|
+
key_value_relationship_indicator: ' => ',
|
366
|
+
special_sub_ro_type: :enum,
|
367
|
+
depth: ro.depth + 1)
|
368
|
+
prog_task.tick
|
369
|
+
print_progress_bar
|
370
|
+
end
|
371
|
+
elsif pretend_klass == Array
|
372
|
+
return if ro.value.count.zero?
|
373
|
+
|
374
|
+
prog_task = Prog::Task.new(max_count: ro.value.count)
|
375
|
+
progress_bar << prog_task
|
376
|
+
|
377
|
+
ro.value.each do |value|
|
378
|
+
ro.sub_ros << roify(value,
|
379
|
+
parent_ro: ro,
|
380
|
+
special_sub_ro_type: :enum,
|
381
|
+
depth: ro.depth + 1)
|
382
|
+
prog_task.tick
|
383
|
+
print_progress_bar
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
def rescue_to_viewwrapped_error
|
389
|
+
begin
|
390
|
+
successful_yielded_return = yield
|
391
|
+
rescue => e
|
392
|
+
legible_error_string = [e.message, *e.backtrace].join("\n")
|
393
|
+
viewwrapped_error = Scryglass::ViewWrapper.new(
|
394
|
+
legible_error_string,
|
395
|
+
string: '«ERROR»'
|
396
|
+
)
|
397
|
+
ensure
|
398
|
+
viewwrapped_error || successful_yielded_return
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
@@ -0,0 +1,514 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Scryglass::Session
|
4
|
+
include Scryglass::RoBuilder
|
5
|
+
|
6
|
+
using AnsilessStringRefinement
|
7
|
+
|
8
|
+
attr_accessor :all_ros, :current_ro, :special_command_targets
|
9
|
+
|
10
|
+
attr_accessor :current_view_coords, :current_lens, :current_subject_type,
|
11
|
+
:view_panels, :current_panel_type,
|
12
|
+
:progress_bar
|
13
|
+
|
14
|
+
attr_accessor :user_input, :last_search, :number_to_move
|
15
|
+
|
16
|
+
CURSOR_CHARACTER = '–' # These are en dashes (alt+dash), not hyphens or em dashes.
|
17
|
+
|
18
|
+
SEARCH_PROMPT = "\e[7mSearch for (regex, case-sensitive): /\e[00m"
|
19
|
+
|
20
|
+
SESSION_CLOSED_MESSAGE = '(Exited scry! Resume session with `scry` or `scry_resume`)'
|
21
|
+
|
22
|
+
SUBJECT_TYPES = [
|
23
|
+
:value,
|
24
|
+
:key
|
25
|
+
].freeze
|
26
|
+
|
27
|
+
CSI = "\e[" # "(C)ontrol (S)equence (I)ntroducer" for ANSI sequences
|
28
|
+
|
29
|
+
def initialize(seed)
|
30
|
+
self.all_ros = []
|
31
|
+
self.current_lens = 0
|
32
|
+
self.current_subject_type = :value
|
33
|
+
self.current_panel_type = :tree
|
34
|
+
self.special_command_targets = []
|
35
|
+
self.number_to_move = ''
|
36
|
+
self.user_input = nil
|
37
|
+
self.progress_bar = Prog::Pipe.new
|
38
|
+
|
39
|
+
top_ro = roify(seed, parent_ro: nil, depth: 1)
|
40
|
+
top_ro.has_cursor = true
|
41
|
+
self.current_ro = top_ro
|
42
|
+
|
43
|
+
expand!(top_ro)
|
44
|
+
|
45
|
+
self.view_panels = {
|
46
|
+
tree: Scryglass::TreePanel.new(scry_session: self),
|
47
|
+
lens: Scryglass::LensPanel.new(scry_session: self),
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def run_scry_ui(actions:)
|
52
|
+
in_scry_session = true
|
53
|
+
redraw = true
|
54
|
+
|
55
|
+
case actions
|
56
|
+
when :record
|
57
|
+
$scry_session_actions_performed = []
|
58
|
+
when :playback
|
59
|
+
if $scry_session_actions_performed.blank?
|
60
|
+
raise 'Could not find recording of previous session\'s actions'
|
61
|
+
end
|
62
|
+
@input_stack = $scry_session_actions_performed.dup
|
63
|
+
end
|
64
|
+
|
65
|
+
# We print a full screen of lines so the first call of draw_screen doesn't
|
66
|
+
# write over any previous valuable content the user had in the console.
|
67
|
+
print Hexes.opacify_screen_string(Hexes.simple_screen_slice(boot_screen))
|
68
|
+
|
69
|
+
while in_scry_session
|
70
|
+
draw_screen if redraw
|
71
|
+
redraw = true
|
72
|
+
|
73
|
+
case actions
|
74
|
+
when :record
|
75
|
+
self.user_input = $stdin.getch
|
76
|
+
$scry_session_actions_performed << user_input
|
77
|
+
when :playback
|
78
|
+
if @input_stack.any? # (IV to be easily accessible for debugging)
|
79
|
+
self.user_input = @input_stack.shift
|
80
|
+
sleep 0.05
|
81
|
+
else
|
82
|
+
self.user_input = $stdin.getch
|
83
|
+
end
|
84
|
+
else
|
85
|
+
self.user_input = $stdin.getch
|
86
|
+
end
|
87
|
+
|
88
|
+
wait_start_time = Time.now
|
89
|
+
|
90
|
+
case user_input
|
91
|
+
when "\u0003"
|
92
|
+
set_console_cursor_below_content
|
93
|
+
raise IRB::Abort, 'Ctrl+C Detected'
|
94
|
+
when 'q'
|
95
|
+
in_scry_session = false
|
96
|
+
visually_close_ui
|
97
|
+
when '1'
|
98
|
+
self.number_to_move += '1'
|
99
|
+
redraw = false # This allows you to type multi-digit number very
|
100
|
+
# quickly and still have it process all the digits.
|
101
|
+
when '2'
|
102
|
+
self.number_to_move += '2'
|
103
|
+
redraw = false
|
104
|
+
when '3'
|
105
|
+
self.number_to_move += '3'
|
106
|
+
redraw = false
|
107
|
+
when '4'
|
108
|
+
self.number_to_move += '4'
|
109
|
+
redraw = false
|
110
|
+
when '5'
|
111
|
+
self.number_to_move += '5'
|
112
|
+
redraw = false
|
113
|
+
when '6'
|
114
|
+
self.number_to_move += '6'
|
115
|
+
redraw = false
|
116
|
+
when '7'
|
117
|
+
self.number_to_move += '7'
|
118
|
+
redraw = false
|
119
|
+
when '8'
|
120
|
+
self.number_to_move += '8'
|
121
|
+
redraw = false
|
122
|
+
when '9'
|
123
|
+
self.number_to_move += '9'
|
124
|
+
redraw = false
|
125
|
+
when '0'
|
126
|
+
if number_to_move.present? # You can append zeros to number_to_move...
|
127
|
+
self.number_to_move += '0'
|
128
|
+
redraw = false
|
129
|
+
else # ...but otherwise it's understood to be a view||cursor reset.
|
130
|
+
reset_the_view_or_cursor
|
131
|
+
end
|
132
|
+
when 'A' # Up arrow
|
133
|
+
action_count = number_to_move.present? ? number_to_move.to_i : 1
|
134
|
+
navigate_up_multiple(action_count)
|
135
|
+
|
136
|
+
self.number_to_move = ''
|
137
|
+
lens_view.recalculate_boundaries if current_panel_type == :lens
|
138
|
+
tree_view.slide_view_to_cursor
|
139
|
+
when 'B' # Down arrow
|
140
|
+
action_count = number_to_move.present? ? number_to_move.to_i : 1
|
141
|
+
navigate_down_multiple(action_count)
|
142
|
+
|
143
|
+
self.number_to_move = ''
|
144
|
+
lens_view.recalculate_boundaries if current_panel_type == :lens
|
145
|
+
tree_view.slide_view_to_cursor
|
146
|
+
when 'C' # Right arrow
|
147
|
+
expand_targets
|
148
|
+
when 'D' # Left arrow
|
149
|
+
collapse_targets
|
150
|
+
lens_view.recalculate_boundaries if current_panel_type == :lens
|
151
|
+
when ' '
|
152
|
+
toggle_view_panel
|
153
|
+
lens_view.recalculate_boundaries if current_panel_type == :lens
|
154
|
+
when 'l'
|
155
|
+
scroll_lens_type
|
156
|
+
lens_view.recalculate_boundaries if current_panel_type == :lens
|
157
|
+
when 'L'
|
158
|
+
toggle_current_subject_type
|
159
|
+
lens_view.recalculate_boundaries if current_panel_type == :lens
|
160
|
+
when 'w'
|
161
|
+
current_view_panel.move_view_up(5)
|
162
|
+
when 's'
|
163
|
+
current_view_panel.move_view_down(5)
|
164
|
+
when 'a'
|
165
|
+
current_view_panel.move_view_left(5)
|
166
|
+
when 'd'
|
167
|
+
current_view_panel.move_view_right(5)
|
168
|
+
when '∑' # Alt+w
|
169
|
+
current_view_panel.move_view_up(50)
|
170
|
+
when 'ß' # Alt+s
|
171
|
+
current_view_panel.move_view_down(50)
|
172
|
+
when 'å' # Alt+a
|
173
|
+
current_view_panel.move_view_left(50)
|
174
|
+
when '∂' # Alt+d
|
175
|
+
current_view_panel.move_view_right(50)
|
176
|
+
when '?'
|
177
|
+
in_scry_session = run_help_screen_ui
|
178
|
+
when '@'
|
179
|
+
build_instance_variables_for_target_ros
|
180
|
+
tree_view.recalculate_boundaries
|
181
|
+
tree_view.slide_view_to_cursor # Just a nice-to-have
|
182
|
+
when '.'
|
183
|
+
build_activerecord_relations_for_target_ros
|
184
|
+
tree_view.recalculate_boundaries
|
185
|
+
tree_view.slide_view_to_cursor # Just a nice-to-have
|
186
|
+
when '('
|
187
|
+
build_enum_children_for_target_ros
|
188
|
+
tree_view.recalculate_boundaries
|
189
|
+
tree_view.slide_view_to_cursor # Just a nice-to-have
|
190
|
+
when '|'
|
191
|
+
sibling_ros = if current_ro.top_ro?
|
192
|
+
[top_ro]
|
193
|
+
else
|
194
|
+
current_ro.parent_ro.sub_ros.dup # If we don't dup,
|
195
|
+
# then '-' can remove ros from `sub_ros`.
|
196
|
+
end
|
197
|
+
if special_command_targets.sort == sibling_ros.sort
|
198
|
+
self.special_command_targets = []
|
199
|
+
else
|
200
|
+
self.special_command_targets = sibling_ros
|
201
|
+
end
|
202
|
+
when '*'
|
203
|
+
all_the_ros = all_ros.dup # If we don't dup,
|
204
|
+
# then '-' can remove ros from all_ros.
|
205
|
+
if special_command_targets.sort == all_the_ros.sort
|
206
|
+
self.special_command_targets = []
|
207
|
+
else
|
208
|
+
self.special_command_targets = all_the_ros
|
209
|
+
end
|
210
|
+
when '-'
|
211
|
+
if special_command_targets.include?(current_ro)
|
212
|
+
special_command_targets.delete(current_ro)
|
213
|
+
else
|
214
|
+
special_command_targets << current_ro
|
215
|
+
end
|
216
|
+
when '/'
|
217
|
+
$stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner)
|
218
|
+
$stdout.write SEARCH_PROMPT
|
219
|
+
$stdout.write "#{CSI}1;#{SEARCH_PROMPT.ansiless_length + 1}H" # (Moves
|
220
|
+
# console cursor to just after the search prompt, before user types)
|
221
|
+
query = $stdin.gets.chomp
|
222
|
+
if query.present?
|
223
|
+
self.last_search = query
|
224
|
+
go_to_next_search_result
|
225
|
+
end
|
226
|
+
when 'n'
|
227
|
+
if last_search
|
228
|
+
go_to_next_search_result
|
229
|
+
else
|
230
|
+
$stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner)
|
231
|
+
$stdout.write "\e[7m-- No Search has been entered --\e[00m"
|
232
|
+
sleep 2
|
233
|
+
end
|
234
|
+
when "\r" # [ENTER]
|
235
|
+
visually_close_ui
|
236
|
+
return subjects_of_target_ros
|
237
|
+
end
|
238
|
+
|
239
|
+
print "\a" if Time.now - wait_start_time > 4 && user_input != '?' # (Audio 'beep')
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def top_ro
|
244
|
+
all_ros.first
|
245
|
+
end
|
246
|
+
|
247
|
+
private
|
248
|
+
|
249
|
+
def print_progress_bar
|
250
|
+
screen_height, _screen_width = $stdout.winsize
|
251
|
+
bar = progress_bar.to_s
|
252
|
+
$stdout.write "#{CSI}#{screen_height};1H" # (Moves console cursor to bottom left corner)
|
253
|
+
print bar if bar.present?
|
254
|
+
end
|
255
|
+
|
256
|
+
def current_view_panel
|
257
|
+
view_panels[current_panel_type]
|
258
|
+
end
|
259
|
+
|
260
|
+
def tree_view
|
261
|
+
view_panels[:tree]
|
262
|
+
end
|
263
|
+
|
264
|
+
def lens_view
|
265
|
+
view_panels[:lens]
|
266
|
+
end
|
267
|
+
|
268
|
+
def colorize(screen_string)
|
269
|
+
dot = '•'
|
270
|
+
cyan_dot = "\e[36m#{dot}\e[00m" # cyan then back to *default*
|
271
|
+
screen_string.gsub!('•', cyan_dot)
|
272
|
+
|
273
|
+
screen_string
|
274
|
+
end
|
275
|
+
|
276
|
+
def display_active_searching_indicator
|
277
|
+
$stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner)
|
278
|
+
message = ' Searching... '
|
279
|
+
pad = SEARCH_PROMPT.length - message.length
|
280
|
+
wing = '-' * (pad / 2)
|
281
|
+
|
282
|
+
$stdout.write "\e[7m#{wing + message + wing}\e[00m"
|
283
|
+
end
|
284
|
+
|
285
|
+
def go_to_next_search_result
|
286
|
+
display_active_searching_indicator
|
287
|
+
|
288
|
+
cut_point = current_ro.index
|
289
|
+
search_set = ((cut_point + 1)...all_ros.count).to_a + (0...cut_point).to_a
|
290
|
+
|
291
|
+
task = Prog::Task.new(max_count: search_set.count)
|
292
|
+
progress_bar << task
|
293
|
+
|
294
|
+
index_of_next_match = search_set.find do |index|
|
295
|
+
scanned_ro = all_ros[index]
|
296
|
+
task.tick
|
297
|
+
print_progress_bar
|
298
|
+
scanned_ro.key_string =~ /#{last_search}/ ||
|
299
|
+
(scanned_ro.nugget? && scanned_ro.value_string =~ /#{last_search}/)
|
300
|
+
end
|
301
|
+
task.force_finish
|
302
|
+
|
303
|
+
if index_of_next_match
|
304
|
+
next_found_ro = all_ros[index_of_next_match]
|
305
|
+
move_cursor_to(next_found_ro)
|
306
|
+
|
307
|
+
scanning_ro = next_found_ro
|
308
|
+
while scanning_ro.parent_ro
|
309
|
+
expand!(scanning_ro.parent_ro)
|
310
|
+
scanning_ro = scanning_ro.parent_ro
|
311
|
+
end
|
312
|
+
|
313
|
+
tree_view.recalculate_boundaries # Yes, necessary :)
|
314
|
+
lens_view.recalculate_boundaries # Yes, necessary :)
|
315
|
+
tree_view.current_view_coords = { y: 0, x: 0 }
|
316
|
+
tree_view.slide_view_to_cursor
|
317
|
+
else
|
318
|
+
$stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner)
|
319
|
+
message = ' No Match Found '
|
320
|
+
pad = SEARCH_PROMPT.length - message.length
|
321
|
+
wing = '-' * (pad / 2)
|
322
|
+
|
323
|
+
$stdout.write "\e[7m#{wing + message + wing}\e[00m"
|
324
|
+
sleep 2
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
def run_help_screen_ui
|
329
|
+
screen_height, _screen_width = $stdout.winsize
|
330
|
+
|
331
|
+
in_help_screen = true
|
332
|
+
current_help_screen_index = 0
|
333
|
+
help_screens = [Scryglass::HELP_SCREEN, Scryglass::HELP_SCREEN_ADVANCED]
|
334
|
+
|
335
|
+
while in_help_screen
|
336
|
+
current_help_screen = help_screens[current_help_screen_index]
|
337
|
+
sliced_help_screen = Hexes.simple_screen_slice(current_help_screen)
|
338
|
+
help_screen_string = Hexes.opacify_screen_string(sliced_help_screen)
|
339
|
+
Hexes.overwrite_screen(help_screen_string)
|
340
|
+
help_screen_user_input = $stdin.getch
|
341
|
+
|
342
|
+
case help_screen_user_input
|
343
|
+
when '?'
|
344
|
+
current_help_screen_index += 1
|
345
|
+
when 'q'
|
346
|
+
$stdout.write "#{CSI}#{screen_height};1H" # (Moves console cursor to
|
347
|
+
# bottom left corner). This helps 'q' not print the console prompt at
|
348
|
+
# the top of the screen, overlapping with the old display.
|
349
|
+
return false
|
350
|
+
when "\u0003"
|
351
|
+
screen_height, _screen_width = $stdout.winsize
|
352
|
+
puts "\n" * screen_height
|
353
|
+
raise IRB::Abort, 'Ctrl+C Detected'
|
354
|
+
end
|
355
|
+
|
356
|
+
current_help_screen = help_screens[current_help_screen_index]
|
357
|
+
unless current_help_screen
|
358
|
+
return true
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
def collapse_targets
|
364
|
+
if special_command_targets.any?
|
365
|
+
target_ros = special_command_targets.dup # dup because some commands
|
366
|
+
# create ros which are added to all_ros and then this process starts
|
367
|
+
# adding them to the list of things it tries to act on!
|
368
|
+
target_ros.each { |target_ro| collapse!(target_ro) }
|
369
|
+
self.special_command_targets = []
|
370
|
+
elsif current_ro.expanded
|
371
|
+
collapse!(current_ro)
|
372
|
+
elsif current_ro.parent_ro
|
373
|
+
collapse!(current_ro.parent_ro)
|
374
|
+
end
|
375
|
+
|
376
|
+
move_cursor_to(current_ro.parent_ro) until current_ro.visible?
|
377
|
+
tree_view.slide_view_to_cursor
|
378
|
+
tree_view.recalculate_boundaries # TODO: should these be conditional? If they are, I might need a potential tree view recalc after toggling lens view to tree view.
|
379
|
+
end
|
380
|
+
|
381
|
+
def expand_targets
|
382
|
+
if special_command_targets.any?
|
383
|
+
target_ros = special_command_targets.dup # dup because some commands
|
384
|
+
# create ros which are added to all_ros and then this process starts
|
385
|
+
# adding them to the list of things it tries to act on!
|
386
|
+
target_ros.each { |target_ro| expand!(target_ro) }
|
387
|
+
self.special_command_targets = []
|
388
|
+
else
|
389
|
+
expand!(current_ro)
|
390
|
+
end
|
391
|
+
tree_view.recalculate_boundaries
|
392
|
+
end
|
393
|
+
|
394
|
+
def reset_the_view_or_cursor
|
395
|
+
if current_view_panel.current_view_coords != { x: 0, y: 0 }
|
396
|
+
current_view_panel.current_view_coords = { x: 0, y: 0 }
|
397
|
+
elsif current_panel_type == :tree
|
398
|
+
move_cursor_to(top_ro)
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
def draw_screen
|
403
|
+
current_view_panel.ensure_correct_view_coords
|
404
|
+
screen_string = current_view_panel.screen_string
|
405
|
+
|
406
|
+
screen_string = colorize(screen_string) if Scryglass.config.dot_coloring
|
407
|
+
Hexes.overwrite_screen(screen_string)
|
408
|
+
$stdout.write "#{CSI}1;1H" # Moves terminal cursor to top left corner,
|
409
|
+
# mostly for consistency.
|
410
|
+
end
|
411
|
+
|
412
|
+
def set_console_cursor_below_content
|
413
|
+
bare_screen_string =
|
414
|
+
current_view_panel.visible_header_string + "\n" +
|
415
|
+
current_view_panel.visible_body_string
|
416
|
+
split_lines = bare_screen_string.split("\n")
|
417
|
+
rows_filled = split_lines.count
|
418
|
+
$stdout.write "#{CSI}#{rows_filled};1H\n" # Moves console cursor to bottom
|
419
|
+
# of *content*, then one more.
|
420
|
+
end
|
421
|
+
|
422
|
+
def visually_close_ui
|
423
|
+
_screen_height, screen_width = $stdout.winsize
|
424
|
+
set_console_cursor_below_content
|
425
|
+
puts '·' * screen_width, "\n"
|
426
|
+
puts SESSION_CLOSED_MESSAGE
|
427
|
+
end
|
428
|
+
|
429
|
+
def subjects_of_target_ros
|
430
|
+
if special_command_targets.any?
|
431
|
+
return_targets = special_command_targets
|
432
|
+
self.special_command_targets = []
|
433
|
+
return return_targets.map(&:current_subject)
|
434
|
+
end
|
435
|
+
|
436
|
+
current_ro.current_subject
|
437
|
+
end
|
438
|
+
|
439
|
+
def navigate_up_multiple(action_count)
|
440
|
+
task = Prog::Task.new(max_count: action_count)
|
441
|
+
progress_bar << task
|
442
|
+
action_count.times do
|
443
|
+
navigate_up
|
444
|
+
task.tick
|
445
|
+
print_progress_bar
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
def navigate_down_multiple(action_count)
|
450
|
+
task = Prog::Task.new(max_count: action_count)
|
451
|
+
progress_bar << task
|
452
|
+
action_count.times do
|
453
|
+
navigate_down
|
454
|
+
task.tick
|
455
|
+
print_progress_bar
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
def expand!(ro)
|
460
|
+
ro.expanded = true if ro.sub_ros.any?
|
461
|
+
end
|
462
|
+
|
463
|
+
def collapse!(ro)
|
464
|
+
ro.expanded = false if ro.expanded
|
465
|
+
end
|
466
|
+
|
467
|
+
def toggle_view_panel
|
468
|
+
self.current_panel_type =
|
469
|
+
case current_panel_type
|
470
|
+
when :tree
|
471
|
+
:lens
|
472
|
+
when :lens
|
473
|
+
:tree
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
def toggle_current_subject_type
|
478
|
+
self.current_subject_type =
|
479
|
+
case current_subject_type
|
480
|
+
when :value
|
481
|
+
:key
|
482
|
+
when :key
|
483
|
+
:value
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
def scroll_lens_type
|
488
|
+
self.current_lens += 1
|
489
|
+
end
|
490
|
+
|
491
|
+
def move_cursor_to(new_ro)
|
492
|
+
current_ro.has_cursor = false
|
493
|
+
new_ro.has_cursor = true
|
494
|
+
self.current_ro = new_ro
|
495
|
+
end
|
496
|
+
|
497
|
+
def navigate_up
|
498
|
+
next_up = current_ro.next_visible_ro_up
|
499
|
+
move_cursor_to(next_up) if next_up
|
500
|
+
end
|
501
|
+
|
502
|
+
def navigate_down
|
503
|
+
next_down = current_ro.next_visible_ro_down
|
504
|
+
move_cursor_to(next_down) if next_down
|
505
|
+
end
|
506
|
+
|
507
|
+
def boot_screen
|
508
|
+
screen_height, screen_width = $stdout.winsize
|
509
|
+
stars = (1..(screen_height * screen_width))
|
510
|
+
.to_a
|
511
|
+
.map { rand(100).zero? ? '.' : ' ' }
|
512
|
+
stars.each_slice(screen_width).map { |set| set.join('') }.join("\n")
|
513
|
+
end
|
514
|
+
end
|