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
 
    
        data/lib/hexes.rb
    ADDED
    
    | 
         @@ -0,0 +1,135 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            ## Hexes takes care of some console/view/IO work for Scryglass
         
     | 
| 
      
 4 
     | 
    
         
            +
            module Hexes
         
     | 
| 
      
 5 
     | 
    
         
            +
              using ClipStringRefinement
         
     | 
| 
      
 6 
     | 
    
         
            +
              using AnsilessStringRefinement
         
     | 
| 
      
 7 
     | 
    
         
            +
              using ConstantDefinedStringRefinement
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              def self.simple_screen_slice(screen_string)
         
     | 
| 
      
 10 
     | 
    
         
            +
                screen_height, screen_width = $stdout.winsize
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                split_lines = screen_string.split("\n")
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                ## Here we cut down the (rectangular if opacified) display array in both
         
     | 
| 
      
 15 
     | 
    
         
            +
                ##   dimensions (into a smaller rectangle), as needed, to fit the view.
         
     | 
| 
      
 16 
     | 
    
         
            +
                sliced_lines = split_lines.map do |string|
         
     | 
| 
      
 17 
     | 
    
         
            +
                  ansi_length = string.length - string.ansiless_length
         
     | 
| 
      
 18 
     | 
    
         
            +
                  slice_length = screen_width + ansi_length
         
     | 
| 
      
 19 
     | 
    
         
            +
                  string[0, slice_length]
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
                sliced_list = sliced_lines[0, screen_height]
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                sliced_list.join("\n")
         
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
              def self.opacify_screen_string(screen_string)
         
     | 
| 
      
 27 
     | 
    
         
            +
                screen_height, screen_width = $stdout.winsize
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                split_lines = screen_string.split("\n")
         
     | 
| 
      
 30 
     | 
    
         
            +
                rows_filled = split_lines.count
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                blank_rows_at_bottom = [screen_height - rows_filled, 0].max
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                # This takes all the unfilled spaces left after a newline, and makes them
         
     | 
| 
      
 35 
     | 
    
         
            +
                #   real spaces, so they'll overwrite whatever was there a second ago. Thus
         
     | 
| 
      
 36 
     | 
    
         
            +
                #   I don't have to worry about clearing the screen all the time, which was
         
     | 
| 
      
 37 
     | 
    
         
            +
                #   seemingly causing console chaff and some flickering.
         
     | 
| 
      
 38 
     | 
    
         
            +
                side_filled_string = split_lines.map do |line|
         
     | 
| 
      
 39 
     | 
    
         
            +
                  margin_to_fill = screen_width - line.ansiless.length
         
     | 
| 
      
 40 
     | 
    
         
            +
                  line + (' ' * margin_to_fill)
         
     | 
| 
      
 41 
     | 
    
         
            +
                end.join("\e[00m\n") # Also turns off ANSI text formatting at the end of
         
     | 
| 
      
 42 
     | 
    
         
            +
                #   each line, in case a formatted string had its "turn off formatting" code
         
     | 
| 
      
 43 
     | 
    
         
            +
                #   cut off from the end. (Reducing the need to end with one at all).
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                blank_line = "\n" + (' ' * screen_width)
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                side_filled_string + (blank_line * blank_rows_at_bottom)
         
     | 
| 
      
 48 
     | 
    
         
            +
              end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
              def self.stdout_rescue
         
     | 
| 
      
 51 
     | 
    
         
            +
                @preserved_stdout_dup = $stdout.dup
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                begin
         
     | 
| 
      
 54 
     | 
    
         
            +
                  yielded_return = yield
         
     | 
| 
      
 55 
     | 
    
         
            +
                rescue => e
         
     | 
| 
      
 56 
     | 
    
         
            +
                  # `e` is raised again in the `ensure` block after stdout is safely reset.
         
     | 
| 
      
 57 
     | 
    
         
            +
                ensure
         
     | 
| 
      
 58 
     | 
    
         
            +
                  $stdout = @preserved_stdout_dup
         
     | 
| 
      
 59 
     | 
    
         
            +
                  raise e if e
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                yielded_return
         
     | 
| 
      
 63 
     | 
    
         
            +
              end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
              def self.capture_io(char_limit: nil)
         
     | 
| 
      
 66 
     | 
    
         
            +
                stdout_rescue do # Ensures that $stdout is reset no matter what
         
     | 
| 
      
 67 
     | 
    
         
            +
                  temporary_io_channel = StringIO.new
         
     | 
| 
      
 68 
     | 
    
         
            +
                  $stdout = temporary_io_channel
         
     | 
| 
      
 69 
     | 
    
         
            +
                  Thread.abort_on_exception = true # So threads can return error text at all
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                  if char_limit
         
     | 
| 
      
 72 
     | 
    
         
            +
                    background_output_thread = Thread.new { yield } # It's assumed that the
         
     | 
| 
      
 73 
     | 
    
         
            +
                    #   yielded block will be printing something somewhat promptly.
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                    sleep 0.05 # Give it a head start (Sometimes makes a difference!)
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                    while temporary_io_channel.size < char_limit
         
     | 
| 
      
 78 
     | 
    
         
            +
                      io_size = temporary_io_channel.size
         
     | 
| 
      
 79 
     | 
    
         
            +
                      sleep 0.05
         
     | 
| 
      
 80 
     | 
    
         
            +
                      new_io_size = temporary_io_channel.size
         
     | 
| 
      
 81 
     | 
    
         
            +
                      break if new_io_size == io_size
         
     | 
| 
      
 82 
     | 
    
         
            +
                    end
         
     | 
| 
      
 83 
     | 
    
         
            +
                    background_output_thread.terminate
         
     | 
| 
      
 84 
     | 
    
         
            +
                  else
         
     | 
| 
      
 85 
     | 
    
         
            +
                    yield
         
     | 
| 
      
 86 
     | 
    
         
            +
                  end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                  temporary_io_channel.rewind
         
     | 
| 
      
 89 
     | 
    
         
            +
                  captured_output = temporary_io_channel.read
         
     | 
| 
      
 90 
     | 
    
         
            +
                  captured_output = captured_output.clip_at(char_limit) if char_limit
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                  captured_output
         
     | 
| 
      
 93 
     | 
    
         
            +
                end
         
     | 
| 
      
 94 
     | 
    
         
            +
              end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
              def self.overwrite_screen(screen_string)
         
     | 
| 
      
 97 
     | 
    
         
            +
                csi = "\e["
         
     | 
| 
      
 98 
     | 
    
         
            +
                $stdout.write "#{csi}s" # Saves terminal cursor position
         
     | 
| 
      
 99 
     | 
    
         
            +
                $stdout.write "#{csi}1;1H" # Moves terminal cursor to top left corner
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                $stdout.print "\r#{screen_string}"
         
     | 
| 
      
 102 
     | 
    
         
            +
                $stdout.write "#{csi}u" # Restores saved terminal cursor position
         
     | 
| 
      
 103 
     | 
    
         
            +
              end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
              def self.hide_db_outputs
         
     | 
| 
      
 106 
     | 
    
         
            +
                necessary_constants = ['Logger', 'ActiveRecord::Base']
         
     | 
| 
      
 107 
     | 
    
         
            +
                necessary_constants_defined = necessary_constants.all?(&:constant_defined?)
         
     | 
| 
      
 108 
     | 
    
         
            +
                return yield unless necessary_constants_defined
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
                rails_logger_defined = 'Rails'.constant_defined? && Rails.try(:logger).present?
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                ## These are purposefully preserved as global variables so retrieval, in
         
     | 
| 
      
 113 
     | 
    
         
            +
                ##   debugging or errored usage, is as easy as possible.
         
     | 
| 
      
 114 
     | 
    
         
            +
                $preserved_ar_base_logger = ActiveRecord::Base.logger.dup
         
     | 
| 
      
 115 
     | 
    
         
            +
                $preserved_rails_logger = Rails.logger.dup if rails_logger_defined
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                begin
         
     | 
| 
      
 118 
     | 
    
         
            +
                  ## Now we create an unused dump string to serve as the output
         
     | 
| 
      
 119 
     | 
    
         
            +
                  ignored_output = StringIO.new
         
     | 
| 
      
 120 
     | 
    
         
            +
                  ignored_log = Logger.new(ignored_output)
         
     | 
| 
      
 121 
     | 
    
         
            +
                  ActiveRecord::Base.logger = ignored_log
         
     | 
| 
      
 122 
     | 
    
         
            +
                  Rails.logger = ignored_log if rails_logger_defined
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                  yielded_return = yield
         
     | 
| 
      
 125 
     | 
    
         
            +
                rescue => e
         
     | 
| 
      
 126 
     | 
    
         
            +
                  # `e` is raised again in the `ensure` after displays are safely reset.
         
     | 
| 
      
 127 
     | 
    
         
            +
                ensure
         
     | 
| 
      
 128 
     | 
    
         
            +
                  ActiveRecord::Base.logger = $preserved_ar_base_logger
         
     | 
| 
      
 129 
     | 
    
         
            +
                  Rails.logger = $preserved_rails_logger if rails_logger_defined
         
     | 
| 
      
 130 
     | 
    
         
            +
                  raise e if e
         
     | 
| 
      
 131 
     | 
    
         
            +
                end
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
                yielded_return
         
     | 
| 
      
 134 
     | 
    
         
            +
              end
         
     | 
| 
      
 135 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/prog.rb
    ADDED
    
    | 
         @@ -0,0 +1,89 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            ## Prog is a simple progress bar for tracking one or more nested or simultaneous
         
     | 
| 
      
 4 
     | 
    
         
            +
            ##   processes. Tasks fit evenly and dynamically into a Pipe, which can then
         
     | 
| 
      
 5 
     | 
    
         
            +
            ##   be displayed at a chosen width.
         
     | 
| 
      
 6 
     | 
    
         
            +
            module Prog
         
     | 
| 
      
 7 
     | 
    
         
            +
              class Pipe
         
     | 
| 
      
 8 
     | 
    
         
            +
                attr_accessor :tasks
         
     | 
| 
      
 9 
     | 
    
         
            +
                attr_accessor :highest_count
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 12 
     | 
    
         
            +
                  self.tasks = []
         
     | 
| 
      
 13 
     | 
    
         
            +
                  self.highest_count = 0
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def to_s(length: $stdout.winsize[1])
         
     | 
| 
      
 17 
     | 
    
         
            +
                  return ' ' * length if tasks.count.zero?
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  unused_length = length
         
     | 
| 
      
 20 
     | 
    
         
            +
                  self.highest_count = [highest_count, tasks.count].max
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  ## Set up the first barrier
         
     | 
| 
      
 23 
     | 
    
         
            +
                  display_string = +'|'
         
     | 
| 
      
 24 
     | 
    
         
            +
                  unused_length -= 1
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  ## Get a first pass at equal task length
         
     | 
| 
      
 27 
     | 
    
         
            +
                  # first_pass_task_length = unused_length/working_tasks.count
         
     | 
| 
      
 28 
     | 
    
         
            +
                  first_pass_task_length = unused_length / highest_count
         
     | 
| 
      
 29 
     | 
    
         
            +
                  if first_pass_task_length < 2
         
     | 
| 
      
 30 
     | 
    
         
            +
                    raise "Prog::Pipe length (#{length}) too small to " \
         
     | 
| 
      
 31 
     | 
    
         
            +
                          "fit all tasks (#{tasks.count})"
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
                  tasks.each do |task|
         
     | 
| 
      
 34 
     | 
    
         
            +
                    task.working_length = first_pass_task_length
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  ## Distribute the remaining space evenly among the first n tasks
         
     | 
| 
      
 38 
     | 
    
         
            +
                  remaining_space = unused_length - (first_pass_task_length * highest_count)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  tasks[0...remaining_space].each { |task| task.working_length += 1 }
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  tasks.each do |task|
         
     | 
| 
      
 42 
     | 
    
         
            +
                    display_string << task.to_s
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
                  display_string.ljust(length, ' ')
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                def <<(task)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  tasks << task
         
     | 
| 
      
 49 
     | 
    
         
            +
                  task.pipe = self
         
     | 
| 
      
 50 
     | 
    
         
            +
                  task.force_finish if task.max_count.zero?
         
     | 
| 
      
 51 
     | 
    
         
            +
                end
         
     | 
| 
      
 52 
     | 
    
         
            +
              end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
              class Task
         
     | 
| 
      
 55 
     | 
    
         
            +
                attr_accessor :pipe, :max_count, :current_count
         
     | 
| 
      
 56 
     | 
    
         
            +
                attr_accessor :working_length
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                def initialize(max_count:)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  self.max_count = max_count
         
     | 
| 
      
 60 
     | 
    
         
            +
                  self.current_count = 0
         
     | 
| 
      
 61 
     | 
    
         
            +
                  self.working_length = nil # (Only set by Prog::Pipe)
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                def tick(number_of_ticks = 1)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  self.current_count += number_of_ticks
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                  force_finish if current_count >= max_count
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                def force_finish
         
     | 
| 
      
 71 
     | 
    
         
            +
                  pipe.tasks.delete(self)
         
     | 
| 
      
 72 
     | 
    
         
            +
                  pipe.highest_count = 0 if pipe.tasks.empty?
         
     | 
| 
      
 73 
     | 
    
         
            +
                end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 76 
     | 
    
         
            +
                  progress_ratio = current_count / max_count.to_f
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                  unused_length = working_length
         
     | 
| 
      
 79 
     | 
    
         
            +
                  display_string = +'|' # (not frozen)
         
     | 
| 
      
 80 
     | 
    
         
            +
                  unused_length -= 1
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                  filled_cells = (unused_length * progress_ratio).floor
         
     | 
| 
      
 83 
     | 
    
         
            +
                  fill_bar = ('=' * filled_cells).ljust(unused_length, ' ')
         
     | 
| 
      
 84 
     | 
    
         
            +
                  display_string.prepend(fill_bar)
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                  display_string
         
     | 
| 
      
 87 
     | 
    
         
            +
                end
         
     | 
| 
      
 88 
     | 
    
         
            +
              end
         
     | 
| 
      
 89 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,67 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module ArrayFitToRefinement
         
     | 
| 
      
 2 
     | 
    
         
            +
              refine Array do
         
     | 
| 
      
 3 
     | 
    
         
            +
                using ClipStringRefinement
         
     | 
| 
      
 4 
     | 
    
         
            +
                using AnsilessStringRefinement
         
     | 
| 
      
 5 
     | 
    
         
            +
                # Warning: Still not going to work nicely if a string ends in an ansi code!
         
     | 
| 
      
 6 
     | 
    
         
            +
                def fit_to(string_length_goal, fill: ' ', ignore_ansi_codes: true)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  string_array = self.map(&:to_s) # This also acts to dup
         
     | 
| 
      
 8 
     | 
    
         
            +
                  length_method = ignore_ansi_codes ? :ansiless_length : :length
         
     | 
| 
      
 9 
     | 
    
         
            +
                  length_result = string_array.join('').send(length_method)
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  if length_result > string_length_goal
         
     | 
| 
      
 13 
     | 
    
         
            +
                    string_array.compress_to(string_length_goal, ignore_ansi_codes: ignore_ansi_codes)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  elsif length_result < string_length_goal
         
     | 
| 
      
 15 
     | 
    
         
            +
                    string_array.expand_to(string_length_goal, ignore_ansi_codes: ignore_ansi_codes, fill: fill)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  else # If it joins to the right length already, we still want to return the expected number of strings.
         
     | 
| 
      
 17 
     | 
    
         
            +
                    spacers = [''] * (string_array.count - 1)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    string_array.zip(spacers).flatten.compact
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                # Warning: Still not going to work nicely if a string ends in an ansi code!
         
     | 
| 
      
 23 
     | 
    
         
            +
                def compress_to(string_length_goal, ignore_ansi_codes:)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  working_array = self.map(&:to_s)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  original_string_count = self.count
         
     | 
| 
      
 26 
     | 
    
         
            +
                  spacers = [''] * (original_string_count - 1)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  length_method = ignore_ansi_codes ? :ansiless_length : :length
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  ## Ensure the strings are short enough to fit:
         
     | 
| 
      
 30 
     | 
    
         
            +
                  slider = 0
         
     | 
| 
      
 31 
     | 
    
         
            +
                  while working_array.join('').send(length_method) > string_length_goal
         
     | 
| 
      
 32 
     | 
    
         
            +
                    longest_string_length = working_array.map { |s| s.send(length_method) }.max
         
     | 
| 
      
 33 
     | 
    
         
            +
                    slider_index = slider % working_array.count
         
     | 
| 
      
 34 
     | 
    
         
            +
                    if working_array[slider_index].send(length_method) >= longest_string_length
         
     | 
| 
      
 35 
     | 
    
         
            +
                      working_array[slider_index] =
         
     | 
| 
      
 36 
     | 
    
         
            +
                        working_array[slider_index].clip_at(working_array[slider_index].send(length_method) - 1,
         
     | 
| 
      
 37 
     | 
    
         
            +
                                                            ignore_ansi_codes: ignore_ansi_codes)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    end
         
     | 
| 
      
 39 
     | 
    
         
            +
                    slider += 1
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  working_array.zip(spacers).flatten.compact
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def expand_to(string_length_goal, ignore_ansi_codes:, fill:)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  original_string_count = self.count
         
     | 
| 
      
 47 
     | 
    
         
            +
                  working_array = self.map(&:to_s)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  spacers = [''] * (original_string_count - 1)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  length_method = ignore_ansi_codes ? :ansiless_length : :length
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  ## Ensure the spacers are large enough to fill out to string_length_goal
         
     | 
| 
      
 52 
     | 
    
         
            +
                  space_to_fill = string_length_goal - working_array.join('').send(length_method)
         
     | 
| 
      
 53 
     | 
    
         
            +
                  first_pass_spacer_length = space_to_fill / spacers.count
         
     | 
| 
      
 54 
     | 
    
         
            +
                  spacers.map! { fill * first_pass_spacer_length }
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                  ## Distribute the remaining space evenly among the last n spacers
         
     | 
| 
      
 57 
     | 
    
         
            +
                  remaining_space = space_to_fill - spacers.join('').send(length_method)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  if remaining_space.positive?
         
     | 
| 
      
 59 
     | 
    
         
            +
                    spacers =
         
     | 
| 
      
 60 
     | 
    
         
            +
                      spacers[0...-remaining_space] +
         
     | 
| 
      
 61 
     | 
    
         
            +
                      spacers[-remaining_space..-1].map! { |spacer| spacer + ' ' } #each { |task| task.working_length += 1 }
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                  working_array.zip(spacers).flatten.compact
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
              end
         
     | 
| 
      
 67 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module ClipStringRefinement
         
     | 
| 
      
 2 
     | 
    
         
            +
              refine String do
         
     | 
| 
      
 3 
     | 
    
         
            +
                using AnsilessStringRefinement
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                # Warning: Still not going to work nicely if a string ends in an ansi code!
         
     | 
| 
      
 6 
     | 
    
         
            +
                def clip_at(clip_length, ignore_ansi_codes: false)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  length_method = ignore_ansi_codes ? :ansiless_length : :length
         
     | 
| 
      
 8 
     | 
    
         
            +
                  original_length = send(length_method)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  ansi_length = ignore_ansi_codes ? length - ansiless_length : 0
         
     | 
| 
      
 10 
     | 
    
         
            +
                  slice_length = clip_length + ansi_length
         
     | 
| 
      
 11 
     | 
    
         
            +
                  clipped_string = self[0...slice_length]
         
     | 
| 
      
 12 
     | 
    
         
            +
                  if clipped_string.send(length_method) < original_length
         
     | 
| 
      
 13 
     | 
    
         
            +
                    clipped_string = clipped_string.mark_as_abbreviated
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  clipped_string
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                # Warning: Still not going to work nicely if a string ends in an ansi code!
         
     | 
| 
      
 20 
     | 
    
         
            +
                def mark_as_abbreviated
         
     | 
| 
      
 21 
     | 
    
         
            +
                  self_dup = dup
         
     | 
| 
      
 22 
     | 
    
         
            +
                  self_dup[-1] = '…' if self_dup[-1]
         
     | 
| 
      
 23 
     | 
    
         
            +
                  self_dup[-2] = '…' if self_dup[-2]
         
     | 
| 
      
 24 
     | 
    
         
            +
                  self_dup
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/scryglass.rb
    ADDED
    
    | 
         @@ -0,0 +1,177 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            ## Bookkeeping and external tools:
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "scryglass/version"
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'active_support/core_ext/object/blank' # This gives us `.present?` and `.blank?` # https://stackoverflow.com/questions/4648684/how-to-use-present-in-ruby-projects
         
     | 
| 
      
 7 
     | 
    
         
            +
            require 'io/console'
         
     | 
| 
      
 8 
     | 
    
         
            +
            require 'pp'
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            ## Refinements and sub-tools:
         
     | 
| 
      
 11 
     | 
    
         
            +
            require 'refinements/ansiless_string_refinement'
         
     | 
| 
      
 12 
     | 
    
         
            +
            require 'refinements/clip_string_refinement'
         
     | 
| 
      
 13 
     | 
    
         
            +
            require 'refinements/constant_defined_string_refinement'
         
     | 
| 
      
 14 
     | 
    
         
            +
            require 'refinements/array_fit_to_refinement'
         
     | 
| 
      
 15 
     | 
    
         
            +
            require 'hexes'
         
     | 
| 
      
 16 
     | 
    
         
            +
            require 'prog'
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            ## Core gem components:
         
     | 
| 
      
 19 
     | 
    
         
            +
            require 'scryglass/config'
         
     | 
| 
      
 20 
     | 
    
         
            +
            require 'scryglass/ro'
         
     | 
| 
      
 21 
     | 
    
         
            +
            require 'scryglass/ro_builder'
         
     | 
| 
      
 22 
     | 
    
         
            +
            require 'scryglass/session'
         
     | 
| 
      
 23 
     | 
    
         
            +
            require 'scryglass/view_wrapper'
         
     | 
| 
      
 24 
     | 
    
         
            +
            require 'scryglass/view_panel'
         
     | 
| 
      
 25 
     | 
    
         
            +
            require 'scryglass/tree_panel'
         
     | 
| 
      
 26 
     | 
    
         
            +
            require 'scryglass/lens_panel'
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
            ## Testing and Demoing:
         
     | 
| 
      
 29 
     | 
    
         
            +
            require 'example_material.rb'
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
            module Scryglass
         
     | 
| 
      
 32 
     | 
    
         
            +
              HELP_SCREEN = <<~'HELPSCREENPAGE'
         
     | 
| 
      
 33 
     | 
    
         
            +
                      q : Quit Scry                               ? : Cycle help panels (1/2)
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                BASIC NAVIGATION: · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
         
     | 
| 
      
 36 
     | 
    
         
            +
                  ·                                                                               ·
         
     | 
| 
      
 37 
     | 
    
         
            +
                  ·   UP / DOWN : Navigate (You can type a number first)                          ·
         
     | 
| 
      
 38 
     | 
    
         
            +
                  ·   RIGHT     : Expand   current or selected row(s)                             ·
         
     | 
| 
      
 39 
     | 
    
         
            +
                  ·   LEFT      : Collapse current or selected row(s)                             ·
         
     | 
| 
      
 40 
     | 
    
         
            +
                  ·                                                                               ·
         
     | 
| 
      
 41 
     | 
    
         
            +
                  ·   ENTER : Close Scry, returning current or selected object(s) (Key or Value)  ·
         
     | 
| 
      
 42 
     | 
    
         
            +
                  ·                                                                               ·
         
     | 
| 
      
 43 
     | 
    
         
            +
                  · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                INSPECTING WITH LENS VIEW:  · · · · · · · · · · · · · ·
         
     | 
| 
      
 46 
     | 
    
         
            +
                  ·                                                   ·
         
     | 
| 
      
 47 
     | 
    
         
            +
                  ·   SPACEBAR : Toggle Lens View                     ·
         
     | 
| 
      
 48 
     | 
    
         
            +
                  ·          l : Cycle through lens types             ·
         
     | 
| 
      
 49 
     | 
    
         
            +
                  ·          L : Toggle subject  (Key/Value of row)   ·
         
     | 
| 
      
 50 
     | 
    
         
            +
                  ·                                                   ·
         
     | 
| 
      
 51 
     | 
    
         
            +
                  · · · · · · · · · · · · · · · · · · · · · · · · · · ·
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                MORE NAVIGATION:  · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
         
     | 
| 
      
 54 
     | 
    
         
            +
                  ·                                                                       ·
         
     | 
| 
      
 55 
     | 
    
         
            +
                  ·      [w]    :  Move view window           0 : Reset view location     ·
         
     | 
| 
      
 56 
     | 
    
         
            +
                  ·   [a][s][d]   (ALT increases speed)      (Press again: reset cursor)  ·
         
     | 
| 
      
 57 
     | 
    
         
            +
                  ·                                                                       ·
         
     | 
| 
      
 58 
     | 
    
         
            +
                  · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
         
     | 
| 
      
 59 
     | 
    
         
            +
              HELPSCREENPAGE
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
              HELP_SCREEN_ADVANCED = <<~'HELPSCREENADVANCEDPAGE'
         
     | 
| 
      
 62 
     | 
    
         
            +
                      q : Quit Scry                               ? : Cycle help panels (2/2)
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                ADVANCED: · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
         
     | 
| 
      
 65 
     | 
    
         
            +
                  ·  DIGGING DEEPER:                                                                    ·
         
     | 
| 
      
 66 
     | 
    
         
            +
                  ·    For current or selected row(s)...                                                ·
         
     | 
| 
      
 67 
     | 
    
         
            +
                  ·      @ : Build instance variable sub-rows                                           ·
         
     | 
| 
      
 68 
     | 
    
         
            +
                  ·      . : Build ActiveRecord association sub-rows                                    ·
         
     | 
| 
      
 69 
     | 
    
         
            +
                  ·      ( : Attempt to smart-build sub-rows, if Enumerable. Usually '@' is preferable. ·
         
     | 
| 
      
 70 
     | 
    
         
            +
                  ·                                                                                     ·
         
     | 
| 
      
 71 
     | 
    
         
            +
                  ·  SELECTING ROWS:                                                                    ·
         
     | 
| 
      
 72 
     | 
    
         
            +
                  ·    * : Select/Deselect ALL rows                                                     ·
         
     | 
| 
      
 73 
     | 
    
         
            +
                  ·    | : Select/Deselect every sibling row under the same parent row                  ·
         
     | 
| 
      
 74 
     | 
    
         
            +
                  ·    - : Select/Deselect current row                                                  ·
         
     | 
| 
      
 75 
     | 
    
         
            +
                  ·                                                                                     ·
         
     | 
| 
      
 76 
     | 
    
         
            +
                  ·  TEXT SEARCH:                                                                       ·
         
     | 
| 
      
 77 
     | 
    
         
            +
                  ·    / : Begin a text search (in tree view)                                           ·
         
     | 
| 
      
 78 
     | 
    
         
            +
                  ·    n : Move to next search result                                                   ·
         
     | 
| 
      
 79 
     | 
    
         
            +
                  ·                                                                                     ·
         
     | 
| 
      
 80 
     | 
    
         
            +
                  · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
         
     | 
| 
      
 81 
     | 
    
         
            +
              HELPSCREENADVANCEDPAGE
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
              def self.config
         
     | 
| 
      
 84 
     | 
    
         
            +
                @config ||= Config.new
         
     | 
| 
      
 85 
     | 
    
         
            +
              end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
              def self.reset_config
         
     | 
| 
      
 88 
     | 
    
         
            +
                @config = Config.new
         
     | 
| 
      
 89 
     | 
    
         
            +
              end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
              def self.configure
         
     | 
| 
      
 92 
     | 
    
         
            +
                yield(config)
         
     | 
| 
      
 93 
     | 
    
         
            +
              end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
              def self.load_silently
         
     | 
| 
      
 96 
     | 
    
         
            +
                begin
         
     | 
| 
      
 97 
     | 
    
         
            +
                  add_kernel_methods
         
     | 
| 
      
 98 
     | 
    
         
            +
                  { success: true, error: nil }
         
     | 
| 
      
 99 
     | 
    
         
            +
                rescue => e
         
     | 
| 
      
 100 
     | 
    
         
            +
                  { success: false, error: e }
         
     | 
| 
      
 101 
     | 
    
         
            +
                end
         
     | 
| 
      
 102 
     | 
    
         
            +
              end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
              def self.load
         
     | 
| 
      
 105 
     | 
    
         
            +
                caller_path = caller_locations.first.path
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                silent_load_result = Scryglass.load_silently
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                if silent_load_result[:success]
         
     | 
| 
      
 110 
     | 
    
         
            +
                  puts "(Scryglass is loaded, from `#{caller_path}`. Use `Scryglass.help` for help getting started)"
         
     | 
| 
      
 111 
     | 
    
         
            +
                else
         
     | 
| 
      
 112 
     | 
    
         
            +
                  puts "(Scryglass failed to load, from `#{caller_path}` " \
         
     | 
| 
      
 113 
     | 
    
         
            +
                       "getting `#{silent_load_result[:error].message}`)"
         
     | 
| 
      
 114 
     | 
    
         
            +
                end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                silent_load_result
         
     | 
| 
      
 117 
     | 
    
         
            +
              end
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
              def self.help
         
     | 
| 
      
 120 
     | 
    
         
            +
                console_help = <<~"CONSOLE_HELP" # Bolded with \e[1m
         
     | 
| 
      
 121 
     | 
    
         
            +
                  \e[1m
         
     | 
| 
      
 122 
     | 
    
         
            +
                  |  To prep Scryglass, call `Scryglass.load`
         
     | 
| 
      
 123 
     | 
    
         
            +
                  |  (Or add it to .irbrc & .pryrc)
         
     | 
| 
      
 124 
     | 
    
         
            +
                  |
         
     | 
| 
      
 125 
     | 
    
         
            +
                  |  To start a Scry Session, call:
         
     | 
| 
      
 126 
     | 
    
         
            +
                  |  >   scry my_object   OR
         
     | 
| 
      
 127 
     | 
    
         
            +
                  |  >   my_object.scry
         
     | 
| 
      
 128 
     | 
    
         
            +
                  |
         
     | 
| 
      
 129 
     | 
    
         
            +
                  |  To resume the previous session:   (in same console session)
         
     | 
| 
      
 130 
     | 
    
         
            +
                  |  >   scry   OR
         
     | 
| 
      
 131 
     | 
    
         
            +
                  |  >   scry_resume (if you're in a breakpoint pry)
         
     | 
| 
      
 132 
     | 
    
         
            +
                  \e[0m
         
     | 
| 
      
 133 
     | 
    
         
            +
                CONSOLE_HELP
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
                puts console_help
         
     | 
| 
      
 136 
     | 
    
         
            +
              end
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
              private
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
              def self.add_kernel_methods
         
     | 
| 
      
 141 
     | 
    
         
            +
                Kernel.module_eval do
         
     | 
| 
      
 142 
     | 
    
         
            +
                  def scry(arg = nil, actions = nil) # `actions` can't be a keyword arg due
         
     | 
| 
      
 143 
     | 
    
         
            +
                    #   to this ruby issue: https://bugs.ruby-lang.org/issues/8316
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                    receiver = self unless to_s == 'main'
         
     | 
| 
      
 146 
     | 
    
         
            +
                    # As in: `receiver.scry`,
         
     | 
| 
      
 147 
     | 
    
         
            +
                    #   and no receiver means scry was called on 'main', (unless self is
         
     | 
| 
      
 148 
     | 
    
         
            +
                    #   different in the because you've pry'd into something!)
         
     | 
| 
      
 149 
     | 
    
         
            +
             
     | 
| 
      
 150 
     | 
    
         
            +
                    seed_object = arg || receiver
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
                    $scry_session = Scryglass::Session.new(seed_object) if seed_object
         
     | 
| 
      
 153 
     | 
    
         
            +
                    # If it's been given an arg or receiver, create new session!
         
     | 
| 
      
 154 
     | 
    
         
            +
                    # The global variable is purposeful, and not accessible outside of
         
     | 
| 
      
 155 
     | 
    
         
            +
                    #   the one particular console instance.
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
                    scry_resume(actions) # Pick up the new or previous session
         
     | 
| 
      
 158 
     | 
    
         
            +
                  end
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
                  # For the user, this is mainly just for pry sessions where `self` isn't `main`
         
     | 
| 
      
 161 
     | 
    
         
            +
                  def scry_resume(actions = nil)
         
     | 
| 
      
 162 
     | 
    
         
            +
                    Scryglass.config.validate!
         
     | 
| 
      
 163 
     | 
    
         
            +
             
     | 
| 
      
 164 
     | 
    
         
            +
                    no_previous_session = $scry_session.nil?
         
     | 
| 
      
 165 
     | 
    
         
            +
                    if no_previous_session
         
     | 
| 
      
 166 
     | 
    
         
            +
                      raise ArgumentError,
         
     | 
| 
      
 167 
     | 
    
         
            +
                            '`scry` requires either an argument, a receiver, or a past' \
         
     | 
| 
      
 168 
     | 
    
         
            +
                            'session to reopen. try `Scryglass.help`'
         
     | 
| 
      
 169 
     | 
    
         
            +
                    end
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
                    Hexes.stdout_rescue do
         
     | 
| 
      
 172 
     | 
    
         
            +
                      $scry_session.run_scry_ui(actions: actions)
         
     | 
| 
      
 173 
     | 
    
         
            +
                    end
         
     | 
| 
      
 174 
     | 
    
         
            +
                  end
         
     | 
| 
      
 175 
     | 
    
         
            +
                end
         
     | 
| 
      
 176 
     | 
    
         
            +
              end
         
     | 
| 
      
 177 
     | 
    
         
            +
            end
         
     |